babylon.skeleton.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. module BABYLON {
  2. /**
  3. * Class used to handle skinning animations
  4. * @see http://doc.babylonjs.com/how_to/how_to_use_bones_and_skeletons
  5. */
  6. export class Skeleton implements IAnimatable {
  7. /**
  8. * Defines the list of child bones
  9. */
  10. public bones = new Array<Bone>();
  11. /**
  12. * Defines an estimate of the dimension of the skeleton at rest
  13. */
  14. public dimensionsAtRest: Vector3;
  15. /**
  16. * Defines a boolean indicating if the root matrix is provided by meshes or by the current skeleton (this is the default value)
  17. */
  18. public needInitialSkinMatrix = false;
  19. /**
  20. * Defines a mesh that override the matrix used to get the world matrix (null by default).
  21. */
  22. public overrideMesh: Nullable<AbstractMesh> = null;
  23. /**
  24. * Gets the list of animations attached to this skeleton
  25. */
  26. public animations: Array<Animation>;
  27. private _scene: Scene;
  28. private _isDirty = true;
  29. private _transformMatrices: Float32Array;
  30. private _transformMatrixTexture: Nullable<RawTexture>;
  31. private _meshesWithPoseMatrix = new Array<AbstractMesh>();
  32. private _animatables: IAnimatable[];
  33. private _identity = Matrix.Identity();
  34. private _synchronizedWithMesh: AbstractMesh;
  35. private _ranges: { [name: string]: Nullable<AnimationRange> } = {};
  36. private _lastAbsoluteTransformsUpdateId = -1;
  37. private _canUseTextureForBones = false;
  38. /** @hidden */
  39. public _numBonesWithLinkedTransformNode = 0;
  40. /**
  41. * Specifies if the skeleton should be serialized
  42. */
  43. public doNotSerialize = false;
  44. /**
  45. * Gets or sets a boolean indicating that bone matrices should be stored as a texture instead of using shader uniforms (default is true).
  46. * Please note that this option is not available when needInitialSkinMatrix === true or if the hardware does not support it
  47. */
  48. public useTextureToStoreBoneMatrices = true;
  49. private _animationPropertiesOverride: Nullable<AnimationPropertiesOverride> = null;
  50. /**
  51. * Gets or sets the animation properties override
  52. */
  53. public get animationPropertiesOverride(): Nullable<AnimationPropertiesOverride> {
  54. if (!this._animationPropertiesOverride) {
  55. return this._scene.animationPropertiesOverride;
  56. }
  57. return this._animationPropertiesOverride;
  58. }
  59. public set animationPropertiesOverride(value: Nullable<AnimationPropertiesOverride>) {
  60. this._animationPropertiesOverride = value;
  61. }
  62. // Events
  63. /**
  64. * An observable triggered before computing the skeleton's matrices
  65. */
  66. public onBeforeComputeObservable = new Observable<Skeleton>();
  67. /**
  68. * Gets a boolean indicating that the skeleton effectively stores matrices into a texture
  69. */
  70. public get isUsingTextureForMatrices() {
  71. return this.useTextureToStoreBoneMatrices && this._canUseTextureForBones && !this.needInitialSkinMatrix;
  72. }
  73. /**
  74. * Creates a new skeleton
  75. * @param name defines the skeleton name
  76. * @param id defines the skeleton Id
  77. * @param scene defines the hosting scene
  78. */
  79. constructor(
  80. /** defines the skeleton name */
  81. public name: string,
  82. /** defines the skeleton Id */
  83. public id: string, scene: Scene) {
  84. this.bones = [];
  85. this._scene = scene || Engine.LastCreatedScene;
  86. this._scene.skeletons.push(this);
  87. //make sure it will recalculate the matrix next time prepare is called.
  88. this._isDirty = true;
  89. const engineCaps = this._scene.getEngine().getCaps();
  90. this._canUseTextureForBones = engineCaps.textureFloat && engineCaps.maxVertexTextureImageUnits > 0;
  91. }
  92. // Members
  93. /**
  94. * Gets the list of transform matrices to send to shaders (one matrix per bone)
  95. * @param mesh defines the mesh to use to get the root matrix (if needInitialSkinMatrix === true)
  96. * @returns a Float32Array containing matrices data
  97. */
  98. public getTransformMatrices(mesh: AbstractMesh): Float32Array {
  99. if (this.needInitialSkinMatrix && mesh._bonesTransformMatrices) {
  100. return mesh._bonesTransformMatrices;
  101. }
  102. if (!this._transformMatrices) {
  103. this.prepare();
  104. }
  105. return this._transformMatrices;
  106. }
  107. /**
  108. * Gets the list of transform matrices to send to shaders inside a texture (one matrix per bone)
  109. * @returns a raw texture containing the data
  110. */
  111. public getTransformMatrixTexture(): Nullable<RawTexture> {
  112. return this._transformMatrixTexture;
  113. }
  114. /**
  115. * Gets the current hosting scene
  116. * @returns a scene object
  117. */
  118. public getScene(): Scene {
  119. return this._scene;
  120. }
  121. // Methods
  122. /**
  123. * Gets a string representing the current skeleton data
  124. * @param fullDetails defines a boolean indicating if we want a verbose version
  125. * @returns a string representing the current skeleton data
  126. */
  127. public toString(fullDetails?: boolean): string {
  128. var ret = `Name: ${this.name}, nBones: ${this.bones.length}`;
  129. ret += `, nAnimationRanges: ${this._ranges ? Object.keys(this._ranges).length : "none"}`;
  130. if (fullDetails) {
  131. ret += ", Ranges: {";
  132. let first = true;
  133. for (let name in this._ranges) {
  134. if (first) {
  135. ret += ", ";
  136. first = false;
  137. }
  138. ret += name;
  139. }
  140. ret += "}";
  141. }
  142. return ret;
  143. }
  144. /**
  145. * Get bone's index searching by name
  146. * @param name defines bone's name to search for
  147. * @return the indice of the bone. Returns -1 if not found
  148. */
  149. public getBoneIndexByName(name: string): number {
  150. for (var boneIndex = 0, cache = this.bones.length; boneIndex < cache; boneIndex++) {
  151. if (this.bones[boneIndex].name === name) {
  152. return boneIndex;
  153. }
  154. }
  155. return -1;
  156. }
  157. /**
  158. * Creater a new animation range
  159. * @param name defines the name of the range
  160. * @param from defines the start key
  161. * @param to defines the end key
  162. */
  163. public createAnimationRange(name: string, from: number, to: number): void {
  164. // check name not already in use
  165. if (!this._ranges[name]) {
  166. this._ranges[name] = new AnimationRange(name, from, to);
  167. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  168. if (this.bones[i].animations[0]) {
  169. this.bones[i].animations[0].createRange(name, from, to);
  170. }
  171. }
  172. }
  173. }
  174. /**
  175. * Delete a specific animation range
  176. * @param name defines the name of the range
  177. * @param deleteFrames defines if frames must be removed as well
  178. */
  179. public deleteAnimationRange(name: string, deleteFrames = true): void {
  180. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  181. if (this.bones[i].animations[0]) {
  182. this.bones[i].animations[0].deleteRange(name, deleteFrames);
  183. }
  184. }
  185. this._ranges[name] = null; // said much faster than 'delete this._range[name]'
  186. }
  187. /**
  188. * Gets a specific animation range
  189. * @param name defines the name of the range to look for
  190. * @returns the requested animation range or null if not found
  191. */
  192. public getAnimationRange(name: string): Nullable<AnimationRange> {
  193. return this._ranges[name];
  194. }
  195. /**
  196. * Gets the list of all animation ranges defined on this skeleton
  197. * @returns an array
  198. */
  199. public getAnimationRanges(): Nullable<AnimationRange>[] {
  200. var animationRanges: Nullable<AnimationRange>[] = [];
  201. var name: string;
  202. var i: number = 0;
  203. for (name in this._ranges) {
  204. animationRanges[i] = this._ranges[name];
  205. i++;
  206. }
  207. return animationRanges;
  208. }
  209. /**
  210. * Copy animation range from a source skeleton.
  211. * This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
  212. * @param source defines the source skeleton
  213. * @param name defines the name of the range to copy
  214. * @param rescaleAsRequired defines if rescaling must be applied if required
  215. * @returns true if operation was successful
  216. */
  217. public copyAnimationRange(source: Skeleton, name: string, rescaleAsRequired = false): boolean {
  218. if (this._ranges[name] || !source.getAnimationRange(name)) {
  219. return false;
  220. }
  221. var ret = true;
  222. var frameOffset = this._getHighestAnimationFrame() + 1;
  223. // make a dictionary of source skeleton's bones, so exact same order or doublely nested loop is not required
  224. var boneDict: { [key: string]: Bone } = {};
  225. var sourceBones = source.bones;
  226. var nBones: number;
  227. var i: number;
  228. for (i = 0, nBones = sourceBones.length; i < nBones; i++) {
  229. boneDict[sourceBones[i].name] = sourceBones[i];
  230. }
  231. if (this.bones.length !== sourceBones.length) {
  232. Tools.Warn(`copyAnimationRange: this rig has ${this.bones.length} bones, while source as ${sourceBones.length}`);
  233. ret = false;
  234. }
  235. var skelDimensionsRatio = (rescaleAsRequired && this.dimensionsAtRest && source.dimensionsAtRest) ? this.dimensionsAtRest.divide(source.dimensionsAtRest) : null;
  236. for (i = 0, nBones = this.bones.length; i < nBones; i++) {
  237. var boneName = this.bones[i].name;
  238. var sourceBone = boneDict[boneName];
  239. if (sourceBone) {
  240. ret = ret && this.bones[i].copyAnimationRange(sourceBone, name, frameOffset, rescaleAsRequired, skelDimensionsRatio);
  241. } else {
  242. Tools.Warn("copyAnimationRange: not same rig, missing source bone " + boneName);
  243. ret = false;
  244. }
  245. }
  246. // do not call createAnimationRange(), since it also is done to bones, which was already done
  247. var range = source.getAnimationRange(name);
  248. if (range) {
  249. this._ranges[name] = new AnimationRange(name, range.from + frameOffset, range.to + frameOffset);
  250. }
  251. return ret;
  252. }
  253. /**
  254. * Forces the skeleton to go to rest pose
  255. */
  256. public returnToRest(): void {
  257. for (var index = 0; index < this.bones.length; index++) {
  258. this.bones[index].returnToRest();
  259. }
  260. }
  261. private _getHighestAnimationFrame(): number {
  262. var ret = 0;
  263. for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
  264. if (this.bones[i].animations[0]) {
  265. var highest = this.bones[i].animations[0].getHighestFrame();
  266. if (ret < highest) {
  267. ret = highest;
  268. }
  269. }
  270. }
  271. return ret;
  272. }
  273. /**
  274. * Begin a specific animation range
  275. * @param name defines the name of the range to start
  276. * @param loop defines if looping must be turned on (false by default)
  277. * @param speedRatio defines the speed ratio to apply (1 by default)
  278. * @param onAnimationEnd defines a callback which will be called when animation will end
  279. * @returns a new animatable
  280. */
  281. public beginAnimation(name: string, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): Nullable<Animatable> {
  282. var range = this.getAnimationRange(name);
  283. if (!range) {
  284. return null;
  285. }
  286. return this._scene.beginAnimation(this, range.from, range.to, loop, speedRatio, onAnimationEnd);
  287. }
  288. /** @hidden */
  289. public _markAsDirty(): void {
  290. this._isDirty = true;
  291. }
  292. /** @hidden */
  293. public _registerMeshWithPoseMatrix(mesh: AbstractMesh): void {
  294. this._meshesWithPoseMatrix.push(mesh);
  295. }
  296. /** @hidden */
  297. public _unregisterMeshWithPoseMatrix(mesh: AbstractMesh): void {
  298. var index = this._meshesWithPoseMatrix.indexOf(mesh);
  299. if (index > -1) {
  300. this._meshesWithPoseMatrix.splice(index, 1);
  301. }
  302. }
  303. private _computeTransformMatrices(targetMatrix: Float32Array, initialSkinMatrix: Nullable<Matrix>): void {
  304. this.onBeforeComputeObservable.notifyObservers(this);
  305. for (var index = 0; index < this.bones.length; index++) {
  306. var bone = this.bones[index];
  307. var parentBone = bone.getParent();
  308. if (parentBone) {
  309. bone.getLocalMatrix().multiplyToRef(parentBone.getWorldMatrix(), bone.getWorldMatrix());
  310. } else {
  311. if (initialSkinMatrix) {
  312. bone.getLocalMatrix().multiplyToRef(initialSkinMatrix, bone.getWorldMatrix());
  313. } else {
  314. bone.getWorldMatrix().copyFrom(bone.getLocalMatrix());
  315. }
  316. }
  317. if (bone._index !== -1) {
  318. var mappedIndex = bone._index === null ? index : bone._index;
  319. bone.getInvertedAbsoluteTransform().multiplyToArray(bone.getWorldMatrix(), targetMatrix, mappedIndex * 16);
  320. }
  321. }
  322. this._identity.copyToArray(targetMatrix, this.bones.length * 16);
  323. }
  324. /**
  325. * Build all resources required to render a skeleton
  326. */
  327. public prepare(): void {
  328. // Update the local matrix of bones with linked transform nodes.
  329. if (this._numBonesWithLinkedTransformNode > 0) {
  330. for (const bone of this.bones) {
  331. if (bone._linkedTransformNode) {
  332. // Computing the world matrix also computes the local matrix.
  333. bone._linkedTransformNode.computeWorldMatrix();
  334. bone._matrix = bone._linkedTransformNode._localMatrix;
  335. bone.markAsDirty();
  336. }
  337. }
  338. }
  339. if (!this._isDirty) {
  340. return;
  341. }
  342. if (this.needInitialSkinMatrix) {
  343. for (var index = 0; index < this._meshesWithPoseMatrix.length; index++) {
  344. var mesh = this._meshesWithPoseMatrix[index];
  345. var poseMatrix = mesh.getPoseMatrix();
  346. if (!mesh._bonesTransformMatrices || mesh._bonesTransformMatrices.length !== 16 * (this.bones.length + 1)) {
  347. mesh._bonesTransformMatrices = new Float32Array(16 * (this.bones.length + 1));
  348. }
  349. if (this._synchronizedWithMesh !== mesh) {
  350. this._synchronizedWithMesh = mesh;
  351. // Prepare bones
  352. for (var boneIndex = 0; boneIndex < this.bones.length; boneIndex++) {
  353. var bone = this.bones[boneIndex];
  354. if (!bone.getParent()) {
  355. var matrix = bone.getBaseMatrix();
  356. matrix.multiplyToRef(poseMatrix, Tmp.Matrix[1]);
  357. bone._updateDifferenceMatrix(Tmp.Matrix[1]);
  358. }
  359. }
  360. }
  361. this._computeTransformMatrices(mesh._bonesTransformMatrices, poseMatrix);
  362. }
  363. } else {
  364. if (!this._transformMatrices || this._transformMatrices.length !== 16 * (this.bones.length + 1)) {
  365. this._transformMatrices = new Float32Array(16 * (this.bones.length + 1));
  366. if (this.isUsingTextureForMatrices) {
  367. if (this._transformMatrixTexture) {
  368. this._transformMatrixTexture.dispose();
  369. }
  370. this._transformMatrixTexture = RawTexture.CreateRGBATexture(this._transformMatrices, (this.bones.length + 1) * 4, 1, this._scene, false, false, Engine.TEXTURE_NEAREST_SAMPLINGMODE, Engine.TEXTURETYPE_FLOAT);
  371. }
  372. }
  373. this._computeTransformMatrices(this._transformMatrices, null);
  374. if (this.isUsingTextureForMatrices && this._transformMatrixTexture) {
  375. this._transformMatrixTexture.update(this._transformMatrices);
  376. }
  377. }
  378. this._isDirty = false;
  379. this._scene._activeBones.addCount(this.bones.length, false);
  380. }
  381. /**
  382. * Gets the list of animatables currently running for this skeleton
  383. * @returns an array of animatables
  384. */
  385. public getAnimatables(): IAnimatable[] {
  386. if (!this._animatables || this._animatables.length !== this.bones.length) {
  387. this._animatables = [];
  388. for (var index = 0; index < this.bones.length; index++) {
  389. this._animatables.push(this.bones[index]);
  390. }
  391. }
  392. return this._animatables;
  393. }
  394. /**
  395. * Clone the current skeleton
  396. * @param name defines the name of the new skeleton
  397. * @param id defines the id of the enw skeleton
  398. * @returns the new skeleton
  399. */
  400. public clone(name: string, id: string): Skeleton {
  401. var result = new Skeleton(name, id || name, this._scene);
  402. result.needInitialSkinMatrix = this.needInitialSkinMatrix;
  403. for (var index = 0; index < this.bones.length; index++) {
  404. var source = this.bones[index];
  405. var parentBone = null;
  406. let parent = source.getParent();
  407. if (parent) {
  408. var parentIndex = this.bones.indexOf(parent);
  409. parentBone = result.bones[parentIndex];
  410. }
  411. var bone = new Bone(source.name, result, parentBone, source.getBaseMatrix().clone(), source.getRestPose().clone());
  412. Tools.DeepCopy(source.animations, bone.animations);
  413. }
  414. if (this._ranges) {
  415. result._ranges = {};
  416. for (var rangeName in this._ranges) {
  417. let range = this._ranges[rangeName];
  418. if (range) {
  419. result._ranges[rangeName] = range.clone();
  420. }
  421. }
  422. }
  423. this._isDirty = true;
  424. return result;
  425. }
  426. /**
  427. * Enable animation blending for this skeleton
  428. * @param blendingSpeed defines the blending speed to apply
  429. * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
  430. */
  431. public enableBlending(blendingSpeed = 0.01) {
  432. this.bones.forEach((bone) => {
  433. bone.animations.forEach((animation: Animation) => {
  434. animation.enableBlending = true;
  435. animation.blendingSpeed = blendingSpeed;
  436. });
  437. });
  438. }
  439. /**
  440. * Releases all resources associated with the current skeleton
  441. */
  442. public dispose() {
  443. this._meshesWithPoseMatrix = [];
  444. // Animations
  445. this.getScene().stopAnimation(this);
  446. // Remove from scene
  447. this.getScene().removeSkeleton(this);
  448. if (this._transformMatrixTexture) {
  449. this._transformMatrixTexture.dispose();
  450. this._transformMatrixTexture = null;
  451. }
  452. }
  453. /**
  454. * Serialize the skeleton in a JSON object
  455. * @returns a JSON object
  456. */
  457. public serialize(): any {
  458. var serializationObject: any = {};
  459. serializationObject.name = this.name;
  460. serializationObject.id = this.id;
  461. if (this.dimensionsAtRest) {
  462. serializationObject.dimensionsAtRest = this.dimensionsAtRest.asArray();
  463. }
  464. serializationObject.bones = [];
  465. serializationObject.needInitialSkinMatrix = this.needInitialSkinMatrix;
  466. for (var index = 0; index < this.bones.length; index++) {
  467. var bone = this.bones[index];
  468. let parent = bone.getParent();
  469. var serializedBone: any = {
  470. parentBoneIndex: parent ? this.bones.indexOf(parent) : -1,
  471. name: bone.name,
  472. matrix: bone.getBaseMatrix().toArray(),
  473. rest: bone.getRestPose().toArray()
  474. };
  475. serializationObject.bones.push(serializedBone);
  476. if (bone.length) {
  477. serializedBone.length = bone.length;
  478. }
  479. if (bone.metadata) {
  480. serializedBone.metadata = bone.metadata;
  481. }
  482. if (bone.animations && bone.animations.length > 0) {
  483. serializedBone.animation = bone.animations[0].serialize();
  484. }
  485. serializationObject.ranges = [];
  486. for (var name in this._ranges) {
  487. let source = this._ranges[name];
  488. if (!source) {
  489. continue;
  490. }
  491. var range: any = {};
  492. range.name = name;
  493. range.from = source.from;
  494. range.to = source.to;
  495. serializationObject.ranges.push(range);
  496. }
  497. }
  498. return serializationObject;
  499. }
  500. /**
  501. * Creates a new skeleton from serialized data
  502. * @param parsedSkeleton defines the serialized data
  503. * @param scene defines the hosting scene
  504. * @returns a new skeleton
  505. */
  506. public static Parse(parsedSkeleton: any, scene: Scene): Skeleton {
  507. var skeleton = new Skeleton(parsedSkeleton.name, parsedSkeleton.id, scene);
  508. if (parsedSkeleton.dimensionsAtRest) {
  509. skeleton.dimensionsAtRest = Vector3.FromArray(parsedSkeleton.dimensionsAtRest);
  510. }
  511. skeleton.needInitialSkinMatrix = parsedSkeleton.needInitialSkinMatrix;
  512. let index: number;
  513. for (index = 0; index < parsedSkeleton.bones.length; index++) {
  514. var parsedBone = parsedSkeleton.bones[index];
  515. var parentBone = null;
  516. if (parsedBone.parentBoneIndex > -1) {
  517. parentBone = skeleton.bones[parsedBone.parentBoneIndex];
  518. }
  519. var rest: Nullable<Matrix> = parsedBone.rest ? Matrix.FromArray(parsedBone.rest) : null;
  520. var bone = new Bone(parsedBone.name, skeleton, parentBone, Matrix.FromArray(parsedBone.matrix), rest);
  521. if (parsedBone.id !== undefined && parsedBone.id !== null) {
  522. bone.id = parsedBone.id;
  523. }
  524. if (parsedBone.length) {
  525. bone.length = parsedBone.length;
  526. }
  527. if (parsedBone.metadata) {
  528. bone.metadata = parsedBone.metadata;
  529. }
  530. if (parsedBone.animation) {
  531. bone.animations.push(Animation.Parse(parsedBone.animation));
  532. }
  533. }
  534. // placed after bones, so createAnimationRange can cascade down
  535. if (parsedSkeleton.ranges) {
  536. for (index = 0; index < parsedSkeleton.ranges.length; index++) {
  537. var data = parsedSkeleton.ranges[index];
  538. skeleton.createAnimationRange(data.name, data.from, data.to);
  539. }
  540. }
  541. return skeleton;
  542. }
  543. /**
  544. * Compute all node absolute transforms
  545. * @param forceUpdate defines if computation must be done even if cache is up to date
  546. */
  547. public computeAbsoluteTransforms(forceUpdate = false): void {
  548. var renderId = this._scene.getRenderId();
  549. if (this._lastAbsoluteTransformsUpdateId != renderId || forceUpdate) {
  550. this.bones[0].computeAbsoluteTransforms();
  551. this._lastAbsoluteTransformsUpdateId = renderId;
  552. }
  553. }
  554. /**
  555. * Gets the root pose matrix
  556. * @returns a matrix
  557. */
  558. public getPoseMatrix(): Nullable<Matrix> {
  559. var poseMatrix: Nullable<Matrix> = null;
  560. if (this._meshesWithPoseMatrix.length > 0) {
  561. poseMatrix = this._meshesWithPoseMatrix[0].getPoseMatrix();
  562. }
  563. return poseMatrix;
  564. }
  565. /**
  566. * Sorts bones per internal index
  567. */
  568. public sortBones(): void {
  569. var bones = new Array<Bone>();
  570. var visited = new Array<boolean>(this.bones.length);
  571. for (var index = 0; index < this.bones.length; index++) {
  572. this._sortBones(index, bones, visited);
  573. }
  574. this.bones = bones;
  575. }
  576. private _sortBones(index: number, bones: Bone[], visited: boolean[]): void {
  577. if (visited[index]) {
  578. return;
  579. }
  580. visited[index] = true;
  581. var bone = this.bones[index];
  582. if (bone._index === undefined) {
  583. bone._index = index;
  584. }
  585. var parentBone = bone.getParent();
  586. if (parentBone) {
  587. this._sortBones(this.bones.indexOf(parentBone), bones, visited);
  588. }
  589. bones.push(bone);
  590. }
  591. }
  592. }