| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import {
- BufferGeometry,
- Float32BufferAttribute,
- Line,
- LineBasicMaterial,
- Object3D,
- Points,
- PointsMaterial
- } from 'three';
- /**
- * Visualizes the motion path of an animated object based on position keyframes
- * from an AnimationClip.
- *
- * ```js
- * const clip = model.animations[ 0 ];
- * const helper = new AnimationPathHelper( model, clip, object );
- * scene.add( helper );
- * ```
- *
- * @augments Object3D
- * @three_import import { AnimationPathHelper } from 'three/addons/helpers/AnimationPathHelper.js';
- */
- class AnimationPathHelper extends Object3D {
- /**
- * Constructs a new animation path helper.
- *
- * @param {Object3D} root - The root object containing the animation clips.
- * @param {AnimationClip} clip - The animation clip containing position keyframes.
- * @param {Object3D} object - The specific object to show the path for.
- * @param {Object} [options={}] - Configuration options.
- * @param {number|Color|string} [options.color=0x00ff00] - The path line color.
- * @param {number|Color|string} [options.markerColor=0xff0000] - The keyframe marker color.
- * @param {number} [options.divisions=100] - Number of samples for smooth path interpolation.
- * @param {boolean} [options.showMarkers=true] - Whether to show markers at keyframe positions.
- * @param {number} [options.markerSize=5] - Size of keyframe markers in pixels.
- */
- constructor( root, clip, object, options = {} ) {
- super();
- const {
- color = 0x00ff00,
- markerColor = 0xff0000,
- divisions = 100,
- showMarkers = true,
- markerSize = 5
- } = options;
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isAnimationPathHelper = true;
- this.type = 'AnimationPathHelper';
- /**
- * The root object containing the animation clips.
- *
- * @type {Object3D}
- */
- this.root = root;
- /**
- * The animation clip containing position keyframes.
- *
- * @type {AnimationClip}
- */
- this.clip = clip;
- /**
- * The object whose path is being visualized.
- *
- * @type {Object3D}
- */
- this.object = object;
- /**
- * Number of samples for smooth path interpolation.
- *
- * @type {number}
- * @default 100
- */
- this.divisions = divisions;
- /**
- * The position track for the object.
- *
- * @type {KeyframeTrack|null}
- * @private
- */
- this._track = this._findTrackForObject( object );
- if ( this._track === null ) {
- console.warn( 'AnimationPathHelper: No position track found for object', object.name );
- return;
- }
- // Create line for path
- const lineGeometry = new BufferGeometry();
- const lineMaterial = new LineBasicMaterial( {
- color: color,
- toneMapped: false
- } );
- /**
- * The line representing the animation path.
- *
- * @type {Line}
- */
- this.line = new Line( lineGeometry, lineMaterial );
- this.add( this.line );
- // Create points for keyframe markers
- if ( showMarkers ) {
- const pointsGeometry = new BufferGeometry();
- const pointsMaterial = new PointsMaterial( {
- color: markerColor,
- size: markerSize,
- sizeAttenuation: false,
- toneMapped: false
- } );
- /**
- * Points marking keyframe positions.
- *
- * @type {Points|null}
- */
- this.points = new Points( pointsGeometry, pointsMaterial );
- this.add( this.points );
- } else {
- this.points = null;
- }
- // Sync matrix with object's parent
- this.matrixAutoUpdate = false;
- this._updateGeometry();
- }
- /**
- * Finds the position track for the given object.
- *
- * @private
- * @param {Object3D} object - The object to find the track for.
- * @returns {KeyframeTrack|null} The position track, or null if not found.
- */
- _findTrackForObject( object ) {
- const targetName = object.uuid + '.position';
- for ( const track of this.clip.tracks ) {
- if ( track.name === targetName && track.getValueSize() === 3 ) {
- return track;
- }
- }
- return null;
- }
- /**
- * Samples the track at regular intervals.
- *
- * @private
- * @returns {Float32Array} Array of sampled positions.
- */
- _sampleTrack() {
- const track = this._track;
- const interpolant = track.createInterpolant();
- const duration = this.clip.duration;
- const positions = [];
- for ( let i = 0; i <= this.divisions; i ++ ) {
- const t = ( i / this.divisions ) * duration;
- const result = interpolant.evaluate( t );
- positions.push( result[ 0 ], result[ 1 ], result[ 2 ] );
- }
- return new Float32Array( positions );
- }
- /**
- * Updates the geometry with sampled path data.
- *
- * @private
- */
- _updateGeometry() {
- if ( this._track === null ) return;
- // Update line geometry
- const sampledPositions = this._sampleTrack();
- this.line.geometry.setAttribute( 'position', new Float32BufferAttribute( sampledPositions, 3 ) );
- this.line.geometry.computeBoundingSphere();
- // Update keyframe markers
- if ( this.points !== null ) {
- this.points.geometry.setAttribute( 'position', new Float32BufferAttribute( new Float32Array( this._track.values ), 3 ) );
- this.points.geometry.computeBoundingSphere();
- }
- }
- /**
- * Updates the helper's transform to match the object's parent.
- *
- * @param {boolean} force - Force matrix update.
- */
- updateMatrixWorld( force ) {
- // Position the helper at the object's parent so the path appears in correct local space
- if ( this.object && this.object.parent ) {
- this.object.parent.updateWorldMatrix( true, false );
- this.matrix.copy( this.object.parent.matrixWorld );
- } else {
- this.matrix.identity();
- }
- this.matrixWorld.copy( this.matrix );
- // Update children
- for ( let i = 0; i < this.children.length; i ++ ) {
- this.children[ i ].updateMatrixWorld( force );
- }
- }
- /**
- * Sets the path line color.
- *
- * @param {number|Color|string} color - The new color.
- */
- setColor( color ) {
- if ( this.line ) this.line.material.color.set( color );
- }
- /**
- * Sets the keyframe marker color.
- *
- * @param {number|Color|string} color - The new color.
- */
- setMarkerColor( color ) {
- if ( this.points ) this.points.material.color.set( color );
- }
- /**
- * Frees the GPU-related resources allocated by this instance.
- */
- dispose() {
- if ( this.line ) {
- this.line.geometry.dispose();
- this.line.material.dispose();
- }
- if ( this.points ) {
- this.points.geometry.dispose();
- this.points.material.dispose();
- }
- }
- }
- export { AnimationPathHelper };
|