skeleton.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. import { Bone } from "./bone.js";
  2. import { Observable } from "../Misc/observable.js";
  3. import { Vector3, Matrix, TmpVectors } from "../Maths/math.vector.js";
  4. import { RawTexture } from "../Materials/Textures/rawTexture.js";
  5. import { Animation } from "../Animations/animation.js";
  6. import { AnimationRange } from "../Animations/animationRange.js";
  7. import { EngineStore } from "../Engines/engineStore.js";
  8. import { Logger } from "../Misc/logger.js";
  9. import { DeepCopier } from "../Misc/deepCopier.js";
  10. /**
  11. * Class used to handle skinning animations
  12. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons
  13. */
  14. export class Skeleton {
  15. /**
  16. * Gets or sets a boolean indicating that bone matrices should be stored as a texture instead of using shader uniforms (default is true).
  17. * Please note that this option is not available if the hardware does not support it
  18. */
  19. get useTextureToStoreBoneMatrices() {
  20. return this._useTextureToStoreBoneMatrices;
  21. }
  22. set useTextureToStoreBoneMatrices(value) {
  23. this._useTextureToStoreBoneMatrices = value;
  24. this._markAsDirty();
  25. }
  26. /**
  27. * Gets or sets the animation properties override
  28. */
  29. get animationPropertiesOverride() {
  30. if (!this._animationPropertiesOverride) {
  31. return this._scene.animationPropertiesOverride;
  32. }
  33. return this._animationPropertiesOverride;
  34. }
  35. set animationPropertiesOverride(value) {
  36. this._animationPropertiesOverride = value;
  37. }
  38. /**
  39. * Gets a boolean indicating that the skeleton effectively stores matrices into a texture
  40. */
  41. get isUsingTextureForMatrices() {
  42. return this.useTextureToStoreBoneMatrices && this._canUseTextureForBones;
  43. }
  44. /**
  45. * Gets the unique ID of this skeleton
  46. */
  47. get uniqueId() {
  48. return this._uniqueId;
  49. }
  50. /**
  51. * Creates a new skeleton
  52. * @param name defines the skeleton name
  53. * @param id defines the skeleton Id
  54. * @param scene defines the hosting scene
  55. */
  56. constructor(
  57. /** defines the skeleton name */
  58. name,
  59. /** defines the skeleton Id */
  60. id, scene) {
  61. this.name = name;
  62. this.id = id;
  63. /**
  64. * Defines the list of child bones
  65. */
  66. this.bones = [];
  67. /**
  68. * Defines a boolean indicating if the root matrix is provided by meshes or by the current skeleton (this is the default value)
  69. */
  70. this.needInitialSkinMatrix = false;
  71. this._isDirty = true;
  72. this._meshesWithPoseMatrix = new Array();
  73. this._identity = Matrix.Identity();
  74. this._currentRenderId = -1;
  75. this._ranges = {};
  76. this._absoluteTransformIsDirty = true;
  77. this._canUseTextureForBones = false;
  78. this._uniqueId = 0;
  79. /** @internal */
  80. this._numBonesWithLinkedTransformNode = 0;
  81. /** @internal */
  82. this._hasWaitingData = null;
  83. /** @internal */
  84. this._parentContainer = null;
  85. /**
  86. * Specifies if the skeleton should be serialized
  87. */
  88. this.doNotSerialize = false;
  89. this._useTextureToStoreBoneMatrices = true;
  90. this._animationPropertiesOverride = null;
  91. // Events
  92. /**
  93. * An observable triggered before computing the skeleton's matrices
  94. */
  95. this.onBeforeComputeObservable = new Observable();
  96. this.bones = [];
  97. this._scene = scene || EngineStore.LastCreatedScene;
  98. this._uniqueId = this._scene.getUniqueId();
  99. this._scene.addSkeleton(this);
  100. //make sure it will recalculate the matrix next time prepare is called.
  101. this._isDirty = true;
  102. const engineCaps = this._scene.getEngine().getCaps();
  103. this._canUseTextureForBones = engineCaps.textureFloat && engineCaps.maxVertexTextureImageUnits > 0;
  104. }
  105. /**
  106. * Gets the current object class name.
  107. * @returns the class name
  108. */
  109. getClassName() {
  110. return "Skeleton";
  111. }
  112. /**
  113. * Returns an array containing the root bones
  114. * @returns an array containing the root bones
  115. */
  116. getChildren() {
  117. return this.bones.filter((b) => !b.getParent());
  118. }
  119. // Members
  120. /**
  121. * Gets the list of transform matrices to send to shaders (one matrix per bone)
  122. * @param mesh defines the mesh to use to get the root matrix (if needInitialSkinMatrix === true)
  123. * @returns a Float32Array containing matrices data
  124. */
  125. getTransformMatrices(mesh) {
  126. if (this.needInitialSkinMatrix) {
  127. if (!mesh) {
  128. throw new Error("getTransformMatrices: When using the needInitialSkinMatrix flag, a mesh must be provided");
  129. }
  130. if (!mesh._bonesTransformMatrices) {
  131. this.prepare(true);
  132. }
  133. return mesh._bonesTransformMatrices;
  134. }
  135. if (!this._transformMatrices || this._isDirty) {
  136. this.prepare(!this._transformMatrices);
  137. }
  138. return this._transformMatrices;
  139. }
  140. /**
  141. * Gets the list of transform matrices to send to shaders inside a texture (one matrix per bone)
  142. * @param mesh defines the mesh to use to get the root matrix (if needInitialSkinMatrix === true)
  143. * @returns a raw texture containing the data
  144. */
  145. getTransformMatrixTexture(mesh) {
  146. if (this.needInitialSkinMatrix && mesh._transformMatrixTexture) {
  147. return mesh._transformMatrixTexture;
  148. }
  149. return this._transformMatrixTexture;
  150. }
  151. /**
  152. * Gets the current hosting scene
  153. * @returns a scene object
  154. */
  155. getScene() {
  156. return this._scene;
  157. }
  158. // Methods
  159. /**
  160. * Gets a string representing the current skeleton data
  161. * @param fullDetails defines a boolean indicating if we want a verbose version
  162. * @returns a string representing the current skeleton data
  163. */
  164. toString(fullDetails) {
  165. let ret = `Name: ${this.name}, nBones: ${this.bones.length}`;
  166. ret += `, nAnimationRanges: ${this._ranges ? Object.keys(this._ranges).length : "none"}`;
  167. if (fullDetails) {
  168. ret += ", Ranges: {";
  169. let first = true;
  170. for (const name in this._ranges) {
  171. if (first) {
  172. ret += ", ";
  173. first = false;
  174. }
  175. ret += name;
  176. }
  177. ret += "}";
  178. }
  179. return ret;
  180. }
  181. /**
  182. * Get bone's index searching by name
  183. * @param name defines bone's name to search for
  184. * @returns the indice of the bone. Returns -1 if not found
  185. */
  186. getBoneIndexByName(name) {
  187. for (let boneIndex = 0, cache = this.bones.length; boneIndex < cache; boneIndex++) {
  188. if (this.bones[boneIndex].name === name) {
  189. return boneIndex;
  190. }
  191. }
  192. return -1;
  193. }
  194. /**
  195. * Create a new animation range
  196. * @param name defines the name of the range
  197. * @param from defines the start key
  198. * @param to defines the end key
  199. */
  200. createAnimationRange(name, from, to) {
  201. // check name not already in use
  202. if (!this._ranges[name]) {
  203. this._ranges[name] = new AnimationRange(name, from, to);
  204. for (let i = 0, nBones = this.bones.length; i < nBones; i++) {
  205. if (this.bones[i].animations[0]) {
  206. this.bones[i].animations[0].createRange(name, from, to);
  207. }
  208. }
  209. }
  210. }
  211. /**
  212. * Delete a specific animation range
  213. * @param name defines the name of the range
  214. * @param deleteFrames defines if frames must be removed as well
  215. */
  216. deleteAnimationRange(name, deleteFrames = true) {
  217. for (let i = 0, nBones = this.bones.length; i < nBones; i++) {
  218. if (this.bones[i].animations[0]) {
  219. this.bones[i].animations[0].deleteRange(name, deleteFrames);
  220. }
  221. }
  222. this._ranges[name] = null; // said much faster than 'delete this._range[name]'
  223. }
  224. /**
  225. * Gets a specific animation range
  226. * @param name defines the name of the range to look for
  227. * @returns the requested animation range or null if not found
  228. */
  229. getAnimationRange(name) {
  230. return this._ranges[name] || null;
  231. }
  232. /**
  233. * Gets the list of all animation ranges defined on this skeleton
  234. * @returns an array
  235. */
  236. getAnimationRanges() {
  237. const animationRanges = [];
  238. let name;
  239. for (name in this._ranges) {
  240. animationRanges.push(this._ranges[name]);
  241. }
  242. return animationRanges;
  243. }
  244. /**
  245. * Copy animation range from a source skeleton.
  246. * This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
  247. * @param source defines the source skeleton
  248. * @param name defines the name of the range to copy
  249. * @param rescaleAsRequired defines if rescaling must be applied if required
  250. * @returns true if operation was successful
  251. */
  252. copyAnimationRange(source, name, rescaleAsRequired = false) {
  253. if (this._ranges[name] || !source.getAnimationRange(name)) {
  254. return false;
  255. }
  256. let ret = true;
  257. const frameOffset = this._getHighestAnimationFrame() + 1;
  258. // make a dictionary of source skeleton's bones, so exact same order or doubly nested loop is not required
  259. const boneDict = {};
  260. const sourceBones = source.bones;
  261. let nBones;
  262. let i;
  263. for (i = 0, nBones = sourceBones.length; i < nBones; i++) {
  264. boneDict[sourceBones[i].name] = sourceBones[i];
  265. }
  266. if (this.bones.length !== sourceBones.length) {
  267. Logger.Warn(`copyAnimationRange: this rig has ${this.bones.length} bones, while source as ${sourceBones.length}`);
  268. ret = false;
  269. }
  270. const skelDimensionsRatio = rescaleAsRequired && this.dimensionsAtRest && source.dimensionsAtRest ? this.dimensionsAtRest.divide(source.dimensionsAtRest) : null;
  271. for (i = 0, nBones = this.bones.length; i < nBones; i++) {
  272. const boneName = this.bones[i].name;
  273. const sourceBone = boneDict[boneName];
  274. if (sourceBone) {
  275. ret = ret && this.bones[i].copyAnimationRange(sourceBone, name, frameOffset, rescaleAsRequired, skelDimensionsRatio);
  276. }
  277. else {
  278. Logger.Warn("copyAnimationRange: not same rig, missing source bone " + boneName);
  279. ret = false;
  280. }
  281. }
  282. // do not call createAnimationRange(), since it also is done to bones, which was already done
  283. const range = source.getAnimationRange(name);
  284. if (range) {
  285. this._ranges[name] = new AnimationRange(name, range.from + frameOffset, range.to + frameOffset);
  286. }
  287. return ret;
  288. }
  289. /**
  290. * Forces the skeleton to go to rest pose
  291. */
  292. returnToRest() {
  293. for (const bone of this.bones) {
  294. if (bone._index !== -1) {
  295. bone.returnToRest();
  296. }
  297. }
  298. }
  299. _getHighestAnimationFrame() {
  300. let ret = 0;
  301. for (let i = 0, nBones = this.bones.length; i < nBones; i++) {
  302. if (this.bones[i].animations[0]) {
  303. const highest = this.bones[i].animations[0].getHighestFrame();
  304. if (ret < highest) {
  305. ret = highest;
  306. }
  307. }
  308. }
  309. return ret;
  310. }
  311. /**
  312. * Begin a specific animation range
  313. * @param name defines the name of the range to start
  314. * @param loop defines if looping must be turned on (false by default)
  315. * @param speedRatio defines the speed ratio to apply (1 by default)
  316. * @param onAnimationEnd defines a callback which will be called when animation will end
  317. * @returns a new animatable
  318. */
  319. beginAnimation(name, loop, speedRatio, onAnimationEnd) {
  320. const range = this.getAnimationRange(name);
  321. if (!range) {
  322. return null;
  323. }
  324. return this._scene.beginAnimation(this, range.from, range.to, loop, speedRatio, onAnimationEnd);
  325. }
  326. /**
  327. * Convert the keyframes for a range of animation on a skeleton to be relative to a given reference frame.
  328. * @param skeleton defines the Skeleton containing the animation range to convert
  329. * @param referenceFrame defines the frame that keyframes in the range will be relative to
  330. * @param range defines the name of the AnimationRange belonging to the Skeleton to convert
  331. * @returns the original skeleton
  332. */
  333. static MakeAnimationAdditive(skeleton, referenceFrame = 0, range) {
  334. const rangeValue = skeleton.getAnimationRange(range);
  335. // We can't make a range additive if it doesn't exist
  336. if (!rangeValue) {
  337. return null;
  338. }
  339. // Find any current scene-level animatable belonging to the target that matches the range
  340. const sceneAnimatables = skeleton._scene.getAllAnimatablesByTarget(skeleton);
  341. let rangeAnimatable = null;
  342. for (let index = 0; index < sceneAnimatables.length; index++) {
  343. const sceneAnimatable = sceneAnimatables[index];
  344. if (sceneAnimatable.fromFrame === rangeValue?.from && sceneAnimatable.toFrame === rangeValue?.to) {
  345. rangeAnimatable = sceneAnimatable;
  346. break;
  347. }
  348. }
  349. // Convert the animations belonging to the skeleton to additive keyframes
  350. const animatables = skeleton.getAnimatables();
  351. for (let index = 0; index < animatables.length; index++) {
  352. const animatable = animatables[index];
  353. const animations = animatable.animations;
  354. if (!animations) {
  355. continue;
  356. }
  357. for (let animIndex = 0; animIndex < animations.length; animIndex++) {
  358. Animation.MakeAnimationAdditive(animations[animIndex], referenceFrame, range);
  359. }
  360. }
  361. // Mark the scene-level animatable as additive
  362. if (rangeAnimatable) {
  363. rangeAnimatable.isAdditive = true;
  364. }
  365. return skeleton;
  366. }
  367. /** @internal */
  368. _markAsDirty() {
  369. this._isDirty = true;
  370. this._absoluteTransformIsDirty = true;
  371. }
  372. /**
  373. * @internal
  374. */
  375. _registerMeshWithPoseMatrix(mesh) {
  376. this._meshesWithPoseMatrix.push(mesh);
  377. }
  378. /**
  379. * @internal
  380. */
  381. _unregisterMeshWithPoseMatrix(mesh) {
  382. const index = this._meshesWithPoseMatrix.indexOf(mesh);
  383. if (index > -1) {
  384. this._meshesWithPoseMatrix.splice(index, 1);
  385. }
  386. }
  387. _computeTransformMatrices(targetMatrix, initialSkinMatrix) {
  388. this.onBeforeComputeObservable.notifyObservers(this);
  389. for (let index = 0; index < this.bones.length; index++) {
  390. const bone = this.bones[index];
  391. bone._childUpdateId++;
  392. const parentBone = bone.getParent();
  393. if (parentBone) {
  394. bone.getLocalMatrix().multiplyToRef(parentBone.getFinalMatrix(), bone.getFinalMatrix());
  395. }
  396. else {
  397. if (initialSkinMatrix) {
  398. bone.getLocalMatrix().multiplyToRef(initialSkinMatrix, bone.getFinalMatrix());
  399. }
  400. else {
  401. bone.getFinalMatrix().copyFrom(bone.getLocalMatrix());
  402. }
  403. }
  404. if (bone._index !== -1) {
  405. const mappedIndex = bone._index === null ? index : bone._index;
  406. bone.getAbsoluteInverseBindMatrix().multiplyToArray(bone.getFinalMatrix(), targetMatrix, mappedIndex * 16);
  407. }
  408. }
  409. this._identity.copyToArray(targetMatrix, this.bones.length * 16);
  410. }
  411. /**
  412. * Build all resources required to render a skeleton
  413. * @param dontCheckFrameId defines a boolean indicating if prepare should be run without checking first the current frame id (default: false)
  414. */
  415. prepare(dontCheckFrameId = false) {
  416. if (!dontCheckFrameId) {
  417. const currentRenderId = this.getScene().getRenderId();
  418. if (this._currentRenderId === currentRenderId) {
  419. return;
  420. }
  421. this._currentRenderId = currentRenderId;
  422. }
  423. // Update the local matrix of bones with linked transform nodes.
  424. if (this._numBonesWithLinkedTransformNode > 0) {
  425. for (const bone of this.bones) {
  426. if (bone._linkedTransformNode) {
  427. const node = bone._linkedTransformNode;
  428. bone.position = node.position;
  429. if (node.rotationQuaternion) {
  430. bone.rotationQuaternion = node.rotationQuaternion;
  431. }
  432. else {
  433. bone.rotation = node.rotation;
  434. }
  435. bone.scaling = node.scaling;
  436. }
  437. }
  438. }
  439. if (this.needInitialSkinMatrix) {
  440. for (const mesh of this._meshesWithPoseMatrix) {
  441. const poseMatrix = mesh.getPoseMatrix();
  442. let needsUpdate = this._isDirty;
  443. if (!mesh._bonesTransformMatrices || mesh._bonesTransformMatrices.length !== 16 * (this.bones.length + 1)) {
  444. mesh._bonesTransformMatrices = new Float32Array(16 * (this.bones.length + 1));
  445. needsUpdate = true;
  446. }
  447. if (!needsUpdate) {
  448. continue;
  449. }
  450. if (this._synchronizedWithMesh !== mesh) {
  451. this._synchronizedWithMesh = mesh;
  452. // Prepare bones
  453. for (const bone of this.bones) {
  454. if (!bone.getParent()) {
  455. const matrix = bone.getBindMatrix();
  456. matrix.multiplyToRef(poseMatrix, TmpVectors.Matrix[1]);
  457. bone._updateAbsoluteBindMatrices(TmpVectors.Matrix[1]);
  458. }
  459. }
  460. if (this.isUsingTextureForMatrices) {
  461. const textureWidth = (this.bones.length + 1) * 4;
  462. if (!mesh._transformMatrixTexture || mesh._transformMatrixTexture.getSize().width !== textureWidth) {
  463. if (mesh._transformMatrixTexture) {
  464. mesh._transformMatrixTexture.dispose();
  465. }
  466. mesh._transformMatrixTexture = RawTexture.CreateRGBATexture(mesh._bonesTransformMatrices, (this.bones.length + 1) * 4, 1, this._scene, false, false, 1, 1);
  467. }
  468. }
  469. }
  470. this._computeTransformMatrices(mesh._bonesTransformMatrices, poseMatrix);
  471. if (this.isUsingTextureForMatrices && mesh._transformMatrixTexture) {
  472. mesh._transformMatrixTexture.update(mesh._bonesTransformMatrices);
  473. }
  474. }
  475. }
  476. else {
  477. if (!this._isDirty) {
  478. return;
  479. }
  480. if (!this._transformMatrices || this._transformMatrices.length !== 16 * (this.bones.length + 1)) {
  481. this._transformMatrices = new Float32Array(16 * (this.bones.length + 1));
  482. if (this.isUsingTextureForMatrices) {
  483. if (this._transformMatrixTexture) {
  484. this._transformMatrixTexture.dispose();
  485. }
  486. this._transformMatrixTexture = RawTexture.CreateRGBATexture(this._transformMatrices, (this.bones.length + 1) * 4, 1, this._scene, false, false, 1, 1);
  487. }
  488. }
  489. this._computeTransformMatrices(this._transformMatrices, null);
  490. if (this.isUsingTextureForMatrices && this._transformMatrixTexture) {
  491. this._transformMatrixTexture.update(this._transformMatrices);
  492. }
  493. }
  494. this._isDirty = false;
  495. }
  496. /**
  497. * Gets the list of animatables currently running for this skeleton
  498. * @returns an array of animatables
  499. */
  500. getAnimatables() {
  501. if (!this._animatables || this._animatables.length !== this.bones.length) {
  502. this._animatables = [];
  503. for (let index = 0; index < this.bones.length; index++) {
  504. this._animatables.push(this.bones[index]);
  505. }
  506. }
  507. return this._animatables;
  508. }
  509. /**
  510. * Clone the current skeleton
  511. * @param name defines the name of the new skeleton
  512. * @param id defines the id of the new skeleton
  513. * @returns the new skeleton
  514. */
  515. clone(name, id) {
  516. const result = new Skeleton(name, id || name, this._scene);
  517. result.needInitialSkinMatrix = this.needInitialSkinMatrix;
  518. for (let index = 0; index < this.bones.length; index++) {
  519. const source = this.bones[index];
  520. let parentBone = null;
  521. const parent = source.getParent();
  522. if (parent) {
  523. const parentIndex = this.bones.indexOf(parent);
  524. parentBone = result.bones[parentIndex];
  525. }
  526. const bone = new Bone(source.name, result, parentBone, source.getBindMatrix().clone(), source.getRestMatrix().clone());
  527. bone._index = source._index;
  528. if (source._linkedTransformNode) {
  529. bone.linkTransformNode(source._linkedTransformNode);
  530. }
  531. DeepCopier.DeepCopy(source.animations, bone.animations);
  532. }
  533. if (this._ranges) {
  534. result._ranges = {};
  535. for (const rangeName in this._ranges) {
  536. const range = this._ranges[rangeName];
  537. if (range) {
  538. result._ranges[rangeName] = range.clone();
  539. }
  540. }
  541. }
  542. this._isDirty = true;
  543. result.prepare(true);
  544. return result;
  545. }
  546. /**
  547. * Enable animation blending for this skeleton
  548. * @param blendingSpeed defines the blending speed to apply
  549. * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-blending
  550. */
  551. enableBlending(blendingSpeed = 0.01) {
  552. this.bones.forEach((bone) => {
  553. bone.animations.forEach((animation) => {
  554. animation.enableBlending = true;
  555. animation.blendingSpeed = blendingSpeed;
  556. });
  557. });
  558. }
  559. /**
  560. * Releases all resources associated with the current skeleton
  561. */
  562. dispose() {
  563. this._meshesWithPoseMatrix.length = 0;
  564. // Animations
  565. this.getScene().stopAnimation(this);
  566. // Remove from scene
  567. this.getScene().removeSkeleton(this);
  568. if (this._parentContainer) {
  569. const index = this._parentContainer.skeletons.indexOf(this);
  570. if (index > -1) {
  571. this._parentContainer.skeletons.splice(index, 1);
  572. }
  573. this._parentContainer = null;
  574. }
  575. if (this._transformMatrixTexture) {
  576. this._transformMatrixTexture.dispose();
  577. this._transformMatrixTexture = null;
  578. }
  579. }
  580. /**
  581. * Serialize the skeleton in a JSON object
  582. * @returns a JSON object
  583. */
  584. serialize() {
  585. const serializationObject = {};
  586. serializationObject.name = this.name;
  587. serializationObject.id = this.id;
  588. if (this.dimensionsAtRest) {
  589. serializationObject.dimensionsAtRest = this.dimensionsAtRest.asArray();
  590. }
  591. serializationObject.bones = [];
  592. serializationObject.needInitialSkinMatrix = this.needInitialSkinMatrix;
  593. for (let index = 0; index < this.bones.length; index++) {
  594. const bone = this.bones[index];
  595. const parent = bone.getParent();
  596. const serializedBone = {
  597. parentBoneIndex: parent ? this.bones.indexOf(parent) : -1,
  598. index: bone.getIndex(),
  599. name: bone.name,
  600. id: bone.id,
  601. matrix: bone.getBindMatrix().asArray(),
  602. rest: bone.getRestMatrix().asArray(),
  603. linkedTransformNodeId: bone.getTransformNode()?.id,
  604. };
  605. serializationObject.bones.push(serializedBone);
  606. if (bone.length) {
  607. serializedBone.length = bone.length;
  608. }
  609. if (bone.metadata) {
  610. serializedBone.metadata = bone.metadata;
  611. }
  612. if (bone.animations && bone.animations.length > 0) {
  613. serializedBone.animation = bone.animations[0].serialize();
  614. }
  615. serializationObject.ranges = [];
  616. for (const name in this._ranges) {
  617. const source = this._ranges[name];
  618. if (!source) {
  619. continue;
  620. }
  621. const range = {};
  622. range.name = name;
  623. range.from = source.from;
  624. range.to = source.to;
  625. serializationObject.ranges.push(range);
  626. }
  627. }
  628. return serializationObject;
  629. }
  630. /**
  631. * Creates a new skeleton from serialized data
  632. * @param parsedSkeleton defines the serialized data
  633. * @param scene defines the hosting scene
  634. * @returns a new skeleton
  635. */
  636. static Parse(parsedSkeleton, scene) {
  637. const skeleton = new Skeleton(parsedSkeleton.name, parsedSkeleton.id, scene);
  638. if (parsedSkeleton.dimensionsAtRest) {
  639. skeleton.dimensionsAtRest = Vector3.FromArray(parsedSkeleton.dimensionsAtRest);
  640. }
  641. skeleton.needInitialSkinMatrix = parsedSkeleton.needInitialSkinMatrix;
  642. let index;
  643. for (index = 0; index < parsedSkeleton.bones.length; index++) {
  644. const parsedBone = parsedSkeleton.bones[index];
  645. const parsedBoneIndex = parsedSkeleton.bones[index].index;
  646. let parentBone = null;
  647. if (parsedBone.parentBoneIndex > -1) {
  648. parentBone = skeleton.bones[parsedBone.parentBoneIndex];
  649. }
  650. const rest = parsedBone.rest ? Matrix.FromArray(parsedBone.rest) : null;
  651. const bone = new Bone(parsedBone.name, skeleton, parentBone, Matrix.FromArray(parsedBone.matrix), rest, null, parsedBoneIndex);
  652. if (parsedBone.id !== undefined && parsedBone.id !== null) {
  653. bone.id = parsedBone.id;
  654. }
  655. if (parsedBone.length) {
  656. bone.length = parsedBone.length;
  657. }
  658. if (parsedBone.metadata) {
  659. bone.metadata = parsedBone.metadata;
  660. }
  661. if (parsedBone.animation) {
  662. bone.animations.push(Animation.Parse(parsedBone.animation));
  663. }
  664. if (parsedBone.linkedTransformNodeId !== undefined && parsedBone.linkedTransformNodeId !== null) {
  665. skeleton._hasWaitingData = true;
  666. bone._waitingTransformNodeId = parsedBone.linkedTransformNodeId;
  667. }
  668. }
  669. // placed after bones, so createAnimationRange can cascade down
  670. if (parsedSkeleton.ranges) {
  671. for (index = 0; index < parsedSkeleton.ranges.length; index++) {
  672. const data = parsedSkeleton.ranges[index];
  673. skeleton.createAnimationRange(data.name, data.from, data.to);
  674. }
  675. }
  676. return skeleton;
  677. }
  678. /**
  679. * Compute all node absolute matrices
  680. * @param forceUpdate defines if computation must be done even if cache is up to date
  681. */
  682. computeAbsoluteMatrices(forceUpdate = false) {
  683. if (this._absoluteTransformIsDirty || forceUpdate) {
  684. this.bones[0].computeAbsoluteMatrices();
  685. this._absoluteTransformIsDirty = false;
  686. }
  687. }
  688. /**
  689. * Compute all node absolute matrices
  690. * @param forceUpdate defines if computation must be done even if cache is up to date
  691. * @deprecated Please use computeAbsoluteMatrices instead
  692. */
  693. computeAbsoluteTransforms(forceUpdate = false) {
  694. this.computeAbsoluteMatrices(forceUpdate);
  695. }
  696. /**
  697. * Gets the root pose matrix
  698. * @returns a matrix
  699. */
  700. getPoseMatrix() {
  701. let poseMatrix = null;
  702. if (this._meshesWithPoseMatrix.length > 0) {
  703. poseMatrix = this._meshesWithPoseMatrix[0].getPoseMatrix();
  704. }
  705. return poseMatrix;
  706. }
  707. /**
  708. * Sorts bones per internal index
  709. */
  710. sortBones() {
  711. const bones = [];
  712. const visited = new Array(this.bones.length);
  713. for (let index = 0; index < this.bones.length; index++) {
  714. this._sortBones(index, bones, visited);
  715. }
  716. this.bones = bones;
  717. }
  718. _sortBones(index, bones, visited) {
  719. if (visited[index]) {
  720. return;
  721. }
  722. visited[index] = true;
  723. const bone = this.bones[index];
  724. if (!bone)
  725. return;
  726. if (bone._index === undefined) {
  727. bone._index = index;
  728. }
  729. const parentBone = bone.getParent();
  730. if (parentBone) {
  731. this._sortBones(this.bones.indexOf(parentBone), bones, visited);
  732. }
  733. bones.push(bone);
  734. }
  735. /**
  736. * Set the current local matrix as the restPose for all bones in the skeleton.
  737. */
  738. setCurrentPoseAsRest() {
  739. this.bones.forEach((b) => {
  740. b.setCurrentPoseAsRest();
  741. });
  742. }
  743. }
  744. //# sourceMappingURL=skeleton.js.map