import * as linalg from './linalg'; function parseTriangle(triangleData) { const dv = new DataView(triangleData.buffer); function data(idx) { const offset = 4 * idx; return dv.getFloat32(offset, /*littleEndian=*/true); } const attributeByteCount = dv.getUint16(48, /*littleEndian=*/true); console.assert(attributeByteCount === 0); const x = [data(3), data(4), data(5)]; const y = [data(6), data(7), data(8)]; const z = [data(9), data(10), data(11)]; const n = linalg.cross(linalg.diff(y, x), linalg.diff(z, y)); return { vertices: [x, y, z], normals: new Array(3).fill(linalg.scale(n, 1 / linalg.norm(n))), textures: new Array(3).fill([0, 0]), }; // no normals, default texture } export async function loadStlModel(url) { const stlDataStream = (await fetch(url)).body; const triangles = []; return new Promise((resolve, reject) => { let bytesReceived = 0; let gotHeader = false; const partial = new Uint8Array(50); // each triangle is 50 bytes let partialOffset = 0; const reader = stlDataStream.getReader(); function pump() { reader.read().then(({ done, value }) => { if (done) { return resolve(triangles); } const skipped = bytesReceived; bytesReceived += value.length; let inputOffset = 0; if (!gotHeader) { if (bytesReceived < 84) { // header + triangle count return pump(); } else { gotHeader = true; inputOffset = 84 - skipped; } } // parse triangle data while (true) { const spaceLeft = 50 - partialOffset; console.assert(spaceLeft > 0); const leftToCopy = value.length - inputOffset; const toCopy = value.subarray(inputOffset, inputOffset + Math.min(leftToCopy, spaceLeft)); partial.set(toCopy, partialOffset); if (leftToCopy < spaceLeft) { break; } // parse a triangle! triangles.push(parseTriangle(partial)); partialOffset = 0; inputOffset += toCopy.length; } pump(); }); } pump(); }); }