diff --git a/js/immersive-pre.js b/js/immersive-pre.js index 07398f5..d752961 100644 --- a/js/immersive-pre.js +++ b/js/immersive-pre.js @@ -39,6 +39,8 @@ if (QueryArgs.getBool("usePolyfill", true)) { let polyfill = new WebXRPolyfill(); } +import { setFrameInfo } from "./util/hitTest.js"; + // XR globals. let xrButton = null; let xrImmersiveRefSpace = null; @@ -296,6 +298,8 @@ function updateInputSources(session, frame, refSpace) { } } } + setFrameInfo(session.inputSources, frame, refSpace); + } function hitTest(inputSource, frame, refSpace) { diff --git a/js/primitive/event.js b/js/primitive/event.js index 9c6a602..a6c3380 100644 --- a/js/primitive/event.js +++ b/js/primitive/event.js @@ -5,6 +5,7 @@ import { SyncObject, updateObject } from "../util/object-sync.js"; import { corelink_message } from "../util/corelink_sender.js"; import { metaroomSyncSender, metaroomWebrtcSender, metaroomEventSender, metaroomInitSender } from "../corelink_handler.js" import { left_controller_trigger, right_controller_trigger } from "../util/input_event_handler.js" +import { addToPressStates } from "../render/core/renderListScene.js" export function initSelfAvatar(id) { if (!window.avatars) { @@ -121,6 +122,7 @@ export function init() { console.log("left already"); } else { initAvatar(payload["user"]); + addToPressStates(payload["user"]); } } // } @@ -363,11 +365,11 @@ export function init() { case "lefttrigger": //left trigger // console.log("call left_controller_trigger", json["state"]['operation']); - left_controller_trigger(json["state"]['operation']); + left_controller_trigger(json["state"]['operation'],json["uid"]); break; case "righttrigger": //left trigger - right_controller_trigger(json["state"]['operation']); + right_controller_trigger(json["state"]['operation'],json["uid"]); break; default: break; diff --git a/js/render/core/renderList.js b/js/render/core/renderList.js index 77bc6b5..389adf3 100644 --- a/js/render/core/renderList.js +++ b/js/render/core/renderList.js @@ -2,6 +2,8 @@ import { CG, Matrix } from "./CG.js"; import { ImprovedNoise } from "../math/improvedNoise.js"; +import { buttonState, controllerMatrix, leftHandState, rightHandState, pressStates } from "../core/renderListScene.js"; +import { pHitTest, pHitTestNew } from "../../util/hitTest.js"; export let m = new Matrix(); @@ -107,6 +109,25 @@ let RenderList = function () { return this; }; + this.hitEvent = (f, isShared) => { + this.hit = f; + this.shared = isShared; + return this; + } + + this.group = (n) => { + if (groups[n] === undefined) { + groups[n] = [this]; + this.groupInd = 0; + } + else if (!groups[n].includes(this)) { + groups[n].push(this); + this.groupInd = groups[n].length - 1; + } + this.groupId = n; + return this; + } + this.setBaseTexture = (url) => { this.texture = url; } @@ -168,12 +189,124 @@ let RenderList = function () { this.beginBuild = () => ((n = 0), (this.num = 0)); this.endBuild = () => { /* do something */ + for (let i=0; i { + this.add = (shape, name) => { if (items[n]) items[n].init(); else items[n] = new Item(); items[n].shape = shape; items[n].matrix = m.value().slice(); + if (name) items[n].mesh = name; this.num++; return items[n++]; }; @@ -245,6 +378,7 @@ let RenderList = function () { item.sz ) ); + item.matrix = mat; switch (item.type) { // render list case 1: { @@ -322,7 +456,7 @@ let RenderList = function () { item.sz ) ); - + items[i].matrix = mat; switch (item.type) { // render list case 1: { @@ -366,16 +500,21 @@ let RenderList = function () { this.program = null; this.num = 0; + this.getItems = () => { + return items; + } + let items = [], n = 0, improvedNoise = new ImprovedNoise(); + let groups = []; }; RenderList.prototype.mMesh = function (V) { return this.add(V); }; RenderList.prototype.mCube = function () { - return this.add(CG.cube); + return this.add(CG.cube, "cube"); }; RenderList.prototype.mPoly4 = function (V) { return this.add(CG.createPoly4Vertices(V)); @@ -390,10 +529,10 @@ RenderList.prototype.mSquare = function () { return this.add(CG.quad); }; RenderList.prototype.mSphere = function () { - return this.add(CG.sphere); + return this.add(CG.sphere, "sphere"); }; RenderList.prototype.mCylinder = function () { - return this.add(CG.cylinder); + return this.add(CG.cylinder, "cylinder"); }; RenderList.prototype.mRoundedCylinder = function () { return this.add(CG.roundedCylinder); diff --git a/js/render/core/renderListScene.js b/js/render/core/renderListScene.js index f0f3c13..a15dd0f 100644 --- a/js/render/core/renderListScene.js +++ b/js/render/core/renderListScene.js @@ -2,6 +2,8 @@ import { mainScene } from "../scenes/mainScene.js"; import { corelink_event } from "../../util/corelink_sender.js"; +export let pressStates = []; +export let leftHandState = "released", rightHandState = "released"; export let viewMatrix = [], time = 0; window.isPressed = false; window.isDragged = false; @@ -36,7 +38,7 @@ export let updateController = (avatar, buttonInfo) => { } }; -export let onPress = (hand, button) => { +export let onPress = (hand, button, id) => { window.isPressed = true; window.isReleased = false; @@ -46,24 +48,35 @@ export let onPress = (hand, button) => { //ZH // console.log("handleSelect"); // corelink_event({ it: "lefttrigger", op: "press" }); + updatePressState(id, hand, "pressed"); + if (hand === "left") leftHandState = "pressed"; + else if (hand === "right") rightHandState = "pressed"; }; -export let onDrag = (hand, button) => { +export let onDrag = (hand, button, id) => { // console.log("onDrag", hand, "button", button, isPressed, isReleased, isDragged); if (window.isPressed && window.justReleased) { window.isDragged = true; window.isReleased = false; window.isPressed = false; + + updatePressState(id, hand, "dragged"); + if (hand === "left") leftHandState = "dragged"; + else if (hand === "right") rightHandState = "dragged"; } }; -export let onRelease = (hand, button) => { +export let onRelease = (hand, button, id) => { if (window.isDragged) { window.isReleased = true; window.isPressed = false; window.isDragged = false; console.log("onRelease", hand, "button", button, window.isPressed, window.isReleased, window.isDragged); + + updatePressState(id, hand, "released"); + if (hand === "left") leftHandState = "released"; + else if (hand === "right") rightHandState = "released"; } //ZH @@ -76,6 +89,22 @@ export let getViews = (views) => { for (let view of views) viewMatrix.push(view.viewMatrix); }; +export let addToPressStates = (id) => { + if (!pressStates.includes(id)) { + if (pressStates[0] === undefined) pressStates[0] = {id: id, lState: "released", rState: "released"}; + else pressStates.push({id: id, lState: "released", rState: "released"}); + } +} + +let updatePressState = (id, hand, state) => { + for (let i=0; i 0) { // console.log('-------------------'); + let tmp = []; for (let i = 0; i < renderList.num; i++) { - this._drawRenderListPrimitive(views, ...renderList.endFrame(i)); + //this._drawRenderListPrimitive(views, ...renderList.endFrame(i)); + // console.log(...renderList.endFrame(i)); + tmp[i] = renderList.endFrame(i); + } + renderList.endBuild(); + for (let i = 0; i < renderList.num; i++) { + this._drawRenderListPrimitive(views, ...tmp[i]); // console.log(...renderList.endFrame(i)); } } diff --git a/js/render/scenes/demoHittest.js b/js/render/scenes/demoHittest.js new file mode 100644 index 0000000..62628f8 --- /dev/null +++ b/js/render/scenes/demoHittest.js @@ -0,0 +1,100 @@ +import { m, renderList } from "../core/renderList.js"; +import { time } from "../core/renderListScene.js"; +import { CG } from "../core/CG.js"; + +let dt = 0, startT = -1, val = 0; +let playAnim = false; + +let DemoHittest = function() { + this.background = null; + this.loadGLTF = false; + this.envInd = null; + + this.display = () => { + + //A cube that: when pointed at (aka "hover"): change color to light green + // when input button 3 ("select" in emulator) is held down: change color to dark green + // when clicked: human figure dabs + m.save(); + renderList.mCube().move(.2,1.5,0).size(.05).color(1,1,1).hitEvent(dab); + m.restore(); + + if (playAnim) { + startT = time; + playAnim = false; + } + if (startT > 0) { + dt = 2*(time - startT); + if (0 <= dt && dt <= 1) val = CG.sCurve(dt); + else if (dt <= 2) val = 1 - CG.sCurve(dt-1); + else { + startT = -1; + dt = 0; + val = 0; + } + } + //human figure + m.save(); + m.translate(-0.2, 1.5, 0); + m.scale(.2); + m.save(); + m.translate(0,-.1,0); + m.rotateZ(-1*(val/3)); + m.rotateX(-1*(val/3)); + m.translate(0,.1,0); + renderList.mSphere().size(.1,.15,.1).color(1,1,1); + m.restore(); + renderList.mSphere().move(0,-.5,0).size(.1,.35,.1).color(1,1,1); + m.save(); + m.translate(.1,-.2,0); + m.rotateZ(Math.PI/2 + val/4); + m.rotateX(val/0.75); + m.translate(0,-.1,0); + renderList.mSphere().size(.04,.15,.04).color(1,1,1); + m.translate(0,-.14,0); + m.rotateX(val*2); + m.rotateZ(val/2); + m.translate(0,-.14,0); + renderList.mSphere().size(.04,.15,.04).color(1,1,1); + m.restore(); + m.save(); + m.translate(-.1,-.2,0); + m.rotateZ(-1*(Math.PI/2 + val/4)); + m.rotateX(val/4); + m.translate(0,-.1,0); + renderList.mSphere().size(.04,.15,.04).color(1,1,1); + m.translate(0,-.14,0); + m.translate(0,-.14,0); + renderList.mSphere().size(.04,.15,.04).color(1,1,1); + m.restore(); + m.restore(); + + //Three cubes that are put into one group + //Add "true" as a second param of hitEvent would make that hitEvent also trigger the hitEvents of other objects in the same group + // (In this case all of them should turn light green when either the leftmost or rightmost cube gets pointed at) + //Add "false" (or nothing) makes it not trigger others + m.save(); + renderList.mCube().move(.2,1.2,0).size(.05).color(1,1,1).hitEvent(turnGreen, true).group(1); + renderList.mCube().move(0,1.2,0).size(.05).color(1,1,1).hitEvent(turnGreen, false).group(1); + renderList.mCube().move(-.2,1.2,0).size(.05).color(1,1,1).hitEvent(turnGreen, true).group(1); + m.restore(); + } +} + +let dab = (ev) => { + ev.hitItem.color([0.3, 1, 0.3]); + + if (ev.state === "dragged" && ev.buttonState[3] === true) { + ev.hitItem.color([0,0.5,0]); + } + + if (ev.state === "pressed") { + playAnim = true; + } +} + +let turnGreen = (ev) => { + ev.hitItem.color([0.3, 1, 0.3]); +} + +export let demoHittest = new DemoHittest(); \ No newline at end of file diff --git a/js/render/scenes/mainScene.js b/js/render/scenes/mainScene.js index 9362ce0..5e2c524 100644 --- a/js/render/scenes/mainScene.js +++ b/js/render/scenes/mainScene.js @@ -34,6 +34,7 @@ import { demoPlanets } from "./demoPlanets.js"; import { demoRealSense } from "./demoRealSense.js"; //import { demoSyncTest } from "./demoSyncTest.js"; //import { demoDraw } from "./demoDraw.js"; +//import { demoHittest } from "./demoHittest.js"; let loadGLTF = false; let curDemoEnv = []; @@ -74,6 +75,7 @@ export let mainScene = () => { //if (demoTextState % 2) loadScene(demoText); else stopScene(demoText); //if (demoSyncTestState % 2) loadScene(demoSyncTest); else stopScene(demoSyncTest); //if (demoDrawState % 2) loadScene(demoDraw); else stopScene(demoDraw); + //if (demoHittestState % 2) loadScene(demoHittest); else stopScene(demoHittest); }; function loadScene(demo) { @@ -161,7 +163,7 @@ function showNameTag() { } } -//window.demoNames = "AirText,Bill,Chris,Hands,Ken,Mocap,NoiseGrid,Objects,Particles,Prajval,Planets,Rohail,RealSense,Speak,Text,SyncTest,Draw"; +//window.demoNames = "AirText,Bill,Chris,Hands,Ken,Mocap,NoiseGrid,Objects,Particles,Prajval,Planets,Rohail,RealSense,Speak,Text,SyncTest,Draw,Hittest"; window.demoNames = "Planets,RealSense"; addDemoButtons(window.demoNames); window.addNameField(); diff --git a/js/util/hitTest.js b/js/util/hitTest.js new file mode 100644 index 0000000..5306b2f --- /dev/null +++ b/js/util/hitTest.js @@ -0,0 +1,175 @@ +import { m, renderList } from "../render/core/renderList.js"; +import { buttonState, controllerMatrix } from "../render/core/renderListScene.js"; +import { Ray } from "../render/math/ray.js"; +import { mat4, vec3, vec4 } from "../render/math/gl-matrix.js"; + +let inputSources = []; +let frame = null; +let refSpace = null; + +export let setFrameInfo = (_inputSources, _frame, _refSpace) => { + inputSources = _inputSources; + frame = _frame; + refSpace = _refSpace; +} + +export let pHitTest = (index) => { + let inputSource = inputSources[index]; + + const targetRayPose = frame.getPose( + inputSource.targetRaySpace, + refSpace, + ); + if (!targetRayPose) { + console.log("no targetRayPose"); + return; + } + //console.log(targetRayPose); + //console.log(targetRayPose.transform.matrix); + //console.log(controllerMatrix.right); + let ray = new Ray(targetRayPose.transform.matrix); + //let ray = new Ray(controllerMatrix.right); + //console.log(ray); + let items = renderList.getItems(); + let t = -1, T = 100000; + let hm = null; + for (let i=0; i= 0) { + if (t < T) { + T = t; + hm = items[i]; + } + } + + } + return hm; +} + +export let pHitTestNew = (index, uid) => { + let ray; + if (index === 0) ray = new Ray(window.avatars[uid].rightController.matrix); + if (index === 1) ray = new Ray(window.avatars[uid].leftController.matrix); + + let items = renderList.getItems(); + let t = -1, T = 100000; + let hm = null; + for (let i=0; i= 0) { + if (t < T) { + T = t; + hm = items[i]; + } + } + + } + return hm; +} + +let rayTraceToUnitSphere = ray => { + let B = vec3.dot(ray.V, ray.W); + let C = vec3.dot(ray.V, ray.V) - 1; + return B * B > C ? -B - Math.sqrt(B * B - C) : -1; +} + +let rayTraceToUnitCube = ray => { + let tx1 = (-1-ray.V[0]) / ray.W[0]; + let tx2 = (1-ray.V[0]) / ray.W[0]; + let ty1 = (-1-ray.V[1]) / ray.W[1]; + let ty2 = (1-ray.V[1]) / ray.W[1]; + let tz1 = (-1-ray.V[2]) / ray.W[2]; + let tz2 = (1-ray.V[2]) / ray.W[2]; + let inMax = Math.max(Math.min(tx1,tx2), Math.min(ty1,ty2), Math.min(tz1,tz2)); + let outMin = Math.min(Math.max(tx1,tx2), Math.max(ty1,ty2), Math.max(tz1,tz2)); + /* + if (inMax >= outMin) return -1; + let side = []; + if (inMax === tx1) side = [-1,0,0]; + if (inMax === tx2) side = [1,0,0]; + if (inMax === ty1) side = [0,-1,0]; + if (inMax === ty2) side = [0,1,0]; + if (inMax === tz1) side = [0,0,-1]; + if (inMax === tz2) side = [0,0,1]; + return [inMax, side]; + */ + return inMax < outMin ? inMax : -1; +} + +let rayTraceToCylinder = ray => { + // the cap part + let y1 = (-1 - ray.V[2]) / ray.W[2]; + let y2 = (1 - ray.V[2]) / ray.W[2]; + let tCapIn = Math.min(y1, y2); + let tCapOut = Math.max(y1, y2); + + // the tube part + let tTubeIn, tTubeOut; + + if (ray.W[0] !== 0 || ray.W[1] !== 0) { + let A = ray.W[0] * ray.W[0] + ray.W[1] * ray.W[1]; + let B = 2 * ray.V[0] * ray.W[0] + 2 * ray.V[1] * ray.W[1]; + let C = ray.V[0] * ray.V[0] + ray.V[1] * ray.V[1] - 1; + let d = B * B - 4 * A * C; + if (d < 0) return -1; + + let x1 = (-1 * B - Math.sqrt(d)) / (2 * A); + let x2 = (-1 * B + Math.sqrt(d)) / (2 * A); + tTubeIn = Math.min(x1, x2); + tTubeOut = Math.max(x1, x2); + } + else { + let d = ray.V[0] * ray.V[0] + ray.V[1] * ray.V[1]; + if (d > 1) return -1; + + tTubeIn = tCapIn; + tTubeOut = tCapOut; + } + + let inMax = Math.max(tTubeIn, tCapIn); + let outMin = Math.min(tTubeOut, tCapOut); + + return inMax < outMin ? inMax : -1; +} \ No newline at end of file diff --git a/js/util/input_event_handler.js b/js/util/input_event_handler.js index d35972c..2e61da1 100644 --- a/js/util/input_event_handler.js +++ b/js/util/input_event_handler.js @@ -5,7 +5,7 @@ import { onPress, onDrag, onRelease } from "../render/core/renderListScene.js"; // ZH: process controller input, as an event, for local or remote source -export function left_controller_trigger(operation) { +export function left_controller_trigger(operation, id) { // console.log("in left_controller_trigger", operation); if (window["demoSyncTestState"] == 1) { console.log("demoSyncTestState"); @@ -13,26 +13,26 @@ export function left_controller_trigger(operation) { window.posY += 0.1; } if (operation == "press") { - onPress("left", 0); + onPress("left", 0, id); } else if (operation == "drag") { - onDrag("left", 0); + onDrag("left", 0, id); } else if (operation == "release") { - onRelease("left", 0); + onRelease("left", 0, id); } } -export function right_controller_trigger(operation) { - console.log("right_controller_trigger", operation); +export function right_controller_trigger(operation, id) { + //console.log("right_controller_trigger", operation); if (window["demoSyncTestState"] == 1) { console.log("demoSyncTestState"); if (operation == "press") window.posY += 0.1; } if (operation == "press") { - onPress("right", 0); + onPress("right", 0, id); } else if (operation == "drag") { - onDrag("right", 0); + onDrag("right", 0, id); } else if (operation == "release") { - onRelease("right", 0); + onRelease("right", 0, id); } } \ No newline at end of file