refactored spec, added focusLine, hashbus & XRWG
This commit is contained in:
parent
89e5af7d28
commit
0d3959359b
24 changed files with 367 additions and 261 deletions
68
src/3rd/js/XRWG.js
Normal file
68
src/3rd/js/XRWG.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// the XRWG (XR WordGraph)is mentioned in the spec
|
||||||
|
//
|
||||||
|
// it collects metadata-keys ('foo' e.g.), names and tags across 3D scene-nodes (.userData.foo e.g.)
|
||||||
|
|
||||||
|
let XRWG = xrf.XRWG = []
|
||||||
|
|
||||||
|
XRWG.word = (key) => XRWG.find( (w) => w.word == word )
|
||||||
|
|
||||||
|
XRWG.cleankey = (word) => String(word).replace(/[^0-9\.a-zA-Z_]/g,'')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/.*:\/\//,'')
|
||||||
|
XRWG.get = (v,k) => XRWG.find( (x) => x[ k || 'word'] == v )
|
||||||
|
|
||||||
|
XRWG.match = (str,types,level) => {
|
||||||
|
level = level || 1000
|
||||||
|
types = types || []
|
||||||
|
let res = XRWG.filter( (n) => {
|
||||||
|
types.map( (type) => n[type] ? n = false : false )
|
||||||
|
return n
|
||||||
|
})
|
||||||
|
str = str.toLowerCase()
|
||||||
|
if( level <10 ) res = res.filter( (n) => n.key == str )
|
||||||
|
if( level <20 ) res = res.filter( (n) => n.word == str || n.key == str )
|
||||||
|
if( level <30 ) res = res.filter( (n) => n.word.match(str) || n.key == str )
|
||||||
|
if( level <40 ) res = res.filter( (n) => n.word.match(str) || n.key == str || String(n.value||'').match(str) )
|
||||||
|
if( level <1001 ) res = res.filter( (n) => n.word.match(str) != null || n.key.match(str) != null || String(n.value||'').match(str) != null)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
XRWG.generate = (opts) => {
|
||||||
|
let {scene,model} = opts
|
||||||
|
XRWG.slice(0,0) // empty
|
||||||
|
|
||||||
|
// collect words from 3d nodes
|
||||||
|
|
||||||
|
let add = (key, spatialNode, type) => {
|
||||||
|
if( !key || key.match(/(^#$|name)/) ) return
|
||||||
|
let node = XRWG.get( XRWG.cleankey(key) )
|
||||||
|
if( node ){
|
||||||
|
node.nodes.push(spatialNode)
|
||||||
|
}else{
|
||||||
|
node = { word: XRWG.cleankey(key), key: key.toLowerCase(), nodes:[spatialNode] }
|
||||||
|
if( spatialNode.userData[key] ) node.value = spatialNode.userData[key]
|
||||||
|
node[type] = true
|
||||||
|
xrf.emit('XRWG',node)
|
||||||
|
XRWG.push( node )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.traverse( (o) => {
|
||||||
|
add( `#${o.name}`, o, 'name')
|
||||||
|
for( let k in o.userData ){
|
||||||
|
if( k == 'tag' ){
|
||||||
|
let tagArr = o.userData.tag.split(" ")
|
||||||
|
.map( (t) => t.trim() )
|
||||||
|
.filter( (t) => t )
|
||||||
|
.map( (w) => add( w, o, 'tag') )
|
||||||
|
}else if( k.match(/^(href|src)$/) ) add( o.userData[k], o, k)
|
||||||
|
else if( k[0] == '#' ) add( k, o , 'pv')
|
||||||
|
else add( k, o , 'query')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// sort by n
|
||||||
|
XRWG.sort( (a,b) => a.nodes.length - b.nodes.length )
|
||||||
|
XRWG = XRWG.reverse() // the cleankey/get functions e.g. will persist
|
||||||
|
console.dir(XRWG)
|
||||||
|
}
|
||||||
|
|
@ -2,81 +2,86 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
schema: {
|
schema: {
|
||||||
},
|
},
|
||||||
init: function () {
|
init: function () {
|
||||||
if( !AFRAME.XRF ) this.initXRFragments()
|
if( !AFRAME.XRF ){
|
||||||
|
document.querySelector('a-scene').addEventListener('loaded', () => {
|
||||||
|
|
||||||
|
//window.addEventListener('popstate', clear )
|
||||||
|
//window.addEventListener('pushstate', clear )
|
||||||
|
|
||||||
|
// enable XR fragments
|
||||||
|
let aScene = document.querySelector('a-scene')
|
||||||
|
let XRF = AFRAME.XRF = xrf.init({
|
||||||
|
THREE,
|
||||||
|
camera: aScene.camera,
|
||||||
|
scene: aScene.object3D,
|
||||||
|
renderer: aScene.renderer,
|
||||||
|
debug: true,
|
||||||
|
loaders: {
|
||||||
|
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
||||||
|
glb: THREE.GLTFLoader
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||||
|
|
||||||
|
// override the camera-related XR Fragments so the camera-rig is affected
|
||||||
|
let camOverride = (xrf,v,opts) => {
|
||||||
|
opts.camera = document.querySelector('[camera]').object3D.parent
|
||||||
|
xrf(v,opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
xrf.pos = camOverride
|
||||||
|
|
||||||
|
// in order to set the rotation programmatically
|
||||||
|
// we need to disable look-controls
|
||||||
|
xrf.rot = (xrf,v,opts) => {
|
||||||
|
let {frag,renderer} = opts;
|
||||||
|
if( frag.q ) return // camera was not targeted for rotation
|
||||||
|
let look = document.querySelector('[look-controls]')
|
||||||
|
if( look ) look.removeAttribute("look-controls")
|
||||||
|
// camOverride(xrf,v,opts)
|
||||||
|
// *TODO* make look-controls compatible, because simply
|
||||||
|
// adding the look-controls will revert to the old rotation (cached somehow?)
|
||||||
|
//setTimeout( () => look.setAttribute("look-controls",""), 100 )
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert portal to a-entity so AFRAME
|
||||||
|
// raycaster can find & execute it
|
||||||
|
xrf.href = (xrf,v,opts) => {
|
||||||
|
camOverride(xrf,v,opts)
|
||||||
|
let {mesh,camera} = opts;
|
||||||
|
let el = document.createElement("a-entity")
|
||||||
|
el.setAttribute("xrf-get",mesh.name )
|
||||||
|
el.setAttribute("class","ray")
|
||||||
|
el.addEventListener("click", mesh.userData.XRF.href.exec )
|
||||||
|
$('a-scene').appendChild(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup xrf-get objects when resetting scene
|
||||||
|
xrf.reset = ((reset) => () => {
|
||||||
|
reset()
|
||||||
|
console.log("aframe reset")
|
||||||
|
let els = [...document.querySelectorAll('[xrf-get]')]
|
||||||
|
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
||||||
|
})(XRF.reset)
|
||||||
|
|
||||||
|
// undo lookup-control shenanigans (which blocks updating camerarig position in VR)
|
||||||
|
aScene.addEventListener('enter-vr', () => document.querySelector('[camera]').object3D.parent.matrixAutoUpdate = true )
|
||||||
|
|
||||||
|
AFRAME.XRF.navigator.to(this.data)
|
||||||
|
.then( (model) => {
|
||||||
|
let gets = [ ...document.querySelectorAll('[xrf-get]') ]
|
||||||
|
gets.map( (g) => g.emit('update') )
|
||||||
|
})
|
||||||
|
|
||||||
|
aScene.emit('XRF',{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if( typeof this.data == "string" ){
|
if( typeof this.data == "string" ){
|
||||||
if( document.location.search || document.location.hash.length > 1 ){ // override url
|
if( document.location.search || document.location.hash.length > 1 ){ // override url
|
||||||
this.data = `${document.location.search.substr(1)}${document.location.hash}`
|
this.data = `${document.location.search.substr(1)}${document.location.hash}`
|
||||||
}
|
}
|
||||||
AFRAME.XRF.navigator.to(this.data)
|
|
||||||
.then( (model) => {
|
|
||||||
let gets = [ ...document.querySelectorAll('[xrf-get]') ]
|
|
||||||
gets.map( (g) => g.emit('update') )
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
initXRFragments: function(){
|
|
||||||
|
|
||||||
//window.addEventListener('popstate', clear )
|
|
||||||
//window.addEventListener('pushstate', clear )
|
|
||||||
|
|
||||||
// enable XR fragments
|
|
||||||
let aScene = document.querySelector('a-scene')
|
|
||||||
let XRF = AFRAME.XRF = xrf.init({
|
|
||||||
THREE,
|
|
||||||
camera: aScene.camera,
|
|
||||||
scene: aScene.object3D,
|
|
||||||
renderer: aScene.renderer,
|
|
||||||
debug: true,
|
|
||||||
loaders: {
|
|
||||||
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
|
||||||
glb: THREE.GLTFLoader
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
|
||||||
|
|
||||||
// override the camera-related XR Fragments so the camera-rig is affected
|
|
||||||
let camOverride = (xrf,v,opts) => {
|
|
||||||
opts.camera = document.querySelector('[camera]').object3D.parent
|
|
||||||
xrf(v,opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
xrf.pos = camOverride
|
|
||||||
|
|
||||||
// in order to set the rotation programmatically
|
|
||||||
// we need to disable look-controls
|
|
||||||
xrf.rot = (xrf,v,opts) => {
|
|
||||||
let {frag,renderer} = opts;
|
|
||||||
if( frag.q ) return // camera was not targeted for rotation
|
|
||||||
let look = document.querySelector('[look-controls]')
|
|
||||||
if( look ) look.removeAttribute("look-controls")
|
|
||||||
camOverride(xrf,v,opts)
|
|
||||||
// *TODO* make look-controls compatible, because simply
|
|
||||||
// adding the look-controls will revert to the old rotation (cached somehow?)
|
|
||||||
//setTimeout( () => look.setAttribute("look-controls",""), 100 )
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert portal to a-entity so AFRAME
|
|
||||||
// raycaster can find & execute it
|
|
||||||
xrf.href = (xrf,v,opts) => {
|
|
||||||
camOverride(xrf,v,opts)
|
|
||||||
let {mesh,camera} = opts;
|
|
||||||
let el = document.createElement("a-entity")
|
|
||||||
el.setAttribute("xrf-get",mesh.name )
|
|
||||||
el.setAttribute("class","ray")
|
|
||||||
el.addEventListener("click", mesh.userData.XRF.href.exec )
|
|
||||||
$('a-scene').appendChild(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup xrf-get objects when resetting scene
|
|
||||||
xrf.reset = ((reset) => () => {
|
|
||||||
reset()
|
|
||||||
console.log("aframe reset")
|
|
||||||
let els = [...document.querySelectorAll('[xrf-get]')]
|
|
||||||
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
|
||||||
})(XRF.reset)
|
|
||||||
|
|
||||||
// undo lookup-control shenanigans (which blocks updating camerarig position in VR)
|
|
||||||
aScene.addEventListener('enter-vr', () => document.querySelector('[camera]').object3D.parent.matrixAutoUpdate = true )
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ xrf.roundrobin = (frag, store) => {
|
||||||
return store.rr[label].index = 0
|
return store.rr[label].index = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xrf.hasTag = (tag,tags) => String(tags).match( new RegExp(`(^| )${tag}( |$)`,`g`) )
|
||||||
|
|
||||||
// map library functions to xrf
|
// map library functions to xrf
|
||||||
for ( let i in xrfragment ) xrf[i] = xrfragment[i]
|
for ( let i in xrfragment ) xrf[i] = xrfragment[i]
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ xrf.emit.promise = function(e, opts){
|
||||||
return { resolve, reject }
|
return { resolve, reject }
|
||||||
}
|
}
|
||||||
xrf.emit.normal(e, opts)
|
xrf.emit.normal(e, opts)
|
||||||
|
delete opts.XRF
|
||||||
if( !opts.promise.halted ) resolve()
|
if( !opts.promise.halted ) resolve()
|
||||||
|
delete opts.promise
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/3rd/js/three/hashbus.js
Normal file
46
src/3rd/js/three/hashbus.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// the hashbus (QueryString eventBus) is mentioned in the spec
|
||||||
|
//
|
||||||
|
// it allows metadata-keys ('foo' e.g.) of 3D scene-nodes (.userData.foo e.g.) to
|
||||||
|
// react by executing code
|
||||||
|
|
||||||
|
let pub = function( url, model, flags ){ // evaluate fragments in url
|
||||||
|
if( !url ) return
|
||||||
|
if( !url.match(/#/) ) url = `#${url}`
|
||||||
|
model = model || xrf.model
|
||||||
|
let { THREE, camera } = xrf
|
||||||
|
let frag = xrf.URI.parse( url, flags != undefined ? flags : xrf.XRF.NAVIGATOR )
|
||||||
|
let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE, hashbus: xrf.hashbus }
|
||||||
|
xrf.emit('hashbus',opts)
|
||||||
|
.then( () => {
|
||||||
|
for ( let k in frag ){
|
||||||
|
pub.fragment(k,opts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
|
||||||
|
pub.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) inside mesh of model
|
||||||
|
if( mesh.userData ){
|
||||||
|
let frag = {}
|
||||||
|
for( let k in mesh.userData ) xrf.Parser.parse( k, mesh.userData[k], frag )
|
||||||
|
for( let k in frag ){
|
||||||
|
let opts = {frag, mesh, model, camera: xrf.camera, scene: model.scene, renderer: xrf.renderer, THREE: xrf.THREE, hashbus: xrf.hashbus }
|
||||||
|
mesh.userData.XRF = frag // allow fragment impl to access XRF obj already
|
||||||
|
xrf.emit('mesh',opts)
|
||||||
|
.then( () => pub.fragment(k,opts) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub.fragment = (k, opts ) => { // evaluate one fragment
|
||||||
|
let frag = opts.frag[k];
|
||||||
|
// call native function (xrf/env.js e.g.), or pass it to user decorator
|
||||||
|
xrf.emit(k,opts)
|
||||||
|
.then( () => {
|
||||||
|
let func = xrf.frag[k] || function(){}
|
||||||
|
if( xrf[k] ) xrf[k]( func, frag, opts)
|
||||||
|
else func( frag, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
xrf.hashbus = { pub }
|
||||||
|
|
@ -43,56 +43,26 @@ xrf.parseModel = function(model,url){
|
||||||
let file = xrf.getFile(url)
|
let file = xrf.getFile(url)
|
||||||
model.file = file
|
model.file = file
|
||||||
// eval embedded XR fragments
|
// eval embedded XR fragments
|
||||||
model.scene.traverse( (mesh) => xrf.eval.mesh(mesh,model) )
|
model.scene.traverse( (mesh) => xrf.hashbus.pub.mesh(mesh,model) )
|
||||||
// add animations
|
// add animations
|
||||||
model.clock = new xrf.THREE.Clock();
|
model.clock = new xrf.THREE.Clock();
|
||||||
model.mixer = new xrf.THREE.AnimationMixer(model.scene)
|
model.mixer = new xrf.THREE.AnimationMixer(model.scene)
|
||||||
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() )
|
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() )
|
||||||
|
|
||||||
|
let tmp = new xrf.THREE.Vector3()
|
||||||
model.render = function(){
|
model.render = function(){
|
||||||
model.mixer.update( model.clock.getDelta() )
|
model.mixer.update( model.clock.getDelta() )
|
||||||
xrf.navigator.material.selection.color.r = (1.0 + Math.sin( model.clock.getElapsedTime() * 10 ))/2
|
|
||||||
|
// update focusline
|
||||||
|
xrf.focusLine.material.color.r = (1.0 + Math.sin( model.clock.getElapsedTime() ))/2
|
||||||
|
xrf.focusLine.material.dashSize = 0.2 + 0.02*Math.sin( model.clock.getElapsedTime() )
|
||||||
|
xrf.focusLine.material.gapSize = 0.1 + 0.02*Math.sin( model.clock.getElapsedTime() *3 )
|
||||||
|
xrf.focusLine.material.opacity = 0.25 + 0.15*Math.sin( model.clock.getElapsedTime() * 3 )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.getLastModel = () => xrf.model.last
|
xrf.getLastModel = () => xrf.model.last
|
||||||
|
|
||||||
xrf.eval = function( url, model, flags ){ // evaluate fragments in url
|
|
||||||
if( !url ) return
|
|
||||||
if( !url.match(/#/) ) url = `#${url}`
|
|
||||||
model = model || xrf.model
|
|
||||||
let { THREE, camera } = xrf
|
|
||||||
let frag = xrf.URI.parse( url, flags != undefined ? flags : xrf.XRF.NAVIGATOR )
|
|
||||||
let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE }
|
|
||||||
xrf.emit('eval',opts)
|
|
||||||
.then( () => {
|
|
||||||
for ( let k in frag ){
|
|
||||||
xrf.eval.fragment(k,opts)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return frag
|
|
||||||
}
|
|
||||||
|
|
||||||
xrf.eval.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) inside mesh of model
|
|
||||||
if( mesh.userData ){
|
|
||||||
let frag = {}
|
|
||||||
for( let k in mesh.userData ) xrf.Parser.parse( k, mesh.userData[k], frag )
|
|
||||||
for( let k in frag ){
|
|
||||||
let opts = {frag, mesh, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE }
|
|
||||||
mesh.userData.XRF = frag // allow fragment impl to access XRF obj already
|
|
||||||
xrf.emit('eval',opts)
|
|
||||||
.then( () => xrf.eval.fragment(k,opts) )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xrf.eval.fragment = (k, opts ) => { // evaluate one fragment
|
|
||||||
let frag = opts.frag[k];
|
|
||||||
// call native function (xrf/env.js e.g.), or pass it to user decorator
|
|
||||||
let func = xrf.frag[k] || function(){}
|
|
||||||
if( xrf[k] ) xrf[k]( func, frag, opts)
|
|
||||||
else func( frag, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
xrf.reset = () => {
|
xrf.reset = () => {
|
||||||
const disposeObject = (obj) => {
|
const disposeObject = (obj) => {
|
||||||
if (obj.children.length > 0) obj.children.forEach((child) => disposeObject(child));
|
if (obj.children.length > 0) obj.children.forEach((child) => disposeObject(child));
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ xrf.navigator = {}
|
||||||
xrf.navigator.to = (url,flags,loader,data) => {
|
xrf.navigator.to = (url,flags,loader,data) => {
|
||||||
if( !url ) throw 'xrf.navigator.to(..) no url given'
|
if( !url ) throw 'xrf.navigator.to(..) no url given'
|
||||||
|
|
||||||
|
let hashbus = xrf.hashbus
|
||||||
|
|
||||||
return new Promise( (resolve,reject) => {
|
return new Promise( (resolve,reject) => {
|
||||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
||||||
|
|
||||||
if( !file || xrf.model.file == file ){ // we're already loaded
|
if( !file || xrf.model.file == file ){ // we're already loaded
|
||||||
xrf.eval( url, xrf.model, flags ) // and eval local URI XR fragments
|
hashbus.pub( url, xrf.model, flags ) // and eval local URI XR fragments
|
||||||
xrf.navigator.updateHash(hash)
|
xrf.navigator.updateHash(hash)
|
||||||
return resolve(xrf.model)
|
return resolve(xrf.model)
|
||||||
}
|
}
|
||||||
|
|
@ -29,14 +31,13 @@ xrf.navigator.to = (url,flags,loader,data) => {
|
||||||
// only change url when loading *another* file
|
// only change url when loading *another* file
|
||||||
if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash )
|
if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash )
|
||||||
xrf.model = model
|
xrf.model = model
|
||||||
|
// spec: 1. generate the XRWG
|
||||||
|
xrf.XRWG.generate({model,scene:model.scene})
|
||||||
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
||||||
xrf.frag.defaultPredefinedView({model,scene:model.scene})
|
xrf.frag.defaultPredefinedView({model,scene:model.scene})
|
||||||
// spec: 2. execute predefined view(s) from URL (https://xrfragment.org/#predefined_view)
|
// spec: 2. execute predefined view(s) from URL (https://xrfragment.org/#predefined_view)
|
||||||
xrf.eval( url, model ) // and eval URI XR fragments
|
hashbus.pub( url, model ) // and eval URI XR fragments
|
||||||
xrf.add( model.scene )
|
xrf.add( model.scene )
|
||||||
if( !hash.match(/pos=/) ){
|
|
||||||
xrf.eval( '#pos=0,0,0' ) // set default position if not specified
|
|
||||||
}
|
|
||||||
xrf.navigator.updateHash(hash)
|
xrf.navigator.updateHash(hash)
|
||||||
resolve(model)
|
resolve(model)
|
||||||
}
|
}
|
||||||
|
|
@ -51,21 +52,28 @@ xrf.navigator.init = () => {
|
||||||
window.addEventListener('popstate', function (event){
|
window.addEventListener('popstate', function (event){
|
||||||
xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
||||||
})
|
})
|
||||||
xrf.navigator.material = {
|
|
||||||
selection: new xrf.THREE.LineBasicMaterial({color:0xFF00FF,linewidth:2})
|
// this allows selectionlines to be updated according to the camera (renderloop)
|
||||||
}
|
xrf.focusLine = new xrf.THREE.Group()
|
||||||
|
xrf.focusLine.material = new xrf.THREE.LineDashedMaterial({color:0xFF00FF,linewidth:3, scale: 1, dashSize: 0.2, gapSize: 0.1,opacity:0.3, transparent:true})
|
||||||
|
xrf.focusLine.isXRF = true
|
||||||
|
xrf.focusLine.position.set(0,0,-0.5);
|
||||||
|
xrf.focusLine.points = []
|
||||||
|
xrf.focusLine.lines = []
|
||||||
|
xrf.camera.add(xrf.focusLine)
|
||||||
|
|
||||||
xrf.navigator.init.inited = true
|
xrf.navigator.init.inited = true
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.navigator.updateHash = (hash) => {
|
xrf.navigator.updateHash = (hash,opts) => {
|
||||||
if( hash == document.location.hash || hash.match(/\|/) ) return // skip unnecesary pushState triggers
|
if( hash.replace(/^#/,'') == document.location.hash.substr(1) || hash.match(/\|/) ) return // skip unnecesary pushState triggers
|
||||||
console.log(`URL: ${document.location.search.substr(1)}#${hash}`)
|
console.log(`URL: ${document.location.search.substr(1)}#${hash}`)
|
||||||
document.location.hash = hash
|
document.location.hash = hash
|
||||||
xrf.emit('updateHash', {hash} )
|
xrf.emit('hash', {...opts, hash: `#${hash}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.navigator.pushState = (file,hash) => {
|
xrf.navigator.pushState = (file,hash) => {
|
||||||
if( file == document.location.search.substr(1) ) return // page is in its default state
|
if( file == document.location.search.substr(1) ) return // page is in its default state
|
||||||
console.log("pushstate")
|
|
||||||
window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` )
|
window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` )
|
||||||
|
xrf.emit('pushState', {file, hash} )
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ xrf.frag.href = function(v, opts){
|
||||||
|
|
||||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||||
let isLocal = v.string[0] == '#'
|
let isLocal = v.string[0] == '#'
|
||||||
let lastPos = `pos=${camera.position.x.toFixed(1)},${camera.position.y.toFixed(1)},${camera.position.z.toFixed(1)}`
|
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
|
||||||
xrf
|
xrf
|
||||||
.emit('href',{click:true,mesh,xrf:v}) // let all listeners agree
|
.emit('href',{click:true,mesh,xrf:v}) // let all listeners agree
|
||||||
.then( () => {
|
.then( () => {
|
||||||
|
|
@ -100,7 +100,7 @@ xrf.frag.href = function(v, opts){
|
||||||
if( !v.string.match(/pos=/) ) v.string += `${v.string[0] == '#' ? '&' : '#'}${lastPos}`
|
if( !v.string.match(/pos=/) ) v.string += `${v.string[0] == '#' ? '&' : '#'}${lastPos}`
|
||||||
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
|
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
|
||||||
|
|
||||||
xrf.navigator.to(v.string,flags) // let's surf to HREF!
|
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,53 +13,78 @@ xrf.frag.updatePredefinedView = (opts) => {
|
||||||
let id = frag.string
|
let id = frag.string
|
||||||
let oldSelection
|
let oldSelection
|
||||||
if(!id) return id // important: ignore empty strings
|
if(!id) return id // important: ignore empty strings
|
||||||
if( mesh.selection ) oldSelection = mesh.selection
|
|
||||||
// Selection of Interest if predefined_view matches object name
|
// Selection of Interest if predefined_view matches object name
|
||||||
if( mesh.visible && (id == mesh.name || id.substr(1) == mesh.userData.class) ){
|
if( mesh.visible ){
|
||||||
xrf.emit('selection',{...opts,frag})
|
xrf.emit('focus',{...opts,frag})
|
||||||
.then( () => {
|
.then( () => {
|
||||||
const margin = 1.2
|
const color = new THREE.Color();
|
||||||
mesh.scale.multiplyScalar( margin )
|
const colors = []
|
||||||
mesh.selection = new xrf.THREE.BoxHelper(mesh,0xff00ff)
|
let from = new THREE.Vector3()
|
||||||
mesh.scale.divideScalar( margin )
|
|
||||||
mesh.selection.material.dispose()
|
|
||||||
mesh.selection.material = xrf.navigator.material.selection
|
|
||||||
mesh.selection.isXRF = true
|
|
||||||
scene.add(mesh.selection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return oldSelection
|
|
||||||
}
|
|
||||||
|
|
||||||
// spec: https://xrfragment.org/#predefined_view
|
let getCenterPoint = (mesh) => {
|
||||||
const predefinedView = (frag,scene,mesh) => {
|
var geometry = mesh.geometry;
|
||||||
let id = frag.string || frag.fragment
|
geometry.computeBoundingBox();
|
||||||
id = `#${id}`
|
var center = new THREE.Vector3();
|
||||||
if( id == '##' ) id = '#'; // default predefined view
|
geometry.boundingBox.getCenter( center );
|
||||||
if( !id ) return // prevent empty matches
|
mesh.localToWorld( center );
|
||||||
if( mesh.userData[id] ){ // get alias
|
return center;
|
||||||
frag = xrf.URI.parse( mesh.userData[id], xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
|
||||||
xrf.emit('predefinedView',{...opts,frag})
|
|
||||||
.then( () => {
|
|
||||||
for ( let k in frag ){
|
|
||||||
let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE }
|
|
||||||
if( frag[k].is( xrf.XRF.PV_EXECUTE ) && scene.XRF_PV_ORIGIN != k ){ // cyclic detection
|
|
||||||
traverseScene(frag[k],scene) // recurse predefined views
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xrf.camera.updateMatrixWorld(true); // always keeps me diving into the docs :]
|
||||||
|
xrf.camera.getWorldPosition(from)
|
||||||
|
from.y -= 0.5 // originate from the heart chakra! :p
|
||||||
|
const points = [from, getCenterPoint(mesh) ]
|
||||||
|
const geometry = new THREE.BufferGeometry().setFromPoints( points );
|
||||||
|
let line = new THREE.Line( geometry, xrf.focusLine.material );
|
||||||
|
line.isXRF = true
|
||||||
|
line.computeLineDistances();
|
||||||
|
xrf.focusLine.lines.push(line)
|
||||||
|
xrf.focusLine.points.push(from)
|
||||||
|
scene.add(line)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const traverseScene = (v,scene) => {
|
//// spec: https://xrfragment.org/#predefined_view
|
||||||
let remove = []
|
//const predefinedView = (frag,scene,mesh) => {
|
||||||
|
// let id = frag.string || frag.fragment
|
||||||
|
// id = `#${id}`
|
||||||
|
// if( id == '##' ) id = '#'; // default predefined view
|
||||||
|
// if( !id ) return // prevent empty matches
|
||||||
|
// if( mesh.userData[id] ){ // get alias
|
||||||
|
// frag = xrf.URI.parse( mesh.userData[id], xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||||
|
// xrf.emit('predefinedView',{...opts,frag})
|
||||||
|
// .then( () => {
|
||||||
|
// for ( let k in frag ){
|
||||||
|
// let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE }
|
||||||
|
// if( frag[k].is( xrf.XRF.PV_EXECUTE ) && scene.XRF_PV_ORIGIN != k ){ // cyclic detection
|
||||||
|
// highlightInScene(frag[k],scene) // recurse predefined views
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
const highlightInScene = (v,scene) => {
|
||||||
if( !scene ) return
|
if( !scene ) return
|
||||||
scene.traverse( (mesh) => {
|
let remove = []
|
||||||
remove.push( selectionOfInterest( v, scene, mesh ) )
|
let id = v.string || v.fragment
|
||||||
predefinedView( v , scene, mesh )
|
if( id == '#' ) return
|
||||||
})
|
let match = xrf.XRWG.match(id)
|
||||||
remove.filter( (e) => e ).map( (selection) => {
|
console.dir({id,match,XRWG:xrf.XRWG})
|
||||||
scene.remove(selection)
|
// erase previous lines
|
||||||
|
xrf.focusLine.lines.map( (line) => scene.remove(line) )
|
||||||
|
xrf.focusLine.points = []
|
||||||
|
xrf.focusLine.lines = []
|
||||||
|
|
||||||
|
scene.traverse( (n) => n.selection ? remove.push(n) : false )
|
||||||
|
remove.map( (n) => scene.remove(n.selection) )
|
||||||
|
// create new selections
|
||||||
|
match.map( (w) => {
|
||||||
|
w.nodes.map( (mesh) => {
|
||||||
|
if( mesh.material )
|
||||||
|
selectionOfInterest( v, scene, mesh )
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,15 +99,16 @@ xrf.frag.updatePredefinedView = (opts) => {
|
||||||
if( v.is( xrf.XRF.PV_EXECUTE ) ){
|
if( v.is( xrf.XRF.PV_EXECUTE ) ){
|
||||||
scene.XRF_PV_ORIGIN = v.string
|
scene.XRF_PV_ORIGIN = v.string
|
||||||
// wait for nested instances to arrive at the scene ?
|
// wait for nested instances to arrive at the scene ?
|
||||||
traverseScene(v,scene)
|
highlightInScene(v,scene)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// react to url changes
|
// react to enduser typing url
|
||||||
xrf.addEventListener('updateHash', (opts) => {
|
xrf.addEventListener('hash', (opts) => {
|
||||||
let frag = xrf.URI.parse( opts.hash, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
let frag = xrf.URI.parse( opts.hash, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||||
|
console.dir({opts,frag})
|
||||||
xrf.frag.updatePredefinedView({frag,scene:xrf.scene})
|
xrf.frag.updatePredefinedView({frag,scene:xrf.scene})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -92,10 +118,3 @@ xrf.addEventListener('href', (opts) => {
|
||||||
let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||||
xrf.frag.updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf})
|
xrf.frag.updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf})
|
||||||
})
|
})
|
||||||
|
|
||||||
//let updateUrl = (opts) => {
|
|
||||||
// console.dir(opts)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//xrf.addEventListener('predefinedView', updateUrl )
|
|
||||||
//xrf.addEventListener('selection', updateUrl )
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ xrf.frag.q = function(v, opts){
|
||||||
scene.traverse( (o) => {
|
scene.traverse( (o) => {
|
||||||
for ( let name in v.query ) {
|
for ( let name in v.query ) {
|
||||||
let qobj = v.query[name];
|
let qobj = v.query[name];
|
||||||
if( qobj.class && o.userData.class && o.userData.class == name ) objs.push(o)
|
if( qobj.tag && o.userData.tag && xrf.hasTag(name,o.userData.tag) ) objs.push(o)
|
||||||
else if( qobj.id && o.name == name ) objs.push(o)
|
else if( qobj.id && o.name == name ) objs.push(o)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -26,16 +26,15 @@ xrf.frag.q = function(v, opts){
|
||||||
|
|
||||||
xrf.frag.q.filter = function(scene,frag){
|
xrf.frag.q.filter = function(scene,frag){
|
||||||
// spec: https://xrfragment.org/#queries
|
// spec: https://xrfragment.org/#queries
|
||||||
let q = frag.q.query
|
let q = frag.q.query
|
||||||
scene.traverse( (mesh) => {
|
scene.traverse( (mesh) => {
|
||||||
for ( let i in q ) {
|
for ( let i in q ) {
|
||||||
let isMeshId = q[i].id != undefined
|
let isMeshId = q[i].id != undefined
|
||||||
let isMeshClass = q[i].class != undefined
|
let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId
|
||||||
let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId && !isMeshClass
|
|
||||||
if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.)
|
if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.)
|
||||||
if( isMeshId && i == mesh.name ) mesh.visible = q[i].id
|
if( isMeshId &&
|
||||||
if( isMeshClass && i == mesh.userData.class ) mesh.visible = q[i].class
|
(i == mesh.name || xrf.hasTag(i,mesh.userData.tag))) mesh.visible = q[i].id
|
||||||
if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i])
|
if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xrf.frag.src = function(v, opts){
|
xrf.frag.src = function(v, opts){
|
||||||
|
|
||||||
opts.embedded = v // indicate embedded XR fragment
|
opts.embedded = v // indicate embedded XR fragment
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE, hashbus} = opts
|
||||||
|
|
||||||
console.log(" └ instancing src")
|
console.log(" └ instancing src")
|
||||||
let src = new THREE.Group()
|
let src = new THREE.Group()
|
||||||
|
|
@ -15,24 +15,26 @@ xrf.frag.src = function(v, opts){
|
||||||
// cherrypicking of object(s)
|
// cherrypicking of object(s)
|
||||||
if( !frag.q ){
|
if( !frag.q ){
|
||||||
for( var i in frag ){
|
for( var i in frag ){
|
||||||
if( scene.getObjectByName(i) ) src.add( obj = scene.getObjectByName(i).clone() )
|
if( scene.getObjectByName(i) ) src.add( obj = scene.getObjectByName(i).clone(true) )
|
||||||
xrf.eval.fragment(i, Object.assign(opts,{frag, model,scene}))
|
hashbus.pub.fragment(i, Object.assign(opts,{frag, model,scene}))
|
||||||
}
|
}
|
||||||
if( src.children.length == 1 ) obj.position.set(0,0,0);
|
if( src.children.length == 1 ) obj.position.set(0,0,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// filtering of objects using query
|
// filtering of objects using query
|
||||||
if( frag.q ){
|
if( frag.q ){
|
||||||
src = scene.clone();
|
src = scene.clone(true);
|
||||||
src.isSRC = true;
|
src.isSRC = src.isXRF = true;
|
||||||
xrf.frag.q.filter(src,frag)
|
xrf.frag.q.filter(src,frag)
|
||||||
}
|
}
|
||||||
src.traverse( (m) => {
|
src.traverse( (m) => {
|
||||||
m.isSRC = true
|
src.isSRC = src.isXRF = true;
|
||||||
if( m.userData && (m.userData.src || m.userData.href) ) return ; // prevent infinite recursion
|
if( m.userData && (m.userData.src || m.userData.href) ) return ; // prevent infinite recursion
|
||||||
xrf.eval.mesh(m,{scene,recursive:true}) // cool idea: recursion-depth based distance between face & src
|
hashbus.pub.mesh(m,{scene,recursive:true}) // cool idea: recursion-depth based distance between face & src
|
||||||
})
|
})
|
||||||
xrf.frag.src.scale( src, opts )
|
xrf.frag.src.scale( src, opts )
|
||||||
|
xrf.frag.src.eval( src, opts )
|
||||||
|
mesh.add( src )
|
||||||
}
|
}
|
||||||
|
|
||||||
const externalSRC = () => {
|
const externalSRC = () => {
|
||||||
|
|
@ -52,24 +54,23 @@ xrf.frag.src = function(v, opts){
|
||||||
else externalSRC() // external file
|
else externalSRC() // external file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xrf.frag.src.eval = function(scene, opts, url){
|
||||||
|
let { mesh, model, camera, renderer, THREE, hashbus} = opts
|
||||||
|
if( url ){
|
||||||
|
let frag = xrfragment.URI.parse(url)
|
||||||
|
// scale URI XR Fragments (queries) inside src-value
|
||||||
|
for( var i in frag ){
|
||||||
|
hashbus.pub.fragment(i, Object.assign(opts,{frag, model:{scene},scene}))
|
||||||
|
}
|
||||||
|
hashbus.pub( '#', {scene} ) // execute the default projection '#' (if exist)
|
||||||
|
hashbus.pub( url, {scene} ) // and eval URI XR fragments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
|
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
|
||||||
xrf.frag.src.scale = function(scene, opts, url){
|
xrf.frag.src.scale = function(scene, opts, url){
|
||||||
let { mesh, model, camera, renderer, THREE} = opts
|
let { mesh, model, camera, renderer, THREE} = opts
|
||||||
let restrictToBoundingBox = mesh.geometry
|
let restrictToBoundingBox = mesh.geometry
|
||||||
if( url ){
|
|
||||||
let frag = xrfragment.URI.parse(url)
|
|
||||||
console.log("parse url:"+url)
|
|
||||||
console.log("children:"+scene.children.length)
|
|
||||||
// scale URI XR Fragments (queries) inside src-value
|
|
||||||
for( var i in frag ){
|
|
||||||
xrf.eval.fragment(i, Object.assign(opts,{frag, model:{scene},scene}))
|
|
||||||
}
|
|
||||||
//if( frag.q ) scene = frag.q.scene
|
|
||||||
//xrf.add( model.scene )
|
|
||||||
xrf.eval( '#', {scene} ) // execute the default projection '#' (if exist)
|
|
||||||
xrf.eval( url, {scene} ) // and eval URI XR fragments
|
|
||||||
//if( !hash.match(/pos=/) ) // xrf.eval( '#pos=0,0,0' ) // set default position if not specified
|
|
||||||
}
|
|
||||||
if( restrictToBoundingBox ){
|
if( restrictToBoundingBox ){
|
||||||
// spec 3 of https://xrfragment.org/#src
|
// spec 3 of https://xrfragment.org/#src
|
||||||
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
|
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
|
||||||
|
|
@ -86,7 +87,6 @@ xrf.frag.src.scale = function(scene, opts, url){
|
||||||
scene.scale.multiply( mesh.scale )
|
scene.scale.multiply( mesh.scale )
|
||||||
}
|
}
|
||||||
scene.isXRF = model.scene.isSRC = true
|
scene.isXRF = model.scene.isSRC = true
|
||||||
mesh.add( scene )
|
|
||||||
if( !opts.recursive && mesh.material ) mesh.material.visible = false // lets hide the preview object because deleting disables animations+nested objs
|
if( !opts.recursive && mesh.material ) mesh.material.visible = false // lets hide the preview object because deleting disables animations+nested objs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,6 +112,7 @@ xrf.frag.src.type['unknown'] = function( url, opts ){
|
||||||
|
|
||||||
xrf.frag.src.type['model/gltf+json'] = function( url, opts ){
|
xrf.frag.src.type['model/gltf+json'] = function( url, opts ){
|
||||||
return new Promise( (resolve,reject) => {
|
return new Promise( (resolve,reject) => {
|
||||||
|
let {mesh} = opts
|
||||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
||||||
let loader
|
let loader
|
||||||
|
|
||||||
|
|
@ -124,6 +125,8 @@ xrf.frag.src.type['model/gltf+json'] = function( url, opts ){
|
||||||
|
|
||||||
const onLoad = (model) => {
|
const onLoad = (model) => {
|
||||||
xrf.frag.src.scale( model.scene, {...opts, model, scene: model.scene}, url )
|
xrf.frag.src.scale( model.scene, {...opts, model, scene: model.scene}, url )
|
||||||
|
xrf.frag.src.eval( model.scene, {...opts, model, scene: model.scene}, url )
|
||||||
|
mesh.add( model.scene )
|
||||||
resolve(model)
|
resolve(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('bg', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.bg ){
|
console.log("└ bg "+v.x+","+v.y+","+v.z);
|
||||||
console.log("└ bg "+v.x+","+v.y+","+v.z);
|
if( scene.background ) delete scene.background
|
||||||
if( scene.background ) delete scene.background
|
scene.background = new THREE.Color( v.x, v.y, v.z )
|
||||||
scene.background = new THREE.Color( v.x, v.y, v.z )
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('env', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.env && !scene.environment ){
|
if( frag.env && !scene.environment ){
|
||||||
let env = mesh.getObjectByName(frag.env.string)
|
let env = scene.getObjectByName(frag.env.string)
|
||||||
if( !env ) return console.warn("xrf.env "+v.string+" not found")
|
if( !env ) env = xrf.scene.getObjectByName(frag.env.string) // repurpose from parent scene
|
||||||
|
if( !env ) return console.warn("xrf.env "+frag.env.string+" not found")
|
||||||
env.material.map.mapping = THREE.EquirectangularReflectionMapping;
|
env.material.map.mapping = THREE.EquirectangularReflectionMapping;
|
||||||
scene.environment = env.material.map
|
scene.environment = env.material.map
|
||||||
//scene.texture = env.material.map
|
//scene.texture = env.material.map
|
||||||
|
|
@ -10,4 +11,5 @@ xrf.addEventListener('eval', (opts) => {
|
||||||
renderer.toneMappingExposure = 2;
|
renderer.toneMappingExposure = 2;
|
||||||
console.log(` └ applied image '${frag.env.string}' as environment map`)
|
console.log(` └ applied image '${frag.env.string}' as environment map`)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('fog', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.fog ){
|
let v = frag.fog
|
||||||
let v = frag.fog
|
console.log("└ fog "+v.x+","+v.y);
|
||||||
console.log("└ fog "+v.x+","+v.y);
|
if( v.x == 0 && v.y == 0 ){
|
||||||
if( v.x == 0 && v.y == 0 ){
|
if( scene.fog ) delete scene.fog
|
||||||
if( scene.fog ) delete scene.fog
|
scene.fog = null;
|
||||||
scene.fog = null;
|
}else scene.fog = new THREE.Fog( scene.background, v.x, v.y );
|
||||||
}else scene.fog = new THREE.Fog( scene.background, v.x, v.y );
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
xrf.macros = {}
|
xrf.macros = {}
|
||||||
|
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('mesh', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE, hashbus} = opts
|
||||||
|
|
||||||
for( let k in frag ){
|
for( let k in frag ){
|
||||||
let id = mesh.name+"_"+k
|
let id = mesh.name+"_"+k
|
||||||
let fragment = frag[k]
|
let fragment = frag[k]
|
||||||
|
|
@ -25,8 +26,7 @@ xrf.addEventListener('eval', (opts) => {
|
||||||
if( xrf.macros[ rrFrag ] ){
|
if( xrf.macros[ rrFrag ] ){
|
||||||
xrf.macros[ rrFrag ].trigger()
|
xrf.macros[ rrFrag ].trigger()
|
||||||
} else {
|
} else {
|
||||||
if( rrFrag[0] == '#' ) xrf.navigator.updateHash(rrFrag)
|
xrf.navigator.to( rrFrag,null,0)
|
||||||
else xrf.eval(rrFrag,null,0)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('mov', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.mov && frag.q ){
|
if( frag.mov && frag.q ){
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('pos', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.pos && frag.q ){
|
if( frag.pos && frag.q ){
|
||||||
// apply roundrobin (if any)
|
// apply roundrobin (if any)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('rot', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.rot && frag.q ){
|
if( frag.rot && frag.q ){
|
||||||
// apply roundrobin (if any)
|
// apply roundrobin (if any)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('scale', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.scale && frag.q ){
|
if( frag.scale && frag.q ){
|
||||||
// apply roundrobin (if any)
|
// apply roundrobin (if any)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
xrf.addEventListener('eval', (opts) => {
|
xrf.addEventListener('show', (opts) => {
|
||||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
if( frag.show && frag.q ){
|
if( frag.show && frag.q ){
|
||||||
let show = frag.show
|
let show = frag.show
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
[
|
[
|
||||||
{"fn":"query","data":"class:bar", "expect":{ "fn":"testProperty","input":["class","bar"],"out":true}},
|
{"fn":"query","data":"tag:bar", "expect":{ "fn":"testProperty","input":["tag","bar"],"out":true}},
|
||||||
{"fn":"query","data":".bar", "expect":{ "fn":"testProperty","input":["class","bar"],"out":true}, "label":".bar shorthand"},
|
{"fn":"query","data":"tag:bar -tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":false}},
|
||||||
{"fn":"query","data":".bar -.foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":false}},
|
{"fn":"query","data":"tag:bar -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true}},
|
||||||
{"fn":"query","data":".bar -.foo .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true}},
|
{"fn":"query","data":"tag:bar -tag:bar tag:bar", "expect":{ "fn":"testProperty","input":["tag","bar"],"out":true}},
|
||||||
{"fn":"query","data":".bar -.bar .bar", "expect":{ "fn":"testProperty","input":["class","bar"],"out":true}},
|
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||||
{"fn":"query","data":".foo -.foo .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true},"label":"class:foo"},
|
{"fn":"query","data":"tag:foo -tag:foo bar:5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||||
{"fn":"query","data":".foo -.foo bar:5 .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true},"label":"class:foo"},
|
{"fn":"query","data":"tag:foo -tag:foo bar:>5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||||
{"fn":"query","data":".foo -.foo bar:>5 .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true},"label":"class:foo"},
|
{"fn":"query","data":"tag:foo -tag:foo bar:>5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||||
{"fn":"query","data":".foo -.foo bar:>5 .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true},"label":"class:foo"},
|
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||||
{"fn":"query","data":".foo -.foo .foo", "expect":{ "fn":"testProperty","input":["class","foo"],"out":true},"label":"class:foo"},
|
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":true},"label":"id:foo"},
|
||||||
{"fn":"query","data":".foo -.foo .foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":false},"label":"!id:foo"},
|
{"fn":"query","data":"tag:foo -foo foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":true},"label":"id:foo?"},
|
||||||
{"fn":"query","data":"foo -foo foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":true},"label":"id:foo?"},
|
|
||||||
{"fn":"query","data":"/foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":true},"label":"foo should be root-only"},
|
{"fn":"query","data":"/foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":true},"label":"foo should be root-only"},
|
||||||
{"fn":"query","data":"/foo foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":false},"label":"foo should recursively selected"}
|
{"fn":"query","data":"/foo foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":false},"label":"foo should recursively selected"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@
|
||||||
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed (multiple)"},
|
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed (multiple)"},
|
||||||
{"fn":"url","data":"#cube.position.x=music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test data assign"},
|
{"fn":"url","data":"#cube.position.x=music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test data assign"},
|
||||||
{"fn":"url","data":"#cube.position.x=@music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test one-way data bind"},
|
{"fn":"url","data":"#cube.position.x=@music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test one-way data bind"},
|
||||||
{"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"_mycustom","out":true},"label":"test custom property"}
|
{"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test custom property"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class Parser {
|
||||||
|
|
||||||
// category: href navigation / portals / teleporting
|
// category: href navigation / portals / teleporting
|
||||||
Frag.set("href", XRF.ASSET | XRF.T_URL | XRF.T_PREDEFINED_VIEW );
|
Frag.set("href", XRF.ASSET | XRF.T_URL | XRF.T_PREDEFINED_VIEW );
|
||||||
Frag.set("class", XRF.ASSET | XRF.T_STRING );
|
Frag.set("tag", XRF.ASSET | XRF.T_STRING );
|
||||||
|
|
||||||
// category: query selector / object manipulation
|
// category: query selector / object manipulation
|
||||||
Frag.set("pos", XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
|
Frag.set("pos", XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,10 @@ class Query {
|
||||||
// 1. requirement: receive arguments: query (string)
|
// 1. requirement: receive arguments: query (string)
|
||||||
|
|
||||||
private var str:String = "";
|
private var str:String = "";
|
||||||
private var q:haxe.DynamicAccess<Dynamic> = {}; // 1. create an associative array/object to store query-arguments as objects
|
private var q:haxe.DynamicAccess<Dynamic> = {}; // 1. create an associative array/object to store query-arguments as objects
|
||||||
private var isProp:EReg = ~/^.*:[><=!]?/; // 1. detect object id's & properties `foo:1` and `foo` (reference regex: `/^.*:[><=!]?/` )
|
private var isProp:EReg = ~/^.*:[><=!]?/; // 1. detect object id's & properties `foo:1` and `foo` (reference regex: `/^.*:[><=!]?/` )
|
||||||
private var isExclude:EReg = ~/^-/; // 1. detect excluders like `-foo`,`-foo:1`,`-.foo`,`-/foo` (reference regex: `/^-/` )
|
private var isExclude:EReg = ~/^-/; // 1. detect excluders like `-foo`,`-foo:1`,`-.foo`,`-/foo` (reference regex: `/^-/` )
|
||||||
private var isRoot:EReg = ~/^[-]?\//; // 1. detect root selectors like `/foo` (reference regex: `/^[-]?\//` )
|
private var isRoot:EReg = ~/^[-]?\//; // 1. detect root selectors like `/foo` (reference regex: `/^[-]?\//` )
|
||||||
private var isClass:EReg = ~/^[-]?class$/; // 1. detect class selectors like `.foo` (reference regex: `/^[-]?class$/` )
|
|
||||||
private var isNumber:EReg = ~/^[0-9\.]+$/; // 1. detect number values like `foo:1` (reference regex: `/^[0-9\.]+$/` )
|
private var isNumber:EReg = ~/^[0-9\.]+$/; // 1. detect number values like `foo:1` (reference regex: `/^[0-9\.]+$/` )
|
||||||
|
|
||||||
public function new(str:String){
|
public function new(str:String){
|
||||||
|
|
@ -66,12 +65,6 @@ class Query {
|
||||||
return this.q;
|
return this.q;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function expandAliases(token:String) : String {
|
|
||||||
// expand '.foo' to 'class:foo'
|
|
||||||
var classAlias = ~/^(-)?\./;
|
|
||||||
return classAlias.match(token) ? StringTools.replace(token,".","class:") : token; // 1. expand aliases like `.foo` into `class:foo`
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get() : Dynamic {
|
public function get() : Dynamic {
|
||||||
return this.q;
|
return this.q;
|
||||||
}
|
}
|
||||||
|
|
@ -102,16 +95,11 @@ class Query {
|
||||||
k = k.substr(1); // 1. then strip key-operator: convert "-foo" into "foo"
|
k = k.substr(1); // 1. then strip key-operator: convert "-foo" into "foo"
|
||||||
}else v = v.substr(oper.length); // 1. then strip value operator: change value ">=foo" into "foo"
|
}else v = v.substr(oper.length); // 1. then strip value operator: change value ">=foo" into "foo"
|
||||||
if( oper.length == 0 ) oper = "=";
|
if( oper.length == 0 ) oper = "=";
|
||||||
if( isClass.match(k) ){
|
var rule:haxe.DynamicAccess<Dynamic> = {};
|
||||||
filter[ prefix+ k ] = oper != "!=";
|
if( isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v);
|
||||||
q.set(v,filter);
|
else rule[oper] = v;
|
||||||
}else{
|
filter['rules'].push( rule ); // 1. add operator and value to rule-array
|
||||||
var rule:haxe.DynamicAccess<Dynamic> = {};
|
q.set( k, filter );
|
||||||
if( isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v);
|
|
||||||
else rule[oper] = v;
|
|
||||||
filter['rules'].push( rule ); // 1. add operator and value to rule-array
|
|
||||||
q.set( k, filter );
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}else{ // 1. <b>ELSE </b> we are dealing with an object
|
}else{ // 1. <b>ELSE </b> we are dealing with an object
|
||||||
filter[ "id" ] = isExclude.match(str) ? false: true; // 1. therefore we we set `id` to `true` or `false` (false=excluder `-`)
|
filter[ "id" ] = isExclude.match(str) ? false: true; // 1. therefore we we set `id` to `true` or `false` (false=excluder `-`)
|
||||||
|
|
@ -121,7 +109,7 @@ class Query {
|
||||||
q.set( str ,filter ); // 1. finally we add the key/value to the store (`store.foo = {id:false,root:true}` e.g.)
|
q.set( str ,filter ); // 1. finally we add the key/value to the store (`store.foo = {id:false,root:true}` e.g.)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for( i in 0...token.length ) process( expandAliases(token[i]) );
|
for( i in 0...token.length ) process( token[i] );
|
||||||
return this.q = q;
|
return this.q = q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue