refactor media fragments

This commit is contained in:
Leon van Kammen 2024-02-08 19:40:43 +01:00
parent 1851ee1d4f
commit e8308d738b
34 changed files with 446 additions and 129 deletions

BIN
doc/RF6_XR_Fragments.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -6,4 +6,5 @@ for topic in Fragments Macros; do
mmark --html RFC_XR_$topic.md | grep -vE '(<!--{|}-->)' > RFC_XR_$topic.html mmark --html RFC_XR_$topic.md | grep -vE '(<!--{|}-->)' > RFC_XR_$topic.html
xml2rfc --v3 RFC_XR_$topic.xml # RFC_XR_$topic.txt xml2rfc --v3 RFC_XR_$topic.xml # RFC_XR_$topic.txt
sed -i 's/Expires: .*//g' RFC_XR_$topic.txt sed -i 's/Expires: .*//g' RFC_XR_$topic.txt
convert -size 700x2400 xc:white -font "FreeMono" +antialias -pointsize 12 -fill black -annotate +15+15 "@RFC_XR_Fragments.txt" -colorspace gray +dither -posterize 6 RF6_XR_Fragments.png
done done

View File

@ -7,6 +7,7 @@
mmark mmark
xml2rfc xml2rfc
wkhtmltopdf-bin wkhtmltopdf-bin
imagemagick
]; ];
} }

View File

@ -20,6 +20,7 @@ XRWG.match = (str,types,level) => {
return n return n
}) })
str = str.toLowerCase() str = str.toLowerCase()
.replace(/[-\*]/,'') // remove excludes and wildcards
if( level <10 ) res = res.filter( (n) => n.key == str ) if( level <10 ) res = res.filter( (n) => n.key == str )
if( level >=10 ) res = res.filter( (n) => n.word == str || n.key == str ) if( level >=10 ) res = res.filter( (n) => n.word == str || n.key == str )
if( level >30 ) res = res.filter( (n) => n.word.match(str) || n.key == str ) if( level >30 ) res = res.filter( (n) => n.word.match(str) || n.key == str )

View File

