343 lines
12 KiB
JavaScript
343 lines
12 KiB
JavaScript
import { Loader, FileLoader, BufferGeometry, BufferAttribute } from "three";
|
|
const _taskCache = /* @__PURE__ */ new WeakMap();
|
|
class DRACOLoader extends Loader {
|
|
constructor(manager) {
|
|
super(manager);
|
|
this.decoderPath = "";
|
|
this.decoderConfig = {};
|
|
this.decoderBinary = null;
|
|
this.decoderPending = null;
|
|
this.workerLimit = 4;
|
|
this.workerPool = [];
|
|
this.workerNextTaskID = 1;
|
|
this.workerSourceURL = "";
|
|
this.defaultAttributeIDs = {
|
|
position: "POSITION",
|
|
normal: "NORMAL",
|
|
color: "COLOR",
|
|
uv: "TEX_COORD"
|
|
};
|
|
this.defaultAttributeTypes = {
|
|
position: "Float32Array",
|
|
normal: "Float32Array",
|
|
color: "Float32Array",
|
|
uv: "Float32Array"
|
|
};
|
|
}
|
|
setDecoderPath(path) {
|
|
this.decoderPath = path;
|
|
return this;
|
|
}
|
|
setDecoderConfig(config) {
|
|
this.decoderConfig = config;
|
|
return this;
|
|
}
|
|
setWorkerLimit(workerLimit) {
|
|
this.workerLimit = workerLimit;
|
|
return this;
|
|
}
|
|
load(url, onLoad, onProgress, onError) {
|
|
const loader = new FileLoader(this.manager);
|
|
loader.setPath(this.path);
|
|
loader.setResponseType("arraybuffer");
|
|
loader.setRequestHeader(this.requestHeader);
|
|
loader.setWithCredentials(this.withCredentials);
|
|
loader.load(
|
|
url,
|
|
(buffer) => {
|
|
const taskConfig = {
|
|
attributeIDs: this.defaultAttributeIDs,
|
|
attributeTypes: this.defaultAttributeTypes,
|
|
useUniqueIDs: false
|
|
};
|
|
this.decodeGeometry(buffer, taskConfig).then(onLoad).catch(onError);
|
|
},
|
|
onProgress,
|
|
onError
|
|
);
|
|
}
|
|
/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
|
|
decodeDracoFile(buffer, callback, attributeIDs, attributeTypes) {
|
|
const taskConfig = {
|
|
attributeIDs: attributeIDs || this.defaultAttributeIDs,
|
|
attributeTypes: attributeTypes || this.defaultAttributeTypes,
|
|
useUniqueIDs: !!attributeIDs
|
|
};
|
|
this.decodeGeometry(buffer, taskConfig).then(callback);
|
|
}
|
|
decodeGeometry(buffer, taskConfig) {
|
|
for (const attribute in taskConfig.attributeTypes) {
|
|
const type = taskConfig.attributeTypes[attribute];
|
|
if (type.BYTES_PER_ELEMENT !== void 0) {
|
|
taskConfig.attributeTypes[attribute] = type.name;
|
|
}
|
|
}
|
|
const taskKey = JSON.stringify(taskConfig);
|
|
if (_taskCache.has(buffer)) {
|
|
const cachedTask = _taskCache.get(buffer);
|
|
if (cachedTask.key === taskKey) {
|
|
return cachedTask.promise;
|
|
} else if (buffer.byteLength === 0) {
|
|
throw new Error(
|
|
"THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred."
|
|
);
|
|
}
|
|
}
|
|
let worker;
|
|
const taskID = this.workerNextTaskID++;
|
|
const taskCost = buffer.byteLength;
|
|
const geometryPending = this._getWorker(taskID, taskCost).then((_worker) => {
|
|
worker = _worker;
|
|
return new Promise((resolve, reject) => {
|
|
worker._callbacks[taskID] = { resolve, reject };
|
|
worker.postMessage({ type: "decode", id: taskID, taskConfig, buffer }, [buffer]);
|
|
});
|
|
}).then((message) => this._createGeometry(message.geometry));
|
|
geometryPending.catch(() => true).then(() => {
|
|
if (worker && taskID) {
|
|
this._releaseTask(worker, taskID);
|
|
}
|
|
});
|
|
_taskCache.set(buffer, {
|
|
key: taskKey,
|
|
promise: geometryPending
|
|
});
|
|
return geometryPending;
|
|
}
|
|
_createGeometry(geometryData) {
|
|
const geometry = new BufferGeometry();
|
|
if (geometryData.index) {
|
|
geometry.setIndex(new BufferAttribute(geometryData.index.array, 1));
|
|
}
|
|
for (let i = 0; i < geometryData.attributes.length; i++) {
|
|
const attribute = geometryData.attributes[i];
|
|
const name = attribute.name;
|
|
const array = attribute.array;
|
|
const itemSize = attribute.itemSize;
|
|
geometry.setAttribute(name, new BufferAttribute(array, itemSize));
|
|
}
|
|
return geometry;
|
|
}
|
|
_loadLibrary(url, responseType) {
|
|
const loader = new FileLoader(this.manager);
|
|
loader.setPath(this.decoderPath);
|
|
loader.setResponseType(responseType);
|
|
loader.setWithCredentials(this.withCredentials);
|
|
return new Promise((resolve, reject) => {
|
|
loader.load(url, resolve, void 0, reject);
|
|
});
|
|
}
|
|
preload() {
|
|
this._initDecoder();
|
|
return this;
|
|
}
|
|
_initDecoder() {
|
|
if (this.decoderPending)
|
|
return this.decoderPending;
|
|
const useJS = typeof WebAssembly !== "object" || this.decoderConfig.type === "js";
|
|
const librariesPending = [];
|
|
if (useJS) {
|
|
librariesPending.push(this._loadLibrary("draco_decoder.js", "text"));
|
|
} else {
|
|
librariesPending.push(this._loadLibrary("draco_wasm_wrapper.js", "text"));
|
|
librariesPending.push(this._loadLibrary("draco_decoder.wasm", "arraybuffer"));
|
|
}
|
|
this.decoderPending = Promise.all(librariesPending).then((libraries) => {
|
|
const jsContent = libraries[0];
|
|
if (!useJS) {
|
|
this.decoderConfig.wasmBinary = libraries[1];
|
|
}
|
|
const fn = DRACOWorker.toString();
|
|
const body = [
|
|
"/* draco decoder */",
|
|
jsContent,
|
|
"",
|
|
"/* worker */",
|
|
fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}"))
|
|
].join("\n");
|
|
this.workerSourceURL = URL.createObjectURL(new Blob([body]));
|
|
});
|
|
return this.decoderPending;
|
|
}
|
|
_getWorker(taskID, taskCost) {
|
|
return this._initDecoder().then(() => {
|
|
if (this.workerPool.length < this.workerLimit) {
|
|
const worker2 = new Worker(this.workerSourceURL);
|
|
worker2._callbacks = {};
|
|
worker2._taskCosts = {};
|
|
worker2._taskLoad = 0;
|
|
worker2.postMessage({ type: "init", decoderConfig: this.decoderConfig });
|
|
worker2.onmessage = function(e) {
|
|
const message = e.data;
|
|
switch (message.type) {
|
|
case "decode":
|
|
worker2._callbacks[message.id].resolve(message);
|
|
break;
|
|
case "error":
|
|
worker2._callbacks[message.id].reject(message);
|
|
break;
|
|
default:
|
|
console.error('THREE.DRACOLoader: Unexpected message, "' + message.type + '"');
|
|
}
|
|
};
|
|
this.workerPool.push(worker2);
|
|
} else {
|
|
this.workerPool.sort(function(a, b) {
|
|
return a._taskLoad > b._taskLoad ? -1 : 1;
|
|
});
|
|
}
|
|
const worker = this.workerPool[this.workerPool.length - 1];
|
|
worker._taskCosts[taskID] = taskCost;
|
|
worker._taskLoad += taskCost;
|
|
return worker;
|
|
});
|
|
}
|
|
_releaseTask(worker, taskID) {
|
|
worker._taskLoad -= worker._taskCosts[taskID];
|
|
delete worker._callbacks[taskID];
|
|
delete worker._taskCosts[taskID];
|
|
}
|
|
debug() {
|
|
console.log(
|
|
"Task load: ",
|
|
this.workerPool.map((worker) => worker._taskLoad)
|
|
);
|
|
}
|
|
dispose() {
|
|
for (let i = 0; i < this.workerPool.length; ++i) {
|
|
this.workerPool[i].terminate();
|
|
}
|
|
this.workerPool.length = 0;
|
|
return this;
|
|
}
|
|
}
|
|
function DRACOWorker() {
|
|
let decoderConfig;
|
|
let decoderPending;
|
|
onmessage = function(e) {
|
|
const message = e.data;
|
|
switch (message.type) {
|
|
case "init":
|
|
decoderConfig = message.decoderConfig;
|
|
decoderPending = new Promise(function(resolve) {
|
|
decoderConfig.onModuleLoaded = function(draco) {
|
|
resolve({ draco });
|
|
};
|
|
DracoDecoderModule(decoderConfig);
|
|
});
|
|
break;
|
|
case "decode":
|
|
const buffer = message.buffer;
|
|
const taskConfig = message.taskConfig;
|
|
decoderPending.then((module) => {
|
|
const draco = module.draco;
|
|
const decoder = new draco.Decoder();
|
|
const decoderBuffer = new draco.DecoderBuffer();
|
|
decoderBuffer.Init(new Int8Array(buffer), buffer.byteLength);
|
|
try {
|
|
const geometry = decodeGeometry(draco, decoder, decoderBuffer, taskConfig);
|
|
const buffers = geometry.attributes.map((attr) => attr.array.buffer);
|
|
if (geometry.index)
|
|
buffers.push(geometry.index.array.buffer);
|
|
self.postMessage({ type: "decode", id: message.id, geometry }, buffers);
|
|
} catch (error) {
|
|
console.error(error);
|
|
self.postMessage({ type: "error", id: message.id, error: error.message });
|
|
} finally {
|
|
draco.destroy(decoderBuffer);
|
|
draco.destroy(decoder);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
};
|
|
function decodeGeometry(draco, decoder, decoderBuffer, taskConfig) {
|
|
const attributeIDs = taskConfig.attributeIDs;
|
|
const attributeTypes = taskConfig.attributeTypes;
|
|
let dracoGeometry;
|
|
let decodingStatus;
|
|
const geometryType = decoder.GetEncodedGeometryType(decoderBuffer);
|
|
if (geometryType === draco.TRIANGULAR_MESH) {
|
|
dracoGeometry = new draco.Mesh();
|
|
decodingStatus = decoder.DecodeBufferToMesh(decoderBuffer, dracoGeometry);
|
|
} else if (geometryType === draco.POINT_CLOUD) {
|
|
dracoGeometry = new draco.PointCloud();
|
|
decodingStatus = decoder.DecodeBufferToPointCloud(decoderBuffer, dracoGeometry);
|
|
} else {
|
|
throw new Error("THREE.DRACOLoader: Unexpected geometry type.");
|
|
}
|
|
if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
|
|
throw new Error("THREE.DRACOLoader: Decoding failed: " + decodingStatus.error_msg());
|
|
}
|
|
const geometry = { index: null, attributes: [] };
|
|
for (const attributeName in attributeIDs) {
|
|
const attributeType = self[attributeTypes[attributeName]];
|
|
let attribute;
|
|
let attributeID;
|
|
if (taskConfig.useUniqueIDs) {
|
|
attributeID = attributeIDs[attributeName];
|
|
attribute = decoder.GetAttributeByUniqueId(dracoGeometry, attributeID);
|
|
} else {
|
|
attributeID = decoder.GetAttributeId(dracoGeometry, draco[attributeIDs[attributeName]]);
|
|
if (attributeID === -1)
|
|
continue;
|
|
attribute = decoder.GetAttribute(dracoGeometry, attributeID);
|
|
}
|
|
geometry.attributes.push(decodeAttribute(draco, decoder, dracoGeometry, attributeName, attributeType, attribute));
|
|
}
|
|
if (geometryType === draco.TRIANGULAR_MESH) {
|
|
geometry.index = decodeIndex(draco, decoder, dracoGeometry);
|
|
}
|
|
draco.destroy(dracoGeometry);
|
|
return geometry;
|
|
}
|
|
function decodeIndex(draco, decoder, dracoGeometry) {
|
|
const numFaces = dracoGeometry.num_faces();
|
|
const numIndices = numFaces * 3;
|
|
const byteLength = numIndices * 4;
|
|
const ptr = draco._malloc(byteLength);
|
|
decoder.GetTrianglesUInt32Array(dracoGeometry, byteLength, ptr);
|
|
const index = new Uint32Array(draco.HEAPF32.buffer, ptr, numIndices).slice();
|
|
draco._free(ptr);
|
|
return { array: index, itemSize: 1 };
|
|
}
|
|
function decodeAttribute(draco, decoder, dracoGeometry, attributeName, attributeType, attribute) {
|
|
const numComponents = attribute.num_components();
|
|
const numPoints = dracoGeometry.num_points();
|
|
const numValues = numPoints * numComponents;
|
|
const byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
|
|
const dataType = getDracoDataType(draco, attributeType);
|
|
const ptr = draco._malloc(byteLength);
|
|
decoder.GetAttributeDataArrayForAllPoints(dracoGeometry, attribute, dataType, byteLength, ptr);
|
|
const array = new attributeType(draco.HEAPF32.buffer, ptr, numValues).slice();
|
|
draco._free(ptr);
|
|
return {
|
|
name: attributeName,
|
|
array,
|
|
itemSize: numComponents
|
|
};
|
|
}
|
|
function getDracoDataType(draco, attributeType) {
|
|
switch (attributeType) {
|
|
case Float32Array:
|
|
return draco.DT_FLOAT32;
|
|
case Int8Array:
|
|
return draco.DT_INT8;
|
|
case Int16Array:
|
|
return draco.DT_INT16;
|
|
case Int32Array:
|
|
return draco.DT_INT32;
|
|
case Uint8Array:
|
|
return draco.DT_UINT8;
|
|
case Uint16Array:
|
|
return draco.DT_UINT16;
|
|
case Uint32Array:
|
|
return draco.DT_UINT32;
|
|
}
|
|
}
|
|
}
|
|
export {
|
|
DRACOLoader
|
|
};
|
|
//# sourceMappingURL=DRACOLoader.js.map
|