/** * Fast binary STL exporter — writes directly from BufferGeometry arrays. * * Eliminates Three.js STLExporter overhead: * - No Mesh/Material creation * - No identity matrix multiplication per vertex * - No redundant normal recomputation * - Bulk Uint8Array.set() instead of per-float DataView calls * * @param {THREE.BufferGeometry} geometry – non-indexed with position + normal * @param {string} [filename] */ export function exportSTL(geometry, filename = 'textured.stl') { const posArr = geometry.attributes.position.array; const norArr = geometry.attributes.normal ? geometry.attributes.normal.array : null; const triCount = (posArr.length / 9) | 0; // Binary STL: 80-byte header + 4-byte tri count + 50 bytes per triangle const bufLen = 84 + 50 * triCount; const buffer = new ArrayBuffer(bufLen); const bytes = new Uint8Array(buffer); const view = new DataView(buffer); // Header: 80 bytes (already zero-filled) view.setUint32(80, triCount, true); // Reinterpret source arrays as raw bytes for bulk copy const posSrc = new Uint8Array(posArr.buffer, posArr.byteOffset, posArr.byteLength); const norSrc = norArr ? new Uint8Array(norArr.buffer, norArr.byteOffset, norArr.byteLength) : null; for (let i = 0; i < triCount; i++) { const dst = 84 + i * 50; const srcOff = i * 36; // 9 floats * 4 bytes if (norSrc) { // Normal: copy first vertex normal (12 bytes) — flat shading, all 3 identical bytes.set(norSrc.subarray(srcOff, srcOff + 12), dst); } else { // Compute face normal from cross product const b = i * 9; const ux = posArr[b+3]-posArr[b], uy = posArr[b+4]-posArr[b+1], uz = posArr[b+5]-posArr[b+2]; const vx = posArr[b+6]-posArr[b], vy = posArr[b+7]-posArr[b+1], vz = posArr[b+8]-posArr[b+2]; const nx = uy*vz-uz*vy, ny = uz*vx-ux*vz, nz = ux*vy-uy*vx; const len = Math.sqrt(nx*nx + ny*ny + nz*nz) || 1; view.setFloat32(dst, nx/len, true); view.setFloat32(dst + 4, ny/len, true); view.setFloat32(dst + 8, nz/len, true); } // Vertices: 36 bytes (3 vertices * 3 floats * 4 bytes) bytes.set(posSrc.subarray(srcOff, srcOff + 36), dst + 12); // Attribute byte count: 0 (already zero-filled) } // Download const blob = new Blob([buffer], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 10000); }