SkinnedMesh.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import { Mesh } from './Mesh.js';
  2. import { Box3 } from '../math/Box3.js';
  3. import { Matrix4 } from '../math/Matrix4.js';
  4. import { Sphere } from '../math/Sphere.js';
  5. import { Vector3 } from '../math/Vector3.js';
  6. import { Vector4 } from '../math/Vector4.js';
  7. import { Ray } from '../math/Ray.js';
  8. import { AttachedBindMode, DetachedBindMode } from '../constants.js';
  9. import { warn } from '../utils.js';
  10. const _baseVector = /*@__PURE__*/ new Vector4();
  11. const _skinIndex = /*@__PURE__*/ new Vector4();
  12. const _skinWeight = /*@__PURE__*/ new Vector4();
  13. const _vector4 = /*@__PURE__*/ new Vector4();
  14. const _matrix4 = /*@__PURE__*/ new Matrix4();
  15. const _vertex = /*@__PURE__*/ new Vector3();
  16. const _sphere = /*@__PURE__*/ new Sphere();
  17. const _inverseMatrix = /*@__PURE__*/ new Matrix4();
  18. const _ray = /*@__PURE__*/ new Ray();
  19. /**
  20. * A mesh that has a {@link Skeleton} that can then be used to animate the
  21. * vertices of the geometry with skinning/skeleton animation.
  22. *
  23. * Next to a valid skeleton, the skinned mesh requires skin indices and weights
  24. * as buffer attributes in its geometry. These attribute define which bones affect a single
  25. * vertex to a certain extend.
  26. *
  27. * Typically skinned meshes are not created manually but loaders like {@link GLTFLoader}
  28. * or {@link FBXLoader } import respective models.
  29. *
  30. * @augments Mesh
  31. * @demo scenes/bones-browser.html
  32. */
  33. class SkinnedMesh extends Mesh {
  34. /**
  35. * Constructs a new skinned mesh.
  36. *
  37. * @param {BufferGeometry} [geometry] - The mesh geometry.
  38. * @param {Material|Array<Material>} [material] - The mesh material.
  39. */
  40. constructor( geometry, material ) {
  41. super( geometry, material );
  42. /**
  43. * This flag can be used for type testing.
  44. *
  45. * @type {boolean}
  46. * @readonly
  47. * @default true
  48. */
  49. this.isSkinnedMesh = true;
  50. this.type = 'SkinnedMesh';
  51. /**
  52. * `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton.
  53. * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton
  54. * across multiple skinned meshes.
  55. *
  56. * @type {(AttachedBindMode|DetachedBindMode)}
  57. * @default AttachedBindMode
  58. */
  59. this.bindMode = AttachedBindMode;
  60. /**
  61. * The base matrix that is used for the bound bone transforms.
  62. *
  63. * @type {Matrix4}
  64. */
  65. this.bindMatrix = new Matrix4();
  66. /**
  67. * The base matrix that is used for resetting the bound bone transforms.
  68. *
  69. * @type {Matrix4}
  70. */
  71. this.bindMatrixInverse = new Matrix4();
  72. /**
  73. * The bounding box of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingBox}.
  74. *
  75. * @type {?Box3}
  76. * @default null
  77. */
  78. this.boundingBox = null;
  79. /**
  80. * The bounding sphere of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingSphere}.
  81. *
  82. * @type {?Sphere}
  83. * @default null
  84. */
  85. this.boundingSphere = null;
  86. }
  87. /**
  88. * Computes the bounding box of the skinned mesh, and updates {@link SkinnedMesh#boundingBox}.
  89. * The bounding box is not automatically computed by the engine; this method must be called by your app.
  90. * If the skinned mesh is animated, the bounding box should be recomputed per frame in order to reflect
  91. * the current animation state.
  92. */
  93. computeBoundingBox() {
  94. const geometry = this.geometry;
  95. if ( this.boundingBox === null ) {
  96. this.boundingBox = new Box3();
  97. }
  98. this.boundingBox.makeEmpty();
  99. const positionAttribute = geometry.getAttribute( 'position' );
  100. for ( let i = 0; i < positionAttribute.count; i ++ ) {
  101. this.getVertexPosition( i, _vertex );
  102. this.boundingBox.expandByPoint( _vertex );
  103. }
  104. }
  105. /**
  106. * Computes the bounding sphere of the skinned mesh, and updates {@link SkinnedMesh#boundingSphere}.
  107. * The bounding sphere is automatically computed by the engine once when it is needed, e.g., for ray casting
  108. * and view frustum culling. If the skinned mesh is animated, the bounding sphere should be recomputed
  109. * per frame in order to reflect the current animation state.
  110. */
  111. computeBoundingSphere() {
  112. const geometry = this.geometry;
  113. if ( this.boundingSphere === null ) {
  114. this.boundingSphere = new Sphere();
  115. }
  116. this.boundingSphere.makeEmpty();
  117. const positionAttribute = geometry.getAttribute( 'position' );
  118. for ( let i = 0; i < positionAttribute.count; i ++ ) {
  119. this.getVertexPosition( i, _vertex );
  120. this.boundingSphere.expandByPoint( _vertex );
  121. }
  122. }
  123. copy( source, recursive ) {
  124. super.copy( source, recursive );
  125. this.bindMode = source.bindMode;
  126. this.bindMatrix.copy( source.bindMatrix );
  127. this.bindMatrixInverse.copy( source.bindMatrixInverse );
  128. this.skeleton = source.skeleton;
  129. if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone();
  130. if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone();
  131. return this;
  132. }
  133. raycast( raycaster, intersects ) {
  134. const material = this.material;
  135. const matrixWorld = this.matrixWorld;
  136. if ( material === undefined ) return;
  137. // test with bounding sphere in world space
  138. if ( this.boundingSphere === null ) this.computeBoundingSphere();
  139. _sphere.copy( this.boundingSphere );
  140. _sphere.applyMatrix4( matrixWorld );
  141. if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
  142. // convert ray to local space of skinned mesh
  143. _inverseMatrix.copy( matrixWorld ).invert();
  144. _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
  145. // test with bounding box in local space
  146. if ( this.boundingBox !== null ) {
  147. if ( _ray.intersectsBox( this.boundingBox ) === false ) return;
  148. }
  149. // test for intersections with geometry
  150. this._computeIntersections( raycaster, intersects, _ray );
  151. }
  152. getVertexPosition( index, target ) {
  153. super.getVertexPosition( index, target );
  154. this.applyBoneTransform( index, target );
  155. return target;
  156. }
  157. /**
  158. * Binds the given skeleton to the skinned mesh.
  159. *
  160. * @param {Skeleton} skeleton - The skeleton to bind.
  161. * @param {Matrix4} [bindMatrix] - The bind matrix. If no bind matrix is provided,
  162. * the skinned mesh's world matrix will be used instead.
  163. */
  164. bind( skeleton, bindMatrix ) {
  165. this.skeleton = skeleton;
  166. if ( bindMatrix === undefined ) {
  167. this.updateMatrixWorld( true );
  168. this.skeleton.calculateInverses();
  169. bindMatrix = this.matrixWorld;
  170. }
  171. this.bindMatrix.copy( bindMatrix );
  172. this.bindMatrixInverse.copy( bindMatrix ).invert();
  173. }
  174. /**
  175. * This method sets the skinned mesh in the rest pose).
  176. */
  177. pose() {
  178. this.skeleton.pose();
  179. }
  180. /**
  181. * Normalizes the skin weights which are defined as a buffer attribute
  182. * in the skinned mesh's geometry.
  183. */
  184. normalizeSkinWeights() {
  185. const vector = new Vector4();
  186. const skinWeight = this.geometry.attributes.skinWeight;
  187. for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
  188. vector.fromBufferAttribute( skinWeight, i );
  189. const scale = 1.0 / vector.manhattanLength();
  190. if ( scale !== Infinity ) {
  191. vector.multiplyScalar( scale );
  192. } else {
  193. vector.set( 1, 0, 0, 0 ); // do something reasonable
  194. }
  195. skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
  196. }
  197. }
  198. updateMatrixWorld( force ) {
  199. super.updateMatrixWorld( force );
  200. if ( this.bindMode === AttachedBindMode ) {
  201. this.bindMatrixInverse.copy( this.matrixWorld ).invert();
  202. } else if ( this.bindMode === DetachedBindMode ) {
  203. this.bindMatrixInverse.copy( this.bindMatrix ).invert();
  204. } else {
  205. warn( 'SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
  206. }
  207. }
  208. /**
  209. * Applies the bone transform associated with the given index to the given
  210. * vector. Can be used to transform positions or direction vectors by providing
  211. * a Vector4 with 1 or 0 in the w component respectively. Returns the updated vector.
  212. *
  213. * @param {number} index - The vertex index.
  214. * @param {Vector3|Vector4} target - The target object that is used to store the method's result.
  215. * @return {Vector3|Vector4} The updated vertex attribute data.
  216. */
  217. applyBoneTransform( index, target ) {
  218. const skeleton = this.skeleton;
  219. const geometry = this.geometry;
  220. _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
  221. _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
  222. if ( target.isVector4 ) {
  223. _baseVector.copy( target );
  224. target.set( 0, 0, 0, 0 );
  225. } else {
  226. _baseVector.set( ...target, 1 );
  227. target.set( 0, 0, 0 );
  228. }
  229. _baseVector.applyMatrix4( this.bindMatrix );
  230. for ( let i = 0; i < 4; i ++ ) {
  231. const weight = _skinWeight.getComponent( i );
  232. if ( weight !== 0 ) {
  233. const boneIndex = _skinIndex.getComponent( i );
  234. _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
  235. target.addScaledVector( _vector4.copy( _baseVector ).applyMatrix4( _matrix4 ), weight );
  236. }
  237. }
  238. if ( target.isVector4 ) {
  239. // ensure the homogenous coordinate remains unchanged after vector operations
  240. target.w = _baseVector.w;
  241. }
  242. return target.applyMatrix4( this.bindMatrixInverse );
  243. }
  244. }
  245. export { SkinnedMesh };