* Toggle displacement preview on/off. * When enabled: subdivides the current geometry to a moderate resolution, * computes smooth normals, and switches the viewer to the subdivided * geometry with vertex-shader displacement. * When disabled: reverts to the original geometry with bump-only previe
(enable)
| 4255 | const vA = new THREE.Vector3(), vB = new THREE.Vector3(), vC = new THREE.Vector3(); |
| 4256 | const e1 = new THREE.Vector3(), e2 = new THREE.Vector3(), fn = new THREE.Vector3(); |
| 4257 | |
| 4258 | for (let i = 0; i < count; i += 3) { |
| 4259 | vA.set(pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]); |
| 4260 | vB.set(pos[(i + 1) * 3], pos[(i + 1) * 3 + 1], pos[(i + 1) * 3 + 2]); |
| 4261 | vC.set(pos[(i + 2) * 3], pos[(i + 2) * 3 + 1], pos[(i + 2) * 3 + 2]); |
| 4262 | e1.subVectors(vB, vA); |
| 4263 | e2.subVectors(vC, vA); |
| 4264 | fn.crossVectors(e1, e2); |
| 4265 | const area = fn.length(); |
| 4266 | if (area < 1e-12) continue; |
| 4267 | for (let v = 0; v < 3; v++) { |
| 4268 | const vi = i + v; |
| 4269 | const id = vertId[vi]; |
| 4270 | snx[id] += nrm[vi * 3] * area; |
| 4271 | sny[id] += nrm[vi * 3 + 1] * area; |
| 4272 | snz[id] += nrm[vi * 3 + 2] * area; |
| 4273 | } |
| 4274 | } |
| 4275 | |
| 4276 | // Normalize accumulated normals |
| 4277 | for (let id = 0; id < uc; id++) { |
| 4278 | const len = Math.sqrt(snx[id] * snx[id] + sny[id] * sny[id] + snz[id] * snz[id]) || 1; |
| 4279 | snx[id] /= len; sny[id] /= len; snz[id] /= len; |
| 4280 | } |
| 4281 | |
| 4282 | // Write smoothNormal attribute via vertId lookup |
| 4283 | const sn = new Float32Array(count * 3); |
| 4284 | for (let i = 0; i < count; i++) { |
| 4285 | const id = vertId[i]; |
| 4286 | sn[i * 3] = snx[id]; sn[i * 3 + 1] = sny[id]; sn[i * 3 + 2] = snz[id]; |
| 4287 | } |
| 4288 | geometry.setAttribute('smoothNormal', new THREE.Float32BufferAttribute(sn, 3)); |
| 4289 | } |
| 4290 | |
| 4291 | // ── Precision masking ───────────────────────────────────────────────────────── |
| 4292 | |
| 4293 | /** Compute the target max edge length from the brush diameter. */ |
| 4294 | function computePrecisionEdgeLength(brushDiameter) { |
| 4295 | // ~20 edge segments around the brush circumference, clamped to a sane floor |
| 4296 | return Math.max(0.05, Math.PI * brushDiameter / 20); |
| 4297 | } |
| 4298 | |
| 4299 | /** |
| 4300 | * Estimate how many triangles subdivision will produce for a given edge length. |
| 4301 | * Uses a sample of existing edges to compute average edge length, then |
| 4302 | * assumes area-proportional subdivision: triCount × (avgEdge / target)². |
| 4303 | */ |
| 4304 | function estimateSubdivisionTriCount(geometry, targetEdge) { |
| 4305 | const pos = geometry.attributes.position; |
| 4306 | const triCount = pos.count / 3; |
| 4307 | // Sample up to 3000 edges (1000 triangles × 3 edges) |
| 4308 | const sampleTris = Math.min(triCount, 1000); |
| 4309 | let totalEdgeLen = 0; |
| 4310 | let edgeCount = 0; |
| 4311 | for (let t = 0; t < sampleTris; t++) { |
| 4312 | const i = t * 3; |
| 4313 | for (let e = 0; e < 3; e++) { |
| 4314 | const a = i + e, b = i + (e + 1) % 3; |
no test coverage detected