* World-space "period" of the texture along U and V — i.e. how many world * millimetres correspond to one full UV repeat. Mirrors the math in * mapping.js (computeUV → applyTransform). * * Returns { periodU_mm, periodV_mm }. Undefined directions (rare) fall back * to the longest planar period
(settings, bounds)
| 65 | * not pick a degenerate axis. |
| 66 | */ |
| 67 | function computeWorldPeriod(settings, bounds) { |
| 68 | // settings.scaleU/scaleV ARE the world period in mm (absolute tile size) — |
| 69 | // identical across all mapping modes by construction. Only the aspect |
| 70 | // correction from mapping.js (effective scale = scale / aspect) remains. |
| 71 | const aspectU = settings.textureAspectU ?? 1; |
| 72 | const aspectV = settings.textureAspectV ?? 1; |
| 73 | return { |
| 74 | periodU_mm: (settings.scaleU || 1e-6) / aspectU, |
| 75 | periodV_mm: (settings.scaleV || 1e-6) / aspectV, |
| 76 | }; |
| 77 | } |
| 78 | |
| 79 | // ── Subdivision triangle-count simulator ───────────────────────────────────── |
| 80 | // |
| 81 | // Walks the actual subdivide-pass logic shape-by-shape, using law-of-cosines |
| 82 | // medians for the 1→2 and 1→3 child-edge lengths. Aggressively memoised on |
| 83 | // quantised (sorted-descending) edge tuples so duplicate CAD-tessellation |
| 84 | // triangles cost O(1). |
| 85 | // |
| 86 | // Per-triangle simulation matches global subdivide() because edge marking is |
| 87 | // purely a function of edge length (L > T?) — same decision regardless of |
| 88 | // which triangle the marked edge belongs to. Empirically within ~5 % of the |
| 89 | // real subdivide() output across 3DBenchy, Barry Bear, Grip70mm, cone, |
| 90 | // cubeWithSmallFillets, laserPlate, and puerta texturized — vs the legacy |
| 91 | // closed-form K · area / edge² which underestimates by 3–7×. |
| 92 | |
| 93 | function simTri(a, b, c, T, memo, depth) { |
| 94 | // Sort descending: a ≥ b ≥ c. |
| 95 | if (a < b) { const t = a; a = b; b = t; } |
| 96 | if (b < c) { const t = b; b = c; c = t; } |
| 97 | if (a < b) { const t = a; a = b; b = t; } |
| 98 | |
| 99 | // Quantise relative to T for cache. 256 bins per multiple of T → sub-percent |
| 100 | // shape-resolution, ample for triangle-count accounting. |
| 101 | const ka = Math.round((a / T) * 256); |
| 102 | const kb = Math.round((b / T) * 256); |
| 103 | const kc = Math.round((c / T) * 256); |
| 104 | const key = ka * 0x40000000 + kb * 0x10000 + kc; |
| 105 | const cached = memo.get(key); |
| 106 | if (cached !== undefined) return cached; |
| 107 | |
| 108 | // Match subdivide()'s 12-pass outer cap so deep slivers behave identically. |
no outgoing calls
no test coverage detected