/* global AFRAME, THREE */ AFRAME.registerComponent('hand-menu', { schema: { location: {default: 'palm', oneOf: ['palm']} }, menuHTML: /* syntax: html */ ` `, init: function () { this.onCollisionStarted = this.onCollisionStarted.bind(this); this.onCollisionEnded = this.onCollisionEnded.bind(this); this.onSceneLoaded = this.onSceneLoaded.bind(this); this.onEnterVR = this.onEnterVR.bind(this); this.throttledOnPinchEvent = AFRAME.utils.throttle(this.throttledOnPinchEvent, 50, this); //this.el.sceneEl.addEventListener('loaded', this.onSceneLoaded); this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR); }, onEnterVR: function () { this.onSceneLoaded() this.setupMenu(); }, onSceneLoaded: function () { var handEls = this.el.sceneEl.querySelectorAll('[hand-tracking-controls]'); for (var i = 0; i < handEls.length; i++) { if (handEls[i] === this.el) { continue; } this.handElement = handEls[i]; } }, setupMenu: function () { var template = document.createElement('template'); template.innerHTML = this.menuHTML; this.menuEl = template.content.children[0]; this.el.sceneEl.appendChild(this.menuEl); if (this.data.location === 'palm') { this.setupPalm(); } }, setupPalm: function () { var el = this.openMenuEl = document.createElement('a-entity'); el.setAttribute('geometry', 'primitive: circle; radius: 0.025'); el.setAttribute('material', 'side: double; src: #palmButton; shader: flat'); el.setAttribute('rotation', '90 0 180'); el.setAttribute('position', '0 -0.035 -0.07'); el.setAttribute('obb-collider', ''); el.addEventListener('obbcollisionstarted', this.onCollisionStarted); el.addEventListener('obbcollisionended', this.onCollisionEnded); this.el.appendChild(el); }, throttledOnPinchEvent: function (evt) { if (evt.type === 'pinchstarted') { this.onPinchStarted(evt); } if (evt.type === 'pinchended') { this.onPinchEnded(evt); } }, onCollisionStarted: function (evt) { var withEl = evt.detail.withEl; if (this.handElement !== withEl) { return; } withEl.addEventListener('pinchstarted', this.throttledOnPinchEvent); withEl.addEventListener('pinchended', this.throttledOnPinchEvent); this.handHoveringEl = withEl; this.updateUI(); }, onCollisionEnded: function (evt) { var withEl = evt.detail.withEl; if (this.handElement !== withEl) { return; } withEl.removeEventListener('pinchstarted', this.throttledOnPinchEvent); if (!this.opened) { withEl.removeEventListener('pinchended', this.throttledOnPinchEvent); } this.handHoveringEl = undefined; this.updateUI(); }, updateUI: function () { var palmButtonImage; if (this.data.location === 'palm') { palmButtonImage = this.handHoveringEl ? '#palmButtonHover' : '#palmButton'; debugger this.openMenuEl.setAttribute('material', 'src', palmButtonImage); return; } }, onPinchStarted: (function () { var auxMatrix = new THREE.Matrix4(); var auxQuaternion = new THREE.Quaternion(); return function (evt) { if (!this.handHoveringEl || this.opened) { return; } this.opened = true; this.menuEl.object3D.position.copy(evt.detail.position); this.menuEl.emit('open'); function lookAtVector (sourcePoint, destPoint) { return auxQuaternion.setFromRotationMatrix( auxMatrix.identity() .lookAt(sourcePoint, destPoint, new THREE.Vector3(0, 1, 0))); } var cameraEl = this.el.sceneEl.querySelector('[camera]'); var rotationQuaternion = lookAtVector(this.menuEl.object3D.position, cameraEl.object3D.position); this.menuEl.object3D.quaternion.copy(rotationQuaternion); this.pinchedEl = this.handHoveringEl; if (this.data.location === 'palm') { this.openMenuEl.object3D.visible = false; } }; })(), onPinchEnded: function (evt) { if (!this.pinchedEl) { return; } this.opened = false; this.menuEl.emit('close'); this.pinchedEl = undefined; this.openMenuEl.object3D.visible = true; }, lookAtCamera: (function () { var auxVector = new THREE.Vector3(); var auxObject3D = new THREE.Object3D(); return function (el) { var cameraEl = this.el.sceneEl.querySelector('[camera]'); auxVector.subVectors(cameraEl.object3D.position, el.object3D.position).add(el.object3D.position); el.object3D.lookAt(auxVector); el.object3D.rotation.z = 0; }; })() }); /* Watch style UI that work both in VR and AR with @aframevr in one line of Try now on @Meta Quest Browser https://a-watch.glitch.me/ Just 400 lines of code: https://glitch.com/edit/#!/a-watch Watch-style intuitive but easy to occlude hands ⌚️ Palm- style less familiar but more robust ✋ Enjoy! Wanna see more of this? sponsor me on @github https://github.com/sponsors/dmarcos */