UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

272 lines (271 loc) 8.35 kB
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 };