* Scan a non-indexed geometry's position array and remove: * - triangles with any non-finite (NaN / ±Infinity) coordinate * - degenerate triangles whose area is below 1e-12 mm² * * Operates in-place by compacting the Float32Array and replacing the * BufferAttribute. Any existing normal attr
(geometry)
| 48 | * |
| 49 | * Operates in-place by compacting the Float32Array and replacing the |
| 50 | * BufferAttribute. Any existing normal attribute is deleted so that |
| 51 | * setupGeometry will recompute it on the clean data. |
| 52 | * |
| 53 | * Returns { nanCount, degenerateCount } so callers can warn the user. |
| 54 | */ |
| 55 | function validateAndCleanGeometry(geometry) { |
| 56 | const pos = geometry.attributes.position; |
| 57 | const src = pos.array; // Float32Array, 9 floats per triangle |
| 58 | const triCount = src.length / 9; |
| 59 | |
| 60 | let writeIdx = 0; |
| 61 | let nanCount = 0; |
| 62 | let degenerateCount = 0; |
| 63 | |
| 64 | for (let t = 0; t < triCount; t++) { |
| 65 | const b = t * 9; |
| 66 | const ax = src[b], ay = src[b+1], az = src[b+2]; |
| 67 | const bx = src[b+3], by = src[b+4], bz = src[b+5]; |
| 68 | const cx = src[b+6], cy = src[b+7], cz = src[b+8]; |
| 69 | |
| 70 | if (!isFinite(ax) || !isFinite(ay) || !isFinite(az) || |
| 71 | !isFinite(bx) || !isFinite(by) || !isFinite(bz) || |
| 72 | !isFinite(cx) || !isFinite(cy) || !isFinite(cz)) { |
| 73 | nanCount++; |
| 74 | continue; |
| 75 | } |
| 76 | |
| 77 | // Cross product of (B−A) × (C−A); skip if area² < 1e-24 (area < 1e-12) |
| 78 | const ux = bx-ax, uy = by-ay, uz = bz-az; |
| 79 | const vx = cx-ax, vy = cy-ay, vz = cz-az; |
| 80 | const area2 = (uy*vz-uz*vy)**2 + (uz*vx-ux*vz)**2 + (ux*vy-uy*vx)**2; |
| 81 | if (area2 < 1e-24) { |
| 82 | degenerateCount++; |
| 83 | continue; |
| 84 | } |
| 85 | |
| 86 | if (writeIdx !== b) { |
| 87 | src[writeIdx] = ax; src[writeIdx+1] = ay; src[writeIdx+2] = az; |
| 88 | src[writeIdx+3] = bx; src[writeIdx+4] = by; src[writeIdx+5] = bz; |
| 89 | src[writeIdx+6] = cx; src[writeIdx+7] = cy; src[writeIdx+8] = cz; |
| 90 | } |
| 91 | writeIdx += 9; |
| 92 | } |
| 93 | |
| 94 | const removed = nanCount + degenerateCount; |
| 95 | if (removed > 0) { |
| 96 | geometry.setAttribute('position', new THREE.BufferAttribute(src.slice(0, writeIdx), 3)); |
| 97 | geometry.deleteAttribute('normal'); // stale — recomputed below |
| 98 | } |
| 99 | |
| 100 | if (writeIdx === 0) { |
| 101 | throw new Error( |
| 102 | `All ${triCount} triangles in the mesh are invalid (${nanCount} NaN, ${degenerateCount} degenerate). Cannot load file.` |
| 103 | ); |
| 104 | } |
| 105 |