skeleton.ts 26 KB

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