stable: new filters work + separated scenes + pos=world1 works

This commit is contained in:
Leon van Kammen 2023-11-24 17:32:53 +01:00
parent cc3f58f493
commit 2262168c3e
19 changed files with 202 additions and 156 deletions

View File

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

View File

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

View File

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

View File

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

View 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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"}
]

View File

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

View File

@ -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,'=')))

View File

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

View File

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

View File

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