Browse Source

AnimationUtils: Move new helpers into GLTFExporter.

Don McCurdy 6 years ago
parent
commit
73038900ec

+ 0 - 17
docs/api/en/animation/AnimationUtils.html

@@ -38,28 +38,11 @@
 		Returns an array by which times and values can be sorted.
 		</p>
 
-		<h3>[method:Number insertKeyframe]( [param:KeyframeTrack track], [param:Number time] )</h3>
-		<p>
-		Inserts a new keyframe into the track at the given time, if it doesn't already exist. Keyframe
-		values are interpolated from existing keyframes. Returns the keyframe index.
-		</p>
-
 		<h3>[method:Boolean isTypedArray]( object )</h3>
 		<p>
 		Returns *true* if the object is a typed array.
 		</p>
 
-		<h3>[method:AnimationClip mergeMorphTargetTracks]( [param:AnimationClip clip], [param:Object3D root] )</h3>
-		<p>
-		Merges all KeyframeTracks that animate morph targets on a single mesh. The clip provided will
-		be modified and returned.
-		</p>
-		<p>
-		If a clip contains three tracks each controlling one of an object's five morph targets, those
-		three tracks will be merged and two empty tracks created for the remaining morph targets, so
-		that all morph targets are controlled by a single track.
-		</p>
-
 		<h3>[method:Array sortedArray]( values, stride, order )</h3>
 		<p>
 		Sorts the array previously returned by [page:AnimationUtils.getKeyframeOrder getKeyframeOrder].

+ 194 - 1
examples/js/exporters/GLTFExporter.js

@@ -1408,7 +1408,7 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
-			clip = THREE.AnimationUtils.mergeMorphTargetTracks( clip.clone(), root );
+			clip = THREE.GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
 
 			var tracks = clip.tracks;
 			var channels = [];
@@ -1900,3 +1900,196 @@ THREE.GLTFExporter.prototype = {
 	}
 
 };
+
+THREE.GLTFExporter.Utils = {
+
+	insertKeyframe: function ( track, time ) {
+
+		var tolerance = 0.001; // 1ms
+		var valueSize = track.getValueSize();
+
+		var times = new track.TimeBufferType( track.times.length + 1 );
+		var values = new track.ValueBufferType( track.values.length + valueSize );
+		var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
+
+		var index;
+
+		if ( track.times.length === 0 ) {
+
+			times[ 0 ] = time;
+
+			for ( var i = 0; i < valueSize; i ++ ) {
+
+				values[ i ] = 0;
+
+			}
+
+			index = 0;
+
+		} else if ( time < track.times[ 0 ] ) {
+
+			if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
+
+			times[ 0 ] = time;
+			times.set( track.times, 1 );
+
+			values.set( interpolant.evaluate( time ), 0 );
+			values.set( track.values, valueSize );
+
+			index = 0;
+
+		} else if ( time > track.times[ track.times.length - 1 ] ) {
+
+			if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
+
+				return track.times.length - 1;
+
+			}
+
+			times[ times.length - 1 ] = time;
+			times.set( track.times, 0 );
+
+			values.set( track.values, 0 );
+			values.set( interpolant.evaluate( time ), track.values.length );
+
+			index = times.length - 1;
+
+		} else {
+
+			for ( var i = 0; i < track.times.length; i ++ ) {
+
+				if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
+
+				if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
+
+					times.set( track.times.slice( 0, i + 1 ), 0 );
+					times[ i + 1 ] = time;
+					times.set( track.times.slice( i + 1 ), i + 2 );
+
+					values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
+					values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
+					values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
+
+					index = i + 1;
+
+					break;
+
+				}
+
+			}
+
+		}
+
+		track.times = times;
+		track.values = values;
+
+		return index;
+
+	},
+
+	mergeMorphTargetTracks: function ( clip, root ) {
+
+		var tracks = [];
+		var mergedTracks = {};
+		var sourceTracks = clip.tracks;
+
+		for ( var i = 0; i < sourceTracks.length; ++ i ) {
+
+			var sourceTrack = sourceTracks[ i ];
+			var sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name );
+			var sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
+
+			if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' ) {
+
+				// Tracks that don't affect morph targets can be kept as-is.
+				tracks.push( sourceTrack );
+				continue;
+
+			}
+
+			if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
+				&& sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
+
+				if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+					// This should never happen, because glTF morph target animations
+					// affect all targets already.
+					throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
+
+				}
+
+				console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
+
+				sourceTrack = sourceTrack.clone();
+				sourceTrack.setInterpolation( InterpolateLinear );
+
+			}
+
+			var targetCount = sourceTrackNode.morphTargetInfluences.length;
+			var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
+
+			if ( targetIndex === undefined ) {
+
+				throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
+
+			}
+
+			var mergedTrack;
+
+			// If this is the first time we've seen this object, create a new
+			// track to store merged keyframe data for each morph target.
+			if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
+
+				mergedTrack = sourceTrack.clone();
+
+				var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
+
+				for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
+
+					values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
+
+				}
+
+				mergedTrack.name = '.morphTargetInfluences';
+				mergedTrack.values = values;
+
+				mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
+				tracks.push( mergedTrack );
+
+				continue;
+
+			}
+
+			var mergedKeyframeIndex = 0;
+			var sourceKeyframeIndex = 0;
+			var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
+
+			mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
+
+			// For every existing keyframe of the merged track, write a (possibly
+			// interpolated) value from the source track.
+			for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
+
+				mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
+
+			}
+
+			// For every existing keyframe of the source track, write a (possibly
+			// new) keyframe to the merged track. Values from the previous loop may
+			// be written again, but keyframes are de-duplicated.
+			for ( var j = 0; j < sourceTrack.times.length; j ++ ) {
+
+				var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
+				mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
+
+			}
+
+		}
+
+		clip.tracks = tracks;
+
+		return clip;
+
+	}
+
+};

