vertexAnimationBaker.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { RawTexture } from "../Materials/Textures/rawTexture.js";
  2. import { Texture } from "../Materials/Textures/texture.js";
  3. import { EncodeArrayBufferToBase64, DecodeBase64ToBinary } from "../Misc/stringTools.js";
  4. import { Skeleton } from "../Bones/skeleton.js";
  5. /**
  6. * Class to bake vertex animation textures.
  7. * @since 5.0
  8. */
  9. export class VertexAnimationBaker {
  10. /**
  11. * Create a new VertexAnimationBaker object which can help baking animations into a texture.
  12. * @param scene Defines the scene the VAT belongs to
  13. * @param meshOrSkeleton Defines the skeleton or the mesh from which to retrieve the skeleton from.
  14. */
  15. constructor(scene, meshOrSkeleton) {
  16. this._scene = scene;
  17. if (meshOrSkeleton instanceof Skeleton) {
  18. this._skeleton = meshOrSkeleton;
  19. this._mesh = null;
  20. }
  21. else {
  22. this._mesh = meshOrSkeleton;
  23. this._skeleton = meshOrSkeleton.skeleton;
  24. }
  25. }
  26. /**
  27. * Bakes the animation into the texture. This should be called once, when the
  28. * scene starts, so the VAT is generated and associated to the mesh.
  29. * @param ranges Defines the ranges in the animation that will be baked.
  30. * @returns The array of matrix transforms for each vertex (columns) and frame (rows), as a Float32Array.
  31. */
  32. async bakeVertexData(ranges) {
  33. if (!this._skeleton) {
  34. throw new Error("No skeleton provided.");
  35. }
  36. const boneCount = this._skeleton.bones.length;
  37. /** total number of frames in our animations */
  38. const frameCount = ranges.reduce((previous, current) => previous + current.to - current.from + 1, 0);
  39. if (isNaN(frameCount)) {
  40. throw new Error("Invalid animation ranges.");
  41. }
  42. // reset our loop data
  43. let textureIndex = 0;
  44. const textureSize = (boneCount + 1) * 4 * 4 * frameCount;
  45. const vertexData = new Float32Array(textureSize);
  46. this._scene.stopAnimation(this._skeleton);
  47. this._skeleton.returnToRest();
  48. // render all frames from our slices
  49. for (const range of ranges) {
  50. for (let frameIndex = range.from; frameIndex <= range.to; frameIndex++) {
  51. await this._executeAnimationFrame(vertexData, frameIndex, textureIndex++);
  52. }
  53. }
  54. return vertexData;
  55. }
  56. /**
  57. * Runs an animation frame and stores its vertex data
  58. *
  59. * @param vertexData The array to save data to.
  60. * @param frameIndex Current frame in the skeleton animation to render.
  61. * @param textureIndex Current index of the texture data.
  62. */
  63. async _executeAnimationFrame(vertexData, frameIndex, textureIndex) {
  64. return new Promise((resolve, _reject) => {
  65. this._scene.beginAnimation(this._skeleton, frameIndex, frameIndex, false, 1.0, () => {
  66. // generate matrices
  67. const skeletonMatrices = this._skeleton.getTransformMatrices(this._mesh);
  68. vertexData.set(skeletonMatrices, textureIndex * skeletonMatrices.length);
  69. resolve();
  70. });
  71. });
  72. }
  73. /**
  74. * Builds a vertex animation texture given the vertexData in an array.
  75. * @param vertexData The vertex animation data. You can generate it with bakeVertexData().
  76. * @returns The vertex animation texture to be used with BakedVertexAnimationManager.
  77. */
  78. textureFromBakedVertexData(vertexData) {
  79. if (!this._skeleton) {
  80. throw new Error("No skeleton provided.");
  81. }
  82. const boneCount = this._skeleton.bones.length;
  83. const texture = RawTexture.CreateRGBATexture(vertexData, (boneCount + 1) * 4, vertexData.length / ((boneCount + 1) * 4 * 4), this._scene, false, false, Texture.NEAREST_NEAREST, 1);
  84. texture.name = "VAT" + this._skeleton.name;
  85. return texture;
  86. }
  87. /**
  88. * Serializes our vertexData to an object, with a nice string for the vertexData.
  89. * @param vertexData The vertex array data.
  90. * @returns This object serialized to a JS dict.
  91. */
  92. serializeBakedVertexDataToObject(vertexData) {
  93. if (!this._skeleton) {
  94. throw new Error("No skeleton provided.");
  95. }
  96. // this converts the float array to a serialized base64 string, ~1.3x larger
  97. // than the original.
  98. const boneCount = this._skeleton.bones.length;
  99. const width = (boneCount + 1) * 4;
  100. const height = vertexData.length / ((boneCount + 1) * 4 * 4);
  101. const data = {
  102. vertexData: EncodeArrayBufferToBase64(vertexData),
  103. width,
  104. height,
  105. };
  106. return data;
  107. }
  108. /**
  109. * Loads previously baked data.
  110. * @param data The object as serialized by serializeBakedVertexDataToObject()
  111. * @returns The array of matrix transforms for each vertex (columns) and frame (rows), as a Float32Array.
  112. */
  113. loadBakedVertexDataFromObject(data) {
  114. return new Float32Array(DecodeBase64ToBinary(data.vertexData));
  115. }
  116. /**
  117. * Serializes our vertexData to a JSON string, with a nice string for the vertexData.
  118. * Should be called right after bakeVertexData().
  119. * @param vertexData The vertex array data.
  120. * @returns This object serialized to a safe string.
  121. */
  122. serializeBakedVertexDataToJSON(vertexData) {
  123. return JSON.stringify(this.serializeBakedVertexDataToObject(vertexData));
  124. }
  125. /**
  126. * Loads previously baked data in string format.
  127. * @param json The json string as serialized by serializeBakedVertexDataToJSON().
  128. * @returns The array of matrix transforms for each vertex (columns) and frame (rows), as a Float32Array.
  129. */
  130. loadBakedVertexDataFromJSON(json) {
  131. return this.loadBakedVertexDataFromObject(JSON.parse(json));
  132. }
  133. }
  134. //# sourceMappingURL=vertexAnimationBaker.js.map