mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
3c9bcfd75c
Write binary STL directly from BufferGeometry typed arrays using Uint8Array.set() bulk copies. Eliminates the Three.js STLExporter overhead: no Mesh/Material creation, no identity matrix multiplication, no redundant normal recomputation, no per-float DataView calls.
72 lines
2.6 KiB
JavaScript
72 lines
2.6 KiB
JavaScript
/**
|
||
* 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);
|
||
}
|