|
@@ -1,43 +1,59 @@
|
|
|
declare var DracoDecoderModule: any;
|
|
|
+declare var WebAssembly: any;
|
|
|
|
|
|
module BABYLON {
|
|
|
/**
|
|
|
+ * Configuration for Draco compression
|
|
|
+ */
|
|
|
+ export interface IDracoCompressionConfiguration {
|
|
|
+ /**
|
|
|
+ * Configuration for the JavaScript decoder or null if not available.
|
|
|
+ */
|
|
|
+ decoder: Nullable<{
|
|
|
+ url: string;
|
|
|
+ }>;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Configuration for the WebAssembly decoder or null if not available.
|
|
|
+ */
|
|
|
+ decoderWasm: Nullable<{
|
|
|
+ binaryUrl: string;
|
|
|
+ wrapperUrl: string;
|
|
|
+ }>;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* Draco compression (https://google.github.io/draco/)
|
|
|
*/
|
|
|
export class DracoCompression implements IDisposable {
|
|
|
- private _workerPool: WorkerPool;
|
|
|
+ private static _DecoderModulePromise: Promise<any>;
|
|
|
|
|
|
/**
|
|
|
- * Gets the url to the draco decoder if available.
|
|
|
+ * Gets the configuration.
|
|
|
*/
|
|
|
- public static DecoderUrl: Nullable<string> = DracoCompression._GetDefaultDecoderUrl();
|
|
|
+ public static Configuration = DracoCompression._GetDefaultConfig();
|
|
|
|
|
|
/**
|
|
|
- * Constructor
|
|
|
- * @param numWorkers The number of workers for async operations
|
|
|
+ * Returns true if the decoder is available.
|
|
|
*/
|
|
|
- constructor(numWorkers = (navigator.hardwareConcurrency || 4)) {
|
|
|
- let workerBlobUrl = URL && URL.createObjectURL && URL.createObjectURL(new Blob([`(${DracoCompression._Worker.toString()})()`], { type: "application/javascript" }));
|
|
|
- if (!workerBlobUrl || !Worker) {
|
|
|
- Tools.Error("Draco Compression disabled. The current context doesn't support worker creation or URL.createObjectURL");
|
|
|
- return;
|
|
|
- }
|
|
|
- const workers = new Array<Worker>(numWorkers);
|
|
|
- for (let i = 0; i < workers.length; i++) {
|
|
|
- const worker = new Worker(workerBlobUrl);
|
|
|
- worker.postMessage({ id: "initDecoder", url: DracoCompression.DecoderUrl });
|
|
|
- workers[i] = worker;
|
|
|
- }
|
|
|
+ public static get DecoderAvailable(): boolean {
|
|
|
+ return (
|
|
|
+ typeof DracoDecoderModule !== "undefined" ||
|
|
|
+ (typeof WebAssembly === "object" && !!DracoCompression.Configuration.decoderWasm) ||
|
|
|
+ !!DracoCompression.Configuration.decoder
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- this._workerPool = new WorkerPool(workers);
|
|
|
+ /**
|
|
|
+ * Constructor
|
|
|
+ */
|
|
|
+ constructor() {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Stop all async operations and release resources.
|
|
|
*/
|
|
|
public dispose(): void {
|
|
|
- this._workerPool.dispose();
|
|
|
- delete this._workerPool;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -47,72 +63,26 @@ module BABYLON {
|
|
|
* @returns A promise that resolves with the decoded vertex data
|
|
|
*/
|
|
|
public decodeMeshAsync(data: ArrayBufferView, attributes: { [kind: string]: number }): Promise<VertexData> {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- this._workerPool.push((worker, onComplete) => {
|
|
|
- const vertexData = new VertexData();
|
|
|
-
|
|
|
- const onError = (error: ErrorEvent) => {
|
|
|
- worker.removeEventListener("error", onError);
|
|
|
- worker.removeEventListener("message", onMessage);
|
|
|
- reject(error);
|
|
|
- onComplete();
|
|
|
- };
|
|
|
+ return DracoCompression._GetDecoderModule().then(wrappedModule => {
|
|
|
+ const module = wrappedModule.module;
|
|
|
+ const vertexData = new VertexData();
|
|
|
|
|
|
- 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 dataCopy = new Uint8Array(data.byteLength);
|
|
|
- dataCopy.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
|
-
|
|
|
- worker.postMessage({ id: "decodeMesh", data: dataCopy, attributes: attributes }, [dataCopy.buffer]);
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The worker function that gets converted to a blob url to pass into a worker.
|
|
|
- */
|
|
|
- private static _Worker(): void {
|
|
|
- // self is actually a DedicatedWorkerGlobalScope
|
|
|
- const _self = self as any as {
|
|
|
- onmessage: (event: MessageEvent) => void;
|
|
|
- postMessage: (message: any, transfer?: any[]) => void;
|
|
|
- close: () => void;
|
|
|
- };
|
|
|
-
|
|
|
- const decodeMesh = (data: ArrayBufferView, attributes: { [kind: string]: number }): void => {
|
|
|
- const dracoModule = new DracoDecoderModule();
|
|
|
- const buffer = new dracoModule.DecoderBuffer();
|
|
|
+ const buffer = new module.DecoderBuffer();
|
|
|
buffer.Init(data, data.byteLength);
|
|
|
|
|
|
- const decoder = new dracoModule.Decoder();
|
|
|
+ const decoder = new module.Decoder();
|
|
|
let geometry: any;
|
|
|
let status: any;
|
|
|
|
|
|
try {
|
|
|
const type = decoder.GetEncodedGeometryType(buffer);
|
|
|
switch (type) {
|
|
|
- case dracoModule.TRIANGULAR_MESH:
|
|
|
- geometry = new dracoModule.Mesh();
|
|
|
+ case module.TRIANGULAR_MESH:
|
|
|
+ geometry = new module.Mesh();
|
|
|
status = decoder.DecodeBufferToMesh(buffer, geometry);
|
|
|
break;
|
|
|
- case dracoModule.POINT_CLOUD:
|
|
|
- geometry = new dracoModule.PointCloud();
|
|
|
+ case module.POINT_CLOUD:
|
|
|
+ geometry = new module.PointCloud();
|
|
|
status = decoder.DecodeBufferToPointCloud(buffer, geometry);
|
|
|
break;
|
|
|
default:
|
|
@@ -125,9 +95,9 @@ module BABYLON {
|
|
|
|
|
|
const numPoints = geometry.num_points();
|
|
|
|
|
|
- if (type === dracoModule.TRIANGULAR_MESH) {
|
|
|
+ if (type === module.TRIANGULAR_MESH) {
|
|
|
const numFaces = geometry.num_faces();
|
|
|
- const faceIndices = new dracoModule.DracoInt32Array();
|
|
|
+ const faceIndices = new module.DracoInt32Array();
|
|
|
try {
|
|
|
const indices = new Uint32Array(numFaces * 3);
|
|
|
for (let i = 0; i < numFaces; i++) {
|
|
@@ -137,68 +107,146 @@ module BABYLON {
|
|
|
indices[offset + 1] = faceIndices.GetValue(1);
|
|
|
indices[offset + 2] = faceIndices.GetValue(2);
|
|
|
}
|
|
|
- _self.postMessage({ id: "indices", value: indices }, [indices.buffer]);
|
|
|
+ vertexData.indices = indices;
|
|
|
}
|
|
|
finally {
|
|
|
- dracoModule.destroy(faceIndices);
|
|
|
+ module.destroy(faceIndices);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (const kind in attributes) {
|
|
|
const uniqueId = attributes[kind];
|
|
|
const attribute = decoder.GetAttributeByUniqueId(geometry, uniqueId);
|
|
|
- const dracoData = new dracoModule.DracoFloat32Array();
|
|
|
+ const dracoData = new module.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]);
|
|
|
+ vertexData.set(babylonData, kind);
|
|
|
}
|
|
|
finally {
|
|
|
- dracoModule.destroy(dracoData);
|
|
|
+ module.destroy(dracoData);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
finally {
|
|
|
if (geometry) {
|
|
|
- dracoModule.destroy(geometry);
|
|
|
+ module.destroy(geometry);
|
|
|
}
|
|
|
|
|
|
- dracoModule.destroy(decoder);
|
|
|
- dracoModule.destroy(buffer);
|
|
|
+ module.destroy(decoder);
|
|
|
+ module.destroy(buffer);
|
|
|
}
|
|
|
|
|
|
- _self.postMessage("done");
|
|
|
+ return vertexData;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _GetDecoderModule(): Promise<any> {
|
|
|
+ if (!DracoCompression._DecoderModulePromise) {
|
|
|
+ let promise: Promise<any>;
|
|
|
+ let config: any = {};
|
|
|
+
|
|
|
+ if (typeof DracoDecoderModule !== "undefined") {
|
|
|
+ promise = Promise.resolve();
|
|
|
+ }
|
|
|
+ else if (typeof WebAssembly === "object" && DracoCompression.Configuration.decoderWasm) {
|
|
|
+ promise = Promise.all([
|
|
|
+ DracoCompression._LoadScriptAsync(DracoCompression.Configuration.decoderWasm.wrapperUrl),
|
|
|
+ DracoCompression._LoadFileAsync(DracoCompression.Configuration.decoderWasm.binaryUrl).then(data => {
|
|
|
+ config.wasmBinary = data;
|
|
|
+ })
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ else if (DracoCompression.Configuration.decoder) {
|
|
|
+ promise = DracoCompression._LoadScriptAsync(DracoCompression.Configuration.decoder.url);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ throw new Error("Invalid decoder configuration");
|
|
|
+ }
|
|
|
+
|
|
|
+ DracoCompression._DecoderModulePromise = promise.then(() => {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ config.onModuleLoaded = (decoderModule: any) => {
|
|
|
+ // decoderModule is Promise-like. Wrap before resolving to avoid loop.
|
|
|
+ resolve({ module: decoderModule });
|
|
|
+ };
|
|
|
+
|
|
|
+ DracoDecoderModule(config);
|
|
|
+ });
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- _self.onmessage = event => {
|
|
|
- switch (event.data.id) {
|
|
|
- case "initDecoder": {
|
|
|
- importScripts(event.data.url);
|
|
|
- break;
|
|
|
- }
|
|
|
- case "decodeMesh": {
|
|
|
- decodeMesh(event.data.data, event.data.attributes);
|
|
|
- break;
|
|
|
+ return DracoCompression._DecoderModulePromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _LoadScriptAsync(url: string): Promise<void> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ Tools.LoadScript(url, () => {
|
|
|
+ resolve();
|
|
|
+ }, message => {
|
|
|
+ reject(new Error(message));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _LoadFileAsync(url: string): Promise<ArrayBuffer> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ Tools.LoadFile(url, data => {
|
|
|
+ resolve(data as ArrayBuffer);
|
|
|
+ }, undefined, undefined, true, (request, exception) => {
|
|
|
+ reject(exception);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private static _GetDefaultConfig(): IDracoCompressionConfiguration {
|
|
|
+ const configuration: IDracoCompressionConfiguration = {
|
|
|
+ decoder: null,
|
|
|
+ decoderWasm: null
|
|
|
+ };
|
|
|
+
|
|
|
+ if (Tools.IsWindowObjectExist()) {
|
|
|
+ let decoderUrl: Nullable<string> = null;
|
|
|
+ let decoderWasmBinaryUrl: Nullable<string> = null;
|
|
|
+ let decoderWasmWrapperUrl: Nullable<string> = null;
|
|
|
+
|
|
|
+ for (let i = 0; i < document.scripts.length; i++) {
|
|
|
+ const type = document.scripts[i].type;
|
|
|
+ const src = document.scripts[i].src;
|
|
|
+ switch (type) {
|
|
|
+ case "text/x-draco-decoder": {
|
|
|
+ decoderUrl = src;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case "text/x-draco-decoder-wasm-binary": {
|
|
|
+ decoderWasmBinaryUrl = src;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case "text/x-draco-decoder-wasm-wrapper": {
|
|
|
+ decoderWasmWrapperUrl = src;
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- };
|
|
|
- }
|
|
|
|
|
|
- private static _GetDefaultDecoderUrl(): Nullable<string> {
|
|
|
- if (!Tools.IsWindowObjectExist()) {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ if (decoderUrl) {
|
|
|
+ configuration.decoder = {
|
|
|
+ url: decoderUrl
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- for (let i = 0; i < document.scripts.length; i++) {
|
|
|
- if (document.scripts[i].type === "text/x-draco-decoder") {
|
|
|
- return document.scripts[i].src;
|
|
|
+ if (decoderWasmWrapperUrl && decoderWasmBinaryUrl) {
|
|
|
+ configuration.decoderWasm = {
|
|
|
+ binaryUrl: decoderWasmBinaryUrl,
|
|
|
+ wrapperUrl: decoderWasmWrapperUrl
|
|
|
+ };
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return null;
|
|
|
+ return configuration;
|
|
|
}
|
|
|
}
|
|
|
}
|