+ 0 - 189
src/animation/AnimationUtils.js

@@ -161,195 +161,6 @@ var AnimationUtils = {
 
 		}
 
-	},
-
-	insertKeyframe: function ( track, time ) {
-
-		var tolerance = 0.001; // 1ms
-		var valueSize = track.getValueSize();
-
-		var times = new track.TimeBufferType( track.times.length + 1 );
-		var values = new track.ValueBufferType( track.values.length + valueSize );
-		var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
-
-		var index;
-
-		if ( track.times.length === 0 ) {
-
-			times[ 0 ] = time;
-
-			for ( var i = 0; i < valueSize; i ++ ) {
-
-				values[ i ] = 0;
-
-			}
-
-			index = 0;
-
-		} else if ( time < track.times[ 0 ] ) {
-
-			if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
-
-			times[ 0 ] = time;
-			times.set( track.times, 1 );
-
-			values.set( interpolant.evaluate( time ), 0 );
-			values.set( track.values, valueSize );
-
-			index = 0;
-
-		} else if ( time > track.times[ track.times.length - 1 ] ) {
-
-			if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
-
-				return track.times.length - 1;
-
-			}
-
-			times[ times.length - 1 ] = time;
-			times.set( track.times, 0 );
-
-			values.set( track.values, 0 );
-			values.set( interpolant.evaluate( time ), track.values.length );
-
-			index = times.length - 1;
-
-		} else {
-
-			for ( var i = 0; i < track.times.length; i ++ ) {
-
-				if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
-
-				if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
-
-					times.set( track.times.slice( 0, i + 1 ), 0 );
-					times[ i + 1 ] = time;
-					times.set( track.times.slice( i + 1 ), i + 2 );
-
-					values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
-					values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
-					values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
-
-					index = i + 1;
-
-					break;
-
-				}
-
-			}
-
-		}
-
-		track.times = times;
-		track.values = values;
-
-		return index;
-
-	},
-
-	mergeMorphTargetTracks: function ( clip, root ) {
-
-		var tracks = [];
-		var mergedTracks = {};
-		var sourceTracks = clip.tracks;
-
-		for ( var i = 0; i < sourceTracks.length; ++ i ) {
-
-			var sourceTrack = sourceTracks[ i ];
-			var sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name );
-			var sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
-
-			if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' ) {
-
-				// Tracks that don't affect morph targets can be kept as-is.
-				tracks.push( sourceTrack );
-				continue;
-
-			}
-
-			if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
-				&& sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
-
-				if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
-
-					// This should never happen, because glTF morph target animations
-					// affect all targets already.
-					throw new Error( 'THREE.AnimationUtils: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
-
-				}
-
-				console.warn( 'THREE.AnimationUtils: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
-
-				sourceTrack = sourceTrack.clone();
-				sourceTrack.setInterpolation( InterpolateLinear );
-
-			}
-
-			var targetCount = sourceTrackNode.morphTargetInfluences.length;
-			var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
-
-			if ( targetIndex === undefined ) {
-
-				throw new Error( 'THREE.AnimationUtils: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
-
-			}
-
-			var mergedTrack;
-
-			// If this is the first time we've seen this object, create a new
-			// track to store merged keyframe data for each morph target.
-			if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
-
-				mergedTrack = sourceTrack.clone();
-
-				var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
-
-				for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
-
-					values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
-
-				}
-
-				mergedTrack.name = '.morphTargetInfluences';
-				mergedTrack.values = values;
-
-				mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
-				tracks.push( mergedTrack );
-
-				continue;
-
-			}
-
-			var mergedKeyframeIndex = 0;
-			var sourceKeyframeIndex = 0;
-			var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
-
-			mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
-
-			// For every existing keyframe of the merged track, write a (possibly
-			// interpolated) value from the source track.
-			for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
-
-				mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
-
-			}
-
-			// For every existing keyframe of the source track, write a (possibly
-			// new) keyframe to the merged track. Values from the previous loop may
-			// be written again, but keyframes are de-duplicated.
-			for ( var j = 0; j < sourceTrack.times.length; j ++ ) {
-
-				var keyframeIndex = AnimationUtils.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
-				mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
-
-			}
-
-		}
-
-		clip.tracks = tracks;
-
-		return clip;
-
 	}
 
 };

+ 114 - 0
test/unit/example/exporters/GLTFExporter.tests.js

@@ -5,6 +5,19 @@
 
 import * as GLTFExporter from '../../../../examples/js/exporters/GLTFExporter';
 
+import { AnimationClip } from '../../../../src/animation/AnimationClip';
+import { BufferAttribute } from '../../../../src/core/BufferAttribute';
+import { BufferGeometry } from '../../../../src/core/BufferGeometry';
+import { Mesh } from '../../../../src/objects/Mesh';
+import { Object3D } from '../../../../src/core/Object3D';
+import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
+import { VectorKeyframeTrack } from '../../../../src/animation/tracks/VectorKeyframeTrack';
+import {
+  InterpolateLinear,
+  InterpolateSmooth,
+  InterpolateDiscrete
+} from '../../../../src/constants.js';
+
 export default QUnit.module( 'Exporters', () => {
 
   QUnit.module( 'GLTFExporter', () => {
@@ -221,6 +234,107 @@ export default QUnit.module( 'Exporters', () => {
 
     } );
 
+    QUnit.test( 'utils - insertKeyframe', ( assert ) => {
+
+      var track;
+      var index;
+
+      function createTrack () {
+        return new VectorKeyframeTrack(
+          'foo.bar',
+          [ 5,    10,   15,   20,   25,   30 ],
+          [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ],
+          InterpolateLinear
+        );
+      }
+
+      track = createTrack();
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 0 );
+      assert.equal( index, 0, 'prepend - index' );
+      assert.smartEqual( Array.from( track.times ), [ 0, 5, 10, 15, 20, 25, 30 ], 'prepend - time' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'prepend - value' );
+
+      track = createTrack();
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 7.5 );
+      assert.equal( index, 1, 'insert - index (linear)' );
+      assert.smartEqual( Array.from( track.times ), [ 5, 7.5, 10, 15, 20, 25, 30 ], 'insert - time (linear)' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 0.5, 4.5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (linear)' );
+
+      track = createTrack();
+      track.setInterpolation( InterpolateDiscrete );
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 16 );
+      assert.equal( index, 3, 'insert - index (linear)' );
+      assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 16, 20, 25, 30 ], 'insert - time (discrete)' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (discrete)' );
+
+      track = createTrack();
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 100 );
+      assert.equal( index, 6, 'append - index' );
+      assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 100 ], 'append time' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0, 5, 0 ], 'append value' );
+
+      track = createTrack();
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 15 );
+      assert.equal( index, 2, 'existing - index' );
+      assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'existing - time' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'existing - value' );
+
+      track = createTrack();
+      index = THREE.GLTFExporter.Utils.insertKeyframe( track, 20.000005 );
+      assert.equal( index, 3, 'tolerance - index' );
+      assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'tolerance - time' );
+      assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'tolerance - value' );
+
+    } );
+
+    QUnit.test( 'utils - mergeMorphTargetTracks', ( assert ) => {
+
+      var trackA = new NumberKeyframeTrack(
+        'foo.morphTargetInfluences[a]',
+        [ 5, 10, 15, 20, 25, 30 ],
+        [ 0, 0.2, 0.4, 0.6, 0.8, 1.0 ],
+        InterpolateLinear
+      );
+
+      var trackB = new NumberKeyframeTrack(
+        'foo.morphTargetInfluences[b]',
+        [ 10, 50 ],
+        [ 0.25, 0.75 ],
+        InterpolateLinear
+      );
+
+      var geometry = new BufferGeometry();
+      var position = new BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
+      geometry.addAttribute( 'position',  position );
+      geometry.morphAttributes.position = [ position, position ];
+
+      var mesh = new Mesh( geometry );
+      mesh.name = 'foo';
+      mesh.morphTargetDictionary.a = 0;
+      mesh.morphTargetDictionary.b = 1;
+
+      var root = new Object3D();
+      root.add( mesh );
+
+      var clip = new AnimationClip( 'waltz', undefined, [ trackA, trackB ] );
+      clip = THREE.GLTFExporter.Utils.mergeMorphTargetTracks( clip, root );
+
+      assert.equal( clip.tracks.length, 1, 'tracks are merged' );
+
+      var track = clip.tracks[ 0 ];
+
+      assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 50 ], 'all keyframes are present' );
+
+      var expectedValues = [ 0, 0.25, 0.2, 0.25, 0.4, 0.3125, 0.6, 0.375, 0.8, 0.4375, 1.0, 0.5, 1.0, 0.75 ];
+
+      for ( var i = 0; i < track.values.length; i ++ ) {
+
+        assert.numEqual( track.values[ i ], expectedValues[ i ], 'all values are merged or interpolated - ' + i );
+
+      }
+
+    } );
+
   } );
 
 } );

