From 5584d22e25cd9a466cc67fec4760917766f512f7 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 22 Jun 2023 08:48:52 +0200 Subject: [PATCH] updated parser: added root-operator / + test & cleanup --- dist/xrfragment.aframe.js | 152 ++++++++++++++++---------------- dist/xrfragment.js | 20 ++--- dist/xrfragment.lua | 54 +++++------- dist/xrfragment.module.js | 20 ++--- dist/xrfragment.py | 24 ++--- dist/xrfragment.three.js | 149 +++++++++++++++---------------- dist/xrfragment.three.module.js | 149 +++++++++++++++---------------- src/Test.hx | 5 +- src/xrfragment/Parser.hx | 2 +- src/xrfragment/Query.hx | 13 ++- src/xrfragment/XRF.hx | 8 +- 11 files changed, 291 insertions(+), 305 deletions(-) diff --git a/dist/xrfragment.aframe.js b/dist/xrfragment.aframe.js index 8edf5e3..950b3c3 100644 --- a/dist/xrfragment.aframe.js +++ b/dist/xrfragment.aframe.js @@ -239,7 +239,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.EMBEDDED | xrfragment_XRF.PROMPT; - if(value.length == 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { + if(value.length == 0 && key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR); v.validate(key); resultMap[key] = v; @@ -265,7 +265,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { var xrfragment_Query = $hx_exports["xrfragment"]["Query"] = function(str) { this.isNumber = new EReg("^[0-9\\.]+$",""); this.isClass = new EReg("^[-]?class$",""); - this.isRoot = new EReg("^/",""); + this.isRoot = new EReg("^[-]?/",""); this.isExclude = new EReg("^-",""); this.isProp = new EReg("^.*:[><=!]?",""); this.q = { }; @@ -350,15 +350,15 @@ xrfragment_Query.prototype = { } return; } else { - if(_gthis.isRoot.match(k)) { - str = HxOverrides.substr(k,1,null); - filter["root"] = true; - } else if(filter["root"] == true) { - Reflect.deleteField(filter,"root"); - } filter["id"] = _gthis.isExclude.match(str) ? false : true; - var key = _gthis.isExclude.match(str) ? HxOverrides.substr(str,1,null) : str; - q[key] = filter; + filter["root"] = _gthis.isRoot.match(str); + if(_gthis.isExclude.match(str)) { + str = HxOverrides.substr(str,1,null); + } + if(_gthis.isRoot.match(str)) { + str = HxOverrides.substr(str,1,null); + } + q[str] = filter; } }; var _g = 0; @@ -663,6 +663,7 @@ xrf.addEventListener = function(eventName, callback) { }; xrf.emit = function(eventName, data){ + if( typeof data != 'object' ) throw 'emit() requires passing objects' return xrf.emit.promise(eventName,data) } @@ -884,11 +885,13 @@ xrf.eval = function( url, model, flags ){ // evaluate fragments in url model = model || xrf.model let { THREE, camera } = xrf let frag = xrf.URI.parse( url, flags || xrf.XRF.NAVIGATOR ) - for ( let k in frag ){ - let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('eval',opts) - .then( () => xrf.eval.fragment(k,opts) ) - } + let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.emit('eval',opts) + .then( () => { + for ( let k in frag ){ + xrf.eval.fragment(k,opts) + } + }) } xrf.eval.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) inside mesh of model @@ -946,15 +949,15 @@ xrf.add = (object) => { xrf.navigator = {} -xrf.navigator.to = (url,event) => { +xrf.navigator.to = (url,flags) => { if( !url ) throw 'xrf.navigator.to(..) no url given' + return new Promise( (resolve,reject) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) - console.log("xrfragment: navigating to "+url) + console.log(url) if( !file || xrf.model.file == file ){ // we're already loaded - document.location.hash = `#${hash}` // just update the hash - xrf.eval( url, xrf.model ) // and eval local URI XR fragments + xrf.eval( url, xrf.model, flags ) // and eval local URI XR fragments return resolve(xrf.model) } @@ -968,10 +971,13 @@ xrf.navigator.to = (url,event) => { loader.load( file, function(model){ model.file = file xrf.add( model.scene ) + // only change url when loading *another* file + if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash ) xrf.model = model - xrf.eval( '#', model ) // execute the default projection '#' (if exist) - xrf.eval( url, model ) // and eval URI XR fragments - xrf.navigator.pushState( `${dir}${file}`, hash ) + xrf.eval( '#', model ) // execute the default projection '#' (if exist) + xrf.eval( url, model ) // and eval URI XR fragments + if( !hash.match(/pos=/) ) + xrf.eval( '#pos=0,0,0' ) // set default position if not specified resolve(model) }) }) @@ -980,7 +986,7 @@ xrf.navigator.to = (url,event) => { xrf.navigator.init = () => { if( xrf.navigator.init.inited ) return window.addEventListener('popstate', function (event){ - xrf.navigator.to( document.location.search.substr(1) + document.location.hash, event) + xrf.navigator.to( document.location.search.substr(1) + document.location.hash ) }) xrf.navigator.material = { selection: new xrf.THREE.LineBasicMaterial({color:0xFF00FF,linewidth:2}) @@ -988,6 +994,12 @@ xrf.navigator.init = () => { xrf.navigator.init.inited = true } +xrf.navigator.updateHash = (hash) => { + if( hash == document.location.hash || hash.match(/\|/) ) return // skip unnecesary pushState triggers + document.location.hash = hash + xrf.emit('updateHash', {hash} ) +} + xrf.navigator.pushState = (file,hash) => { if( file == document.location.search.substr(1) ) return // page is in its default state window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` ) @@ -995,36 +1007,12 @@ xrf.navigator.pushState = (file,hash) => { xrf.frag.env = function(v, opts){ let { mesh, model, camera, scene, renderer, THREE} = opts let env = mesh.getObjectByName(v.string) + if( !env ) return console.warn("xrf.env "+v.string+" not found") env.material.map.mapping = THREE.EquirectangularReflectionMapping; scene.environment = env.material.map //scene.texture = env.material.map renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 2; - // apply to meshes *DISABLED* renderer.environment does this - const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); - setTimeout( () => { - scene.traverse( (mesh) => { - //if (mesh.material && mesh.material.map && mesh.material.metalness == 1.0) { - // mesh.material = new THREE.MeshBasicMaterial({ map: mesh.material.map }); - // mesh.material.dithering = true - // mesh.material.map.anisotropy = maxAnisotropy; - // mesh.material.needsUpdate = true; - //} - //if (mesh.material && mesh.material.metalness == 1.0 ){ - // mesh.material = new THREE.MeshBasicMaterial({ - // color:0xffffff, - // emissive: mesh.material.map, - // envMap: env.material.map, - // side: THREE.DoubleSide, - // flatShading: true - // }) - // mesh.material.needsUpdate = true - // //mesh.material.envMap = env.material.map; - // //mesh.material.envMap.intensity = 5; - // //mesh.material.needsUpdate = true; - //} - }); - },500) console.log(` └ applied image '${v.string}' as environment map`) } /** @@ -1122,10 +1110,8 @@ xrf.frag.href = function(v, opts){ xrf .emit('href',{click:true,mesh,xrf:v}) // let all listeners agree .then( () => { - if( v.string[0] == '#' && v.string.match(/(\||#q)/) ){ // apply modifications to scene *TODO* decide on queries... - console.log("ja") - xrf.eval( v.string, xrf.model, xrf.XRF.PV_OVERRIDE ) - }else xrf.navigator.to(v.string) // or let's surf to HREF! + const flags = v.string[0] == '#' && v.string.match(/(\||#q)/) ? xrf.XRF.PV_OVERRIDE : undefined + xrf.navigator.to(v.string,flags) // or let's surf to HREF! }) } @@ -1173,7 +1159,6 @@ xrf.frag.href = function(v, opts){ */ xrf.frag.pos = function(v, opts){ let { frag, mesh, model, camera, scene, renderer, THREE} = opts - console.log(" └ setting camera position to "+v.string) if( !frag.q ){ camera.position.x = v.x @@ -1181,18 +1166,15 @@ xrf.frag.pos = function(v, opts){ camera.position.z = v.z } } -const doPredefinedView = (opts) => { +const updatePredefinedView = (opts) => { let {frag,scene} = opts const selectionOfInterest = (frag,scene,mesh) => { let id = frag.string if(!id) return id // important: ignore empty strings - if( mesh.selection ){ - scene.remove(mesh.selection) - delete mesh.selection - } + if( mesh.selection ) return mesh // Selection of Interest if predefined_view matches object name - if( id == mesh.name || id.substr(1) == mesh.userData.class ){ + if( mesh.visible && (id == mesh.name || id.substr(1) == mesh.userData.class) ){ xrf.emit('selection',{...opts,frag}) .then( () => { const margin = 1.2 @@ -1212,39 +1194,51 @@ const doPredefinedView = (opts) => { if( !id ) return // prevent empty matches if( mesh.userData[`#${id}`] ){ // get alias frag = xrf.URI.parse( mesh.userData[`#${id}`], xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - for ( let k in frag ){ - let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('predefinedView',{...opts,frag}) - .then( () => xrf.eval.fragment(k,opts) ) - } + xrf.emit('predefinedView',{...opts,frag}) + .then( () => { + for ( let k in frag ){ + let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.eval.fragment(k,opts) + } + }) } } + const traverseScene = (v,scene) => { + let remove = [] + if( !scene ) return + scene.traverse( (mesh) => { + remove.push( selectionOfInterest( v, scene, mesh ) ) + predefinedView( v , scene, mesh ) + }) + remove.filter( (e) => e ).map( (mesh) => { + scene.remove(mesh.selection) + delete mesh.selection + }) + } + let pviews = [] for ( let i in frag ) { let v = frag[i] if( v.is( xrf.XRF.PV_EXECUTE ) ){ if( v.args ) v = v.args[ xrf.roundrobin(v,xrf.model) ] // wait for nested instances to arrive at the scene - setTimeout( () => { - if( !scene ) return - scene.traverse( (mesh) => { - selectionOfInterest( v, scene, mesh ) - predefinedView( v , scene, mesh ) - }) - },100) - } + setTimeout( () => traverseScene(v,scene), 100 ) + console.dir(v) + if( v.string ) pviews.push(v.string) + }else if( v.is( xrf.XRF.NAVIGATOR ) ) pviews.push(`${i}=${v.string}`) } + if( pviews.length ) xrf.navigator.updateHash( pviews.join("&") ) } // when predefined view occurs in url changes -xrf.addEventListener('eval', doPredefinedView ) +xrf.addEventListener('eval', updatePredefinedView ) // clicking href url with predefined view xrf.addEventListener('href', (opts) => { if( !opts.click || opts.xrf.string[0] != '#' ) return let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - doPredefinedView({frag,scene:xrf.scene}) + updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf}) }) //let updateUrl = (opts) => { @@ -1295,6 +1289,7 @@ xrf.frag.q = function(v, opts){ let isMeshId = q[i].id != undefined let isMeshClass = q[i].class != undefined let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId && !isMeshClass + if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.) if( isMeshId && i == mesh.name ) mesh.visible = q[i].id if( isMeshClass && i == mesh.userData.class ) mesh.visible = q[i].class if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i]) @@ -1338,18 +1333,20 @@ xrf.frag.src = function(v, opts){ console.log(" └ inserting "+i+" (srcScene)") srcScene.position.set(0,0,0) srcScene.rotation.set(0,0,0) + // add interactive elements (href's e.g.) + srcScene.add( xrf.interactive.clone() ) srcScene.traverse( (m) => { if( m.userData && (m.userData.src || m.userData.href) ) return ;//delete m.userData.src // prevent infinite recursion xrf.eval.mesh(m,{scene,recursive:true}) - m.name = mesh.name+"."+m.name // prefix meshname so predefined views don't affect objectnames anymore + m.isSRC = true }) + console.dir(xrf) if( srcScene.visible ) src.add( srcScene ) } src.position.copy( mesh.position ) src.rotation.copy( mesh.rotation ) src.scale.copy( mesh.scale ) mesh.add(src) - console.dir(opts) if( !opts.recursive ) mesh.material.visible = false // lets hide the preview object because deleting disables animations+nested objs },10) } @@ -1360,6 +1357,9 @@ window.AFRAME.registerComponent('xrf', { init: function () { if( !AFRAME.XRF ) this.initXRFragments() if( this.data ){ + if( document.location.search || document.location.hash.length > 1 ){ // override url + this.data = `${document.location.search.substr(1)}${document.location.hash}` + } AFRAME.XRF.navigator.to(this.data) .then( (model) => { let gets = [ ...document.querySelectorAll('[xrf-get]') ] diff --git a/dist/xrfragment.js b/dist/xrfragment.js index 253f4a6..a2ea538 100644 --- a/dist/xrfragment.js +++ b/dist/xrfragment.js @@ -239,7 +239,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.EMBEDDED | xrfragment_XRF.PROMPT; - if(value.length == 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { + if(value.length == 0 && key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR); v.validate(key); resultMap[key] = v; @@ -265,7 +265,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { var xrfragment_Query = $hx_exports["xrfragment"]["Query"] = function(str) { this.isNumber = new EReg("^[0-9\\.]+$",""); this.isClass = new EReg("^[-]?class$",""); - this.isRoot = new EReg("^/",""); + this.isRoot = new EReg("^[-]?/",""); this.isExclude = new EReg("^-",""); this.isProp = new EReg("^.*:[><=!]?",""); this.q = { }; @@ -350,15 +350,15 @@ xrfragment_Query.prototype = { } return; } else { - if(_gthis.isRoot.match(k)) { - str = HxOverrides.substr(k,1,null); - filter["root"] = true; - } else if(filter["root"] == true) { - Reflect.deleteField(filter,"root"); - } filter["id"] = _gthis.isExclude.match(str) ? false : true; - var key = _gthis.isExclude.match(str) ? HxOverrides.substr(str,1,null) : str; - q[key] = filter; + filter["root"] = _gthis.isRoot.match(str); + if(_gthis.isExclude.match(str)) { + str = HxOverrides.substr(str,1,null); + } + if(_gthis.isRoot.match(str)) { + str = HxOverrides.substr(str,1,null); + } + q[str] = filter; } }; var _g = 0; diff --git a/dist/xrfragment.lua b/dist/xrfragment.lua index 5af60f0..68447e2 100644 --- a/dist/xrfragment.lua +++ b/dist/xrfragment.lua @@ -1546,7 +1546,7 @@ __xrfragment_Parser.parse = function(key,value,resultMap) else Frag_h.session = value1; end; - if ((__lua_lib_luautf8_Utf8.len(value) == 0) and (Frag_h[key] == nil)) then + if (((__lua_lib_luautf8_Utf8.len(value) == 0) and (__lua_lib_luautf8_Utf8.len(key) > 0)) and (Frag_h[key] == nil)) then local v = __xrfragment_XRF.new(key, _hx_bit.bor(__xrfragment_XRF.PV_EXECUTE,__xrfragment_XRF.NAVIGATOR)); v:validate(key); resultMap[key] = v; @@ -1633,7 +1633,7 @@ end __xrfragment_Query.super = function(self,str) self.isNumber = EReg.new("^[0-9\\.]+$", ""); self.isClass = EReg.new("^[-]?class$", ""); - self.isRoot = EReg.new("^/", ""); + self.isRoot = EReg.new("^[-]?/", ""); self.isExclude = EReg.new("^-", ""); self.isProp = EReg.new("^.*:[><=!]?", ""); self.q = _hx_e(); @@ -1889,29 +1889,6 @@ __xrfragment_Query.prototype.parse = function(self,str,recurse) end; do return end; else - if (_gthis.isRoot:match(k)) then - local pos = 1; - local len = nil; - if ((len == nil) or (len > (pos + __lua_lib_luautf8_Utf8.len(k)))) then - len = __lua_lib_luautf8_Utf8.len(k); - else - if (len < 0) then - len = __lua_lib_luautf8_Utf8.len(k) + len; - end; - end; - if (pos < 0) then - pos = __lua_lib_luautf8_Utf8.len(k) + pos; - end; - if (pos < 0) then - pos = 0; - end; - str = __lua_lib_luautf8_Utf8.sub(k, pos + 1, pos + len); - filter.root = true; - else - if (Reflect.field(filter, "root") == true) then - Reflect.deleteField(filter, "root"); - end; - end; local value = (function() local _hx_7 if (_gthis.isExclude:match(str)) then @@ -1920,7 +1897,8 @@ __xrfragment_Query.prototype.parse = function(self,str,recurse) return _hx_7 end )(); filter.id = value; - local key; + local value = _gthis.isRoot:match(str); + filter.root = value; if (_gthis.isExclude:match(str)) then local pos = 1; local len = nil; @@ -1937,11 +1915,27 @@ __xrfragment_Query.prototype.parse = function(self,str,recurse) if (pos < 0) then pos = 0; end; - key = __lua_lib_luautf8_Utf8.sub(str, pos + 1, pos + len); - else - key = str; + str = __lua_lib_luautf8_Utf8.sub(str, pos + 1, pos + len); end; - q[key] = filter; + if (_gthis.isRoot:match(str)) then + local pos = 1; + local len = nil; + if ((len == nil) or (len > (pos + __lua_lib_luautf8_Utf8.len(str)))) then + len = __lua_lib_luautf8_Utf8.len(str); + else + if (len < 0) then + len = __lua_lib_luautf8_Utf8.len(str) + len; + end; + end; + if (pos < 0) then + pos = __lua_lib_luautf8_Utf8.len(str) + pos; + end; + if (pos < 0) then + pos = 0; + end; + str = __lua_lib_luautf8_Utf8.sub(str, pos + 1, pos + len); + end; + q[str] = filter; end; end; local _g = 0; diff --git a/dist/xrfragment.module.js b/dist/xrfragment.module.js index 0d2b1d5..aa66b0c 100644 --- a/dist/xrfragment.module.js +++ b/dist/xrfragment.module.js @@ -239,7 +239,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.EMBEDDED | xrfragment_XRF.PROMPT; - if(value.length == 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { + if(value.length == 0 && key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR); v.validate(key); resultMap[key] = v; @@ -265,7 +265,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { var xrfragment_Query = $hx_exports["xrfragment"]["Query"] = function(str) { this.isNumber = new EReg("^[0-9\\.]+$",""); this.isClass = new EReg("^[-]?class$",""); - this.isRoot = new EReg("^/",""); + this.isRoot = new EReg("^[-]?/",""); this.isExclude = new EReg("^-",""); this.isProp = new EReg("^.*:[><=!]?",""); this.q = { }; @@ -350,15 +350,15 @@ xrfragment_Query.prototype = { } return; } else { - if(_gthis.isRoot.match(k)) { - str = HxOverrides.substr(k,1,null); - filter["root"] = true; - } else if(filter["root"] == true) { - Reflect.deleteField(filter,"root"); - } filter["id"] = _gthis.isExclude.match(str) ? false : true; - var key = _gthis.isExclude.match(str) ? HxOverrides.substr(str,1,null) : str; - q[key] = filter; + filter["root"] = _gthis.isRoot.match(str); + if(_gthis.isExclude.match(str)) { + str = HxOverrides.substr(str,1,null); + } + if(_gthis.isRoot.match(str)) { + str = HxOverrides.substr(str,1,null); + } + q[str] = filter; } }; var _g = 0; diff --git a/dist/xrfragment.py b/dist/xrfragment.py index 2c7b6a4..beb5d73 100644 --- a/dist/xrfragment.py +++ b/dist/xrfragment.py @@ -1316,7 +1316,7 @@ class xrfragment_Parser: Frag.h["unit"] = (xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING) Frag.h["description"] = (xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING) Frag.h["session"] = (((((xrfragment_XRF.ASSET | xrfragment_XRF.T_URL) | xrfragment_XRF.PV_OVERRIDE) | xrfragment_XRF.NAVIGATOR) | xrfragment_XRF.EMBEDDED) | xrfragment_XRF.PROMPT) - if ((len(value) == 0) and (not (key in Frag.h))): + if (((len(value) == 0) and ((len(key) > 0))) and (not (key in Frag.h))): v = xrfragment_XRF(key,(xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR)) v.validate(key) setattr(resultMap,(("_hx_" + key) if ((key in python_Boot.keywords)) else (("_hx_" + key) if (((((len(key) > 2) and ((ord(key[0]) == 95))) and ((ord(key[1]) == 95))) and ((ord(key[(len(key) - 1)]) != 95)))) else key)),v) @@ -1345,7 +1345,7 @@ class xrfragment_Query: def __init__(self,_hx_str): self.isNumber = EReg("^[0-9\\.]+$","") self.isClass = EReg("^[-]?class$","") - self.isRoot = EReg("^/","") + self.isRoot = EReg("^[-]?/","") self.isExclude = EReg("^-","") self.isProp = EReg("^.*:[><=!]?","") self.q = _hx_AnonObject({}) @@ -1432,21 +1432,23 @@ class xrfragment_Query: setattr(q,(("_hx_" + k) if ((k in python_Boot.keywords)) else (("_hx_" + k) if (((((len(k) > 2) and ((ord(k[0]) == 95))) and ((ord(k[1]) == 95))) and ((ord(k[(len(k) - 1)]) != 95)))) else k)),_hx_filter) return else: - _this = _gthis.isRoot - _this.matchObj = python_lib_Re.search(_this.pattern,k) - if (_this.matchObj is not None): - _hx_str = HxString.substr(k,1,None) - setattr(_hx_filter,(("_hx_" + "root") if (("root" in python_Boot.keywords)) else (("_hx_" + "root") if (((((len("root") > 2) and ((ord("root"[0]) == 95))) and ((ord("root"[1]) == 95))) and ((ord("root"[(len("root") - 1)]) != 95)))) else "root")),True) - elif (Reflect.field(_hx_filter,"root") == True): - Reflect.deleteField(_hx_filter,"root") _this = _gthis.isExclude _this.matchObj = python_lib_Re.search(_this.pattern,_hx_str) value = (False if ((_this.matchObj is not None)) else True) setattr(_hx_filter,(("_hx_" + "id") if (("id" in python_Boot.keywords)) else (("_hx_" + "id") if (((((len("id") > 2) and ((ord("id"[0]) == 95))) and ((ord("id"[1]) == 95))) and ((ord("id"[(len("id") - 1)]) != 95)))) else "id")),value) + _this = _gthis.isRoot + _this.matchObj = python_lib_Re.search(_this.pattern,_hx_str) + value = (_this.matchObj is not None) + setattr(_hx_filter,(("_hx_" + "root") if (("root" in python_Boot.keywords)) else (("_hx_" + "root") if (((((len("root") > 2) and ((ord("root"[0]) == 95))) and ((ord("root"[1]) == 95))) and ((ord("root"[(len("root") - 1)]) != 95)))) else "root")),value) _this = _gthis.isExclude _this.matchObj = python_lib_Re.search(_this.pattern,_hx_str) - key = (HxString.substr(_hx_str,1,None) if ((_this.matchObj is not None)) else _hx_str) - setattr(q,(("_hx_" + key) if ((key in python_Boot.keywords)) else (("_hx_" + key) if (((((len(key) > 2) and ((ord(key[0]) == 95))) and ((ord(key[1]) == 95))) and ((ord(key[(len(key) - 1)]) != 95)))) else key)),_hx_filter) + if (_this.matchObj is not None): + _hx_str = HxString.substr(_hx_str,1,None) + _this = _gthis.isRoot + _this.matchObj = python_lib_Re.search(_this.pattern,_hx_str) + if (_this.matchObj is not None): + _hx_str = HxString.substr(_hx_str,1,None) + setattr(q,(("_hx_" + _hx_str) if ((_hx_str in python_Boot.keywords)) else (("_hx_" + _hx_str) if (((((len(_hx_str) > 2) and ((ord(_hx_str[0]) == 95))) and ((ord(_hx_str[1]) == 95))) and ((ord(_hx_str[(len(_hx_str) - 1)]) != 95)))) else _hx_str)),_hx_filter) process = _hx_local_0 _g = 0 _g1 = len(token) diff --git a/dist/xrfragment.three.js b/dist/xrfragment.three.js index befe22d..951255f 100644 --- a/dist/xrfragment.three.js +++ b/dist/xrfragment.three.js @@ -239,7 +239,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.EMBEDDED | xrfragment_XRF.PROMPT; - if(value.length == 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { + if(value.length == 0 && key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR); v.validate(key); resultMap[key] = v; @@ -265,7 +265,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { var xrfragment_Query = $hx_exports["xrfragment"]["Query"] = function(str) { this.isNumber = new EReg("^[0-9\\.]+$",""); this.isClass = new EReg("^[-]?class$",""); - this.isRoot = new EReg("^/",""); + this.isRoot = new EReg("^[-]?/",""); this.isExclude = new EReg("^-",""); this.isProp = new EReg("^.*:[><=!]?",""); this.q = { }; @@ -350,15 +350,15 @@ xrfragment_Query.prototype = { } return; } else { - if(_gthis.isRoot.match(k)) { - str = HxOverrides.substr(k,1,null); - filter["root"] = true; - } else if(filter["root"] == true) { - Reflect.deleteField(filter,"root"); - } filter["id"] = _gthis.isExclude.match(str) ? false : true; - var key = _gthis.isExclude.match(str) ? HxOverrides.substr(str,1,null) : str; - q[key] = filter; + filter["root"] = _gthis.isRoot.match(str); + if(_gthis.isExclude.match(str)) { + str = HxOverrides.substr(str,1,null); + } + if(_gthis.isRoot.match(str)) { + str = HxOverrides.substr(str,1,null); + } + q[str] = filter; } }; var _g = 0; @@ -663,6 +663,7 @@ xrf.addEventListener = function(eventName, callback) { }; xrf.emit = function(eventName, data){ + if( typeof data != 'object' ) throw 'emit() requires passing objects' return xrf.emit.promise(eventName,data) } @@ -884,11 +885,13 @@ xrf.eval = function( url, model, flags ){ // evaluate fragments in url model = model || xrf.model let { THREE, camera } = xrf let frag = xrf.URI.parse( url, flags || xrf.XRF.NAVIGATOR ) - for ( let k in frag ){ - let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('eval',opts) - .then( () => xrf.eval.fragment(k,opts) ) - } + let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.emit('eval',opts) + .then( () => { + for ( let k in frag ){ + xrf.eval.fragment(k,opts) + } + }) } xrf.eval.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) inside mesh of model @@ -946,15 +949,15 @@ xrf.add = (object) => { xrf.navigator = {} -xrf.navigator.to = (url,event) => { +xrf.navigator.to = (url,flags) => { if( !url ) throw 'xrf.navigator.to(..) no url given' + return new Promise( (resolve,reject) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) - console.log("xrfragment: navigating to "+url) + console.log(url) if( !file || xrf.model.file == file ){ // we're already loaded - document.location.hash = `#${hash}` // just update the hash - xrf.eval( url, xrf.model ) // and eval local URI XR fragments + xrf.eval( url, xrf.model, flags ) // and eval local URI XR fragments return resolve(xrf.model) } @@ -968,10 +971,13 @@ xrf.navigator.to = (url,event) => { loader.load( file, function(model){ model.file = file xrf.add( model.scene ) + // only change url when loading *another* file + if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash ) xrf.model = model - xrf.eval( '#', model ) // execute the default projection '#' (if exist) - xrf.eval( url, model ) // and eval URI XR fragments - xrf.navigator.pushState( `${dir}${file}`, hash ) + xrf.eval( '#', model ) // execute the default projection '#' (if exist) + xrf.eval( url, model ) // and eval URI XR fragments + if( !hash.match(/pos=/) ) + xrf.eval( '#pos=0,0,0' ) // set default position if not specified resolve(model) }) }) @@ -980,7 +986,7 @@ xrf.navigator.to = (url,event) => { xrf.navigator.init = () => { if( xrf.navigator.init.inited ) return window.addEventListener('popstate', function (event){ - xrf.navigator.to( document.location.search.substr(1) + document.location.hash, event) + xrf.navigator.to( document.location.search.substr(1) + document.location.hash ) }) xrf.navigator.material = { selection: new xrf.THREE.LineBasicMaterial({color:0xFF00FF,linewidth:2}) @@ -988,6 +994,12 @@ xrf.navigator.init = () => { xrf.navigator.init.inited = true } +xrf.navigator.updateHash = (hash) => { + if( hash == document.location.hash || hash.match(/\|/) ) return // skip unnecesary pushState triggers + document.location.hash = hash + xrf.emit('updateHash', {hash} ) +} + xrf.navigator.pushState = (file,hash) => { if( file == document.location.search.substr(1) ) return // page is in its default state window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` ) @@ -995,36 +1007,12 @@ xrf.navigator.pushState = (file,hash) => { xrf.frag.env = function(v, opts){ let { mesh, model, camera, scene, renderer, THREE} = opts let env = mesh.getObjectByName(v.string) + if( !env ) return console.warn("xrf.env "+v.string+" not found") env.material.map.mapping = THREE.EquirectangularReflectionMapping; scene.environment = env.material.map //scene.texture = env.material.map renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 2; - // apply to meshes *DISABLED* renderer.environment does this - const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); - setTimeout( () => { - scene.traverse( (mesh) => { - //if (mesh.material && mesh.material.map && mesh.material.metalness == 1.0) { - // mesh.material = new THREE.MeshBasicMaterial({ map: mesh.material.map }); - // mesh.material.dithering = true - // mesh.material.map.anisotropy = maxAnisotropy; - // mesh.material.needsUpdate = true; - //} - //if (mesh.material && mesh.material.metalness == 1.0 ){ - // mesh.material = new THREE.MeshBasicMaterial({ - // color:0xffffff, - // emissive: mesh.material.map, - // envMap: env.material.map, - // side: THREE.DoubleSide, - // flatShading: true - // }) - // mesh.material.needsUpdate = true - // //mesh.material.envMap = env.material.map; - // //mesh.material.envMap.intensity = 5; - // //mesh.material.needsUpdate = true; - //} - }); - },500) console.log(` └ applied image '${v.string}' as environment map`) } /** @@ -1122,10 +1110,8 @@ xrf.frag.href = function(v, opts){ xrf .emit('href',{click:true,mesh,xrf:v}) // let all listeners agree .then( () => { - if( v.string[0] == '#' && v.string.match(/(\||#q)/) ){ // apply modifications to scene *TODO* decide on queries... - console.log("ja") - xrf.eval( v.string, xrf.model, xrf.XRF.PV_OVERRIDE ) - }else xrf.navigator.to(v.string) // or let's surf to HREF! + const flags = v.string[0] == '#' && v.string.match(/(\||#q)/) ? xrf.XRF.PV_OVERRIDE : undefined + xrf.navigator.to(v.string,flags) // or let's surf to HREF! }) } @@ -1173,7 +1159,6 @@ xrf.frag.href = function(v, opts){ */ xrf.frag.pos = function(v, opts){ let { frag, mesh, model, camera, scene, renderer, THREE} = opts - console.log(" └ setting camera position to "+v.string) if( !frag.q ){ camera.position.x = v.x @@ -1181,18 +1166,15 @@ xrf.frag.pos = function(v, opts){ camera.position.z = v.z } } -const doPredefinedView = (opts) => { +const updatePredefinedView = (opts) => { let {frag,scene} = opts const selectionOfInterest = (frag,scene,mesh) => { let id = frag.string if(!id) return id // important: ignore empty strings - if( mesh.selection ){ - scene.remove(mesh.selection) - delete mesh.selection - } + if( mesh.selection ) return mesh // Selection of Interest if predefined_view matches object name - if( id == mesh.name || id.substr(1) == mesh.userData.class ){ + if( mesh.visible && (id == mesh.name || id.substr(1) == mesh.userData.class) ){ xrf.emit('selection',{...opts,frag}) .then( () => { const margin = 1.2 @@ -1212,39 +1194,51 @@ const doPredefinedView = (opts) => { if( !id ) return // prevent empty matches if( mesh.userData[`#${id}`] ){ // get alias frag = xrf.URI.parse( mesh.userData[`#${id}`], xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - for ( let k in frag ){ - let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('predefinedView',{...opts,frag}) - .then( () => xrf.eval.fragment(k,opts) ) - } + xrf.emit('predefinedView',{...opts,frag}) + .then( () => { + for ( let k in frag ){ + let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.eval.fragment(k,opts) + } + }) } } + const traverseScene = (v,scene) => { + let remove = [] + if( !scene ) return + scene.traverse( (mesh) => { + remove.push( selectionOfInterest( v, scene, mesh ) ) + predefinedView( v , scene, mesh ) + }) + remove.filter( (e) => e ).map( (mesh) => { + scene.remove(mesh.selection) + delete mesh.selection + }) + } + let pviews = [] for ( let i in frag ) { let v = frag[i] if( v.is( xrf.XRF.PV_EXECUTE ) ){ if( v.args ) v = v.args[ xrf.roundrobin(v,xrf.model) ] // wait for nested instances to arrive at the scene - setTimeout( () => { - if( !scene ) return - scene.traverse( (mesh) => { - selectionOfInterest( v, scene, mesh ) - predefinedView( v , scene, mesh ) - }) - },100) - } + setTimeout( () => traverseScene(v,scene), 100 ) + console.dir(v) + if( v.string ) pviews.push(v.string) + }else if( v.is( xrf.XRF.NAVIGATOR ) ) pviews.push(`${i}=${v.string}`) } + if( pviews.length ) xrf.navigator.updateHash( pviews.join("&") ) } // when predefined view occurs in url changes -xrf.addEventListener('eval', doPredefinedView ) +xrf.addEventListener('eval', updatePredefinedView ) // clicking href url with predefined view xrf.addEventListener('href', (opts) => { if( !opts.click || opts.xrf.string[0] != '#' ) return let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - doPredefinedView({frag,scene:xrf.scene}) + updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf}) }) //let updateUrl = (opts) => { @@ -1295,6 +1289,7 @@ xrf.frag.q = function(v, opts){ let isMeshId = q[i].id != undefined let isMeshClass = q[i].class != undefined let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId && !isMeshClass + if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.) if( isMeshId && i == mesh.name ) mesh.visible = q[i].id if( isMeshClass && i == mesh.userData.class ) mesh.visible = q[i].class if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i]) @@ -1338,18 +1333,20 @@ xrf.frag.src = function(v, opts){ console.log(" └ inserting "+i+" (srcScene)") srcScene.position.set(0,0,0) srcScene.rotation.set(0,0,0) + // add interactive elements (href's e.g.) + srcScene.add( xrf.interactive.clone() ) srcScene.traverse( (m) => { if( m.userData && (m.userData.src || m.userData.href) ) return ;//delete m.userData.src // prevent infinite recursion xrf.eval.mesh(m,{scene,recursive:true}) - m.name = mesh.name+"."+m.name // prefix meshname so predefined views don't affect objectnames anymore + m.isSRC = true }) + console.dir(xrf) if( srcScene.visible ) src.add( srcScene ) } src.position.copy( mesh.position ) src.rotation.copy( mesh.rotation ) src.scale.copy( mesh.scale ) mesh.add(src) - console.dir(opts) if( !opts.recursive ) mesh.material.visible = false // lets hide the preview object because deleting disables animations+nested objs },10) } diff --git a/dist/xrfragment.three.module.js b/dist/xrfragment.three.module.js index 55d3d3e..e98dcb6 100644 --- a/dist/xrfragment.three.module.js +++ b/dist/xrfragment.three.module.js @@ -239,7 +239,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.EMBEDDED | xrfragment_XRF.PROMPT; - if(value.length == 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { + if(value.length == 0 && key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key)) { var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR); v.validate(key); resultMap[key] = v; @@ -265,7 +265,7 @@ xrfragment_Parser.parse = function(key,value,resultMap) { var xrfragment_Query = $hx_exports["xrfragment"]["Query"] = function(str) { this.isNumber = new EReg("^[0-9\\.]+$",""); this.isClass = new EReg("^[-]?class$",""); - this.isRoot = new EReg("^/",""); + this.isRoot = new EReg("^[-]?/",""); this.isExclude = new EReg("^-",""); this.isProp = new EReg("^.*:[><=!]?",""); this.q = { }; @@ -350,15 +350,15 @@ xrfragment_Query.prototype = { } return; } else { - if(_gthis.isRoot.match(k)) { - str = HxOverrides.substr(k,1,null); - filter["root"] = true; - } else if(filter["root"] == true) { - Reflect.deleteField(filter,"root"); - } filter["id"] = _gthis.isExclude.match(str) ? false : true; - var key = _gthis.isExclude.match(str) ? HxOverrides.substr(str,1,null) : str; - q[key] = filter; + filter["root"] = _gthis.isRoot.match(str); + if(_gthis.isExclude.match(str)) { + str = HxOverrides.substr(str,1,null); + } + if(_gthis.isRoot.match(str)) { + str = HxOverrides.substr(str,1,null); + } + q[str] = filter; } }; var _g = 0; @@ -663,6 +663,7 @@ xrf.addEventListener = function(eventName, callback) { }; xrf.emit = function(eventName, data){ + if( typeof data != 'object' ) throw 'emit() requires passing objects' return xrf.emit.promise(eventName,data) } @@ -884,11 +885,13 @@ xrf.eval = function( url, model, flags ){ // evaluate fragments in url model = model || xrf.model let { THREE, camera } = xrf let frag = xrf.URI.parse( url, flags || xrf.XRF.NAVIGATOR ) - for ( let k in frag ){ - let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('eval',opts) - .then( () => xrf.eval.fragment(k,opts) ) - } + let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.emit('eval',opts) + .then( () => { + for ( let k in frag ){ + xrf.eval.fragment(k,opts) + } + }) } xrf.eval.mesh = (mesh,model) => { // evaluate embedded fragments (metadata) inside mesh of model @@ -946,15 +949,15 @@ xrf.add = (object) => { xrf.navigator = {} -xrf.navigator.to = (url,event) => { +xrf.navigator.to = (url,flags) => { if( !url ) throw 'xrf.navigator.to(..) no url given' + return new Promise( (resolve,reject) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) - console.log("xrfragment: navigating to "+url) + console.log(url) if( !file || xrf.model.file == file ){ // we're already loaded - document.location.hash = `#${hash}` // just update the hash - xrf.eval( url, xrf.model ) // and eval local URI XR fragments + xrf.eval( url, xrf.model, flags ) // and eval local URI XR fragments return resolve(xrf.model) } @@ -968,10 +971,13 @@ xrf.navigator.to = (url,event) => { loader.load( file, function(model){ model.file = file xrf.add( model.scene ) + // only change url when loading *another* file + if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash ) xrf.model = model - xrf.eval( '#', model ) // execute the default projection '#' (if exist) - xrf.eval( url, model ) // and eval URI XR fragments - xrf.navigator.pushState( `${dir}${file}`, hash ) + xrf.eval( '#', model ) // execute the default projection '#' (if exist) + xrf.eval( url, model ) // and eval URI XR fragments + if( !hash.match(/pos=/) ) + xrf.eval( '#pos=0,0,0' ) // set default position if not specified resolve(model) }) }) @@ -980,7 +986,7 @@ xrf.navigator.to = (url,event) => { xrf.navigator.init = () => { if( xrf.navigator.init.inited ) return window.addEventListener('popstate', function (event){ - xrf.navigator.to( document.location.search.substr(1) + document.location.hash, event) + xrf.navigator.to( document.location.search.substr(1) + document.location.hash ) }) xrf.navigator.material = { selection: new xrf.THREE.LineBasicMaterial({color:0xFF00FF,linewidth:2}) @@ -988,6 +994,12 @@ xrf.navigator.init = () => { xrf.navigator.init.inited = true } +xrf.navigator.updateHash = (hash) => { + if( hash == document.location.hash || hash.match(/\|/) ) return // skip unnecesary pushState triggers + document.location.hash = hash + xrf.emit('updateHash', {hash} ) +} + xrf.navigator.pushState = (file,hash) => { if( file == document.location.search.substr(1) ) return // page is in its default state window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` ) @@ -995,36 +1007,12 @@ xrf.navigator.pushState = (file,hash) => { xrf.frag.env = function(v, opts){ let { mesh, model, camera, scene, renderer, THREE} = opts let env = mesh.getObjectByName(v.string) + if( !env ) return console.warn("xrf.env "+v.string+" not found") env.material.map.mapping = THREE.EquirectangularReflectionMapping; scene.environment = env.material.map //scene.texture = env.material.map renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 2; - // apply to meshes *DISABLED* renderer.environment does this - const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); - setTimeout( () => { - scene.traverse( (mesh) => { - //if (mesh.material && mesh.material.map && mesh.material.metalness == 1.0) { - // mesh.material = new THREE.MeshBasicMaterial({ map: mesh.material.map }); - // mesh.material.dithering = true - // mesh.material.map.anisotropy = maxAnisotropy; - // mesh.material.needsUpdate = true; - //} - //if (mesh.material && mesh.material.metalness == 1.0 ){ - // mesh.material = new THREE.MeshBasicMaterial({ - // color:0xffffff, - // emissive: mesh.material.map, - // envMap: env.material.map, - // side: THREE.DoubleSide, - // flatShading: true - // }) - // mesh.material.needsUpdate = true - // //mesh.material.envMap = env.material.map; - // //mesh.material.envMap.intensity = 5; - // //mesh.material.needsUpdate = true; - //} - }); - },500) console.log(` └ applied image '${v.string}' as environment map`) } /** @@ -1122,10 +1110,8 @@ xrf.frag.href = function(v, opts){ xrf .emit('href',{click:true,mesh,xrf:v}) // let all listeners agree .then( () => { - if( v.string[0] == '#' && v.string.match(/(\||#q)/) ){ // apply modifications to scene *TODO* decide on queries... - console.log("ja") - xrf.eval( v.string, xrf.model, xrf.XRF.PV_OVERRIDE ) - }else xrf.navigator.to(v.string) // or let's surf to HREF! + const flags = v.string[0] == '#' && v.string.match(/(\||#q)/) ? xrf.XRF.PV_OVERRIDE : undefined + xrf.navigator.to(v.string,flags) // or let's surf to HREF! }) } @@ -1173,7 +1159,6 @@ xrf.frag.href = function(v, opts){ */ xrf.frag.pos = function(v, opts){ let { frag, mesh, model, camera, scene, renderer, THREE} = opts - console.log(" └ setting camera position to "+v.string) if( !frag.q ){ camera.position.x = v.x @@ -1181,18 +1166,15 @@ xrf.frag.pos = function(v, opts){ camera.position.z = v.z } } -const doPredefinedView = (opts) => { +const updatePredefinedView = (opts) => { let {frag,scene} = opts const selectionOfInterest = (frag,scene,mesh) => { let id = frag.string if(!id) return id // important: ignore empty strings - if( mesh.selection ){ - scene.remove(mesh.selection) - delete mesh.selection - } + if( mesh.selection ) return mesh // Selection of Interest if predefined_view matches object name - if( id == mesh.name || id.substr(1) == mesh.userData.class ){ + if( mesh.visible && (id == mesh.name || id.substr(1) == mesh.userData.class) ){ xrf.emit('selection',{...opts,frag}) .then( () => { const margin = 1.2 @@ -1212,39 +1194,51 @@ const doPredefinedView = (opts) => { if( !id ) return // prevent empty matches if( mesh.userData[`#${id}`] ){ // get alias frag = xrf.URI.parse( mesh.userData[`#${id}`], xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - for ( let k in frag ){ - let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } - xrf.emit('predefinedView',{...opts,frag}) - .then( () => xrf.eval.fragment(k,opts) ) - } + xrf.emit('predefinedView',{...opts,frag}) + .then( () => { + for ( let k in frag ){ + let opts = {frag, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE } + xrf.eval.fragment(k,opts) + } + }) } } + const traverseScene = (v,scene) => { + let remove = [] + if( !scene ) return + scene.traverse( (mesh) => { + remove.push( selectionOfInterest( v, scene, mesh ) ) + predefinedView( v , scene, mesh ) + }) + remove.filter( (e) => e ).map( (mesh) => { + scene.remove(mesh.selection) + delete mesh.selection + }) + } + let pviews = [] for ( let i in frag ) { let v = frag[i] if( v.is( xrf.XRF.PV_EXECUTE ) ){ if( v.args ) v = v.args[ xrf.roundrobin(v,xrf.model) ] // wait for nested instances to arrive at the scene - setTimeout( () => { - if( !scene ) return - scene.traverse( (mesh) => { - selectionOfInterest( v, scene, mesh ) - predefinedView( v , scene, mesh ) - }) - },100) - } + setTimeout( () => traverseScene(v,scene), 100 ) + console.dir(v) + if( v.string ) pviews.push(v.string) + }else if( v.is( xrf.XRF.NAVIGATOR ) ) pviews.push(`${i}=${v.string}`) } + if( pviews.length ) xrf.navigator.updateHash( pviews.join("&") ) } // when predefined view occurs in url changes -xrf.addEventListener('eval', doPredefinedView ) +xrf.addEventListener('eval', updatePredefinedView ) // clicking href url with predefined view xrf.addEventListener('href', (opts) => { if( !opts.click || opts.xrf.string[0] != '#' ) return let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.EMBEDDED ) - doPredefinedView({frag,scene:xrf.scene}) + updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf}) }) //let updateUrl = (opts) => { @@ -1295,6 +1289,7 @@ xrf.frag.q = function(v, opts){ let isMeshId = q[i].id != undefined let isMeshClass = q[i].class != undefined let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId && !isMeshClass + if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.) if( isMeshId && i == mesh.name ) mesh.visible = q[i].id if( isMeshClass && i == mesh.userData.class ) mesh.visible = q[i].class if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i]) @@ -1338,18 +1333,20 @@ xrf.frag.src = function(v, opts){ console.log(" └ inserting "+i+" (srcScene)") srcScene.position.set(0,0,0) srcScene.rotation.set(0,0,0) + // add interactive elements (href's e.g.) + srcScene.add( xrf.interactive.clone() ) srcScene.traverse( (m) => { if( m.userData && (m.userData.src || m.userData.href) ) return ;//delete m.userData.src // prevent infinite recursion xrf.eval.mesh(m,{scene,recursive:true}) - m.name = mesh.name+"."+m.name // prefix meshname so predefined views don't affect objectnames anymore + m.isSRC = true }) + console.dir(xrf) if( srcScene.visible ) src.add( srcScene ) } src.position.copy( mesh.position ) src.rotation.copy( mesh.rotation ) src.scale.copy( mesh.scale ) mesh.add(src) - console.dir(opts) if( !opts.recursive ) mesh.material.visible = false // lets hide the preview object because deleting disables animations+nested objs },10) } diff --git a/src/Test.hx b/src/Test.hx index 8861bbe..c7a8f2a 100644 --- a/src/Test.hx +++ b/src/Test.hx @@ -44,10 +44,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 == "equal.multi" ) valid = equalMulti(res, item); - if( item.expect.fn == "testQueryRoot" ){ - if( !item.expect.out ) valid = !q.get()[ item.expect.input[0] ].root; - else valid = item.expect.out == q.get()[ item.expect.input[0] ].root; - } + if( item.expect.fn == "testQueryRoot" ) valid = item.expect.out == q.get()[ item.expect.input[0] ].root; var ok:String = valid ? "[ ✔ ] " : "[ ❌] "; trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? " (" + (item.label?item.label:item.expect.fn) +")" : "")); if( !valid ) errors += 1; diff --git a/src/xrfragment/Parser.hx b/src/xrfragment/Parser.hx index b7a297f..13dcddf 100644 --- a/src/xrfragment/Parser.hx +++ b/src/xrfragment/Parser.hx @@ -61,7 +61,7 @@ class Parser { */ // dynamic fragments cases: predefined views & assign/binds - if( value.length == 0 && !Frag.exists(key) ){ + if( value.length == 0 && key.length > 0 && !Frag.exists(key) ){ var v:XRF = new XRF(key, XRF.PV_EXECUTE | XRF.NAVIGATOR ); v.validate(key); // will fail but will parse multiple args for us (separated by |) resultMap.set(key, v ); diff --git a/src/xrfragment/Query.hx b/src/xrfragment/Query.hx index c995be7..8ac6be0 100644 --- a/src/xrfragment/Query.hx +++ b/src/xrfragment/Query.hx @@ -44,7 +44,7 @@ class Query { private var q:haxe.DynamicAccess = {}; private var isProp:EReg = ~/^.*:[><=!]?/; private var isExclude:EReg = ~/^-/; - private var isRoot:EReg = ~/^\//; + private var isRoot:EReg = ~/^[-]?\//; private var isClass:EReg = ~/^[-]?class$/; private var isNumber:EReg = ~/^[0-9\.]+$/; @@ -104,12 +104,11 @@ class Query { } return; }else{ // id - if( isRoot.match(k) ){ - str = k.substr(1); // convert "/foo" to "foo" - filter[ "root" ] = true; - }else if( filter[ "root" ] == true ) filter.remove("root"); // undo root-only - filter[ "id" ] = isExclude.match(str) ? false: true; - q.set( (isExclude.match(str) ? str.substr(1) : str ) ,filter ); + filter[ "id" ] = isExclude.match(str) ? false: true; + filter[ "root" ] = isRoot.match(str) ? true: false; + if( isExclude.match(str) ) str = str.substr(1); // convert '-foo' into 'foo' + if( isRoot.match(str) ) str = str.substr(1); // convert '/foo' into 'foo' + q.set( str ,filter ); } } for( i in 0...token.length ) process( expandAliases(token[i]) ); diff --git a/src/xrfragment/XRF.hx b/src/xrfragment/XRF.hx index 990e501..06727ca 100644 --- a/src/xrfragment/XRF.hx +++ b/src/xrfragment/XRF.hx @@ -18,10 +18,10 @@ class XRF { public static var QUERY_OPERATOR:Int = 4; // fragment will be applied to result of queryselecto public static var PROMPT:Int = 8; // ask user whether this fragment value can be changed public static var ROUNDROBIN:Int = 16; // evaluation of this (multi) value can be roundrobined - public static var NAVIGATOR:Int = 32; // fragment can be overridden by (manual) browser URI change - public static var EMBEDDED:Int = 64; // fragment can be overridden by an embedded URL - public static var PV_OVERRIDE:Int = 128; // fragment can be overridden when specified in predefined view value - public static var PV_EXECUTE:Int = 256; // fragment can be overridden by (manual) browser URI change + public static var NAVIGATOR:Int = 32; // fragment can be overridden by (manual) browser URI change + public static var EMBEDDED:Int = 64; // fragment can be overridden by an embedded URL + public static var PV_OVERRIDE:Int = 128; // embedded fragment can be overridden when specified in predefined view value + public static var PV_EXECUTE:Int = 256; // predefined view // high-level value-types (powers of 2) public static var T_COLOR:Int = 8192;