AnimationPathHelper.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import {
  2. BufferGeometry,
  3. Float32BufferAttribute,
  4. Line,
  5. LineBasicMaterial,
  6. Object3D,
  7. Points,
  8. PointsMaterial
  9. } from 'three';
  10. /**
  11. * Visualizes the motion path of an animated object based on position keyframes
  12. * from an AnimationClip.
  13. *
  14. * ```js
  15. * const clip = model.animations[ 0 ];
  16. * const helper = new AnimationPathHelper( model, clip, object );
  17. * scene.add( helper );
  18. * ```
  19. *
  20. * @augments Object3D
  21. * @three_import import { AnimationPathHelper } from 'three/addons/helpers/AnimationPathHelper.js';
  22. */
  23. class AnimationPathHelper extends Object3D {
  24. /**
  25. * Constructs a new animation path helper.
  26. *
  27. * @param {Object3D} root - The root object containing the animation clips.
  28. * @param {AnimationClip} clip - The animation clip containing position keyframes.
  29. * @param {Object3D} object - The specific object to show the path for.
  30. * @param {Object} [options={}] - Configuration options.
  31. * @param {number|Color|string} [options.color=0x00ff00] - The path line color.
  32. * @param {number|Color|string} [options.markerColor=0xff0000] - The keyframe marker color.
  33. * @param {number} [options.divisions=100] - Number of samples for smooth path interpolation.
  34. * @param {boolean} [options.showMarkers=true] - Whether to show markers at keyframe positions.
  35. * @param {number} [options.markerSize=5] - Size of keyframe markers in pixels.
  36. */
  37. constructor( root, clip, object, options = {} ) {
  38. super();
  39. const {
  40. color = 0x00ff00,
  41. markerColor = 0xff0000,
  42. divisions = 100,
  43. showMarkers = true,
  44. markerSize = 5
  45. } = options;
  46. /**
  47. * This flag can be used for type testing.
  48. *
  49. * @type {boolean}
  50. * @readonly
  51. * @default true
  52. */
  53. this.isAnimationPathHelper = true;
  54. this.type = 'AnimationPathHelper';
  55. /**
  56. * The root object containing the animation clips.
  57. *
  58. * @type {Object3D}
  59. */
  60. this.root = root;
  61. /**
  62. * The animation clip containing position keyframes.
  63. *
  64. * @type {AnimationClip}
  65. */
  66. this.clip = clip;
  67. /**
  68. * The object whose path is being visualized.
  69. *
  70. * @type {Object3D}
  71. */
  72. this.object = object;
  73. /**
  74. * Number of samples for smooth path interpolation.
  75. *
  76. * @type {number}
  77. * @default 100
  78. */
  79. this.divisions = divisions;
  80. /**
  81. * The position track for the object.
  82. *
  83. * @type {KeyframeTrack|null}
  84. * @private
  85. */
  86. this._track = this._findTrackForObject( object );
  87. if ( this._track === null ) {
  88. console.warn( 'AnimationPathHelper: No position track found for object', object.name );
  89. return;
  90. }
  91. // Create line for path
  92. const lineGeometry = new BufferGeometry();
  93. const lineMaterial = new LineBasicMaterial( {
  94. color: color,
  95. toneMapped: false
  96. } );
  97. /**
  98. * The line representing the animation path.
  99. *
  100. * @type {Line}
  101. */
  102. this.line = new Line( lineGeometry, lineMaterial );
  103. this.add( this.line );
  104. // Create points for keyframe markers
  105. if ( showMarkers ) {
  106. const pointsGeometry = new BufferGeometry();
  107. const pointsMaterial = new PointsMaterial( {
  108. color: markerColor,
  109. size: markerSize,
  110. sizeAttenuation: false,
  111. toneMapped: false
  112. } );
  113. /**
  114. * Points marking keyframe positions.
  115. *
  116. * @type {Points|null}
  117. */
  118. this.points = new Points( pointsGeometry, pointsMaterial );
  119. this.add( this.points );
  120. } else {
  121. this.points = null;
  122. }
  123. // Sync matrix with object's parent
  124. this.matrixAutoUpdate = false;
  125. this._updateGeometry();
  126. }
  127. /**
  128. * Finds the position track for the given object.
  129. *
  130. * @private
  131. * @param {Object3D} object - The object to find the track for.
  132. * @returns {KeyframeTrack|null} The position track, or null if not found.
  133. */
  134. _findTrackForObject( object ) {
  135. const targetName = object.uuid + '.position';
  136. for ( const track of this.clip.tracks ) {
  137. if ( track.name === targetName && track.getValueSize() === 3 ) {
  138. return track;
  139. }
  140. }
  141. return null;
  142. }
  143. /**
  144. * Samples the track at regular intervals.
  145. *
  146. * @private
  147. * @returns {Float32Array} Array of sampled positions.
  148. */
  149. _sampleTrack() {
  150. const track = this._track;
  151. const interpolant = track.createInterpolant();
  152. const duration = this.clip.duration;
  153. const positions = [];
  154. for ( let i = 0; i <= this.divisions; i ++ ) {
  155. const t = ( i / this.divisions ) * duration;
  156. const result = interpolant.evaluate( t );
  157. positions.push( result[ 0 ], result[ 1 ], result[ 2 ] );
  158. }
  159. return new Float32Array( positions );
  160. }
  161. /**
  162. * Updates the geometry with sampled path data.
  163. *
  164. * @private
  165. */
  166. _updateGeometry() {
  167. if ( this._track === null ) return;
  168. // Update line geometry
  169. const sampledPositions = this._sampleTrack();
  170. this.line.geometry.setAttribute( 'position', new Float32BufferAttribute( sampledPositions, 3 ) );
  171. this.line.geometry.computeBoundingSphere();
  172. // Update keyframe markers
  173. if ( this.points !== null ) {
  174. this.points.geometry.setAttribute( 'position', new Float32BufferAttribute( new Float32Array( this._track.values ), 3 ) );
  175. this.points.geometry.computeBoundingSphere();
  176. }
  177. }
  178. /**
  179. * Updates the helper's transform to match the object's parent.
  180. *
  181. * @param {boolean} force - Force matrix update.
  182. */
  183. updateMatrixWorld( force ) {
  184. // Position the helper at the object's parent so the path appears in correct local space
  185. if ( this.object && this.object.parent ) {
  186. this.object.parent.updateWorldMatrix( true, false );
  187. this.matrix.copy( this.object.parent.matrixWorld );
  188. } else {
  189. this.matrix.identity();
  190. }
  191. this.matrixWorld.copy( this.matrix );
  192. // Update children
  193. for ( let i = 0; i < this.children.length; i ++ ) {
  194. this.children[ i ].updateMatrixWorld( force );
  195. }
  196. }
  197. /**
  198. * Sets the path line color.
  199. *
  200. * @param {number|Color|string} color - The new color.
  201. */
  202. setColor( color ) {
  203. if ( this.line ) this.line.material.color.set( color );
  204. }
  205. /**
  206. * Sets the keyframe marker color.
  207. *
  208. * @param {number|Color|string} color - The new color.
  209. */
  210. setMarkerColor( color ) {
  211. if ( this.points ) this.points.material.color.set( color );
  212. }
  213. /**
  214. * Frees the GPU-related resources allocated by this instance.
  215. */
  216. dispose() {
  217. if ( this.line ) {
  218. this.line.geometry.dispose();
  219. this.line.material.dispose();
  220. }
  221. if ( this.points ) {
  222. this.points.geometry.dispose();
  223. this.points.material.dispose();
  224. }
  225. }
  226. }
  227. export { AnimationPathHelper };
粤ICP备19079148号