From 8be5e485cedd1e2baf3a582c511fb39d58658f98 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 2 Nov 2023 16:49:18 +0100 Subject: [PATCH] src's and global scene have separate animation mixers now yay! \o/ --- doc/RFC_XR_Fragments.md | 11 +- example/aframe/sandbox/index.html | 4 +- example/assets/AnimatedCube.bin | Bin 0 -> 1860 bytes example/assets/AnimatedCube.gltf | 262 ++++++++++++++++++++++ example/assets/AnimatedCube_NLA.gltf | 160 +++++++++++++ example/assets/css/style.css | 1 + example/assets/index.glb | Bin 2596880 -> 2666972 bytes example/assets/index.gltf | 322 +++++++++++---------------- make | 2 +- src/3rd/js/three/xrf/src.js | 21 +- src/3rd/js/three/xrf/src/audio.js | 1 + src/3rd/js/three/xrf/t.js | 129 +++++------ src/xrfragment/Parser.hx | 3 +- 13 files changed, 636 insertions(+), 280 deletions(-) create mode 100644 example/assets/AnimatedCube.bin create mode 100644 example/assets/AnimatedCube.gltf create mode 100644 example/assets/AnimatedCube_NLA.gltf diff --git a/doc/RFC_XR_Fragments.md b/doc/RFC_XR_Fragments.md index 8a5f638..f6619d6 100644 --- a/doc/RFC_XR_Fragments.md +++ b/doc/RFC_XR_Fragments.md @@ -218,13 +218,13 @@ sub-delims = "," / "=" | vector3 | x,y,z | 2,3.0,4 | 3-dimensional vector | | timevector | speed | 1 | 1D timeline: play | | | | 0 | 1D timeline: stop | -| | x,speed | 1,1 | 1D timeline: play at offset `1` at (normal) speed `1` | -| | | 0,0 | 1D timeline: stop | +| | x,speed | 1,2 | 1D timeline: play at offset `1` at (normal) speed `2` | +| | | 0,0 | 1D timeline: stop (stopoffset-startoffset == 0) | | | | 0,1 | 1D timeline: unpause with (normal) speed `1` | -| | | [1,100],1 | 1D timeline: play (loop) between offset `1` and `100` at normal speed (`1`) | +| | | 1..100,1 | 1D timeline: play (loop) between offset `1` and `100` at normal speed (`1`) | | | x,y,xspeed,yspeed | 0,0.5,0,0 | 2D timeline: stop uv-coordinate at `0,0.5` | | | | 0,0.5,0.2,0 | 2D timeline: play uv-coordinate at offset `0,0.5` and scroll `x` (=u) `0.2` within each second | -| | | 0,[0,0.5],0.2,0 | 2D timeline: play uv-coordinate between offset `0,0` and `0,0.5` (loop) and scroll `x` (=u) `0.2` within each second | +| | | 0,0..0.5,0.2,0 | 2D timeline: play uv-coordinate between offset `0,0` and `0,0.5` (loop) and scroll `x` (=u) `0.2` within each second | | | x,y,z,xspeed,yspeed,zspeed | 0,0.5,1,0.2,0,2 | XD timeline: play uv-coordinate at `0,0.5` and scroll `x` (=u) `0.2` within each second and pass `1` and `2` as custom data to shader uniforms `za` and `zb` | > NOTE: XR Fragments are optional but also file- and protocol-agnostic, which means that programmatic 3D scene(nodes) can also use the mechanism/metadata. @@ -240,7 +240,8 @@ These are automatic fragment-to-metadata mappings, which only trigger if the 3D | `#` | string | `#cam01` | set camera as active camera | | `#=` | string=string | `#car=metallic`| set material of car to material with name `metallic` | | | string=string | `#product=metallic`| set material of objects tagged with `product` to material with name `metallic` | -| `#=` | string=timevector | `#sky=0,0.5,0.1,0`| set uv-position to `0,0.5` (and autoscroll x with max `0.1` every second)| +| `#=` | string=[media frag](https://www.w3.org/TR/media-frags/#valid-uri) | `#foo=0,1`| play media `src` using [media fragment URI](https://www.w3.org/TR/media-frags/#valid-uri) | +| `#=` | string=timevector | `#sky=0,0.5,0.1,0`| sets 1D/2D/3D time(line) vectors (uv-position e.g.) to `0,0.5` (and autoscroll x with max `0.1` every second)| | | | `#music=1,2`| play media of object (`src: podcast.mp3` e.g.) from beginning (`1`) at double speed (`2`) | # Spatial Referencing 3D diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index 6c25f6f..81a8eee 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -31,8 +31,8 @@ - - + + diff --git a/example/assets/AnimatedCube.bin b/example/assets/AnimatedCube.bin new file mode 100644 index 0000000000000000000000000000000000000000..72f7d2d52ad79f8cb5a71f1640e51bd91ddd4a08 GIT binary patch literal 1860 zcmcIiNm2tb3~bhfJ?uMSx#vc4;9!1+Pke|YHN}&70;pxn<1wC04xp+;Qg=(1lmImE zLzEMXZdSj8IFF~S%d*u)mLv4dUgVS;@e;1EYR#tBYw zhI3rt5?8p!4W_uo9q#cE-(Ww!rJiwb{`LJzof`+e#)6sNWpTo+Z&6FW)Hu`fjf^*~ zdE?5Qcx{GKXE{Ay)5X7@6$xNr5u&Fq$W jJ-NI#=d3p8wU=*eI-KBURa|)MnRMJOei9aE4n2MVd$l|d literal 0 HcmV?d00001 diff --git a/example/assets/AnimatedCube.gltf b/example/assets/AnimatedCube.gltf new file mode 100644 index 0000000..ff117b0 --- /dev/null +++ b/example/assets/AnimatedCube.gltf @@ -0,0 +1,262 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 3, + "max" : [ + 2.000000 + ], + "min" : [ + 0.000000 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 3, + "max" : [ + 0.000000, + 1.000000, + 0.000000, + 1.000000 + ], + "min" : [ + 0.000000, + -8.742278e-008, + 0.000000, + -1.000000 + ], + "type" : "VEC4" + }, + { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 36, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000001 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 4, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + -0.000000, + -0.000000, + 1.000000 + ], + "min" : [ + 0.000000, + -0.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC4" + }, + { + "bufferView" : 6, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000 + ], + "type" : "VEC2" + } + ], + "animations" : [ + { + "channels" : [ + { + "sampler" : 0, + "target" : { + "node" : 0, + "path" : "rotation" + } + } + ], + "name" : "animation_AnimatedCube", + "samplers" : [ + { + "input" : 0, + "interpolation" : "LINEAR", + "output" : 1 + } + ] + } + ], + "asset" : { + "generator" : "VKTS glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 12, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 48, + "byteOffset" : 12 + }, + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 60, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 132, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 564, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 996, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 1572, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 1860, + "uri" : "AnimatedCube.bin" + } + ], + "images" : [ + { + "uri" : "AnimatedCube_BaseColor.png" + }, + { + "uri" : "AnimatedCube_MetallicRoughness.png" + } + ], + "materials" : [ + { + "name" : "AnimatedCube", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 0 + }, + "metallicRoughnessTexture" : { + "index" : 1 + } + } + } + ], + "meshes" : [ + { + "name" : "AnimatedCube", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 4, + "POSITION" : 3, + "TANGENT" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 2, + "material" : 0, + "mode" : 4 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "AnimatedCube", + "rotation" : [ + 0.000000, + -1.000000, + 0.000000, + 0.000000 + ] + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "nodes" : [ + 0 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + } + ] +} \ No newline at end of file diff --git a/example/assets/AnimatedCube_NLA.gltf b/example/assets/AnimatedCube_NLA.gltf new file mode 100644 index 0000000..dbedcd5 --- /dev/null +++ b/example/assets/AnimatedCube_NLA.gltf @@ -0,0 +1,160 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.5.30", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"AnimatedCube" + } + ], + "animations":[ + { + "channels":[ + { + "sampler":0, + "target":{ + "node":0, + "path":"rotation" + } + } + ], + "name":"walk", + "samplers":[ + { + "input":4, + "interpolation":"LINEAR", + "output":5 + } + ] + } + ], + "materials":[ + { + "name":"AnimatedCube", + "pbrMetallicRoughness":{} + } + ], + "meshes":[ + { + "name":"AnimatedCube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "TEXCOORD_0":1, + "NORMAL":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":36, + "max":[ + 1, + 1, + 1.0000009536743164 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":36, + "type":"VEC2" + }, + { + "bufferView":2, + "componentType":5126, + "count":36, + "type":"VEC3" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":3, + "max":[ + 2 + ], + "min":[ + 0 + ], + "type":"SCALAR" + }, + { + "bufferView":5, + "componentType":5126, + "count":3, + "type":"VEC4" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":432, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":432, + "target":34962 + }, + { + "buffer":0, + "byteLength":432, + "byteOffset":720, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":1152, + "target":34963 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":1224 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":1236 + } + ], + "buffers":[ + { + "byteLength":1284, + "uri":"data:application/octet-stream;base64,AACAPwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAPwAAgL8AAIC/7/9/PwAAgD8IAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/7/9/PwAAgD8IAIA/AACAPwAAgL8AAIA/7/9/PwAAgD8IAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAPwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AACAvwAAgD8AAAAAAACAPwAAAAAAAAAAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAACAvwAAgD8AAIC/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAAAAAAACAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAIAAAIC/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAAAAAAAAAgD8AAABAAAAAAAAAAAAAAACAAACAPwAAAIAAAIC/AAAAAC69OzMAAAAALr27MwAAAIAAAIA/" + } + ] +} diff --git a/example/assets/css/style.css b/example/assets/css/style.css index c0b37ff..65950bf 100644 --- a/example/assets/css/style.css +++ b/example/assets/css/style.css @@ -130,6 +130,7 @@ a#model{ } html.a-fullscreen a.btn-foot { + line-height:36px; margin-right:10px; } diff --git a/example/assets/index.glb b/example/assets/index.glb index 484acec378f40da51fb5a236f3ef17b76d141c1a..131e26fc96f936c11832acba52af1071ad3dc0fa 100644 GIT binary patch delta 80457 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 delta 10837 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 ep$F@o { + const addModel = (model,url,frag) => { + let scene = model.scene src = xrf.frag.src.filterScene(scene,{...opts,frag}) xrf.frag.src.scale( src, opts, url ) xrf.frag.src.eval( src, opts, url ) + // allow 't'-fragment to setup separate animmixer + xrf.emit('parseModel', {...opts, scene:src, model}) enableSourcePortation(src) mesh.add(src) mesh.traverse( (n) => n.isSRC = n.isXRF = true ) @@ -49,20 +51,23 @@ xrf.frag.src = function(v, opts){ return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts) }) .then( (model) => { - if( model && model.scene ) addScene(model.scene, url, frag ) + if( model && model.scene ) addModel(model, url, frag ) }) .finally( () => { }) .catch( console.error ) } - if( url[0] == "#" ) addScene(scene,url,vfrag) // current file - else externalSRC(url,vfrag) // external file + if( url[0] == "#" ){ + let modelClone = {...model, scene: model.scene.clone()} + modelClone.scenes = [modelClone.scene] + modelClone.animations = modelClone.animations.map( (a) => a.clone() ) + addModel(modelClone,url,vfrag) // current file + }else externalSRC(url,vfrag) // external file } xrf.frag.src.eval = function(scene, opts, url){ let { mesh, model, camera, renderer, THREE, hashbus} = opts if( url ){ - console.log(mesh.name+" url="+url) //let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) //let frag = xrfragment.URI.parse(url) //// scale URI XR Fragments (queries) inside src-value @@ -123,13 +128,13 @@ xrf.frag.src.filterScene = (scene,opts) => { } hashbus.pub.fragment(i, Object.assign(opts,{frag, model,scene})) } - }else src = scene.clone(true) + } if( src.children.length == 1 ) obj.position.set(0,0,0); } // filtering of objects using query if( frag.q ){ - src = scene.clone(true); + src = scene xrf.frag.q.filter(src,frag) } src.traverse( (m) => { diff --git a/src/3rd/js/three/xrf/src/audio.js b/src/3rd/js/three/xrf/src/audio.js index 55a752b..68147c9 100644 --- a/src/3rd/js/three/xrf/src/audio.js +++ b/src/3rd/js/three/xrf/src/audio.js @@ -47,6 +47,7 @@ let loadAudio = (mimetype) => function(url,opts){ // setting loop if( t.z ) sound.setLoop( true ) // apply embedded audio/video samplerate/fps or global mixer fps + return console.warn("TODO: convert samplerate frames to seconds!") 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 : (t.y == undefined ? xrf.model.mixer.time : t.y) diff --git a/src/3rd/js/three/xrf/t.js b/src/3rd/js/three/xrf/t.js index 9ef4e0b..11ca61a 100644 --- a/src/3rd/js/three/xrf/t.js +++ b/src/3rd/js/three/xrf/t.js @@ -3,53 +3,45 @@ xrf.frag.t = function(v, opts){ if( !model.mixer ) return if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene') - model.mixer.t = v - let mixer = model.mixer - xrf.frag.t.calculateLoop( v, mixer.loop, mixer.loop.fps ) - - // update speed - mixer.timeScale = mixer.loop.speed + xrf.mixers.map ( (mixer) => { + mixer.t = v + + // update speed + mixer.timeScale = mixer.loop.speed = v.x + mixer.loop.speedAbs = Math.abs(v.x) - 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 != 0 ) + if( v.y != undefined || v.z != undefined ) mixer.updateLoop( v ) + // play animations + mixer.play( v.x != 0 ) + }) } -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.default = { + x:0, // (play from) offset (in seconds) + y:0 // optional: (stop at) offset (in seconds) } -xrf.frag.t.setupMixer = function(opts){ - let {model,file,url} = opts - let mixer = model.mixer = new xrf.THREE.AnimationMixer(model.scene) +// setup animation mixer for global scene & src scenes +xrf.addEventListener('parseModel', (opts) => { + let {model} = opts + let mixer = model.mixer = new xrf.THREE.AnimationMixer(model.scene) + mixer.model = model + mixer.loop = {} + mixer.i = xrf.mixers.length - mixer.initClips = () => { - if( mixer.clipsInited ) return // fire only once - model.animations.map( (anim) => { + model.animations.map( (anim) => { + anim.action = mixer.clipAction( anim, model.scene ) + }) + + mixer.checkZombies = (animations) => { + if( mixer.zombieCheck ) return // fire only once + animations.map( (anim) => { + // collect zombie animations and warn user let zombies = anim.tracks.map( (t) => { let name = t.name.replace(/\..*/,'') return !model.scene.getObjectByName(name) ? {anim:anim.name,obj:t.name} : undefined }) - if( !anim.action ){ - anim.action = mixer.clipAction( anim ) - anim.action.setLoop(THREE.LoopOnce) - } if( zombies.length > 0 ){ zombies .filter( (z) => z ) // filter out undefined @@ -57,31 +49,37 @@ xrf.frag.t.setupMixer = function(opts){ console.warn(`TIP: remove dots in objectnames in blender (which adds dots when duplicating)`) } }) - mixer.clipsInited = true + mixer.zombieCheck = true } - mixer.play = (play) => { - mixer.initClips() - mixer.isPlaying = play - model.animations.map( (anim) => { - if( mixer.isPlaying === false) anim.action.stop() - else{ - anim.action.play() - } - mixer.update(0) - }) - xrf.emit( play === false ? 'stop' : 'play',{play}) + mixer.play = (t) => { + mixer.isPlaying = t.x != 0 + mixer.updateLoop(t) + xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying}) } 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 :] + mixer.updateLoop = (t) => { + mixer.loop.timeStart = t.y != undefined ? t.y : mixer.loop.timeStart + mixer.loop.timeStop = t.z != undefined ? t.z : mixer.loop.timeStop + mixer.model.animations.map( (anim) => { + if( mixer.loop.timeStart != undefined ){ + //if( anim.action ) delete anim.action + //anim.action = mixer.clipAction( anim ) + anim.action.time = mixer.loop.timeStart + anim.action.setLoop( THREE.LoopOnce, ) + anim.action.timeScale = mixer.timeScale + anim.action.enabled = true + anim.action.play() + } + }) + mixer.setTime(mixer.loop.timeStart) + mixer.time = Math.abs( mixer.loop.timeStart ) + mixer.update(0) + mixer.checkZombies( model.animations) } // update loop when needed @@ -92,9 +90,9 @@ xrf.frag.t.setupMixer = function(opts){ if( time == 0 ) return update.call(this,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 - //} + if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop ){ + setTimeout( (time,anims) => mixer.updateLoop(time), 0, mixer.loop.timeStart ) // prevent recursion + } return update.call( this, time ) } mixer.update.patched = true @@ -102,19 +100,12 @@ xrf.frag.t.setupMixer = function(opts){ // 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 - }) + model.animations.map( (a) => mixer.duration = ( a.duration > mixer.duration ) ? a.duration : mixer.duration ) } - 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=/) ){ let url = document.location.href @@ -127,17 +118,11 @@ if( document.location.hash.match(/t=/) ){ window.addEventListener('touchstart', playAfterUserGesture ) } -xrf.addEventListener('parseModel', (opts) => { - let {model,file,url} = opts - // add animations - xrf.frag.t.setupMixer(opts) -}) - xrf.addEventListener('render', (opts) => { let model = xrf.model let {time} = opts if( !model ) return - if( xrf.mixers.length ) xrf.mixers.map( (m) => m.update( time ) ) + if( xrf.mixers.length ) xrf.mixers.map( (m) => m.isPlaying ? m.update( time ) : false ) // update camera if possible if( model.cameras && model.cameras.length && xrf.mixers.length ){ diff --git a/src/xrfragment/Parser.hx b/src/xrfragment/Parser.hx index 06e084b..1271020 100644 --- a/src/xrfragment/Parser.hx +++ b/src/xrfragment/Parser.hx @@ -33,7 +33,8 @@ class Parser { Frag.set("env", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA ); // category: animation - Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_VECTOR3 | XRF.NAVIGATOR | XRF.METADATA); + Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_STRING | XRF.NAVIGATOR | XRF.METADATA); + Frag.set("tv", 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 );