babylon.dracoCompression.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. declare var DracoDecoderModule: any;
  2. declare var WebAssembly: any;
  3. module BABYLON {
  4. /**
  5. * Configuration for Draco compression
  6. */
  7. export interface IDracoCompressionConfiguration {
  8. /**
  9. * Configuration for the decoder.
  10. */
  11. decoder?: {
  12. /**
  13. * The url to the WebAssembly module.
  14. */
  15. wasmUrl?: string;
  16. /**
  17. * The url to the WebAssembly binary.
  18. */
  19. wasmBinaryUrl?: string;
  20. /**
  21. * The url to the fallback JavaScript module.
  22. */
  23. fallbackUrl?: string;
  24. };
  25. }
  26. /**
  27. * Draco compression (https://google.github.io/draco/)
  28. *
  29. * This class wraps the Draco module.
  30. *
  31. * **Encoder**
  32. *
  33. * The encoder is not currently implemented.
  34. *
  35. * **Decoder**
  36. *
  37. * By default, the configuration points to a copy of the Draco decoder files for glTF from https://preview.babylonjs.com.
  38. *
  39. * To update the configuration, use the following code:
  40. * ```javascript
  41. * BABYLON.DracoCompression.Configuration = {
  42. * decoder: {
  43. * wasmUrl: "<url to the WebAssembly library>",
  44. * wasmBinaryUrl: "<url to the WebAssembly binary>",
  45. * fallbackUrl: "<url to the fallback JavaScript library>",
  46. * }
  47. * };
  48. * ```
  49. *
  50. * Draco has two versions, one for WebAssembly and one for JavaScript. The decoder configuration can be set to only support Webssembly or only support the JavaScript version.
  51. * Decoding will automatically fallback to the JavaScript version if WebAssembly version is not configured or if WebAssembly is not supported by the browser.
  52. * Use `BABYLON.DracoCompression.DecoderAvailable` to determine if the decoder is available for the current session.
  53. *
  54. * To decode Draco compressed data, create a DracoCompression object and call decodeMeshAsync:
  55. * ```javascript
  56. * var dracoCompression = new BABYLON.DracoCompression();
  57. * var vertexData = await dracoCompression.decodeMeshAsync(data, {
  58. * [BABYLON.VertexBuffer.PositionKind]: 0
  59. * });
  60. * ```
  61. *
  62. * @see https://www.babylonjs-playground.com/#N3EK4B#0
  63. */
  64. export class DracoCompression implements IDisposable {
  65. private static _DecoderModulePromise: Promise<any>;
  66. /**
  67. * The configuration. Defaults to the following urls:
  68. * - wasmUrl: "https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js"
  69. * - wasmBinaryUrl: "https://preview.babylonjs.com/draco_decoder_gltf.wasm"
  70. * - fallbackUrl: "https://preview.babylonjs.com/draco_decoder_gltf.js"
  71. */
  72. public static Configuration: IDracoCompressionConfiguration = {
  73. decoder: {
  74. wasmUrl: "https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js",
  75. wasmBinaryUrl: "https://preview.babylonjs.com/draco_decoder_gltf.wasm",
  76. fallbackUrl: "https://preview.babylonjs.com/draco_decoder_gltf.js"
  77. }
  78. };
  79. /**
  80. * Returns true if the decoder is available.
  81. */
  82. public static get DecoderAvailable(): boolean {
  83. if (typeof DracoDecoderModule !== "undefined") {
  84. return true;
  85. }
  86. const decoder = DracoCompression.Configuration.decoder;
  87. if (decoder) {
  88. if (decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") {
  89. return true;
  90. }
  91. if (decoder.fallbackUrl) {
  92. return true;
  93. }
  94. }
  95. return false;
  96. }
  97. /**
  98. * Constructor
  99. */
  100. constructor() {
  101. }
  102. /**
  103. * Stop all async operations and release resources.
  104. */
  105. public dispose(): void {
  106. }
  107. /**
  108. * Decode Draco compressed mesh data to vertex data.
  109. * @param data The ArrayBuffer or ArrayBufferView for the Draco compression data
  110. * @param attributes A map of attributes from vertex buffer kinds to Draco unique ids
  111. * @returns A promise that resolves with the decoded vertex data
  112. */
  113. public decodeMeshAsync(data: ArrayBuffer | ArrayBufferView, attributes: { [kind: string]: number }): Promise<VertexData> {
  114. const dataView = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
  115. return DracoCompression._GetDecoderModule().then(wrappedModule => {
  116. const module = wrappedModule.module;
  117. const vertexData = new VertexData();
  118. const buffer = new module.DecoderBuffer();
  119. buffer.Init(dataView, dataView.byteLength);
  120. const decoder = new module.Decoder();
  121. let geometry: any;
  122. let status: any;
  123. try {
  124. const type = decoder.GetEncodedGeometryType(buffer);
  125. switch (type) {
  126. case module.TRIANGULAR_MESH:
  127. geometry = new module.Mesh();
  128. status = decoder.DecodeBufferToMesh(buffer, geometry);
  129. break;
  130. case module.POINT_CLOUD:
  131. geometry = new module.PointCloud();
  132. status = decoder.DecodeBufferToPointCloud(buffer, geometry);
  133. break;
  134. default:
  135. throw new Error(`Invalid geometry type ${type}`);
  136. }
  137. if (!status.ok() || !geometry.ptr) {
  138. throw new Error(status.error_msg());
  139. }
  140. const numPoints = geometry.num_points();
  141. if (type === module.TRIANGULAR_MESH) {
  142. const numFaces = geometry.num_faces();
  143. const faceIndices = new module.DracoInt32Array();
  144. try {
  145. const indices = new Uint32Array(numFaces * 3);
  146. for (let i = 0; i < numFaces; i++) {
  147. decoder.GetFaceFromMesh(geometry, i, faceIndices);
  148. const offset = i * 3;
  149. indices[offset + 0] = faceIndices.GetValue(0);
  150. indices[offset + 1] = faceIndices.GetValue(1);
  151. indices[offset + 2] = faceIndices.GetValue(2);
  152. }
  153. vertexData.indices = indices;
  154. }
  155. finally {
  156. module.destroy(faceIndices);
  157. }
  158. }
  159. for (const kind in attributes) {
  160. const uniqueId = attributes[kind];
  161. const attribute = decoder.GetAttributeByUniqueId(geometry, uniqueId);
  162. const dracoData = new module.DracoFloat32Array();
  163. try {
  164. decoder.GetAttributeFloatForAllPoints(geometry, attribute, dracoData);
  165. const babylonData = new Float32Array(numPoints * attribute.num_components());
  166. for (let i = 0; i < babylonData.length; i++) {
  167. babylonData[i] = dracoData.GetValue(i);
  168. }
  169. vertexData.set(babylonData, kind);
  170. }
  171. finally {
  172. module.destroy(dracoData);
  173. }
  174. }
  175. }
  176. finally {
  177. if (geometry) {
  178. module.destroy(geometry);
  179. }
  180. module.destroy(decoder);
  181. module.destroy(buffer);
  182. }
  183. return vertexData;
  184. });
  185. }
  186. private static _GetDecoderModule(): Promise<any> {
  187. if (!DracoCompression._DecoderModulePromise) {
  188. let promise: Nullable<Promise<any>> = null;
  189. let config: any = {};
  190. if (typeof DracoDecoderModule !== "undefined") {
  191. promise = Promise.resolve();
  192. }
  193. else {
  194. const decoder = DracoCompression.Configuration.decoder;
  195. if (decoder) {
  196. if (decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") {
  197. promise = Promise.all([
  198. DracoCompression._LoadScriptAsync(decoder.wasmUrl),
  199. DracoCompression._LoadFileAsync(decoder.wasmBinaryUrl).then(data => {
  200. config.wasmBinary = data;
  201. })
  202. ]);
  203. }
  204. else if (decoder.fallbackUrl) {
  205. promise = DracoCompression._LoadScriptAsync(decoder.fallbackUrl);
  206. }
  207. }
  208. }
  209. if (!promise) {
  210. throw new Error("Draco decoder module is not available");
  211. }
  212. DracoCompression._DecoderModulePromise = promise.then(() => {
  213. return new Promise(resolve => {
  214. config.onModuleLoaded = (decoderModule: any) => {
  215. // decoderModule is Promise-like. Wrap before resolving to avoid loop.
  216. resolve({ module: decoderModule });
  217. };
  218. DracoDecoderModule(config);
  219. });
  220. });
  221. }
  222. return DracoCompression._DecoderModulePromise;
  223. }
  224. private static _LoadScriptAsync(url: string): Promise<void> {
  225. return new Promise((resolve, reject) => {
  226. Tools.LoadScript(url, () => {
  227. resolve();
  228. }, message => {
  229. reject(new Error(message));
  230. });
  231. });
  232. }
  233. private static _LoadFileAsync(url: string): Promise<ArrayBuffer> {
  234. return new Promise((resolve, reject) => {
  235. Tools.LoadFile(url, data => {
  236. resolve(data as ArrayBuffer);
  237. }, undefined, undefined, true, (request, exception) => {
  238. reject(exception);
  239. });
  240. });
  241. }
  242. }
  243. }