main: work in progress [might break]
/ mirror_to_github (push) Successful in 23s Details
/ test (push) Successful in 3s Details

This commit is contained in:
Leon van Kammen 2024-05-24 14:37:05 +00:00
parent 84361d303a
commit a4ddb52310
4 changed files with 201 additions and 34 deletions

44
com/data2event.js Normal file
View File

@ -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})
}
})
},
})

View File

@ -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]

View File

@ -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 )

View File

@ -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});
}, },