@ -4,6 +4,13 @@ AFRAME.registerComponent('xrf-gaze',{
schema:{ schema:{
spawn:{type:'boolean',default:false}, spawn:{type:'boolean',default:false},
}, },
events:{
"fusing": function(e){
if( e.detail.mouseEvent ) return // ignore click event
console.dir(e)
}
},
setGazer: function(state){ setGazer: function(state){
let cam = document.querySelector("[camera]") let cam = document.querySelector("[camera]")
if( state ){ if( state ){
@ -15,7 +22,6 @@ AFRAME.registerComponent('xrf-gaze',{
raycaster="objects: .ray" raycaster="objects: .ray"
visible="true" visible="true"
position="0 0 -1" position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: #BBBBBB; shader: flat"> material="color: #BBBBBB; shader: flat">
</a-entity>` </a-entity>`
}else{ }else{
@ -25,9 +31,12 @@ AFRAME.registerComponent('xrf-gaze',{
} }
}, },
init:function(data){ init:function(data){
let enabled = () => AFRAME.utils.device.isMobile()
let setVisible = (state) => { let setVisible = (state) => {
if( enabled() ) this.setGazer(state) if( AFRAME.utils.device.isMobile() ){
this.setGazer(state)
if( state || xrf.debug ) this.el.setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
else this.el.removeAttribute("geometry")
}
} }
setVisible(false); setVisible(false);

View File

@ -3,17 +3,29 @@
// it allows metadata-keys ('foo' e.g.) of 3D scene-nodes (.userData.foo e.g.) to // it allows metadata-keys ('foo' e.g.) of 3D scene-nodes (.userData.foo e.g.) to
// react by executing code // react by executing code
let pub = function( url, model, flags ){ // evaluate fragments in url let pub = function( url, node_or_model, flags ){ // evaluate fragments in url
if( !url ) return if( !url ) return
if( !url.match(/#/) ) url = `#${url}` if( !url.match(/#/) ) url = `#${url}`
model = model || xrf.model
let { THREE, camera } = xrf let { THREE, camera } = xrf
let frag = xrf.URI.parse( url, flags ) let frag = xrf.URI.parse( url, flags )
let opts = {frag, mesh:xrf.camera, model, camera: xrf.camera, scene: xrf.scene, renderer: xrf.renderer, THREE: xrf.THREE, hashbus: xrf.hashbus } let fromNode = node_or_model != xrf.model
let opts = {
frag,
mesh: fromNode ? node_or_model : xrf.camera,
model: xrf.model,
camera: xrf.camera,
scene: xrf.scene,
renderer: xrf.renderer,
THREE: xrf.THREE,
hashbus: xrf.hashbus
}
xrf.emit('hashbus',opts) xrf.emit('hashbus',opts)
.then( () => { .then( () => {
for ( let k in frag ){ for ( let k in frag ){
pub.fragment(k,opts) let nodeAlias = fromNode && opts.mesh && opts.mesh.userData && opts.mesh.userData[k] && opts.mesh.userData[k][0] == '#'
if( nodeAlias ) pub(opts.mesh.userData[k], opts.mesh) // evaluate node alias
else pub.fragment(k,opts)
} }
}) })
return frag return frag
@ -37,7 +49,7 @@ pub.fragment = (k, opts ) => { // evaluate one fragment
let frag = opts.frag[k]; let frag = opts.frag[k];
let isPVorMediaFrag = frag.is( xrf.XRF.PV_EXECUTE ) || frag.is( xrf.XRF.T_MEDIAFRAG) let isPVorMediaFrag = frag.is( xrf.XRF.PV_EXECUTE ) || frag.is( xrf.XRF.T_MEDIAFRAG)
if( !opts.skipXRWG && isPVorMediaFrag ) pub.XRWG(opts) if( !opts.skipXRWG && isPVorMediaFrag ) pub.XRWG(k,opts)
// call native function (xrf/env.js e.g.), or pass it to user decorator // call native function (xrf/env.js e.g.), or pass it to user decorator
xrf.emit(k,opts) xrf.emit(k,opts)
@ -48,32 +60,37 @@ pub.fragment = (k, opts ) => { // evaluate one fragment
}) })
} }
pub.XRWG = (opts) => { pub.XRWG = (word,opts) => {
let {frag,scene,model,renderer} = opts let {frag,scene,model,renderer} = opts
// if this query was triggered by an src-value, lets filter it // if this query was triggered by an src-value, lets filter it
const isSRC = opts.embedded && opts.embedded.fragment == 'src' const isSRC = opts.embedded && opts.embedded.fragment == 'src'
if( !isSRC ){ // spec : https://xrfragment.org/#src if( !isSRC ){ // spec : https://xrfragment.org/#src
for ( let i in frag ) {
let v = frag[i]
let id = v.is( xrf.XRF.T_DYNAMIC ) ? v.fragment : v.string || v.fragment
if( id == '#' || !id ) return
let match = xrf.XRWG.match(id)
if( v.is( xrf.XRF.PV_EXECUTE ) && !v.is( xrf.XRF.T_DYNAMIC ) ){ let triggeredByMesh = opts.model != opts.mesh
// evaluate aliases
match.map( (w) => { let v = frag[word]
if( w.key == `#${id}` ){ let id = v.is( xrf.XRF.T_DYNAMICKEY ) ? word : v.string || word
if( w.value && w.value[0] == '#' ){
// if value is alias, execute fragment value if( id == '#' || !id ) return
xrf.hashbus.pub( w.value, xrf.model, xrf.XRF.METADATA | xrf.XRF.PV_OVERRIDE | xrf.XRF.NAVIGATOR ) let match = xrf.XRWG.match(id)
}
if( !triggeredByMesh && (v.is( xrf.XRF.PV_EXECUTE ) || v.is( xrf.XRF.T_DYNAMIC)) && !v.is( xrf.XRF.T_DYNAMICKEYVALUE ) ){
// evaluate global aliases or tag/objectnames
match.map( (w) => {
if( w.key == `#${id}` ){
if( w.value && w.value[0] == '#' ){
// if value is alias, execute fragment value
xrf.hashbus.pub( w.value, xrf.model, xrf.XRF.METADATA | xrf.XRF.PV_OVERRIDE | xrf.XRF.NAVIGATOR )
} }
}) }
xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene }) })
}else{ xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene })
xrf.emit('dynamicKeyValue',{ ...opts,v,frag,id,match,scene }) }else if( v.string ){
} // evaluate global aliases
xrf.emit('dynamicKeyValue',{ ...opts,v,frag,id,match,scene })
}else{
xrf.emit('dynamicKey',{ ...opts,v,frag,id,match,scene })
} }
} }
} }

View File

@ -1,4 +1,4 @@
xrf.frag = {} xrf.frag = {dynamic:{}}
xrf.model = {} xrf.model = {}
xrf.mixers = [] xrf.mixers = []
@ -24,7 +24,7 @@ xrf.patchRenderer = function(opts){
xrf.clock = new xrf.THREE.Clock() xrf.clock = new xrf.THREE.Clock()
renderer.render = ((render) => function(scene,camera){ renderer.render = ((render) => function(scene,camera){
// update clock // update clock
let time = xrf.clock.getDelta() let time = xrf.clock.delta = xrf.clock.getDelta()
xrf.emit('render',{scene,camera,time,render}) // allow fragments to do something at renderframe xrf.emit('render',{scene,camera,time,render}) // allow fragments to do something at renderframe
render(scene,camera) render(scene,camera)
xrf.emit('renderPost',{scene,camera,time,render,renderer}) // allow fragments to do something after renderframe xrf.emit('renderPost',{scene,camera,time,render,renderer}) // allow fragments to do something after renderframe

View File

@ -30,6 +30,12 @@ xrf.interactiveGroup = function(THREE,renderer,camera){
const raycaster = new Raycaster(); const raycaster = new Raycaster();
const tempMatrix = new Matrix4(); const tempMatrix = new Matrix4();
let dispatchEvent = (object,_event) => {
object.dispatchEvent(_event)
// bubble up
object.traverseAncestors( (n) => n.userData && n.userData.href && n.dispatchEvent(_event) )
}
// Pointer Events // Pointer Events
const element = renderer.domElement; const element = renderer.domElement;
@ -56,12 +62,12 @@ xrf.interactiveGroup = function(THREE,renderer,camera){
_event.type = event.type; _event.type = event.type;
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
object.dispatchEvent( _event ); dispatchEvent( object, _event );
}else{ }else{
if( object.selected ) { if( object.selected ) {
_event.type = 'mouseleave' _event.type = 'mouseleave'
object.dispatchEvent(_event) dispatchEvent( object, _event)
} }
} }
@ -106,12 +112,12 @@ xrf.interactiveGroup = function(THREE,renderer,camera){
_event.type = events[ event.type ]; _event.type = events[ event.type ];
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
object.dispatchEvent( _event ); dispatchEvent( object, _event );
}else{ }else{
if( object.selected ) { if( object.selected ) {
_event.type = 'mouseleave' _event.type = 'mouseleave'
object.dispatchEvent(_event) dispatchEvent( object, _event)
} }
} }

View File

@ -1,12 +1,14 @@
// this is called by navigator.js rather than by a URL e.g.
xrf.frag.defaultPredefinedViews = (opts) => { xrf.frag.defaultPredefinedViews = (opts) => {
let {scene,model} = opts; let {scene,model} = opts;
scene.traverse( (n) => { scene.traverse( (n) => {
if( n.userData && n.userData['#'] ){ if( n.userData && n.userData['#'] ){
let frag = xrf.URI.parse( n.userData['#'] ) let frag = xrf.URI.parse( n.userData['#'] )
if( !n.parent && document.location.hash.length < 2){ if( !n.parent && document.location.hash.length < 2){
xrf.navigator.to( n.userData['#'] ) // evaluate static XR fragments xrf.navigator.to( n.userData['#'] ) // evaluate default XR fragments (global-level)
}else{ }else{
xrf.hashbus.pub( n.userData['#'] ) // evaluate static XR fragments xrf.hashbus.pub( n.userData['#'], n ) // evaluate default XR fragments (node-level)
} }
} }
}) })

View File

@ -0,0 +1,56 @@
// this holds all the URI Template variables (https://www.rfc-editor.org/rfc/rfc6570)
xrf.addEventListener('parseModel', (opts) => {
let {model,url,file} = opts
if( model.isSRC || opts.isSRC ) return // ignore SRC models
xrf.URI.vars = new Proxy({},{
set(me,k,v){ me[k] = v },
get(me,k ){
if( k == '__object' ){
let obj = {}
for( let i in xrf.URI.vars ) obj[i] = xrf.URI.vars[i]()
return obj
}
return me[k]
},
})
model.scene.traverse( (n) => {
if( n.userData ){
for( let i in n.userData ){
if( i[0] == '#' || i.match(/^(href|src|tag)$/) ) continue // ignore XR Fragment aliases
xrf.URI.vars[i] = () => n.userData[i]
}
}
})
})
xrf.addEventListener('dynamicKeyValue', (opts) => {
// select active camera if any
let {id,match,v} = opts
if( !v.is( xrf.XRF.CUSTOMFRAG) ) return // only process custom frags from here
// check if fragment is an objectname
if( match.length > 0 ){
xrf.frag.dynamic.material(v,opts)
}else{
if( !xrf.URI.vars[ v.string ] ) return // only assign to known values
xrf.URI.vars[ id ] = xrf.URI.vars[ v.string ] // update var
if( xrf.debug ) console.log(`URI.vars[${id}]='${v.string}'`)
xrf.scene.traverse( (n) => { // reflect new changes
if( n.userData && n.userData.src && n.userData.srcTemplate ){
let srcOldFragments = n.userData.src.replace(/.*#/,'')
let srcNewFragments = xrf.frag.src.expandURI( n ).replace(/.*#/,'')
if( srcOldFragments != srcNewFragments ){
console.log(`URI.vars[${id}] => updating ${n.name}`)
let frag = xrf.hashbus.pub( srcNewFragments, n )
}
}
})
}
})

View File

@ -0,0 +1,14 @@
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('dynamicKey', (opts) => {
// select active camera if any
let {id,match,v} = opts
match.map( (w) => {
w.nodes.map( (node) => {
if( node.isCamera ){
console.log("switching camera to cam: "+node.name)
xrf.model.camera = node
}
})
})
})

View File

@ -1,7 +1,3 @@
/*
* TODO: refactor/fix this (queries are being refactored to filters)
*/
xrf.addEventListener('dynamicKey', (opts) => { xrf.addEventListener('dynamicKey', (opts) => {
let {scene,id,match,v} = opts let {scene,id,match,v} = opts

View File

@ -0,0 +1,35 @@
xrf.frag.dynamic.material = function(v,opts){
let {match} = opts
setMaterial = (mesh,material,reset) => {
if( !mesh.materialOriginal ) mesh.materialOriginal = mesh.material
if( reset ) mesh.material = mesh.materialOriginal
else mesh.material = material
}
// update material in case of <tag_or_object>[*]=<materialname>
let material
xrf.scene.traverse( (n) => n.material && (n.material.name == v.string) && (material = n.material) )
if( !material && !v.reset ) return // nothing to do
if( material ) xrf.frag.dynamic.material.setMatch(match,material,v)
}
xrf.frag.dynamic.material.setMatch = function(match,material,v){
match.map( (m) => {
m.nodes.map( (n) => {
n.material = setMaterial( n, material, v.reset )
if( v.filter.q.deep ) n.traverse( (c) => c.material && setMaterial( c, material, v.reset ) )
})
})
}
xrf.addEventListener('dynamicKey', (opts) => {
let {v,match} = opts
if( v.reset ){
xrf.frag.dynamic.material.setMatch(match,null,v)
}
})

View File

@ -62,7 +62,7 @@ xrf.frag.href = function(v, opts){
xrf.interactive.objects.map( (o) => { xrf.interactive.objects.map( (o) => {
let newState = o.name == mesh.name ? state : false let newState = o.name == mesh.name ? state : false
if( o.material ){ if( o.material ){
if( o.material.uniforms ) o.material.uniforms.selected.value = newState if( o.material.uniforms && o.material.uniforms.selected ) o.material.uniforms.selected.value = newState
//if( o.material.emissive ) o.material.emissive.r = o.material.emissive.g = o.material.emissive.b = newState ? 2.0 : 1.0 //if( o.material.emissive ) o.material.emissive.r = o.material.emissive.g = o.material.emissive.b = newState ? 2.0 : 1.0
if( o.material.emissive ){ if( o.material.emissive ){
if( !o.material.emissive.original ) o.material.emissive.original = o.material.emissive.clone() if( !o.material.emissive.original ) o.material.emissive.original = o.material.emissive.clone()

11
src/3rd/js/three/xrf/s.js Normal file
View File

@ -0,0 +1,11 @@
xrf.frag.suv = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( !mesh.geometry ) return // nothing to do here
xrf.frag.uv.init(mesh)
mesh.suv.x = v.x
mesh.suv.y = v.y !== undefined ? v.y : v.x
mesh.suv.loop = v.loop === true ? true : false
xrf.frag.uv.scroll(mesh)
}

View File

@ -4,17 +4,25 @@ xrf.frag.src = function(v, opts){
opts.embedded = v // indicate embedded XR fragment opts.embedded = v // indicate embedded XR fragment
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
let url = v.string let url = xrf.frag.src.expandURI( mesh, v.string )
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url) let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
opts.isLocal = v.string[0] == '#' opts.isLocal = v.string[0] == '#'
opts.isPortal = xrf.frag.src.renderAsPortal(mesh) opts.isPortal = xrf.frag.src.renderAsPortal(mesh)
opts.isSRC = true opts.isSRC = true
if(xrf.debug) console.log(`src.js: instancing ${opts.isLocal?'local':'remote'} object ${url}`) if(xrf.debug) console.log(`src.js: instancing ${opts.isLocal?'local':'remote'} object ${url}`)
if( opts.isLocal ){ if( opts.isLocal ){
xrf.frag.src.localSRC(url,srcFrag,opts) // local xrf.frag.src.localSRC(url,srcFrag,opts) // local
}else xrf.frag.src.externalSRC(url,srcFrag,opts) // external file }else xrf.frag.src.externalSRC(url,srcFrag,opts) // external file
xrf.hashbus.pub( url.replace(/.*#/,''), mesh) // eval src-url fragments
}
xrf.frag.src.expandURI = function(mesh,uri){
if( uri ) mesh.userData.srcTemplate = uri
mesh.userData.src = xrf.URI.template( mesh.userData.srcTemplate, xrf.URI.vars.__object )
return mesh.userData.src
} }
xrf.frag.src.addModel = (model,url,frag,opts) => { xrf.frag.src.addModel = (model,url,frag,opts) => {
@ -32,7 +40,7 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
}else{ }else{
xrf.frag.src.scale( scene, opts, url ) // scale scene xrf.frag.src.scale( scene, opts, url ) // scale scene
mesh.add(scene) mesh.add(scene)
xrf.emit('parseModel', {...opts, scene, model}) xrf.emit('parseModel', {...opts, isSRC:true, scene, model})
} }
// flag everything isSRC & isXRF // flag everything isSRC & isXRF
mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true }) mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
@ -66,7 +74,9 @@ xrf.frag.src.externalSRC = (url,frag,opts) => {
fetch(url, { method: 'HEAD' }) fetch(url, { method: 'HEAD' })
.then( (res) => { .then( (res) => {
let mimetype = res.headers.get('Content-type') let mimetype = res.headers.get('Content-type')
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf' if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
if( url.replace(/#.*/,'').match(/\.(frag|fs|glsl)$/) ) mimetype = 'x-shader/x-fragment'
if( url.replace(/#.*/,'').match(/\.(vert|vs)$/) ) mimetype = 'x-shader/x-fragment'
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype = //if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
opts = { ...opts, frag, mimetype } opts = { ...opts, frag, mimetype }
return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts) return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts)
@ -98,7 +108,6 @@ xrf.frag.src.scale = function(scene, opts, url){
// remove invisible objects (hidden by selectors) which might corrupt boundingbox size-detection // remove invisible objects (hidden by selectors) which might corrupt boundingbox size-detection
let cleanScene = scene.clone() let cleanScene = scene.clone()
if( !cleanScene ) debugger
let remove = [] let remove = []
const notVisible = (n) => !n.visible || (n.material && !n.material.visible) const notVisible = (n) => !n.visible || (n.material && !n.material.visible)
cleanScene.traverse( (n) => notVisible(n) && n.children.length == 0 && (remove.push(n)) ) cleanScene.traverse( (n) => notVisible(n) && n.children.length == 0 && (remove.push(n)) )

View File

@ -23,7 +23,8 @@ let loadAudio = (mimetype) => function(url,opts){
let sound = isPositionalAudio ? new THREE.PositionalAudio( camera.listener) let sound = isPositionalAudio ? new THREE.PositionalAudio( camera.listener)
: new THREE.Audio( camera.listener ) : new THREE.Audio( camera.listener )
mesh.audio = {} mesh.media = mesh.media || {}
mesh.media.audio = { play: () => mesh.media.audio.autoplay = true }
audioLoader.load( url.replace(/#.*/,''), function( buffer ) { audioLoader.load( url.replace(/#.*/,''), function( buffer ) {
@ -36,9 +37,11 @@ let loadAudio = (mimetype) => function(url,opts){
//sound.setDirectionalCone( 360, 360, 0.01 ); //sound.setDirectionalCone( 360, 360, 0.01 );
} }
sound.playXRF = (t) => { mesh.add(sound)
mesh.add(sound)
sound.pub = (t) => {
try{ try{
sound.t = t
if( sound.isPlaying && t.y != undefined ) sound.stop() if( sound.isPlaying && t.y != undefined ) sound.stop()
if( sound.isPlaying && t.y == undefined ) sound.pause() if( sound.isPlaying && t.y == undefined ) sound.pause()
@ -65,11 +68,11 @@ let loadAudio = (mimetype) => function(url,opts){
}catch(e){ console.warn(e) } }catch(e){ console.warn(e) }
} }
// autoplay if user already requested play // autoplay if user already requested play (before the sound was loaded)
let autoplay = mesh.audio && mesh.audio.autoplay let autoplay = mesh.media.audio && mesh.media.audio.autoplay
mesh.audio = sound mesh.media.audio = sound
if( autoplay ){ if( autoplay ){
xrf.hashbus.pub(mesh.audio.autoplay) xrf.hashbus.pub(mesh.media.audio.autoplay)
} }
}); });
} }

View File

@ -0,0 +1,66 @@
/*
* extensions: .frag/.fs/.vs/.vert
*/
xrf.frag.src.type['x-shader/x-fragment'] = function(url,opts){
let {mesh,THREE} = opts
let isFragmentShader = /\.(fs|frag|glsl)$/
let isVertexShader = /\.(vs|vert)$/
let shaderReqs = []
let shaderCode = {}
let shader = {
fragment: { code: '', url: url.match( isFragmentShader ) ? url : '' },
vertex: { code: '', url: url.match( isVertexShader ) ? url : '' }
}
var onShaderLoaded = ((args) => (type, status, code) => {
shader[type].status = status
shader[type].code = code
if( shader.fragment.code && shader.vertex.code ){
let oldMaterial = mesh.material
mesh.material = new THREE.RawShaderMaterial({
uniforms: {
time: { value: 1.0 },
resolution: { value: new THREE.Vector2(1.0,1.0) }
},
// basic shaders include following common vars/funcs: https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/common.glsl.js
fragmentShader: shader.fragment.status == 200 ? shader.fragment.code : THREE.ShaderChunk.meshbasic_frag,
vertexShader: shader.vertex.status == 200 ? shader.vertex.code : THREE.ShaderChunk.meshbasic_vert,
});
mesh.material.needsUpdate = true
mesh.needsUpdate = true
mesh.onBeforeRender = () => {
if( !mesh.material || !mesh.material.uniforms ) return mesh.onBeforeRender = function(){}
mesh.material.uniforms.time.value = xrf.clock.elapsedTime
}
}
})({})
// sidecar-load vertex shader file
if( shader.fragment.url && !shader.vertex.url ){
shader.vertex.url = shader.fragment.url.replace(/\.fs$/, '.vs')
.replace(/\.frag$/, '.vert')
}
if( shader.fragment.url ){
fetch(shader.fragment.url)
.then( (res) => res.text().then( (code) => onShaderLoaded('fragment',res.status,code) ) )
}
if( shader.vertex.url ){
fetch(shader.vertex.url)
.then( (res) => res.text().then( (code) => onShaderLoaded('vertex',res.status,code) ) )
}
}
xrf.frag.src.type['x-shader/x-vertex'] = xrf.frag.src.type['x-shader/x-fragment']

View File

@ -74,6 +74,9 @@ xrf.portalNonEuclidian = function(opts){
this.setupListeners = () => { this.setupListeners = () => {
// below is a somewhat weird tapdance to render the portals **after** the scene
// is rendered (otherwise it messes up occlusion)
mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){ mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){
mesh.portal.needUpdate = true mesh.portal.needUpdate = true
} }

View File

@ -5,7 +5,9 @@ let loadVideo = (mimetype) => function(url,opts){
const THREE = xrf.THREE const THREE = xrf.THREE
let frag = xrf.URI.parse( url ) let frag = xrf.URI.parse( url )
let video = mesh.video = document.createElement('video') mesh.media = mesh.media || {}
let video = mesh.media.video = document.createElement('video')
video.setAttribute("crossOrigin","anonymous") video.setAttribute("crossOrigin","anonymous")
video.setAttribute("playsinline",'') video.setAttribute("playsinline",'')
video.addEventListener('loadedmetadata', function(){ video.addEventListener('loadedmetadata', function(){
@ -17,21 +19,23 @@ let loadVideo = (mimetype) => function(url,opts){
// set range // set range
video.addEventListener('timeupdate', function timeupdate() { video.addEventListener('timeupdate', function timeupdate() {
if (video.t && video.t.y !== undefined && video.t.y > video.t.x && Math.abs(video.currentTime) >= video.t.y ){ if (video.t && video.t.y !== undefined && video.t.y > video.t.x && Math.abs(video.currentTime) >= video.t.y ){
if( video.t.speed.length ) video.currentTime = video.t.x // speed means loop if( video.looping ) video.currentTime = video.t.x // speed means loop
else video.pause() else video.pause()
} }
},false) },false)
}) })
video.src = url video.src = url
video.playXRF = (t) => { video.speed = 1.0
video.looping = false
video.pub = (t) => {
video.t = t video.t = t
video.pause() video.pause()
if( t.x !== undefined && t.x == t.y ) return // stop paused if( t.x !== undefined && t.x == t.y ) return // stop paused
else{ else{
video.currentTime = t.x video.currentTime = t.x
video.time = t.x video.time = t.x
video.playbackRate = Math.abs( t.speed.length ? t.speed[0] : 1.0 ) // html5 video does not support reverseplay :/ video.playbackRate = Math.abs( video.speed ) // html5 video does not support reverseplay :/
video.play() video.play()
} }
} }

View File

@ -0,0 +1,11 @@
xrf.frag.suv = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( !mesh.geometry ) return // nothing to do here
xrf.frag.uv.init(mesh)
mesh.suv.x = v.x
mesh.suv.y = v.y !== undefined ? v.y : v.x
mesh.suv.loop = v.loop === true ? true : false
mesh.onBeforeRender = xrf.frag.uv.scroll
}

View File

@ -1,5 +1,15 @@
// this is the global #t mediafragment handler (which affects the 3D animation)
xrf.frag.t = function(v, opts){ xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts let { frag, mesh, model, camera, scene, renderer, THREE} = opts
// handle object media players
if( mesh && mesh.media ){
for( let i in mesh.media ) mesh.media[i].pub(v)
return
}
// otherwise handle global 3D animations
if( !model.mixer ) return if( !model.mixer ) return
if( !model.animations || model.animations[0] == undefined ){ if( !model.animations || model.animations[0] == undefined ){
console.warn('no animations found in model') console.warn('no animations found in model')
@ -11,10 +21,10 @@ xrf.frag.t = function(v, opts){
mixer.t = v mixer.t = v
// update speed // update speed
mixer.timeScale = mixer.loop.speed = v.x mixer.timeScale = mixer.loop.speed || 1.0
mixer.loop.speedAbs = Math.abs(v.x) mixer.loop.speedAbs = Math.abs( mixer.timeScale )
if( v.y != undefined || v.z != undefined ) mixer.updateLoop( v ) mixer.updateLoop( v )
// play animations // play animations
mixer.play( v ) mixer.play( v )
@ -31,7 +41,7 @@ xrf.addEventListener('parseModel', (opts) => {
let {model} = opts let {model} = opts
let mixer = model.mixer = new xrf.THREE.AnimationMixer(model.scene) let mixer = model.mixer = new xrf.THREE.AnimationMixer(model.scene)
mixer.model = model mixer.model = model
mixer.loop = {timeStart:0,timeStop:0} mixer.loop = {timeStart:0,timeStop:0,speed:1.0}
mixer.i = xrf.mixers.length mixer.i = xrf.mixers.length
mixer.actions = [] mixer.actions = []
@ -41,7 +51,6 @@ xrf.addEventListener('parseModel', (opts) => {
mixer.actions.push( mixer.clipAction( anim, model.scene ) ) mixer.actions.push( mixer.clipAction( anim, model.scene ) )
}) })
mixer.checkZombies = (animations) => { mixer.checkZombies = (animations) => {
if( mixer.zombieCheck ) return // fire only once if( mixer.zombieCheck ) return // fire only once
animations.map( (anim) => { animations.map( (anim) => {
@ -62,7 +71,7 @@ xrf.addEventListener('parseModel', (opts) => {
} }
mixer.play = (t) => { mixer.play = (t) => {
mixer.isPlaying = t.x != 0 mixer.isPlaying = t.x !== undefined && t.x != t.y
mixer.updateLoop(t) mixer.updateLoop(t)
xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying}) xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying})
} }
@ -72,17 +81,15 @@ xrf.addEventListener('parseModel', (opts) => {
} }
mixer.updateLoop = (t) => { mixer.updateLoop = (t) => {
mixer.loop.timeStart = t.y != undefined ? t.y : mixer.loop.timeStart mixer.loop.timeStart = t.x != undefined ? t.x : mixer.loop.timeStart
mixer.loop.timeStop = t.z != undefined ? t.z : mixer.loop.timeStop mixer.loop.timeStop = t.y != undefined ? t.y : mixer.loop.timeStop
mixer.actions.map( (action) => { mixer.actions.map( (action) => {
if( mixer.loop.timeStart != undefined ){ if( mixer.loop.timeStart != undefined ){
action.time = mixer.loop.timeStart action.time = mixer.loop.timeStart
action.setLoop( xrf.THREE.LoopOnce, ) action.setLoop( xrf.THREE.LoopOnce, )
action.timeScale = mixer.timeScale action.timeScale = mixer.timeScale
action.enabled = true action.enabled = true
if( t.x != 0 ){ if( t.x === 0 ) action.play()
action.play()
}
} }
}) })
mixer.setTime(mixer.loop.timeStart) mixer.setTime(mixer.loop.timeStart)
@ -152,19 +159,6 @@ xrf.addEventListener('render', (opts) => {
} }
}) })
xrf.addEventListener('dynamicKey', (opts) => {
// select active camera if any
let {id,match,v} = opts
match.map( (w) => {
w.nodes.map( (node) => {
if( node.isCamera ){
console.log("switching camera to cam: "+node.name)
xrf.model.camera = node
}
})
})
})
// remove mixers and stop mixers when loading another scene // remove mixers and stop mixers when loading another scene
xrf.addEventListener('reset', (opts) => { xrf.addEventListener('reset', (opts) => {
xrf.mixers.map( (m) => m.stop()) xrf.mixers.map( (m) => m.stop())

View File

@ -0,0 +1,57 @@
xrf.frag.uv = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( !mesh.geometry ) return // nothing to do here
xrf.frag.uv.init(mesh)
mesh.uv.x = v.x
mesh.uv.y = v.y !== undefined ? v.y : v.x
mesh.onBeforeRender = xrf.frag.uv.scroll
}
xrf.frag.uv.init = function(mesh){
if( !mesh.uv ) mesh.uv = {x:0, y:0, w:1, h:1, uv:false}
if( !mesh.suv ) mesh.suv = {x:1, y:1, loop:false }
let uv = mesh.geometry.getAttribute("uv")
if( !uv.old ) uv.old = uv.clone()
}
xrf.frag.uv.scroll = function(){
if( this.suv.x > 0.0 || this.suv.y > 0.0 ){
let diff = 0.0 // distance to end-state (non-looping mode)
let uv = this.geometry.getAttribute("uv")
// translate!
for( let i = 0; i < uv.count; i++ ){
let u = uv.getX(i)
let v = uv.getY(i)
let uTarget = uv.old.getX(i) + this.uv.x
let vTarget = uv.old.getY(i) + this.uv.y
if( this.suv.loop ){
u += this.suv.x * xrf.clock.delta
v += this.suv.y * xrf.clock.delta
}else{
// recover from super-high uv-values due to looped scrolling
if( Math.abs(u-uTarget) > 1.0 ) u = uv.old.getX(i)
if( Math.abs(v-vTarget) > 1.0 ) v = uv.old.getY(i)
u = u > uTarget ? u + (this.suv.x * -xrf.clock.delta)
: u + (this.suv.x * xrf.clock.delta)
v = v > vTarget ? v + (this.suv.y * -xrf.clock.delta)
: v + (this.suv.y * xrf.clock.delta)
diff += Math.abs( u - uTarget ) // are we done yet? (non-looping mode)
diff += Math.abs( v - vTarget )
}
uv.setXY(i,u,v)
}
uv.needsUpdate = true
if( !this.suv.loop && diff < 0.05 ){ // stop animating if done
this.onBeforeRender = function(){}
}
}
}

View File

@ -0,0 +1,9 @@
xrf.frag.xywh = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
xrf.mediafragment.init(mesh)
mesh.xywh.x = v.floats[0]
mesh.xywh.y = v.floats[1] !== undefined ? v.floats[1] : mesh.xywh.x
mesh.xywh.w = v.floats[2] !== undefined ? v.floats[2] : mesh.xywh.y
mesh.xywh.h = v.floats[3] !== undefined ? v.floats[3] : mesh.xywh.w
// TODO: nondestructive cropping of texture (not superimportant now)
}

View File

@ -18,16 +18,17 @@ class Test {
test( "url.json", Spec.load("src/spec/url.json") ); test( "url.json", Spec.load("src/spec/url.json") );
test( "pos.json", Spec.load("src/spec/pos.json") ); test( "pos.json", Spec.load("src/spec/pos.json") );
test( "t.json", Spec.load("src/spec/t.json") ); test( "t.json", Spec.load("src/spec/t.json") );
test( "xywh.json", Spec.load("src/spec/xywh.json") ); test( "xywh.json", Spec.load("src/spec/xywh.json") );
test( "s.json", Spec.load("src/spec/s.json") ); test( "s.json", Spec.load("src/spec/s.json") );
test( "sxy.json", Spec.load("src/spec/sxy.json") ); test( "suv.json", Spec.load("src/spec/suv.json") );
test( "suv.json", Spec.load("src/spec/uv.json") );
test( "filter.selectors.json", Spec.load("src/spec/filter.selectors.json") ); test( "filter.selectors.json", Spec.load("src/spec/filter.selectors.json") );
//test( Spec.load("src/spec/tmp.json") ); //test( Spec.load("src/spec/tmp.json") );
if( errors > 1 ) trace("\n-----\n[ ] "+errors+" errors :/"); if( errors > 1 ) trace("\n-----\n[ ] "+errors+" errors :/");
} }
static public function test( topic:String, spec:Array<Dynamic>):Void { static public function test( topic:String, spec:Array<Dynamic>):Void {
trace("\n[.] running "+topic); trace("\n[ . ] running "+topic);
var Filter = xrfragment.Filter; var Filter = xrfragment.Filter;
for( i in 0...spec.length ){ for( i in 0...spec.length ){
var f:Filter = null; var f:Filter = null;
@ -52,7 +53,7 @@ class Test {
if( item.expect.fn == "equal.mediafragmentT" ) valid = equalMediaFragment(res,item,"t"); if( item.expect.fn == "equal.mediafragmentT" ) valid = equalMediaFragment(res,item,"t");
if( item.expect.fn == "equal.mediafragmentXYWH") valid = equalMediaFragment(res,item,"xywh"); if( item.expect.fn == "equal.mediafragmentXYWH") valid = equalMediaFragment(res,item,"xywh");
if( item.expect.fn == "equal.mediafragmentS") valid = equalMediaFragment(res,item,"s"); if( item.expect.fn == "equal.mediafragmentS") valid = equalMediaFragment(res,item,"s");
if( item.expect.fn == "equal.mediafragmentSXY") valid = equalMediaFragment(res,item,"sxy"); if( item.expect.fn == "equal.mediafragmentSUV") valid = equalMediaFragment(res,item,"suv");
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 == "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; 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 ? "[ ] " : "[ ] "; var ok:String = valid ? "[ ] " : "[ ] ";

View File

@ -10,6 +10,5 @@
{"fn":"filter","data":"price=>2", "expect":{ "fn":"testProperty","input":["price","1"],"out":false}}, {"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","5"],"out":false}},
{"fn":"filter","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","1"],"out":true}}, {"fn":"filter","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","1"],"out":true}},
{"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":1},"label":"foo should be deep"}
{"fn":"url","data":"#foo**", "expect":{ "fn":"testFilterDeep","input":["foo"],"out":2},"label":"foo should be deep incl. embeds"}
] ]

View File

@ -1,5 +1,5 @@
[ [
{"fn":"url","data":"http://foo.com?foo=1#s=1", "expect":{ "fn":"equal.mediafragmentSpeed", "input":"0","out":"1"},"label":"playback speed"}, {"fn":"url","data":"http://foo.com?foo=1#s=1", "expect":{ "fn":"equal.x", "input":"s","out":"1"},"label":"playback speed"},
{"fn":"url","data":"http://foo.com?foo=1#s=0.5", "expect":{ "fn":"equal.mediafragmentSpeed", "input":"0","out":"0.5"},"label":"playback speed"}, {"fn":"url","data":"http://foo.com?foo=1#s=0.5", "expect":{ "fn":"equal.x", "input":"s","out":"0.5"},"label":"playback speed"},
{"fn":"url","data":"http://foo.com?foo=1#s=-0.5", "expect":{ "fn":"equal.mediafragmentSpeed", "input":"0","out":"-0.5"},"label":"playback speed"} {"fn":"url","data":"http://foo.com?foo=1#s=-0.5", "expect":{ "fn":"equal.x", "input":"s","out":"-0.5"},"label":"playback speed"}
] ]

View File

@ -1,4 +1,4 @@
[ [
{"fn":"url","data":"http://foo.com?foo=1#sxy=l:0,0.1", "expect":{ "fn":"equal.mediafragmentSXY", "input":"1","out":"0.2"},"label":"sxy"}, {"fn":"url","data":"http://foo.com?foo=1#suv=l:0,0.1", "expect":{ "fn":"equal.mediafragmentSUV", "input":"1","out":"0.1"},"label":"suv"},
{"fn":"url","data":"http://foo.com?foo=1#sxy=0,0.1", "expect":{ "fn":"equal.mediafragmentSXY", "input":"1","out":"0.2"},"label":"sxy looped"} {"fn":"url","data":"http://foo.com?foo=1#suv=0.2,0.1", "expect":{ "fn":"equal.mediafragmentSUV", "input":"0","out":"0.2"},"label":"suv looped"}
] ]

View File

@ -8,8 +8,5 @@
{"fn":"url","data":"http://foo.com?foo=1#t=1,100", "expect":{ "fn":"equal.xy", "input":"t","out":"1,100"},"label":"a equal.xy"}, {"fn":"url","data":"http://foo.com?foo=1#t=1,100", "expect":{ "fn":"equal.xy", "input":"t","out":"1,100"},"label":"a equal.xy"},
{"fn":"url","data":"http://foo.com?foo=1#t=2,500", "expect":{ "fn":"testBrowserOverride", "input":"t","out":true},"label":"browser URI can override t (defined in asset)"}, {"fn":"url","data":"http://foo.com?foo=1#t=2,500", "expect":{ "fn":"testBrowserOverride", "input":"t","out":true},"label":"browser URI can override t (defined in asset)"},
{"fn":"url","data":"http://foo.com?foo=1#t=1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment"}, {"fn":"url","data":"http://foo.com?foo=1#t=1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment"},
{"fn":"url","data":"http://foo.com?foo=1#t=l:1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment loop"}, {"fn":"url","data":"http://foo.com?foo=1#t=l:1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment loop"}
{"fn":"url","data":"http://foo.com?foo=1#t=v:l:1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment uv loop "},
{"fn":"url","data":"http://foo.com?foo=1#t=v:1,100,400,500", "expect":{ "fn":"equal.mediafragmentT", "input":"3","out":"500"},"label":"a equal.mediafragment uv"},
{"fn":"url","data":"http://foo.com?foo=1#t=v:1,2", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test uv is set"}
] ]

3
src/spec/uv.json Normal file
View File

@ -0,0 +1,3 @@
[
{"fn":"url","data":"http://foo.com?foo=1#uv=1.2,2.2", "expect":{ "fn":"equal.xy", "input":"uv","out":"1.2,2.2"},"label":"equal.string uv"}
]

View File

@ -15,27 +15,27 @@ class Parser {
// here we define allowed characteristics & datatypes for each fragment (stored as bitmasked int for performance purposes) // here we define allowed characteristics & datatypes for each fragment (stored as bitmasked int for performance purposes)
var Frag:Map<String, Int> = new Map<String, Int>(); var Frag:Map<String, Int> = new Map<String, Int>();
Frag.set("#", XRF.ASSET | XRF.T_PREDEFINED_VIEW | XRF.PV_EXECUTE ); Frag.set("#", XRF.IMMUTABLE | XRF.T_PREDEFINED_VIEW | XRF.PV_EXECUTE );
Frag.set("src", XRF.ASSET | XRF.T_URL ); Frag.set("src", XRF.T_URL );
Frag.set("href", XRF.ASSET | XRF.T_URL | XRF.T_PREDEFINED_VIEW ); Frag.set("href", XRF.T_URL | XRF.T_PREDEFINED_VIEW );
Frag.set("tag", XRF.ASSET | XRF.T_STRING ); Frag.set("tag", XRF.IMMUTABLE | XRF.T_STRING );
// spatial category: query selector / object manipulation // spatial category: query selector / object manipulation
Frag.set("pos", XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.T_STRING | XRF.METADATA | XRF.NAVIGATOR ); Frag.set("pos", XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.T_STRING | XRF.METADATA | XRF.NAVIGATOR );
Frag.set("rot", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR ); Frag.set("rot", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR );
// category: media fragments // category: media fragments
Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_MEDIAFRAG | XRF.NAVIGATOR | XRF.METADATA); Frag.set("t", XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_MEDIAFRAG | XRF.NAVIGATOR | XRF.METADATA);
Frag.set("xywh", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_MEDIAFRAG | XRF.NAVIGATOR | XRF.METADATA); Frag.set("xywh", XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_MEDIAFRAG | XRF.NAVIGATOR | XRF.METADATA);
Frag.set("s", XRF.PV_OVERRIDE | XRF.T_MEDIAFRAG | XRF.T_FLOAT );
Frag.set("uv", XRF.T_VECTOR2 | XRF.T_MEDIAFRAG );
Frag.set("suv", XRF.T_VECTOR2 | XRF.T_MEDIAFRAG );
// category: author / metadata // category: author / metadata
Frag.set("namespace", XRF.ASSET | XRF.T_STRING ); Frag.set("namespace", XRF.IMMUTABLE | XRF.T_STRING );
Frag.set("SPDX", XRF.ASSET | XRF.T_STRING ); Frag.set("SPDX", XRF.IMMUTABLE | XRF.T_STRING );
Frag.set("unit", XRF.ASSET | XRF.T_STRING ); Frag.set("unit", XRF.IMMUTABLE | XRF.T_STRING );
Frag.set("description", XRF.ASSET | XRF.T_STRING ); Frag.set("description", XRF.IMMUTABLE | XRF.T_STRING );
// category: multiparty
Frag.set("session", XRF.ASSET | XRF.T_URL | XRF.PV_OVERRIDE | XRF.NAVIGATOR | XRF.METADATA | XRF.PROMPT );
/** /**
* # Spec * # Spec
@ -51,11 +51,12 @@ class Parser {
// dynamic fragments cases: predefined views & assign/binds // dynamic fragments cases: predefined views & assign/binds
var isPVDynamic:Bool = key.length > 0 && !Frag.exists(key); var isPVDynamic:Bool = key.length > 0 && !Frag.exists(key);
var isPVDefault:Bool = value.length == 0 && key.length > 0 && key == "#"; if( isPVDynamic ){ // 1. add key(values) to store as [predefined view](predefined_view) or dynamic assignment
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 ); var v:XRF = new XRF(key, XRF.PV_EXECUTE | XRF.NAVIGATOR, index );
v.validate(value); // ignore failures (empty values are allowed) v.validate(value); // ignore failures (empty values are allowed)
v.flags = XRF.set( XRF.T_DYNAMIC, v.flags ); v.flags = XRF.set( XRF.T_DYNAMICKEY, v.flags );
if( !Frag.exists(key) ) v.flags = XRF.set( XRF.CUSTOMFRAG, v.flags );
if( value.length == 0 ) v.flags = XRF.set( XRF.T_DYNAMICKEYVALUE, v.flags );
store.set( keyStripped, v ); store.set( keyStripped, v );
return true; return true;
} }
@ -67,11 +68,11 @@ class Parser {
trace(" fragment '"+key+"' has incompatible value ("+value+")");// 1. don't add to store if value-type is incorrect trace(" fragment '"+key+"' has incompatible value ("+value+")");// 1. don't add to store if value-type is incorrect
return false; return false;
} }
store.set( keyStripped, v); // 1. if valid, add to store store.set( keyStripped, v); // 1. if valid, add to store
if( debug ) trace(" "+key+": "+v.string); if( debug ) trace(" "+key+": "+v.string);
}else{ // 1. expose (but mark) non-offical fragments too }else{ // 1. expose (but mark) non-offical fragments too
if( Std.isOfType(value, String) ) v.guessType(v,value); if( Std.isOfType(value, String) ) v.guessType(v,value);
v.noXRF = true; v.flags = XRF.set( XRF.CUSTOMFRAG, v.flags );
store.set( keyStripped ,v); store.set( keyStripped ,v);
} }
return true; return true;

View File

@ -52,6 +52,19 @@ class URI {
} }
return store; return store;
} }
@keep
public static function template(uri:String, vars:Dynamic):String {
var parts = uri.split("#");
if( parts.length == 1 ) return uri; // we only do level1 fragment expansion
var frag = parts[1];
frag = StringTools.replace(frag,"{","::");
frag = StringTools.replace(frag,"}","::");
frag = new haxe.Template(frag).execute(vars);
frag = StringTools.replace(frag,"null",""); // *TODO* needs regex to check for [#&]null[&]
parts[1] = frag;
return parts.join("#");
}
} }
/** /**

View File

@ -10,14 +10,14 @@ class XRF {
* this class represents a fragment (value) * this class represents a fragment (value)
*/ */
// public static inline readonly ASSET // public static inline readonly IMMUTABLE
// scope types (powers of 2) // scope types (powers of 2)
public static var ASSET:Int = 1; // fragment is immutable public static var IMMUTABLE:Int = 1; // fragment is immutable
public static var PROP_BIND:Int = 2; // fragment binds/controls one property with another public static var PROP_BIND:Int = 2; // fragment binds/controls one property with another
public static var QUERY_OPERATOR:Int = 4; // fragment will be applied to result of filterselecto public static var QUERY_OPERATOR:Int = 4; // fragment will be applied to result of filterselecto
public static var PROMPT:Int = 8; // ask user whether this fragment value can be changed 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 CUSTOMFRAG: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 NAVIGATOR:Int = 32; // fragment can be overridden by (manual) browser URI change
public static var METADATA:Int = 64; // fragment can be overridden by an embedded URL public static var METADATA: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_OVERRIDE:Int = 128; // embedded fragment can be overridden when specified in predefined view value
@ -33,7 +33,8 @@ class XRF {
public static var T_PREDEFINED_VIEW:Int = 524288; public static var T_PREDEFINED_VIEW:Int = 524288;
public static var T_STRING:Int = 1048576; public static var T_STRING:Int = 1048576;
public static var T_MEDIAFRAG:Int = 2097152; public static var T_MEDIAFRAG:Int = 2097152;
public static var T_DYNAMIC:Int = 4194304; public static var T_DYNAMICKEY:Int = 4194304;
public static var T_DYNAMICKEYVALUE:Int = 8388608;
// regexes // 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 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})$/`
@ -43,12 +44,13 @@ class XRF {
public static var isUrl:EReg = ~/(:\/\/)?\..*/; // 1. url/file */` public static var isUrl:EReg = ~/(:\/\/)?\..*/; // 1. url/file */`
public static var isUrlOrPretypedView: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 operators:EReg = ~/(^[-]|^[!]|[\*]$)/g; // 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 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 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 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\.]+$/` ) public static var isNumber:EReg = ~/^[0-9\.]+$/; // 1. detect number values like `foo=1` (reference regex= `/^[0-9\.]+$/` )
public static var isMediaFrag:EReg = ~/^(uv:)?(l:)?([0-9\.,\*]+)$/; // 1. detect (extended) media fragment public static var isMediaFrag:EReg = ~/^(l:)?([0-9\.,\*]+)$/; // 1. detect (extended) media fragment
public static var isReset:EReg = ~/^!/; // 1. detect reset operation
// value holder(s) // |------|------|--------|----------------------------------| // value holder(s) // |------|------|--------|----------------------------------|
public var fragment:String; public var fragment:String;
@ -58,15 +60,13 @@ class XRF {
public var y:Float; public var y:Float;
public var z:Float; public var z:Float;
public var floats:Array<Float> = new Array<Float>(); public var floats:Array<Float> = new Array<Float>();
public var speed:Array<Float> = new Array<Float>();
public var color:String; // |string| color| FFFFFF (hex) | #fog=5m,FFAACC | public var color:String; // |string| color| FFFFFF (hex) | #fog=5m,FFAACC |
public var string:String; // |string| | | #q=-sun | public var string:String; // |string| | | #q=-sun |
public var int:Int; // |int | | [-]x[xxxxx] | #price:>=100 | public var int:Int; // |int | | [-]x[xxxxx] | #price:>=100 |
public var float:Float; // |float | | [-]x[.xxxx] (ieee)| #prio=-20 | public var float:Float; // |float | | [-]x[.xxxx] (ieee)| #prio=-20 |
public var filter:Filter; public var filter:Filter;
public var noXRF:Bool; public var reset:Bool;
public var loop:Bool; public var loop:Bool;
public var uv:Bool;
// //
public function new(_fragment:String,_flags:Int,?_index:Int){ public function new(_fragment:String,_flags:Int,?_index:Int){
fragment = _fragment; fragment = _fragment;
@ -99,16 +99,13 @@ class XRF {
@:keep @:keep
public function guessType(v:XRF, str:String):Void { public function guessType(v:XRF, str:String):Void {
v.string = str; v.string = str;
if( isReset.match(v.fragment) ) v.reset = true;
if( !Std.isOfType(str,String) ) return; if( !Std.isOfType(str,String) ) return;
if( str.length > 0 ){ if( str.length > 0 ){
if( str.split("l:").length > 1 ){ if( str.split("l:").length > 1 ){
str = str.split("l:")[1]; str = str.split("l:")[1];
v.loop = true; v.loop = true;
} }
if( str.split("uv:").length > 1 ){
str = str.split("uv:")[1];
v.uv = true;
}
if( str.split(",").length > 1){ // 1. `,` assumes 1D/2D/3D vector-values like x[,y[,z]] if( str.split(",").length > 1){ // 1. `,` assumes 1D/2D/3D vector-values like x[,y[,z]]
var xyzn:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values var xyzn:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
if( xyzn.length > 0 ) v.x = Std.parseFloat(xyzn[0]); // 1. anything else will be treated as string-value if( xyzn.length > 0 ) v.x = Std.parseFloat(xyzn[0]); // 1. anything else will be treated as string-value
@ -127,6 +124,7 @@ class XRF {
if( isInt.match(str) ){ if( isInt.match(str) ){
v.int = Std.parseInt(str); v.int = Std.parseInt(str);
v.x = cast(v.int); v.x = cast(v.int);
v.floats.push( cast(v.x) );
} }
v.filter = new Filter(v.fragment+"="+v.string); v.filter = new Filter(v.fragment+"="+v.string);
}else v.filter = new Filter(v.fragment); }else v.filter = new Filter(v.fragment);