diff --git a/doc/RFC_XR_Fragments.md b/doc/RFC_XR_Fragments.md index fb97ddc..a6519f0 100644 --- a/doc/RFC_XR_Fragments.md +++ b/doc/RFC_XR_Fragments.md @@ -512,6 +512,8 @@ It's simple but powerful syntax which allows filtering the scene using searcheng > \* = `#-/cube` hides object `cube` only in the root-scene (not nested `cube` objects)
`#-cube` hides both object `cube` in the root-scene AND nested `skybox` objects | +Nested selection is always implied (there's no `*` `>`/`<` css-like operators on purpose) which keeps XR Fragments easy to implement, and still allows fine-grained control chaining nested selectors (`#-sky&house&-table` e.g.). + [» example implementation](https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/js/three/xrf/q.js) [» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/filter.gltf#L192) [» discussion](https://github.com/coderofsalvation/xrfragment/issues/3) diff --git a/example/assets/index.glb b/example/assets/index.glb index ecfa84b..72dae66 100644 Binary files a/example/assets/index.glb and b/example/assets/index.glb differ diff --git a/src/3rd/js/three/hashbus.js b/src/3rd/js/three/hashbus.js index 287d863..8bfe31f 100644 --- a/src/3rd/js/three/hashbus.js +++ b/src/3rd/js/three/hashbus.js @@ -35,6 +35,8 @@ pub.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) insid pub.fragment = (k, opts ) => { // evaluate one fragment let frag = opts.frag[k]; + if( frag.is( xrf.XRF.PV_EXECUTE ) ) pub.XRWG({...opts,frag}) + // call native function (xrf/env.js e.g.), or pass it to user decorator xrf.emit(k,opts) .then( () => { @@ -46,6 +48,7 @@ pub.fragment = (k, opts ) => { // evaluate one fragment pub.XRWG = (opts) => { let {frag,scene,model,renderer} = opts + console.dir(opts) // if this query was triggered by an src-value, lets filter it const isSRC = opts.embedded && opts.embedded.fragment == 'src' @@ -62,15 +65,12 @@ pub.XRWG = (opts) => { match.map( (w) => { if( w.key == `#${id}` ){ if( w.value && w.value[0] == '#' ){ - frag = xrf.URI.parse( w.value ) - v = Object.values(frag)[0] // if value is alias, execute fragment value xrf.hashbus.pub( w.value, xrf.model, xrf.XRF.METADATA | xrf.XRF.PV_OVERRIDE | xrf.XRF.NAVIGATOR ) - xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene }) } } }) - if( !match.length ) xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene }) + xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene }) }else{ xrf.emit('dynamicKeyValue',{ ...opts,v,frag,id,match,scene }) } diff --git a/src/3rd/js/three/xrf/dynamic/filter.js b/src/3rd/js/three/xrf/dynamic/filter.js index bf2ec1e..4a976cc 100644 --- a/src/3rd/js/three/xrf/dynamic/filter.js +++ b/src/3rd/js/three/xrf/dynamic/filter.js @@ -23,7 +23,7 @@ xrf.filter = function(query, cb){ xrf.filter.scene = function(opts){ let {scene,frag} = opts -console.dir(opts) + xrf.filter .sort(frag) // get (sorted) filters from XR Fragments .process(frag,scene,opts) // show/hide things @@ -45,7 +45,7 @@ xrf.filter.process = function(frag,scene,opts){ const hasName = (m,name,filter) => m.name == name const hasNameOrTag = (m,name_or_tag,filter) => hasName(m,name_or_tag) || String(m.userData['tag']).match( new RegExp("(^| )"+name_or_tag) ) - const cleanupKey = (k) => k.replace(/[-\*]/g,'') + const cleanupKey = (k) => k.replace(/[-\*\/]/g,'') let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false let showers = frag.filters.filter( (v) => v.filter.get().show === true ) @@ -84,6 +84,12 @@ xrf.filter.process = function(frag,scene,opts){ if( processed ) processed[n.uuid] == true } + const insideSRC = (m) => { + let src = false + m.traverseAncestors( (n) => n.isSRC ? src = true : false ) + return src + } + // then show/hide things based on secondary selectors frag.filters.map( (v) => { const filter = v.filter.get() @@ -91,19 +97,21 @@ xrf.filter.process = function(frag,scene,opts){ let processed = {} scene.traverse( (m) => { - - if( filter.root && m.isSRC ) return // ignore src nodes when root is specific (#/VR #/AR e.g.) - - // filter on value(expression) #foo=>3 e.g. + // filter on value(expression) #foo=>3 e.g. *TODO* do this in XRWG if( filter.value && m.userData[filter.key] ){ + if( filter.root && insideSRC(m) ) return const visible = v.filter.testProperty(filter.key, m.userData[filter.key], filter.show === false ) setVisible(m,visible,processed) return } - // include/exclude object(s) when id/tag matches (#foo or #-foo e.g.) - if( hasNameOrTag(m,name_or_tag,filter) ){ - setVisible(m,filter.show) - } + }) + // include/exclude object(s) when id/tag matches (#foo or #-foo e.g.) + let matches = xrf.XRWG.match(name_or_tag) + matches.map( (match) => { + match.nodes.map( (node) => { + if( filter.root && insideSRC(node) ) return + setVisible(node,filter.show) + }) }) }) diff --git a/src/3rd/js/three/xrf/src.js b/src/3rd/js/three/xrf/src.js index 3ae3b78..641696b 100644 --- a/src/3rd/js/three/xrf/src.js +++ b/src/3rd/js/three/xrf/src.js @@ -17,8 +17,8 @@ xrf.frag.src.addModel = (model,url,frag,opts) => { let {mesh} = opts let scene = model.scene xrf.frag.src.filterScene(scene,{...opts,frag}) // filter scene - mesh.traverse( (n) => n.isSRC = n.isXRF = true ) // mark everything isSRC & isXRF if( mesh.material ) mesh.material.visible = false // hide placeholder object + mesh.traverse( (n) => n.isSRC = n.isXRF = true ) // mark everything isSRC & isXRF //enableSourcePortation(scene) if( xrf.frag.src.renderAsPortal(mesh) ){ if( !opts.isLocal ) xrf.scene.add(scene) diff --git a/src/3rd/js/three/xrf/src/video.js b/src/3rd/js/three/xrf/src/video.js index 8123f68..f4b3934 100644 --- a/src/3rd/js/three/xrf/src/video.js +++ b/src/3rd/js/three/xrf/src/video.js @@ -15,11 +15,11 @@ let loadVideo = (mimetype) => function(url,opts){ mat.map = texture mesh.material = mat // set range - video.addEventListener('timeupdate', function timeupdate() { - if (video.t && video.currentTime < video.t.y || video.currentTime >= video.t.z ) { - vid.currentTime = video.t.y - } - },false) + //video.addEventListener('timeupdate', function timeupdate() { + // if (frag.t && video.currentTime < frag.t.y || video.currentTime >= frag.t.z ) { + // video.currentTime = frag.t.y + // } + //},false) }) video.src = url diff --git a/src/3rd/js/three/xrf/t.js b/src/3rd/js/three/xrf/t.js index 63668b5..b5a72a1 100644 --- a/src/3rd/js/three/xrf/t.js +++ b/src/3rd/js/three/xrf/t.js @@ -34,6 +34,7 @@ xrf.addEventListener('parseModel', (opts) => { model.animations.map( (anim) => { anim.optimize() + console.log("action: "+anim.name) mixer.actions.push( mixer.clipAction( anim, model.scene ) ) }) diff --git a/src/xrfragment/Filter.hx b/src/xrfragment/Filter.hx index 1094987..3810ba4 100644 --- a/src/xrfragment/Filter.hx +++ b/src/xrfragment/Filter.hx @@ -56,7 +56,7 @@ class Filter { 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 isNumber:EReg = ~/^[0-9\.]+$/; // 1. detect number values like `foo=1` (reference regex= `/^[0-9\.]+$/` ) - private var operators:EReg = ~/(^-|\*$|\/)/; // 1. detect operators so you can easily strip keys (reference regex= `/(^-|\*$)/` ) + private var operators:EReg = ~/(^-)?(\/)?/; // 1. detect operators so you can easily strip keys (reference regex= `/(^-|\*$)/` ) private var isSelectorExclude:EReg = ~/^-/; // 1. detect exclude keys like `-foo` (reference regex= `/^-/` ) public function new(str:String){ diff --git a/src/xrfragment/Parser.hx b/src/xrfragment/Parser.hx index 8e01751..c89e496 100644 --- a/src/xrfragment/Parser.hx +++ b/src/xrfragment/Parser.hx @@ -9,7 +9,7 @@ import xrfragment.XRF; class Parser { public static var error:String = ""; public static var debug:Bool = false; - public static var keyClean:EReg = ~/(\*$|^-|\/)/g; + public static var keyClean:EReg = ~/(^-)?(\/)?/; // 1. detect - and / operators so you can easily strip keys (reference regex= ~/(^-)?(\/)?/; ) @:keep public static function parse(key:String,value:String,store:haxe.DynamicAccess,?index:Int):Bool { @@ -53,7 +53,7 @@ class Parser { var isPVDefault:Bool = value.length == 0 && key.length > 0 && key == "#"; if( isPVDynamic ){ //|| isPVDefault ){ // 1. add keys without values to store as [predefined view](predefined_view) var v:XRF = new XRF(key, XRF.PV_EXECUTE | XRF.NAVIGATOR, index ); - v.validate(value); // will fail but will parse multiple args for us (separated by |) + v.validate(value); // ignore failures (empty values are allowed) store.set( keyClean.replace(key,''), v ); return true; } diff --git a/test/aframe/filter.js b/test/aframe/filter.js index 61e0c7c..d951857 100644 --- a/test/aframe/filter.js +++ b/test/aframe/filter.js @@ -18,6 +18,7 @@ createScene = (noadd) => { b.userData.score = 2 b.userData.tag = "foo bar" c.userData.tag = "flop flap" + a.userData.tag = "VR" a.userData.price = 1 b.userData.price = 5 c.userData.price = 10 @@ -103,3 +104,7 @@ console.assert( test(), {scn,reason:`tagfilter: #-b&-foo&bar&flop&-bar&flop"`}) scn = filterScene("#-price&price=>5") test = () => scn.visible("a",false,true) && scn.visible("b",true) && scn.visible("c",true) console.assert( test(), {scn,reason:`tagfilter: #-price&price=>5"`}) + +scn = filterScene("#-/VR&b") +test = () => scn.visible("a",false,true) && scn.visible("b",true) && scn.visible("c",true) +console.assert( test(), {scn,reason:`tagfilter: #-/VR&b"`})