update documentation
This commit is contained in:
parent
d91d8b5c16
commit
e368b74e4f
File diff suppressed because one or more lines are too long
|
@ -4,6 +4,7 @@
|
|||
# nativeBuildInputs is usually what you want -- tools you need to run
|
||||
nativeBuildInputs = with pkgs.buildPackages; [
|
||||
|
||||
nodejs
|
||||
haxe
|
||||
mmark
|
||||
xml2rfc
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
xrf.frag = {}
|
||||
xrf.model = {}
|
||||
xrf.mixers = []
|
||||
|
||||
xrf.init = ((init) => function(opts){
|
||||
let scene = new opts.THREE.Group()
|
||||
|
@ -8,23 +9,26 @@ xrf.init = ((init) => function(opts){
|
|||
init(opts)
|
||||
if( opts.loaders ) Object.values(opts.loaders).map( xrf.patchLoader )
|
||||
|
||||
xrf.patchRenderer(opts.renderer)
|
||||
xrf.patchRenderer(opts)
|
||||
xrf.navigator.init()
|
||||
// return xrfragment lib as 'xrf' query functor (like jquery)
|
||||
for ( let i in xrf ) xrf.query[i] = xrf[i]
|
||||
return xrf.query
|
||||
})(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.enabled = true;
|
||||
xrf.clock = new xrf.THREE.Clock()
|
||||
renderer.render = ((render) => function(scene,camera){
|
||||
// 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)
|
||||
xrf.emit('render',{scene,camera,time}) // allow fragments to do something at renderframe
|
||||
render(scene,camera)
|
||||
})(renderer.render.bind(renderer))
|
||||
|
||||
}
|
||||
|
||||
xrf.patchLoader = function(loader){
|
||||
|
@ -78,6 +82,12 @@ xrf.reset = () => {
|
|||
xrf.add( xrf.interactive )
|
||||
xrf.layers = 0
|
||||
xrf.emit('reset',{})
|
||||
// remove mixers
|
||||
xrf.mixers.map( (m) => {
|
||||
m.stop()
|
||||
delete m
|
||||
})
|
||||
xrf.mixers = []
|
||||
}
|
||||
|
||||
xrf.parseUrl = (url) => {
|
||||
|
|
|
@ -34,7 +34,7 @@ xrf.frag.href = function(v, opts){
|
|||
|
||||
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) => {
|
||||
|
||||
|
|
|
@ -23,28 +23,34 @@ let loadAudio = (mimetype) => function(url,opts){
|
|||
let sound = isPositionalAudio ? new THREE.PositionalAudio(listener) : new THREE.Audio(listener)
|
||||
audioLoader.load( url.replace(/#.*/,''), function( buffer ) {
|
||||
sound.setBuffer( buffer );
|
||||
sound.setLoop(true);
|
||||
sound.setVolume(0.5);
|
||||
sound.setLoop(false);
|
||||
sound.setVolume(1.0);
|
||||
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
|
||||
t = hardcodedLoop ? { ...frag.t, x: t.x} : t // override with hardcoded metadata except playstate (x)
|
||||
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
|
||||
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
|
||||
sound.setLoop( t.z > 0 )
|
||||
if( t.z ) sound.setLoop( true )
|
||||
// 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 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.x != 0 ){
|
||||
sound.offset = loopStart > 0 ? loopStart : timeStart
|
||||
sound.play()
|
||||
if( t.y != undefined ){
|
||||
console.dir({loopStart,t})
|
||||
sound.setLoopStart( loopStart )
|
||||
sound.offset = loopStart
|
||||
}
|
||||
|
||||
sound.play()
|
||||
}
|
||||
}
|
||||
mesh.add(sound)
|
||||
|
@ -57,11 +63,11 @@ let audioMimeTypes = [
|
|||
'audio/mpeg',
|
||||
'audio/weba',
|
||||
'audio/aac',
|
||||
'audio/ogg',
|
||||
'application/ogg'
|
||||
]
|
||||
audioMimeTypes.map( (mimetype) => xrf.frag.src.type[ mimetype ] = loadAudio(mimetype) )
|
||||
|
||||
// listen to t XR fragment changes
|
||||
xrf.addEventListener('t', (opts) => {
|
||||
let t = opts.frag.t
|
||||
xrf.audio.map( (a) => a.playXRF(t) )
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
xrf.frag.t = function(v, 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')
|
||||
|
||||
model.mixer.t = v
|
||||
|
@ -10,20 +10,61 @@ xrf.frag.t = function(v, opts){
|
|||
// update speed
|
||||
mixer.timeScale = mixer.loop.speed
|
||||
|
||||
let updateTime = (time) => {
|
||||
mixer.setTime(time)
|
||||
mixer.time = Math.abs(mixer.time)
|
||||
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
|
||||
if( v.y != undefined || v.z != undefined ) xrf.mixers[0].updateLoop( mixer.loop.timeStart )
|
||||
if( v.y > 0 && v.z > 0 ){
|
||||
xrf.model.animations.map( (anim) => {
|
||||
if( !anim.action ) return
|
||||
anim.action.setLoop( v.z == 0 ? THREE.LoopOnce : THREE.LoopRepeat, v.z == 0 ? 0 : 99999999)
|
||||
})
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
anim.action.setLoop( v.z == 0 ? THREE.LoopOnce : THREE.LoopRepeat, v.z == 0 ? 0 : 99999999)
|
||||
}
|
||||
|
||||
xrf.frag.t.default = {x:1, y:0, z:0}
|
||||
|
||||
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
|
||||
|
@ -33,25 +74,29 @@ xrf.frag.t = function(v, opts){
|
|||
mixer.time = Math.abs(mixer.time)
|
||||
if( time == 0 ) return update.call(this,time)
|
||||
|
||||
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
|
||||
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion
|
||||
}
|
||||
return update.call( mixer, time )
|
||||
// loop jump
|
||||
//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( this, time )
|
||||
}
|
||||
mixer.update.patched = true
|
||||
}
|
||||
}
|
||||
|
||||
xrf.frag.t.default = {x:1, y:0, z:0}
|
||||
// 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.calculateLoop = (t,obj,fps) => {
|
||||
obj.speed = t.x
|
||||
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)
|
||||
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.mixers.push(mixer)
|
||||
console.log("mixer fps="+mixer.loop.fps+" frames:"+mixer.frames+" duration:"+mixer.duration)
|
||||
}
|
||||
|
||||
if( document.location.hash.match(/t=/) ){
|
||||
|
@ -68,36 +113,17 @@ if( document.location.hash.match(/t=/) ){
|
|||
xrf.addEventListener('parseModel', (opts) => {
|
||||
let {model,file,url} = opts
|
||||
// add animations
|
||||
model.clock = new xrf.THREE.Clock();
|
||||
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.frag.t.setupMixer(opts)
|
||||
})
|
||||
|
||||
xrf.addEventListener('render', (opts) => {
|
||||
let model = xrf.model
|
||||
let {time} = opts
|
||||
if( !model || !model.clock ) return
|
||||
model.mixer.update( time )
|
||||
if( !model ) return
|
||||
if( xrf.mixers.length ) xrf.mixers.map( (m) => m.update( time ) )
|
||||
|
||||
// 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()
|
||||
// cam.fov = model.cameras[0].fov (why is blender not exporting radians?)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
xrf.addEventListener('pos', (opts) => {
|
||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||
if( frag.pos && frag.q ){
|
||||
// apply roundrobin (if any)
|
||||
if( v.args ) v = v.args[ xrf.roundrobin(v,model) ]
|
||||
//if( frag.pos && frag.q ){
|
||||
// // apply roundrobin (if any)
|
||||
// if( v.args ) v = v.args[ xrf.roundrobin(v,model) ]
|
||||
|
||||
frag.q.getObjects().map( (o) => {
|
||||
// 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.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
|
||||
})
|
||||
}
|
||||
// frag.q.getObjects().map( (o) => {
|
||||
// // 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.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
|
||||
// })
|
||||
//}
|
||||
})
|
||||
|
|
30
src/Test.hx
30
src/Test.hx
|
@ -15,15 +15,17 @@ class Test {
|
|||
static var errors:Int = 0;
|
||||
|
||||
static public function main():Void {
|
||||
test( Spec.load("src/spec/url.json") );
|
||||
test( Spec.load("src/spec/query.selectors.json") );
|
||||
test( Spec.load("src/spec/query.root.json") );
|
||||
test( Spec.load("src/spec/query.rules.json") );
|
||||
test( "url.json", Spec.load("src/spec/url.json") );
|
||||
test( "t.json", Spec.load("src/spec/t.json") );
|
||||
test( "q.selectors.json", Spec.load("src/spec/query.selectors.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") );
|
||||
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;
|
||||
for( i in 0...spec.length ){
|
||||
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 == "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.x" ) valid = equalX(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.multi" ) valid = equalMulti(res, item);
|
||||
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) +")" : ""));
|
||||
|
@ -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 {
|
||||
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) );
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
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 {
|
||||
var Uri = xrfragment.URI;
|
||||
var url:String = "http://foo.com?foo=1#bar=flop&a=1,2&b=c|d|1,2,3";
|
||||
|
|
|
@ -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)"}
|
||||
]
|
|
@ -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,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#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&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":"#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"}
|
||||
]
|
||||
|
|
|
@ -24,16 +24,16 @@ class Parser {
|
|||
Frag.set("tag", XRF.ASSET | XRF.T_STRING );
|
||||
|
||||
// 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("scale", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | 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("mov", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
Frag.set("show", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.ROUNDROBIN | XRF.T_INT | 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.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR );
|
||||
Frag.set("mov", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | 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 );
|
||||
|
||||
// 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("physics", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
|
||||
|
@ -50,7 +50,7 @@ class Parser {
|
|||
Frag.set("description", XRF.ASSET | XRF.T_STRING );
|
||||
|
||||
// category: multiparty
|
||||
Frag.set("session", XRF.ASSET | XRF.T_URL | XRF.PV_OVERRIDE | XRF.NAVIGATOR | XRF.METADATA | XRF.PROMPT );
|
||||
Frag.set("session", XRF.ASSET | XRF.T_URL | XRF.PV_OVERRIDE | XRF.NAVIGATOR | XRF.METADATA | XRF.PROMPT );
|
||||
|
||||
/**
|
||||
* # Spec
|
||||
|
|
|
@ -37,8 +37,8 @@ class XRF {
|
|||
|
||||
// regexes
|
||||
public static var isColor:EReg = ~/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; // 1. hex colors are detected using regex `/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/`
|
||||
public static var isInt:EReg = ~/^[0-9]+$/; // 1. integers are detected using regex `/^[0-9]+$/`
|
||||
public static var isFloat:EReg = ~/^[0-9]+\.[0-9]+$/; // 1. floats are detected using regex `/^[0-9]+\.[0-9]+$/`
|
||||
public static var isInt:EReg = ~/^[-0-9]+$/; // 1. integers are detected using regex `/^[0-9]+$/`
|
||||
public static var isFloat:EReg = ~/^[-0-9]+\.[0-9]+$/; // 1. floats are detected using regex `/^[0-9]+\.[0-9]+$/`
|
||||
public static var isVector:EReg = ~/([,]+|\w)/; // 1. vectors are detected using regex `/[,]/` (but can also be an string referring to an entity-ID in the asset)
|
||||
public static var isUrl:EReg = ~/(:\/\/)?\..*/; // 1. url/file */`
|
||||
public static var isUrlOrPretypedView:EReg = ~/(^#|:\/\/)?\..*/; // 1. url/file */`
|
||||
|
@ -55,7 +55,6 @@ class XRF {
|
|||
public var string:String; // |string| | | #q=-sun |
|
||||
public var int:Int; // |int | | [-]x[xxxxx] | #price:>=100 |
|
||||
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 noXRF:Bool;
|
||||
//
|
||||
|
@ -65,6 +64,7 @@ class XRF {
|
|||
}
|
||||
|
||||
public function is(flag:Int):Bool {
|
||||
if( !Std.isOfType(flags,Int) ) flags = 0;
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
|
@ -78,25 +78,12 @@ class XRF {
|
|||
|
||||
public function validate(value:String) : Bool{
|
||||
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)
|
||||
if( fragment == "q" ) query = (new Query(value)).get();
|
||||
// validate
|
||||
var ok:Bool = true;
|
||||
if( !Std.isOfType(args,Array) ){
|
||||
if( 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;
|
||||
}
|
||||
if( !is(T_FLOAT) && is(T_VECTOR2) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float)) ) ok = false;
|
||||
if( !is(T_VECTOR2) && is(T_VECTOR3) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float) && Std.isOfType(z,Float)) ) ok = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
@ -104,16 +91,22 @@ class XRF {
|
|||
public function guessType(v:XRF, str:String):Void {
|
||||
v.string = str;
|
||||
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
|
||||
if( xyz.length > 0 ) v.x = Std.parseFloat(xyz[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( xyz.length > 2 ) v.z = Std.parseFloat(xyz[2]); //
|
||||
if( xyz.length > 3 ) v.w = Std.parseFloat(xyz[3]); //
|
||||
var xyzw:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
|
||||
if( xyzw.length > 0 ) v.x = Std.parseFloat(xyzw[0]); // 1. anything else will be treated as string-value
|
||||
if( xyzw.length > 1 ) v.y = Std.parseFloat(xyzw[1]); // 1. incompatible value-types will be dropped / not used
|
||||
if( xyzw.length > 2 ) v.z = Std.parseFloat(xyzw[2]); //
|
||||
if( xyzw.length > 3 ) v.w = Std.parseFloat(xyzw[3]); //
|
||||
} // > the xrfragment specification should stay simple enough
|
||||
// > 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( isFloat.match(str) ) v.float = Std.parseFloat(str);
|
||||
if( isInt.match(str) ) v.int = Std.parseInt(str);
|
||||
if( isFloat.match(str) ){
|
||||
v.x = Std.parseFloat(str);
|
||||
v.float = v.x;
|
||||
}
|
||||
if( isInt.match(str) ){
|
||||
v.int = Std.parseInt(str);
|
||||
v.x = cast(v.int);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue