123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- import { Tools } from "../../Misc/tools";
- import { WorkerPool } from '../../Misc/workerPool';
- import { Nullable } from "../../types";
- import { IDisposable } from "../../scene";
- import { VertexData } from "../../Meshes/mesh.vertexData";
- declare var DracoDecoderModule: any;
- declare var WebAssembly: any;
- declare function importScripts(...urls: string[]): void;
- /**
- * Configuration for Draco compression
- */
- export interface IDracoCompressionConfiguration {
- /**
- * Configuration for the decoder.
- */
- decoder?: {
- /**
- * The url to the WebAssembly module.
- */
- wasmUrl?: string;
- /**
- * The url to the WebAssembly binary.
- */
- wasmBinaryUrl?: string;
- /**
- * The url to the fallback JavaScript module.
- */
- fallbackUrl?: string;
- };
- }
- /**
- * Draco compression (https://google.github.io/draco/)
- *
- * This class wraps the Draco module.
- *
- * **Encoder**
- *
- * The encoder is not currently implemented.
- *
- * **Decoder**
- *
- * By default, the configuration points to a copy of the Draco decoder files for glTF from the babylon.js preview cdn https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js.
- *
- * To update the configuration, use the following code:
- * ```javascript
- * DracoCompression.Configuration = {
- * decoder: {
- * wasmUrl: "<url to the WebAssembly library>",
- * wasmBinaryUrl: "<url to the WebAssembly binary>",
- * fallbackUrl: "<url to the fallback JavaScript library>",
- * }
- * };
- * ```
- *
- * 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.
- * Decoding will automatically fallback to the JavaScript version if WebAssembly version is not configured or if WebAssembly is not supported by the browser.
- * Use `DracoCompression.DecoderAvailable` to determine if the decoder is available for the current session.
- *
- * To decode Draco compressed data, create a DracoCompression object and call decodeMeshAsync:
- * ```javascript
- * var dracoCompression = new DracoCompression();
- * var vertexData = await dracoCompression.decodeMeshAsync(data, {
- * [VertexBuffer.PositionKind]: 0
- * });
- * ```
- *
- * @see https://www.babylonjs-playground.com/#N3EK4B#0
- */
- export class DracoCompression implements IDisposable {
- private _workerPoolPromise: Promise<WorkerPool>;
- /**
- * The configuration. Defaults to the following urls:
- * - wasmUrl: "https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js"
- * - wasmBinaryUrl: "https://preview.babylonjs.com/draco_decoder_gltf.wasm"
- * - fallbackUrl: "https://preview.babylonjs.com/draco_decoder_gltf.js"
- */
- public static Configuration: IDracoCompressionConfiguration = {
- decoder: {
- wasmUrl: "https://preview.babylonjs.com/draco_wasm_wrapper_gltf.js",
- wasmBinaryUrl: "https://preview.babylonjs.com/draco_decoder_gltf.wasm",
- fallbackUrl: "https://preview.babylonjs.com/draco_decoder_gltf.js"
- }
- };
- /**
- * Returns true if the decoder is available.
- */
- public static get DecoderAvailable(): boolean {
- if (typeof DracoDecoderModule !== "undefined") {
- return true;
- }
- const decoder = DracoCompression.Configuration.decoder;
- if (decoder) {
- if (decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") {
- return true;
- }
- if (decoder.fallbackUrl) {
- return true;
- }
- }
- return false;
- }
- /**
- * Default number of workers to create when creating the draco compression object.
- */
- public static DefaultNumWorkers = DracoCompression.GetDefaultNumWorkers();
- private static GetDefaultNumWorkers(): number {
- const hardwareConcurrency = navigator && navigator.hardwareConcurrency;
- if (!hardwareConcurrency) {
- return 1;
- }
- // Use 50% of the available logical processors but capped at 4.
- return Math.min(Math.floor(hardwareConcurrency * 0.5), 4);
- }
- /**
- * Constructor
- * @param numWorkers The number of workers for async operations
- */
- constructor(numWorkers = DracoCompression.DefaultNumWorkers) {
- if (!URL || !URL.createObjectURL) {
- throw new Error("Object URLs are not available");
- }
- if (!Worker) {
- throw new Error("Workers are not available");
- }
- this._workerPoolPromise = this._loadDecoderWasmBinaryAsync().then((decoderWasmBinary) => {
- const workerBlobUrl = URL.createObjectURL(new Blob([`(${DracoCompression._Worker.toString()})()`], { type: "application/javascript" }));
- const workerPromises = new Array<Promise<Worker>>(numWorkers);
- for (let i = 0; i < workerPromises.length; i++) {
- workerPromises[i] = new Promise((resolve, reject) => {
- const decoder = DracoCompression.Configuration.decoder;
- if (decoder) {
- const worker = new Worker(workerBlobUrl);
- const onError = (error: ErrorEvent) => {
- worker.removeEventListener("error", onError);
- worker.removeEventListener("message", onMessage);
- reject(error);
- };
- const onMessage = (message: MessageEvent) => {
- if (message.data === "done") {
- worker.removeEventListener("error", onError);
- worker.removeEventListener("message", onMessage);
- resolve(worker);
- }
- };
- worker.addEventListener("error", onError);
- worker.addEventListener("message", onMessage);
- worker.postMessage({
- id: "initDecoder",
- decoderWasmUrl: decoder.wasmUrl ? Tools.GetAbsoluteUrl(decoder.wasmUrl) : null,
- decoderWasmBinary: decoderWasmBinary,
- fallbackUrl: decoder.fallbackUrl ? Tools.GetAbsoluteUrl(decoder.fallbackUrl) : null
- });
- }
- });
- }
- return Promise.all(workerPromises).then((workers) => {
- return new WorkerPool(workers);
- });
- });
- }
- /**
- * Stop all async operations and release resources.
- */
- public dispose(): void {
- this._workerPoolPromise.then((workerPool) => {
- workerPool.dispose();
- });
- delete this._workerPoolPromise;
- }
- /**
- * Returns a promise that resolves when ready. Call this manually to ensure draco compression is ready before use.
- * @returns a promise that resolves when ready
- */
- public whenReadyAsync(): Promise<void> {
- return this._workerPoolPromise.then(() => { });
- }
- /**
- * Decode Draco compressed mesh data to vertex data.
- * @param data The ArrayBuffer or ArrayBufferView for the Draco compression data
- * @param attributes A map of attributes from vertex buffer kinds to Draco unique ids
- * @returns A promise that resolves with the decoded vertex data
- */
- public decodeMeshAsync(data: ArrayBuffer | ArrayBufferView, attributes?: { [kind: string]: number }): Promise<VertexData> {
- const dataView = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
- return this._workerPoolPromise.then((workerPool) => {
- return new Promise<VertexData>((resolve, reject) => {
- workerPool.push((worker, onComplete) => {
- const vertexData = new VertexData();
- const onError = (error: ErrorEvent) => {
- worker.removeEventListener("error", onError);
- worker.removeEventListener("message", onMessage);
- reject(error);
- onComplete();
- };
- const onMessage = (message: MessageEvent) => {
- if (message.data === "done") {
- worker.removeEventListener("error", onError);
- worker.removeEventListener("message", onMessage);
- resolve(vertexData);
- onComplete();
- }
- else if (message.data.id === "indices") {
- vertexData.indices = message.data.value;
- }
- else {
- vertexData.set(message.data.value, message.data.id);
- }
- };
- worker.addEventListener("error", onError);
- worker.addEventListener("message", onMessage);
- const dataViewCopy = new Uint8Array(dataView.byteLength);
- dataViewCopy.set(new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength));
- worker.postMessage({ id: "decodeMesh", dataView: dataViewCopy, attributes: attributes }, [dataViewCopy.buffer]);
- });
- });
- });
- }
- /**
- * The worker function that gets converted to a blob url to pass into a worker.
- */
- private static _Worker(): void {
- const nativeAttributeTypes: { [kind: string]: string } = {
- "position": "POSITION",
- "normal": "NORMAL",
- "color": "COLOR",
- "uv": "TEX_COORD"
- };
- // self is actually a DedicatedWorkerGlobalScope
- const _self = self as any as {
- onmessage: (event: MessageEvent) => void;
- postMessage: (message: any, transfer?: any[]) => void;
- close: () => void;
- };
- let decoderModulePromise: Promise<any>;
- function initDecoder(decoderWasmUrl: string | undefined, decoderWasmBinary: ArrayBuffer | undefined, fallbackUrl: string | undefined): void {
- if (decoderWasmUrl && decoderWasmBinary && typeof WebAssembly === "object") {
- importScripts(decoderWasmUrl);
- decoderModulePromise = DracoDecoderModule({
- wasmBinary: decoderWasmBinary
- });
- }
- else if (fallbackUrl) {
- importScripts(fallbackUrl);
- decoderModulePromise = DracoDecoderModule();
- }
- else {
- throw Error("Failed to initialize Draco decoder");
- }
- _self.postMessage("done");
- }
- function decodeMesh(dataView: ArrayBufferView, attributes: { [kind: string]: number }): void {
- decoderModulePromise.then((decoderModule) => {
- const buffer = new decoderModule.DecoderBuffer();
- buffer.Init(dataView, dataView.byteLength);
- const decoder = new decoderModule.Decoder();
- let geometry: any;
- let status: any;
- try {
- const type = decoder.GetEncodedGeometryType(buffer);
- switch (type) {
- case decoderModule.TRIANGULAR_MESH:
- geometry = new decoderModule.Mesh();
- status = decoder.DecodeBufferToMesh(buffer, geometry);
- break;
- case decoderModule.POINT_CLOUD:
- geometry = new decoderModule.PointCloud();
- status = decoder.DecodeBufferToPointCloud(buffer, geometry);
- break;
- default:
- throw new Error(`Invalid geometry type ${type}`);
- }
- if (!status.ok() || !geometry.ptr) {
- throw new Error(status.error_msg());
- }
- const numPoints = geometry.num_points();
- if (type === decoderModule.TRIANGULAR_MESH) {
- const numFaces = geometry.num_faces();
- const faceIndices = new decoderModule.DracoInt32Array();
- try {
- const indices = new Uint32Array(numFaces * 3);
- for (let i = 0; i < numFaces; i++) {
- decoder.GetFaceFromMesh(geometry, i, faceIndices);
- const offset = i * 3;
- indices[offset + 0] = faceIndices.GetValue(0);
- indices[offset + 1] = faceIndices.GetValue(1);
- indices[offset + 2] = faceIndices.GetValue(2);
- }
- _self.postMessage({ id: "indices", value: indices }, [indices.buffer]);
- }
- finally {
- decoderModule.destroy(faceIndices);
- }
- }
- const processAttribute = (kind: string, attribute: any) => {
- const dracoData = new decoderModule.DracoFloat32Array();
- try {
- decoder.GetAttributeFloatForAllPoints(geometry, attribute, dracoData);
- const babylonData = new Float32Array(numPoints * attribute.num_components());
- for (let i = 0; i < babylonData.length; i++) {
- babylonData[i] = dracoData.GetValue(i);
- }
- _self.postMessage({ id: kind, value: babylonData }, [babylonData.buffer]);
- }
- finally {
- decoderModule.destroy(dracoData);
- }
- };
- if (attributes) {
- for (const kind in attributes) {
- const id = attributes[kind];
- const attribute = decoder.GetAttributeByUniqueId(geometry, id);
- processAttribute(kind, attribute);
- }
- }
- else {
- for (const kind in nativeAttributeTypes) {
- const id = decoder.GetAttributeId(geometry, decoderModule[nativeAttributeTypes[kind]]);
- if (id !== -1) {
- const attribute = decoder.GetAttribute(geometry, id);
- processAttribute(kind, attribute);
- }
- }
- }
- }
- finally {
- if (geometry) {
- decoderModule.destroy(geometry);
- }
- decoderModule.destroy(decoder);
- decoderModule.destroy(buffer);
- }
- _self.postMessage("done");
- });
- }
- _self.onmessage = (event) => {
- const data = event.data;
- switch (data.id) {
- case "initDecoder": {
- initDecoder(data.decoderWasmUrl, data.decoderWasmBinary, data.fallbackUrl);
- break;
- }
- case "decodeMesh": {
- decodeMesh(data.dataView, data.attributes);
- break;
- }
- }
- };
- }
- private _loadDecoderWasmBinaryAsync(): Promise<Nullable<ArrayBuffer>> {
- const decoder = DracoCompression.Configuration.decoder;
- if (decoder && decoder.wasmUrl && decoder.wasmBinaryUrl && typeof WebAssembly === "object") {
- const wasmBinaryUrl = Tools.GetAbsoluteUrl(decoder.wasmBinaryUrl);
- return new Promise((resolve, reject) => {
- Tools.LoadFile(wasmBinaryUrl, (data) => {
- resolve(data as ArrayBuffer);
- }, undefined, undefined, true, (request, exception) => {
- reject(exception);
- });
- });
- }
- else {
- return Promise.resolve(null);
- }
- }
- }
|