src's and global scene have separate animation mixers now yay! \o/

This commit is contained in:
Leon van Kammen 2023-11-02 16:49:18 +01:00
parent d474cb5e8d
commit 8be5e485ce
13 changed files with 636 additions and 280 deletions

View File

@ -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
| `#<cameraname>` | string | `#cam01` | set camera as active camera |
| `#<objectname>=<material>` | 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` |
| `#<objectname>=<timevector>` | 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)|
| `#<objectname>=<mediafrag>` | 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) |
| `#<objectname>=<timevector>` | 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

View File

@ -31,8 +31,8 @@
<canvas id="qrcode" style="display:none" width="300" height="300"></canvas>
<a-scene light="defaultLightsEnabled: false">
<a-entity id="player">
<a-entity camera="fov:90" wasd-controls look-controls position="0 1.6 0" id="camera"></a-entity>
<a-entity id="player" wasd-controls look-controls>
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor">
<a-entity rotation="-90 0 0" position="0 0.1 0" id="navigator">
<a-entity id="back" xrf-button="label: <; width:0.05; action: history.back()" position="-0.025 0 0" class="ray"></a-entity>

Binary file not shown.

View File

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

View File

@ -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/"
}
]
}

View File

@ -130,6 +130,7 @@ a#model{
}
html.a-fullscreen a.btn-foot {
line-height:36px;
margin-right:10px;
}

Binary file not shown.

File diff suppressed because one or more lines are too long

2
make
View File

@ -69,7 +69,7 @@ build_js(){
cat dist/xrfragment.js \
src/3rd/js/*.js \
src/3rd/js/three/*.js \
src/3rd/js/three/xrmacro/*.js \
src/3rd/js/three/xrmacro/env.js \
src/3rd/js/three/xrf/*.js \
src/3rd/js/three/xrf/dynamic/*.js \
src/3rd/js/three/xrf/src/*.js > dist/xrfragment.three.js

View File

@ -5,16 +5,18 @@ xrf.frag.src = function(v, opts){
opts.embedded = v // indicate embedded XR fragment
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
console.log(" └ instancing src")
let src;
let url = v.string
let vfrag = xrfragment.URI.parse(url)
opts.isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
const addScene = (scene,url,frag) => {
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) => {

View File

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

View File

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

View File

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