+ 0 - 112
test/unit/src/animation/AnimationUtils.tests.js

@@ -3,125 +3,13 @@
  */
 /* global QUnit */
 
-import { AnimationClip } from '../../../../src/animation/AnimationClip';
 import { AnimationUtils } from '../../../../src/animation/AnimationUtils';
-import { BufferAttribute } from '../../../../src/core/BufferAttribute';
-import { BufferGeometry } from '../../../../src/core/BufferGeometry';
-import { Mesh } from '../../../../src/objects/Mesh';
-import { Object3D } from '../../../../src/core/Object3D';
-import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
-import { VectorKeyframeTrack } from '../../../../src/animation/tracks/VectorKeyframeTrack';
-import {
-	InterpolateLinear,
-	InterpolateSmooth,
-	InterpolateDiscrete
-} from '../../../../src/constants.js';
 
 export default QUnit.module( 'Animation', () => {
 
 	QUnit.module( 'AnimationUtils', () => {
 
 		// PUBLIC STUFF
-		QUnit.test( 'insertKeyframe', ( assert ) => {
-
-			var track;
-			var index;
-
-			function createTrack () {
-				return new VectorKeyframeTrack(
-					'foo.bar',
-					[ 5,    10,   15,   20,   25,   30 ],
-					[ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ],
-					InterpolateLinear
-				);
-			}
-
-			track = createTrack();
-			index = AnimationUtils.insertKeyframe( track, 0 );
-			assert.equal( index, 0, 'prepend - index' );
-			assert.smartEqual( Array.from( track.times ), [ 0, 5, 10, 15, 20, 25, 30 ], 'prepend - time' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'prepend - value' );
-
-			track = createTrack();
-			index = AnimationUtils.insertKeyframe( track, 7.5 );
-			assert.equal( index, 1, 'insert - index (linear)' );
-			assert.smartEqual( Array.from( track.times ), [ 5, 7.5, 10, 15, 20, 25, 30 ], 'insert - time (linear)' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 0.5, 4.5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (linear)' );
-
-			track = createTrack();
-			track.setInterpolation( InterpolateDiscrete );
-			index = AnimationUtils.insertKeyframe( track, 16 );
-			assert.equal( index, 3, 'insert - index (linear)' );
-			assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 16, 20, 25, 30 ], 'insert - time (discrete)' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (discrete)' );
-
-			track = createTrack();
-			index = AnimationUtils.insertKeyframe( track, 100 );
-			assert.equal( index, 6, 'append - index' );
-			assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 100 ], 'append time' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0, 5, 0 ], 'append value' );
-
-			track = createTrack();
-			index = AnimationUtils.insertKeyframe( track, 15 );
-			assert.equal( index, 2, 'existing - index' );
-			assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'existing - time' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'existing - value' );
-
-			track = createTrack();
-			index = AnimationUtils.insertKeyframe( track, 20.000005 );
-			assert.equal( index, 3, 'tolerance - index' );
-			assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'tolerance - time' );
-			assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'tolerance - value' );
-
-		} );
-
-		QUnit.test( 'mergeMorphTargetTracks', ( assert ) => {
-
-			var trackA = new NumberKeyframeTrack(
-				'foo.morphTargetInfluences[a]',
-				[ 5, 10, 15, 20, 25, 30 ],
-				[ 0, 0.2, 0.4, 0.6, 0.8, 1.0 ],
-				InterpolateLinear
-			);
-
-			var trackB = new NumberKeyframeTrack(
-				'foo.morphTargetInfluences[b]',
-				[ 10, 50 ],
-				[ 0.25, 0.75 ],
-				InterpolateLinear
-			);
-
-			var geometry = new BufferGeometry();
-			var position = new BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
-			geometry.addAttribute( 'position',  position );
-			geometry.morphAttributes.position = [ position, position ];
-
-			var mesh = new Mesh( geometry );
-			mesh.name = 'foo';
-			mesh.morphTargetDictionary.a = 0;
-			mesh.morphTargetDictionary.b = 1;
-
-			var root = new Object3D();
-			root.add( mesh );
-
-			var clip = new AnimationClip( 'waltz', undefined, [ trackA, trackB ] );
-			clip = AnimationUtils.mergeMorphTargetTracks( clip, root );
-
-			assert.equal( clip.tracks.length, 1, 'tracks are merged' );
-
-			var track = clip.tracks[ 0 ];
-
-			assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 50 ], 'all keyframes are present' );
-
-			var expectedValues = [ 0, 0.25, 0.2, 0.25, 0.4, 0.3125, 0.6, 0.375, 0.8, 0.4375, 1.0, 0.5, 1.0, 0.75 ];
-
-			for ( var i = 0; i < track.values.length; i ++ ) {
-
-				assert.numEqual( track.values[ i ], expectedValues[ i ], 'all values are merged or interpolated - ' + i );
-
-			}
-
-		} );
 
 		QUnit.todo( "arraySlice", ( assert ) => {