<!-- NEW5 — TEST 5 · THE BEND AT THE HEART · part 221 -->
ents[ outTangentOffset + 1 ];

			// inTangent of current keyframe (C1)
			const inTangentOffset = i1 * tangentStride + i * 2;
			const c1x = inTangents[ inTangentOffset ];
			const c1y = inTangents[ inTangentOffset + 1 ];

			// Find the curve parameter s where the Bezier X(s) matches t, then evaluate Y(s)
			const s = solveBezierParameter( t, t0, c0x, c1x, t1 );

			result[ i ] = cubicBezier( s, v0, c0y, c1y, v1 );

		}

		return result;

	}

}

function cubicBezier( s, p0, p1, p2, p3 ) {

	const k = 1 - s;

	return k * k * k * p0 + 3 * k * k * s * p1 + 3 * k * s * s * p2 + s * s * s * p3;

}

function cubicBezierSlope( s, p0, p1, p2, p3 ) {

	const k = 1 - s;

	return 3 * k * k * ( p1 - p0 ) + 6 * k * s * ( p2 - p1 ) + 3 * s * s * ( p3 - p2 );

}

// Solves cubicBezier( s, x0, x1, x2, x3 ) = x for s in [0,1] using Newton-Raphson

function solveBezierParameter( x, x0, x1, x2, x3 ) {

	let s = ( x - x0 ) / ( x3 - x0 );

	for ( let i = 0; i < 8; i ++ ) {

		const error = cubicBezier( s, x0, x1, x2, x3 ) - x;
		if ( Math.abs( error ) < 1e-10 ) break;

		const slope = cubicBezierSlope( s, x0, x1, x2, x3 );
		if ( Math.abs( slope ) < 1e-10 ) break;

		s = Math.max( 0, Math.min( 1, s - error / slope ) );

	}

	return s;

}

/**
 * Represents a timed sequence of keyframes, which are composed of lists of
 * times and related values, and which are used to animate a specific property
 * of an object.
 */
class KeyframeTrack {

	/**
	 * Constructs a new keyframe track.
	 *
	 * @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|InterpolateBezier)} [interpolation] - The interpolation type.
	 */
	constructor( name, times, values, interpolation ) {

		if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
		if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );

		/**
		 * The track's name can refer to morph targets or bones or
		 * possibly other values within an animated object. See {@link PropertyBinding#parseTrackName}
		 * for the forms of strings that can be parsed for property binding.
		 *
		 * @type {string}
		 */
		this.name = name;

		/**
		 * The keyframe times.
		 *
		 * @type {Float32Array}
		 */
		this.times = convertArray( times, this.TimeBufferType );

		/**
		 * The keyframe values.
		 *
		 * @type {Float32Array}
		 */
		this.values = convertArray( values, this.ValueBufferType );

		this.setInterpolation( interpolation || this.DefaultInterpolation );

	}

	/**
	 * Converts the keyframe track to JSON.
	 *
	 * @static
	 * @param {KeyframeTrack} track - The keyframe track to serialize.
	 * @return {Object} The serialized keyframe track as JSON.
	 */
	static toJSON( track ) {

		const trackType = track.constructor;

		let json;

		// derived classes can define a static toJSON method
		if ( trackType.toJSON !== this.toJSON ) {

			json = trackType.toJSON( track );

		} else {

			// by default, we assume the data can be serialized as-is
			json = {

				'name': track.name,
				'times': convertArray( track.times, Array ),
				'values': convertArray( track.values, Array )

			};

			const interpolation = track.getInterpolation();

			if ( interpolation !== track.DefaultInterpolation ) {

				json.interpolation = interpolation;

			}

			if ( hasTangents( track.settings ) ) {

				json.settings = {
					inTangents: convertArray( track.settings.inTangents, Array ),
					outTangents: convertArray( track.settings.outTangents, Array )
				};

			}

		}

		json.type = track.ValueTypeName; // mandatory

		return json;

	}

	/**
	 * Factory method for creating a new discrete interpolant.
	 *
	 * @static
	 * @param {TypedArray} [result] - The result buffer.
	 * @return {DiscreteInterpolant} The new interpolant.
	 */
	InterpolantFactoryMethodDiscrete( result ) {

		return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );

	}

	/**
	 * Factory method for creating a new linear interpolant.
	 *
	 * @static
	 * @param {TypedArray} [result] - The result buffer.
	 * @return {LinearInterpolant} The new interpolant.
	 */
	InterpolantFactoryMethodLinear( result ) {

		return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );

	}

	/**
	 * Factory method for creating a new smooth interpolant.
	 *
	 * @static
	 * @param {TypedArray} [result] - The result buffer.
	 * @return {CubicInterpolant} The new interpolant.
	 */
	InterpolantFactoryMethodSmooth( result ) {

		return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );

	}

	/**
	 * Factory method for creating a new Bezier interpolant.
	 *
	 * The Bezier interpolant requires tangent data to be set via the `settings` proper