diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 90d31915ca4cd86be16bc852be37049714a60c58..5236fa8da9f86a6dbf47bf0c9f5bc2f558f9ad46 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -54,14 +54,14 @@ THREE.GLTFLoader = ( function () { } ); - parser.parse( function ( scene, cameras, animations ) { + parser.parse( function ( scene, cameras, clips ) { console.timeEnd( 'GLTFLoader' ); var glTF = { "scene": scene, "cameras": cameras, - "animations": animations + "clips": clips }; callback( glTF ); @@ -241,233 +241,66 @@ THREE.GLTFLoader = ( function () { }; - /* GLTFANIMATION */ + /* ANIMATION */ - GLTFLoader.Animations = new GLTFRegistry(); - - // Construction/initialization - function GLTFAnimation( interps ) { - - this.running = false; - this.loop = false; - this.duration = 0; - this.startTime = 0; - this.interps = []; - - this.uuid = THREE.Math.generateUUID(); - - if ( interps ) { - - this.createInterpolators( interps ); - - } - - } - - GLTFAnimation.prototype.createInterpolators = function ( interps ) { + function createClip( name, interps ) { + var tracks = []; for ( var i = 0, len = interps.length; i < len; i ++ ) { - var interp = new GLTFInterpolator( interps[ i ] ); - this.interps.push( interp ); - this.duration = Math.max( this.duration, interp.duration ); - - } - - }; - - // Start/stop - GLTFAnimation.prototype.play = function () { - - if ( this.running ) - return; - - this.startTime = Date.now(); - this.running = true; - GLTFLoader.Animations.add( this.uuid, this ); - - }; - - GLTFAnimation.prototype.stop = function () { - - this.running = false; - GLTFLoader.Animations.remove( this.uuid ); - - }; - - // Update - drive key frame evaluation - GLTFAnimation.prototype.update = function () { - - if ( ! this.running ) return; - - var now = Date.now(); - var deltat = ( now - this.startTime ) / 1000; - var t = deltat % this.duration; - var nCycles = Math.floor( deltat / this.duration ); - var interps = this.interps; - - if ( nCycles >= 1 && ! this.loop ) { - - this.running = false; - - for ( var i = 0, l = interps.length; i < l; i ++ ) { - - interps[ i ].interp( this.duration ); - - } - - this.stop(); - - } else { - - for ( var i = 0, l = interps.length; i < l; i ++ ) { - - interps[ i ].interp( t ); - - } - - } - - }; - - /* GLTFINTERPOLATOR */ - - function GLTFInterpolator( param ) { - - this.keys = param.keys; - this.values = param.values; - this.count = param.count; - this.type = param.type; - this.path = param.path; - this.isRot = false; - - var node = param.target; - node.updateMatrix(); - node.matrixAutoUpdate = true; - this.targetNode = node; - - switch ( param.path ) { - - case "translation" : - - this.target = node.position; - this.originalValue = node.position.clone(); - break; - - case "rotation" : + validateInterpolator( interps[ i ] ); - this.target = node.quaternion; - this.originalValue = node.quaternion.clone(); - this.isRot = true; - break; - - case "scale" : - - this.target = node.scale; - this.originalValue = node.scale.clone(); - break; + interps[ i ].target.updateMatrix(); + interps[ i ].target.matrixAutoUpdate = true; + tracks.push( new THREE.KeyframeTrack( + interps[ i ].name, + interps[ i ].times, + interps[ i ].values, + interps[ i ].type + ) ); } - this.duration = this.keys[ this.count - 1 ]; - - this.vec1 = new THREE.Vector3(); - this.vec2 = new THREE.Vector3(); - this.vec3 = new THREE.Vector3(); - this.quat1 = new THREE.Quaternion(); - this.quat2 = new THREE.Quaternion(); - this.quat3 = new THREE.Quaternion(); - + return new THREE.AnimationClip( name, undefined, tracks ); } - //Interpolation and tweening methods - GLTFInterpolator.prototype.interp = function ( t ) { - - if ( t == this.keys[ 0 ] ) { - - if ( this.isRot ) { - - this.quat3.fromArray( this.values ); - - } else { - - this.vec3.fromArray( this.values ); - - } - - } else if ( t < this.keys[ 0 ] ) { + /** + * Interp times are frequently non-sequential in the monster and cesium man + * models. That's probably a sign that the exporter is misbehaving, but to + * keep this backward-compatible we can swallow the errors. + */ + function validateInterpolator( interp ) { - if ( this.isRot ) { + var times = []; + var values = []; + var prevTime = null; + var currTime = null; - this.quat1.copy( this.originalValue ); - this.quat2.fromArray( this.values ); - THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, t / this.keys[ 0 ] ); + var stride = interp.values.length / interp.times.length; - } else { - - this.vec3.copy( this.originalValue ); - this.vec2.fromArray( this.values ); - this.vec3.lerp( this.vec2, t / this.keys[ 0 ] ); - - } - - } else if ( t >= this.keys[ this.count - 1 ] ) { - - if ( this.isRot ) { - - this.quat3.fromArray( this.values, ( this.count - 1 ) * 4 ); + for ( var i = 0; i < interp.times.length; i ++ ) { - } else { - - this.vec3.fromArray( this.values, ( this.count - 1 ) * 3 ); - - } - - } else { + currTime = interp.times[ i ]; - for ( var i = 0; i < this.count - 1; i ++ ) { + if (prevTime !== null && prevTime <= currTime) { - var key1 = this.keys[ i ]; - var key2 = this.keys[ i + 1 ]; + times.push( currTime ); - if ( t >= key1 && t <= key2 ) { + for ( var j = 0; j < stride; j++ ) { - if ( this.isRot ) { - - this.quat1.fromArray( this.values, i * 4 ); - this.quat2.fromArray( this.values, ( i + 1 ) * 4 ); - THREE.Quaternion.slerp( this.quat1, this.quat2, this.quat3, ( t - key1 ) / ( key2 - key1 ) ); - - } else { - - this.vec3.fromArray( this.values, i * 3 ); - this.vec2.fromArray( this.values, ( i + 1 ) * 3 ); - this.vec3.lerp( this.vec2, ( t - key1 ) / ( key2 - key1 ) ); - - } + values.push( interp.values[ i * stride + j ] ); } } + prevTime = currTime; } - if ( this.target ) { - - if ( this.isRot ) { - - this.target.copy( this.quat3 ); - - } else { - - this.target.copy( this.vec3 ); - - } - - } - - }; + interp.times = new Float32Array( times ); + interp.values = new Float32Array( values ); + } /*********************************/ /********** INTERNALS ************/ @@ -539,6 +372,16 @@ THREE.GLTFLoader = ( function () { 'MAT4': 16 }; + var PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion' + }; + + var INTERPOLATION = { + LINEAR: THREE.InterpolateLinear + }; + /* UTILITY FUNCTIONS */ function _each( object, callback, thisObj ) { @@ -798,7 +641,7 @@ THREE.GLTFLoader = ( function () { "scenes", "cameras", - "animations" + "clips" ] ).then( function ( dependencies ) { @@ -813,16 +656,16 @@ THREE.GLTFLoader = ( function () { } - var animations = []; + var clips = []; - for ( var name in dependencies.animations ) { + for ( var name in dependencies.clips ) { - var animation = dependencies.animations[ name ]; - animations.push( animation ); + var clip = dependencies.clips[ name ]; + clips.push( clip ); } - callback( scene, cameras, animations ); + callback( scene, cameras, clips); }.bind( this ) ); @@ -1435,7 +1278,9 @@ THREE.GLTFLoader = ( function () { }; - GLTFParser.prototype.loadAnimations = function () { + + + GLTFParser.prototype.loadClips = function () { var scope = this; @@ -1470,12 +1315,11 @@ THREE.GLTFLoader = ( function () { if ( node ) { var interp = { - keys: inputAccessor.array, + times: inputAccessor.array, values: outputAccessor.array, - count: inputAccessor.count, target: node, - path: target.path, - type: sampler.interpolation + type: INTERPOLATION[ sampler.interpolation ], + name: node.name + '.' + PATH_PROPERTIES[ target.path ] }; interps.push( interp ); @@ -1486,10 +1330,7 @@ THREE.GLTFLoader = ( function () { } - var _animation = new GLTFAnimation( interps ); - _animation.name = "animation_" + animationId; - - return _animation; + return createClip( "animation_" + animationId, interps ); } ); diff --git a/examples/webgl_loader_gltf.html b/examples/webgl_loader_gltf.html index 52f7f703ba5a33eb37b266b5837e33d79f29d1c8..2f372c78a8b12f87fc9d94dc6f989ce9539946c6 100644 --- a/examples/webgl_loader_gltf.html +++ b/examples/webgl_loader_gltf.html @@ -137,6 +137,8 @@ var cameraNames = []; var defaultCamera = null; var gltf = null; + var mixer = null; + var clock = new THREE.Clock(); function onload() { @@ -312,17 +314,18 @@ } - if (gltf.animations && gltf.animations.length) { + if (gltf.clips && gltf.clips.length) { - var i, len = gltf.animations.length; + mixer = new THREE.AnimationMixer(object); + + var i, len = gltf.clips.length; for (i = 0; i < len; i++) { - var animation = gltf.animations[i]; - animation.loop = true; + var clip = gltf.clips[i]; // There's .3333 seconds junk at the tail of the Monster animation that // keeps it from looping cleanly. Clip it at 3 seconds if (sceneInfo.animationTime) - animation.duration = sceneInfo.animationTime; - animation.play(); + clip.duration = sceneInfo.animationTime; + mixer.clipAction(clip).play(); } } @@ -353,7 +356,7 @@ function animate() { requestAnimationFrame( animate ); - THREE.GLTFLoader.Animations.update(); + if (mixer) mixer.update(clock.getDelta()); THREE.GLTFLoader.Shaders.update(scene, camera); if (cameraIndex == 0) orbitControls.update(); @@ -513,16 +516,17 @@ function toggleAnimations() { - var i, len = gltf.animations.length; + var i, len = gltf.clips.length; for (i = 0; i < len; i++) { - var animation = gltf.animations[i]; + var clip = gltf.clips[i]; + var action = mixer.existingAction( clip ); - if (animation.running) { - animation.stop(); + if (action.isRunning()) { + action.stop(); } else { - animation.play(); + action.play(); } } @@ -549,17 +553,10 @@ cameraNames = []; defaultCamera = null; - if (!loader || !gltf.animations) + if (!loader || !mixer) return; - var i, len = gltf.animations.length; - - for (i = 0; i < len; i++) { - var animation = gltf.animations[i]; - if (animation.running) { - animation.stop(); - } - } + mixer.stopAllAction(); }