update documentation

This commit is contained in:
Leon van Kammen 2023-10-30 16:15:08 +01:00
parent d91d8b5c16
commit e368b74e4f
12 changed files with 1366 additions and 947 deletions

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,7 @@
# nativeBuildInputs is usually what you want -- tools you need to run # nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [ nativeBuildInputs = with pkgs.buildPackages; [
nodejs
haxe haxe
mmark mmark
xml2rfc xml2rfc

View file

@ -1,5 +1,6 @@
xrf.frag = {} xrf.frag = {}
xrf.model = {} xrf.model = {}
xrf.mixers = []
xrf.init = ((init) => function(opts){ xrf.init = ((init) => function(opts){
let scene = new opts.THREE.Group() let scene = new opts.THREE.Group()
@ -8,23 +9,26 @@ xrf.init = ((init) => function(opts){
init(opts) init(opts)
if( opts.loaders ) Object.values(opts.loaders).map( xrf.patchLoader ) if( opts.loaders ) Object.values(opts.loaders).map( xrf.patchLoader )
xrf.patchRenderer(opts.renderer) xrf.patchRenderer(opts)
xrf.navigator.init() xrf.navigator.init()
// return xrfragment lib as 'xrf' query functor (like jquery) // return xrfragment lib as 'xrf' query functor (like jquery)
for ( let i in xrf ) xrf.query[i] = xrf[i] for ( let i in xrf ) xrf.query[i] = xrf[i]
return xrf.query return xrf.query
})(xrf.init) })(xrf.init)
xrf.patchRenderer = function(renderer){ xrf.patchRenderer = function(opts){
let {renderer,camera} = opts
renderer.xr.addEventListener( 'sessionstart', () => xrf.baseReferenceSpace = renderer.xr.getReferenceSpace() ); renderer.xr.addEventListener( 'sessionstart', () => xrf.baseReferenceSpace = renderer.xr.getReferenceSpace() );
renderer.xr.enabled = true; renderer.xr.enabled = true;
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.model && xrf.model.clock ? xrf.model.clock.getDelta() : 0 let time = xrf.clock.getDelta()
// allow entities to do stuff during render (onBeforeRender and onAfterRender don't always fire) // allow entities to do stuff during render (onBeforeRender and onAfterRender don't always fire)
xrf.emit('render',{scene,camera,time}) // allow fragments to do something at renderframe xrf.emit('render',{scene,camera,time}) // allow fragments to do something at renderframe
render(scene,camera) render(scene,camera)
})(renderer.render.bind(renderer)) })(renderer.render.bind(renderer))
} }
xrf.patchLoader = function(loader){ xrf.patchLoader = function(loader){
@ -78,6 +82,12 @@ xrf.reset = () => {
xrf.add( xrf.interactive ) xrf.add( xrf.interactive )
xrf.layers = 0 xrf.layers = 0
xrf.emit('reset',{}) xrf.emit('reset',{})
// remove mixers
xrf.mixers.map( (m) => {
m.stop()
delete m
})
xrf.mixers = []
} }
xrf.parseUrl = (url) => { xrf.parseUrl = (url) => {

View file

@ -34,7 +34,7 @@ xrf.frag.href = function(v, opts){
if( mesh.userData.XRF.href.exec ) return // mesh already initialized if( mesh.userData.XRF.href.exec ) return // mesh already initialized
mesh.material = mesh.material.clone() // we need this so we can individually highlight meshes if( mesh.material ) mesh.material = mesh.material.clone() // we need this so we can individually highlight meshes
let click = mesh.userData.XRF.href.exec = (e) => { let click = mesh.userData.XRF.href.exec = (e) => {

View file

@ -23,28 +23,34 @@ let loadAudio = (mimetype) => function(url,opts){
let sound = isPositionalAudio ? new THREE.PositionalAudio(listener) : new THREE.Audio(listener) let sound = isPositionalAudio ? new THREE.PositionalAudio(listener) : new THREE.Audio(listener)
audioLoader.load( url.replace(/#.*/,''), function( buffer ) { audioLoader.load( url.replace(/#.*/,''), function( buffer ) {
sound.setBuffer( buffer ); sound.setBuffer( buffer );
sound.setLoop(true); sound.setLoop(false);
sound.setVolume(0.5); sound.setVolume(1.0);
sound.playXRF = (t) => { sound.playXRF = (t) => {
if( sound.isPlaying ) sound.stop()
if( sound.isPlaying && t.y != undefined ) sound.stop()
if( sound.isPlaying && t.y == undefined ) sound.pause()
let hardcodedLoop = frag.t != undefined let hardcodedLoop = frag.t != undefined
t = hardcodedLoop ? { ...frag.t, x: t.x} : t // override with hardcoded metadata except playstate (x) t = hardcodedLoop ? { ...frag.t, x: t.x} : t // override with hardcoded metadata except playstate (x)
if( t && t.x != 0 ){ if( t && t.x != 0 ){
// *TODO* https://stackoverflow.com/questions/12484052/how-can-i-reverse-playback-in-web-audio-api-but-keep-a-forward-version-as-well // *TODO* https://stackoverflow.com/questions/12484052/how-can-i-reverse-playback-in-web-audio-api-but-keep-a-forward-version-as-well
sound.setPlaybackRate( Math.abs(t.x) ) // WebAudio does not support negative playback t.x = Math.abs(t.x)
sound.setPlaybackRate( t.x ) // WebAudio does not support negative playback
// setting loop // setting loop
sound.setLoop( t.z > 0 ) if( t.z ) sound.setLoop( true )
// apply embedded audio/video samplerate/fps or global mixer fps // apply embedded audio/video samplerate/fps or global mixer fps
let loopStart = hardcodedLoop ? t.y / buffer.sampleRate : t.y / xrf.model.mixer.loop.fps let loopStart = hardcodedLoop ? t.y / buffer.sampleRate : t.y / xrf.model.mixer.loop.fps
let loopEnd = hardcodedLoop ? t.z / buffer.sampleRate : t.z / xrf.model.mixer.loop.fps let loopEnd = hardcodedLoop ? t.z / buffer.sampleRate : t.z / xrf.model.mixer.loop.fps
let timeStart = loopStart > 0 ? loopStart : xrf.model.mixer.time let timeStart = loopStart > 0 ? loopStart : (t.y == undefined ? xrf.model.mixer.time : t.y)
if( t.y > 0 ) sound.setLoopStart( loopStart )
if( t.z > 0 ) sound.setLoopEnd( loopEnd ) if( t.z > 0 ) sound.setLoopEnd( loopEnd )
if( t.x != 0 ){ if( t.y != undefined ){
sound.offset = loopStart > 0 ? loopStart : timeStart console.dir({loopStart,t})
sound.play() sound.setLoopStart( loopStart )
sound.offset = loopStart
} }
sound.play()
} }
} }
mesh.add(sound) mesh.add(sound)
@ -57,11 +63,11 @@ let audioMimeTypes = [
'audio/mpeg', 'audio/mpeg',
'audio/weba', 'audio/weba',
'audio/aac', 'audio/aac',
'audio/ogg',
'application/ogg' 'application/ogg'
] ]
audioMimeTypes.map( (mimetype) => xrf.frag.src.type[ mimetype ] = loadAudio(mimetype) ) audioMimeTypes.map( (mimetype) => xrf.frag.src.type[ mimetype ] = loadAudio(mimetype) )
// listen to t XR fragment changes
xrf.addEventListener('t', (opts) => { xrf.addEventListener('t', (opts) => {
let t = opts.frag.t let t = opts.frag.t
xrf.audio.map( (a) => a.playXRF(t) ) xrf.audio.map( (a) => a.playXRF(t) )

View file

@ -1,6 +1,6 @@
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
console.log(` └ setting animation loop to ${v.x} ${v.y} ${v.z}` ) if( !model.mixer ) return
if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene') if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene')
model.mixer.t = v model.mixer.t = v
@ -10,20 +10,61 @@ xrf.frag.t = function(v, opts){
// update speed // update speed
mixer.timeScale = mixer.loop.speed mixer.timeScale = mixer.loop.speed
let updateTime = (time) => { if( v.y != undefined || v.z != undefined ) xrf.mixers[0].updateLoop( mixer.loop.timeStart )
mixer.setTime(time) if( v.y > 0 && v.z > 0 ){
mixer.time = Math.abs(mixer.time) xrf.model.animations.map( (anim) => {
mixer.update(0) // (forgetting) this little buddy costed me lots of time :] if( !anim.action ) return
anim.action.setLoop( v.z == 0 ? THREE.LoopOnce : THREE.LoopRepeat, v.z == 0 ? 0 : 99999999)
})
} }
// play animations // play animations
mixer.play( v.x == 1 ) mixer.play( v.x != 0 )
if( v.y > 0 || v.z > 0 ) updateTime( mixer.loop.timeStart ) }
if( v.y > 0 && v.z > 0 ){
xrf.model.animations.map( (anim) => { xrf.frag.t.default = {x:1, y:0, z:0}
anim.action.setLoop( v.z == 0 ? THREE.LoopOnce : THREE.LoopRepeat, v.z == 0 ? 0 : 99999999)
xrf.frag.t.calculateLoop = (t,obj,fps) => {
obj.speed = t ? t.x : 0
obj.speedAbs = Math.abs(obj.speed)
obj.frameStart = t ? t.y || obj.frameStart : 1
obj.frameStop = t ? t.z || obj.frameStop : 0
// always recalculate time using frameStart/Stop
obj.timeStart = obj.frameStart == 0 ? obj.mixerStart || 0 : obj.frameStart-1 / (fps * obj.speedAbs)
obj.timeStop = obj.frameStop / (fps * obj.speedAbs)
}
xrf.frag.t.setupMixer = function(opts){
let {model,file,url} = opts
let mixer = model.mixer = new xrf.THREE.AnimationMixer(model.scene)
mixer.play = (play) => {
mixer.isPlaying = play
model.animations.map( (anim) => {
if( !anim.action ){
anim.action = mixer.clipAction( anim )
anim.action.enabled = true
anim.action.setLoop(THREE.LoopOnce)
}
if( mixer.isPlaying === false) anim.action.stop()
else{
anim.action.play()
}
mixer.update(0)
}) })
xrf.emit( play === false ? 'stop' : 'play',{play})
}
mixer.stop = () => {
mixer.play(false)
}
mixer.updateLoop = (time) => {
console.log("updateLoop "+time)
mixer.setTime(time)
mixer.time = Math.abs(time)
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
} }
// update loop when needed // update loop when needed
@ -33,25 +74,29 @@ xrf.frag.t = function(v, opts){
mixer.time = Math.abs(mixer.time) mixer.time = Math.abs(mixer.time)
if( time == 0 ) return update.call(this,time) if( time == 0 ) return update.call(this,time)
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){ // loop jump
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion //if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
} // setTimeout( (time) => mixer.updateLoop(time),0,mixer.loop.timeStart) // prevent recursion
return update.call( mixer, time ) //}
return update.call( this, time )
} }
mixer.update.patched = true mixer.update.patched = true
} }
// calculate total duration/frame based on longest animation
mixer.duration = 0
mixer.frames = 0
if( model.animations.length ){
model.animations.map( (a) => {
mixer.duration = a.duration > mixer.duration ? a.duration : mixer.duration
mixer.frames = a.tracks[0].times.length > mixer.frames ? a.tracks[0].times.length : mixer.frames
})
} }
xrf.frag.t.default = {x:1, y:0, z:0} mixer.loop = {fps: mixer.frames / mixer.duration}
xrf.frag.t.calculateLoop( null, mixer.loop, mixer.loop.fps ) // gltf uses looppoints in seconds (not frames)
xrf.frag.t.calculateLoop = (t,obj,fps) => { xrf.mixers.push(mixer)
obj.speed = t.x console.log("mixer fps="+mixer.loop.fps+" frames:"+mixer.frames+" duration:"+mixer.duration)
obj.speedAbs = Math.abs(t.x)
obj.frameStart = t.y || obj.frameStart
obj.frameStop = t.z || obj.frameStop
// always recalculate time using frameStart/Stop
obj.timeStart = obj.frameStart / (fps * obj.speedAbs)
obj.timeStop = obj.frameStop / (fps * obj.speedAbs)
} }
if( document.location.hash.match(/t=/) ){ if( document.location.hash.match(/t=/) ){
@ -68,36 +113,17 @@ if( document.location.hash.match(/t=/) ){
xrf.addEventListener('parseModel', (opts) => { xrf.addEventListener('parseModel', (opts) => {
let {model,file,url} = opts let {model,file,url} = opts
// add animations // add animations
model.clock = new xrf.THREE.Clock(); xrf.frag.t.setupMixer(opts)
let mixer = new xrf.THREE.AnimationMixer(model.scene)
mixer.play = (play) => {
mixer.isPlaying = play
model.animations.map( (anim) => {
anim.action = mixer.clipAction( anim )
anim.action.setLoop(THREE.LoopOnce,0)
if( play === false) anim.action.stop()
else anim.action.play()
})
xrf.emit( play === false ? 'stop' : 'play',{play})
}
mixer.stop = () => {
mixer.play(false)
}
mixer.duration = model.animations.length ? model.animations[0].duration : 1
mixer.frames = model.animations.length ? model.animations[0].tracks[0].times.length : 1
mixer.loop = mixer.loop || {frameStart:0,frameStop:99999999,speed: 1}
mixer.loop.fps = mixer.frames / mixer.duration
model.mixer = mixer
}) })
xrf.addEventListener('render', (opts) => { xrf.addEventListener('render', (opts) => {
let model = xrf.model let model = xrf.model
let {time} = opts let {time} = opts
if( !model || !model.clock ) return if( !model ) return
model.mixer.update( time ) if( xrf.mixers.length ) xrf.mixers.map( (m) => m.update( time ) )
// update camera if possible // update camera if possible
if( model.cameras.length && model.mixer.isPlaying ){ if( model.cameras && model.cameras.length && xrf.mixers.length ){
let cam = xrf.camera.getCam() let cam = xrf.camera.getCam()
// cam.fov = model.cameras[0].fov (why is blender not exporting radians?) // cam.fov = model.cameras[0].fov (why is blender not exporting radians?)

View file

@ -1,14 +1,14 @@
xrf.addEventListener('pos', (opts) => { xrf.addEventListener('pos', (opts) => {
let { frag, mesh, model, camera, scene, renderer, THREE} = opts let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( frag.pos && frag.q ){ //if( frag.pos && frag.q ){
// apply roundrobin (if any) // // apply roundrobin (if any)
if( v.args ) v = v.args[ xrf.roundrobin(v,model) ] // if( v.args ) v = v.args[ xrf.roundrobin(v,model) ]
frag.q.getObjects().map( (o) => { // frag.q.getObjects().map( (o) => {
// if object has no parent (name == 'Scene') use absolute positioning, otherwise relative to parent // // if object has no parent (name == 'Scene') use absolute positioning, otherwise relative to parent
o.position.x = o.parent.name == 'Scene' ? v.x : o.positionOriginal.x + v.x // o.position.x = o.parent.name == 'Scene' ? v.x : o.positionOriginal.x + v.x
o.position.y = o.parent.name == 'Scene' ? v.z : o.positionOriginal.y + v.z // o.position.y = o.parent.name == 'Scene' ? v.z : o.positionOriginal.y + v.z
o.position.z = o.parent.name == 'Scene' ? v.y : o.positionOriginal.z + v.y // o.position.z = o.parent.name == 'Scene' ? v.y : o.positionOriginal.z + v.y
}) // })
} //}
}) })

View file

@ -15,15 +15,17 @@ class Test {
static var errors:Int = 0; static var errors:Int = 0;
static public function main():Void { static public function main():Void {
test( Spec.load("src/spec/url.json") ); test( "url.json", Spec.load("src/spec/url.json") );
test( Spec.load("src/spec/query.selectors.json") ); test( "t.json", Spec.load("src/spec/t.json") );
test( Spec.load("src/spec/query.root.json") ); test( "q.selectors.json", Spec.load("src/spec/query.selectors.json") );
test( Spec.load("src/spec/query.rules.json") ); test( "q.root.json", Spec.load("src/spec/query.root.json") );
test( "q.rules.json", Spec.load("src/spec/query.rules.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(spec:Array<Dynamic>):Void { static public function test( topic:String, spec:Array<Dynamic>):Void {
trace("\n[.] running "+topic);
var Query = xrfragment.Query; var Query = xrfragment.Query;
for( i in 0...spec.length ){ for( i in 0...spec.length ){
var q:Query = null; var q:Query = null;
@ -41,9 +43,9 @@ class Test {
if( item.expect.fn == "testBrowserOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.NAVIGATOR)).exists(item.expect.input); if( item.expect.fn == "testBrowserOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.NAVIGATOR)).exists(item.expect.input);
if( item.expect.fn == "testEmbedOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.METADATA)).exists(item.expect.input); if( item.expect.fn == "testEmbedOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.METADATA)).exists(item.expect.input);
if( item.expect.fn == "equal.string" ) valid = res.get(item.expect.input) && item.expect.out == res.get(item.expect.input).string; if( item.expect.fn == "equal.string" ) valid = res.get(item.expect.input) && item.expect.out == res.get(item.expect.input).string;
if( item.expect.fn == "equal.x" ) valid = equalX(res,item);
if( item.expect.fn == "equal.xy" ) valid = equalXY(res,item); 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.xyz" ) valid = equalXYZ(res,item);
if( item.expect.fn == "equal.multi" ) valid = equalMulti(res, item);
if( item.expect.fn == "testQueryRoot" ) 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 ? "[ ] " : "[ ] "; var ok:String = valid ? "[ ] " : "[ ] ";
trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? " (" + (item.label?item.label:item.expect.fn) +")" : "")); trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? " (" + (item.label?item.label:item.expect.fn) +")" : ""));
@ -51,6 +53,11 @@ class Test {
} }
} }
static public function equalX(res:haxe.DynamicAccess<Dynamic>, item:Dynamic):Bool {
if( !item.expect.out && !res.get(item.expect.input) ) return true;
else return res.get(item.expect.input) && item.expect.out == (Std.string(res.get(item.expect.input).x) );
}
static public function equalXY(res:haxe.DynamicAccess<Dynamic>, item:Dynamic):Bool { static public function equalXY(res:haxe.DynamicAccess<Dynamic>, item:Dynamic):Bool {
if( !item.expect.out && !res.get(item.expect.input) ) return true; if( !item.expect.out && !res.get(item.expect.input) ) return true;
else return res.get(item.expect.input) && item.expect.out == (Std.string(res.get(item.expect.input).x) +","+ Std.string(res.get(item.expect.input).y) ); else return res.get(item.expect.input) && item.expect.out == (Std.string(res.get(item.expect.input).x) +","+ Std.string(res.get(item.expect.input).y) );
@ -61,17 +68,6 @@ class Test {
else return res.get(item.expect.input) && item.expect.out == (Std.string(res.get(item.expect.input).x) +","+ Std.string(res.get(item.expect.input).y)+","+ Std.string(res.get(item.expect.input).z)); else return res.get(item.expect.input) && item.expect.out == (Std.string(res.get(item.expect.input).x) +","+ Std.string(res.get(item.expect.input).y)+","+ Std.string(res.get(item.expect.input).z));
} }
static public function equalMulti(res:haxe.DynamicAccess<Dynamic>, item:Dynamic):Bool {
var target:Dynamic = res.get(item.expect.input);
var str:String = "";
if( !target ) return false;
for( i in 0...target.args.length ){
str = str + "|" + target.args[i].string;
}
str = str.substr(1);
return item.expect.out ? str == item.expect.out : false;
}
static public function testUrl():Void { static public function testUrl():Void {
var Uri = xrfragment.URI; var Uri = xrfragment.URI;
var url:String = "http://foo.com?foo=1#bar=flop&a=1,2&b=c|d|1,2,3"; var url:String = "http://foo.com?foo=1#bar=flop&a=1,2&b=c|d|1,2,3";

10
src/spec/t.json Normal file
View file

@ -0,0 +1,10 @@
[
{"fn":"url","data":"http://foo.com?foo=1#t=1", "expect":{ "fn":"equal.x", "input":"t","out":"1"},"label":"a equal.x"},
{"fn":"url","data":"http://foo.com?foo=1#t=-1", "expect":{ "fn":"equal.x", "input":"t","out":"-1"},"label":"a equal.x"},
{"fn":"url","data":"http://foo.com?foo=1#t=-1.02", "expect":{ "fn":"equal.x", "input":"t","out":"-1.02"},"label":"a equal.x"},
{"fn":"url","data":"http://foo.com?foo=1#t=1,2,3", "expect":{ "fn":"equal.xy", "input":"t","out":"1,2"},"label":"a equal.xy"},
{"fn":"url","data":"http://foo.com?foo=1#t=1,2,3", "expect":{ "fn":"equal.xyz", "input":"t","out":"1,2,3"},"label":"a equal.xyz"},
{"fn":"url","data":"http://foo.com?foo=1#t=1,-2,3", "expect":{ "fn":"equal.xyz", "input":"t","out":"1,-2,3"},"label":"a equal.xyz"},
{"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)"}
]

View file

@ -1,17 +1,9 @@
[ [
{"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2", "expect":{ "fn":"equal.xyz", "input":"pos","out":false},"label":"equal.xyz: should trigger incompatible type)"}, {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2", "expect":{ "fn":"equal.xyz", "input":"pos","out":false},"label":"equal.xyz: should trigger incompatible type)"},
{"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2,3", "expect":{ "fn":"equal.xyz", "input":"pos","out":"1.2,2.2,3"},"label":"equal.xyz"}, {"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2,3", "expect":{ "fn":"equal.xyz", "input":"pos","out":"1.2,2.2,3"},"label":"equal.xyz"},
{"fn":"url","data":"http://foo.com?foo=1#t=1,100", "expect":{ "fn":"equal.xy", "input":"t","out":"1,100"},"label":"a equal.xy"},
{"fn":"url","data":"http://foo.com?foo=1#prio=foo", "expect":{ "fn":"testParsed", "input":"prio","out":false},"label":"should trigger incompatible type"},
{"fn":"url","data":"http://foo.com?foo=1#pos=c|d|1,2,3", "expect":{ "fn":"equal.multi", "input":"pos","out":"c|d|1,2,3"},"label":"b equal.multi"},
{"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#q=-bar", "expect":{ "fn":"testBrowserOverride", "input":"q","out":false},"label":"browser URI cannot override q (defined in asset)"}, {"fn":"url","data":"http://foo.com?foo=1#q=-bar", "expect":{ "fn":"testBrowserOverride", "input":"q","out":false},"label":"browser URI cannot override q (defined in asset)"},
{"fn":"url","data":"http://foo.com?foo=1#scale=2,2,2", "expect":{ "fn":"testBrowserOverride", "input":"scale","out":false},"label":"scale does not have NAVIGATOR set"},
{"fn":"url","data":"http://foo.com?foo=1#scale=2,2,2", "expect":{ "fn":"testEmbedOverride", "input":"scale","out":true},"label":"embedded (src) URI can override scale"},
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed"}, {"fn":"url","data":"http://foo.com?foo=1#mypredefinedview", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed"},
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"another","out":true},"label":"test predefined view executed (multiple)"}, {"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"another","out":true},"label":"test predefined view executed (multiple)"},
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed (multiple)"}, {"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed (multiple)"},
{"fn":"url","data":"#cube.position.x=music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test data assign"},
{"fn":"url","data":"#cube.position.x=@music.position.x", "expect":{ "fn":"testPropertyAssign", "input":"cube.position.x","out":true},"label":"test one-way data bind"},
{"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test custom property"} {"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test custom property"}
] ]

View file

@ -24,16 +24,16 @@ class Parser {
Frag.set("tag", XRF.ASSET | XRF.T_STRING ); Frag.set("tag", XRF.ASSET | XRF.T_STRING );
// category: query selector / object manipulation // category: query selector / object manipulation
Frag.set("pos", XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR ); Frag.set("pos", XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
Frag.set("q", XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA ); Frag.set("q", XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA );
Frag.set("scale", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.METADATA ); Frag.set("scale", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
Frag.set("rot", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR ); Frag.set("rot", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR );
Frag.set("mov", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.METADATA ); Frag.set("mov", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
Frag.set("show", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_INT | XRF.METADATA ); Frag.set("show", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_INT | XRF.METADATA );
Frag.set("env", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA ); Frag.set("env", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA );
// category: animation // category: animation
Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR2 | XRF.NAVIGATOR | XRF.METADATA); Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_VECTOR3 | XRF.NAVIGATOR | XRF.METADATA);
Frag.set("gravity", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA ); Frag.set("gravity", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
Frag.set("physics", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA ); Frag.set("physics", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );

View file

@ -37,8 +37,8 @@ class XRF {
// 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})$/`
public static var isInt:EReg = ~/^[0-9]+$/; // 1. integers are detected using regex `/^[0-9]+$/` public static var isInt:EReg = ~/^[-0-9]+$/; // 1. integers are detected using regex `/^[0-9]+$/`
public static var isFloat:EReg = ~/^[0-9]+\.[0-9]+$/; // 1. floats are detected using regex `/^[0-9]+\.[0-9]+$/` public static var isFloat:EReg = ~/^[-0-9]+\.[0-9]+$/; // 1. floats are detected using regex `/^[0-9]+\.[0-9]+$/`
public static var isVector:EReg = ~/([,]+|\w)/; // 1. vectors are detected using regex `/[,]/` (but can also be an string referring to an entity-ID in the asset) public static var isVector:EReg = ~/([,]+|\w)/; // 1. vectors are detected using regex `/[,]/` (but can also be an string referring to an entity-ID in the asset)
public static var isUrl:EReg = ~/(:\/\/)?\..*/; // 1. url/file */` public static var isUrl:EReg = ~/(:\/\/)?\..*/; // 1. url/file */`
public static var isUrlOrPretypedView:EReg = ~/(^#|:\/\/)?\..*/; // 1. url/file */` public static var isUrlOrPretypedView:EReg = ~/(^#|:\/\/)?\..*/; // 1. url/file */`
@ -55,7 +55,6 @@ class XRF {
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 args:Array<XRF>; // |array | mixed| \|-separated | #pos=0,0,0\|90,0,0 |
public var query:Query; public var query:Query;
public var noXRF:Bool; public var noXRF:Bool;
// //
@ -65,6 +64,7 @@ class XRF {
} }
public function is(flag:Int):Bool { public function is(flag:Int):Bool {
if( !Std.isOfType(flags,Int) ) flags = 0;
return (flags & flag) != 0; return (flags & flag) != 0;
} }
@ -78,25 +78,12 @@ class XRF {
public function validate(value:String) : Bool{ public function validate(value:String) : Bool{
guessType(this, value); // 1. extract the type guessType(this, value); // 1. extract the type
// process multiple values
if( value.split("|").length > 1 ){ // 1. use `|` on stringvalues, to split multiple values
this.args = new Array<XRF>();
var args:Array<String> = value.split("|");
for( i in 0...args.length){
var x:XRF = new XRF(fragment,flags);
guessType(x, args[i]); // 1. for each multiple value, guess the type
this.args.push( x );
}
}
// special case: query has its own DSL (*TODO* allow fragments to have custom validators) // special case: query has its own DSL (*TODO* allow fragments to have custom validators)
if( fragment == "q" ) query = (new Query(value)).get(); if( fragment == "q" ) query = (new Query(value)).get();
// validate // validate
var ok:Bool = true; var ok:Bool = true;
if( !Std.isOfType(args,Array) ){ if( !is(T_FLOAT) && is(T_VECTOR2) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float)) ) ok = false;
if( is(T_VECTOR3) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float) && Std.isOfType(z,Float)) ) ok = false; if( !is(T_VECTOR2) && is(T_VECTOR3) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float) && Std.isOfType(z,Float)) ) ok = false;
if( is(T_VECTOR2) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float)) ) ok = false;
if( is(T_INT) && !Std.isOfType(int,Int) ) ok = false;
}
return ok; return ok;
} }
@ -104,16 +91,22 @@ class XRF {
public function guessType(v:XRF, str:String):Void { public function guessType(v:XRF, str:String):Void {
v.string = str; v.string = str;
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 xyz:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values var xyzw:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
if( xyz.length > 0 ) v.x = Std.parseFloat(xyz[0]); // 1. anything else will be treated as string-value if( xyzw.length > 0 ) v.x = Std.parseFloat(xyzw[0]); // 1. anything else will be treated as string-value
if( xyz.length > 1 ) v.y = Std.parseFloat(xyz[1]); // 1. incompatible value-types will be dropped / not used if( xyzw.length > 1 ) v.y = Std.parseFloat(xyzw[1]); // 1. incompatible value-types will be dropped / not used
if( xyz.length > 2 ) v.z = Std.parseFloat(xyz[2]); // if( xyzw.length > 2 ) v.z = Std.parseFloat(xyzw[2]); //
if( xyz.length > 3 ) v.w = Std.parseFloat(xyz[3]); // if( xyzw.length > 3 ) v.w = Std.parseFloat(xyzw[3]); //
} // > the xrfragment specification should stay simple enough } // > the xrfragment specification should stay simple enough
// > for anyone to write a parser using either regexes or grammar/lexers // > for anyone to write a parser using either regexes or grammar/lexers
if( isColor.match(str) ) v.color = str; // > therefore expressions/comprehensions are not supported (max wildcard/comparison operators for queries e.g.) if( isColor.match(str) ) v.color = str; // > therefore expressions/comprehensions are not supported (max wildcard/comparison operators for queries e.g.)
if( isFloat.match(str) ) v.float = Std.parseFloat(str); if( isFloat.match(str) ){
if( isInt.match(str) ) v.int = Std.parseInt(str); v.x = Std.parseFloat(str);
v.float = v.x;
}
if( isInt.match(str) ){
v.int = Std.parseInt(str);
v.x = cast(v.int);
}
} }
} }