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"`})