playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
272 lines (271 loc) • 8.35 kB
JavaScript
function DracoWorker(jsUrl, wasmUrl) {
let draco;
const POSITION_ATTRIBUTE = 0;
const NORMAL_ATTRIBUTE = 1;
const wrap = (typedArray, dataType) => {
switch (dataType) {
case draco.DT_INT8:
return new Int8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
case draco.DT_INT16:
return new Int16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);
case draco.DT_INT32:
return new Int32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
case draco.DT_UINT8:
return new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
case draco.DT_UINT16:
return new Uint16Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 2);
case draco.DT_UINT32:
return new Uint32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
case draco.DT_FLOAT32:
return new Float32Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength / 4);
}
return null;
};
const componentSizeInBytes = (dataType) => {
switch (dataType) {
case draco.DT_INT8:
return 1;
case draco.DT_INT16:
return 2;
case draco.DT_INT32:
return 4;
case draco.DT_UINT8:
return 1;
case draco.DT_UINT16:
return 2;
case draco.DT_UINT32:
return 4;
case draco.DT_FLOAT32:
return 4;
}
return 1;
};
const toEngineDataType = (dataType) => {
switch (dataType) {
case draco.DT_INT8:
return 0;
case draco.DT_UINT8:
return 1;
case draco.DT_INT16:
return 2;
case draco.DT_UINT16:
return 3;
case draco.DT_INT32:
return 4;
case draco.DT_UINT32:
return 5;
case draco.DT_FLOAT32:
return 6;
default:
return 6;
}
};
const attributeSizeInBytes = (attribute) => {
return attribute.num_components() * componentSizeInBytes(attribute.data_type());
};
const attributeOrder = {
0: 0,
// position
1: 1,
// normal
5: 2,
// tangent
2: 3,
// color
7: 4,
// joints
8: 5,
// weights
4: 6,
// generic (used for blend indices and weights)
3: 7
// texcoord
};
const generateNormals = (vertices, indices) => {
const subtract = (dst, a2, b2) => {
dst[0] = a2[0] - b2[0];
dst[1] = a2[1] - b2[1];
dst[2] = a2[2] - b2[2];
};
const cross = (dst, a2, b2) => {
dst[0] = a2[1] * b2[2] - b2[1] * a2[2];
dst[1] = a2[2] * b2[0] - b2[2] * a2[0];
dst[2] = a2[0] * b2[1] - b2[0] * a2[1];
};
const normalize = (dst, offset) => {
const a2 = dst[offset + 0];
const b2 = dst[offset + 1];
const c2 = dst[offset + 2];
const l = 1 / Math.sqrt(a2 * a2 + b2 * b2 + c2 * c2);
dst[offset + 0] *= l;
dst[offset + 1] *= l;
dst[offset + 2] *= l;
};
const copy = (dst, src, srcOffset) => {
for (let i = 0; i < 3; ++i) {
dst[i] = src[srcOffset + i];
}
};
const numTriangles = indices.length / 3;
const numVertices = vertices.length / 3;
const result = new Float32Array(vertices.length);
const a = [0, 0, 0], b = [0, 0, 0], c = [0, 0, 0], t1 = [0, 0, 0], t2 = [0, 0, 0], n = [0, 0, 0];
for (let i = 0; i < numTriangles; ++i) {
const v0 = indices[i * 3 + 0] * 3;
const v1 = indices[i * 3 + 1] * 3;
const v2 = indices[i * 3 + 2] * 3;
copy(a, vertices, v0);
copy(b, vertices, v1);
copy(c, vertices, v2);
subtract(t1, b, a);
subtract(t2, c, a);
cross(n, t1, t2);
normalize(n, 0);
for (let j = 0; j < 3; ++j) {
result[v0 + j] += n[j];
result[v1 + j] += n[j];
result[v2 + j] += n[j];
}
}
for (let i = 0; i < numVertices; ++i) {
normalize(result, i * 3);
}
return new Uint8Array(result.buffer);
};
const decodeMesh = (inputBuffer) => {
const result = {};
const buffer = new draco.DecoderBuffer();
buffer.Init(inputBuffer, inputBuffer.length);
const decoder = new draco.Decoder();
if (decoder.GetEncodedGeometryType(buffer) !== draco.TRIANGULAR_MESH) {
result.error = "Failed to decode draco mesh: not a mesh";
return result;
}
const mesh = new draco.Mesh();
const status = decoder.DecodeBufferToMesh(buffer, mesh);
if (!status || !status.ok() || draco.getPointer(mesh) === 0) {
result.error = "Failed to decode draco asset";
return result;
}
const numIndices = mesh.num_faces() * 3;
const shortIndices = mesh.num_points() <= 65535;
const indicesSize = numIndices * (shortIndices ? 2 : 4);
const indicesPtr = draco._malloc(indicesSize);
if (shortIndices) {
decoder.GetTrianglesUInt16Array(mesh, indicesSize, indicesPtr);
result.indices = new Uint16Array(draco.HEAPU16.buffer, indicesPtr, numIndices).slice().buffer;
} else {
decoder.GetTrianglesUInt32Array(mesh, indicesSize, indicesPtr);
result.indices = new Uint32Array(draco.HEAPU32.buffer, indicesPtr, numIndices).slice().buffer;
}
draco._free(indicesPtr);
const attributes = [];
for (let i = 0; i < mesh.num_attributes(); ++i) {
attributes.push(decoder.GetAttribute(mesh, i));
}
attributes.sort((a, b) => {
return (attributeOrder[a.attribute_type()] ?? attributeOrder.length) - (attributeOrder[b.attribute_type()] ?? attributeOrder.length);
});
let totalVertexSize = 0;
const offsets = attributes.map((a) => {
const offset = totalVertexSize;
totalVertexSize += Math.ceil(attributeSizeInBytes(a) / 4) * 4;
return offset;
});
const hasNormals = attributes.some((a) => a.attribute_type() === NORMAL_ATTRIBUTE);
let normalOffset = offsets[1] ?? 0;
if (!hasNormals) {
normalOffset = offsets[0] + Math.ceil(attributeSizeInBytes(attributes[0]) / 4) * 4;
for (let i = 1; i < offsets.length; ++i) {
offsets[i] += 12;
}
totalVertexSize += 12;
}
result.attributes = attributes.map((a, i) => ({
id: a.unique_id(),
dataType: toEngineDataType(a.data_type()),
numComponents: a.num_components(),
offset: offsets[i]
}));
if (!hasNormals) {
result.attributes.splice(1, 0, {
id: -1,
// special id to indicate generated normals
dataType: 6,
// TYPE_FLOAT32
numComponents: 3,
offset: normalOffset
});
}
result.stride = totalVertexSize;
result.vertices = new ArrayBuffer(mesh.num_points() * totalVertexSize);
const dst = new Uint8Array(result.vertices);
for (let i = 0; i < mesh.num_attributes(); ++i) {
const attribute = attributes[i];
const sizeInBytes = attributeSizeInBytes(attribute);
const ptrSize = mesh.num_points() * sizeInBytes;
const ptr = draco._malloc(ptrSize);
decoder.GetAttributeDataArrayForAllPoints(mesh, attribute, attribute.data_type(), ptrSize, ptr);
const src = new Uint8Array(draco.HEAPU8.buffer, ptr, ptrSize);
for (let j = 0; j < mesh.num_points(); ++j) {
for (let c = 0; c < sizeInBytes; ++c) {
dst[j * totalVertexSize + offsets[i] + c] = src[j * sizeInBytes + c];
}
}
if (!hasNormals && attribute.attribute_type() === POSITION_ATTRIBUTE) {
const normals = generateNormals(
wrap(src, attribute.data_type()),
shortIndices ? new Uint16Array(result.indices) : new Uint32Array(result.indices)
);
for (let j = 0; j < mesh.num_points(); ++j) {
for (let c = 0; c < 12; ++c) {
dst[j * totalVertexSize + normalOffset + c] = normals[j * 12 + c];
}
}
}
draco._free(ptr);
}
draco.destroy(mesh);
draco.destroy(decoder);
draco.destroy(buffer);
return result;
};
const decode = (data) => {
const result = decodeMesh(new Uint8Array(data.buffer));
self.postMessage({
jobId: data.jobId,
error: result.error,
indices: result.indices,
vertices: result.vertices,
attributes: result.attributes,
stride: result.stride
}, [result.indices, result.vertices].filter((t) => t != null));
};
const workQueue = [];
self.onmessage = (message) => {
const data = message.data;
switch (data.type) {
case "init":
self.DracoDecoderModule({
instantiateWasm: (imports, successCallback) => {
WebAssembly.instantiate(data.module, imports).then((result) => successCallback(result)).catch((reason) => console.error(`instantiate failed + ${reason}`));
return {};
}
}).then((instance) => {
draco = instance;
workQueue.forEach((data2) => decode(data2));
});
break;
case "decodeMesh":
if (draco) {
decode(data);
} else {
workQueue.push(data);
}
break;
}
};
}
export {
DracoWorker
};