repo: janusweb
action: commit
revision: 
path_from: 
revision_from: dfd40daad80f9b8cee7e29eec3bd7f4be0e548dc:
path_to: 
revision_to: 
git.thebackupbox.net
janusweb
git clone git://git.thebackupbox.net/janusweb
commit dfd40daad80f9b8cee7e29eec3bd7f4be0e548dc
Author: James Baicoianu 
Date:   Fri Jun 26 08:57:54 2020 -0700

    Full tracked controller support

diff --git a/scripts/janusxrplayer.js b/scripts/janusxrplayer.js
index d79c635eb03496dd0211fb2568fd5f11f0f6cae7..
index ..12ba6828e7aa0ee43206407cd1502e8a40de0ae3 100644
--- a/scripts/janusxrplayer.js
+++ b/scripts/janusxrplayer.js
@@ -1,4 +1,4 @@
-elation.require(['engine.things.generic'], function() {
+elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profiles'], function() {
   elation.component.add('engine.things.janusxrplayer', function() {
     this.postinit = function() {
       elation.engine.things.janusxrplayer.extendclass.postinit.call(this);
@@ -9,6 +9,9 @@ elation.require(['engine.things.generic'], function() {
         console.log('xr player gets a session', this.session);
         let sess = this.session;
         sess.addEventListener('inputsourceschange', (ev) => { this.processInputSources(this.session.inputSources);});
+        sess.addEventListener('selectstart', (ev) => { this.handleSelectStart(ev);});
+        sess.addEventListener('selectend', (ev) => { this.handleSelectEnd(ev);});
+        sess.addEventListener('select', (ev) => { this.handleSelect(ev);});
       }
       this.inputs = {};
       initJanusObjects();
@@ -16,6 +19,8 @@ elation.require(['engine.things.generic'], function() {
         head: this.createObject('trackedplayer_head', { })
       };
       this.camera = this.spawn('camera', this.name + '_camera', { position: [0,0,0] } );
+
+      this.teleporter = player.createObject('locomotion_teleporter', { xrplayer: this.getProxyObject() });
     }
     this.setSession = function(session) {
       this.session = session;
@@ -36,6 +41,10 @@ elation.require(['engine.things.generic'], function() {
     this.processInputSources = function(inputs) {
       for (let i = 0; i < inputs.length; i++) {
         let input = inputs[i];
+        let id = 'hand_' + input.handedness;
+        if (this.trackedobjects[id]) {
+          this.trackedobjects[id].updateDevice(input);
+        }
         this.inputs[input.handedness] = input;
       }
     }
@@ -44,7 +53,7 @@ elation.require(['engine.things.generic'], function() {
       let xrReferenceFrame = this.getReferenceFrame();
       let viewerPose = xrFrame.getViewerPose(xrReferenceFrame);
       if (viewerPose) {
-        this.trackedobjects['head'].updatePose(viewerPose);
+        this.trackedobjects['head'].updatePose(viewerPose, xrFrame, xrReferenceFrame);
       } else {
         // You've lost your head!  Should we do something about it?
       }
@@ -52,26 +61,48 @@ elation.require(['engine.things.generic'], function() {
         let input = this.inputs[k];
         let id = 'hand_' + k;
         if (!this.trackedobjects[id]) {
-          //this.trackedobjects[id] = this.spawn('janusxrplayer_trackedobject', null, { });
           this.trackedobjects[id] = this.createObject('trackedplayer_hand', { device: input });
         }
         let pose = xrFrame.getPose(input.gripSpace, xrReferenceFrame);
         if (pose) {
-          this.trackedobjects[id].updatePose(pose);
+          let raypose = xrFrame.getPose(input.targetRaySpace, xrReferenceFrame);
+          this.trackedobjects[id].updatePose(pose, xrFrame, xrReferenceFrame, raypose);
           this.trackedobjects[id].visible = true;
         } else {
           this.trackedobjects[id].visible = false;
         }
       }
-      this.position.copy(player.pos);
+      //this.position.copy(player.pos);
       //player.orientation.copy(this.orientation);
       player.head.orientation.copy(this.trackedobjects['head'].orientation);
       player.head.position.copy(this.trackedobjects['head'].position)
       this.dispatchEvent({type: 'xrframe', data: xrReferenceFrame});
+//this.orientation.copy(player.orientation);
     }
     this.getReferenceFrame = function() {
       return this.engine.client.xrspace;
     }
+    this.handleSelectStart = function(ev) {
+      console.log('select started', ev);
+      let handid = 'hand_' + ev.inputSource.handedness;
+      if (this.trackedobjects[handid]) {
+        this.trackedobjects[handid].handleSelectStart(ev);
+      }
+    }
+    this.handleSelectEnd = function(ev) {
+      console.log('select ended', ev);
+      let handid = 'hand_' + ev.inputSource.handedness;
+      if (this.trackedobjects[handid]) {
+        this.trackedobjects[handid].handleSelectEnd(ev);
+      }
+    }
+    this.handleSelect = function(ev) {
+      console.log('selected', ev);
+      let handid = 'hand_' + ev.inputSource.handedness;
+      if (this.trackedobjects[handid]) {
+        this.trackedobjects[handid].handleSelect(ev);
+      }
+    }
   }, elation.engine.things.janusbase);
   elation.component.add('engine.things.janusxrplayer_trackedobject', function() {
     this.create = function() {
@@ -84,7 +115,7 @@ elation.require(['engine.things.generic'], function() {
       });
 */
     },
-    this.updatePose = function(pose) {
+    this.updatePose = function(pose, xrFrame, xrReferenceFrame) {
       this.position.copy(pose.transform.position);
       this.orientation.copy(pose.transform.orientation);
     }
@@ -146,7 +177,7 @@ elation.require(['engine.things.generic'], function() {
         });
 */
       },
-      updatePose(pose) {
+      updatePose(pose, xrFrame, xrReferenceFrame) {
         if (pose) {
           let pos = pose.transform.position;
           if (pos) {
@@ -165,54 +196,76 @@ elation.require(['engine.things.generic'], function() {
     janus.registerElement('trackedplayer_hand', {
       device: null,
       create() {
-        this.handle = this.createObject('object', {
-          id: 'sphere',
-          col: '#333',
-          scale: V(.03, .03, .1),
-          pos: V(0,0,.05),
-          rotation: V(15,0,0)
-        });
-this.handle.col = 'red';
-    /*
-        this.ring = this.createObject('object', {
-          id: 'torus',
-          scale: V(.075),
-          pos: V(0,0,0),
-          col: '#333',
-          rotation: V(30,0,0),
-        });
-        this.ring.opacity = .1;
-    */
-        this.face = this.createObject('object', {
-          pos: V(0, 0.02, .01),
-          rotation: V(5,0,0)
+        this.gamepad = this.device.gamepad;
+        if (this.gamepad) {
+          this.createMotionController(this.device);
+          janus.engine.systems.controls.addVirtualGamepad(this.device.gamepad);
+        }
+        this.pointer = this.parent.createObject('trackedplayer_controller_laser', {
+          hand: this
         });
-        this.underface = this.face.createObject('object', {
-          id: 'cylinder',
-          scale: V(.045, .0075, .045),
-          pos: V(0,-.0075,0),
-          col: '#333',
+      },
+      updateDevice(device) {
+        if (device !== this.device) {
+          this.device = device;
+          if (this.device.gamepad !== this.gamepad) {
+            janus.engine.systems.controls.removeVirtualGamepad(this.gamepad);
+            janus.engine.systems.controls.addVirtualGamepad(this.device.gamepad);
+            this.gamepad = this.device.gamepad;
+          }
+        }
+      },
+      stopInput() {
+        janus.engine.systems.controls.removeVirtualGamepad(this.device.gamepad);
+      },
+      async createMotionController(xrInputSource) {
+        let uri = 'https://baicoianu.com/~bai/webxr-input-profiles/packages/assets/dist/profiles';
+        const { profile, assetPath } = await elation.janusweb.external.webxrinput.fetchProfile(xrInputSource, uri);
+        const motionController = new elation.janusweb.external.webxrinput.MotionController(xrInputSource, profile, assetPath);
+        this.motionController = motionController;
+
+        this.controller = this.createObject('object', {
+          id: motionController.assetUrl,
         });
-        this.buttons = [];
-        this.createButtons();
+      },
+      updateMotionControllerModel(motionController) {
+        // Update the 3D model to reflect the button, thumbstick, and touchpad state
+        const motionControllerRoot = this.objects['3d'].getObjectByName('root');

-    /*
-        this.pointer = this.createObject('trackedplayer_controller_laser', {
-          positions: [V(0,0,0), V(0,0,-1000)],
-          col: 'red'
+        if (!motionControllerRoot) {
+          return;
+        }
+
+        Object.values(motionController.components).forEach((component) => {
+          for (let k in component.visualResponses) {
+            let visualResponse = component.visualResponses[k];
+            // Find the topmost node in the visualization
+            const valueNode = motionControllerRoot.getObjectByName(visualResponse.valueNodeName);
+
+            // Calculate the new properties based on the weight supplied
+            if (visualResponse.valueNodeProperty === 'visibility') {
+              valueNode.visible = visualResponse.value;
+            } else if (visualResponse.valueNodeProperty === 'transform') {
+              const minNode = motionControllerRoot.getObjectByName(visualResponse.minNodeName);
+              const maxNode = motionControllerRoot.getObjectByName(visualResponse.maxNodeName);
+
+              THREE.Quaternion.slerp(
+                minNode.quaternion,
+                maxNode.quaternion,
+                valueNode.quaternion,
+                visualResponse.value
+              );
+
+              valueNode.position.lerpVectors(
+                minNode.position,
+                maxNode.position,
+                visualResponse.value
+              );
+            }
+          };
         });
-    */
-    /*
-    if (this.device.hand == 'left') {
-    this.createObject('fpscounter', {
-      pos: V(),
-      scale: V(.25),
-      rotation: V(-90, 0, 0)
-    });
-    }
-    */
       },
-      updatePose(pose) {
+      updatePose(pose, xrFrame, xrReferenceFrame, raypose) {
         if (pose) {
           let pos = pose.transform.position;
           this.pos.copy(pos);
@@ -244,6 +297,26 @@ this.handle.col = 'red';
             }
           }
 */
+          if (this.device.hand) {
+            if (!this.handmodel) {
+              console.log('new hand!', this.device.hand, this.device);
+              this.handmodel = this.createObject('object', {
+                id: 'cube',
+                scale: V(.2, .075, .025),
+                col: 'green'
+              });
+            }
+            let handpos = xrFrame.getPose(this.device.hand[XRHand.WRIST], xrReferenceFrame);
+            this.handmodel.pos = handpos.transform.position;
+            this.handmodel.orientation.copy(handpos.transform.orientation);
+          }
+          if (this.motionController) {
+            this.updateMotionControllerModel(this.motionController);
+          }
+          if (this.pointer && raypose) {
+            this.pointer.pos.copy(raypose.transform.position);
+            this.pointer.orientation.copy(raypose.transform.orientation);
+          }
         }
       },
       createButtons() {
@@ -297,7 +370,21 @@ this.handle.col = 'red';
             ha.pulse(value, duration);
           });
         }
-      }
+      },
+      raycast(dir, pos) {
+        if (this.pointer) {
+          return this.pointer.raycast(dir, pos);
+        }
+      },
+      handleSelectStart(ev) {
+        this.pointer.handleSelectStart(ev);
+      },
+      handleSelectEnd(ev) {
+        this.pointer.handleSelectEnd(ev);
+      },
+      handleSelect(ev) {
+        this.pointer.handleSelect(ev);
+      },
     });
     janus.registerElement('trackedplayer_controller_button', {
       size: .1,
@@ -373,25 +460,112 @@ this.handle.col = 'red';
     });

     janus.registerElement('trackedplayer_controller_laser', {
+      hand: null,
       create() {
         this.lasercolor = V(1,0,0);
         this.laser = this.createObject('linesegments', {
           positions: [V(0,0,0), V(0,0,-1000)],
+          opacity: .5,
           col: this.lasercolor
         });
         this.raycaster = this.createObject('raycaster', {
+          //rotation: V(-45,0,0),
         });
         this.raycaster.addEventListener('raycastenter', (ev) => this.handleRaycastEnter(ev));
         this.raycaster.addEventListener('raycastleave', (ev) => this.handleRaycastLeave(ev));
       },
       handleRaycastEnter(ev) {
-        this.laser.color = V(0,1,0);
-        this.laser.updateColor();
+        let proxyobj = ev.data.object,
+            obj = proxyobj._target;
+
+        if (this.activeobject) {
+          this.engine.client.view.proxyEvent({
+            type: "mouseout",
+            element: this.activeobject.objects['3d'],
+            //relatedTarget: oldpickedthing,
+            data: ev.data.intersection,
+            clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point?
+            clientY: 0,
+          }, this.activeobject.objects['3d']);
+        }
+        this.activeobject = proxyobj;
+
+        if (obj && proxyobj && (
+              obj.onclick ||
+              elation.events.hasEventListener(obj, 'click') ||
+              elation.events.hasEventListener(proxyobj, 'click') ||
+              obj.onmousedown ||
+              elation.events.hasEventListener(obj, 'mousedown') ||
+              elation.events.hasEventListener(proxyobj, 'mousedown')
+            )) {
+          this.laser.col.setRGB(0,1,0);
+          this.laser.opacity = .4;
+          this.laser.updateColor();
+        } else {
+          this.laser.col.setRGB(1,0,0);
+          this.laser.opacity = .1;
+          this.laser.updateColor();
+        }
+
+        let evdata = {
+          type: "mouseover",
+          element: ev.data.intersection.mesh,
+          //relatedTarget: oldpickedthing,
+          data: ev.data.intersection,
+          clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point?
+          clientY: 0,
+        };
+        if (this.hand) {
+          evdata.data.gamepad = this.hand.device.gamepad;
+        }
+        //elation.events.fire(evdata);
+        this.engine.client.view.proxyEvent(evdata, evdata.element);
       },
       handleRaycastLeave(ev) {
-        this.laser.color = V(0,1,1);
+        this.laser.col.setRGB(0,1,1);
+        this.laser.opacity = .2;
         this.laser.updateColor();
       },
+      handleSelectStart(ev) {
+        if (this.activeobject) {
+          let obj = this.activeobject.objects['3d'];
+          this.engine.client.view.proxyEvent({
+            type: "mousedown",
+            element: obj,
+            //data: ev.data.intersection, // FIXME - need to store intersection data from raycastmove
+            clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point?
+            clientY: 0,
+            button: 0,
+          }, obj);
+        }
+      },
+      handleSelectEnd(ev) {
+        if (this.activeobject) {
+          let obj = this.activeobject.objects['3d'];
+          this.engine.client.view.proxyEvent({
+            type: "mousemove",
+            element: obj,
+            //data: ev.data.intersection, // FIXME - need to store intersection data from raycastmove
+            clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point?
+            clientY: 0,
+            button: 0,
+          }, obj);
+        }
+      },
+      handleSelect(ev) {
+        if (this.activeobject) {
+          let obj = this.activeobject.objects['3d'];
+          console.log('CLICK IT', this.activeobject);
+          this.engine.client.view.proxyEvent({
+            type: "click",
+            element: obj,
+            //data: ev.data.intersection, // FIXME - need to store intersection data from raycastmove
+            clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point?
+            clientY: 0,
+            button: 0,
+          }, obj);
+        }
+      },
     });
   }
 });

-----END OF PAGE-----