UNPKG

janusweb

Version:
964 lines (925 loc) 36.9 kB
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); this.defineProperties({ session: { type: 'object' }, xrview: { type: 'object' } }); this.inputs = {}; initJanusObjects(); this.trackedobjects = { 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() }); if (this.session) { console.log('xr player got a new session', this.session); this.setSession(this.session, this.xrview); } } this.setSession = function(session, view) { this.session = session; console.log('xr player session changed', this.session, view); this.inputs = {}; if (session) { session.addEventListener('inputsourceschange', (ev) => { this.processInputSources(this.session.inputSources);}); session.addEventListener('selectstart', (ev) => { this.handleSelectStart(ev);}); session.addEventListener('selectend', (ev) => { this.handleSelectEnd(ev);}); session.addEventListener('select', (ev) => { this.handleSelect(ev);}); if (this.session.inputSources && this.session.inputSources.length > 0) { this.processInputSources(this.session.inputSources); } } else { this.resetOrientation(); } this.engine.systems.sound.setActiveListener(this.trackedobjects.head._target.objects['3d']); if (session) { player.cursor_visible = false; } else { player.cursor_visible = room.cursor_visible; } this.xrview = view; if (view && this.camera) { setTimeout(() => { view.setactivething(this); }, 100); } } this.resetOrientation = function() { player.pos.copy(this.position); //player.orientation.copy(this.orientation); player.head.orientation.set(0,0,0,1); player.head.position.set(0,0,0); } 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; } } this.updateXR = function(xrFrame) { if (!xrFrame) return; let xrReferenceFrame = this.getReferenceFrame(); let viewerPose = xrFrame.getViewerPose(xrReferenceFrame); if (viewerPose) { this.trackedobjects['head'].updatePose(viewerPose, xrFrame, xrReferenceFrame); } else { // You've lost your head! Should we do something about it? } let hasControllers = false; for (let k in this.inputs) { let input = this.inputs[k]; let id = 'hand_' + k; if (!this.trackedobjects[id]) { this.trackedobjects[id] = this.createObject('trackedplayer_hand', { device: input }); } let pose = null, raypose = null; if (input.gripSpace) { pose = xrFrame.getPose(input.gripSpace, xrReferenceFrame); } if (input.targetRaySpace) { raypose = xrFrame.getPose(input.targetRaySpace, xrReferenceFrame); } this.trackedobjects[id].updatePose(pose, xrFrame, xrReferenceFrame, raypose); if (pose) { this.trackedobjects[id].visible = true; hasControllers = true; } else { this.trackedobjects[id].visible = false; } } if (player.gazecaster) { player.gazecaster.enabled = !hasControllers; } //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).sub(player.neck.position).sub(player.torso.position); this.dispatchEvent({type: 'xrframe', data: xrReferenceFrame}); //this.orientation.copy(player.orientation); } this.getReferenceFrame = function() { return this.engine.systems.render.renderer.xr.getReferenceSpace(); } this.handleSelectStart = function(ev) { console.log('select started', ev); let handid = 'hand_' + ev.inputSource.handedness; if (this.trackedobjects[handid]) { this.trackedobjects[handid].handleSelectStart(ev); } // FIXME - probably need to fake a mouse event instead of passing through a select event elation.events.fire({type: 'janus_room_mousedown', element: room._target, event: 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); } // FIXME - probably need to fake a mouse event instead of passing through a select event elation.events.fire({type: 'janus_room_mouseup', element: room._target, event: ev}); } this.handleSelect = function(ev) { console.log('selected', ev); let handid = 'hand_' + ev.inputSource.handedness; if (this.trackedobjects[handid]) { this.trackedobjects[handid].handleSelect(ev); } // FIXME - probably need to fake a mouse event instead of passing through a select event if (room.onClick) { room.onClick(ev); } elation.events.fire({type: 'click', element: room._target, event: ev}); } this.getProxyObject = function(classdef) { if (!this._proxyobject) { this._proxyobject = elation.engine.things.janusobject.extendclass.getProxyObject.call(this, classdef); this._proxyobject._proxydefs = { session: [ 'property', 'session'], xrview: [ 'property', 'xrview'], }; } return this._proxyobject; } }, elation.engine.things.janusbase); elation.component.add('engine.things.janusxrplayer_trackedobject', function() { this.create = function() { /* this.createObject('object', { id: 'cube', scale: V(.05,.1,.05), pos: V(0,.05,0), col: 'red' }); */ }, this.updatePose = function(pose, xrFrame, xrReferenceFrame) { if (pose) { this.position.copy(pose.transform.position); this.orientation.copy(pose.transform.orientation); } } }, elation.engine.things.janusbase); function initJanusObjects() { let deviceMapping = { 'Oculus Touch (Left)': { axes: [ {name: 'thumbstick', indexes: [0, 1]} ], buttons: [ {type: 'thumbstick', name: 'thumbstick', pos: [-.01, 0, 0]}, {type: 'button', name: 'trigger', pos: [0, -.02, -.01], size: .012}, {type: 'button', name: 'grab', pos: [.01, -.02, .04], size: .012}, {type: 'button', name: 'X', pos: [.01, 0, .01], size: .009}, {type: 'button', name: 'Y', pos: [.015, 0, 0], size: .009}, {type: 'button', name: 'menu', pos: [0, 0, .02], size: .005} ] }, 'Oculus Touch (Right)': { axes: [ {name: 'thumbstick', indexes: [0, 1]} ], buttons: [ {type: 'thumbstick', name: 'thumbstick', pos: [.01, 0, 0]}, {type: 'button', name: 'trigger', pos: [0, -.02, -.01], size: .012}, {type: 'button', name: 'grab', pos: [-.01, -.02, .04], size: .012}, {type: 'button', name: 'B', pos: [-.01, 0, .01], size: .009}, {type: 'button', name: 'A', pos: [-.015, 0, 0], size: .009}, {type: 'button', name: 'home', pos: [0, 0, .02], size: .005} ] }, }; janus.registerElement('trackedplayer_head', { device: null, create() { /* this.headmodel = this.createObject('object', { id: 'sphere', //collision_id: 'sphere', //collision_trigger: true, col: 'yellow', scale: V(.2, .3, .2) }); this.hmdmodel = this.createObject('object', { id: 'cube', //collision_id: 'cube', col: '#111', scale: V(.175,.075,.1), pos: V(0, .05, -.05) }); this.mouth = this.createObject('object', { id: 'sphere', col: '#111', scale: V(.15, .05, .05), pos: V(0, -.05, -.075) }); */ }, updatePose(pose, xrFrame, xrReferenceFrame) { if (pose) { let pos = pose.transform.position; if (pos) { this.pos.copy(pos); } let orientation = pose.transform.orientation; if (orientation) { this.orientation.copy(orientation); } } else { this.pos.set(0,0,0); } } }); janus.registerElement('trackedplayer_hand', { device: null, create() { 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.virtualskeleton = this.createObject('trackedplayer_hand_virtualskeleton', { hand: this.device.hand, }); let menupos = (this.device.handedness == 'left' ? V(.01, .1, .1) : V(-.01, 0, .1)), menurot = (this.device.handedness == 'left' ? V(180, 90, 90) : V(180, -90, -90)); this.menu = this.createObject('xrmenu', { scale: V(.25), pos: menupos, rotation: menurot, }); this.menupos = menupos; this.menurot = menurot; this.removeChild(this.menu); player.setHand(this.device.handedness, this.virtualskeleton); }, updateDevice(device) { if (device !== this.device) { this.device = device; this.pointer.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://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@latest/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.loadNewAsset('object', { id: 'blah-' + this.device.handedness, src: motionController.assetUrl }); this.controller = this.createObject('object', { id: 'blah-' + this.device.handedness, cull_face: 'back', visible: true, }); this.controller.addEventListener('load', (ev) => { this.controller.visible = true; }); }, updateMotionControllerModel(motionController) { // Update the 3D model to reflect the button, thumbstick, and touchpad state const motionControllerRoot = this.objects['3d'].getObjectByName('root'); 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); if (!valueNode) continue; // 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); valueNode.quaternion.slerp( minNode.quaternion, maxNode.quaternion, visualResponse.value ); valueNode.position.lerpVectors( minNode.position, maxNode.position, visualResponse.value ); } }; }); }, updatePose(pose, xrFrame, xrReferenceFrame, raypose) { if (pose) { let pos = pose.transform.position; this.pos.copy(pos); let orientation = pose.transform.orientation; this.orientation.copy(orientation); /* if (this.device.axes.length > 0) { let axes = this.device.axes; if (this.thumb) { this.thumb.setXY(axes[0], axes[1]); } } if (this.device.buttons.length > 0) { let numbuttons = this.device.buttons.length; for (let i = 0; i < numbuttons; i++) { let button = this.buttons[i], buttonstate = this.device.buttons[i]; if (button) { if (buttonstate.pressed) { button.setState('pressed'); } else if (buttonstate.touched) { button.setState('touched'); } else { button.setState('default'); } } } } */ if (this.device.hand) { if (!this.skeleton) { console.log('new hand!', this.device.hand, this.device, this); this.skeleton = this.parent.createObject('trackedplayer_hand_skeleton', { hand: this.device.hand, }); player.setHand(this.device.handedness, this.skeleton); } this.skeleton.updatePose(xrFrame, xrReferenceFrame); if (this.controller) this.controller.visible = false; if (this.skeleton) this.skeleton.visible = true; } else { if (this.controller) { this.controller.visible = true; this.controller.opacity = player.cursor_opacity; } if (this.skeleton) { this.skeleton.die(); this.skeleton = false; player.setHand(this.device.handedness, this.virtualskeleton); } } if (this.motionController) { this.updateMotionControllerModel(this.motionController); } } if (this.pointer) { if (raypose) { this.pointer.pos.copy(raypose.transform.position); this.pointer.orientation.copy(raypose.transform.orientation); this.pointer.updateCursorOpacity(); this.pointer.visible = this.visible && this.opacity > 0; } else { this.pointer.visible = false; } } if (this.gamepad && this.gamepad.buttons && this.gamepad.buttons[4]) { let button = this.gamepad.buttons[4]; if (button.pressed) { this.buttonwaspressed = true; } else if (this.buttonwaspressed && !button.pressed) { this.buttonwaspressed = false; if (!this.menu.parent || this.menu.parent === this) { player.appendChild(this.menu); player.worldToLocal(this.pointer.localToWorld(this.menu.pos.set(0,0,-.25))); this.menu.billboard = 'y'; } else { //this.appendChild(this.menu); player.removeChild(this.menu); this.menu.billboard = ''; this.menu.pos.copy(this.menupos); //this.menu.visible = false; } } } }, createButtons() { if (this.device) { let key = 'Oculus Touch (' + this.device.handedness + ')'; //Object.keys(deviceMapping).find(id => this.device.id.startsWith(id)); let cfg = deviceMapping[key]; let gamepad = this.device.gamepad; if (gamepad) { let controllerbuttons = gamepad.buttons; if (cfg) { for (let i = 0; i < controllerbuttons.length; i++) { let button = controllerbuttons[i], buttoncfg = cfg.buttons[i] || {}, buttontype = buttoncfg.type || 'button', buttonpos = (buttoncfg.pos ? V(buttoncfg.pos[0], buttoncfg.pos[1], buttoncfg.pos[2]) : V()), buttonsize = buttoncfg.size || .0125; this.buttons[i] = this.face.createObject('trackedplayer_controller_' + buttontype, { pos: buttonpos, size: buttonsize, }); if (buttontype == 'thumbstick') { // FIXME - this should respect the axis index configuration this.thumb = this.buttons[i]; } } } else { this.thumb = this.createObject('trackedplayer_controller_thumbstick', { size: .02, pos: V(-.01,.015,.01), col: '#666', rotation: V(5,0,0) }); let numbuttons = controllerbuttons.length; for (let i = 0; i < controllerbuttons.length; i++) { if (!this.buttons[i]) { this.buttons[i] = this.face.createObject('trackedplayer_controller_button', { pos: V(.015 * i - (numbuttons * .015 / 2), .025, -.01), size: .0125, }); } } } } } }, pulse(value, duration) { if (this.device.hapticActuators) { this.device.hapticActuators.forEach(ha => { 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, state: 'default', states: { default: '#666', touched: '#888', pressed: '#6f6', highlighted: '#f0f', }, create() { this.button = this.createObject('object', { id: 'cylinder', scale: V(this.size, this.size / 5, this.size), col: this.states[this.state] }); }, setState(state) { if (this.button && this.states[state] && this.state != state) { this.state = state; this.button.col = this.states[state]; if (state == 'pressed') { this.parent.parent.pulse(1, 20); this.button.pos.y = -this.size / (5 * 1.9); } else if (state == 'default') { //this.parent.pulse(1, 20); this.button.pos.y = 0; } else if (state == 'touched') { this.button.pos.y = 0; } } } }); janus.registerElement('trackedplayer_controller_thumbstick', { range: 30, size: .1, state: 'default', states: { default: '#666', touched: '#888', pressed: '#6f6', highlighted: '#f0f', }, create() { this.shaft = this.createObject('object', { id: 'cylinder', scale: V(this.size / 5, this.size / 2, this.size / 5), col: this.states.default, }); this.button = this.createObject('object', { id: 'cylinder', scale: V(this.size, this.size / 5, this.size), col: this.states.default, pos: V(0,this.size / 2,0) }); }, setState(state) { if (this.button && this.states[state] && this.state != state) { this.state = state; this.button.col = this.states[state]; if (state == 'pressed') { this.parent.parent.pulse(1, 20); } else if (state == 'default') { //this.parent.pulse(1, 20); } } }, setXY(x, y) { this.rotation.set(y * this.range, 0, x * -this.range); } }); janus.registerElement('trackedplayer_controller_laser', { hand: null, device: null, create() { this.lasercolor = V(1,0,0); this.laser = this.createObject('linesegments', { positions: [V(0,0,0), V(0,0,-5)], opacity: .8, col: this.lasercolor, visible: false, transparent: true, }); this.bonk = this.createObject('object', { id: 'cylinder', col: 'white', lighting: false, scale: V(.005, 5, .005), rotation: V(-90,0,0), opacity: .2, }); this.cursor = room.createObject('object', { id: 'plane', scale: V(.75), image_id: 'cursor_dot_inactive', //billboard: 'y', visible: true, renderorder: 20, depth_test: false, depth_write: false, lighting: false, opacity: .5, cull_face: 'none', billboard: 'y', transparent: true, }); this.raycaster = this.createObject('raycaster', {}); this.raycaster.addEventListener('raycastenter', (ev) => this.handleRaycastEnter(ev)); this.raycaster.addEventListener('raycastmove', (ev) => this.handleRaycastMove(ev)); this.raycaster.addEventListener('raycastleave', (ev) => this.handleRaycastLeave(ev)); }, updateLaserEndpoint(endpoint, normal) { this.worldToLocal(this.laser.positions[1].copy(endpoint)); this.laser.updateLine(); this.cursor.pos.copy(endpoint); let cursorscale = player.head.distanceTo(endpoint) / 12; this.cursor.scale.set(cursorscale, cursorscale, cursorscale); //this.cursor.zdir = normal; if (this.cursor.room !== room._target) room.appendChild(this.cursor); //this.cursor.zdir.copy(player.view_dir).multiplyScalar(-1); //this.cursor.zdir = normal; this.cursor.visible = true; this.cursor.opacity = .8 * player.cursor_opacity; }, updateCursorOpacity() { if (!this.activeobject) return; let proxyobj = this.activeobject, obj = proxyobj._target; 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,255,0); this.laser.opacity = player.cursor_opacity * .8; this.laser.updateColor(); this.cursor.image_id = 'cursor_dot_active'; this.cursor.opacity = player.cursor_opacity; this.bonk.col.setRGB(.5,1,.5); this.bonk.opacity = 0.2 * player.cursor_opacity; //this.laser.visible = false; // FIXME - hack to disable laser without breaking things /* let gamepad = this.device.gamepad; if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) { gamepad.hapticActuators[0].pulse(1, 100); } */ } else { this.laser.col.setRGB(255,0,0); this.laser.opacity = player.cursor_opacity * .4; this.laser.updateColor(); this.cursor.image_id = 'cursor_dot_inactive'; this.cursor.opacity = player.cursor_opacity / 2; this.bonk.col.setRGB(1,1,1); //this.laser.visible = false; // FIXME - hack to disable laser without breaking things } }, handleRaycastEnter(ev) { let proxyobj = ev.data.object, obj = proxyobj._target; let oldactiveobject = this.activeobject; if (this.activeobject) { this.engine.client.view.proxyEvent({ type: "mouseout", element: this.activeobject.objects['3d'], relatedTarget: this.activeobject, data: ev.data.intersection, clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point? clientY: 0, bubbles: true, }, 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 = player.cursor_opacity * .8; this.laser.updateColor(); this.cursor.image_id = 'cursor_dot_active'; this.cursor.opacity = player.cursor_opacity; //this.laser.visible = false; // FIXME - hack to disable laser without breaking things /* let gamepad = this.device.gamepad; if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) { gamepad.hapticActuators[0].pulse(1, 100); } */ } else { this.laser.col.setRGB(1,0,0); this.laser.opacity = player.cursor_opacity * .4; this.laser.updateColor(); this.cursor.image_id = 'cursor_dot_inactive'; this.cursor.opacity = player.cursor_opacity / 2; //this.laser.visible = false; // FIXME - hack to disable laser without breaking things } this.updateLaserEndpoint(ev.data.intersection.point, ev.data.intersection.face.normal); this.lasthit = ev.data.intersection; let evdata = { type: "mouseover", element: ev.data.intersection.mesh, relatedTarget: oldactiveobject, data: this.lasthit, clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point? clientY: 0, bubbles: true, inputSourceObject: this.hand, }; if (this.hand) { evdata.data.gamepad = this.hand.device.gamepad; } //elation.events.fire(evdata); this.engine.client.view.proxyEvent(evdata, evdata.element); }, handleRaycastMove(ev) { this.updateLaserEndpoint(ev.data.intersection.point, ev.data.intersection.face.normal); if (this.activeobject) { let obj = this.activeobject.objects['3d']; this.lasthit = ev.data.intersection; this.engine.client.view.proxyEvent({ type: "mousemove", element: obj, data: this.lasthit, //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, bubbles: true, inputSourceObject: this.hand, }, obj); } }, handleRaycastLeave(ev) { this.laser.col.setRGB(0,1,1); this.laser.opacity = player.cursor_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 data: this.lasthit, clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point? clientY: 0, button: 0, bubbles: true, inputSourceObject: this.hand, }, obj); } }, handleSelectEnd(ev) { if (this.activeobject) { let obj = this.activeobject.objects['3d']; this.engine.client.view.proxyEvent({ type: "mouseup", element: obj, //data: ev.data.intersection, // FIXME - need to store intersection data from raycastmove data: this.lasthit, clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point? clientY: 0, button: 0, bubbles: true, inputSourceObject: this.hand, }, 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 data: this.lasthit, clientX: 0, // FIXME - can we project back to the screen x,y from the intersection point? clientY: 0, button: 0, bubbles: true, inputSourceObject: this.hand, }, obj); } }, }); janus.registerElement('trackedplayer_hand_skeleton', { hand: null, fingernames: ['THUMB', 'INDEX', 'MIDDLE', 'RING', 'LITTLE'], phalanxnames: ['METACARPAL', 'PHALANX_PROXIMAL', 'PHALANX_DISTAL', 'PHALANX_TIP'], jointnames: [ 'wrist', 'thumb-metacarpal', 'thumb-phalanx-proximal', 'thumb-phalanx-distal', 'thumb-tip', 'index-finger-metacarpal', 'index-finger-phalanx-proximal', 'index-finger-phalanx-intermediate', 'index-finger-phalanx-distal', 'index-finger-tip', 'middle-finger-metacarpal', 'middle-finger-phalanx-proximal', 'middle-finger-phalanx-intermediate', 'middle-finger-phalanx-distal', 'middle-finger-tip', 'ring-finger-metacarpal', 'ring-finger-phalanx-proximal', 'ring-finger-phalanx-intermediate', 'ring-finger-phalanx-distal', 'ring-finger-tip', 'pinky-finger-metacarpal', 'pinky-finger-phalanx-proximal', 'pinky-finger-phalanx-intermediate', 'pinky-finger-phalanx-distal', 'pinky-finger-tip', ], isactive: true, handpos: null, p0: null, p1: null, p2: null, p3: null, p4: null, create() { this.handmodel = this.parent.createObject('object', { id: 'cube', scale: V(.075, .025, .075), col: 'green', visible: false }); this.tmpobj = new THREE.Object3D(); this.tmpquat = new THREE.Quaternion(); this.jointmaterial = new THREE.MeshPhysicalMaterial({color: 0xccffcc, metalness: 1, roughness: .5, emissive: 0xccffcc, transparent: true}); this.tmpobj.matrixAutoUpdate = false; this.joints = new THREE.InstancedMesh(new THREE.SphereGeometry(1), this.jointmaterial, 26); this.joints.frustumCulled = false; this.joints.instanceMatrix.setUsage(THREE.DynamicDrawUsage); let tmpobj = this.tmpobj; for (let i = 0; i < 26; i++) { tmpobj.position.set(Math.random(), Math.random(), Math.random()); tmpobj.scale.set(.0001, .0001, .0001); tmpobj.updateMatrix(); this.joints.setMatrixAt(i, tmpobj.matrix); } this.jointposes = {}; this.objects['3d'].add(this.joints); this.handpos = new THREE.Vector3(0, 0, 0); this.p0 = new THREE.Vector3(0, 0, 0); this.p1 = new THREE.Vector3(0, 0, 0); this.p2 = new THREE.Vector3(0, 0, 0); this.p3 = new THREE.Vector3(0, 0, 0); this.p4 = new THREE.Vector3(0, 0, 0); }, updatePose(xrFrame, xrReferenceFrame) { if (!this.hand || !this.joints || !this.tmpobj) { return; } let tmpobj = this.tmpobj, tmpquat = this.tmpquat, i = 0; let poses = this.jointposes; /* let wristpose = xrFrame.getJointPose(this.hand[XRHand['WRIST']], xrReferenceFrame); this.jointposes['WRIST'] = wristpose; for (let fingernum = 0; fingernum < this.fingernames.length; fingernum++) { let fingername = this.fingernames[fingernum]; for (let jointnum = 0; jointnum < this.phalanxnames.length; jointnum++) { i++; let jointname = fingername + '_' + this.phalanxnames[jointnum]; let jointpose = xrFrame.getJointPose(this.hand[XRHand[jointname]], xrReferenceFrame); if (jointpose) { let radius = Math.max(.005, jointpose.radius); tmpobj.position.copy(jointpose.transform.position); tmpobj.quaternion.copy(jointpose.transform.orientation); tmpobj.scale.set(radius, radius, radius); tmpobj.updateMatrix(); this.joints.setMatrixAt(i, tmpobj.matrix); } this.jointposes[jointname] = jointpose; } } */ const handvalues = this.hand.values(), jointposes = this.jointposes; for ( const inputjoint of handvalues ) { const jointpose = xrFrame.getJointPose( inputjoint, xrReferenceFrame ); const jointname = inputjoint.jointName; if (jointpose && jointpose.transform) { let radius = Math.max(.002, jointpose.radius); /* tmpobj.position.copy(jointpose.transform.position); tmpobj.quaternion.copy(jointpose.transform.orientation); tmpobj.scale.set(radius, radius, radius); tmpobj.updateMatrix(); */ tmpquat.copy(jointpose.transform.orientation); if (tmpobj.scale.x != radius) tmpobj.scale.set(radius, radius, radius); tmpobj.matrix.compose(jointpose.transform.position, tmpquat, tmpobj.scale) this.joints.setMatrixAt(i, tmpobj.matrix); //FIVARSlog('xr.debug', 'update joint: ' + i + ', ' + jointname + ', ' + tmpobj.matrix.elements.join(', ')); } i++; jointposes[jointname] = jointpose; } this.joints.instanceMatrix.needsUpdate = true; //this.joints.computeBoundingSphere(); if (jointposes['wrist']) this.localToWorld(this.handpos.copy(this.jointposes['wrist'].transform.position)); if (jointposes['thumb-tip']) this.localToWorld(this.p0.copy(this.jointposes['thumb-tip'].transform.position)); if (jointposes['index-finger-tip']) this.localToWorld(this.p1.copy(this.jointposes['index-finger-tip'].transform.position)); if (jointposes['middle-finger-tip']) this.localToWorld(this.p2.copy(this.jointposes['middle-finger-tip'].transform.position)); if (jointposes['ring-finger-tip']) this.localToWorld(this.p3.copy(this.jointposes['ring-finger-tip'].transform.position)); if (jointposes['pinky-finger-tip']) this.localToWorld(this.p4.copy(this.jointposes['pinky-finger-tip'].transform.position)); this.isactive = true; this.jointmaterial.opacity = player.cursor_opacity; } }); janus.registerElement('trackedplayer_hand_virtualskeleton', { hand: null, isactive: true, handpos: null, p0: null, p1: null, p2: null, p3: null, p4: null, create() { this.handpos = V(); this.p0 = V(); this.p1 = V(); this.p2 = V(); this.p3 = V(); this.p4 = V(); }, update() { this.localToWorld(this.handpos.set(0,0,0)); this.localToWorld(this.p0.set(0,0,0)); this.localToWorld(this.p1.set(0,0,0)); this.localToWorld(this.p2.set(0,0,0)); this.localToWorld(this.p3.set(0,0,0)); this.localToWorld(this.p4.set(0,0,0)); } }); } });