|
@@ -13,11 +13,15 @@ THREE.AnimationClip = function ( name, duration, tracks ) {
|
|
|
this.duration = ( duration !== undefined ) ? duration : -1;
|
|
|
|
|
|
// this means it should figure out its duration by scanning the tracks
|
|
|
- if( this.duration < 0 ) {
|
|
|
- for( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
+ if ( this.duration < 0 ) {
|
|
|
+
|
|
|
+ for ( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
+
|
|
|
var track = this.tracks[i];
|
|
|
this.duration = Math.max( track.times[ track.times.length - 1 ] );
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// maybe only do these on demand, as doing them here could potentially slow down loading
|
|
@@ -33,7 +37,7 @@ THREE.AnimationClip.prototype = {
|
|
|
|
|
|
trim: function() {
|
|
|
|
|
|
- for( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
+ for ( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
|
|
|
this.tracks[ i ].trim( 0, this.duration );
|
|
|
|
|
@@ -45,7 +49,7 @@ THREE.AnimationClip.prototype = {
|
|
|
|
|
|
optimize: function() {
|
|
|
|
|
|
- for( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
+ for ( var i = 0; i < this.tracks.length; i ++ ) {
|
|
|
|
|
|
this.tracks[ i ].optimize();
|
|
|
|
|
@@ -57,241 +61,266 @@ THREE.AnimationClip.prototype = {
|
|
|
|
|
|
};
|
|
|
|
|
|
-// parse the standard JSON format for clips
|
|
|
-THREE.AnimationClip.parse = function( json ) {
|
|
|
+// Static methods:
|
|
|
|
|
|
- var tracks = [],
|
|
|
- jsonTracks = json[ 'tracks' ],
|
|
|
- frameTime = 1.0 / ( json[ 'fps' ] || 1.0 );
|
|
|
+Object.assign( THREE.AnimationClip, {
|
|
|
|
|
|
- for( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {
|
|
|
+ parse: function( json ) {
|
|
|
|
|
|
- tracks.push( THREE.KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );
|
|
|
+ var tracks = [],
|
|
|
+ jsonTracks = json[ 'tracks' ],
|
|
|
+ frameTime = 1.0 / ( json[ 'fps' ] || 1.0 );
|
|
|
|
|
|
- }
|
|
|
+ for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {
|
|
|
|
|
|
- return new THREE.AnimationClip( json[ 'name' ], json[ 'duration' ], tracks );
|
|
|
+ tracks.push( THREE.KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );
|
|
|
|
|
|
-};
|
|
|
+ }
|
|
|
|
|
|
+ return new THREE.AnimationClip( json[ 'name' ], json[ 'duration' ], tracks );
|
|
|
|
|
|
-THREE.AnimationClip.toJSON = function( clip ) {
|
|
|
+ },
|
|
|
|
|
|
- var tracks = [],
|
|
|
- clipTracks = clip.tracks;
|
|
|
|
|
|
- var json = {
|
|
|
+ toJSON: function( clip ) {
|
|
|
|
|
|
- 'name': clip.name,
|
|
|
- 'duration': clip.duration,
|
|
|
- 'tracks': tracks
|
|
|
+ var tracks = [],
|
|
|
+ clipTracks = clip.tracks;
|
|
|
|
|
|
- };
|
|
|
+ var json = {
|
|
|
|
|
|
- for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {
|
|
|
+ 'name': clip.name,
|
|
|
+ 'duration': clip.duration,
|
|
|
+ 'tracks': tracks
|
|
|
|
|
|
- tracks.push( THREE.KeyframeTrack.toJSON( clipTracks[ i ] ) );
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
+ for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {
|
|
|
|
|
|
- return json;
|
|
|
+ tracks.push( THREE.KeyframeTrack.toJSON( clipTracks[ i ] ) );
|
|
|
|
|
|
-}
|
|
|
+ }
|
|
|
|
|
|
+ return json;
|
|
|
|
|
|
-THREE.AnimationClip.CreateFromMorphTargetSequence = function( name, morphTargetSequence, fps ) {
|
|
|
+ },
|
|
|
|
|
|
- var numMorphTargets = morphTargetSequence.length;
|
|
|
- var tracks = [];
|
|
|
|
|
|
- for( var i = 0; i < numMorphTargets; i ++ ) {
|
|
|
+ CreateFromMorphTargetSequence: function( name, morphTargetSequence, fps ) {
|
|
|
|
|
|
- var times = [];
|
|
|
- var values = [];
|
|
|
+ var numMorphTargets = morphTargetSequence.length;
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- times.push(
|
|
|
- ( i + numMorphTargets - 1 ) % numMorphTargets,
|
|
|
- i,
|
|
|
- ( i + 1 ) % numMorphTargets );
|
|
|
+ for ( var i = 0; i < numMorphTargets; i ++ ) {
|
|
|
|
|
|
- values.push( 0, 1, 0 );
|
|
|
+ var times = [];
|
|
|
+ var values = [];
|
|
|
|
|
|
- var order = THREE.AnimationUtils.getKeyframeOrder( times );
|
|
|
- times = THREE.AnimationUtils.sortedArray( times, 1, order );
|
|
|
- values = THREE.AnimationUtils.sortedArray( values, 1, order );
|
|
|
+ times.push(
|
|
|
+ ( i + numMorphTargets - 1 ) % numMorphTargets,
|
|
|
+ i,
|
|
|
+ ( i + 1 ) % numMorphTargets );
|
|
|
|
|
|
- // if there is a key at the first frame, duplicate it as the last frame as well for perfect loop.
|
|
|
- if( times[ 0 ] === 0 ) {
|
|
|
+ values.push( 0, 1, 0 );
|
|
|
|
|
|
- times.push( numMorphTargets );
|
|
|
- values.push( values[ 0 ] );
|
|
|
+ var order = THREE.AnimationUtils.getKeyframeOrder( times );
|
|
|
+ times = THREE.AnimationUtils.sortedArray( times, 1, order );
|
|
|
+ values = THREE.AnimationUtils.sortedArray( values, 1, order );
|
|
|
|
|
|
- }
|
|
|
+ // if there is a key at the first frame, duplicate it as the
|
|
|
+ // last frame as well for perfect loop.
|
|
|
+ if ( times[ 0 ] === 0 ) {
|
|
|
|
|
|
- tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', times, values ).scale( 1.0 / fps ) );
|
|
|
- }
|
|
|
+ times.push( numMorphTargets );
|
|
|
+ values.push( values[ 0 ] );
|
|
|
|
|
|
- return new THREE.AnimationClip( name, -1, tracks );
|
|
|
+ }
|
|
|
|
|
|
-};
|
|
|
+ tracks.push(
|
|
|
+ new THREE.NumberKeyframeTrack(
|
|
|
+ '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
|
|
|
+ times, values
|
|
|
+ ).scale( 1.0 / fps ) );
|
|
|
+ }
|
|
|
|
|
|
-THREE.AnimationClip.findByName = function( clipArray, name ) {
|
|
|
+ return new THREE.AnimationClip( name, -1, tracks );
|
|
|
+
|
|
|
+ },
|
|
|
|
|
|
- for( var i = 0; i < clipArray.length; i ++ ) {
|
|
|
+ findByName: function( clipArray, name ) {
|
|
|
|
|
|
- if( clipArray[ i ].name === name ) {
|
|
|
+ for ( var i = 0; i < clipArray.length; i ++ ) {
|
|
|
|
|
|
- return clipArray[ i ];
|
|
|
+ if ( clipArray[ i ].name === name ) {
|
|
|
|
|
|
+ return clipArray[ i ];
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- return null;
|
|
|
+ return null;
|
|
|
|
|
|
-};
|
|
|
+ },
|
|
|
+
|
|
|
+ CreateClipsFromMorphTargetSequences: function( morphTargets, fps ) {
|
|
|
+
|
|
|
+ var animationToMorphTargets = {};
|
|
|
+
|
|
|
+ // tested with https://regex101.com/ on trick sequences
|
|
|
+ // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
|
|
|
+ var pattern = /^([\w-]*?)([\d]+)$/;
|
|
|
|
|
|
-THREE.AnimationClip.CreateClipsFromMorphTargetSequences = function( morphTargets, fps ) {
|
|
|
+ // sort morph target names into animation groups based
|
|
|
+ // patterns like Walk_001, Walk_002, Run_001, Run_002
|
|
|
+ for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {
|
|
|
|
|
|
- var animationToMorphTargets = {};
|
|
|
+ var morphTarget = morphTargets[ i ];
|
|
|
+ var parts = morphTarget.name.match( pattern );
|
|
|
|
|
|
- // tested with https://regex101.com/ on trick sequences such flamingo_flyA_003, flamingo_run1_003, crdeath0059
|
|
|
- var pattern = /^([\w-]*?)([\d]+)$/;
|
|
|
+ if ( parts && parts.length > 1 ) {
|
|
|
|
|
|
- // sort morph target names into animation groups based patterns like Walk_001, Walk_002, Run_001, Run_002
|
|
|
- for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {
|
|
|
+ var name = parts[ 1 ];
|
|
|
|
|
|
- var morphTarget = morphTargets[ i ];
|
|
|
- var parts = morphTarget.name.match( pattern );
|
|
|
+ var animationMorphTargets = animationToMorphTargets[ name ];
|
|
|
+ if ( ! animationMorphTargets ) {
|
|
|
|
|
|
- if ( parts && parts.length > 1 ) {
|
|
|
+ animationToMorphTargets[ name ] = animationMorphTargets = [];
|
|
|
|
|
|
- var name = parts[ 1 ];
|
|
|
+ }
|
|
|
+
|
|
|
+ animationMorphTargets.push( morphTarget );
|
|
|
|
|
|
- var animationMorphTargets = animationToMorphTargets[ name ];
|
|
|
- if( ! animationMorphTargets ) {
|
|
|
- animationToMorphTargets[ name ] = animationMorphTargets = [];
|
|
|
}
|
|
|
|
|
|
- animationMorphTargets.push( morphTarget );
|
|
|
+ }
|
|
|
+
|
|
|
+ var clips = [];
|
|
|
+
|
|
|
+ for ( var name in animationToMorphTargets ) {
|
|
|
+
|
|
|
+ clips.push( THREE.AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
- }
|
|
|
+ return clips;
|
|
|
|
|
|
- var clips = [];
|
|
|
+ },
|
|
|
|
|
|
- for( var name in animationToMorphTargets ) {
|
|
|
+ // parse the animation.hierarchy format
|
|
|
+ parseAnimation: function( animation, bones, nodeName ) {
|
|
|
|
|
|
- clips.push( THREE.AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps ) );
|
|
|
- }
|
|
|
+ if ( ! animation ) {
|
|
|
|
|
|
- return clips;
|
|
|
+ console.error( " no animation in JSONLoader data" );
|
|
|
+ return null;
|
|
|
|
|
|
-};
|
|
|
+ }
|
|
|
|
|
|
-// parse the animation.hierarchy format
|
|
|
-THREE.AnimationClip.parseAnimation = function( animation, bones ) {
|
|
|
+ var convertTrack = function( trackName, animationKeys, propertyName, trackType ) {
|
|
|
|
|
|
- if( ! animation ) {
|
|
|
- console.error( " no animation in JSONLoader data" );
|
|
|
- return null;
|
|
|
- }
|
|
|
+ // only return track if there are actually keys.
|
|
|
+ if ( animationKeys.length === 0 ) return null;
|
|
|
|
|
|
- var convertTrack = function( trackName, animationKeys, propertyName, trackType ) {
|
|
|
+ var times = [];
|
|
|
+ var values = [];
|
|
|
|
|
|
- // only return track if there are actually keys.
|
|
|
- if ( animationKeys.length === 0 ) return null;
|
|
|
+ THREE.AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
|
|
|
|
|
|
- var times = [];
|
|
|
- var values = [];
|
|
|
+ return new trackType( trackName, times, values );
|
|
|
|
|
|
- THREE.AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
|
|
|
+ };
|
|
|
|
|
|
- return new trackType( trackName, times, values );
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- };
|
|
|
+ var clipName = animation.name || 'default';
|
|
|
+ // automatic length determination in AnimationClip.
|
|
|
+ var duration = animation.length || -1;
|
|
|
+ var fps = animation.fps || 30;
|
|
|
|
|
|
- var tracks = [];
|
|
|
+ var hierarchyTracks = animation.hierarchy || [];
|
|
|
|
|
|
- var clipName = animation.name || 'default';
|
|
|
- var duration = animation.length || -1; // automatic length determination in AnimationClip.
|
|
|
- var fps = animation.fps || 30;
|
|
|
+ for ( var h = 0; h < hierarchyTracks.length; h ++ ) {
|
|
|
|
|
|
- var hierarchyTracks = animation.hierarchy || [];
|
|
|
+ var animationKeys = hierarchyTracks[ h ].keys;
|
|
|
|
|
|
- for ( var h = 0; h < hierarchyTracks.length; h ++ ) {
|
|
|
+ // skip empty tracks
|
|
|
+ if ( ! animationKeys || animationKeys.length == 0 ) continue;
|
|
|
|
|
|
- var animationKeys = hierarchyTracks[ h ].keys;
|
|
|
+ // process morph targets in a way exactly compatible
|
|
|
+ // with AnimationHandler.init( animation )
|
|
|
+ if ( animationKeys[0].morphTargets ) {
|
|
|
|
|
|
- // skip empty tracks
|
|
|
- if( ! animationKeys || animationKeys.length == 0 ) {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ // figure out all morph targets used in this track
|
|
|
+ var morphTargetNames = {};
|
|
|
+ for ( var k = 0; k < animationKeys.length; k ++ ) {
|
|
|
|
|
|
- // process morph targets in a way exactly compatible with AnimationHandler.init( animation )
|
|
|
- if( animationKeys[0].morphTargets ) {
|
|
|
+ if ( animationKeys[k].morphTargets ) {
|
|
|
|
|
|
- // figure out all morph targets used in this track
|
|
|
- var morphTargetNames = {};
|
|
|
- for( var k = 0; k < animationKeys.length; k ++ ) {
|
|
|
+ for ( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
|
|
|
|
|
|
- if( animationKeys[k].morphTargets ) {
|
|
|
- for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
|
|
|
+ morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1;
|
|
|
+ }
|
|
|
|
|
|
- morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1;
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
- }
|
|
|
+ // create a track for each morph target with all zero
|
|
|
+ // morphTargetInfluences except for the keys in which
|
|
|
+ // the morphTarget is named.
|
|
|
+ for ( var morphTargetName in morphTargetNames ) {
|
|
|
|
|
|
- // create a track for each morph target with all zero morphTargetInfluences except for the keys in which the morphTarget is named.
|
|
|
- for( var morphTargetName in morphTargetNames ) {
|
|
|
+ var times = [];
|
|
|
+ var values = [];
|
|
|
|
|
|
- var times = [];
|
|
|
- var values = [];
|
|
|
+ for ( var m = 0;
|
|
|
+ m !== animationKeys[k].morphTargets.length; ++ m ) {
|
|
|
|
|
|
- for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
|
|
|
+ var animationKey = animationKeys[k];
|
|
|
|
|
|
- var animationKey = animationKeys[k];
|
|
|
+ times.push( animationKey.time );
|
|
|
+ values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 )
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- times.push( animationKey.time );
|
|
|
- values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 )
|
|
|
+ tracks.push( new THREE.NumberKeyframeTrack(
|
|
|
+ '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
- tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
|
|
|
+ duration = morphTargetNames.length * ( fps || 1.0 );
|
|
|
|
|
|
- }
|
|
|
+ } else {
|
|
|
|
|
|
- duration = morphTargetNames.length * ( fps || 1.0 );
|
|
|
+ var boneName = '.bones[' + bones[ h ].name + ']';
|
|
|
|
|
|
- } else {
|
|
|
+ // track contains positions...
|
|
|
+ var positionTrack = convertTrack( boneName + '.position', animationKeys, 'pos', THREE.VectorKeyframeTrack );
|
|
|
+ if ( positionTrack ) tracks.push( positionTrack );
|
|
|
|
|
|
- var boneName = '.bones[' + bones[ h ].name + ']';
|
|
|
+ // track contains quaternions...
|
|
|
+ var quaternionTrack = convertTrack( boneName + '.quaternion', animationKeys, 'rot', THREE.QuaternionKeyframeTrack );
|
|
|
+ if ( quaternionTrack ) tracks.push( quaternionTrack );
|
|
|
|
|
|
- // track contains positions...
|
|
|
- var positionTrack = convertTrack( boneName + '.position', animationKeys, 'pos', THREE.VectorKeyframeTrack );
|
|
|
- if( positionTrack ) tracks.push( positionTrack );
|
|
|
+ // track contains quaternions...
|
|
|
+ var scaleTrack = convertTrack( boneName + '.scale', animationKeys, 'scl', THREE.VectorKeyframeTrack );
|
|
|
+ if ( scaleTrack ) tracks.push( scaleTrack );
|
|
|
|
|
|
- // track contains quaternions...
|
|
|
- var quaternionTrack = convertTrack( boneName + '.quaternion', animationKeys, 'rot', THREE.QuaternionKeyframeTrack );
|
|
|
- if( quaternionTrack ) tracks.push( quaternionTrack );
|
|
|
+ }
|
|
|
|
|
|
- // track contains quaternions...
|
|
|
- var scaleTrack = convertTrack( boneName + '.scale', animationKeys, 'scl', THREE.VectorKeyframeTrack );
|
|
|
- if( scaleTrack ) tracks.push( scaleTrack );
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( tracks.length === 0 ) {
|
|
|
+
|
|
|
+ return null;
|
|
|
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if( tracks.length === 0 ) {
|
|
|
+ var clip = new THREE.AnimationClip( clipName, duration, tracks );
|
|
|
|
|
|
- return null;
|
|
|
+ return clip;
|
|
|
|
|
|
}
|
|
|
|
|
|
- var clip = new THREE.AnimationClip( clipName, duration, tracks );
|
|
|
-
|
|
|
- return clip;
|
|
|
+} );
|
|
|
|
|
|
-};
|