Refresh (or initially build) the precision mesh from current brush size.
()
| 4114 | for (const { index, isActive } of slots) { |
| 4115 | if (isActive) activeSlot = covers.length; |
| 4116 | const r = _layerCoverOn(index, geometry); |
| 4117 | covers.push(r.cover); |
| 4118 | hards.push(r.hardMasked); |
| 4119 | } |
| 4120 | |
| 4121 | const maskAttr = _ensureAttr(geometry, 'layerMask', posCount, 4, 0.0); |
| 4122 | const m = maskAttr.array; |
| 4123 | const nSlots = covers.length; |
| 4124 | for (let i = 0; i < posCount; i++) { |
| 4125 | const o = i * 4; |
| 4126 | for (let k = 0; k < 4; k++) { |
| 4127 | const c = covers[k]; |
| 4128 | m[o + k] = k < nSlots ? (c ? c[i] : 1) : 0; |
| 4129 | } |
| 4130 | } |
| 4131 | maskAttr.needsUpdate = true; |
| 4132 | |
| 4133 | // Ensure faceNormal attribute exists (needed by shader for angle masking). |
| 4134 | // For the original geometry normal == faceNormal; for subdivided geometry |
| 4135 | // addFaceNormals() is called after subdivision, but guard here in case the |
| 4136 | // attribute is still missing. |
| 4137 | if (!geometry.attributes.faceNormal) { |
| 4138 | addFaceNormals(geometry); |
| 4139 | } |
| 4140 | |
| 4141 | // Ensure falloff attributes exist so the shader doesn't read 0.0 for missing |
| 4142 | // attributes (which would make every weight 0 → entire model appears masked). |
| 4143 | // This matters when a fresh geometry is displayed while the masking tool is |
| 4144 | // active (e.g. a freshly flattened paint mesh) because the expensive recomputation |
| 4145 | // below is intentionally skipped during active masking. |
| 4146 | const falloffAttr = _ensureAttr(geometry, 'layerFalloff', posCount, 4, 1.0); |
| 4147 | const typeAttr = _ensureAttr(geometry, 'boundaryMaskTypeAttr', posCount, 1, 1.0); |
| 4148 | |
| 4149 | // Skip expensive per-vertex falloff and boundary edge recomputation while |
| 4150 | // actively masking; both will be recalculated when the masking tool is |
| 4151 | // deactivated (in setExclusionTool → updateFaceMask with exclusionTool=null). |
| 4152 | if (forceFalloff || (!exclusionTool && (_falloffDirty || geometry !== _falloffGeometry))) { |
| 4153 | const f = falloffAttr.array; |
| 4154 | f.fill(1.0); |
| 4155 | typeAttr.array.fill(1.0); |
| 4156 | for (let k = 0; k < slots.length; k++) { |
| 4157 | const { index, isActive } = slots[k]; |
| 4158 | const ls = isActive ? settings : layers[index].settings; |
| 4159 | const dist = ls.boundaryFalloff ?? 0; |
| 4160 | if (dist <= 0) continue; |
| 4161 | const r = computeBoundaryFalloff(geometry, hards[k], dist, ls.boundaryFalloffCurve); |
| 4162 | if (!r) continue; |
| 4163 | for (let i = 0; i < posCount; i++) f[i * 4 + k] = r.falloff[i]; |
| 4164 | if (isActive) typeAttr.array.set(r.maskType); |
| 4165 | } |
| 4166 | falloffAttr.needsUpdate = true; |
| 4167 | typeAttr.needsUpdate = true; |
| 4168 | if (!exclusionTool) computeBoundaryEdges(geometry, activeSlot >= 0 ? hards[activeSlot] : null, settings.boundaryFalloff ?? 0); |
| 4169 | _falloffDirty = false; |
| 4170 | _falloffGeometry = geometry; |
| 4171 | } |
| 4172 | syncBoundaryEdgeUniforms(); |
| 4173 | requestRender(); |
no test coverage detected