stable: new filters work + separated scenes + pos=world1 works
This commit is contained in:
parent
cc3f58f493
commit
2262168c3e
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
]
|
||||
|
|
|
@ -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…
Reference in New Issue