Deactivate precision masking and bake the refined mesh as the new base geometry.
()
| 4061 | refineLenSlider.value = result.edge; |
| 4062 | refineLenVal.value = result.edge; |
| 4063 | checkResolutionWarning(); |
| 4064 | scheduleDisplacementPreviewResolutionRefresh(); |
| 4065 | |
| 4066 | // Set max-tri via the slider's existing input event so settings.maxTriangles |
| 4067 | // and the displayed label stay consistent with all other slider drag paths. |
| 4068 | maxTriSlider.value = d.recommendedMaxTri; |
| 4069 | maxTriSlider.dispatchEvent(new Event('input', { bubbles: true })); |
| 4070 | |
| 4071 | const maxLabel = formatM(d.recommendedMaxTri); |
| 4072 | const clampedNote = d.budgetClamped |
| 4073 | ? ` <span class="clamped">[${t('ui.smartResBudgetCapped')}]</span>` |
| 4074 | : ''; |
| 4075 | smartResInfo.innerHTML = tHtml('ui.smartResInfo', { |
| 4076 | edge: result.edge.toFixed(2), |
| 4077 | ppe: d.pixelsPerEdge.toFixed(1), |
| 4078 | pix: d.pixMm.toFixed(3), |
| 4079 | area: (d.surfaceArea / 100).toFixed(0), // cm² |
| 4080 | tris: maxLabel, |
| 4081 | }) + clampedNote; |
| 4082 | smartResInfo.classList.remove('hidden'); |
| 4083 | } |
| 4084 | |
| 4085 | function updateSmartResBtnState() { |
| 4086 | if (!smartResBtn) return; |
| 4087 | smartResBtn.disabled = !(currentGeometry && activeMapEntry); |
| 4088 | } |
| 4089 | |
| 4090 | if (smartResBtn) smartResBtn.addEventListener('click', applySmartResolution); |
| 4091 | |
| 4092 | /** |
| 4093 | * Refresh the per-vertex layer attributes the preview shader reads on a |
| 4094 | * geometry: `layerMask` (vec4, one channel per layer slot — 1 = textured, |
| 4095 | * 0 = user-excluded, between = soft brush), `layerFalloff` (vec4, boundary |
| 4096 | * falloff per slot) and `boundaryMaskTypeAttr` (active layer's boundary |
| 4097 | * type: 0 = user mask, 1 = angle mask). Angle masking stays in the shader. |
| 4098 | * |
| 4099 | * Attributes are created fresh when the geometry's size differs (so Three.js |
| 4100 | * allocates a new WebGL buffer) and flagged needsUpdate otherwise. |
| 4101 | * |
| 4102 | * forceFalloff: recompute the boundary falloff even while a masking tool is |
| 4103 | * active (a freshly built display mesh needs it for its initial state). |
| 4104 | */ |
| 4105 | function updateFaceMask(geometry, { forceFalloff = false } = {}) { |
| 4106 | if (!geometry) return; |
| 4107 | const posCount = geometry.attributes.position.count; |
| 4108 | const slots = _layerSlots(); |
| 4109 | |
| 4110 | // Per-slot coverage on this geometry's corners (null = fully textured) and |
| 4111 | // per-face user-mask flags (null = none) for the falloff passes. |
| 4112 | const covers = [], hards = []; |
| 4113 | let activeSlot = -1; |
| 4114 | for (const { index, isActive } of slots) { |
| 4115 | if (isActive) activeSlot = covers.length; |
| 4116 | const r = _layerCoverOn(index, geometry); |
no test coverage detected