main: work in progress [might break]
This commit is contained in:
parent
84361d303a
commit
a4ddb52310
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* ## data_events
|
||||||
|
*
|
||||||
|
* allows components to react to data changes
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <script>
|
||||||
|
* AFRAME.registerComponent('mycom',{
|
||||||
|
* init: function(){ this.data.foo = 1 },
|
||||||
|
* event: {
|
||||||
|
* foo: (e) => alert("I was updated!")
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* </script>
|
||||||
|
*
|
||||||
|
* <a-entity mycom data_events/>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
AFRAME.registerComponent('data2event',{
|
||||||
|
|
||||||
|
init: function(){
|
||||||
|
setTimeout( () => {
|
||||||
|
for( let i in this.el.components ){
|
||||||
|
let com = this.el.components[i]
|
||||||
|
com.data = this.reactify( this.el, com.data)
|
||||||
|
}
|
||||||
|
},50)
|
||||||
|
},
|
||||||
|
|
||||||
|
reactify: function(el,data){
|
||||||
|
return new Proxy(data, {
|
||||||
|
get(me,k,v) {
|
||||||
|
return me[k]
|
||||||
|
},
|
||||||
|
set(me,k,v){
|
||||||
|
me[k] = v
|
||||||
|
el.emit(k,{el,k,v})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
11
com/dom.js
11
com/dom.js
|
@ -42,7 +42,11 @@ AFRAME.registerComponent('dom',{
|
||||||
this.com = c
|
this.com = c
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if( !this.dom ) return console.warn('dom.js did not find a .dom object inside components')
|
if( !this.dom || !this.com){
|
||||||
|
return console.warn('dom.js did not find a .dom object inside component')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.dir(this.dom)
|
||||||
|
|
||||||
this
|
this
|
||||||
.ensureOverlay()
|
.ensureOverlay()
|
||||||
|
@ -56,14 +60,14 @@ AFRAME.registerComponent('dom',{
|
||||||
this.el.emit('DOMready',{el: this.el.dom})
|
this.el.emit('DOMready',{el: this.el.dom})
|
||||||
},
|
},
|
||||||
|
|
||||||
ensureOverlay(){
|
ensureOverlay: function(){
|
||||||
// ensure overlay
|
// ensure overlay
|
||||||
let overlay = document.querySelector('#overlay')
|
let overlay = document.querySelector('#overlay')
|
||||||
if( !overlay ){
|
if( !overlay ){
|
||||||
overlay = document.createElement('div')
|
overlay = document.createElement('div')
|
||||||
overlay.id = "overlay"
|
overlay.id = "overlay"
|
||||||
document.body.appendChild(overlay)
|
document.body.appendChild(overlay)
|
||||||
document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay")
|
// sceneEl.setAttribute("webxr","overlayElement:#overlay")
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
@ -85,6 +89,7 @@ AFRAME.registerComponent('dom',{
|
||||||
this.el.dom = document.createElement('div')
|
this.el.dom = document.createElement('div')
|
||||||
this.el.dom.innerHTML = this.dom.html(this)
|
this.el.dom.innerHTML = this.dom.html(this)
|
||||||
this.el.dom.className = this.dom.attrName
|
this.el.dom.className = this.dom.attrName
|
||||||
|
console.dir(this.com.data)
|
||||||
this.com.data = this.reactify( this.el, this.com.data )
|
this.com.data = this.reactify( this.el, this.com.data )
|
||||||
if( this.dom.events ) this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) )
|
if( this.dom.events ) this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) )
|
||||||
this.el.dom = this.el.dom.children[0]
|
this.el.dom = this.el.dom.children[0]
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
AFRAME.registerComponent('helloworld', {
|
AFRAME.registerComponent('helloworld', {
|
||||||
schema: {
|
schema: {
|
||||||
wireframe: { type:"boolean", "default":false},
|
wireframe: { type:"boolean", "default":false},
|
||||||
foo: {type:"string","default":"foo"}
|
text: {type:"string","default":"hello world"}
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:['dom'],
|
dependencies: ['data2event'],
|
||||||
|
|
||||||
init: function () {
|
init: async function() {
|
||||||
this.el.object3D.visible = false
|
this.el.object3D.visible = false
|
||||||
|
|
||||||
|
await AFRAME.utils.require(this.dependencies)
|
||||||
|
this.el.setAttribute("data2event","")
|
||||||
|
|
||||||
this.el.innerHTML = `
|
this.el.innerHTML = `
|
||||||
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
|
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
|
||||||
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
|
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
|
||||||
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
|
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
|
||||||
<a-entity position="0 1.8 -3" scale="10 10 10" text="value: ${this.data.foo}; align:center; color:#888"></a-entity>
|
<a-entity position="0 1.8 -3" scale="10 10 10" text="value: ${this.data.text}; align:center; color:#888"></a-entity>
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ AFRAME.registerComponent('helloworld', {
|
||||||
|
|
||||||
launcher: function(e){
|
launcher: function(e){
|
||||||
this.el.object3D.visible = !this.el.object3D.visible
|
this.el.object3D.visible = !this.el.object3D.visible
|
||||||
|
clearInterval(this.interval)
|
||||||
this.interval = setInterval( () => {
|
this.interval = setInterval( () => {
|
||||||
this.data.wireframe = !this.data.wireframe
|
this.data.wireframe = !this.data.wireframe
|
||||||
}, 500 )
|
}, 500 )
|
||||||
|
|
166
com/launcher.js
166
com/launcher.js
|
@ -18,23 +18,54 @@
|
||||||
|
|
||||||
AFRAME.registerComponent('launcher', {
|
AFRAME.registerComponent('launcher', {
|
||||||
schema: {
|
schema: {
|
||||||
attach: { type:"selector"}
|
attach: { type:"selector"},
|
||||||
|
padding: { type:"number","default":0.15},
|
||||||
|
fingerTip: {type:"selector"},
|
||||||
|
fingerDistance: {type:"number", "default":0.25},
|
||||||
|
rescale: {type:"number","default":0.4},
|
||||||
|
open: { type:"boolean", "default":true},
|
||||||
|
colors: { type:"array", "default": [
|
||||||
|
'#4C73FE',
|
||||||
|
'#554CFE',
|
||||||
|
'#864CFE',
|
||||||
|
'#B44CFE',
|
||||||
|
'#E24CFE',
|
||||||
|
'#FE4CD3',
|
||||||
|
'#333333',
|
||||||
|
]},
|
||||||
|
paused: { type:"boolean","default":false},
|
||||||
|
cols: { type:"number", "default": 5 }
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies:['dom'],
|
dependencies:['dom'],
|
||||||
|
|
||||||
init: async function () {
|
init: async function () {
|
||||||
this.data.apps = []
|
this.worldPosition = new THREE.Vector3()
|
||||||
|
|
||||||
await AFRAME.utils.require({
|
await AFRAME.utils.require({
|
||||||
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
|
||||||
dom: "./com/dom.js",
|
dom: "./com/dom.js",
|
||||||
svgfile: "https://7dir.github.io/aframe-svgfile-component/aframe-svgfile-component.min.js",
|
data_events: "./com/data2event.js"
|
||||||
})
|
})
|
||||||
|
|
||||||
this.el.setAttribute("dom","")
|
this.el.setAttribute("dom","")
|
||||||
this.render()
|
this.render()
|
||||||
this.el.sceneEl.addEventListener('enter-vr', (e) => this.render() )
|
|
||||||
|
if( this.data.attach ){
|
||||||
|
this.el.object3D.visible = false
|
||||||
|
if( this.isHand(this.data.attach) ){
|
||||||
|
this.data.attach.addEventListener('model-loaded', () => this.attachMenu() )
|
||||||
|
// add button
|
||||||
|
this.menubutton = this.createMenuButton()
|
||||||
|
this.menubutton.object3D.visible = false
|
||||||
|
this.data.attach.appendChild( this.menubutton )
|
||||||
|
}else this.data.attach.appendChild(this.el)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
isHand: (el) => {
|
||||||
|
return el.getAttributeNames().filter( (n) => n.match(/^hand-tracking/) ? n : null ).length ? true : false
|
||||||
},
|
},
|
||||||
|
|
||||||
dom: {
|
dom: {
|
||||||
|
@ -100,26 +131,40 @@ AFRAME.registerComponent('launcher', {
|
||||||
},
|
},
|
||||||
|
|
||||||
events:{
|
events:{
|
||||||
|
open: function(){
|
||||||
|
this.preventAccidentalButtonPresses()
|
||||||
|
if( this.data.open ){
|
||||||
|
this.el.setAttribute("animation",`dur: 200; property: scale; from: 0 0 1; to: ${this.data.rescale} ${this.data.rescale} ${this.data.rescale}`)
|
||||||
|
this.menubutton.object3D.visible = false
|
||||||
|
}else{
|
||||||
|
this.el.setAttribute("animation",`dur: 200; property: scale; from: ${this.data.rescale} ${this.data.rescale} ${this.data.rescale}; to: 0 0 1`)
|
||||||
|
this.menubutton.object3D.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
preventAccidentalButtonPresses: function(){
|
||||||
|
this.data.paused = true
|
||||||
|
setTimeout( () => this.data.paused = false, 500 ) // prevent menubutton press collide with animated buttons
|
||||||
|
},
|
||||||
|
|
||||||
|
createMenuButton: function(colo){
|
||||||
|
let aentity = document.createElement('a-entity')
|
||||||
|
aentity.setAttribute("mixin","menubutton")
|
||||||
|
aentity.addEventListener('obbcollisionstarted', this.onpress )
|
||||||
|
aentity.addEventListener('obbcollisionended', this.onreleased )
|
||||||
|
return aentity
|
||||||
},
|
},
|
||||||
|
|
||||||
render: async function(){
|
render: async function(){
|
||||||
if( !this.el.dom ) return // too early (dom.js component not ready)
|
if( !this.el.dom ) return // too early (dom.js component not ready)
|
||||||
|
|
||||||
let inVR = this.sceneEl && this.sceneEl.renderer.xr.isPresenting
|
|
||||||
let items = [...this.el.children]
|
let items = [...this.el.children]
|
||||||
let requires = []
|
let requires = []
|
||||||
let i = 0
|
let i = 0
|
||||||
let colors = [
|
let j = 0
|
||||||
'#4C73FE',
|
let colors = this.data.colors
|
||||||
'#554CFE',
|
const add2D = (launchCom,el,manifest) => {
|
||||||
'#864CFE',
|
|
||||||
'#B44CFE',
|
|
||||||
'#E24CFE',
|
|
||||||
'#FE4CD3'
|
|
||||||
]
|
|
||||||
|
|
||||||
const add2D = (launchCom,el,manifest,aentity) => {
|
|
||||||
let btn = document.createElement('button')
|
let btn = document.createElement('button')
|
||||||
btn.innerHTML = `${ manifest?.icons?.length > 0
|
btn.innerHTML = `${ manifest?.icons?.length > 0
|
||||||
? `<img src='${manifest.icons[0].src}' title='${manifest.name}: ${manifest.description}'/>`
|
? `<img src='${manifest.icons[0].src}' title='${manifest.name}: ${manifest.description}'/>`
|
||||||
|
@ -132,36 +177,105 @@ AFRAME.registerComponent('launcher', {
|
||||||
const add3D = (launchCom,el,manifest) => {
|
const add3D = (launchCom,el,manifest) => {
|
||||||
let aentity = document.createElement('a-entity')
|
let aentity = document.createElement('a-entity')
|
||||||
let atext = document.createElement('a-entity')
|
let atext = document.createElement('a-entity')
|
||||||
|
let padding = this.data.padding
|
||||||
|
if( (i % this.data.cols) == 0 ) j++
|
||||||
aentity.setAttribute("mixin","menuitem")
|
aentity.setAttribute("mixin","menuitem")
|
||||||
aentity.setAttribute("position",`${i++ * 0.2} 0 0`)
|
aentity.setAttribute("position",`${padding+(i++ % this.data.cols) * padding} ${j*padding} 0`)
|
||||||
if( !aentity.getAttribute("material")){
|
if( !aentity.getAttribute("material")){
|
||||||
aentity.setAttribute('material',`side: double; color: ${colors[ i % colors.length]}`)
|
aentity.setAttribute('material',`side: double; color: ${colors[ i % colors.length]}`)
|
||||||
}
|
}
|
||||||
|
aentity.addEventListener('obbcollisionstarted', this.onpress )
|
||||||
|
aentity.addEventListener('obbcollisionended', this.onreleased )
|
||||||
atext.setAttribute("text",`value: ${manifest.short_name}; align: baseline; anchor: align; align:center; wrapCount:7`)
|
atext.setAttribute("text",`value: ${manifest.short_name}; align: baseline; anchor: align; align:center; wrapCount:7`)
|
||||||
atext.setAttribute("scale","0.1 0.1 0.1")
|
atext.setAttribute("scale","0.1 0.1 0.1")
|
||||||
atext.setAttribute("position","0 0 0.0")
|
|
||||||
aentity.appendChild(atext)
|
aentity.appendChild(atext)
|
||||||
this.el.appendChild(aentity)
|
this.el.appendChild(aentity)
|
||||||
|
aentity.launchCom = launchCom
|
||||||
return aentity
|
return aentity
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally render them!
|
// finally render them!
|
||||||
this.el.dom.innerHTML = '' // clear
|
this.el.dom.innerHTML = '' // clear
|
||||||
this.system.components.map( (c) => {
|
this.system.components.map( (c) => {
|
||||||
const launchComponentKey = c.getAttributeNames().pop()
|
const launchComponentKey = c.getAttributeNames().shift()
|
||||||
const launchCom = c.components[ launchComponentKey ]
|
const launchCom = c.components[ launchComponentKey ]
|
||||||
if( !launchCom ) return console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`)
|
if( !launchCom ) return console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`)
|
||||||
const manifest = launchCom.manifest
|
const manifest = launchCom.manifest
|
||||||
if( manifest ){
|
if( manifest ){
|
||||||
add2D(launchCom,c,manifest, add3D(launchCom,c,manifest) )
|
add2D(launchCom,c,manifest)
|
||||||
|
add3D(launchCom,c,manifest)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if( this.data.attach ){
|
},
|
||||||
this.el.object3D.visible = inVR ? true : false
|
|
||||||
// if( inVR ) this.data.attach.appendChild(this.el)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onpress: function(e){
|
||||||
|
const launcher = document.querySelector('[launcher]').components.launcher
|
||||||
|
if( launcher.data.paused ) return // prevent accidental pressed due to animation
|
||||||
|
if( e.detail.withEl.computedMixinStr == 'menuitem' ) return // dont react to menuitems touching eachother
|
||||||
|
|
||||||
|
// if user press menu button toggle menu
|
||||||
|
if( launcher && e.srcElement.computedMixinStr == 'menubutton' ){
|
||||||
|
return launcher.data.open = !launcher.data.open
|
||||||
|
}
|
||||||
|
if( launcher && !launcher.data.open ) return // dont process menuitems when menu is closed
|
||||||
|
let el = e.srcElement
|
||||||
|
if(!el) return
|
||||||
|
el.object3D.traverse( (o) => {
|
||||||
|
if( o.material && o.material.color ){
|
||||||
|
if( !o.material.colorOriginal ) o.material.colorOriginal = o.material.color.clone()
|
||||||
|
o.material.color.r *= 0.3
|
||||||
|
o.material.color.g *= 0.3
|
||||||
|
o.material.color.b *= 0.3
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if( el.launchCom ){
|
||||||
|
console.log("launcher.js: launching "+el.launchCom.el.getAttributeNames().shift())
|
||||||
|
launcher.preventAccidentalButtonPresses()
|
||||||
|
el.launchCom.el.emit('launcher') // launch component!
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onreleased: function(e){
|
||||||
|
if( e.detail.withEl.computedMixinStr == 'menuitem' ) return // dont react to menuitems touching eachother
|
||||||
|
let el = e.srcElement
|
||||||
|
el.object3D.traverse( (o) => {
|
||||||
|
if( o.material && o.material.color ){
|
||||||
|
if( o.material.colorOriginal ) o.material.color = o.material.colorOriginal.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
attachMenu: function(){
|
||||||
|
if( this.el.parentNode != this.data.attach ){
|
||||||
|
this.el.object3D.visible = true
|
||||||
|
let armature = this.data.attach.object3D.getObjectByName('Armature')
|
||||||
|
if( !armature ) return console.warn('cannot find armature')
|
||||||
|
this.data.attach.object3D.children[0].add(this.el.object3D)
|
||||||
|
this.el.object3D.scale.x = this.data.rescale
|
||||||
|
this.el.object3D.scale.y = this.data.rescale
|
||||||
|
this.el.object3D.scale.z = this.data.rescale
|
||||||
|
|
||||||
|
// add obb-collider to index finger-tip
|
||||||
|
let aentity = document.createElement('a-entity')
|
||||||
|
trackedObject3DVariable = 'parentNode.components.hand-tracking-controls.bones.9';
|
||||||
|
this.data.fingerTip.appendChild(aentity)
|
||||||
|
aentity.setAttribute('obb-collider', {trackedObject3D: trackedObject3DVariable, size: 0.015});
|
||||||
|
|
||||||
|
if( this.isHand(this.data.attach) ){
|
||||||
|
// shortly show and hide menu into palm (hint user)
|
||||||
|
setTimeout( () => { this.data.open = false }, 1500 )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tick: function(){
|
||||||
|
if( this.data.open ){
|
||||||
|
let indexTipPosition = document.querySelector('#right-hand[hand-tracking-controls]').components['hand-tracking-controls'].indexTipPosition
|
||||||
|
this.el.object3D.getWorldPosition(this.worldPosition)
|
||||||
|
const lookingAtPalm = this.data.attach.components['hand-tracking-controls'].wristObject3D.rotation.z > 2.0
|
||||||
|
if( !lookingAtPalm ){ this.data.open = false }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
manifest: { // HTML5 manifest to identify app to xrsh
|
manifest: { // HTML5 manifest to identify app to xrsh
|
||||||
|
@ -223,7 +337,7 @@ AFRAME.registerSystem('launcher',{
|
||||||
|
|
||||||
init: function(){
|
init: function(){
|
||||||
this.components = []
|
this.components = []
|
||||||
// observer HTML changes in <a-scene>
|
// observe HTML changes in <a-scene>
|
||||||
observer = new MutationObserver( (a,b) => this.getLaunchables(a,b) )
|
observer = new MutationObserver( (a,b) => this.getLaunchables(a,b) )
|
||||||
observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
|
observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue