Skeleton.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import {
  2. RGBAFormat,
  3. FloatType
  4. } from '../constants.js';
  5. import { Bone } from './Bone.js';
  6. import { Matrix4 } from '../math/Matrix4.js';
  7. import { DataTexture } from '../textures/DataTexture.js';
  8. import { generateUUID } from '../math/MathUtils.js';
  9. import { warn } from '../utils.js';
  10. const _offsetMatrix = /*@__PURE__*/ new Matrix4();
  11. const _identityMatrix = /*@__PURE__*/ new Matrix4();
  12. /**
  13. * Class for representing the armatures in `three.js`. The skeleton
  14. * is defined by a hierarchy of bones.
  15. *
  16. * ```js
  17. * const bones = [];
  18. *
  19. * const shoulder = new THREE.Bone();
  20. * const elbow = new THREE.Bone();
  21. * const hand = new THREE.Bone();
  22. *
  23. * shoulder.add( elbow );
  24. * elbow.add( hand );
  25. *
  26. * bones.push( shoulder , elbow, hand);
  27. *
  28. * shoulder.position.y = -5;
  29. * elbow.position.y = 0;
  30. * hand.position.y = 5;
  31. *
  32. * const armSkeleton = new THREE.Skeleton( bones );
  33. * ```
  34. */
  35. class Skeleton {
  36. /**
  37. * Constructs a new skeleton.
  38. *
  39. * @param {Array<Bone>} [bones] - An array of bones.
  40. * @param {Array<Matrix4>} [boneInverses] - An array of bone inverse matrices.
  41. * If not provided, these matrices will be computed automatically via {@link Skeleton#calculateInverses}.
  42. */
  43. constructor( bones = [], boneInverses = [] ) {
  44. this.uuid = generateUUID();
  45. /**
  46. * An array of bones defining the skeleton.
  47. *
  48. * @type {Array<Bone>}
  49. */
  50. this.bones = bones.slice( 0 );
  51. /**
  52. * An array of bone inverse matrices.
  53. *
  54. * @type {Array<Matrix4>}
  55. */
  56. this.boneInverses = boneInverses;
  57. /**
  58. * An array buffer holding the bone data.
  59. * Input data for {@link Skeleton#boneTexture}.
  60. *
  61. * @type {?Float32Array}
  62. * @default null
  63. */
  64. this.boneMatrices = null;
  65. /**
  66. * An array buffer holding the bone data of the previous frame.
  67. * Required for computing velocity. Maintained in {@link SkinningNode}.
  68. *
  69. * @type {?Float32Array}
  70. * @default null
  71. */
  72. this.previousBoneMatrices = null;
  73. /**
  74. * A texture holding the bone data for use
  75. * in the vertex shader.
  76. *
  77. * @type {?DataTexture}
  78. * @default null
  79. */
  80. this.boneTexture = null;
  81. this.init();
  82. }
  83. /**
  84. * Initializes the skeleton. This method gets automatically called by the constructor
  85. * but depending on how the skeleton is created it might be necessary to call this method
  86. * manually.
  87. */
  88. init() {
  89. const bones = this.bones;
  90. const boneInverses = this.boneInverses;
  91. this.boneMatrices = new Float32Array( bones.length * 16 );
  92. // calculate inverse bone matrices if necessary
  93. if ( boneInverses.length === 0 ) {
  94. this.calculateInverses();
  95. } else {
  96. // handle special case
  97. if ( bones.length !== boneInverses.length ) {
  98. warn( 'Skeleton: Number of inverse bone matrices does not match amount of bones.' );
  99. this.boneInverses = [];
  100. for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
  101. this.boneInverses.push( new Matrix4() );
  102. }
  103. }
  104. }
  105. }
  106. /**
  107. * Computes the bone inverse matrices. This method resets {@link Skeleton#boneInverses}
  108. * and fills it with new matrices.
  109. */
  110. calculateInverses() {
  111. this.boneInverses.length = 0;
  112. for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
  113. const inverse = new Matrix4();
  114. if ( this.bones[ i ] ) {
  115. inverse.copy( this.bones[ i ].matrixWorld ).invert();
  116. }
  117. this.boneInverses.push( inverse );
  118. }
  119. }
  120. /**
  121. * Resets the skeleton to the base pose.
  122. */
  123. pose() {
  124. // recover the bind-time world matrices
  125. for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
  126. const bone = this.bones[ i ];
  127. if ( bone ) {
  128. bone.matrixWorld.copy( this.boneInverses[ i ] ).invert();
  129. }
  130. }
  131. // compute the local matrices, positions, rotations and scales
  132. for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
  133. const bone = this.bones[ i ];
  134. if ( bone ) {
  135. if ( bone.parent && bone.parent.isBone ) {
  136. bone.matrix.copy( bone.parent.matrixWorld ).invert();
  137. bone.matrix.multiply( bone.matrixWorld );
  138. } else {
  139. bone.matrix.copy( bone.matrixWorld );
  140. }
  141. bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
  142. }
  143. }
  144. }
  145. /**
  146. * Resets the skeleton to the base pose.
  147. */
  148. update() {
  149. const bones = this.bones;
  150. const boneInverses = this.boneInverses;
  151. const boneMatrices = this.boneMatrices;
  152. const boneTexture = this.boneTexture;
  153. // flatten bone matrices to array
  154. for ( let i = 0, il = bones.length; i < il; i ++ ) {
  155. // compute the offset between the current and the original transform
  156. const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;
  157. _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );
  158. _offsetMatrix.toArray( boneMatrices, i * 16 );
  159. }
  160. if ( boneTexture !== null ) {
  161. boneTexture.needsUpdate = true;
  162. }
  163. }
  164. /**
  165. * Returns a new skeleton with copied values from this instance.
  166. *
  167. * @return {Skeleton} A clone of this instance.
  168. */
  169. clone() {
  170. return new Skeleton( this.bones, this.boneInverses );
  171. }
  172. /**
  173. * Computes a data texture for passing bone data to the vertex shader.
  174. *
  175. * @return {Skeleton} A reference of this instance.
  176. */
  177. computeBoneTexture() {
  178. // layout (1 matrix = 4 pixels)
  179. // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
  180. // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8)
  181. // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16)
  182. // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32)
  183. // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)
  184. let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix
  185. size = Math.ceil( size / 4 ) * 4;
  186. size = Math.max( size, 4 );
  187. const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
  188. boneMatrices.set( this.boneMatrices ); // copy current values
  189. const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );
  190. boneTexture.needsUpdate = true;
  191. this.boneMatrices = boneMatrices;
  192. this.boneTexture = boneTexture;
  193. return this;
  194. }
  195. /**
  196. * Searches through the skeleton's bone array and returns the first with a
  197. * matching name.
  198. *
  199. * @param {string} name - The name of the bone.
  200. * @return {Bone|undefined} The found bone. `undefined` if no bone has been found.
  201. */
  202. getBoneByName( name ) {
  203. for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
  204. const bone = this.bones[ i ];
  205. if ( bone.name === name ) {
  206. return bone;
  207. }
  208. }
  209. return undefined;
  210. }
  211. /**
  212. * Frees the GPU-related resources allocated by this instance. Call this
  213. * method whenever this instance is no longer used in your app.
  214. */
  215. dispose( ) {
  216. if ( this.boneTexture !== null ) {
  217. this.boneTexture.dispose();
  218. this.boneTexture = null;
  219. }
  220. }
  221. /**
  222. * Setups the skeleton by the given JSON and bones.
  223. *
  224. * @param {Object} json - The skeleton as serialized JSON.
  225. * @param {Object<string, Bone>} bones - An array of bones.
  226. * @return {Skeleton} A reference of this instance.
  227. */
  228. fromJSON( json, bones ) {
  229. this.uuid = json.uuid;
  230. for ( let i = 0, l = json.bones.length; i < l; i ++ ) {
  231. const uuid = json.bones[ i ];
  232. let bone = bones[ uuid ];
  233. if ( bone === undefined ) {
  234. warn( 'Skeleton: No bone found with UUID:', uuid );
  235. bone = new Bone();
  236. }
  237. this.bones.push( bone );
  238. this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) );
  239. }
  240. this.init();
  241. return this;
  242. }
  243. /**
  244. * Serializes the skeleton into JSON.
  245. *
  246. * @return {Object} A JSON object representing the serialized skeleton.
  247. * @see {@link ObjectLoader#parse}
  248. */
  249. toJSON() {
  250. const data = {
  251. metadata: {
  252. version: 4.7,
  253. type: 'Skeleton',
  254. generator: 'Skeleton.toJSON'
  255. },
  256. bones: [],
  257. boneInverses: []
  258. };
  259. data.uuid = this.uuid;
  260. const bones = this.bones;
  261. const boneInverses = this.boneInverses;
  262. for ( let i = 0, l = bones.length; i < l; i ++ ) {
  263. const bone = bones[ i ];
  264. data.bones.push( bone.uuid );
  265. const boneInverse = boneInverses[ i ];
  266. data.boneInverses.push( boneInverse.toArray() );
  267. }
  268. return data;
  269. }
  270. }
  271. export { Skeleton };