stable: new filters work + separated scenes + pos=world1 works
This commit is contained in:
		
							parent
							
								
									cc3f58f493
								
							
						
					
					
						commit
						2262168c3e
					
				
					 19 changed files with 202 additions and 156 deletions
				
			
		| 
						 | 
				
			
			@ -279,7 +279,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
 | 
			
		|||
 | 
			
		||||
| fragment | type | functionality |
 | 
			
		||||
|----------|--------|------------------------------|
 | 
			
		||||
| <b>#pos</b>=0,0,0 | vector3 | (re)position camera    |
 | 
			
		||||
| <b>#pos</b>=0,0,0 | vector3 or string| (re)position camera based on coordinates directly, or indirectly using objectname (its worldposition)   |
 | 
			
		||||
| <b>#t</b>=0,100 | vector3 | set playback speed, and (re)position looprange of scene-animation or `src`-mediacontent  |
 | 
			
		||||
| <b>#rot</b>=0,90,0 | vector3 | rotate camera    |
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -366,7 +366,7 @@ Resizing will be happen accordingly to its placeholder object `aquariumcube`, se
 | 
			
		|||
 | 
			
		||||
1. local/remote content is instanced by the `src` (filter) value (and attaches it to the placeholder mesh containing the `src` property) 
 | 
			
		||||
2. by default all objects are loaded into the instanced src (scene) object (but not shown yet)
 | 
			
		||||
2. <b>local</b> `src` values (`#...` e.g.) starting with a non-negating filter (`#cube` e.g.) will make that object (with name `cube`) the new root of the scene at position 0,0,0
 | 
			
		||||
2. <b>local</b> `src` values (`#...` e.g.) starting with a non-negating filter (`#cube` e.g.) will (deep)reparent that object (with name `cube`) as the new root of the scene at position 0,0,0
 | 
			
		||||
3. <b>local</b> `src` values should respect (negative) filters (`#-foo&price=>3`)
 | 
			
		||||
4. the instanced scene (from a `src` value) should be <b>scaled accordingly</b> to its placeholder object or <b>scaled relatively</b> based on the scale-property (of a geometry-less placeholder, an 'empty'-object in blender e.g.). For more info see Chapter Scaling.
 | 
			
		||||
5. <b>external</b> `src` values should be served with appropriate mimetype (so the XR Fragment-compatible browser will now how to render it). The bare minimum supported mimetypes are:
 | 
			
		||||
| 
						 | 
				
			
			@ -415,6 +415,14 @@ navigation, portals & mutations
 | 
			
		|||
[» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/href.gltf#L192)<br>
 | 
			
		||||
[» discussion](https://github.com/coderofsalvation/xrfragment/issues/1)<br>
 | 
			
		||||
 | 
			
		||||
## Walking surfaces 
 | 
			
		||||
 | 
			
		||||
XR Fragment-compatible viewers can infer this data based scanning the scene for:
 | 
			
		||||
 | 
			
		||||
1. materialless (nameless & textureless) mesh-objects (without `src` and `href`)
 | 
			
		||||
 | 
			
		||||
> optionally the viewer can offer thumbstick, mouse or joystick teleport-tools for non-roomscale VR/AR setups.
 | 
			
		||||
 | 
			
		||||
## UX spec
 | 
			
		||||
 | 
			
		||||
End-users should always have read/write access to: 
 | 
			
		||||
| 
						 | 
				
			
			@ -503,16 +511,19 @@ It's simple but powerful syntax which allows filtering the scene using searcheng
 | 
			
		|||
 | 
			
		||||
## including/excluding
 | 
			
		||||
 | 
			
		||||
By default, selectors work like photoshop-layers: they scan for matching layer(name/properties) within the scene-graph.
 | 
			
		||||
Each matched object (not their children) will be toggled (in)visible when selecting.
 | 
			
		||||
 | 
			
		||||
| operator | info                                                                                                                          |
 | 
			
		||||
|----------|-------------------------------------------------------------------------------------------------------------------------------|
 | 
			
		||||
| `-`      | hides object(s) (`#-myobject&-objects` e.g.                                                                                   |
 | 
			
		||||
| `=`      | indicates an object-embedded custom property key/value (`#price=4&category=foo` e.g.)                                         |
 | 
			
		||||
| `=>` `=<`| compare float or int number (`#price=>4` e.g.)                                                                                |
 | 
			
		||||
| `/`      | reference to root-scene.<br>Useful in case of (preventing) showing/hiding objects in nested scenes (instanced by `src`) (*)   |
 | 
			
		||||
| `*`      | deepselect: automatically select children of selected object, including local (nonremote) embedded objects (starting with `#`)|
 | 
			
		||||
 | 
			
		||||
> \* = `#-/cube` hides object `cube` only in the root-scene (not nested `cube` objects)<br> `#-cube` hides both object `cube` in the root-scene <b>AND</b> nested `skybox` objects |
 | 
			
		||||
> NOTE 1: after an external embedded object has been instanced (`src: https://y.com/bar.fbx#room` e.g.), filters do not affect them anymore (reason: local tag/name collisions can be mitigated easily, but not in case of remote content).
 | 
			
		||||
 | 
			
		||||
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.).
 | 
			
		||||
> NOTE 2: depending on the used 3D framework, toggling objects (in)visible should happen by enabling/disableing writing to the colorbuffer (to allow children being still visible while their parents are invisible).
 | 
			
		||||
 | 
			
		||||
[» 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)
 | 
			
		||||
| 
						 | 
				
			
			@ -929,6 +940,15 @@ This document has no IANA actions.
 | 
			
		|||
* [NLNET](https://nlnet.nl)
 | 
			
		||||
* [Future of Text](https://futureoftext.org)
 | 
			
		||||
* [visual-meta.info](https://visual-meta.info)
 | 
			
		||||
* Michiel Leenaars
 | 
			
		||||
* Gerben van der Broeke
 | 
			
		||||
* Mauve
 | 
			
		||||
* Jens Finkhäuser
 | 
			
		||||
* Marc Belmont
 | 
			
		||||
* Tim Gerritsen
 | 
			
		||||
* Frode Hegland
 | 
			
		||||
* Brandel Zackernuk
 | 
			
		||||
* Mark Anderson
 | 
			
		||||
 | 
			
		||||
# Appendix: Definitions 
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -26,7 +26,18 @@ window.AFRAME.registerComponent('xrf', {
 | 
			
		|||
        })
 | 
			
		||||
        if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
 | 
			
		||||
 | 
			
		||||
        xrf.addEventListener('navigateLoaded', () => setTimeout( () => AFRAME.fade.out(),500)  )
 | 
			
		||||
        xrf.addEventListener('navigateLoaded', () => {
 | 
			
		||||
          setTimeout( () => AFRAME.fade.out(),500) 
 | 
			
		||||
 | 
			
		||||
          // *TODO* this does not really belong here perhaps
 | 
			
		||||
          let blinkControls = document.querySelector('[blink-controls]')
 | 
			
		||||
          if( blinkControls ){
 | 
			
		||||
            blinkControls = blinkControls.components['blink-controls']
 | 
			
		||||
            blinkControls.defaultCollisionMeshes = xrf.getCollisionMeshes()
 | 
			
		||||
            blinkControls.update()
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        xrf.addEventListener('href', (opts) => {
 | 
			
		||||
          if( opts.click){ 
 | 
			
		||||
            let p       = opts.promise()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,7 +48,6 @@ 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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,3 +100,9 @@ xrf.add = (object) => {
 | 
			
		|||
  object.isXRF = true // mark for easy deletion when replacing scene
 | 
			
		||||
  xrf.scene.add(object)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
xrf.hasNoMaterial = (mesh) => {
 | 
			
		||||
  const hasTexture        = mesh.material && mesh.material.map 
 | 
			
		||||
  const hasMaterialName   = mesh.material && mesh.material.name.length > 0 
 | 
			
		||||
  return mesh.geometry && !hasMaterialName && !hasTexture
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								src/3rd/js/three/util/collision.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/3rd/js/three/util/collision.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
xrf.getCollisionMeshes = () => {
 | 
			
		||||
  let meshes = []
 | 
			
		||||
  xrf.scene.traverse( (n) => {
 | 
			
		||||
    if( !n.userData.href && !n.userData.src && xrf.hasNoMaterial(n) ){
 | 
			
		||||
      meshes.push(n)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return meshes
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -42,13 +42,28 @@ xrf.filter.sort = function(frag){
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
xrf.filter.process = function(frag,scene,opts){
 | 
			
		||||
  const cleanupKey   = (k) => k.replace(/[-\*\/]/g,'')
 | 
			
		||||
  let firstFilter    = frag.filters.length ? frag.filters[0].filter.get() : false 
 | 
			
		||||
  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,'')
 | 
			
		||||
 | 
			
		||||
  let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false 
 | 
			
		||||
  let  showers    = frag.filters.filter( (v) => v.filter.get().show === true ) 
 | 
			
		||||
  // utility functions
 | 
			
		||||
  const getOrCloneMaterial = (o) => {
 | 
			
		||||
    if( o.material ){
 | 
			
		||||
      if( o.material.isXRF ) return o.material
 | 
			
		||||
      o.material = o.material.clone()
 | 
			
		||||
      o.material.isXRF = true
 | 
			
		||||
      return o.material
 | 
			
		||||
    }
 | 
			
		||||
    return {}
 | 
			
		||||
  }
 | 
			
		||||
  const setVisible = (n,visible,filter,processed) => {
 | 
			
		||||
    if( processed && processed[n.uuid] ) return 
 | 
			
		||||
    getOrCloneMaterial(n).visible = visible
 | 
			
		||||
    console.log(n.name+" => "+(visible?"show":"hide"))
 | 
			
		||||
    if( filter.deep ) n.traverse( (m) => getOrCloneMaterial(m).visible = visible )
 | 
			
		||||
    if( processed ) processed[n.uuid] == true 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // spec 2: https://xrfragment.org/doc/RFC_XR_Macros.html#embedding-xr-content-using-src
 | 
			
		||||
  // reparent scene based on objectname in case it matches a (non-negating) selector 
 | 
			
		||||
| 
						 | 
				
			
			@ -63,56 +78,35 @@ xrf.filter.process = function(frag,scene,opts){
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const setVisible = (n,visible,processed) => {
 | 
			
		||||
    if( processed && processed[n.uuid] ) return 
 | 
			
		||||
    n.visible = visible
 | 
			
		||||
    n.traverse( (n) => n.visible = visible )
 | 
			
		||||
 | 
			
		||||
    // for hidden parents, clone material and set material to invisible
 | 
			
		||||
    // otherwise n will not be rendered
 | 
			
		||||
    if( visible ){
 | 
			
		||||
      n.traverseAncestors( (parent) => {
 | 
			
		||||
        if( !parent.visible ){
 | 
			
		||||
          parent.visible = true
 | 
			
		||||
          if( parent.material && !parent.material.isXRF ){
 | 
			
		||||
            parent.material = parent.material.clone()
 | 
			
		||||
            parent.material.visible = false
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    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
 | 
			
		||||
  // we don't use the XRWG (everything) because we process only the given (sub)scene
 | 
			
		||||
  frag.filters.map( (v) => {
 | 
			
		||||
    const filter  = v.filter.get()
 | 
			
		||||
    const name_or_tag = cleanupKey(v.fragment)
 | 
			
		||||
    let processed = {}
 | 
			
		||||
    let extembeds = {}
 | 
			
		||||
 | 
			
		||||
    // hide external objects temporarely
 | 
			
		||||
    scene.traverse( (m) => {
 | 
			
		||||
      // 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
 | 
			
		||||
      if( m.isSRCExternal ){
 | 
			
		||||
        m.traverse( (n) => (extembeds[ n.uuid ] = m) && (n.visible = false) )
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    // 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)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    scene.traverseVisible( (m) => {
 | 
			
		||||
      // filter on value(expression) #foo=>3 e.g. *TODO* do this in XRWG
 | 
			
		||||
      if( filter.value && m.userData[filter.key] ){
 | 
			
		||||
        const visible = v.filter.testProperty(filter.key, m.userData[filter.key], filter.show === false )
 | 
			
		||||
        setVisible(m,visible,filter,processed)
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      if( hasNameOrTag(m,name_or_tag,filter ) ){
 | 
			
		||||
        setVisible(m,filter.show,filter)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // show external objects again 
 | 
			
		||||
    for ( let i in extembeds ) extembeds[i].visible = true
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return xrf.filter
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,18 @@
 | 
			
		|||
xrf.frag.pos = function(v, opts){
 | 
			
		||||
  let { frag, mesh, model, camera, scene, renderer, THREE} = opts
 | 
			
		||||
  camera.position.x = v.x
 | 
			
		||||
  camera.position.y = v.y
 | 
			
		||||
  camera.position.z = v.z
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
 | 
			
		||||
  if( v.x == undefined ){
 | 
			
		||||
    let obj = scene.getObjectByName(v.string)
 | 
			
		||||
    if( !obj ) return 
 | 
			
		||||
    let pos = obj.position.clone()
 | 
			
		||||
    obj.getWorldPosition(pos)
 | 
			
		||||
    camera.position.copy(pos)
 | 
			
		||||
  }else{ 
 | 
			
		||||
    // spec: direct coordinate: https://xrfragment.org/#navigating%203D
 | 
			
		||||
    camera.position.x = v.x
 | 
			
		||||
    camera.position.y = v.y
 | 
			
		||||
    camera.position.z = v.z
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
 | 
			
		|||
  let scene = model.scene
 | 
			
		||||
  xrf.frag.src.filterScene(scene,{...opts,frag})     // filter scene
 | 
			
		||||
  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)
 | 
			
		||||
| 
						 | 
				
			
			@ -27,14 +26,15 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
 | 
			
		|||
    xrf.frag.src.scale( scene, opts, url )           // scale scene
 | 
			
		||||
    mesh.add(scene)
 | 
			
		||||
  }
 | 
			
		||||
  // flag everything isSRC & isXRF
 | 
			
		||||
  mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
 | 
			
		||||
  xrf.emit('parseModel', {...opts, scene, model}) 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
xrf.frag.src.renderAsPortal = (mesh) => {
 | 
			
		||||
  const hasTexture        = mesh.material && mesh.material.map 
 | 
			
		||||
  // *TODO* should support better isFlat(mesh) check
 | 
			
		||||
  const isPlane           = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4 
 | 
			
		||||
  const hasMaterialName   = mesh.material && mesh.material.name.length > 0 
 | 
			
		||||
  return mesh.geometry && !hasMaterialName && !hasTexture && isPlane
 | 
			
		||||
  return xrf.hasNoMaterial(mesh) && isPlane
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
xrf.frag.src.enableSourcePortation = (src) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,8 @@ let loadAudio = (mimetype) => function(url,opts){
 | 
			
		|||
  let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
 | 
			
		||||
  let frag = xrf.URI.parse( url )
 | 
			
		||||
 | 
			
		||||
  return
 | 
			
		||||
 | 
			
		||||
  /* WebAudio: setup context via THREEjs */
 | 
			
		||||
  if( !camera.listener ){
 | 
			
		||||
    camera.listener = new THREE.AudioListener();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,8 +85,8 @@ xrf.portalNonEuclidian = function(opts){
 | 
			
		|||
        let stencilObject              = mesh.portal.stencilObject
 | 
			
		||||
        let newScale                   = mesh.scale 
 | 
			
		||||
        let cameraDirection            = mesh.portal.cameraDirection
 | 
			
		||||
        let cameraPosition            = mesh.portal.cameraPosition
 | 
			
		||||
        let raycaster            = mesh.portal.raycaster
 | 
			
		||||
        let cameraPosition             = mesh.portal.cameraPosition
 | 
			
		||||
        let raycaster                  = mesh.portal.raycaster
 | 
			
		||||
 | 
			
		||||
        // init
 | 
			
		||||
        if( !opts.isLocal ) stencilObject.visible = true 
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +149,7 @@ xrf.portalNonEuclidian.setMaterial = function(mesh){
 | 
			
		|||
  mesh.material.colorWrite   = false;
 | 
			
		||||
  mesh.material.stencilWrite = true;
 | 
			
		||||
  mesh.material.stencilRef   = xrf.portalNonEuclidian.stencilRef;
 | 
			
		||||
  mesh.renderOrder           = xrf.portalNonEuclidian.stencilRef;
 | 
			
		||||
  mesh.renderOrder           = 0;//xrf.portalNonEuclidian.stencilRef;
 | 
			
		||||
  mesh.material.stencilFunc  = THREE.AlwaysStencilFunc;
 | 
			
		||||
  mesh.material.stencilZPass = THREE.ReplaceStencilOp;
 | 
			
		||||
  //mesh.material.stencilFail  = THREE.ReplaceStencilOp;
 | 
			
		||||
| 
						 | 
				
			
			@ -157,5 +157,10 @@ xrf.portalNonEuclidian.setMaterial = function(mesh){
 | 
			
		|||
  return mesh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
xrf.addEventListener('parseModel',(opts) => {
 | 
			
		||||
  const scene = opts.model.scene
 | 
			
		||||
  scene.traverse( (n) => n.renderOrder = 10 ) // rendering everything *after* the stencil buffers
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
xrf.portalNonEuclidian.stencilRef = 1
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ class Test {
 | 
			
		|||
 | 
			
		||||
  static public function main():Void {
 | 
			
		||||
    test( "url.json",         Spec.load("src/spec/url.json") );
 | 
			
		||||
    test( "pos.json",         Spec.load("src/spec/pos.json") );
 | 
			
		||||
    test( "t.json",           Spec.load("src/spec/t.json") );
 | 
			
		||||
    test( "filter.selectors.json", Spec.load("src/spec/filter.selectors.json") );
 | 
			
		||||
    //test( Spec.load("src/spec/tmp.json") );
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +47,7 @@ class Test {
 | 
			
		|||
      if( item.expect.fn == "equal.xy"            ) valid = equalXY(res,item);
 | 
			
		||||
      if( item.expect.fn == "equal.xyz"           ) valid = equalXYZ(res,item);
 | 
			
		||||
      if( item.expect.fn == "testFilterRoot"      ) valid = res.exists(item.expect.input[0]) && res.get(item.expect.input[0]).filter.get().root == item.expect.out;
 | 
			
		||||
      if( item.expect.fn == "testFilterDeep"      ) valid = res.exists(item.expect.input[0]) && res.get(item.expect.input[0]).filter.get().deep == item.expect.out;
 | 
			
		||||
      var ok:String = valid ? "[ ✔ ] " : "[ ❌] ";
 | 
			
		||||
      trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? "    (" + (item.label?item.label:item.expect.fn) +")" : ""));
 | 
			
		||||
			if( !valid ) errors += 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,6 @@
 | 
			
		|||
  {"fn":"filter","data":"price=>2", "expect":{ "fn":"testProperty","input":["price","1"],"out":false}},
 | 
			
		||||
  {"fn":"filter","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","5"],"out":false}},
 | 
			
		||||
  {"fn":"filter","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","1"],"out":true}},
 | 
			
		||||
  {"fn":"url","data":"#/foo",    "expect":{ "fn":"testFilterRoot","input":["foo"],"out":true},"label":"foo should be root-only"},
 | 
			
		||||
  {"fn":"url","data":"#/foo&foo","expect":{ "fn":"testFilterRoot","input":["foo"],"out":false},"label":"foo should recursively selected"},
 | 
			
		||||
  {"fn":"url","data":"#/foo&foo&/bar", "expect":{ "fn":"testFilterRoot","input":["foo"],"out":false},"label":"bar should be root-only"},
 | 
			
		||||
  {"fn":"url","data":"#-/foo", "expect":{ "fn":"testFilterRoot","input":["foo"],"out":true},"label":"foo should be root-only"}
 | 
			
		||||
  {"fn":"url","data":"#foo*", "expect":{ "fn":"testFilterDeep","input":["foo"],"out":1},"label":"foo should be deep"},
 | 
			
		||||
  {"fn":"url","data":"#foo**", "expect":{ "fn":"testFilterDeep","input":["foo"],"out":2},"label":"foo should be deep incl. embeds"}
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								src/spec/pos.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/spec/pos.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
[
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2", "expect":{ "fn":"equal.string",    "input":"pos","out":"1.2,2.2"},"label":"equal.string"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2,3", "expect":{ "fn":"equal.xyz",    "input":"pos","out":"1.2,2.2,3"},"label":"equal.xyz"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=1,2,3", "expect":{ "fn":"equal.xyz", "input":"pos","out":"1,2,3"},"label":"pos equal.xyz"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=world2", "expect":{ "fn":"equal.string", "input":"pos","out":"world2"},"label":"pos equal.xyz"}
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,4 @@
 | 
			
		|||
[
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2", "expect":{ "fn":"equal.xyz",    "input":"pos","out":false},"label":"equal.xyz: should trigger incompatible type)"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2,3", "expect":{ "fn":"equal.xyz",    "input":"pos","out":"1.2,2.2,3"},"label":"equal.xyz"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#mypredefinedview", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed"},
 | 
			
		||||
  {"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"another","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)"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
// Copyright (c) 2023 Leon van Kammen/NLNET 
 | 
			
		||||
package xrfragment;
 | 
			
		||||
 | 
			
		||||
import xrfragment.XRF;
 | 
			
		||||
//return untyped __js__("window.location.search");
 | 
			
		||||
 | 
			
		||||
#if js
 | 
			
		||||
| 
						 | 
				
			
			@ -52,12 +53,6 @@ class Filter {
 | 
			
		|||
 | 
			
		||||
  private var str:String = "";
 | 
			
		||||
  private var q:haxe.DynamicAccess<Dynamic> = {};       //  1. create an associative array/object to store filter-arguments as objects
 | 
			
		||||
  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 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 isSelectorExclude:EReg  = ~/^-/;          //  1. detect exclude keys like `-foo`   (reference regex= `/^-/` )
 | 
			
		||||
 | 
			
		||||
  public function new(str:String){
 | 
			
		||||
    if( str != null  ) this.parse(str);
 | 
			
		||||
| 
						 | 
				
			
			@ -84,24 +79,23 @@ class Filter {
 | 
			
		|||
      var filter:haxe.DynamicAccess<Dynamic> = {};
 | 
			
		||||
      if( q.get(prefix+k) ) filter = q.get(prefix+k);
 | 
			
		||||
 | 
			
		||||
      if( isProp.match(str) ){                             // 1. <b>WHEN</b></b> when a `=` key/value is detected: 
 | 
			
		||||
      if( XRF.isProp.match(str) ){                             // 1. <b>WHEN</b></b> when a `=` key/value is detected: 
 | 
			
		||||
        var oper:String = "";
 | 
			
		||||
        if( str.indexOf(">")  != -1 ) oper = ">";          // 1. then scan for `>` operator
 | 
			
		||||
        if( str.indexOf("<")  != -1 ) oper = "<";          // 1. then scan for `<` operator
 | 
			
		||||
        if( isExclude.match(k) ){
 | 
			
		||||
        if( XRF.isExclude.match(k) ){
 | 
			
		||||
          k = k.substr(1);                                 // 1. then strip operators from key: convert "-foo" into "foo" 
 | 
			
		||||
        }
 | 
			
		||||
        v = v.substr(oper.length);                         // 1. then strip operators from value: change value ">=foo" into "foo" 
 | 
			
		||||
        if( oper.length == 0 ) oper = "=";                 // 1. when no operators detected, assume operator '='
 | 
			
		||||
        var rule:haxe.DynamicAccess<Dynamic> = {};
 | 
			
		||||
        if( isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v);
 | 
			
		||||
        if( XRF.isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v);
 | 
			
		||||
        else rule[oper] = v;
 | 
			
		||||
        q.set('expr',rule);
 | 
			
		||||
      }else{ // 1. <b>ELSE </b> we are dealing with an object
 | 
			
		||||
        q.set("root", isRoot.match(str)     ? true  : false ); //  1. and we set `root` to `true` or `false` (true=`/` root selector is present)
 | 
			
		||||
      }
 | 
			
		||||
      q.set("show", isExclude.match(str)    ? false : true  ); //  1. therefore we we set `show` to `true` or `false` (false=excluder `-`)
 | 
			
		||||
      q.set("key", operators.replace(k,'') );
 | 
			
		||||
      q.set("deep", XRF.isDeep.match(str)     ? k.split("*").length-1 : 0 ); //  1. and we set `deep` to >0 or based on * occurences (true=`*` deep selector is present)
 | 
			
		||||
      q.set("show", XRF.isExclude.match(str)    ? false : true  ); //  1. therefore we we set `show` to `true` or `false` (false=excluder `-`)
 | 
			
		||||
      q.set("key", XRF.operators.replace(k,'') );
 | 
			
		||||
      q.set("value",v);
 | 
			
		||||
    }
 | 
			
		||||
    for( i in 0...token.length ) process( token[i] );
 | 
			
		||||
| 
						 | 
				
			
			@ -153,8 +147,8 @@ class Filter {
 | 
			
		|||
        if( Reflect.field(f,'!=') != null && testprop( Std.string(value) == Std.string(Reflect.field(f,'!='))) && exclude ) qualify += 1;
 | 
			
		||||
      }else{
 | 
			
		||||
        if( Reflect.field(f,'*')  != null && testprop( Std.parseFloat(value) != null                                   ) ) qualify += 1;
 | 
			
		||||
        if( Reflect.field(f,'>')  != null && testprop( Std.parseFloat(value) >  Std.parseFloat(Reflect.field(f,'>' )) ) ) qualify += 1;
 | 
			
		||||
        if( Reflect.field(f,'<')  != null && testprop( Std.parseFloat(value) <  Std.parseFloat(Reflect.field(f,'<' )) ) ) qualify += 1;
 | 
			
		||||
        if( Reflect.field(f,'>')  != null && testprop( Std.parseFloat(value) >= Std.parseFloat(Reflect.field(f,'>' )) ) ) qualify += 1;
 | 
			
		||||
        if( Reflect.field(f,'<')  != null && testprop( Std.parseFloat(value) <= Std.parseFloat(Reflect.field(f,'<' )) ) ) qualify += 1;
 | 
			
		||||
        if( Reflect.field(f,'=')  != null && (
 | 
			
		||||
          testprop( value == Reflect.field(f,'='))                   ||
 | 
			
		||||
          testprop( Std.parseFloat(value) == Std.parseFloat(Reflect.field(f,'=')))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ import xrfragment.XRF;
 | 
			
		|||
class Parser {
 | 
			
		||||
    public static var error:String  = "";
 | 
			
		||||
    public static var debug:Bool    = false;
 | 
			
		||||
    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<Dynamic>,?index:Int):Bool {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +21,7 @@ class Parser {
 | 
			
		|||
      Frag.set("tag",           XRF.ASSET | XRF.T_STRING          );
 | 
			
		||||
 | 
			
		||||
      // spatial category: query selector / object manipulation
 | 
			
		||||
      Frag.set("pos",           XRF.PV_OVERRIDE  | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
 | 
			
		||||
      Frag.set("pos",           XRF.PV_OVERRIDE    | XRF.T_VECTOR3 | XRF.T_STRING | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
 | 
			
		||||
      Frag.set("rot",           XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE  | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR );
 | 
			
		||||
 | 
			
		||||
      // category: animation
 | 
			
		||||
| 
						 | 
				
			
			@ -48,13 +47,15 @@ class Parser {
 | 
			
		|||
 | 
			
		||||
      //  1. requirement: receive arguments: key (string), value (string), store (writable associative array/object)
 | 
			
		||||
 | 
			
		||||
      var keyStripped:String = XRF.operators.replace( key, '' );
 | 
			
		||||
 | 
			
		||||
			// dynamic fragments cases: predefined views & assign/binds
 | 
			
		||||
      var isPVDynamic:Bool = key.length > 0 && !Frag.exists(key);
 | 
			
		||||
      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); // ignore failures (empty values are allowed)
 | 
			
		||||
				store.set( keyClean.replace(key,''), v );
 | 
			
		||||
				store.set( keyStripped, v );
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,12 +66,12 @@ class Parser {
 | 
			
		|||
          trace("⚠ fragment '"+key+"' has incompatible value ("+value+")");//  1. don't add to store if value-type is incorrect
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        store.set( keyClean.replace(key,''), v);                                                //  1. if valid, add to store
 | 
			
		||||
        store.set( keyStripped, v);                                                //  1. if valid, add to store
 | 
			
		||||
        if( debug ) trace("✔ "+key+": "+v.string);
 | 
			
		||||
      }else{                                                               //  1. expose (but mark) non-offical fragments too 
 | 
			
		||||
        if( Std.isOfType(value, String) ) v.guessType(v,value);
 | 
			
		||||
        v.noXRF = true;
 | 
			
		||||
        store.set( keyClean.replace(key,'') ,v);
 | 
			
		||||
        store.set( keyStripped ,v);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,13 +36,18 @@ class XRF {
 | 
			
		|||
	public static var T_STRING_OBJ_PROP:Int   = 4194304;
 | 
			
		||||
 | 
			
		||||
  // regexes
 | 
			
		||||
  public static var isColor:EReg  = ~/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; //  1. hex colors are detected using regex `/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/`
 | 
			
		||||
  public static var isInt:EReg    = ~/^[-0-9]+$/;                           //  1. integers are detected using regex `/^[0-9]+$/`
 | 
			
		||||
  public static var isFloat:EReg  = ~/^[-0-9]+\.[0-9]+$/;                   //  1. floats are detected using regex `/^[0-9]+\.[0-9]+$/`
 | 
			
		||||
  public static var isVector:EReg = ~/([,]+|\w)/;                          //  1. vectors are detected using regex `/[,]/` (but can also be an string referring to an entity-ID in the asset)
 | 
			
		||||
  public static var isUrl:EReg    = ~/(:\/\/)?\..*/;                       //  1. url/file */` 
 | 
			
		||||
  public static var isColor:EReg   = ~/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; //  1. hex colors are detected using regex `/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/`
 | 
			
		||||
  public static var isInt:EReg     = ~/^[-0-9]+$/;                          //  1. integers are detected using regex `/^[0-9]+$/`
 | 
			
		||||
  public static var isFloat:EReg   = ~/^[-0-9]+\.[0-9]+$/;                  //  1. floats are detected using regex `/^[0-9]+\.[0-9]+$/`
 | 
			
		||||
  public static var isVector:EReg  = ~/([,]+|\w)/;                          //  1. vectors are detected using regex `/[,]/` (but can also be an string referring to an entity-ID in the asset)
 | 
			
		||||
  public static var isUrl:EReg     = ~/(:\/\/)?\..*/;                       //  1. url/file */` 
 | 
			
		||||
  public static var isUrlOrPretypedView:EReg = ~/(^#|:\/\/)?\..*/;                       //  1. url/file */` 
 | 
			
		||||
  public static var isString:EReg = ~/.*/;                                 //  1. anything else is string  `/.*/`
 | 
			
		||||
  public static var isString:EReg  = ~/.*/;                                 //  1. anything else is string  `/.*/`
 | 
			
		||||
  public static var operators:EReg = ~/(^-|[\*]+)/;                         //  1. detect operators so you can easily strip keys (reference regex= `~/(^-)?(\/)?(\*)?/` )
 | 
			
		||||
  public static var isProp:EReg    = ~/^.*=[><=]?/;                         //  1. detect object id's & properties `foo=1` and `foo` (reference regex= `~/^.*=[><=]?/`  )
 | 
			
		||||
  public static var isExclude:EReg = ~/^-/;                                 //  1. detect excluders like `-foo`,`-foo=1`,`-.foo`,`-/foo` (reference regex= `/^-/` )
 | 
			
		||||
  public static var isDeep:EReg    = ~/\*/;                                 //  1. detect deep selectors like `foo*` (reference regex= `/\*$/` )
 | 
			
		||||
  public static var isNumber:EReg  = ~/^[0-9\.]+$/;                         //  1. detect number values like `foo=1` (reference regex= `/^[0-9\.]+$/` )
 | 
			
		||||
 | 
			
		||||
  // value holder(s)                                                       //  |------|------|--------|----------------------------------|
 | 
			
		||||
  public var fragment:String;
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +88,7 @@ class XRF {
 | 
			
		|||
    // validate
 | 
			
		||||
    var ok:Bool = true;
 | 
			
		||||
    if( !is(T_FLOAT)   && is(T_VECTOR2) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float)) ) ok = false;
 | 
			
		||||
    if( !is(T_VECTOR2) && is(T_VECTOR3) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float) && Std.isOfType(z,Float)) ) ok = false;
 | 
			
		||||
    if( !(is(T_VECTOR2) || is(T_STRING)) && is(T_VECTOR3) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float) && Std.isOfType(z,Float)) ) ok = false;
 | 
			
		||||
    return ok;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,33 +2,40 @@
 | 
			
		|||
THREE = AFRAME.THREE
 | 
			
		||||
 | 
			
		||||
createScene = (noadd) => {
 | 
			
		||||
  let obj  = {a:{},b:{},c:{}}
 | 
			
		||||
  let obj  = {a:{},b:{},c:{},d:{},extembed:{}}
 | 
			
		||||
  for ( let i in obj ){
 | 
			
		||||
    obj[i] = new THREE.Object3D()
 | 
			
		||||
    obj[i].name = i
 | 
			
		||||
    obj[i].material = {visible:true, clone: () => ({visible:true}) }
 | 
			
		||||
  }
 | 
			
		||||
  let {a,b,c} = obj
 | 
			
		||||
  let scene = new THREE.Scene()
 | 
			
		||||
  let {a,b,c,d,extembed} = obj
 | 
			
		||||
  let scene = xrf.scene = new THREE.Scene()
 | 
			
		||||
  if( !noadd ){
 | 
			
		||||
    a.add(b)
 | 
			
		||||
    b.add(c)
 | 
			
		||||
    scene.add(a)
 | 
			
		||||
    extembed.add(d)
 | 
			
		||||
    scene.add(extembed)
 | 
			
		||||
  }
 | 
			
		||||
  b.userData.score = 2
 | 
			
		||||
  b.userData.tag = "foo bar"
 | 
			
		||||
  c.userData.tag = "flop flap"
 | 
			
		||||
  a.userData.tag = "VR"
 | 
			
		||||
  b.userData.tag = "foo hide"
 | 
			
		||||
  c.userData.tag = "flop flap VR"
 | 
			
		||||
  a.userData.price = 1
 | 
			
		||||
  b.userData.price = 5
 | 
			
		||||
  c.userData.price = 10
 | 
			
		||||
  return {a,b,c,scene}
 | 
			
		||||
  b.isSRC = "local"
 | 
			
		||||
  d.userData.tag = "VR"
 | 
			
		||||
  extembed.isSRC = true 
 | 
			
		||||
  extembed.isSRCExternal = true 
 | 
			
		||||
  return {a,b,c,d,extembed,scene}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
filterScene = (URI) => {
 | 
			
		||||
filterScene = (URI,opts) => {
 | 
			
		||||
  opts = opts || {}
 | 
			
		||||
  frag = xrf.URI.parse(URI)
 | 
			
		||||
  var {a,b,c,scene} = createScene()
 | 
			
		||||
  xrf.filter.scene({scene,frag})
 | 
			
		||||
  var {a,b,c,d,extembed,scene} = createScene()
 | 
			
		||||
  xrf.filter.scene({...opts,scene,frag})
 | 
			
		||||
 | 
			
		||||
  scene.visible = (objname, expected, checkMaterial) => {
 | 
			
		||||
    let o = scene.getObjectByName(objname)
 | 
			
		||||
| 
						 | 
				
			
			@ -42,69 +49,46 @@ filterScene = (URI) => {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
scn       = filterScene("#b")
 | 
			
		||||
test      = () => !scn.visible("a") && scn.visible("b",true) && scn.visible("c",true) 
 | 
			
		||||
test      = () => scn.visible("a",true,true) && scn.visible("b",true) && scn.visible("c",true) 
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #b `})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-b")
 | 
			
		||||
test = () =>  scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #-b `})
 | 
			
		||||
scn       = filterScene("#-b")
 | 
			
		||||
test      = () => scn.visible("a",true,true) && scn.visible("b",false,true) && scn.visible("c",true) && scn.visible("c",true,true)
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #-b`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#a&-b")
 | 
			
		||||
test = () =>  scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #a&-b `})
 | 
			
		||||
scn       = filterScene("#-b*")
 | 
			
		||||
test      = () => scn.visible("a",true,true) && scn.visible("b",false,true) && scn.visible("c",false,true) 
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #b*`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-b&b") 
 | 
			
		||||
scn       = filterScene("#b",{reparent:true})
 | 
			
		||||
test      = () => scn.visible("a",false) && scn.visible("b",true) && scn.visible("c",true) 
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #b (reparent scene)`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-b&b*") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #-b&b `})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-c") 
 | 
			
		||||
test = () =>  scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`objectname: #-c `})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#score") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
scn = filterScene("#-a&score*") 
 | 
			
		||||
test = () => scn.visible("a",false,true) && scn.visible("b",true,true) && scn.visible("c",true,true)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #score `})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#score=>1") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #score>=1`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#score=2") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
scn = filterScene("#-a&score*=2") 
 | 
			
		||||
test = () => scn.visible("a",false,true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #score=2`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#score=>3") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #score=>3`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-score=>1") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #-score=>1`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-score=>1&c") 
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("b",false,true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`propertyfilter: #-score=>1&c`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-foo")
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("b",false)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-foo `})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-c&flop")
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-c&flop`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-b&-foo&bar&flop")
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-b&-foo&bar&flop`})
 | 
			
		||||
 | 
			
		||||
scn = filterScene("#-b&-foo&bar&flop&-bar&flop")
 | 
			
		||||
test = () => scn.visible("a",true) && scn.visible("b",false,true) && scn.visible("c",true)
 | 
			
		||||
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)
 | 
			
		||||
scn = filterScene("#-price*&price=>5")
 | 
			
		||||
test = () => scn.visible("a",false,true) && scn.visible("b",true,true) && scn.visible("c",true,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"`})
 | 
			
		||||
scn = filterScene("#-hide*")
 | 
			
		||||
test = () => scn.visible("a",true,true) && scn.visible("b",false,true) && scn.visible("c",false,true)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-hide*"`})
 | 
			
		||||
 | 
			
		||||
scn  = filterScene("#-VR")
 | 
			
		||||
test = () => scn.visible("a",false,true) && scn.visible("b",true,true) && scn.visible("c",false,true) && scn.visible("extembed",true,true) && scn.visible("d",true,true)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-VR"`})
 | 
			
		||||
 | 
			
		||||
scn  = filterScene("#-VR*")
 | 
			
		||||
test = () => scn.visible("a",false,true) && scn.visible("b",false,true) && scn.visible("c",false,true) && scn.visible("extembed",true,true) && scn.visible("d",true,true)
 | 
			
		||||
console.assert( test(), {scn,reason:`tagfilter: #-VR*"`})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue