* Build a mapping from each subdivided face to its nearest original face * using a grid-accelerated nearest-centroid lookup, with face normal * tiebreaking to prevent boundary faces from being mapped to the wrong * original face (e.g. a subdivided face on a cube edge mapped to the * adjacent fac
(subdivGeo)
| 3694 | // Count triangles in this shell |
| 3695 | let count = 0; |
| 3696 | for (let tt = 0; tt < triCount; tt++) if (shellIds[tt] === s) count++; |
| 3697 | const outPos = new Float32Array(count * 9); |
| 3698 | const outNrm = srcNrm ? new Float32Array(count * 9) : null; |
| 3699 | let dst = 0; |
| 3700 | for (let tt = 0; tt < triCount; tt++) { |
| 3701 | if (shellIds[tt] !== s) continue; |
| 3702 | const src = tt * 9; |
| 3703 | outPos.set(srcPos.subarray(src, src + 9), dst); |
| 3704 | if (outNrm) outNrm.set(srcNrm.subarray(src, src + 9), dst); |
| 3705 | dst += 9; |
| 3706 | } |
| 3707 | const geo = new THREE.BufferGeometry(); |
| 3708 | geo.setAttribute('position', new THREE.BufferAttribute(outPos, 3)); |
| 3709 | if (outNrm) geo.setAttribute('normal', new THREE.BufferAttribute(outNrm, 3)); |
| 3710 | addDiagFaces(geo, SHELL_COLORS[s % SHELL_COLORS.length], 0.55); |
| 3711 | } |
| 3712 | } else if (kind === 'intersects' && lastAdvancedDiag && lastAdvancedDiag.intersectFaces) { |
| 3713 | const geo = buildExclusionOverlayGeo(currentGeometry, lastAdvancedDiag.intersectFaces); |
| 3714 | addDiagFaces(geo, 0xff0000, 0.7, true); |
| 3715 | } else if (kind === 'bodyIntersects' && lastAdvancedDiag && lastAdvancedDiag.bodyIntersectFaces) { |
| 3716 | const geo = buildExclusionOverlayGeo(currentGeometry, lastAdvancedDiag.bodyIntersectFaces); |
| 3717 | addDiagFaces(geo, 0xf59e0b, 0.7, true); |
| 3718 | } else if (kind === 'overlaps' && lastAdvancedDiag && lastAdvancedDiag.overlapFaces) { |
| 3719 | const geo = buildExclusionOverlayGeo(currentGeometry, lastAdvancedDiag.overlapFaces); |
| 3720 | addDiagFaces(geo, 0xf59e0b, 0.7); |
| 3721 | } |
| 3722 | } |
| 3723 | |
| 3724 | /** |
| 3725 | * Build a single issue line element with a "Show" toggle button. |
| 3726 | * @param {string} text – the issue description |
| 3727 | * @param {string} kind – highlight kind key |
| 3728 | * @returns {HTMLElement} |
| 3729 | */ |
| 3730 | function makeDiagLine(text, kind) { |
| 3731 | const row = document.createElement('div'); |
| 3732 | row.style.cssText = 'display:flex;justify-content:space-between;align-items:baseline;gap:8px'; |
| 3733 | const span = document.createElement('span'); |
| 3734 | span.textContent = '\u26a0 ' + text; |
| 3735 | const btn = document.createElement('button'); |
| 3736 | btn.className = 'diag-show-btn'; |
| 3737 | btn.dataset.kind = kind; |
| 3738 | btn.textContent = activeDiagHighlight === kind ? t('diag.hide') : t('diag.show'); |
| 3739 | btn.addEventListener('click', () => toggleDiagHighlight(kind)); |
| 3740 | row.appendChild(span); |
| 3741 | row.appendChild(btn); |
| 3742 | return row; |
| 3743 | } |
| 3744 | |
| 3745 | function renderFastDiag(diag) { |
| 3746 | meshDiagFast.innerHTML = ''; |
| 3747 | |
| 3748 | const defects = diag.openEdges > 0 || diag.nonManifoldEdges > 0; |
| 3749 | if (!defects) meshDiagFast.textContent = t('diag.meshOk'); |
| 3750 | if (diag.openEdges > 0) |
| 3751 | meshDiagFast.appendChild(makeDiagLine(t('diag.openEdges', { n: diag.openEdges }), 'openEdges')); |
| 3752 | if (diag.nonManifoldEdges > 0) |
| 3753 | meshDiagFast.appendChild(makeDiagLine(t('diag.nonManifoldEdges', { n: diag.nonManifoldEdges }), 'nonManifold')); |