UNPKG

playcanvas

Version:

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

285 lines (283 loc) 8.89 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, 1: 1, 5: 2, 2: 3, 7: 4, 8: 5, 4: 6, 3: 7 }; const generateNormals = (vertices, indices)=>{ const subtract = (dst, a, b)=>{ dst[0] = a[0] - b[0]; dst[1] = a[1] - b[1]; dst[2] = a[2] - b[2]; }; const cross = (dst, a, b)=>{ dst[0] = a[1] * b[2] - b[1] * a[2]; dst[1] = a[2] * b[0] - b[2] * a[0]; dst[2] = a[0] * b[1] - b[0] * a[1]; }; const normalize = (dst, offset)=>{ const a = dst[offset + 0]; const b = dst[offset + 1]; const c = dst[offset + 2]; const l = 1.0 / Math.sqrt(a * a + b * b + c * c); 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, dataType: 6, 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((data)=>decode(data)); }); break; case 'decodeMesh': if (draco) { decode(data); } else { workQueue.push(data); } break; } }; } export { DracoWorker };