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 = with pkgs.buildPackages; [
nodejs
haxe
mmark
xml2rfc

View File

@ -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) => {

View File

@ -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) => {

View File

@ -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) )

View File

@ -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?)

View File

@ -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
// })
//}
})

View File

@ -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";

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,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"}
]

View File

@ -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

View File

@ -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);
}
}
}