Browse Source

Moved Interpolant family of classes to math.

tschw 9 years ago
parent
commit
4126288ad3

+ 6 - 0
src/Three.js

@@ -363,6 +363,12 @@ THREE.InterpolateDiscrete = 2300;
 THREE.InterpolateLinear = 2301;
 THREE.InterpolateSmooth = 2302;
 
+// Interpolant ending modes
+
+THREE.ZeroCurvatureEnding = 2400;
+THREE.ZeroSlopeEnding = 2401;
+THREE.WrapAroundEnding = 2402;
+
 // DEPRECATED
 
 THREE.Projector = function () {

+ 4 - 4
src/animation/AnimationAction.js

@@ -162,9 +162,9 @@ THREE.AnimationAction.prototype = {
 
 		var timeScale = this.timeScale;
 
-		if( timeScale.getAt !== undefined ) {
+		if( timeScale.evaluate !== undefined ) {
 
-			return timeScale.getAt( time )[ 0 ];
+			return timeScale.evaluate( time )[ 0 ];
 
 		}
 
@@ -177,9 +177,9 @@ THREE.AnimationAction.prototype = {
 
 		var weight = this.weight;
 
-		if( weight.getAt !== undefined ) {
+		if( weight.evaluate !== undefined ) {
 
-			return weight.getAt( time )[ 0 ];
+			return weight.evaluate( time )[ 0 ];
 
 		}
 

+ 3 - 3
src/animation/AnimationMixer.js

@@ -122,7 +122,7 @@ THREE.AnimationMixer.prototype = {
 
 			++ propertyMixer.referenceCount;
 
-			interpolants[ i ].result = propertyMixer.buffer;
+			interpolants[ i ].resultBuffer = propertyMixer.buffer;
 			actionBindings[ i ] = propertyMixer;
 
 		}
@@ -171,7 +171,7 @@ THREE.AnimationMixer.prototype = {
 			var propertyMixer = actionBindings[ i ];
 			actionBindings[ i ] = null;
 
-			interpolants[ i ].result = null;
+			interpolants[ i ].resultBuffer = null;
 
 			// eventually remove the binding from the array
 			if( -- propertyMixer.referenceCount === 0 ) {
@@ -344,7 +344,7 @@ THREE.AnimationMixer.prototype = {
 
 			for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {
 
-				interpolants[ j ].getAt( actionTime );
+				interpolants[ j ].evaluate( actionTime );
 				propertyMixers[ j ].accumulate( accuIndex, weight );
 
 			}

+ 0 - 226
src/animation/Interpolant.js

@@ -1,226 +0,0 @@
-/**
- *
- * Abstract base class for interpolants over timed sample values.
- * It handles seeking of the interval and boundary cases. Concrete
- * subclasses then implement the actual intepolation by filling in
- * the Template Methods.
- *
- *
- * @author tschw
- */
-
-THREE.Interpolant = function( times, values, stride, result ) {
-
-	this.times = times;
-	this.values = values;
-	this.stride = stride;
-
-	this.result = result;
-
-	this.cachedIndex = 0;
-
-};
-
-THREE.Interpolant.prototype = {
-
-	constructor: THREE.Intepolant,
-
-	getAt: function( time ) {
-
-		var times = this.times;
-		var index = this.cachedIndex;
-
-		var keyTime = times[ index ];
-		var prevKeyTime = times[ index - 1 ];
-
-		validate_interval: {
-
-			seek: for (;;) {
-
-				var right;
-
-//- See http://jsperf.com/comparison-to-undefined/2
-//- slower code:
-//-
-//- 			if ( time >= keyTime || keyTime === undefined ) {
-				if ( ! ( time < keyTime ) ) {
-
-					// linear scan forward
-
-					for ( var giveUpAt = index + 2; ;) {
-
-						if ( keyTime === undefined ) {
-
-							// after end
-
-							index = times.length - 1;
-							this.cachedIndex = index;
-							return this._afterEnd( index, time, prevKeyTime );
-
-						}
-
-						if ( index === giveUpAt ) break;
-
-						prevKeyTime = keyTime;
-						keyTime = times[ ++ index ];
-
-						if ( time < keyTime ) {
-
-							// we have arrived at the sought interval
-							break seek;
-
-						}
-
-					}
-
-					// prepare binary search on the right side of the index
-					right = times.length;
-
-//- slower code:
-//-				} else if ( time < prevKeyTime || prevKeyTime === undefined ) {
-				} else if ( ! ( time >= prevKeyTime ) ) {
-
-					// looping?
-
-					var secondKeyTime = times[ 1 ];
-
-					if ( time < secondKeyTime ) {
-
-						index = 2; // + 1, using the scan for the details
-						prevKeyTime = secondKeyTime;
-
-					}
-
-					// linear reverse scan
-
-					for ( var giveUpAt = index - 2; ;) {
-
-						if ( prevKeyTime === undefined ) {
-
-							// before start
-
-							this.cachedIndex = 0;
-							return this._beforeStart( 0, time, keyTime );
-
-						}
-
-						if ( index === giveUpAt ) break;
-
-						keyTime = prevKeyTime;
-						prevKeyTime = times[ -- index - 1 ];
-
-						if ( time >= prevKeyTime ) {
-
-							// we have arrived at the sought interval
-							break seek;
-
-						}
-
-					}
-
-					// prepare binary search on the left side of the index
-					right = index;
-					index = 0;
-
-				} else {
-
-					// the interval is valid
-
-					break validate_interval;
-
-				}
-
-				// binary search
-
-				while ( index < right ) {
-
-					var mid = ( index + right ) >>> 1;
-
-					if ( time >= times[ mid ] ) {
-
-						index = mid + 1;
-
-					} else {
-
-						right = mid;
-
-					}
-
-				}
-
-				keyTime = times[ index ];
-				prevKeyTime = times[ index - 1 ];
-
-				// check boundary cases, again
-//-				continue;
-
-			} // seek loop
-
-			this.cachedIndex = index;
-
-			this._intervalChanged( index, prevKeyTime, keyTime );
-
-		} // validate_interval block
-
-		return this._interpolate( index, prevKeyTime, time, keyTime );
-
-	},
-
-	parameters: null, // optional, subclass-specific parameter structure
-	// Note: The indirection allows central control of many interpolants.
-
-	DefaultParameters: {},
-
-	// --- Protected interface
-
-	_getParameters: function() {
-
-		return this.parameters || this.DefaultParameters;
-
-	},
-
-	_copyKeyframe: function( index ) {
-
-		// copies the state at a keyframe to the result buffer
-
-		var result = this.result,
-
-			values = this.values,
-			stride = this.stride,
-			offset = index * stride;
-
-		for ( var i = 0; i !== stride; ++ i ) {
-
-			result[ i ] = values[ offset + i ];
-
-		}
-
-		return result;
-
-	},
-
-	// Template methods for derived classes:
-
-	_interpolate: function( i1, t0, t, t1 ) {
-
-		throw new Error( "call to abstract method" );
-
-	},
-
-	_intervalChanged: function( i1, t0, t1 ) {
-
-		// empty
-
-	}
-
-};
-
-Object.assign( THREE.Interpolant.prototype, {
-
-	_beforeStart: //( 0, t, t0 )
-		THREE.Interpolant.prototype._copyKeyframe,
-
-	_afterEnd: //( N-1, tN, t )
-		THREE.Interpolant.prototype._copyKeyframe
-
-} );

+ 1 - 1
src/animation/KeyframeTrack.js

@@ -55,7 +55,7 @@ THREE.KeyframeTrack.prototype = {
 
 	InterpolantFactoryMethodSmooth: function( result ) {
 
-		return new THREE.SmoothInterpolant(
+		return new THREE.CubicInterpolant(
 				this.times, this.values, this.getValueSize(), result );
 
 	},

+ 0 - 25
src/animation/interpolants/DiscreteInterpolant.js

@@ -1,25 +0,0 @@
-/**
- *
- * Interpolant that yields the keyframe value at the start of its interval.
- *
- *
- * @author tschw
- */
-
-THREE.DiscreteInterpolant = function( times, values, stride, result ) {
-
-	THREE.Interpolant.call( this, times, values, stride, result );
-
-};
-
-Object.assign( THREE.DiscreteInterpolant.prototype, THREE.Interpolant.prototype, {
-
-	constructor: THREE.DiscreteInterpolant,
-
-	_interpolate: function( i1, t0, t, t1 ) {
-
-		return this._copyKeyframe( i1 - 1 );
-
-	}
-
-} );

+ 0 - 44
src/animation/interpolants/LinearInterpolant.js

@@ -1,44 +0,0 @@
-/**
- *
- * Interpolant the returns a time-proportional mix of the keyframe values of
- * the surrounding interval.
- *
- *
- * @author tschw
- */
-
-THREE.LinearInterpolant = function( times, values, stride, result ) {
-
-	THREE.Interpolant.call( this, times, values, stride, result );
-
-};
-
-Object.assign( THREE.LinearInterpolant.prototype, THREE.Interpolant.prototype, {
-
-	constructor: THREE.LinearInterpolant,
-
-	_interpolate: function( i1, t0, t, t1 ) {
-
-		var values = this.values,
-			stride = this.stride,
-			result = this.result,
-
-			offset1 = i1 * stride,
-			offset0 = offset1 - stride,
-
-			weight1 = ( t - t0 ) / ( t1 - t0 ),
-			weight0 = 1 - weight1;
-
-		for ( var i = 0; i !== stride; ++ i ) {
-
-			result[ i ] =
-					values[ offset0 + i ] * weight0 +
-					values[ offset1 + i ] * weight1;
-
-		}
-
-		return result;
-
-	}
-
-} );

+ 0 - 40
src/animation/interpolants/SlerpInterpolant.js

@@ -1,40 +0,0 @@
-/**
- *
- * Spherical linear quaternion interpolant.
- *
- *
- * @author tschw
- */
-
-THREE.SlerpInterpolant = function( times, values, stride, result ) {
-
-	THREE.Interpolant.call( this, times, values, stride, result );
-
-};
-
-Object.assign( THREE.SlerpInterpolant.prototype, THREE.Interpolant.prototype, {
-
-	constructor: THREE.SlerpInterpolant,
-
-	_interpolate: function( i1, t0, t, t1 ) {
-
-		var values = this.values,
-			stride = this.stride,
-			result = this.result,
-
-			offset = i1 * stride,
-
-			alpha = ( t - t0 ) / ( t1 - t0 );
-
-		for ( var end = offset + stride; offset !== end; offset += 4 ) {
-
-			THREE.Quaternion.slerpFlat( result, 0,
-					values, offset - stride, values, offset, alpha );
-
-		}
-
-		return result;
-
-	}
-
-} );

+ 0 - 103
src/animation/interpolants/SmoothInterpolant.js

@@ -1,103 +0,0 @@
-/**
- *
- * Cubic hermite spline interpolant.
- *
- *
- * @author tschw
- */
-
-THREE.SmoothInterpolant = function( times, values, stride, result ) {
-
-	THREE.Interpolant.call( this, times, values, stride, result );
-
-};
-
-Object.assign( THREE.SmoothInterpolant.prototype, THREE.Interpolant.prototype, {
-
-	constructor: THREE.SmoothInterpolant,
-
-	DefaultParameters: {
-
-		zeroVelocityAtStart: false,
-		zeroVelocityAtEnd: false
-
-	},
-
-	_intervalChanged: function( i1, t0, t1 ) {
-
-		var times = this.times,
-			iPrev = i1 - 2,
-			iNext = i1 + 1,
-
-			tPrev = times[ iPrev ],
-			tNext = times[ iNext ];
-
-		if ( tPrev === undefined ) {
-
-			iPrev = i1;
-
-			tPrev = this._getParameters().zeroVelocityAtStart ?
-					2 * t0 - t1 : // yields f'(t0) = 0, IOW accelerates
-					t1; // yields f''(t0) = 0, IOW turns into a straight line
-
-		}
-
-		if ( tNext === undefined ) {
-
-			iNext = i1 - 1;
-			tNext = this._getParameters().zeroVelocityAtEnd ?
-					2 * t1 - t0 : // yields f'(tN) = 0, IOW decelerates
-					t0; // yields f''(tN) = 0, IOW turns into a straight line
-
-		}
-
-		var halfDt = ( t1 - t0 ) * 0.5,
-			stride = this.stride;
-
-		this.weightPrev = halfDt / ( t0 - tPrev );
-		this.weightNext = halfDt / ( tNext - t1 );
-		this.offsetPrev = iPrev * stride;
-		this.offsetNext = iNext * stride;
-
-	},
-
-	_interpolate: function( i1, t0, t, t1 ) {
-
-		var times = this.times,
-			values = this.values,
-			stride = this.stride,
-			result = this.result,
-
-			o1 = i1 * stride,		o0 = o1 - stride,
-			oP = this.offsetPrev, 	oN = this.offsetNext,
-
-			wP = this.weightPrev, wN = this.weightNext,
-
-			p = ( t - t0 ) / ( t1 - t0 ),
-			pp = p * p,
-			ppp = pp * p;
-
-		// evaluate polynomials
-
-		var sP = - wP * ppp + 2 * wP * pp  - wP * p;
-		var s0 = ( 1 + wP ) * ppp + ( -1.5 - 2 * wP ) * pp + ( -0.5 + wP ) * p + 1;
-		var s1 = ( -1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;
-		var sN = wN * ppp - wN * pp;
-
-		// mix down
-
-		for ( var i = 0; i !== stride; ++ i ) {
-
-			result[ i ] =
-					sP * values[ oP + i ] +
-					s0 * values[ o0 + i ] +
-					s1 * values[ o1 + i ] +
-					sN * values[ oN + i ];
-
-		}
-
-		return result;
-
-	}
-
-} );

+ 1 - 1
src/animation/tracks/QuaternionKeyframeTrack.js

@@ -25,7 +25,7 @@ Object.assign( THREE.QuaternionKeyframeTrack.prototype, THREE.KeyframeTrack.prot
 
 	InterpolantFactoryMethodLinear: function( result ) {
 
-		return new THREE.SlerpInterpolant(
+		return new THREE.QuaternionLinearInterpolant(
 				this.times, this.values, this.getValueSize(), result );
 
 	},

+ 257 - 0
src/math/Interpolant.js

@@ -0,0 +1,257 @@
+/**
+ * Abstract base class of interpolants over parametric samples.
+ *
+ * The parameter domain is one dimensional, typically the time or a path
+ * along a curve defined by the data.
+ *
+ * The sample values can have any dimensionality and derived classes may
+ * apply special interpretations to the data.
+ *
+ * This class provides the interval seek in a Template Method, deferring
+ * the actual interpolation to derived classes.
+ *
+ * Time complexity is O(1) for linear access crossing at most two points
+ * and O(log N) for random access, where N is the number of positions.
+ *
+ * References:
+ *
+ * 		http://www.oodesign.com/template-method-pattern.html
+ *
+ * @author tschw
+ */
+
+THREE.Interpolant = function(
+		parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+	this.parameterPositions = parameterPositions;
+	this._cachedIndex = 0;
+
+	this.resultBuffer = resultBuffer !== undefined ?
+			resultBuffer : new sampleValues.constructor( sampleSize );
+	this.sampleValues = sampleValues;
+	this.valueSize = sampleSize;
+
+};
+
+THREE.Interpolant.prototype = {
+
+	constructor: THREE.Intepolant,
+
+	evaluate: function( t ) {
+
+		var pp = this.parameterPositions,
+			i1 = this._cachedIndex,
+
+			t1 = pp[   i1   ],
+			t0 = pp[ i1 - 1 ];
+
+		validate_interval: {
+
+			seek: {
+
+				var right;
+
+				linear_scan: {
+//- See http://jsperf.com/comparison-to-undefined/3
+//- slower code:
+//-
+//- 				if ( t >= t1 || t1 === undefined ) {
+					forward_scan: if ( ! ( t < t1 ) ) {
+
+						for ( var giveUpAt = i1 + 2; ;) {
+
+							if ( t1 === undefined ) {
+
+								if ( t < t0 ) break forward_scan;
+
+								// after end
+
+								i1 = pp.length;
+								this._cachedIndex = i1;
+								return this.afterEnd_( i1 - 1, t, t0 );
+
+							}
+
+							if ( i1 === giveUpAt ) break; // this loop
+
+							t0 = t1;
+							t1 = pp[ ++ i1 ];
+
+							if ( t < t1 ) {
+
+								// we have arrived at the sought interval
+								break seek;
+
+							}
+
+						}
+
+						// prepare binary search on the right side of the index
+						right = pp.length;
+						break linear_scan;
+
+					}
+
+//- slower code:
+//-					if ( t < t0 || t0 === undefined ) {
+					if ( ! ( t >= t0 ) ) {
+
+						// looping?
+
+						var t1global = pp[ 1 ];
+
+						if ( t < t1global ) {
+
+							i1 = 2; // + 1, using the scan for the details
+							t0 = t1global;
+
+						}
+
+						// linear reverse scan
+
+						for ( var giveUpAt = i1 - 2; ;) {
+
+							if ( t0 === undefined ) {
+
+								// before start
+
+								this._cachedIndex = 0;
+								return this.beforeStart_( 0, t, t1 );
+
+							}
+
+							if ( i1 === giveUpAt ) break; // this loop
+
+							t1 = t0;
+							t0 = pp[ -- i1 - 1 ];
+
+							if ( t >= t0 ) {
+
+								// we have arrived at the sought interval
+								break seek;
+
+							}
+
+						}
+
+						// prepare binary search on the left side of the index
+						right = i1;
+						i1 = 0;
+						break linear_scan;
+
+					}
+
+					// the interval is valid
+
+					break validate_interval;
+
+				} // linear scan
+
+				// binary search
+
+				while ( i1 < right ) {
+
+					var mid = ( i1 + right ) >>> 1;
+
+					if ( t < pp[ mid ] ) {
+
+						right = mid;
+
+					} else {
+
+						i1 = mid + 1;
+
+					}
+
+				}
+
+				t1 = pp[   i1   ];
+				t0 = pp[ i1 - 1 ];
+
+				// check boundary cases, again
+
+				if ( t0 === undefined ) {
+
+					this._cachedIndex = 0;
+					return this.beforeStart_( 0, t, t1 );
+
+				}
+
+				if ( t1 === undefined ) {
+
+					i1 = pp.length;
+					this._cachedIndex = i1;
+					return this.afterEnd_( i1 - 1, t0, t );
+
+				}
+
+			} // seek
+
+			this._cachedIndex = i1;
+
+			this.intervalChanged_( i1, t0, t1 );
+
+		} // validate_interval
+
+		return this.interpolate_( i1, t0, t, t1 );
+
+	},
+
+	settings: null, // optional, subclass-specific settings structure
+	// Note: The indirection allows central control of many interpolants.
+
+	// --- Protected interface
+
+	DefaultSettings_: {},
+
+	getSettings_: function() {
+
+		return this.settings || this.DefaultSettings_;
+
+	},
+
+	copySampleValue_: function( index ) {
+
+		// copies a sample value to the result buffer
+
+		var result = this.resultBuffer,
+			values = this.sampleValues,
+			stride = this.valueSize,
+			offset = index * stride;
+
+		for ( var i = 0; i !== stride; ++ i ) {
+
+			result[ i ] = values[ offset + i ];
+
+		}
+
+		return result;
+
+	},
+
+	// Template methods for derived classes:
+
+	interpolate_: function( i1, t0, t, t1 ) {
+
+		throw new Error( "call to abstract method" );
+		// implementations shall return this.resultBuffer
+
+	},
+
+	intervalChanged_: function( i1, t0, t1 ) {
+
+		// empty
+
+	}
+
+};
+
+Object.assign( THREE.Interpolant.prototype, {
+
+	beforeStart_: //( 0, t, t0 ), returns this.resultBuffer
+		THREE.Interpolant.prototype.copySampleValue_,
+
+	afterEnd_: //( N-1, tN-1, t ), returns this.resultBuffer
+		THREE.Interpolant.prototype.copySampleValue_
+
+} );

+ 152 - 0
src/math/interpolants/CubicInterpolant.js

@@ -0,0 +1,152 @@
+/**
+ * Fast and simple cubic spline interpolant.
+ *
+ * It was derived from a Hermitian construction setting the first derivative
+ * at each sample position to the linear slope between neighboring positions
+ * over their parameter interval.
+ *
+ * @author tschw
+ */
+
+THREE.CubicInterpolant = function(
+		parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+	THREE.Interpolant.call(
+			this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+	this._weightPrev = -0;
+	this._offsetPrev = -0;
+	this._weightNext = -0;
+	this._offsetNext = -0;
+
+};
+
+THREE.CubicInterpolant.prototype =
+		Object.assign( Object.create( THREE.Interpolant.prototype ), {
+
+	constructor: THREE.CubicInterpolant,
+
+	DefaultSettings_: {
+
+		endingStart: 	THREE.ZeroCurvatureEnding,
+		endingEnd:		THREE.ZeroCurvatureEnding
+
+	},
+
+	intervalChanged_: function( i1, t0, t1 ) {
+
+		var pp = this.parameterPositions,
+			iPrev = i1 - 2,
+			iNext = i1 + 1,
+
+			tPrev = pp[ iPrev ],
+			tNext = pp[ iNext ];
+
+		if ( tPrev === undefined ) {
+
+			switch ( this.getSettings_().endingStart ) {
+
+				case THREE.ZeroSlopeEnding:
+
+					// f'(t0) = 0
+					iPrev = i1;
+					tPrev = 2 * t0 - t1;
+
+					break;
+
+				case THREE.WrapAroundEnding:
+
+					// use the other end of the curve
+					iPrev = pp.length - 2;
+					tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
+
+					break;
+
+				default: // ZeroCurvatureEnding
+
+					// f''(t0) = 0 a.k.a. Natural Spline
+					iPrev = i1;
+					tPrev = t1;
+
+			}
+
+		}
+
+		if ( tNext === undefined ) {
+
+			switch ( this.getSettings_().endingEnd ) {
+
+				case THREE.ZeroSlopeEnding:
+
+					// f'(tN) = 0
+					iNext = i1;
+					tNext = 2 * t1 - t0;
+
+					break;
+
+				case THREE.WrapAroundEnding:
+
+					// use the other end of the curve
+					iNext = 1;
+					tNext = t1 + pp[ 1 ] - pp[ 0 ];
+
+					break;
+
+				default: // ZeroCurvatureEnding
+
+					// f''(tN) = 0, a.k.a. Natural Spline
+					iNext = i1 - 1;
+					tNext = t0;
+
+			}
+
+		}
+
+		var halfDt = ( t1 - t0 ) * 0.5,
+			stride = this.valueSize;
+
+		this._weightPrev = halfDt / ( t0 - tPrev );
+		this._weightNext = halfDt / ( tNext - t1 );
+		this._offsetPrev = iPrev * stride;
+		this._offsetNext = iNext * stride;
+
+	},
+
+	interpolate_: function( i1, t0, t, t1 ) {
+
+		var result = this.resultBuffer,
+			values = this.sampleValues,
+			stride = this.valueSize,
+
+			o1 = i1 * stride,		o0 = o1 - stride,
+			oP = this._offsetPrev, 	oN = this._offsetNext,
+			wP = this._weightPrev,	wN = this._weightNext,
+
+			p = ( t - t0 ) / ( t1 - t0 ),
+			pp = p * p,
+			ppp = pp * p;
+
+		// evaluate polynomials
+
+		var sP =     - wP   * ppp   +         2 * wP    * pp    -          wP   * p;
+		var s0 = ( 1 + wP ) * ppp   + (-1.5 - 2 * wP )  * pp    + ( -0.5 + wP ) * p     + 1;
+		var s1 = (-1 - wN ) * ppp   + ( 1.5 +   wN   )  * pp    +    0.5        * p;
+		var sN =       wN   * ppp   -           wN      * pp;
+
+		// combine data linearly
+
+		for ( var i = 0; i !== stride; ++ i ) {
+
+			result[ i ] =
+					sP * values[ oP + i ] +
+					s0 * values[ o0 + i ] +
+					s1 * values[ o1 + i ] +
+					sN * values[ oN + i ];
+
+		}
+
+		return result;
+
+	}
+
+} );

+ 28 - 0
src/math/interpolants/DiscreteInterpolant.js

@@ -0,0 +1,28 @@
+/**
+ *
+ * Interpolant that evaluates to the sample value at the position preceeding
+ * the parameter.
+ *
+ * @author tschw
+ */
+
+THREE.DiscreteInterpolant = function(
+		parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+	THREE.Interpolant.call(
+			this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+};
+
+THREE.DiscreteInterpolant.prototype =
+		Object.assign( Object.create( THREE.Interpolant.prototype ), {
+
+	constructor: THREE.DiscreteInterpolant,
+
+	interpolate_: function( i1, t0, t, t1 ) {
+
+		return this.copySampleValue_( i1 - 1 );
+
+	}
+
+} );

+ 42 - 0
src/math/interpolants/LinearInterpolant.js

@@ -0,0 +1,42 @@
+/**
+ * @author tschw
+ */
+
+THREE.LinearInterpolant = function(
+		parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+	THREE.Interpolant.call(
+			this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+};
+
+THREE.LinearInterpolant.prototype =
+		Object.assign( Object.create( THREE.Interpolant.prototype ), {
+
+	constructor: THREE.LinearInterpolant,
+
+	interpolate_: function( i1, t0, t, t1 ) {
+
+		var result = this.resultBuffer,
+			values = this.sampleValues,
+			stride = this.valueSize,
+
+			offset1 = i1 * stride,
+			offset0 = offset1 - stride,
+
+			weight1 = ( t - t0 ) / ( t1 - t0 ),
+			weight0 = 1 - weight1;
+
+		for ( var i = 0; i !== stride; ++ i ) {
+
+			result[ i ] =
+					values[ offset0 + i ] * weight0 +
+					values[ offset1 + i ] * weight1;
+
+		}
+
+		return result;
+
+	}
+
+} );

+ 41 - 0
src/math/interpolants/QuaternionLinearInterpolant.js

@@ -0,0 +1,41 @@
+/**
+ * Spherical linear unit quaternion interpolant.
+ *
+ * @author tschw
+ */
+
+THREE.QuaternionLinearInterpolant = function(
+		parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+	THREE.Interpolant.call(
+			this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+};
+
+THREE.QuaternionLinearInterpolant.prototype =
+		Object.assign( Object.create( THREE.Interpolant.prototype ), {
+
+	constructor: THREE.QuaternionLinearInterpolant,
+
+	interpolate_: function( i1, t0, t, t1 ) {
+
+		var result = this.resultBuffer,
+			values = this.sampleValues,
+			stride = this.valueSize,
+
+			offset = i1 * stride,
+
+			alpha = ( t - t0 ) / ( t1 - t0 );
+
+		for ( var end = offset + stride; offset !== end; offset += 4 ) {
+
+			THREE.Quaternion.slerpFlat( result, 0,
+					values, offset - stride, values, offset, alpha );
+
+		}
+
+		return result;
+
+	}
+
+} );

+ 5 - 5
utils/build/includes/common.json

@@ -18,6 +18,11 @@
 	"src/math/Math.js",
 	"src/math/Spline.js",
 	"src/math/Triangle.js",
+	"src/math/Interpolant.js",
+	"src/math/interpolants/CubicInterpolant.js",
+	"src/math/interpolants/DiscreteInterpolant.js",
+	"src/math/interpolants/LinearInterpolant.js",
+	"src/math/interpolants/QuaternionLinearInterpolant.js",
 	"src/core/Clock.js",
 	"src/core/EventDispatcher.js",
 	"src/core/Layers.js",
@@ -38,7 +43,6 @@
 	"src/animation/AnimationClip.js",
 	"src/animation/AnimationMixer.js",
 	"src/animation/AnimationUtils.js",
-	"src/animation/Interpolant.js",
 	"src/animation/KeyframeTrack.js",
 	"src/animation/PropertyBinding.js",
 	"src/animation/PropertyMixer.js",
@@ -47,10 +51,6 @@
 	"src/animation/tracks/QuaternionKeyframeTrack.js",
 	"src/animation/tracks/StringKeyframeTrack.js",
 	"src/animation/tracks/VectorKeyframeTrack.js",
-	"src/animation/interpolants/DiscreteInterpolant.js",
-	"src/animation/interpolants/LinearInterpolant.js",
-	"src/animation/interpolants/SlerpInterpolant.js",
-	"src/animation/interpolants/SmoothInterpolant.js",
 	"src/cameras/Camera.js",
 	"src/cameras/CubeCamera.js",
 	"src/cameras/OrthographicCamera.js",

+ 6 - 1
utils/build/includes/math.json

@@ -17,5 +17,10 @@
 	"src/math/Plane.js",
 	"src/math/Math.js",
 	"src/math/Spline.js",
-	"src/math/Triangle.js"
+	"src/math/Triangle.js",
+	"src/math/Interpolant.js",
+	"src/math/interpolants/CubicInterpolant.js",
+	"src/math/interpolants/DiscreteInterpolant.js",
+	"src/math/interpolants/LinearInterpolant.js",
+	"src/math/interpolants/QuaternionLinearInterpolant.js"
 ]