Explorar o código

Animation: Add BezierInterpolant (#32829)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
mrdoob hai 1 mes
pai
achega
f698e5b2f3

+ 1 - 0
src/Three.Core.js

@@ -111,6 +111,7 @@ export { QuaternionLinearInterpolant } from './math/interpolants/QuaternionLinea
 export { LinearInterpolant } from './math/interpolants/LinearInterpolant.js';
 export { DiscreteInterpolant } from './math/interpolants/DiscreteInterpolant.js';
 export { CubicInterpolant } from './math/interpolants/CubicInterpolant.js';
+export { BezierInterpolant } from './math/interpolants/BezierInterpolant.js';
 export { Interpolant } from './math/Interpolant.js';
 export { Triangle } from './math/Triangle.js';
 export { MathUtils } from './math/MathUtils.js';

+ 44 - 5
src/animation/KeyframeTrack.js

@@ -1,11 +1,13 @@
 import {
 	InterpolateLinear,
 	InterpolateSmooth,
-	InterpolateDiscrete
+	InterpolateDiscrete,
+	InterpolateBezier
 } from '../constants.js';
 import { CubicInterpolant } from '../math/interpolants/CubicInterpolant.js';
 import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
 import { DiscreteInterpolant } from '../math/interpolants/DiscreteInterpolant.js';
+import { BezierInterpolant } from '../math/interpolants/BezierInterpolant.js';
 import * as AnimationUtils from './AnimationUtils.js';
 import { warn, error } from '../utils.js';
 
@@ -22,7 +24,7 @@ class KeyframeTrack {
 	 * @param {string} name - The keyframe track's name.
 	 * @param {Array<number>} times - A list of keyframe times.
 	 * @param {Array<number|string|boolean>} values - A list of keyframe values.
-	 * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type.
+	 * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth|InterpolateBezier)} [interpolation] - The interpolation type.
 	 */
 	constructor( name, times, values, interpolation ) {
 
@@ -140,10 +142,37 @@ class KeyframeTrack {
 
 	}
 
+	/**
+	 * Factory method for creating a new Bezier interpolant.
+	 *
+	 * The Bezier interpolant requires tangent data to be set via the `settings` property
+	 * on the track before creating the interpolant. The settings should contain:
+	 * - `inTangents`: Float32Array with [time, value] pairs per keyframe per component
+	 * - `outTangents`: Float32Array with [time, value] pairs per keyframe per component
+	 *
+	 * @static
+	 * @param {TypedArray} [result] - The result buffer.
+	 * @return {BezierInterpolant} The new interpolant.
+	 */
+	InterpolantFactoryMethodBezier( result ) {
+
+		const interpolant = new BezierInterpolant( this.times, this.values, this.getValueSize(), result );
+
+		// Pass tangent data from track settings to interpolant
+		if ( this.settings ) {
+
+			interpolant.settings = this.settings;
+
+		}
+
+		return interpolant;
+
+	}
+
 	/**
 	 * Defines the interpolation factor method for this keyframe track.
 	 *
-	 * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type.
+	 * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth|InterpolateBezier)} interpolation - The interpolation type.
 	 * @return {KeyframeTrack} A reference to this keyframe track.
 	 */
 	setInterpolation( interpolation ) {
@@ -170,6 +199,12 @@ class KeyframeTrack {
 
 				break;
 
+			case InterpolateBezier:
+
+				factoryMethod = this.InterpolantFactoryMethodBezier;
+
+				break;
+
 		}
 
 		if ( factoryMethod === undefined ) {
@@ -206,7 +241,7 @@ class KeyframeTrack {
 	/**
 	 * Returns the current interpolation type.
 	 *
-	 * @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type.
+	 * @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth|InterpolateBezier)} The interpolation type.
 	 */
 	getInterpolation() {
 
@@ -224,6 +259,10 @@ class KeyframeTrack {
 
 				return InterpolateSmooth;
 
+			case this.InterpolantFactoryMethodBezier:
+
+				return InterpolateBezier;
+
 		}
 
 	}
@@ -589,7 +628,7 @@ KeyframeTrack.prototype.ValueBufferType = Float32Array;
 /**
  * The default interpolation type of this keyframe track.
  *
- * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)}
+ * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth|InterpolateBezier)}
  * @default InterpolateLinear
  */
 KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;

+ 11 - 0
src/constants.js

@@ -1165,6 +1165,17 @@ export const InterpolateLinear = 2301;
  */
 export const InterpolateSmooth = 2302;
 
+/**
+ * Bezier interpolation mode for keyframe tracks.
+ *
+ * Uses cubic Bezier curves with explicit 2D control points.
+ * Requires tangent data to be set on the track.
+ *
+ * @type {number}
+ * @constant
+ */
+export const InterpolateBezier = 2303;
+
 /**
  * Zero curvature ending for animations.
  *

+ 108 - 0
src/math/interpolants/BezierInterpolant.js

@@ -0,0 +1,108 @@
+import { Interpolant } from '../Interpolant.js';
+
+/**
+ * A Bezier interpolant using cubic Bezier curves with 2D control points.
+ *
+ * This interpolant supports the COLLADA/Maya style of Bezier animation where
+ * each keyframe has explicit in/out tangent control points specified as
+ * 2D coordinates (time, value).
+ *
+ * The tangent data must be provided via the `settings` object:
+ * - `settings.inTangents`: Float32Array with [time, value] pairs per keyframe per component
+ * - `settings.outTangents`: Float32Array with [time, value] pairs per keyframe per component
+ *
+ * For a track with N keyframes and stride S:
+ * - Each tangent array has N * S * 2 values
+ * - Layout: [k0_c0_time, k0_c0_value, k0_c1_time, k0_c1_value, ..., k0_cS_time, k0_cS_value,
+ *            k1_c0_time, k1_c0_value, ...]
+ *
+ * @augments Interpolant
+ */
+class BezierInterpolant extends Interpolant {
+
+	interpolate_( i1, t0, t, t1 ) {
+
+		const result = this.resultBuffer;
+		const values = this.sampleValues;
+		const stride = this.valueSize;
+
+		const offset1 = i1 * stride;
+		const offset0 = offset1 - stride;
+
+		const settings = this.settings || this.DefaultSettings_;
+		const inTangents = settings.inTangents;
+		const outTangents = settings.outTangents;
+
+		// If no tangent data, fall back to linear interpolation
+		if ( ! inTangents || ! outTangents ) {
+
+			const weight1 = ( t - t0 ) / ( t1 - t0 );
+			const weight0 = 1 - weight1;
+
+			for ( let i = 0; i !== stride; ++ i ) {
+
+				result[ i ] = values[ offset0 + i ] * weight0 + values[ offset1 + i ] * weight1;
+
+			}
+
+			return result;
+
+		}
+
+		const tangentStride = stride * 2;
+		const i0 = i1 - 1;
+
+		for ( let i = 0; i !== stride; ++ i ) {
+
+			const v0 = values[ offset0 + i ];
+			const v1 = values[ offset1 + i ];
+
+			// outTangent of previous keyframe (C0)
+			const outTangentOffset = i0 * tangentStride + i * 2;
+			const c0x = outTangents[ outTangentOffset ];
+			const c0y = outTangents[ outTangentOffset + 1 ];
+
+			// inTangent of current keyframe (C1)
+			const inTangentOffset = i1 * tangentStride + i * 2;
+			const c1x = inTangents[ inTangentOffset ];
+			const c1y = inTangents[ inTangentOffset + 1 ];
+
+			// Solve for Bezier parameter s where Bx(s) = t using Newton-Raphson
+			let s = ( t - t0 ) / ( t1 - t0 );
+			let s2, s3, oneMinusS, oneMinusS2, oneMinusS3;
+
+			for ( let iter = 0; iter < 8; iter ++ ) {
+
+				s2 = s * s;
+				s3 = s2 * s;
+				oneMinusS = 1 - s;
+				oneMinusS2 = oneMinusS * oneMinusS;
+				oneMinusS3 = oneMinusS2 * oneMinusS;
+
+				// Bezier X(s) = (1-s)³·t0 + 3(1-s)²s·c0x + 3(1-s)s²·c1x + s³·t1
+				const bx = oneMinusS3 * t0 + 3 * oneMinusS2 * s * c0x + 3 * oneMinusS * s2 * c1x + s3 * t1;
+
+				const error = bx - t;
+				if ( Math.abs( error ) < 1e-10 ) break;
+
+				// Derivative dX/ds
+				const dbx = 3 * oneMinusS2 * ( c0x - t0 ) + 6 * oneMinusS * s * ( c1x - c0x ) + 3 * s2 * ( t1 - c1x );
+				if ( Math.abs( dbx ) < 1e-10 ) break;
+
+				s = s - error / dbx;
+				s = Math.max( 0, Math.min( 1, s ) );
+
+			}
+
+			// Evaluate Bezier Y(s)
+			result[ i ] = oneMinusS3 * v0 + 3 * oneMinusS2 * s * c0y + 3 * oneMinusS * s2 * c1y + s3 * v1;
+
+		}
+
+		return result;
+
+	}
+
+}
+
+export { BezierInterpolant };

粤ICP备19079148号