handmenu test
This commit is contained in:
parent
1832cd18fe
commit
58d452447a
|
@ -0,0 +1,75 @@
|
||||||
|
AFRAME.registerComponent('aframe-inspector', {
|
||||||
|
schema: {
|
||||||
|
foo: { type:"string"}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
document.querySelector('a-scene').setAttribute("inspector","url: https://cdn.jsdelivr.net/gh/aframevr/aframe-inspector@master/dist/aframe-inspector.min.js")
|
||||||
|
},
|
||||||
|
|
||||||
|
requires:{ },
|
||||||
|
|
||||||
|
events:{
|
||||||
|
|
||||||
|
// component events
|
||||||
|
ready: function(e){ },
|
||||||
|
|
||||||
|
launcher: function(e){
|
||||||
|
$('[inspector]').components.inspector.openInspector()
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
"short_name": "inspector",
|
||||||
|
"name": "AFRAME inspector",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "https://css.gg/list-tree.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "/?source=pwa",
|
||||||
|
"start_url": "/?source=pwa",
|
||||||
|
"background_color": "#3367D6",
|
||||||
|
"display": "standalone",
|
||||||
|
"scope": "/",
|
||||||
|
"theme_color": "#3367D6",
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "What is the latest news?",
|
||||||
|
"cli":{
|
||||||
|
"usage": "helloworld <type> [options]",
|
||||||
|
"example": "helloworld news",
|
||||||
|
"args":{
|
||||||
|
"--latest": {type:"string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"short_name": "Today",
|
||||||
|
"description": "View weather information for today",
|
||||||
|
"url": "/today?source=pwa",
|
||||||
|
"icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "use CTRL+ALT+i to launch inspector",
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/screenshot1.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "540x720",
|
||||||
|
"form_factor": "narrow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"help":`
|
||||||
|
Helloworld application
|
||||||
|
|
||||||
|
This is a help file which describes the application.
|
||||||
|
It will be rendered thru troika text, and will contain
|
||||||
|
headers based on non-punctualized lines separated by linebreaks,
|
||||||
|
in above's case "\nHelloworld application\n" will qualify as header.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -31,6 +31,8 @@ AFRAME.registerComponent('launcher', {
|
||||||
|
|
||||||
requires:{
|
requires:{
|
||||||
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
|
||||||
|
'hand-menu': "./com/control/hand-menu.js",
|
||||||
|
'hand-menu-button': "./com/control/hand-menu-button.js",
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
|
@ -108,6 +110,7 @@ AFRAME.registerComponent('launcher', {
|
||||||
|
|
||||||
ready: function( ){
|
ready: function( ){
|
||||||
this.el.dom.children[0].id = this.el.uid // important hint for html-mesh
|
this.el.dom.children[0].id = this.el.uid // important hint for html-mesh
|
||||||
|
document.querySelector('#left-hand').setAttribute('hand-menu','')
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
95
app/paste.js
95
app/paste.js
|
@ -5,12 +5,11 @@ AFRAME.registerComponent('paste', {
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
|
|
||||||
//this.el.innerHTML = ` `
|
//this.el.innerHTML = ` `
|
||||||
},
|
},
|
||||||
|
|
||||||
requires:{
|
requires:{
|
||||||
// somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js"
|
osbutton: "com/osbutton.js"
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
@ -23,23 +22,99 @@ AFRAME.registerComponent('paste', {
|
||||||
navigator.clipboard.readText()
|
navigator.clipboard.readText()
|
||||||
.then( (base64) => {
|
.then( (base64) => {
|
||||||
let mimetype = base64.replace(/;base64,.*/,'')
|
let mimetype = base64.replace(/;base64,.*/,'')
|
||||||
console.log(base64.substr(0,100))
|
|
||||||
let data = base64.replace(/.*;base64,/,'')
|
let data = base64.replace(/.*;base64,/,'')
|
||||||
if( data.match(/<a-/) ){
|
let type = this.textHeuristic(data)
|
||||||
let el = document.createElement('a-entity')
|
console.log("type="+type)
|
||||||
el.innerHTML = data
|
switch( this.textHeuristic(data) ){
|
||||||
AFRAME.scenes[0].appendChild(el)
|
case "aframe": this.insertAFRAME(data); break;
|
||||||
console.log(data)
|
default: this.insertText(data); break;
|
||||||
}
|
}
|
||||||
|
this.count += 1
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
textHeuristic: function(text){
|
||||||
|
// Script type identification clues
|
||||||
|
const bashClues = ["|", "if ", "fi", "cat"];
|
||||||
|
const htmlClues = ["/>", "href=", "src="];
|
||||||
|
const aframeClues = ["<a-entity", "/>", "position="];
|
||||||
|
const jsClues = ["var ", "let ", "function ", "setTimeout","console."];
|
||||||
|
// Count occurrences of clues for each script type
|
||||||
|
const bashCount = bashClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const htmlCount = htmlClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const aframeCount = aframeClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
const jsCount = jsClues.reduce((acc, clue) => acc + (text.includes(clue) ? 1 : 0), 0);
|
||||||
|
|
||||||
|
// Identify the script with the most clues or return unknown if inconclusive
|
||||||
|
const maxCount = Math.max(bashCount, htmlCount, jsCount, aframeCount);
|
||||||
|
if (maxCount === 0) {
|
||||||
|
return "unknown";
|
||||||
|
} else if (bashCount === maxCount) {
|
||||||
|
return "bash";
|
||||||
|
} else if (htmlCount === maxCount) {
|
||||||
|
return "html";
|
||||||
|
} else if (jsCount === maxCount) {
|
||||||
|
return "javascript";
|
||||||
|
} else {
|
||||||
|
return "aframe";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
insertAFRAME: function(data){
|
||||||
|
let scene = document.createElement('a-entity')
|
||||||
|
scene.id = "embedAframe"
|
||||||
|
scene.innerHTML = data
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"aframe",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
insertText: function(data){
|
||||||
|
let el = document.createElement('a-text')
|
||||||
|
el.setAttribute("value",data)
|
||||||
|
el.setAttribute("color","white")
|
||||||
|
el.setAttribute("align","center")
|
||||||
|
el.setAttribute("anchor","align")
|
||||||
|
let osbutton = this.wrapOSButton(el,"text",data)
|
||||||
|
AFRAME.scenes[0].appendChild(osbutton)
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapOSButton: function(el,type,data){
|
||||||
|
let osbutton = document.createElement('a-entity')
|
||||||
|
let height = type == 'aframe' ? 0.3 : 0.1
|
||||||
|
let depth = type == 'aframe' ? 0.3 : 0.05
|
||||||
|
osbutton.setAttribute("osbutton",`width:0.3; height: ${height}; depth: ${depth}; color:blue `)
|
||||||
|
osbutton.appendChild(el)
|
||||||
|
osbutton.object3D.position.copy( this.getPositionInFrontOfCamera() )
|
||||||
|
return osbutton
|
||||||
|
},
|
||||||
|
|
||||||
|
getPositionInFrontOfCamera: function(){
|
||||||
|
const camera = this.el.sceneEl.camera;
|
||||||
|
let pos = new THREE.Vector3()
|
||||||
|
let direction = new THREE.Vector3();
|
||||||
|
// Get camera's forward direction (without rotation)
|
||||||
|
camera.getWorldDirection(direction);
|
||||||
|
camera.getWorldPosition(pos)
|
||||||
|
direction.normalize();
|
||||||
|
// Scale the direction by 1 meter
|
||||||
|
direction.multiplyScalar(1.5);
|
||||||
|
// Add the camera's position to the scaled direction to get the target point
|
||||||
|
pos.add(direction);
|
||||||
|
return pos
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
"short_name": "Hello world",
|
"short_name": "Paste",
|
||||||
"name": "Hello world",
|
"name": "Paste",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "https://css.gg/clipboard.svg",
|
"src": "https://css.gg/clipboard.svg",
|
||||||
|
|
|
@ -24,7 +24,7 @@ AFRAME.registerComponent('spatialize', {
|
||||||
this.el.sceneEl.addEventListener('launched',(e) => {
|
this.el.sceneEl.addEventListener('launched',(e) => {
|
||||||
console.dir(e)
|
console.dir(e)
|
||||||
let appEl = e.detail.el.dom
|
let appEl = e.detail.el.dom
|
||||||
if( appEl.style && appEl.style.display != 'none' && appEl.innerHTML ){
|
if( appEl && appEl.style && appEl.style.display != 'none' && appEl.innerHTML ){
|
||||||
this.btn.style.display = '' // show button
|
this.btn.style.display = '' // show button
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* global AFRAME, THREE */
|
||||||
|
AFRAME.registerComponent('hand-menu-button', {
|
||||||
|
schema: {
|
||||||
|
src: {default: ''},
|
||||||
|
srcHover: {default: ''},
|
||||||
|
mixin: {default: ''}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
this.onWatchButtonHovered = this.onWatchButtonHovered.bind(this);
|
||||||
|
this.onAnimationComplete = this.onAnimationComplete.bind(this);
|
||||||
|
this.onCollisionStarted = this.onCollisionStarted.bind(this);
|
||||||
|
this.onCollisionEnded = this.onCollisionEnded.bind(this);
|
||||||
|
this.onAnimationBegin = this.onAnimationBegin.bind(this);
|
||||||
|
this.onPinchEnded = this.onPinchEnded.bind(this);
|
||||||
|
|
||||||
|
this.el.addEventListener('obbcollisionstarted', this.onCollisionStarted);
|
||||||
|
this.el.addEventListener('obbcollisionended', this.onCollisionEnded);
|
||||||
|
this.el.object3D.renderOrder = 1000;
|
||||||
|
|
||||||
|
this.menuEl = this.el.parentEl;
|
||||||
|
this.handMenuEl = this.el.sceneEl.querySelector('[hand-menu]');
|
||||||
|
|
||||||
|
this.menuEl.addEventListener('animationbegin', this.onAnimationBegin);
|
||||||
|
this.menuEl.addEventListener('animationcomplete', this.onAnimationComplete);
|
||||||
|
},
|
||||||
|
|
||||||
|
onAnimationBegin: function (evt) {
|
||||||
|
// To prevent menu activations while animation is running.
|
||||||
|
if (evt.detail.name === 'animation__open') { this.menuOpen = false; }
|
||||||
|
},
|
||||||
|
|
||||||
|
onAnimationComplete: function (evt) {
|
||||||
|
if (evt.detail.name === 'animation__open') { this.menuOpen = true; }
|
||||||
|
if (evt.detail.name === 'animation__close') { this.menuOpen = false; }
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollisionStarted: function (evt) {
|
||||||
|
var withEl = evt.detail.withEl;
|
||||||
|
if (this.handMenuEl === withEl ||
|
||||||
|
!withEl.components['hand-tracking-controls']) { return; }
|
||||||
|
if (!this.menuOpen) { return; }
|
||||||
|
this.handHoveringEl = withEl;
|
||||||
|
this.el.emit('watchbuttonhoverstarted');
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollisionEnded: function (evt) {
|
||||||
|
var withEl = evt.detail.withEl;
|
||||||
|
if (this.handMenuEl === withEl ||
|
||||||
|
!withEl.components['hand-tracking-controls']) { return; }
|
||||||
|
this.disableHover();
|
||||||
|
this.handHoveringEl = undefined;
|
||||||
|
this.el.emit('watchbuttonhoverended');
|
||||||
|
},
|
||||||
|
|
||||||
|
enableHover: function () {
|
||||||
|
this.handHoveringEl.addEventListener('pinchended', this.onPinchEnded);
|
||||||
|
this.el.setAttribute('material', 'src', this.data.srcHover);
|
||||||
|
},
|
||||||
|
|
||||||
|
disableHover: function () {
|
||||||
|
if (!this.handHoveringEl) { return; }
|
||||||
|
this.handHoveringEl.removeEventListener('pinchended', this.onPinchEnded);
|
||||||
|
this.el.setAttribute('material', 'src', this.data.src);
|
||||||
|
},
|
||||||
|
|
||||||
|
onPinchEnded: (function () {
|
||||||
|
var spawnPosition = new THREE.Vector3(0, 1, 0);
|
||||||
|
return function () {
|
||||||
|
var cubeEl;
|
||||||
|
var newEntityEl;
|
||||||
|
if (!this.menuOpen) { return; }
|
||||||
|
this.menuOpen = false;
|
||||||
|
if (!this.handHoveringEl || !this.data.mixin) { return; }
|
||||||
|
// Spawn shape a little above the menu.
|
||||||
|
spawnPosition.set(0, 1, 0);
|
||||||
|
// Apply rotation of the menu.
|
||||||
|
spawnPosition.applyQuaternion(this.el.parentEl.object3D.quaternion);
|
||||||
|
// 20cm above the menu.
|
||||||
|
spawnPosition.normalize().multiplyScalar(0.2);
|
||||||
|
spawnPosition.add(this.el.parentEl.object3D.position);
|
||||||
|
|
||||||
|
newEntityEl = document.createElement('a-entity');
|
||||||
|
newEntityEl.setAttribute('mixin', this.data.mixin);
|
||||||
|
newEntityEl.setAttribute('position', spawnPosition);
|
||||||
|
this.el.sceneEl.appendChild(newEntityEl);
|
||||||
|
this.handHoveringEl.removeEventListener('pinchended', this.onPinchEnded);
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
|
||||||
|
onWatchButtonHovered: function (evt) {
|
||||||
|
if (evt.target === this.el || !this.handHoveringEl) { return; }
|
||||||
|
this.disableHover();
|
||||||
|
this.handHoveringEl = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
User's hand can collide with multiple buttons simultaneously but only want one in a hovered state.
|
||||||
|
This system keeps track of all the collided buttons, keeping just the closest to the hand in a hovered state.
|
||||||
|
*/
|
||||||
|
AFRAME.registerSystem('hand-menu-button', {
|
||||||
|
init: function () {
|
||||||
|
this.onWatchButtonHovered = this.onWatchButtonHovered.bind(this);
|
||||||
|
this.el.parentEl.addEventListener('watchbuttonhoverended', this.onWatchButtonHovered);
|
||||||
|
this.el.parentEl.addEventListener('watchbuttonhoverstarted', this.onWatchButtonHovered);
|
||||||
|
this.hoveredButtonEls = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
tick: function () {
|
||||||
|
var buttonWorldPosition = new THREE.Vector3();
|
||||||
|
var thumbPosition;
|
||||||
|
var smallestDistance = 1000000;
|
||||||
|
var currentDistance;
|
||||||
|
var closestButtonEl;
|
||||||
|
if (this.hoveredButtonEls.length < 2) { return; }
|
||||||
|
thumbPosition = this.hoveredButtonEls[0].components['hand-menu-button'].handHoveringEl.components['obb-collider'].trackedObject3D.position;
|
||||||
|
for (var i = 0; i < this.hoveredButtonEls.length; ++i) {
|
||||||
|
this.hoveredButtonEls[i].object3D.getWorldPosition(buttonWorldPosition);
|
||||||
|
currentDistance = buttonWorldPosition.distanceTo(thumbPosition);
|
||||||
|
if (currentDistance < smallestDistance) {
|
||||||
|
closestButtonEl = this.hoveredButtonEls[i];
|
||||||
|
smallestDistance = currentDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hoveredButtonEl === closestButtonEl) { return; }
|
||||||
|
|
||||||
|
this.hoveredButtonEl = closestButtonEl;
|
||||||
|
|
||||||
|
for (i = 0; i < this.hoveredButtonEls.length; ++i) {
|
||||||
|
if (!this.hoveredButtonEls[i].components['hand-menu-button'].handHoveringEl) { continue; }
|
||||||
|
if (this.hoveredButtonEls[i] === closestButtonEl) {
|
||||||
|
this.hoveredButtonEls[i].components['hand-menu-button'].enableHover();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.hoveredButtonEls[i].components['hand-menu-button'].disableHover();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onWatchButtonHovered: function (evt) {
|
||||||
|
this.buttonEls = this.el.sceneEl.querySelectorAll('[hand-menu-button]');
|
||||||
|
this.hoveredButtonEls = [];
|
||||||
|
for (var i = 0; i < this.buttonEls.length; ++i) {
|
||||||
|
if (!this.buttonEls[i].components['hand-menu-button'].handHoveringEl) { continue; }
|
||||||
|
this.hoveredButtonEls.push(this.buttonEls[i]);
|
||||||
|
}
|
||||||
|
if (this.hoveredButtonEls.length === 1) {
|
||||||
|
this.hoveredButtonEl = this.hoveredButtonEls[0];
|
||||||
|
this.hoveredButtonEls[0].components['hand-menu-button'].enableHover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,198 @@
|
||||||
|
/* global AFRAME, THREE */
|
||||||
|
AFRAME.registerComponent('hand-menu', {
|
||||||
|
schema: {
|
||||||
|
location: {default: 'palm', oneOf: ['palm']}
|
||||||
|
},
|
||||||
|
|
||||||
|
menuHTML: /* syntax: html */ `
|
||||||
|
<a-entity
|
||||||
|
scale="0 0 0"
|
||||||
|
animation__open="startEvents: open; dur: 200; property: scale; from: 0 0 1; to: 0.1 0.1 0.1"
|
||||||
|
animation__close="startEvents: close; dur: 200; property: scale; from: 0.1 0.1 0.1; to: 0 0 1">
|
||||||
|
|
||||||
|
<a-entity
|
||||||
|
obb-collider
|
||||||
|
hand-menu-button="src: #infoButton; srcHover: #infoButtonHover"
|
||||||
|
position="0 0 0"
|
||||||
|
rotation="0 180 0"
|
||||||
|
geometry="primitive: plane; width: 1; height: 1;"
|
||||||
|
material="side: double; color: white; shader: flat; src: #infoButton; transparent: true;">
|
||||||
|
</a-entity>
|
||||||
|
|
||||||
|
<a-entity
|
||||||
|
obb-collider
|
||||||
|
hand-menu-button="src: #cubeButton; srcHover: #cubeButtonHover; mixin: cube"
|
||||||
|
position="0 1.12 0"
|
||||||
|
geometry="primitive: plane; width: 1; height: 1;"
|
||||||
|
material="side: double; color: white; shader: flat; src: #cubeButton; transparent: true">
|
||||||
|
</a-entity>
|
||||||
|
|
||||||
|
<a-entity
|
||||||
|
obb-collider
|
||||||
|
hand-menu-button="src: #cylinderButton; srcHover: #cylinderButtonHover; mixin: cylinder"
|
||||||
|
position="0 -1.12 0"
|
||||||
|
geometry="primitive: plane; width: 1; height: 1;"
|
||||||
|
material="side: double; color: white; shader: flat; src: #cylinderButton; transparent: true">
|
||||||
|
</a-entity>
|
||||||
|
|
||||||
|
<a-entity
|
||||||
|
obb-collider
|
||||||
|
hand-menu-button="src: #planeButton; srcHover: #planeButtonHover; mixin: plane"
|
||||||
|
position="-1.12 0 0"
|
||||||
|
geometry="primitive: plane; width: 1; height: 1;"
|
||||||
|
material="side: double; color: white; shader: flat; src: #planeButton; transparent: true">
|
||||||
|
</a-entity>
|
||||||
|
|
||||||
|
<a-entity
|
||||||
|
obb-collider
|
||||||
|
hand-menu-button="src: #sphereButton; srcHover: #sphereButtonHover; mixin: sphere"
|
||||||
|
position="1.12 0 0"
|
||||||
|
geometry="primitive: plane; width: 1; height: 1;"
|
||||||
|
material="side: double; color: white; shader: flat; src: #sphereButton; transparent: true">
|
||||||
|
</a-entity>
|
||||||
|
</a-entity>
|
||||||
|
`,
|
||||||
|
|
||||||
|
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 <HTML>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
*/
|
|
@ -0,0 +1,86 @@
|
||||||
|
AFRAME.registerComponent('osbutton',{
|
||||||
|
|
||||||
|
data:{
|
||||||
|
width: {type: 'number', default: 0.4},
|
||||||
|
height: {type: 'number', default: 0.2},
|
||||||
|
depth: {type: 'number', default: 0.06},
|
||||||
|
color: {type: 'color', default: 'blue'},
|
||||||
|
distance: {type: 'number'},
|
||||||
|
label: {type: 'string'}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
this
|
||||||
|
.createBox()
|
||||||
|
.setupDistanceTick()
|
||||||
|
},
|
||||||
|
|
||||||
|
setupDistanceTick: function(){
|
||||||
|
// we throttle by distance, to support scenes with loads of clickable objects (far away)
|
||||||
|
if( !this.data.distance ) this.data.distance = 0.9
|
||||||
|
this.distance = -1
|
||||||
|
this.worldPosition = new THREE.Vector3()
|
||||||
|
this.posCam = new THREE.Vector3()
|
||||||
|
this.tick = this.throttleByDistance( () => this.showSource() )
|
||||||
|
},
|
||||||
|
|
||||||
|
createBox: function(){
|
||||||
|
let geometry = this.geometry = new THREE.BoxGeometry(this.data.width, this.data.height, this.data.depth);
|
||||||
|
this.material = new THREE.MeshStandardMaterial({color: this.data.color });
|
||||||
|
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||||
|
this.scaleChildToButton(this.el.object3D, this.mesh)
|
||||||
|
this.el.object3D.add(this.mesh)
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
|
||||||
|
throttleByDistance: function(f){
|
||||||
|
return function(){
|
||||||
|
if( this.distance < 0 ) return f() // first call
|
||||||
|
if( !f.tid ){
|
||||||
|
let x = this.distance
|
||||||
|
let y = x*(x*0.05)*1000 // parabolic curve
|
||||||
|
f.tid = setTimeout( function(){
|
||||||
|
f.tid = null
|
||||||
|
f()
|
||||||
|
}, y )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showSource: function(){
|
||||||
|
this.el.sceneEl.camera.getWorldPosition(this.posCam)
|
||||||
|
this.el.object3D.getWorldPosition(this.worldPosition)
|
||||||
|
this.distance = this.posCam.distanceTo(this.worldPosition)
|
||||||
|
|
||||||
|
if( this.distance < this.data.distance ){
|
||||||
|
this.material.side = THREE.BackSide
|
||||||
|
}else{
|
||||||
|
this.material.side = THREE.FrontSide
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scaleChildToButton: function(scene, mesh ){
|
||||||
|
let cleanScene = scene.clone()
|
||||||
|
let remove = []
|
||||||
|
const notVisible = (n) => !n.visible || (n.material && !n.material.visible)
|
||||||
|
cleanScene.traverse( (n) => notVisible(n) && n.children.length == 0 && (remove.push(n)) )
|
||||||
|
remove.map( (n) => n.removeFromParent() )
|
||||||
|
let restrictTo3DBoundingBox = mesh.geometry
|
||||||
|
if( restrictTo3DBoundingBox ){
|
||||||
|
// normalize instanced objectsize to boundingbox
|
||||||
|
let sizeFrom = new THREE.Vector3()
|
||||||
|
let sizeTo = new THREE.Vector3()
|
||||||
|
let empty = new THREE.Object3D()
|
||||||
|
new THREE.Box3().setFromObject(mesh).getSize(sizeTo)
|
||||||
|
new THREE.Box3().setFromObject(cleanScene).getSize(sizeFrom)
|
||||||
|
let ratio = sizeFrom.divide(sizeTo)
|
||||||
|
scene.children.map( (c) => {
|
||||||
|
if( c.uuid != mesh.uuid ){
|
||||||
|
c.scale.multiplyScalar( 1.0 / Math.max(ratio.x, ratio.y, ratio.z))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in New Issue