axstack 0.36.1 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +5 -4
  2. package/bin/axstack.js +4 -18
  3. package/docs/getting-started.md +6 -6
  4. package/docs/host-operations.md +9 -5
  5. package/docs/installation.md +20 -41
  6. package/docs/workflows.md +12 -9
  7. package/package.json +1 -1
  8. package/profiles/presets/claude-only.json +3 -3
  9. package/profiles/presets/codex-only.json +3 -3
  10. package/profiles/presets/mixed.json +5 -4
  11. package/skills/axstack/references/automations.md +7 -2
  12. package/skills/axstack/references/lifecycle.md +2 -1
  13. package/skills/axstack/references/review-manager-prompt.md +44 -3
  14. package/skills/axstack/references/role-roster.md +13 -6
  15. package/skills/axstack/references/routing.md +2 -1
  16. package/skills/axstack/references/run-record.md +2 -0
  17. package/skills/axstack/references/ui-verification.md +6 -10
  18. package/skills/axstack-anatomy/ANATOMY.md +172 -0
  19. package/skills/axstack-anatomy/CHANGELOG.md +70 -0
  20. package/skills/axstack-anatomy/LICENSE +21 -0
  21. package/skills/axstack-anatomy/PROVENANCE.md +21 -0
  22. package/skills/axstack-anatomy/README.md +316 -0
  23. package/skills/axstack-anatomy/SKILL.md +74 -0
  24. package/skills/axstack-anatomy/SOURCE-PROOF.json +107 -0
  25. package/skills/axstack-anatomy/examples/arcade-cabinet/arcade-cabinet.css +44 -0
  26. package/skills/axstack-anatomy/examples/arcade-cabinet/build.mjs +714 -0
  27. package/skills/axstack-anatomy/examples/arcade-cabinet/live.js +597 -0
  28. package/skills/axstack-anatomy/examples/arcade-cabinet/parts.mjs +89 -0
  29. package/skills/axstack-anatomy/examples/arcade-cabinet/screen.mjs +114 -0
  30. package/skills/axstack-anatomy/examples/desk-computer/build.mjs +435 -0
  31. package/skills/axstack-anatomy/examples/dial-indicator/build.mjs +275 -0
  32. package/skills/axstack-anatomy/examples/raptor-engine/build.mjs +938 -0
  33. package/skills/axstack-anatomy/examples/raptor-engine/live.js +907 -0
  34. package/skills/axstack-anatomy/examples/raptor-engine/raptor.css +359 -0
  35. package/skills/axstack-anatomy/examples/ripple-tank/build.mjs +558 -0
  36. package/skills/axstack-anatomy/examples/ripple-tank/live.js +579 -0
  37. package/skills/axstack-anatomy/examples/ripple-tank/model.mjs +131 -0
  38. package/skills/axstack-anatomy/examples/ripple-tank/ripple.css +103 -0
  39. package/skills/axstack-anatomy/examples/spot-plate/art.tsx +110 -0
  40. package/skills/axstack-anatomy/examples/spot-plate/drops.ts +541 -0
  41. package/skills/axstack-anatomy/examples/spot-plate/fan.ts +84 -0
  42. package/skills/axstack-anatomy/examples/spot-plate/figure.tsx +25 -0
  43. package/skills/axstack-anatomy/examples/spot-plate/geometry.ts +280 -0
  44. package/skills/axstack-anatomy/examples/spot-plate/live.tsx +339 -0
  45. package/skills/axstack-anatomy/examples/spot-plate/rig.ts +230 -0
  46. package/skills/axstack-anatomy/examples/spot-plate/spot-plate.css +92 -0
  47. package/skills/axstack-anatomy/examples/test-rig/art.tsx +196 -0
  48. package/skills/axstack-anatomy/examples/test-rig/figure.tsx +23 -0
  49. package/skills/axstack-anatomy/examples/test-rig/geometry.ts +305 -0
  50. package/skills/axstack-anatomy/examples/test-rig/live.tsx +511 -0
  51. package/skills/axstack-anatomy/examples/test-rig/pose.ts +107 -0
  52. package/skills/axstack-anatomy/examples/test-rig/rig.ts +170 -0
  53. package/skills/axstack-anatomy/examples/test-rig/stretch.css +80 -0
  54. package/skills/axstack-anatomy/examples/test-rig/view.ts +99 -0
  55. package/skills/axstack-anatomy/examples/turning-dial/build.mjs +479 -0
  56. package/skills/axstack-anatomy/examples/turning-dial/dial.css +10 -0
  57. package/skills/axstack-anatomy/examples/turning-dial/live.js +342 -0
  58. package/skills/axstack-anatomy/examples/turning-dial/stress.mjs +269 -0
  59. package/skills/axstack-anatomy/kit/audit.mjs +1913 -0
  60. package/skills/axstack-anatomy/kit/canvas-painter.mjs +722 -0
  61. package/skills/axstack-anatomy/kit/gl-shared.mjs +184 -0
  62. package/skills/axstack-anatomy/kit/gl.mjs +184 -0
  63. package/skills/axstack-anatomy/kit/iso-kit.mjs +674 -0
  64. package/skills/axstack-anatomy/kit/iso-kit.ts +842 -0
  65. package/skills/axstack-anatomy/kit/iso.css +106 -0
  66. package/skills/axstack-anatomy/kit/lathe.mjs +1184 -0
  67. package/skills/axstack-anatomy/kit/react/draw.tsx +205 -0
  68. package/skills/axstack-anatomy/kit/react/live.tsx +140 -0
  69. package/skills/axstack-anatomy/kit/tube.mjs +306 -0
  70. package/skills/axstack-anatomy/kit/turn-audit.mjs +1096 -0
  71. package/skills/axstack-anatomy/kit/turn-build.mjs +1355 -0
  72. package/skills/axstack-anatomy/kit/turn-fixed.mjs +3800 -0
  73. package/skills/axstack-anatomy/kit/turn.mjs +3610 -0
  74. package/skills/axstack-anatomy/references/3d.md +352 -0
  75. package/skills/axstack-anatomy/references/craft.md +325 -0
  76. package/skills/axstack-anatomy/references/kit.md +419 -0
  77. package/skills/axstack-anatomy/references/motion.md +165 -0
  78. package/skills/axstack-anatomy/references/react.md +42 -0
  79. package/skills/axstack-anatomy/references/verify.md +351 -0
  80. package/skills/axstack-anatomy/references/walkthrough.md +75 -0
  81. package/skills/axstack-anatomy/references/webgl.md +473 -0
  82. package/skills/axstack-anatomy/scripts/audit-affine-bounds.test.mjs +54 -0
  83. package/skills/axstack-anatomy/scripts/audit-cache.test.mjs +37 -0
  84. package/skills/axstack-anatomy/scripts/audit-clearance-envelope.test.mjs +39 -0
  85. package/skills/axstack-anatomy/scripts/audit-empty-clipping.test.mjs +38 -0
  86. package/skills/axstack-anatomy/scripts/audit-order-convergence.test.mjs +55 -0
  87. package/skills/axstack-anatomy/scripts/audit-path-origin.test.mjs +26 -0
  88. package/skills/axstack-anatomy/scripts/audit-profile.test.mjs +41 -0
  89. package/skills/axstack-anatomy/scripts/audit-projection.test.mjs +36 -0
  90. package/skills/axstack-anatomy/scripts/audit-slide-clearance.test.mjs +58 -0
  91. package/skills/axstack-anatomy/scripts/audit-svg-attributes.test.mjs +46 -0
  92. package/skills/axstack-anatomy/scripts/audit-taper.test.mjs +29 -0
  93. package/skills/axstack-anatomy/scripts/capture-selector.test.mjs +131 -0
  94. package/skills/axstack-anatomy/scripts/capture.mjs +228 -0
  95. package/skills/axstack-anatomy/scripts/capture.test.mjs +42 -0
  96. package/skills/axstack-anatomy/scripts/chrome-port.mjs +36 -0
  97. package/skills/axstack-anatomy/scripts/chrome-startup.test.mjs +38 -0
  98. package/skills/axstack-anatomy/scripts/drive-actions.test.mjs +46 -0
  99. package/skills/axstack-anatomy/scripts/drive.mjs +302 -0
  100. package/skills/axstack-anatomy/scripts/drive.test.mjs +72 -0
  101. package/skills/axstack-anatomy/scripts/enter-key.test.mjs +38 -0
  102. package/skills/axstack-anatomy/scripts/fidelity-cut.test.mjs +54 -0
  103. package/skills/axstack-anatomy/scripts/fidelity-tube-coverage.test.mjs +40 -0
  104. package/skills/axstack-anatomy/scripts/inline-kit.mjs +26 -0
  105. package/skills/axstack-anatomy/scripts/line-ends.mjs +306 -0
  106. package/skills/axstack-anatomy/scripts/lines-arcs.test.mjs +50 -0
  107. package/skills/axstack-anatomy/scripts/lines-bezier.test.mjs +28 -0
  108. package/skills/axstack-anatomy/scripts/lines-round.test.mjs +56 -0
  109. package/skills/axstack-anatomy/scripts/lines-rounded-rect.test.mjs +32 -0
  110. package/skills/axstack-anatomy/scripts/lines.mjs +31 -0
  111. package/skills/axstack-anatomy/scripts/lines.test.mjs +38 -0
  112. package/skills/axstack-anatomy/scripts/navigation.test.mjs +55 -0
  113. package/skills/axstack-anatomy/scripts/orbit-sampling.test.mjs +50 -0
  114. package/skills/axstack-anatomy/scripts/react-dots-opacity.test.mjs +51 -0
  115. package/skills/axstack-anatomy/scripts/react-readout-first-write.test.mjs +37 -0
  116. package/skills/axstack-anatomy/scripts/react-solid-seamless.test.mjs +47 -0
  117. package/skills/axstack-anatomy/scripts/react-visibility-target.test.mjs +38 -0
  118. package/skills/axstack-anatomy/scripts/turn-bench.mjs +74 -0
  119. package/skills/axstack-anatomy/scripts/turn-check.mjs +641 -0
  120. package/skills/axstack-anatomy/scripts/turn-fidelity.mjs +413 -0
  121. package/skills/axstack-anatomy/scripts/turn-group-reference.test.mjs +40 -0
  122. package/skills/axstack-anatomy/scripts/turn-split-owner.test.mjs +62 -0
  123. package/skills/axstack-anatomy/theme.js +56 -0
  124. package/skills/axstack-explain/SKILL.md +28 -15
  125. package/skills/axstack-explain/references/delivery.md +22 -0
  126. package/skills/{axstack-diagram → axstack-explain}/references/fidelity.md +1 -7
  127. package/skills/axstack-explain/references/inline-pages.md +8 -8
  128. package/skills/axstack-explain/references/visual-qa.md +3 -5
  129. package/skills/axstack-explain/references/whiteboard-defense.md +59 -0
  130. package/skills/axstack-research/SKILL.md +18 -6
  131. package/src/archify-legacy.js +70 -0
  132. package/src/capabilities.js +1 -29
  133. package/src/installer.js +11 -31
  134. package/skills/axstack-diagram/SKILL.md +0 -28
  135. package/skills/axstack-diagram/references/archify.md +0 -76
  136. package/src/archify-pin.js +0 -5
  137. package/src/archify.js +0 -151
@@ -0,0 +1,3610 @@
1
+ const T3_ROOT = Math.sqrt(1.6);
2
+ const T3_TAU = Math.PI * 2;
3
+ const T3_RAD = Math.PI / 180;
4
+ const T3_LB = [0, 0.2, 0.42, 0.62];
5
+ const T3_EB = [0.25, 0.5, 0.75];
6
+ const T3_SOFT = 0.035;
7
+ const T3_LEVELS = 32;
8
+ const T3_Q = 64;
9
+ const t3State = { detail: 1, exact: false };
10
+
11
+ function t3Num(v) {
12
+ if (t3State.exact) return String(Math.round(v * 1e6) / 1e6);
13
+ let t = Math.round(v * 100);
14
+ if (t === 0) return "0";
15
+ let sign = "";
16
+ if (t < 0) {
17
+ sign = "-";
18
+ t = -t;
19
+ }
20
+ const whole = Math.floor(t / 100);
21
+ const part = t - whole * 100;
22
+ if (part === 0) return sign + whole;
23
+ if (part < 10) return sign + whole + ".0" + part;
24
+ return sign + whole + "." + (part % 10 === 0 ? part / 10 : part);
25
+ }
26
+
27
+ function t3Fix(v, scale) {
28
+ const t = Math.round(v * scale) / scale;
29
+ return t === 0 ? "0" : String(t);
30
+ }
31
+
32
+ function t3Path(buf, count, closed) {
33
+ if (count < 2) return "";
34
+ let s = "M" + t3Num(buf[0]) + " " + t3Num(buf[1]);
35
+ for (let i = 1; i < count; i++) s += "L" + t3Num(buf[2 * i]) + " " + t3Num(buf[2 * i + 1]);
36
+ return closed ? s + "Z" : s;
37
+ }
38
+
39
+ const t3Step = (a, b, x) => {
40
+ const t = Math.min(1, Math.max(0, (x - a) / (b - a)));
41
+ return t * t * (3 - 2 * t);
42
+ };
43
+
44
+ function t3Level(score, bands) {
45
+ let u = 0;
46
+ for (let i = 0; i < bands.length; i++) u += t3Step(bands[i] - T3_SOFT, bands[i] + T3_SOFT, score);
47
+ return Math.round(u * T3_Q);
48
+ }
49
+
50
+ function t3Grow(f, name, size, Kind) {
51
+ if (!f[name] || f[name].length < size) f[name] = new Kind(Math.max(size, f[name] ? f[name].length * 2 : 16));
52
+ return f[name];
53
+ }
54
+
55
+ function t3Box(buf, count, box) {
56
+ let x0 = Infinity;
57
+ let y0 = Infinity;
58
+ let x1 = -Infinity;
59
+ let y1 = -Infinity;
60
+ for (let i = 0; i < count; i++) {
61
+ const x = buf[2 * i];
62
+ const y = buf[2 * i + 1];
63
+ if (x < x0) x0 = x;
64
+ if (x > x1) x1 = x;
65
+ if (y < y0) y0 = y;
66
+ if (y > y1) y1 = y;
67
+ }
68
+ box[0] = x0;
69
+ box[1] = y0;
70
+ box[2] = x1;
71
+ box[3] = y1;
72
+ }
73
+
74
+ function t3Unit(v) {
75
+ const l = Math.hypot(v[0], v[1], v[2]) || 1;
76
+ return [v[0] / l, v[1] / l, v[2] / l];
77
+ }
78
+
79
+ function t3Rot(axis, degrees) {
80
+ const [x, y, z] = t3Unit(axis);
81
+ const c = Math.cos(degrees * T3_RAD);
82
+ const s = Math.sin(degrees * T3_RAD);
83
+ const C = 1 - c;
84
+ return new Float64Array([c + x * x * C, x * y * C - z * s, x * z * C + y * s, y * x * C + z * s, c + y * y * C, y * z * C - x * s, z * x * C - y * s, z * y * C + x * s, c + z * z * C]);
85
+ }
86
+
87
+ function t3Mul(A, B) {
88
+ const out = new Float64Array(9);
89
+ for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) out[r * 3 + c] = A[r * 3] * B[c] + A[r * 3 + 1] * B[3 + c] + A[r * 3 + 2] * B[6 + c];
90
+ return out;
91
+ }
92
+
93
+ const t3Apply = (R, v) => [R[0] * v[0] + R[1] * v[1] + R[2] * v[2], R[3] * v[0] + R[4] * v[1] + R[5] * v[2], R[6] * v[0] + R[7] * v[1] + R[8] * v[2]];
94
+
95
+ const T3_EYE = new Float64Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
96
+
97
+ function t3Camera(P, pose) {
98
+ const a = (P.azimuth ?? 45) * T3_RAD;
99
+ const e = (P.elevation ?? 30) * T3_RAD;
100
+ const k = P.scale * T3_ROOT;
101
+ const sa = Math.sin(a);
102
+ const ca = Math.cos(a);
103
+ const se = Math.sin(e);
104
+ const ce = Math.cos(e);
105
+ const cam = { m: new Float64Array(6), ox: 0, oy: 0, V: [0, 0, 0], L: [0, 0, 0], k, turn: 0, R: T3_EYE, t: [0, 0, 0], lx: 0, ly: 1, sa, ca, se, ce, origin: P.origin };
106
+ const lw = Math.hypot(sa, ca, 1.2);
107
+ if (pose && pose.R && !(pose.turn !== undefined && pose.root)) {
108
+ const R = pose.R;
109
+ const t = pose.t ?? [0, 0, 0];
110
+ const r0 = [sa * k, -ca * k, 0];
111
+ const r1 = [ca * se * k, sa * se * k, -ce * k];
112
+ for (let c = 0; c < 3; c++) {
113
+ cam.m[c] = r0[0] * R[c] + r0[1] * R[3 + c] + r0[2] * R[6 + c];
114
+ cam.m[3 + c] = r1[0] * R[c] + r1[1] * R[3 + c] + r1[2] * R[6 + c];
115
+ }
116
+ cam.ox = P.origin[0] + r0[0] * t[0] + r0[1] * t[1] + r0[2] * t[2];
117
+ cam.oy = P.origin[1] + r1[0] * t[0] + r1[1] * t[1] + r1[2] * t[2];
118
+ const V = [ca * ce, sa * ce, se];
119
+ const L = [-sa / lw, ca / lw, 1.2 / lw];
120
+ for (let c = 0; c < 3; c++) {
121
+ cam.V[c] = R[c] * V[0] + R[3 + c] * V[1] + R[6 + c] * V[2];
122
+ cam.L[c] = R[c] * L[0] + R[3 + c] * L[1] + R[6 + c] * L[2];
123
+ }
124
+ cam.R = R;
125
+ cam.t = t;
126
+ } else {
127
+ const turn = pose ? pose.turn ?? 0 : 0;
128
+ const pivot = pose && pose.pivot ? pose.pivot : [0, 0];
129
+ const b = a - turn * T3_RAD;
130
+ const sb = Math.sin(b);
131
+ const cb = Math.cos(b);
132
+ cam.m[0] = sb * k;
133
+ cam.m[1] = -cb * k;
134
+ cam.m[2] = 0;
135
+ cam.m[3] = cb * se * k;
136
+ cam.m[4] = sb * se * k;
137
+ cam.m[5] = -ce * k;
138
+ cam.ox = P.origin[0] + (pivot[0] * sa - pivot[1] * ca) * k;
139
+ cam.oy = P.origin[1] + (pivot[0] * ca + pivot[1] * sa) * se * k;
140
+ cam.V = [cb * ce, sb * ce, se];
141
+ cam.L = [-sb / lw, cb / lw, 1.2 / lw];
142
+ cam.turn = turn;
143
+ const c = Math.cos(turn * T3_RAD);
144
+ const s = Math.sin(turn * T3_RAD);
145
+ cam.R = new Float64Array([c, -s, 0, s, c, 0, 0, 0, 1]);
146
+ cam.t = [pivot[0], pivot[1], 0];
147
+ }
148
+ const lh = Math.hypot(cam.L[0], cam.L[1]) || 1;
149
+ cam.lx = cam.L[0] / lh;
150
+ cam.ly = cam.L[1] / lh;
151
+ return cam;
152
+ }
153
+
154
+ function t3Quat(q) {
155
+ const l = Math.hypot(q[0], q[1], q[2], q[3]) || 1;
156
+ const x = q[0] / l;
157
+ const y = q[1] / l;
158
+ const z = q[2] / l;
159
+ const w = q[3] / l;
160
+ return new Float64Array([1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w), 2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w), 2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)]);
161
+ }
162
+
163
+ function t3FreeRot(v) {
164
+ if (v.R) return v.R instanceof Float64Array ? v.R : Float64Array.from(v.R);
165
+ if (v.q) return t3Quat(v.q);
166
+ if (v.axis) return t3Rot(v.axis, v.angle ?? 0);
167
+ return T3_EYE;
168
+ }
169
+
170
+ function t3Poses(groups, values) {
171
+ const get = (name) => (values instanceof Map ? values.get(name) : values ? values[name] : undefined) ?? 0;
172
+ const list = [];
173
+ for (let index = 0; index < groups.length; index++) {
174
+ const g = groups[index];
175
+ const v = get(g.name);
176
+ let parent = g.parent === undefined || g.parent === null || g.parent < 0 ? null : list[g.parent];
177
+ let po = parent ? groups[g.parent].origin : [0, 0, 0];
178
+ let R = T3_EYE;
179
+ let t = [g.origin[0] - po[0], g.origin[1] - po[1], g.origin[2] - po[2]];
180
+ if (g.kind === "turn") R = t3Rot([0, 0, 1], v);
181
+ else if (g.kind === "hinge") R = t3Rot(g.axis, v);
182
+ else if (g.kind === "slide") {
183
+ const d = t3Unit(g.direction);
184
+ t[0] += d[0] * v;
185
+ t[1] += d[1] * v;
186
+ t[2] += d[2] * v;
187
+ } else if (g.kind === "free" && v && typeof v === "object") {
188
+ R = t3FreeRot(v);
189
+ if (v.world) {
190
+ parent = null;
191
+ po = [0, 0, 0];
192
+ t = [g.origin[0], g.origin[1], g.origin[2]];
193
+ }
194
+ if (v.t) {
195
+ t[0] += v.t[0];
196
+ t[1] += v.t[1];
197
+ t[2] += v.t[2];
198
+ }
199
+ }
200
+ if (!parent) {
201
+ const pose = { R, t, value: v };
202
+ if (g.kind === "turn") Object.assign(pose, { turn: v, pivot: [g.origin[0], g.origin[1]], root: true });
203
+ list.push(pose);
204
+ continue;
205
+ }
206
+ const tw = t3Apply(parent.R, t);
207
+ list.push({ R: t3Mul(parent.R, R), t: [tw[0] + parent.t[0], tw[1] + parent.t[1], tw[2] + parent.t[2]], value: v });
208
+ }
209
+ return list;
210
+ }
211
+
212
+ function t3PosesMap(groups, values) {
213
+ const list = t3Poses(groups, values);
214
+ return new Map(groups.map((g, index) => [g.name, list[index]]));
215
+ }
216
+
217
+ function t3Roles(part) {
218
+ const roles = [];
219
+ const spec = part.spec;
220
+ const details = part.details ?? {};
221
+ const add = (name, type, extra = {}) => roles.push({ name, type, ...extra });
222
+ const tone = part.tone ?? "hi";
223
+ const crease = part.crease ?? "faint";
224
+ const fadedSlots = (prefix, count, cls, lineTone) => {
225
+ for (let j = 0; j < count; j++) add(`${prefix}${j}`, "slot", { cls, tone: lineTone });
226
+ };
227
+ const planes = () => (part.planes ?? []).forEach((_, j) => add(`plane${j}`, "plane"));
228
+ const dots = () => (part.dots ?? []).forEach((dot, j) => add(`c${j}`, "dot", { tone: dot.tone ?? "mid", size: dot.size ?? 0.5 }));
229
+ const sheets = (top) => {
230
+ for (let t = 0; t < 4; t++) add(`s${t}`, "path", { cls: "iso-shade", shade: t });
231
+ if (top) add("s4", "path", { cls: "iso-top" });
232
+ };
233
+ const inner = () => {
234
+ if ((details.seams && details.seams.length) || (details.ribs && details.ribs.seams)) add("inner", "path", { cls: "iso-line", tone: "faint" });
235
+ };
236
+ const ribs = () => {
237
+ if (details.ribs) fadedSlots("r", spec.slots?.ribs ?? 6, "iso-line", details.ribs.tone ?? "lo");
238
+ };
239
+ if (part.kind === "prism") {
240
+ add("fill", "path", { cls: "iso-fill" });
241
+ const faces = spec.ring.length / 2 + (spec.up ? 0 : 2);
242
+ for (let j = 0; j < faces; j++) add(`f${j}`, "face");
243
+ if (spec.up) add("top", "xform", { cls: "iso-top" });
244
+ if (crease !== "none") add("crease", "path", { cls: "iso-line iso-crease", tone: crease });
245
+ if (spec.bevel && crease !== "none") fadedSlots("v", spec.slots?.bevel ?? 6, "iso-line iso-bevel", crease);
246
+ if (details.seams && details.seams.length) add("inner", "path", { cls: "iso-line", tone: "faint" });
247
+ planes();
248
+ add("edge", "path", { cls: "iso-line iso-edge", tone });
249
+ dots();
250
+ } else if (part.kind === "round" || part.kind === "lathe") {
251
+ add("fill", "path", { cls: "iso-fill" });
252
+ sheets(true);
253
+ const ends = part.kind === "round" ? spec.ends : spec.caps.map((cap) => (cap ? "flat" : "open"));
254
+ ends.forEach((end, j) => end === "flat" && add(`k${j}`, "face"));
255
+ if (crease !== "none") add("crease", "path", { cls: "iso-line iso-crease", tone: crease });
256
+ inner();
257
+ ribs();
258
+ planes();
259
+ add("edge", "path", { cls: "iso-line iso-edge", tone });
260
+ dots();
261
+ } else if (part.kind === "ball") {
262
+ add("fill", "path", { cls: "iso-fill" });
263
+ sheets(false);
264
+ spec.flats.forEach((_, j) => add(`f${j}`, "face"));
265
+ if (crease !== "none") add("crease", "path", { cls: "iso-line iso-crease", tone: crease });
266
+ planes();
267
+ add("edge", "path", { cls: "iso-line iso-edge", tone });
268
+ dots();
269
+ } else if (part.kind === "tube") {
270
+ add("body", "path", { cls: "tb-body" });
271
+ if (spec.wide) {
272
+ add("shine", "path", { cls: "tb-shine" });
273
+ add("shade", "path", { cls: "tb-shade" });
274
+ }
275
+ if (spec.rings) fadedSlots("r", spec.slots?.ribs ?? 6, "tb-ring", spec.rings.tone ?? "lo");
276
+ add("edges", "path", { cls: "iso-line tb-edge", tone });
277
+ } else if (part.kind === "fixed") {
278
+ add("move", "self");
279
+ ribs();
280
+ planes();
281
+ dots();
282
+ }
283
+ return roles;
284
+ }
285
+
286
+ function t3Planes(list) {
287
+ return (list ?? []).map((plane) => ({ o: plane.o, u: plane.u, v: plane.v, n: plane.n, fade: plane.fade ?? [0.04, 0.3], billboard: Boolean(plane.billboard) }));
288
+ }
289
+
290
+ function t3Dots(list) {
291
+ return (list ?? []).map((dot) => ({ p: dot.p, n: dot.n ?? null, size: dot.size ?? 0.5, fade: dot.fade ?? [0.04, 0.42] }));
292
+ }
293
+
294
+ function t3PreparePrism(run, part) {
295
+ const spec = part.spec;
296
+ const n = spec.ring.length / 2;
297
+ run.n = n;
298
+ run.up = Boolean(spec.up);
299
+ const ring = Float64Array.from(spec.ring);
300
+ run.ring = ring;
301
+ const nx = new Float64Array(n);
302
+ const ny = new Float64Array(n);
303
+ for (let i = 0; i < n; i++) {
304
+ const j = (i + 1) % n;
305
+ const dx = ring[2 * j] - ring[2 * i];
306
+ const dy = ring[2 * j + 1] - ring[2 * i + 1];
307
+ const l = Math.hypot(dx, dy) || 1;
308
+ nx[i] = dy / l;
309
+ ny[i] = -dx / l;
310
+ }
311
+ run.nx = nx;
312
+ run.ny = ny;
313
+ const F = spec.up ? { o: [0, 0, 0], a: [0, 0, 1], u: [1, 0, 0], v: [0, 1, 0] } : spec.F;
314
+ run.F = F;
315
+ run.s0 = spec.up ? spec.z0 : spec.s0;
316
+ run.s1 = spec.up ? spec.z1 : spec.s1;
317
+ const lo = new Float64Array(3 * n);
318
+ const hi = new Float64Array(3 * n);
319
+ const N3 = new Float64Array(3 * n);
320
+ for (let i = 0; i < n; i++) {
321
+ const x = ring[2 * i];
322
+ const y = ring[2 * i + 1];
323
+ for (let c = 0; c < 3; c++) {
324
+ const base = F.o[c] + F.u[c] * x + F.v[c] * y;
325
+ lo[3 * i + c] = base + F.a[c] * run.s0;
326
+ hi[3 * i + c] = base + F.a[c] * run.s1;
327
+ N3[3 * i + c] = F.u[c] * nx[i] + F.v[c] * ny[i];
328
+ }
329
+ }
330
+ run.lo = lo;
331
+ run.hi = hi;
332
+ run.N3 = N3;
333
+ run.sharp = new Uint8Array(n);
334
+ for (let i = 0; i < n; i++) {
335
+ const p = (i - 1 + n) % n;
336
+ run.sharp[i] = nx[p] * nx[i] + ny[p] * ny[i] < Math.cos(0.35) ? 1 : 0;
337
+ }
338
+ if (spec.bevel) {
339
+ const inset = new Float64Array(2 * n);
340
+ for (let i = 0; i < n; i++) {
341
+ const p = (i - 1 + n) % n;
342
+ const mx = nx[p] + nx[i];
343
+ const my = ny[p] + ny[i];
344
+ const l = Math.hypot(mx, my) || 1;
345
+ inset[2 * i] = ring[2 * i] - (mx / l) * spec.bevel;
346
+ inset[2 * i + 1] = ring[2 * i + 1] - (my / l) * spec.bevel;
347
+ }
348
+ run.inset = inset;
349
+ }
350
+ run.seams = part.details && part.details.seams ? part.details.seams.slice() : null;
351
+ run.cut = spec.cuts && spec.cuts.length ? spec.cuts : null;
352
+ }
353
+
354
+ function t3Axes(run, part) {
355
+ if (run.kind === "ball") return run.flats.map((flat) => flat.n);
356
+ if (run.kind === "tube") return run.tips.slice();
357
+ if (run.kind === "prism") {
358
+ const out = [run.F.a];
359
+ for (let i = 0; i < run.n; i++) {
360
+ const n = [run.N3[3 * i], run.N3[3 * i + 1], run.N3[3 * i + 2]];
361
+ if (out.every((m) => Math.abs(m[0] * n[0] + m[1] * n[1] + m[2] * n[2]) < 0.9995)) out.push(n);
362
+ }
363
+ return out;
364
+ }
365
+ if (run.F) return [run.F.a];
366
+ if (part.spec && part.spec.F) return [part.spec.F.a];
367
+ return [];
368
+ }
369
+
370
+ function t3Cloud(run, part) {
371
+ const out = [];
372
+ const ring = (F, s, r, count) => {
373
+ for (let j = 0; j < count; j++) {
374
+ const c = Math.cos((j / count) * T3_TAU);
375
+ const n = Math.sin((j / count) * T3_TAU);
376
+ out.push(F.o[0] + F.a[0] * s + r * (c * F.u[0] + n * F.v[0]), F.o[1] + F.a[1] * s + r * (c * F.u[1] + n * F.v[1]), F.o[2] + F.a[2] * s + r * (c * F.u[2] + n * F.v[2]));
377
+ }
378
+ };
379
+ if (run.kind === "prism") {
380
+ for (let i = 0; i < 3 * run.n; i++) out.push(run.lo[i]);
381
+ for (let i = 0; i < 3 * run.n; i++) out.push(run.hi[i]);
382
+ } else if (run.kind === "round") {
383
+ ring(run.F, run.s0, run.r0, 24);
384
+ ring(run.F, run.s1, run.r1, 24);
385
+ for (const [end, s, r] of [[0, run.s0, run.r0], [1, run.s1, run.r1]]) {
386
+ if (run.ends[end] !== "dome") continue;
387
+ const sign = end ? 1 : -1;
388
+ for (const b of [0.5, 1.0, 1.35]) ring(run.F, s + sign * r * Math.sin(b), r * Math.cos(b), 6);
389
+ out.push(run.F.o[0] + run.F.a[0] * (s + sign * r), run.F.o[1] + run.F.a[1] * (s + sign * r), run.F.o[2] + run.F.a[2] * (s + sign * r));
390
+ }
391
+ } else if (run.kind === "ball") {
392
+ const golden = Math.PI * (3 - Math.sqrt(5));
393
+ for (let i = 0; i < 64; i++) {
394
+ const y = 1 - (2 * (i + 0.5)) / 64;
395
+ const rad = Math.sqrt(1 - y * y);
396
+ const w = [Math.cos(golden * i) * rad, Math.sin(golden * i) * rad, y];
397
+ const p = [run.c[0] + run.r * w[0], run.c[1] + run.r * w[1], run.c[2] + run.r * w[2]];
398
+ let inside = true;
399
+ for (const flat of run.flats) if ((p[0] - run.c[0]) * flat.n[0] + (p[1] - run.c[1]) * flat.n[1] + (p[2] - run.c[2]) * flat.n[2] > flat.d + 1e-9) inside = false;
400
+ if (inside) out.push(p[0], p[1], p[2]);
401
+ }
402
+ for (const flat of run.flats)
403
+ for (let j = 0; j < 12; j++) {
404
+ const c = Math.cos((j / 12) * T3_TAU) * flat.rho;
405
+ const n = Math.sin((j / 12) * T3_TAU) * flat.rho;
406
+ out.push(run.c[0] + flat.n[0] * flat.d + c * flat.u[0] + n * flat.v[0], run.c[1] + flat.n[1] * flat.d + c * flat.u[1] + n * flat.v[1], run.c[2] + flat.n[2] * flat.d + c * flat.u[2] + n * flat.v[2]);
407
+ }
408
+ } else if (run.kind === "lathe") {
409
+ const lv = run.levels;
410
+ const step = Math.max(4, Math.floor(lv.length / 4 / 8)) * 4;
411
+ for (let i = 0; i < lv.length; i += step) ring(run.F, lv[i], lv[i + 1], 8);
412
+ ring(run.F, lv[lv.length - 4], lv[lv.length - 3], 8);
413
+ } else if (run.kind === "tube") {
414
+ const b = run.body;
415
+ const stride = Math.max(1, Math.floor(b.count / 8));
416
+ for (let i = 0; i < b.count; i += stride) {
417
+ const T = t3Unit([b.D3[3 * i], b.D3[3 * i + 1], b.D3[3 * i + 2]]);
418
+ const [u, v] = t3Basis(T);
419
+ ring({ o: [b.P3[3 * i], b.P3[3 * i + 1], b.P3[3 * i + 2]], a: T, u, v }, 0, run.r, 8);
420
+ }
421
+ } else if (part.cloud) for (const v of part.cloud) out.push(v);
422
+ return Float64Array.from(out);
423
+ }
424
+
425
+ function t3Unpack(value, grouped) {
426
+ if (!value) return new Float64Array(0);
427
+ if (typeof value !== "string") return Float64Array.from(value);
428
+ const text = atob(value);
429
+ const bytes = new Uint8Array(text.length);
430
+ for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i);
431
+ const view = new DataView(bytes.buffer);
432
+ const size = grouped ? 10 : 8;
433
+ const stride = grouped ? 6 : 5;
434
+ const count = bytes.length / size;
435
+ const out = new Float64Array(count * stride);
436
+ for (let e = 0; e < count; e++) {
437
+ let o = e * size;
438
+ const at = e * stride;
439
+ out[at] = view.getUint16(o);
440
+ out[at + 1] = view.getUint16(o + 2);
441
+ o += 4;
442
+ if (grouped) {
443
+ out[at + 2] = view.getUint16(o) - 1;
444
+ o += 2;
445
+ }
446
+ let x = (view.getUint16(o) / 65535) * 2 - 1;
447
+ let y = (view.getUint16(o + 2) / 65535) * 2 - 1;
448
+ const z = 1 - Math.abs(x) - Math.abs(y);
449
+ if (z < 0) {
450
+ const ox = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);
451
+ const oy = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);
452
+ x = ox;
453
+ y = oy;
454
+ }
455
+ const l = Math.hypot(x, y, z) || 1;
456
+ out[at + stride - 3] = x / l;
457
+ out[at + stride - 2] = y / l;
458
+ out[at + stride - 1] = z / l;
459
+ }
460
+ return out;
461
+ }
462
+
463
+ function t3Prepare(data, { unit = 1 } = {}) {
464
+ const P = data.P;
465
+ const runs = data.parts.map((part, index) => {
466
+ const run = { index, name: part.name, kind: part.kind, group: part.group ?? -1, layer: part.layer, part, roles: t3Roles(part), planes: t3Planes(part.planes), dots: t3Dots(part.dots), unit };
467
+ run.role = Object.fromEntries(run.roles.map((role, j) => [role.name, j]));
468
+ if (part.kind === "prism") t3PreparePrism(run, part);
469
+ else if (part.kind === "round") t3PrepareRound(run, part, P, unit);
470
+ else if (part.kind === "ball") t3PrepareBall(run, part, P, unit);
471
+ else if (part.kind === "lathe") t3PrepareLathe(run, part, P, unit);
472
+ else if (part.kind === "tube") t3PrepareTube(run, part, data);
473
+ else if (part.kind === "fixed") {
474
+ run.anchor = part.spec.anchor;
475
+ run.home = part.spec.home;
476
+ run.box0 = part.spec.box;
477
+ run.hull0 = Float64Array.from(part.spec.hull);
478
+ run.F = part.spec.F ?? null;
479
+ run.ribs = part.details && part.details.ribs ? part.details.ribs : null;
480
+ }
481
+ run.slots = part.spec.slots ?? {};
482
+ run.cloud = t3Cloud(run, part);
483
+ run.axes = t3Axes(run, part);
484
+ const cn = run.cloud.length / 3;
485
+ run.centroid = [0, 0, 0];
486
+ for (let i = 0; i < cn; i++) for (let c = 0; c < 3; c++) run.centroid[c] += run.cloud[3 * i + c] / Math.max(1, cn);
487
+ return run;
488
+ });
489
+ const layers = data.layers.map((layer, index) => {
490
+ const parts = Int32Array.from(layer.parts);
491
+ const local = new Map();
492
+ parts.forEach((global, at) => local.set(global, at));
493
+ const planar = t3Unpack(data.planes && data.planes[layer.name], false);
494
+ const dynamic = Int32Array.from((data.dynamic && data.dynamic[layer.name]) ?? []);
495
+ const free = Int32Array.from((data.free && data.free[layer.name]) ?? []);
496
+ const framed = t3Unpack(data.framed && data.framed[layer.name], true);
497
+ const groupPairs = [];
498
+ const raw = (data.groupPairs && data.groupPairs[layer.name]) ?? [];
499
+ const members = new Map();
500
+ for (const global of parts) {
501
+ const g = runs[global].group;
502
+ if (!members.has(g)) members.set(g, []);
503
+ members.get(g).push(global);
504
+ }
505
+ for (let p = 0; p < raw.length; p += 6) groupPairs.push({ a: Int32Array.from(members.get(raw[p]) ?? []), b: Int32Array.from(members.get(raw[p + 1]) ?? []), frame: raw[p + 2], n: [raw[p + 3], raw[p + 4], raw[p + 5]], boxA: new Float64Array(4), boxB: new Float64Array(4) });
506
+ const looseAt = new Uint8Array(parts.length);
507
+ for (const global of free) looseAt[local.get(global)] = 1;
508
+ return { index, name: layer.name, parts, local, rest: Int32Array.from(layer.rest ?? layer.parts), planar, framed, groupPairs, dynamic, free, looseAt, warm: new Map() };
509
+ });
510
+ return { P, data, unit, groups: data.groups, runs, layers };
511
+ }
512
+
513
+ function t3Frame(run) {
514
+ const count = run.roles.length;
515
+ return { d: new Array(count).fill(""), q: new Int32Array(count), w: new Float64Array(count).fill(-1), t: new Array(count).fill(""), o: new Float64Array(count).fill(1), box: new Float64Array(4), hull: new Float64Array(64), hullN: 0, cx: new Float64Array(count), cy: new Float64Array(count), quads: null, quadN: 0 };
516
+ }
517
+
518
+ const t3Buf = { a: new Float64Array(1024), b: new Float64Array(1024), c: new Float64Array(1024) };
519
+
520
+ function t3Scratch(name, size) {
521
+ if (t3Buf[name].length < size) t3Buf[name] = new Float64Array(size * 2);
522
+ return t3Buf[name];
523
+ }
524
+
525
+ function t3SetHull(f, buf, count) {
526
+ const hull = t3Grow(f, "hull", 2 * count, Float64Array);
527
+ for (let i = 0; i < 2 * count; i++) hull[i] = buf[i];
528
+ f.hullN = count;
529
+ }
530
+
531
+ function t3Faded(f, run, prefix, lines, count) {
532
+ if (!f.fade) f.fade = {};
533
+ const detail = t3State.detail;
534
+ if (detail < 1) for (let i = 0; i < count; i++) lines.alpha[i] *= detail;
535
+ if (detail < 0.01) count = 0;
536
+ f.fade[prefix] = { key: lines.key.slice(0, count), alpha: lines.alpha.slice(0, count) };
537
+ const slots = run.slots[prefix === "v" ? "bevel" : "ribs"] ?? 6;
538
+ const levels = run._levels ?? (run._levels = new Array(T3_LEVELS + 1));
539
+ for (let l = 0; l <= T3_LEVELS; l++) levels[l] = "";
540
+ for (let i = 0; i < count; i++) {
541
+ const alpha = lines.alpha[i];
542
+ const level = Math.min(T3_LEVELS, Math.round(alpha * T3_LEVELS));
543
+ if (level <= 0) continue;
544
+ levels[level] += lines.d[i];
545
+ }
546
+ const present = [];
547
+ for (let l = T3_LEVELS; l >= 1; l--) if (levels[l]) present.push([l, levels[l]]);
548
+ while (present.length > slots) {
549
+ let best = 1;
550
+ let gap = Infinity;
551
+ for (let i = 1; i < present.length; i++) {
552
+ const g = present[i - 1][0] - present[i][0];
553
+ if (g < gap) {
554
+ gap = g;
555
+ best = i;
556
+ }
557
+ }
558
+ const [a, da] = present[best - 1];
559
+ const [b, db] = present[best];
560
+ present.splice(best - 1, 2, [(a + b) / 2, da + db]);
561
+ }
562
+ for (let j = 0; j < slots; j++) {
563
+ const role = run.role[`${prefix}${j}`];
564
+ if (role === undefined) continue;
565
+ if (j < present.length) {
566
+ f.d[role] = present[j][1];
567
+ f.o[role] = present[j][0] / T3_LEVELS;
568
+ } else {
569
+ f.d[role] = "";
570
+ f.o[role] = 0;
571
+ }
572
+ }
573
+ return present.length;
574
+ }
575
+
576
+ const t3Lines = { d: [], alpha: [], key: [] };
577
+ const t3None = { d: [], alpha: [], key: [] };
578
+
579
+ function t3EmitPrism(run, cam, f) {
580
+ const n = run.n;
581
+ const m = cam.m;
582
+ const ox = cam.ox;
583
+ const oy = cam.oy;
584
+ const V = cam.V;
585
+ const L = cam.L;
586
+ const lo = run.lo;
587
+ const hi = run.hi;
588
+ const N3 = run.N3;
589
+ const bot = t3Scratch("a", 2 * n);
590
+ const top = t3Scratch("b", 2 * n);
591
+ for (let i = 0; i < n; i++) {
592
+ const a = 3 * i;
593
+ bot[2 * i] = ox + m[0] * lo[a] + m[1] * lo[a + 1] + m[2] * lo[a + 2];
594
+ bot[2 * i + 1] = oy + m[3] * lo[a] + m[4] * lo[a + 1] + m[5] * lo[a + 2];
595
+ top[2 * i] = ox + m[0] * hi[a] + m[1] * hi[a + 1] + m[2] * hi[a + 2];
596
+ top[2 * i + 1] = oy + m[3] * hi[a] + m[4] * hi[a + 1] + m[5] * hi[a + 2];
597
+ }
598
+ const F = run.F;
599
+ const aV = F.a[0] * V[0] + F.a[1] * V[1] + F.a[2] * V[2];
600
+ const near = aV >= 0 ? top : bot;
601
+ const far = aV >= 0 ? bot : top;
602
+ const facing = run._facing ?? (run._facing = new Float64Array(n));
603
+ for (let i = 0; i < n; i++) facing[i] = N3[3 * i] * V[0] + N3[3 * i + 1] * V[1] + N3[3 * i + 2] * V[2];
604
+ const on = (i) => facing[((i % n) + n) % n] > 1e-9;
605
+ let i0 = -1;
606
+ for (let i = 0; i < n; i++)
607
+ if (on(i) && !on(i - 1)) {
608
+ i0 = i;
609
+ break;
610
+ }
611
+ const sil = t3Scratch("c", 4 * n + 8);
612
+ const silAt = run._silAt ?? (run._silAt = new Int32Array(2 * n + 4));
613
+ let count = 0;
614
+ let i1 = i0;
615
+ const nearSet = aV >= 0 ? hi : lo;
616
+ const farSet = aV >= 0 ? lo : hi;
617
+ if (i0 < 0) {
618
+ for (let i = 0; i < n; i++) {
619
+ sil[2 * count] = near[2 * i];
620
+ sil[2 * count + 1] = near[2 * i + 1];
621
+ silAt[count] = i;
622
+ count++;
623
+ }
624
+ } else {
625
+ while (on(i1)) i1++;
626
+ for (let j = i0; j <= i1; j++) {
627
+ const i = j % n;
628
+ sil[2 * count] = far[2 * i];
629
+ sil[2 * count + 1] = far[2 * i + 1];
630
+ silAt[count] = -1 - i;
631
+ count++;
632
+ }
633
+ for (let j = i1; j <= i0 + n; j++) {
634
+ const i = j % n;
635
+ sil[2 * count] = near[2 * i];
636
+ sil[2 * count + 1] = near[2 * i + 1];
637
+ silAt[count] = i;
638
+ count++;
639
+ }
640
+ if (count > 2 && Math.abs(sil[0] - sil[2 * count - 2]) < 1e-9 && Math.abs(sil[1] - sil[2 * count - 1]) < 1e-9) count--;
641
+ }
642
+ const outline = t3Path(sil, count, true);
643
+ f.d[run.role.fill] = outline;
644
+ if (run.cut) {
645
+ const inPlane = (index) => {
646
+ const at = silAt[index];
647
+ const set = at < 0 ? farSet : nearSet;
648
+ const v = at < 0 ? -1 - at : at;
649
+ return run.cut.some((cut) => Math.abs(cut.n[0] * set[3 * v] + cut.n[1] * set[3 * v + 1] + cut.n[2] * set[3 * v + 2] - cut.d) < 1e-3);
650
+ };
651
+ const flags = new Uint8Array(count);
652
+ const onSame = (i, j) => {
653
+ const at = (index) => {
654
+ const a = silAt[index];
655
+ const set = a < 0 ? farSet : nearSet;
656
+ const v = a < 0 ? -1 - a : a;
657
+ return [set[3 * v], set[3 * v + 1], set[3 * v + 2]];
658
+ };
659
+ const p = at(i);
660
+ const q = at(j);
661
+ return run.cut.some((cut) => Math.abs(cut.n[0] * p[0] + cut.n[1] * p[1] + cut.n[2] * p[2] - cut.d) < 1e-3 && Math.abs(cut.n[0] * q[0] + cut.n[1] * q[1] + cut.n[2] * q[2] - cut.d) < 1e-3);
662
+ };
663
+ for (let j = 0; j < count; j++) flags[j] = inPlane(j) && onSame(j, (j + 1) % count) ? 1 : 0;
664
+ f.d[run.role.edge] = t3Broken(sil, count, flags);
665
+ } else f.d[run.role.edge] = outline;
666
+ t3Box(sil, count, f.box);
667
+ t3SetHull(f, sil, count);
668
+ const quad = new Float64Array(8);
669
+ for (let i = 0; i < n; i++) {
670
+ const role = run.role[`f${i}`];
671
+ if (!(facing[i] > 1e-6)) {
672
+ f.d[role] = "";
673
+ continue;
674
+ }
675
+ const j = (i + 1) % n;
676
+ quad[0] = top[2 * i];
677
+ quad[1] = top[2 * i + 1];
678
+ quad[2] = top[2 * j];
679
+ quad[3] = top[2 * j + 1];
680
+ quad[4] = bot[2 * j];
681
+ quad[5] = bot[2 * j + 1];
682
+ quad[6] = bot[2 * i];
683
+ quad[7] = bot[2 * i + 1];
684
+ f.d[role] = t3Path(quad, 4, true);
685
+ let score;
686
+ if (run.up) {
687
+ score = (run.nx[i] * cam.lx + run.ny[i] * cam.ly + 1) / 2;
688
+ f.q[role] = t3Level(score, T3_EB);
689
+ } else {
690
+ score = N3[3 * i] * L[0] + N3[3 * i + 1] * L[1] + N3[3 * i + 2] * L[2];
691
+ f.q[role] = t3Level(score, T3_LB);
692
+ }
693
+ const e1 = Math.hypot(quad[2] - quad[0], quad[3] - quad[1]);
694
+ const e2 = Math.hypot(quad[6] - quad[0], quad[7] - quad[1]);
695
+ const area = Math.abs((quad[2] - quad[0]) * (quad[7] - quad[1]) - (quad[3] - quad[1]) * (quad[6] - quad[0]));
696
+ const wide = area / (Math.max(e1, e2) || 1);
697
+ f.w[role] = wide < 0.4 ? Math.round(wide * 100) / 100 : -1;
698
+ }
699
+ if (run.up) {
700
+ const z = run.s1;
701
+ f.t[run.role.top] = `matrix(${t3Fix(m[0], 1e5)} ${t3Fix(m[3], 1e5)} ${t3Fix(m[1], 1e5)} ${t3Fix(m[4], 1e5)} ${t3Fix(ox + m[2] * z, 100)} ${t3Fix(oy + m[5] * z, 100)})`;
702
+ } else {
703
+ for (let side = 0; side < 2; side++) {
704
+ const role = run.role[`f${n + side}`];
705
+ const sign = side ? 1 : -1;
706
+ const face = sign * aV;
707
+ if (!(face > 1e-6)) {
708
+ f.d[role] = "";
709
+ continue;
710
+ }
711
+ const ring = side ? top : bot;
712
+ f.d[role] = t3Path(ring, n, true);
713
+ f.q[role] = t3Level(sign * (F.a[0] * L[0] + F.a[1] * L[1] + F.a[2] * L[2]), T3_LB);
714
+ let minor = Infinity;
715
+ let x0 = Infinity;
716
+ let x1 = -Infinity;
717
+ for (let i = 0; i < n; i++) {
718
+ const j = (i + 1) % n;
719
+ const ex = ring[2 * j] - ring[2 * i];
720
+ const ey = ring[2 * j + 1] - ring[2 * i + 1];
721
+ const l = Math.hypot(ex, ey);
722
+ if (l < 1e-9) continue;
723
+ let lo2 = Infinity;
724
+ let hi2 = -Infinity;
725
+ for (let q = 0; q < n; q++) {
726
+ const v = ((ring[2 * q] - ring[2 * i]) * ey - (ring[2 * q + 1] - ring[2 * i + 1]) * ex) / l;
727
+ if (v < lo2) lo2 = v;
728
+ if (v > hi2) hi2 = v;
729
+ }
730
+ minor = Math.min(minor, hi2 - lo2);
731
+ x0 = Math.min(x0, lo2);
732
+ x1 = Math.max(x1, hi2);
733
+ }
734
+ f.w[role] = minor < 0.4 ? Math.round(minor * 100) / 100 : -1;
735
+ }
736
+ }
737
+ if (run.role.crease !== undefined) {
738
+ let crease = "";
739
+ if (run.up) {
740
+ if (i0 >= 0) {
741
+ const buf = t3Scratch("a", 0);
742
+ let c = 0;
743
+ const rim = t3Scratch("c", 2 * n + 4);
744
+ for (let j = i0; j <= i1; j++) {
745
+ const i = j % n;
746
+ rim[2 * c] = top[2 * i];
747
+ rim[2 * c + 1] = top[2 * i + 1];
748
+ c++;
749
+ }
750
+ crease = t3Path(rim, c, false);
751
+ void buf;
752
+ }
753
+ } else {
754
+ const capOn = [-aV > 1e-6, aV > 1e-6];
755
+ const seg = new Float64Array(4);
756
+ for (let i = 0; i < n; i++) {
757
+ const p = (i - 1 + n) % n;
758
+ if (run.sharp[i] && facing[i] > 1e-6 && facing[p] > 1e-6) {
759
+ seg[0] = bot[2 * i];
760
+ seg[1] = bot[2 * i + 1];
761
+ seg[2] = top[2 * i];
762
+ seg[3] = top[2 * i + 1];
763
+ crease += t3Path(seg, 2, false);
764
+ }
765
+ }
766
+ for (let side = 0; side < 2; side++) {
767
+ if (!capOn[side] || i0 < 0) continue;
768
+ const ring = side ? top : bot;
769
+ const rim = t3Scratch("c", 2 * n + 4);
770
+ let c = 0;
771
+ for (let j = i0; j <= i1; j++) {
772
+ const i = j % n;
773
+ rim[2 * c] = ring[2 * i];
774
+ rim[2 * c + 1] = ring[2 * i + 1];
775
+ c++;
776
+ }
777
+ crease += t3Path(rim, c, false);
778
+ }
779
+ }
780
+ f.d[run.role.crease] = crease;
781
+ }
782
+ if (run.inset && run.role.v0 !== undefined && t3State.detail < 0.01) f.levels = t3Faded(f, run, "v", t3None, 0);
783
+ else if (run.inset && run.role.v0 !== undefined) {
784
+ const lines = t3Lines;
785
+ lines.d.length = 0;
786
+ lines.alpha.length = 0;
787
+ lines.key.length = 0;
788
+ lines.key.length = 0;
789
+ const capSide = aV >= 0 ? 1 : 0;
790
+ const s = capSide ? run.s1 : run.s0;
791
+ const capAlpha = run.up ? 1 : t3Step(0.02, 0.2, Math.abs(aV));
792
+ const ins = t3Scratch("c", 2 * n);
793
+ for (let i = 0; i < n; i++) {
794
+ const x = run.inset[2 * i];
795
+ const y = run.inset[2 * i + 1];
796
+ const px = F.o[0] + F.a[0] * s + F.u[0] * x + F.v[0] * y;
797
+ const py = F.o[1] + F.a[1] * s + F.u[1] * x + F.v[1] * y;
798
+ const pz = F.o[2] + F.a[2] * s + F.u[2] * x + F.v[2] * y;
799
+ ins[2 * i] = ox + m[0] * px + m[1] * py + m[2] * pz;
800
+ ins[2 * i + 1] = oy + m[3] * px + m[4] * py + m[5] * pz;
801
+ }
802
+ const rim = capSide ? top : bot;
803
+ const seg = new Float64Array(4);
804
+ for (let i = 0; i < n; i++) {
805
+ const p = (i - 1 + n) % n;
806
+ const ai = capAlpha * t3Step(0, 0.14, facing[i]);
807
+ const ap = capAlpha * t3Step(0, 0.14, facing[p]);
808
+ if (ai > 0.01) {
809
+ const j = (i + 1) % n;
810
+ seg[0] = ins[2 * i];
811
+ seg[1] = ins[2 * i + 1];
812
+ seg[2] = ins[2 * j];
813
+ seg[3] = ins[2 * j + 1];
814
+ lines.d.push(t3Path(seg, 2, false));
815
+ lines.alpha.push(ai);
816
+ lines.key.push(2 * i);
817
+ }
818
+ const corner = ai * (1 - ap) + ap * (1 - ai);
819
+ if (corner > 0.01) {
820
+ seg[0] = ins[2 * i];
821
+ seg[1] = ins[2 * i + 1];
822
+ seg[2] = rim[2 * i];
823
+ seg[3] = rim[2 * i + 1];
824
+ lines.d.push(t3Path(seg, 2, false));
825
+ lines.alpha.push(corner);
826
+ lines.key.push(2 * i + 1);
827
+ }
828
+ }
829
+ f.levels = t3Faded(f, run, "v", lines, lines.d.length);
830
+ }
831
+ if (run.seams && run.role.inner !== undefined) {
832
+ let inner = "";
833
+ if (i0 >= 0) {
834
+ const rim = t3Scratch("c", 2 * n + 4);
835
+ for (const s of run.seams) {
836
+ let c = 0;
837
+ for (let j = i0; j <= i1; j++) {
838
+ const i = j % n;
839
+ const x = run.ring[2 * i];
840
+ const y = run.ring[2 * i + 1];
841
+ const px = F.o[0] + F.a[0] * s + F.u[0] * x + F.v[0] * y;
842
+ const py = F.o[1] + F.a[1] * s + F.u[1] * x + F.v[1] * y;
843
+ const pz = F.o[2] + F.a[2] * s + F.u[2] * x + F.v[2] * y;
844
+ rim[2 * c] = ox + m[0] * px + m[1] * py + m[2] * pz;
845
+ rim[2 * c + 1] = oy + m[3] * px + m[4] * py + m[5] * pz;
846
+ c++;
847
+ }
848
+ inner += t3Path(rim, c, false);
849
+ }
850
+ }
851
+ f.d[run.role.inner] = inner;
852
+ }
853
+ }
854
+
855
+ function t3Broken(buf, count, skip) {
856
+ let start = -1;
857
+ for (let j = 0; j < count; j++)
858
+ if (skip[(j - 1 + count) % count] && !skip[j]) {
859
+ start = j;
860
+ break;
861
+ }
862
+ if (start < 0) return skip[0] ? "" : t3Path(buf, count, true);
863
+ let out = "";
864
+ let run = [];
865
+ for (let step = 0; step < count; step++) {
866
+ const j = (start + step) % count;
867
+ if (skip[j]) {
868
+ if (run.length) {
869
+ run.push((j + 0) % count);
870
+ out += t3PathOf(run.map((i) => [buf[2 * i], buf[2 * i + 1]]), false);
871
+ run = [];
872
+ }
873
+ continue;
874
+ }
875
+ run.push(j);
876
+ }
877
+ if (run.length) {
878
+ run.push((start + count) % count);
879
+ out += t3PathOf(run.map((i) => [buf[2 * i], buf[2 * i + 1]]), false);
880
+ }
881
+ return out;
882
+ }
883
+
884
+ function t3EmitPlanes(run, cam, f, sx = 0, sy = 0) {
885
+ const m = cam.m;
886
+ const V = cam.V;
887
+ for (let j = 0; j < run.planes.length; j++) {
888
+ const plane = run.planes[j];
889
+ const role = run.role[`plane${j}`];
890
+ const o = plane.o;
891
+ const X = cam.ox - sx + m[0] * o[0] + m[1] * o[1] + m[2] * o[2];
892
+ const Y = cam.oy - sy + m[3] * o[0] + m[4] * o[1] + m[5] * o[2];
893
+ const facing = plane.n[0] * V[0] + plane.n[1] * V[1] + plane.n[2] * V[2];
894
+ const alpha = Math.round(t3Step(plane.fade[0], plane.fade[1], facing) * 100) / 100;
895
+ f.o[role] = alpha;
896
+ if (plane.billboard) {
897
+ f.t[role] = `translate(${t3Num(X)} ${t3Num(Y)})`;
898
+ continue;
899
+ }
900
+ const u = plane.u;
901
+ const v = plane.v;
902
+ const a = m[0] * u[0] + m[1] * u[1] + m[2] * u[2];
903
+ const b = m[3] * u[0] + m[4] * u[1] + m[5] * u[2];
904
+ const c = m[0] * v[0] + m[1] * v[1] + m[2] * v[2];
905
+ const d = m[3] * v[0] + m[4] * v[1] + m[5] * v[2];
906
+ f.t[role] = `matrix(${t3Fix(a, 1e5)} ${t3Fix(b, 1e5)} ${t3Fix(c, 1e5)} ${t3Fix(d, 1e5)} ${t3Fix(X, 100)} ${t3Fix(Y, 100)})`;
907
+ }
908
+ }
909
+
910
+ function t3EmitDots(run, cam, f, sx = 0, sy = 0) {
911
+ const m = cam.m;
912
+ const V = cam.V;
913
+ for (let j = 0; j < run.dots.length; j++) {
914
+ const dot = run.dots[j];
915
+ const role = run.role[`c${j}`];
916
+ const p = dot.p;
917
+ f.cx[role] = cam.ox - sx + m[0] * p[0] + m[1] * p[1] + m[2] * p[2];
918
+ f.cy[role] = cam.oy - sy + m[3] * p[0] + m[4] * p[1] + m[5] * p[2];
919
+ const alpha = (dot.n ? t3Step(dot.fade[0], dot.fade[1], dot.n[0] * V[0] + dot.n[1] * V[1] + dot.n[2] * V[2]) : 1) * t3State.detail;
920
+ f.o[role] = alpha < 0.005 ? 0 : Math.round(alpha * 100) / 100;
921
+ }
922
+ }
923
+
924
+ function t3EmitFixed(run, cam, f) {
925
+ const a = run.anchor;
926
+ const m = cam.m;
927
+ const X = cam.ox + m[0] * a[0] + m[1] * a[1] + m[2] * a[2];
928
+ const Y = cam.oy + m[3] * a[0] + m[4] * a[1] + m[5] * a[2];
929
+ const dx = X - run.home[0];
930
+ const dy = Y - run.home[1];
931
+ const rx = Math.round(dx * 100) / 100;
932
+ const ry = Math.round(dy * 100) / 100;
933
+ f.t[run.role.move] = rx || ry ? `translate(${t3Num(rx)} ${t3Num(ry)})` : "";
934
+ f.shiftX = rx;
935
+ f.shiftY = ry;
936
+ const b = run.box0;
937
+ f.box[0] = b[0] + rx;
938
+ f.box[1] = b[1] + ry;
939
+ f.box[2] = b[2] + rx;
940
+ f.box[3] = b[3] + ry;
941
+ const count = run.hull0.length / 2;
942
+ const hull = t3Grow(f, "hull", 2 * count, Float64Array);
943
+ for (let i = 0; i < count; i++) {
944
+ hull[2 * i] = run.hull0[2 * i] + rx;
945
+ hull[2 * i + 1] = run.hull0[2 * i + 1] + ry;
946
+ }
947
+ f.hullN = count;
948
+ if (run.ribs && run.F) t3EmitRibs(run, cam, f, run.F, run.ribs, rx, ry);
949
+ t3EmitPlanes(run, cam, f, rx, ry);
950
+ t3EmitDots(run, cam, f, rx, ry);
951
+ }
952
+
953
+ function t3EmitRibs(run, cam, f, F, ribs, sx, sy) {
954
+ if (t3State.detail < 0.01) {
955
+ f.levels = t3Faded(f, run, "r", t3None, 0);
956
+ return;
957
+ }
958
+ const m = cam.m;
959
+ const V = cam.V;
960
+ const lines = t3Lines;
961
+ lines.d.length = 0;
962
+ lines.alpha.length = 0;
963
+ lines.key.length = 0;
964
+ const { s0, s1, count, phase = 0, twist = 0, fade = [0.1, 0.55] } = ribs;
965
+ const r0 = ribs.r0 ?? ribs.r;
966
+ const r1 = ribs.r1 ?? r0;
967
+ const slope = (r1 - r0) / (s1 - s0 || 1);
968
+ const norm = Math.hypot(1, slope);
969
+ const aV = F.a[0] * V[0] + F.a[1] * V[1] + F.a[2] * V[2];
970
+ const uV = F.u[0] * V[0] + F.u[1] * V[1] + F.u[2] * V[2];
971
+ const vV = F.v[0] * V[0] + F.v[1] * V[1] + F.v[2] * V[2];
972
+ const seg = new Float64Array(4);
973
+ const at = (s, r, t, out, index) => {
974
+ const c = Math.cos(t);
975
+ const n = Math.sin(t);
976
+ const px = F.o[0] + F.a[0] * s + r * (c * F.u[0] + n * F.v[0]);
977
+ const py = F.o[1] + F.a[1] * s + r * (c * F.u[1] + n * F.v[1]);
978
+ const pz = F.o[2] + F.a[2] * s + r * (c * F.u[2] + n * F.v[2]);
979
+ out[index] = cam.ox - sx + m[0] * px + m[1] * py + m[2] * pz;
980
+ out[index + 1] = cam.oy - sy + m[3] * px + m[4] * py + m[5] * pz;
981
+ };
982
+ for (let i = 0; i < count; i++) {
983
+ const t = phase + (i / count) * T3_TAU;
984
+ const tm = t + twist / 2;
985
+ const facing = (Math.cos(tm) * uV + Math.sin(tm) * vV - slope * aV) / norm;
986
+ const alpha = t3Step(fade[0], fade[1], facing);
987
+ if (alpha * T3_LEVELS < 0.5) continue;
988
+ at(s0, r0, t, seg, 0);
989
+ at(s1, r1, t + twist, seg, 2);
990
+ lines.d.push(t3Path(seg, 2, false));
991
+ lines.alpha.push(alpha);
992
+ lines.key.push(i);
993
+ }
994
+ f.levels = t3Faded(f, run, "r", lines, lines.d.length);
995
+ }
996
+
997
+
998
+ function t3Geo(F, cam) {
999
+ const m = cam.m;
1000
+ const V = cam.V;
1001
+ const L = cam.L;
1002
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
1003
+ const lin = (w) => [m[0] * w[0] + m[1] * w[1] + m[2] * w[2], m[3] * w[0] + m[4] * w[1] + m[5] * w[2]];
1004
+ const O = [cam.ox + m[0] * F.o[0] + m[1] * F.o[1] + m[2] * F.o[2], cam.oy + m[3] * F.o[0] + m[4] * F.o[1] + m[5] * F.o[2]];
1005
+ const aV = dot(F.a, V);
1006
+ const uV = dot(F.u, V);
1007
+ const vV = dot(F.v, V);
1008
+ const aL = dot(F.a, L);
1009
+ const uL = dot(F.u, L);
1010
+ const vL = dot(F.v, L);
1011
+ return { O, SA: lin(F.a), SU: lin(F.u), SV: lin(F.v), aV, aL, view: { a: aV, flat: Math.hypot(uV, vV), phi: Math.atan2(vV, uV) }, light: { a: aL, flat: Math.hypot(uL, vL), phi: Math.atan2(vL, uL) } };
1012
+ }
1013
+
1014
+ function t3At(geo, s, r, t, buf, index) {
1015
+ const c = Math.cos(t);
1016
+ const n = Math.sin(t);
1017
+ buf[index] = geo.O[0] + s * geo.SA[0] + r * (c * geo.SU[0] + n * geo.SV[0]);
1018
+ buf[index + 1] = geo.O[1] + s * geo.SA[1] + r * (c * geo.SU[1] + n * geo.SV[1]);
1019
+ }
1020
+
1021
+ function t3Arcs(nr, na, view, light) {
1022
+ const cuts = [];
1023
+ if (Math.abs(nr) > 1e-9) {
1024
+ const visibleCut = -(na * view.a) / (nr * view.flat);
1025
+ if (visibleCut > -1 && visibleCut < 1) {
1026
+ const d = Math.acos(visibleCut);
1027
+ cuts.push(view.phi - d, view.phi + d);
1028
+ }
1029
+ for (let b = 0; b < T3_LB.length; b++) {
1030
+ const c = (T3_LB[b] - na * light.a) / (nr * light.flat);
1031
+ if (c > -1 && c < 1) {
1032
+ const d = Math.acos(c);
1033
+ cuts.push(light.phi - d, light.phi + d);
1034
+ }
1035
+ }
1036
+ }
1037
+ for (let i = 0; i < cuts.length; i++) {
1038
+ const w = ((cuts[i] % T3_TAU) + T3_TAU) % T3_TAU;
1039
+ cuts[i] = Math.round(w * 1e9) / 1e9;
1040
+ }
1041
+ cuts.sort((a, b) => a - b);
1042
+ const sorted = [];
1043
+ for (let i = 0; i < cuts.length; i++) if (!sorted.length || cuts[i] !== sorted[sorted.length - 1]) sorted.push(cuts[i]);
1044
+ const label = (angle) => {
1045
+ const facing = nr * view.flat * Math.cos(angle - view.phi) + na * view.a;
1046
+ if (facing <= 1e-9) return -1;
1047
+ const score = nr * light.flat * Math.cos(angle - light.phi) + na * light.a;
1048
+ let tone = 0;
1049
+ while (tone < T3_LB.length && score >= T3_LB[tone]) tone++;
1050
+ return tone;
1051
+ };
1052
+ if (!sorted.length) {
1053
+ const tone = label(0);
1054
+ return tone < 0 ? [] : [{ tone, from: 0, to: T3_TAU, full: true }];
1055
+ }
1056
+ const merged = [];
1057
+ for (let index = 0; index < sorted.length; index++) {
1058
+ const from = sorted[index];
1059
+ const to = index + 1 < sorted.length ? sorted[index + 1] : sorted[0] + T3_TAU;
1060
+ const tone = label((from + to) / 2);
1061
+ const last = merged[merged.length - 1];
1062
+ if (last && last.tone === tone && Math.abs(last.to - from) < 1e-9) last.to = to;
1063
+ else merged.push({ tone, from, to });
1064
+ }
1065
+ if (merged.length > 1 && merged[0].tone === merged[merged.length - 1].tone && Math.abs(merged[merged.length - 1].to - T3_TAU - merged[0].from) < 1e-9) {
1066
+ const last = merged.pop();
1067
+ merged[0].from = last.from - T3_TAU;
1068
+ }
1069
+ const out = [];
1070
+ for (const arc of merged) if (arc.tone >= 0) out.push(arc);
1071
+ return out;
1072
+ }
1073
+
1074
+ function t3Strip(geo, s0, r0, s1, r1, from, to, step) {
1075
+ const count = Math.max(1, Math.ceil((to - from) / step));
1076
+ const buf = t3Scratch("c", 4 * count + 8);
1077
+ for (let j = 0; j <= count; j++) {
1078
+ const t = from + ((to - from) * j) / count;
1079
+ t3At(geo, s0, r0, t, buf, 2 * j);
1080
+ t3At(geo, s1, r1, t, buf, 2 * (2 * count + 1 - j));
1081
+ }
1082
+ return t3Path(t3Oriented(buf, 2 * count + 2), 2 * count + 2, true);
1083
+ }
1084
+
1085
+ function t3Oriented(buf, count) {
1086
+ let area = 0;
1087
+ for (let i = 0; i < count; i++) {
1088
+ const j = (i + 1) % count;
1089
+ area += buf[2 * i] * buf[2 * j + 1] - buf[2 * j] * buf[2 * i + 1];
1090
+ }
1091
+ if (area >= 0) return buf;
1092
+ for (let i = 0, j = count - 1; i < j; i++, j--) {
1093
+ const x = buf[2 * i];
1094
+ const y = buf[2 * i + 1];
1095
+ buf[2 * i] = buf[2 * j];
1096
+ buf[2 * i + 1] = buf[2 * j + 1];
1097
+ buf[2 * j] = x;
1098
+ buf[2 * j + 1] = y;
1099
+ }
1100
+ return buf;
1101
+ }
1102
+
1103
+ function t3Hull(points) {
1104
+ const sorted = points.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
1105
+ if (sorted.length < 3) return sorted;
1106
+ const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
1107
+ const lower = [];
1108
+ for (const p of sorted) {
1109
+ while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 1e-12) lower.pop();
1110
+ lower.push(p);
1111
+ }
1112
+ const upper = [];
1113
+ for (let i = sorted.length - 1; i >= 0; i--) {
1114
+ const p = sorted[i];
1115
+ while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 1e-12) upper.pop();
1116
+ upper.push(p);
1117
+ }
1118
+ return lower.slice(0, -1).concat(upper.slice(0, -1));
1119
+ }
1120
+
1121
+ function t3PathOf(points, closed) {
1122
+ if (points.length < 2) return "";
1123
+ let s = "M" + t3Num(points[0][0]) + " " + t3Num(points[0][1]);
1124
+ for (let i = 1; i < points.length; i++) s += "L" + t3Num(points[i][0]) + " " + t3Num(points[i][1]);
1125
+ return closed ? s + "Z" : s;
1126
+ }
1127
+
1128
+ function t3Basis(w) {
1129
+ const helper = Math.abs(w[2]) > 0.9 ? [1, 0, 0] : [0, 0, 1];
1130
+ const u = t3Unit([helper[1] * w[2] - helper[2] * w[1], helper[2] * w[0] - helper[0] * w[2], helper[0] * w[1] - helper[1] * w[0]]);
1131
+ return [u, [w[1] * u[2] - w[2] * u[1], w[2] * u[0] - w[0] * u[2], w[0] * u[1] - w[1] * u[0]]];
1132
+ }
1133
+
1134
+ function t3Meet(A, a, B, b) {
1135
+ const ab = A[0] * B[0] + A[1] * B[1] + A[2] * B[2];
1136
+ const det = 1 - ab * ab;
1137
+ if (det < 1e-12) return [];
1138
+ const alpha = (a - b * ab) / det;
1139
+ const beta = (b - a * ab) / det;
1140
+ const base = [A[0] * alpha + B[0] * beta, A[1] * alpha + B[1] * beta, A[2] * alpha + B[2] * beta];
1141
+ const rest = 1 - (base[0] * base[0] + base[1] * base[1] + base[2] * base[2]);
1142
+ if (rest < 0) return [];
1143
+ const cr = [A[1] * B[2] - A[2] * B[1], A[2] * B[0] - A[0] * B[2], A[0] * B[1] - A[1] * B[0]];
1144
+ const k = Math.sqrt(rest / det);
1145
+ return [
1146
+ [base[0] + cr[0] * k, base[1] + cr[1] * k, base[2] + cr[2] * k],
1147
+ [base[0] - cr[0] * k, base[1] - cr[1] * k, base[2] - cr[2] * k],
1148
+ ];
1149
+ }
1150
+
1151
+ const t3Tables = new Map();
1152
+ function t3Table(count) {
1153
+ let table = t3Tables.get(count);
1154
+ if (!table) {
1155
+ table = new Float64Array(2 * count);
1156
+ for (let j = 0; j < count; j++) {
1157
+ table[2 * j] = Math.cos((j / count) * T3_TAU);
1158
+ table[2 * j + 1] = Math.sin((j / count) * T3_TAU);
1159
+ }
1160
+ t3Tables.set(count, table);
1161
+ }
1162
+ return table;
1163
+ }
1164
+
1165
+ function t3Dome(cam, C, e, r, count, sheets) {
1166
+ const m = cam.m;
1167
+ const V = cam.V;
1168
+ const L = cam.L;
1169
+ const Sx = cam.ox + m[0] * C[0] + m[1] * C[1] + m[2] * C[2];
1170
+ const Sy = cam.oy + m[3] * C[0] + m[4] * C[1] + m[5] * C[2];
1171
+ const flat = (n) => [Sx + r * (m[0] * n[0] + m[1] * n[1] + m[2] * n[2]), Sy + r * (m[3] * n[0] + m[4] * n[1] + m[5] * n[2])];
1172
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
1173
+ const table = t3Table(count);
1174
+ const circle = (W, value, keep) => {
1175
+ const [U, Q] = t3Basis(W);
1176
+ const rad = Math.sqrt(Math.max(0, 1 - value * value));
1177
+ const out = [];
1178
+ for (let j = 0; j < count; j++) {
1179
+ const cs = table[2 * j] * rad;
1180
+ const sn = table[2 * j + 1] * rad;
1181
+ const n = [W[0] * value + U[0] * cs + Q[0] * sn, W[1] * value + U[1] * cs + Q[1] * sn, W[2] * value + U[2] * cs + Q[2] * sn];
1182
+ if (keep(n)) out.push(n);
1183
+ }
1184
+ return out;
1185
+ };
1186
+ const rim = circle(V, 0, (n) => dot(n, e) >= -1e-9).map((n) => [flat(n), dot(n, L)]);
1187
+ const base = circle(e, 0, (n) => dot(n, V) >= -1e-9).map((n) => [flat(n), dot(n, L)]);
1188
+ const corners = t3Meet(V, 0, e, 0).map((n) => [flat(n), dot(n, L)]);
1189
+ for (let tone = 0; tone < 5; tone++) {
1190
+ const tau = tone === 0 ? -Infinity : T3_LB[tone - 1];
1191
+ const candidates = [];
1192
+ for (const list of [rim, base, corners]) for (const [p, lit] of list) if (lit >= tau - 1e-9) candidates.push(p);
1193
+ if (tone > 0) {
1194
+ for (const n of circle(L, tau, (n) => dot(n, V) >= -1e-9 && dot(n, e) >= -1e-9)) candidates.push(flat(n));
1195
+ for (const n of t3Meet(L, tau, V, 0)) if (dot(n, e) >= -1e-9) candidates.push(flat(n));
1196
+ for (const n of t3Meet(L, tau, e, 0)) if (dot(n, V) >= -1e-9) candidates.push(flat(n));
1197
+ }
1198
+ if (candidates.length < 3) continue;
1199
+ const region = t3Hull(candidates);
1200
+ if (region.length >= 3) sheets[tone] += t3PathOf(region, true);
1201
+ }
1202
+ }
1203
+
1204
+ function t3BoxOf(pts, box) {
1205
+ let x0 = Infinity;
1206
+ let y0 = Infinity;
1207
+ let x1 = -Infinity;
1208
+ let y1 = -Infinity;
1209
+ for (const [x, y] of pts) {
1210
+ if (x < x0) x0 = x;
1211
+ if (x > x1) x1 = x;
1212
+ if (y < y0) y0 = y;
1213
+ if (y > y1) y1 = y;
1214
+ }
1215
+ box[0] = x0;
1216
+ box[1] = y0;
1217
+ box[2] = x1;
1218
+ box[3] = y1;
1219
+ }
1220
+
1221
+ function t3Thin(points, most) {
1222
+ if (points.length <= most) return points;
1223
+ const n = points.length;
1224
+ const picks = [];
1225
+ for (let i = 0; i < most; i++) picks.push(Math.floor((i * n) / most));
1226
+ const thin = picks.map((i) => points[i]);
1227
+ let err = 0;
1228
+ for (let j = 0; j < most; j++) {
1229
+ const a = thin[j];
1230
+ const b = thin[(j + 1) % most];
1231
+ const dx = b[0] - a[0];
1232
+ const dy = b[1] - a[1];
1233
+ const l = Math.hypot(dx, dy) || 1;
1234
+ const end = j + 1 < most ? picks[j + 1] : picks[0] + n;
1235
+ for (let i = picks[j] + 1; i < end; i++) {
1236
+ const p = points[i % n];
1237
+ const out = ((p[0] - a[0]) * dy - (p[1] - a[1]) * dx) / l;
1238
+ if (Math.abs(out) > err) err = Math.abs(out);
1239
+ }
1240
+ }
1241
+ return t3Grown(thin, err);
1242
+ }
1243
+
1244
+ function t3Grown(poly, by) {
1245
+ if (!(by > 0)) return poly;
1246
+ let area = 0;
1247
+ for (let i = 0; i < poly.length; i++) {
1248
+ const a = poly[i];
1249
+ const b = poly[(i + 1) % poly.length];
1250
+ area += a[0] * b[1] - b[0] * a[1];
1251
+ }
1252
+ const sign = area >= 0 ? 1 : -1;
1253
+ const n = poly.length;
1254
+ const normals = poly.map((a, i) => {
1255
+ const b = poly[(i + 1) % n];
1256
+ const l = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;
1257
+ return [(sign * (b[1] - a[1])) / l, (-sign * (b[0] - a[0])) / l];
1258
+ });
1259
+ return poly.map((p, i) => {
1260
+ const na = normals[(i - 1 + n) % n];
1261
+ const nb = normals[i];
1262
+ const mx = na[0] + nb[0];
1263
+ const my = na[1] + nb[1];
1264
+ const dd = (mx * mx + my * my) / 2 || 1;
1265
+ return [p[0] + (mx * by) / dd, p[1] + (my * by) / dd];
1266
+ });
1267
+ }
1268
+
1269
+ function t3HullOut(f, points, most = 32) {
1270
+ const thin = t3Thin(points, most);
1271
+ const hull = t3Grow(f, "hull", 2 * thin.length, Float64Array);
1272
+ for (let i = 0; i < thin.length; i++) {
1273
+ hull[2 * i] = thin[i][0];
1274
+ hull[2 * i + 1] = thin[i][1];
1275
+ }
1276
+ f.hullN = thin.length;
1277
+ }
1278
+
1279
+ function t3SupportHull(geo, cam, components, dirs) {
1280
+ const table = t3Table(dirs);
1281
+ const pts = [];
1282
+ const { O, SA, SU, SV } = geo;
1283
+ for (let j = 0; j < dirs; j++) {
1284
+ const dx = table[2 * j];
1285
+ const dy = table[2 * j + 1];
1286
+ const pa = SA[0] * dx + SA[1] * dy;
1287
+ const pu = SU[0] * dx + SU[1] * dy;
1288
+ const pv = SV[0] * dx + SV[1] * dy;
1289
+ const w = Math.hypot(pu, pv);
1290
+ let best = -Infinity;
1291
+ let px = 0;
1292
+ let py = 0;
1293
+ for (let c = 0; c < components.length; c += 3) {
1294
+ const kind = components[c];
1295
+ const s = components[c + 1];
1296
+ const r = components[c + 2];
1297
+ if (kind === 0) {
1298
+ const value = s * pa + r * w;
1299
+ if (value > best) {
1300
+ best = value;
1301
+ const cs = w > 1e-12 ? pu / w : 1;
1302
+ const sn = w > 1e-12 ? pv / w : 0;
1303
+ px = O[0] + s * SA[0] + r * (cs * SU[0] + sn * SV[0]);
1304
+ py = O[1] + s * SA[1] + r * (cs * SU[1] + sn * SV[1]);
1305
+ }
1306
+ } else {
1307
+ const rk = r * cam.k;
1308
+ const value = s * pa + rk;
1309
+ if (value > best) {
1310
+ best = value;
1311
+ px = O[0] + s * SA[0] + rk * dx;
1312
+ py = O[1] + s * SA[1] + rk * dy;
1313
+ }
1314
+ }
1315
+ }
1316
+ const last = pts[pts.length - 1];
1317
+ if (!last || Math.abs(last[0] - px) + Math.abs(last[1] - py) > 0.02) pts.push([px, py]);
1318
+ }
1319
+ if (pts.length > 2) {
1320
+ const a = pts[0];
1321
+ const b = pts[pts.length - 1];
1322
+ if (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) <= 0.02) pts.pop();
1323
+ }
1324
+ return pts;
1325
+ }
1326
+
1327
+ function t3ArcPoints(geo, s, r, from, to, k, out, cut = false) {
1328
+ const count = Math.max(2, Math.ceil((Math.abs(to - from) * 48) / T3_TAU), Math.ceil((Math.abs(to - from) * Math.max(r, 1e-6) * k) / 1.5));
1329
+ for (let j = 0; j <= count; j++) {
1330
+ const t = from + ((to - from) * j) / count;
1331
+ const c = Math.cos(t);
1332
+ const n = Math.sin(t);
1333
+ const p = [geo.O[0] + s * geo.SA[0] + r * (c * geo.SU[0] + n * geo.SV[0]), geo.O[1] + s * geo.SA[1] + r * (c * geo.SU[1] + n * geo.SV[1])];
1334
+ if (cut) p.cut = true;
1335
+ out.push(p);
1336
+ }
1337
+ }
1338
+
1339
+ function t3RoundOutline(run, geo, cam) {
1340
+ const k = cam.k;
1341
+ const { s0, s1, r0, r1, ends } = run;
1342
+ const domes = (ends[0] === "dome" ? 1 : 0) + (ends[1] === "dome" ? 1 : 0);
1343
+ if (domes === 2) {
1344
+ const c0 = [geo.O[0] + s0 * geo.SA[0], geo.O[1] + s0 * geo.SA[1]];
1345
+ const c1 = [geo.O[0] + s1 * geo.SA[0], geo.O[1] + s1 * geo.SA[1]];
1346
+ const rk = r0 * k;
1347
+ const dx = c1[0] - c0[0];
1348
+ const dy = c1[1] - c0[1];
1349
+ const l = Math.hypot(dx, dy);
1350
+ const count = Math.max(24, Math.ceil((Math.PI * rk) / 1.5));
1351
+ const out = [];
1352
+ if (l < 1e-6) {
1353
+ for (let j = 0; j < 2 * count; j++) {
1354
+ const t = (j / (2 * count)) * T3_TAU;
1355
+ out.push([c1[0] + rk * Math.cos(t), c1[1] + rk * Math.sin(t)]);
1356
+ }
1357
+ return out;
1358
+ }
1359
+ const beta = Math.atan2(dy, dx);
1360
+ for (let j = 0; j <= count; j++) {
1361
+ const t = beta - Math.PI / 2 + (Math.PI * j) / count;
1362
+ out.push([c1[0] + rk * Math.cos(t), c1[1] + rk * Math.sin(t)]);
1363
+ }
1364
+ for (let j = 0; j <= count; j++) {
1365
+ const t = beta + Math.PI / 2 + (Math.PI * j) / count;
1366
+ out.push([c0[0] + rk * Math.cos(t), c0[1] + rk * Math.sin(t)]);
1367
+ }
1368
+ return out;
1369
+ }
1370
+ if (domes === 1) {
1371
+ const pts = [];
1372
+ for (const [end, s, r] of [[0, s0, r0], [1, s1, r1]]) {
1373
+ t3ArcPoints(geo, s, r, 0, T3_TAU, k, pts);
1374
+ if (ends[end] !== "dome") continue;
1375
+ const sign = end ? 1 : -1;
1376
+ const c = [geo.O[0] + s * geo.SA[0], geo.O[1] + s * geo.SA[1]];
1377
+ const rk = r * k;
1378
+ const beta = Math.atan2(sign * geo.SA[1], sign * geo.SA[0]);
1379
+ const count = Math.max(24, Math.ceil((Math.PI * rk) / 1.5));
1380
+ for (let j = 0; j <= count; j++) {
1381
+ const t = beta - Math.PI / 2 + (Math.PI * j) / count;
1382
+ pts.push([c[0] + rk * Math.cos(t), c[1] + rk * Math.sin(t)]);
1383
+ }
1384
+ }
1385
+ return t3Hull(pts);
1386
+ }
1387
+ const view = geo.view;
1388
+ const out = [];
1389
+ let c;
1390
+ if (view.flat < 1e-9) c = run.na * view.a > 0 ? -2 : 2;
1391
+ else c = -(run.na * view.a) / (run.nr * view.flat);
1392
+ if (c <= -1 || c >= 1) {
1393
+ const side = c <= -1;
1394
+ const capFirst = -geo.aV > 0;
1395
+ const keepFirst = side ? !capFirst : capFirst;
1396
+ const s = keepFirst ? s0 : s1;
1397
+ const r = keepFirst ? r0 : r1;
1398
+ t3ArcPoints(geo, s, r, 0, T3_TAU, k, out, run.cutS !== null && Math.abs(run.cutS - s) < 1e-6);
1399
+ out.pop();
1400
+ return out;
1401
+ }
1402
+ const d = Math.acos(c);
1403
+ const lo = view.phi - d;
1404
+ const hi = view.phi + d;
1405
+ const cut0 = run.cutS !== null && Math.abs(run.cutS - s0) < 1e-6;
1406
+ const cut1 = run.cutS !== null && Math.abs(run.cutS - s1) < 1e-6;
1407
+ if (-geo.aV > 0) t3ArcPoints(geo, s0, r0, hi, lo + T3_TAU, k, out, cut0);
1408
+ else t3ArcPoints(geo, s0, r0, lo, hi, k, out, cut0);
1409
+ const tail = [];
1410
+ if (geo.aV > 0) t3ArcPoints(geo, s1, r1, hi, lo + T3_TAU, k, tail, cut1);
1411
+ else t3ArcPoints(geo, s1, r1, lo, hi, k, tail, cut1);
1412
+ const first = out[out.length - 1];
1413
+ const a = tail[0];
1414
+ const b = tail[tail.length - 1];
1415
+ if (Math.hypot(a[0] - first[0], a[1] - first[1]) > Math.hypot(b[0] - first[0], b[1] - first[1])) tail.reverse();
1416
+ for (const p of tail) out.push(p);
1417
+ return out;
1418
+ }
1419
+
1420
+ function t3LatheMid(a, b) {
1421
+ const h = b.s - a.s;
1422
+ const ga = -a.na / a.nr;
1423
+ const gb = -b.na / b.nr;
1424
+ const nr = a.nr + b.nr;
1425
+ const na = a.na + b.na;
1426
+ const l = Math.hypot(nr, na) || 1;
1427
+ return { s: (a.s + b.s) / 2, r: (a.r + b.r) / 2 + (h * (ga - gb)) / 8, nr: nr / l, na: na / l };
1428
+ }
1429
+
1430
+ function t3LatheOutline(run, geo, cam) {
1431
+ const k = cam.k;
1432
+ const view = geo.view;
1433
+ const lv = run.levels;
1434
+ const pts = [];
1435
+ const cut = (L) => (view.flat < 1e-9 ? Infinity : -(L.na * view.a) / (L.nr * view.flat));
1436
+ const inside = (L) => L.r <= 1e-9 || Math.abs(cut(L)) < 1;
1437
+ const sides = (L) => {
1438
+ if (L.r <= 1e-9) {
1439
+ const p = [geo.O[0] + L.s * geo.SA[0], geo.O[1] + L.s * geo.SA[1]];
1440
+ return [p, p];
1441
+ }
1442
+ const c = Math.max(-1, Math.min(1, cut(L)));
1443
+ const d = Math.acos(c);
1444
+ return [view.phi - d, view.phi + d].map((t) => {
1445
+ const cs = Math.cos(t);
1446
+ const sn = Math.sin(t);
1447
+ return [geo.O[0] + L.s * geo.SA[0] + L.r * (cs * geo.SU[0] + sn * geo.SV[0]), geo.O[1] + L.s * geo.SA[1] + L.r * (cs * geo.SU[1] + sn * geo.SV[1])];
1448
+ });
1449
+ };
1450
+ const span = (a, b) => {
1451
+ const pa = sides(a);
1452
+ const pb = sides(b);
1453
+ return Math.max(Math.hypot(pa[0][0] - pb[0][0], pa[0][1] - pb[0][1]), Math.hypot(pa[1][0] - pb[1][0], pa[1][1] - pb[1][1]));
1454
+ };
1455
+ const refine = (a, b, depth) => {
1456
+ if (depth >= 10 || span(a, b) < 1.2) return;
1457
+ const mid = t3LatheMid(a, b);
1458
+ refine(a, mid, depth + 1);
1459
+ pts.push(...sides(mid));
1460
+ refine(mid, b, depth + 1);
1461
+ };
1462
+ let previous = null;
1463
+ for (let i = 0; i < lv.length; i += 4) {
1464
+ const L = { s: lv[i], r: lv[i + 1], nr: lv[i + 2], na: lv[i + 3] };
1465
+ if (inside(L)) pts.push(...sides(L));
1466
+ if (previous) {
1467
+ const pin = inside(previous);
1468
+ const lin = inside(L);
1469
+ if (pin && lin) refine(previous, L, 0);
1470
+ else if (pin !== lin) {
1471
+ let a = pin ? previous : L;
1472
+ let b = pin ? L : previous;
1473
+ const start = a;
1474
+ for (let it = 0; it < 14; it++) {
1475
+ const mid = t3LatheMid(a.s < b.s ? a : b, a.s < b.s ? b : a);
1476
+ if (inside(mid)) a = mid;
1477
+ else b = mid;
1478
+ }
1479
+ pts.push(...sides(a));
1480
+ refine(start.s < a.s ? start : a, start.s < a.s ? a : start, 0);
1481
+ }
1482
+ }
1483
+ previous = L;
1484
+ }
1485
+ for (const i of [0, lv.length - 4]) if (lv[i + 1] > 1e-9) t3ArcPoints(geo, lv[i], lv[i + 1], 0, T3_TAU, k, pts);
1486
+ return t3Hull(pts);
1487
+ }
1488
+
1489
+ function t3SeamArc(geo, s, r, slope, k, trim = 0.3) {
1490
+ const view = geo.view;
1491
+ const norm = Math.hypot(1, slope);
1492
+ let lo;
1493
+ let hi;
1494
+ if (view.flat < 1e-9) {
1495
+ if (!((-slope * view.a) / norm > 0)) return "";
1496
+ lo = view.phi;
1497
+ hi = view.phi + T3_TAU;
1498
+ } else {
1499
+ const cc = (slope * view.a) / view.flat;
1500
+ if (cc >= 1) return "";
1501
+ if (cc <= -1) {
1502
+ lo = view.phi;
1503
+ hi = view.phi + T3_TAU;
1504
+ } else {
1505
+ const d = Math.acos(cc);
1506
+ lo = view.phi - d;
1507
+ hi = view.phi + d;
1508
+ }
1509
+ }
1510
+ const full = hi - lo >= T3_TAU - 1e-9;
1511
+ const buf = t3Scratch("c", 2 * 520);
1512
+ if (!full) {
1513
+ const span = hi - lo;
1514
+ const cutAt = (from, sign) => {
1515
+ t3At(geo, s, r, from, buf, 0);
1516
+ const px = buf[0];
1517
+ const py = buf[1];
1518
+ let a = 0;
1519
+ let b = span * 0.25;
1520
+ t3At(geo, s, r, from + sign * b, buf, 0);
1521
+ if (Math.hypot(buf[0] - px, buf[1] - py) <= trim) return b;
1522
+ for (let it = 0; it < 18; it++) {
1523
+ const mid = (a + b) / 2;
1524
+ t3At(geo, s, r, from + sign * mid, buf, 0);
1525
+ if (Math.hypot(buf[0] - px, buf[1] - py) < trim) a = mid;
1526
+ else b = mid;
1527
+ }
1528
+ return (a + b) / 2;
1529
+ };
1530
+ const d0 = cutAt(lo, 1);
1531
+ const d1 = cutAt(hi, -1);
1532
+ lo += d0;
1533
+ hi -= d1;
1534
+ }
1535
+ const count = Math.min(512, Math.max(4, Math.ceil(((hi - lo) * r * k) / 1.5)));
1536
+ for (let j = 0; j <= count; j++) t3At(geo, s, r, lo + ((hi - lo) * j) / count, buf, 2 * j);
1537
+ return t3Path(buf, full ? count : count + 1, full);
1538
+ }
1539
+
1540
+ function t3RibLines(geo, F, cam, ribs, sx, sy) {
1541
+ const lines = t3Lines;
1542
+ lines.d.length = 0;
1543
+ lines.alpha.length = 0;
1544
+ lines.key.length = 0;
1545
+ const { s0, s1, count, phase = 0, twist = 0, fade = [0.1, 0.55] } = ribs;
1546
+ const r0 = ribs.r0 ?? ribs.r;
1547
+ const r1 = ribs.r1 ?? r0;
1548
+ const slope = (r1 - r0) / (s1 - s0 || 1);
1549
+ const norm = Math.hypot(1, slope);
1550
+ const view = geo.view;
1551
+ const seg = new Float64Array(4);
1552
+ for (let i = 0; i < count; i++) {
1553
+ const t = phase + (i / count) * T3_TAU;
1554
+ const tm = t + twist / 2;
1555
+ const facing = (view.flat * Math.cos(tm - view.phi) - slope * view.a) / norm;
1556
+ const alpha = t3Step(fade[0], fade[1], facing);
1557
+ if (alpha * T3_LEVELS < 0.5) continue;
1558
+ t3At(geo, s0, r0, t, seg, 0);
1559
+ t3At(geo, s1, r1, t + twist, seg, 2);
1560
+ seg[0] -= sx;
1561
+ seg[1] -= sy;
1562
+ seg[2] -= sx;
1563
+ seg[3] -= sy;
1564
+ lines.d.push(t3Path(seg, 2, false));
1565
+ lines.alpha.push(alpha);
1566
+ lines.key.push(i);
1567
+ }
1568
+ return lines;
1569
+ }
1570
+
1571
+ function t3PrepareRound(run, part, P, unit) {
1572
+ const spec = part.spec;
1573
+ const k = P.scale * T3_ROOT;
1574
+ run.F = spec.F;
1575
+ run.s0 = spec.s0;
1576
+ run.s1 = spec.s1;
1577
+ run.r0 = spec.r0;
1578
+ run.r1 = spec.r1;
1579
+ run.ends = spec.ends;
1580
+ const g = (spec.r1 - spec.r0) / (spec.s1 - spec.s0 || 1);
1581
+ const norm = Math.hypot(1, g);
1582
+ run.nr = 1 / norm;
1583
+ run.na = -g / norm;
1584
+ run.g = g;
1585
+ const rmax = Math.max(spec.r0, spec.r1);
1586
+ const around = T3_TAU * rmax * k * unit;
1587
+ run.fine = Math.max(around < 30 ? 16 : 24, Math.min(96, Math.ceil(around / 2.2)));
1588
+ run.dirs = Math.max(around < 30 ? 16 : 32, Math.min(256, Math.ceil(around / 1.5)));
1589
+ run.capCount = Math.max(around < 30 ? 16 : 24, Math.min(160, Math.ceil(around / 1.5)));
1590
+ run.domeCount = Math.max(around < 30 ? 12 : 16, Math.min(48, Math.round(run.fine * 0.75)));
1591
+ const comps = [];
1592
+ for (const [end, s, r] of [[0, spec.s0, spec.r0], [1, spec.s1, spec.r1]]) comps.push(spec.ends[end] === "dome" ? 1 : 0, s, r);
1593
+ run.components = comps;
1594
+ const details = part.details ?? {};
1595
+ run.seams = (details.seams ?? []).map((s) => ({ s, r: spec.r0 + g * (s - spec.s0), slope: g }));
1596
+ run.ribs = details.ribs ? { ...details.ribs, r0: details.ribs.r0 ?? spec.r0 + g * (details.ribs.s0 - spec.s0), r1: details.ribs.r1 ?? spec.r0 + g * (details.ribs.s1 - spec.s0) } : null;
1597
+ run.cutS = null;
1598
+ for (const cut of spec.cuts ?? []) {
1599
+ const along = cut.n[0] * spec.F.a[0] + cut.n[1] * spec.F.a[1] + cut.n[2] * spec.F.a[2];
1600
+ const sCut = (cut.d - (cut.n[0] * spec.F.o[0] + cut.n[1] * spec.F.o[1] + cut.n[2] * spec.F.o[2])) / along;
1601
+ if (Math.abs(sCut - spec.s0) < 1e-6 || Math.abs(sCut - spec.s1) < 1e-6) run.cutS = sCut;
1602
+ }
1603
+ }
1604
+
1605
+ function t3EmitRound(run, cam, f) {
1606
+ const geo = t3Geo(run.F, cam);
1607
+ const k = cam.k;
1608
+ const step = T3_TAU / run.fine;
1609
+ const sheets = ["", "", "", "", ""];
1610
+ const { s0, s1, r0, r1 } = run;
1611
+ for (const arc of t3Arcs(run.nr, run.na, geo.view, geo.light)) sheets[arc.tone] += t3Strip(geo, s0, r0, s1, r1, arc.from, arc.to, step);
1612
+ const F = run.F;
1613
+ for (let end = 0; end < 2; end++) {
1614
+ if (run.ends[end] !== "dome") continue;
1615
+ const s = end ? s1 : s0;
1616
+ const sign = end ? 1 : -1;
1617
+ const C = [F.o[0] + F.a[0] * s, F.o[1] + F.a[1] * s, F.o[2] + F.a[2] * s];
1618
+ t3Dome(cam, C, [F.a[0] * sign, F.a[1] * sign, F.a[2] * sign], end ? r1 : r0, run.domeCount, sheets);
1619
+ }
1620
+ for (let t = 0; t < 5; t++) f.d[run.role[`s${t}`]] = sheets[t];
1621
+ const pts = t3RoundOutline(run, geo, cam);
1622
+ const outline = t3PathOf(pts, true);
1623
+ f.d[run.role.fill] = outline;
1624
+ if (run.cutS !== null) {
1625
+ const buf = new Float64Array(2 * pts.length);
1626
+ const skip = new Uint8Array(pts.length);
1627
+ pts.forEach((p, j) => {
1628
+ buf[2 * j] = p[0];
1629
+ buf[2 * j + 1] = p[1];
1630
+ });
1631
+ for (let j = 0; j < pts.length; j++) skip[j] = pts[j].cut && pts[(j + 1) % pts.length].cut ? 1 : 0;
1632
+ f.d[run.role.edge] = t3Broken(buf, pts.length, skip);
1633
+ } else f.d[run.role.edge] = outline;
1634
+ t3BoxOf(pts, f.box);
1635
+ t3HullOut(f, pts);
1636
+ const view = geo.view;
1637
+ let crease = "";
1638
+ for (let end = 0; end < 2; end++) {
1639
+ if (run.ends[end] !== "flat") continue;
1640
+ const role = run.role[`k${end}`];
1641
+ const sign = end ? 1 : -1;
1642
+ const s = end ? s1 : s0;
1643
+ const r = end ? r1 : r0;
1644
+ const face = sign * geo.aV;
1645
+ if (!(face > 1e-9)) {
1646
+ f.d[role] = "";
1647
+ continue;
1648
+ }
1649
+ const n = run.capCount;
1650
+ const buf = t3Scratch("c", 2 * n);
1651
+ const table = t3Table(n);
1652
+ for (let j = 0; j < n; j++) {
1653
+ const c = table[2 * j];
1654
+ const q = table[2 * j + 1];
1655
+ buf[2 * j] = geo.O[0] + s * geo.SA[0] + r * (c * geo.SU[0] + q * geo.SV[0]);
1656
+ buf[2 * j + 1] = geo.O[1] + s * geo.SA[1] + r * (c * geo.SU[1] + q * geo.SV[1]);
1657
+ }
1658
+ f.d[role] = t3Path(buf, n, true);
1659
+ f.q[role] = t3Level(sign * geo.aL, T3_LB);
1660
+ const minor = 2 * r * k * Math.abs(face);
1661
+ f.w[role] = minor < 0.4 ? Math.round(minor * 100) / 100 : -1;
1662
+ if (run.role.crease === undefined) continue;
1663
+ let lo;
1664
+ let hi;
1665
+ if (view.flat < 1e-9) {
1666
+ if (!(run.na * view.a > 1e-9)) continue;
1667
+ lo = 0;
1668
+ hi = T3_TAU;
1669
+ } else {
1670
+ const cc = -(run.na * view.a) / (run.nr * view.flat);
1671
+ if (cc >= 1) continue;
1672
+ if (cc <= -1) {
1673
+ lo = view.phi;
1674
+ hi = view.phi + T3_TAU;
1675
+ } else {
1676
+ const d = Math.acos(cc);
1677
+ lo = view.phi - d;
1678
+ hi = view.phi + d;
1679
+ }
1680
+ }
1681
+ const count = Math.min(400, Math.max(4, Math.ceil(((hi - lo) * r * k) / 1.5)));
1682
+ const arc = t3Scratch("c", 2 * count + 2);
1683
+ for (let j = 0; j <= count; j++) t3At(geo, s, r, lo + ((hi - lo) * j) / count, arc, 2 * j);
1684
+ crease += t3Path(arc, count + 1, false);
1685
+ }
1686
+ if (run.role.crease !== undefined) f.d[run.role.crease] = crease;
1687
+ if (run.role.inner !== undefined) {
1688
+ let inner = "";
1689
+ for (const seam of run.seams) inner += t3SeamArc(geo, seam.s, seam.r, seam.slope, k);
1690
+ if (run.ribs && run.ribs.seams) {
1691
+ const rs = run.ribs;
1692
+ const slope = (rs.r1 - rs.r0) / (rs.s1 - rs.s0 || 1);
1693
+ if (rs.seams !== "last") inner += t3SeamArc(geo, rs.s0, rs.r0, slope, k);
1694
+ if (rs.seams !== "first") inner += t3SeamArc(geo, rs.s1, rs.r1, slope, k);
1695
+ }
1696
+ f.d[run.role.inner] = inner;
1697
+ }
1698
+ if (run.ribs) {
1699
+ const lines = t3State.detail < 0.01 ? t3None : t3RibLines(geo, run.F, cam, run.ribs, 0, 0);
1700
+ f.levels = t3Faded(f, run, "r", lines, lines.d.length);
1701
+ }
1702
+ }
1703
+
1704
+ function t3PrepareBall(run, part, P, unit) {
1705
+ const spec = part.spec;
1706
+ const k = P.scale * T3_ROOT;
1707
+ run.c = spec.c;
1708
+ run.r = spec.r;
1709
+ run.flats = spec.flats.map((flat) => {
1710
+ const n = t3Unit(flat.n);
1711
+ const rho = Math.sqrt(Math.max(0, spec.r * spec.r - flat.d * flat.d));
1712
+ const [u, v] = t3Basis(n);
1713
+ return { n, d: flat.d, rho, u, v };
1714
+ });
1715
+ run.limb = Math.max(32, Math.min(160, Math.ceil((T3_TAU * spec.r * k * unit) / 1.5)));
1716
+ run.rimCount = run.flats.map((flat) => Math.max(20, Math.min(120, Math.ceil((T3_TAU * flat.rho * k * unit) / 1.5))));
1717
+ }
1718
+
1719
+ function t3ClipConvex(subject, clip) {
1720
+ if (subject.length < 3 || clip.length < 3) return [];
1721
+ let area = 0;
1722
+ for (let i = 0; i < clip.length; i++) {
1723
+ const a = clip[i];
1724
+ const b = clip[(i + 1) % clip.length];
1725
+ area += a[0] * b[1] - b[0] * a[1];
1726
+ }
1727
+ const sign = area >= 0 ? 1 : -1;
1728
+ let out = subject;
1729
+ for (let i = 0; i < clip.length && out.length; i++) {
1730
+ const a = clip[i];
1731
+ const b = clip[(i + 1) % clip.length];
1732
+ const side = (p) => sign * ((b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]));
1733
+ const input = out;
1734
+ out = [];
1735
+ for (let j = 0; j < input.length; j++) {
1736
+ const p = input[j];
1737
+ const q = input[(j + 1) % input.length];
1738
+ const sp = side(p);
1739
+ const sq = side(q);
1740
+ if (sp >= 0) out.push(p);
1741
+ if ((sp >= 0) !== (sq >= 0)) {
1742
+ const t = sp / (sp - sq);
1743
+ out.push([p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t]);
1744
+ }
1745
+ }
1746
+ }
1747
+ return out;
1748
+ }
1749
+
1750
+ function t3EmitBall(run, cam, f) {
1751
+ const m = cam.m;
1752
+ const V = cam.V;
1753
+ const L = cam.L;
1754
+ const { c, r, flats } = run;
1755
+ const Cx = cam.ox + m[0] * c[0] + m[1] * c[1] + m[2] * c[2];
1756
+ const Cy = cam.oy + m[3] * c[0] + m[4] * c[1] + m[5] * c[2];
1757
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
1758
+ const flat = (w) => [Cx + r * (m[0] * w[0] + m[1] * w[1] + m[2] * w[2]), Cy + r * (m[3] * w[0] + m[4] * w[1] + m[5] * w[2])];
1759
+ const inside = (w, skip = -1) => {
1760
+ for (let i = 0; i < flats.length; i++) if (i !== skip && dot(w, flats[i].n) * r > flats[i].d + 1e-7) return false;
1761
+ return true;
1762
+ };
1763
+ const [U, W] = t3Basis(V);
1764
+ const n = run.limb;
1765
+ const table = t3Table(n);
1766
+ const limb = [];
1767
+ for (let j = 0; j < n; j++) limb.push([U[0] * table[2 * j] + W[0] * table[2 * j + 1], U[1] * table[2 * j] + W[1] * table[2 * j + 1], U[2] * table[2 * j] + W[2] * table[2 * j + 1]]);
1768
+ const sil = [];
1769
+ for (const w of limb) if (inside(w)) sil.push(flat(w));
1770
+ const rims = flats.map((cut, ci) => {
1771
+ const count = run.rimCount[ci];
1772
+ const t = t3Table(count);
1773
+ const list = [];
1774
+ for (let j = 0; j < count; j++) {
1775
+ const w = [(cut.n[0] * cut.d + cut.rho * (cut.u[0] * t[2 * j] + cut.v[0] * t[2 * j + 1])) / r, (cut.n[1] * cut.d + cut.rho * (cut.u[1] * t[2 * j] + cut.v[1] * t[2 * j + 1])) / r, (cut.n[2] * cut.d + cut.rho * (cut.u[2] * t[2 * j] + cut.v[2] * t[2 * j + 1])) / r];
1776
+ list.push({ w, keep: inside(w, ci), p: flat(w) });
1777
+ }
1778
+ return list;
1779
+ });
1780
+ flats.forEach((cut, ci) => {
1781
+ for (const w of t3Meet(V, 0, cut.n, cut.d / r)) if (inside(w, ci)) sil.push(flat(w));
1782
+ rims[ci].forEach(({ keep, p }) => keep && sil.push(p));
1783
+ });
1784
+ const hull = t3Hull(sil);
1785
+ const outline = t3PathOf(hull, true);
1786
+ f.d[run.role.fill] = outline;
1787
+ f.d[run.role.edge] = outline;
1788
+ f.d[run.role.s0] = outline;
1789
+ const capOf = (threshold) => {
1790
+ const points = [];
1791
+ for (const w of limb) if (dot(w, L) >= threshold && inside(w)) points.push(flat(w));
1792
+ const radius = Math.sqrt(Math.max(0, 1 - threshold * threshold));
1793
+ const [LU, LW] = t3Basis(L);
1794
+ for (let j = 0; j < n; j++) {
1795
+ const w = [L[0] * threshold + radius * (LU[0] * table[2 * j] + LW[0] * table[2 * j + 1]), L[1] * threshold + radius * (LU[1] * table[2 * j] + LW[1] * table[2 * j + 1]), L[2] * threshold + radius * (LU[2] * table[2 * j] + LW[2] * table[2 * j + 1])];
1796
+ if (dot(w, V) >= -1e-9) points.push(flat(w));
1797
+ }
1798
+ if (dot(L, V) > threshold) points.push(flat(V));
1799
+ for (const w of t3Meet(L, threshold, V, 0)) if (inside(w)) points.push(flat(w));
1800
+ flats.forEach((cut, ci) => {
1801
+ for (const w of t3Meet(V, 0, cut.n, cut.d / r)) if (inside(w, ci) && dot(w, L) >= threshold) points.push(flat(w));
1802
+ for (const w of t3Meet(L, threshold, cut.n, cut.d / r)) if (inside(w, ci) && dot(w, V) >= -1e-9) points.push(flat(w));
1803
+ for (const { w, keep, p } of rims[ci]) if (keep && dot(w, L) >= threshold && dot(w, V) >= -1e-9) points.push(p);
1804
+ });
1805
+ if (points.length < 3) return "";
1806
+ const region = t3ClipConvex(t3Hull(points), hull);
1807
+ return region.length >= 3 ? t3PathOf(region, true) : "";
1808
+ };
1809
+ f.d[run.role.s1] = capOf(T3_LB[1]);
1810
+ f.d[run.role.s2] = capOf(T3_LB[2]);
1811
+ f.d[run.role.s3] = capOf(T3_LB[3]);
1812
+ let crease = "";
1813
+ flats.forEach((cut, ci) => {
1814
+ const role = run.role[`f${ci}`];
1815
+ const face = dot(cut.n, V);
1816
+ if (!(face > 1e-9)) {
1817
+ f.d[role] = "";
1818
+ return;
1819
+ }
1820
+ const pts = t3ClipConvex(rims[ci].map(({ p }) => p), hull);
1821
+ f.d[role] = t3PathOf(pts, true);
1822
+ f.q[role] = t3Level(dot(cut.n, L), T3_LB);
1823
+ const minor = 2 * cut.rho * cam.k * face;
1824
+ f.w[role] = minor < 0.4 ? Math.round(minor * 100) / 100 : -1;
1825
+ const list = rims[ci];
1826
+ const seen = list.map(({ w, keep }) => keep && dot(w, V) > 0);
1827
+ const start = seen.indexOf(false);
1828
+ if (start < 0) {
1829
+ crease += t3PathOf(list.map(({ p }) => p), true);
1830
+ return;
1831
+ }
1832
+ let cur = null;
1833
+ for (let s = 1; s <= list.length; s++) {
1834
+ const i = (start + s) % list.length;
1835
+ if (seen[i]) (cur ??= []).push(list[i].p);
1836
+ else if (cur) {
1837
+ if (cur.length > 1) crease += t3PathOf(cur, false);
1838
+ cur = null;
1839
+ }
1840
+ }
1841
+ if (cur && cur.length > 1) crease += t3PathOf(cur, false);
1842
+ });
1843
+ if (run.role.crease !== undefined) f.d[run.role.crease] = crease;
1844
+ let x0 = Infinity;
1845
+ let y0 = Infinity;
1846
+ let x1 = -Infinity;
1847
+ let y1 = -Infinity;
1848
+ for (const [x, y] of hull) {
1849
+ if (x < x0) x0 = x;
1850
+ if (x > x1) x1 = x;
1851
+ if (y < y0) y0 = y;
1852
+ if (y > y1) y1 = y;
1853
+ }
1854
+ f.box[0] = x0;
1855
+ f.box[1] = y0;
1856
+ f.box[2] = x1;
1857
+ f.box[3] = y1;
1858
+ t3HullOut(f, hull);
1859
+ }
1860
+
1861
+ function t3PrepareLathe(run, part, P, unit) {
1862
+ const spec = part.spec;
1863
+ const k = P.scale * T3_ROOT;
1864
+ run.F = spec.F;
1865
+ run.levels = spec.levels;
1866
+ run.caps = spec.caps;
1867
+ let rmax = 0;
1868
+ for (let i = 0; i < spec.levels.length; i += 4) rmax = Math.max(rmax, spec.levels[i + 1]);
1869
+ run.fine = Math.max(24, Math.min(96, Math.ceil((T3_TAU * rmax * k * unit) / 2.2)));
1870
+ run.dirs = Math.max(32, Math.min(256, Math.ceil((T3_TAU * rmax * k * unit) / 1.5)));
1871
+ run.capCount = Math.max(24, Math.min(160, Math.ceil((T3_TAU * rmax * k * unit) / 1.5)));
1872
+ const comps = [];
1873
+ for (let i = 0; i < spec.levels.length; i += 4) comps.push(0, spec.levels[i], spec.levels[i + 1]);
1874
+ run.components = comps;
1875
+ const details = part.details ?? {};
1876
+ run.seams = (details.seams ?? []).map((seam) => (typeof seam === "number" ? t3LatheSeam(spec.levels, seam) : seam));
1877
+ run.ribs = details.ribs ?? null;
1878
+ }
1879
+
1880
+ function t3LatheSeam(levels, s) {
1881
+ for (let i = 0; i + 4 < levels.length; i += 4) {
1882
+ const sa = levels[i];
1883
+ const sb = levels[i + 4];
1884
+ if ((s - sa) * (s - sb) <= 0 && sa !== sb) {
1885
+ const t = (s - sa) / (sb - sa);
1886
+ const r = levels[i + 1] + (levels[i + 5] - levels[i + 1]) * t;
1887
+ const slope = (levels[i + 5] - levels[i + 1]) / (sb - sa);
1888
+ return { s, r, slope };
1889
+ }
1890
+ }
1891
+ return { s, r: levels[1], slope: 0 };
1892
+ }
1893
+
1894
+ function t3LatheSheets(levels, geo, step, buckets) {
1895
+ const at = (L, t) => {
1896
+ const c = Math.cos(t);
1897
+ const n = Math.sin(t);
1898
+ return [geo.O[0] + L.s * geo.SA[0] + L.r * (c * geo.SU[0] + n * geo.SV[0]), geo.O[1] + L.s * geo.SA[1] + L.r * (c * geo.SU[1] + n * geo.SV[1])];
1899
+ };
1900
+ const arcPoints = (L, from, to) => {
1901
+ const count = Math.max(1, Math.ceil((to - from) / step));
1902
+ const out = new Array(count + 1);
1903
+ for (let index = 0; index <= count; index++) out[index] = at(L, from + ((to - from) * index) / count);
1904
+ return out;
1905
+ };
1906
+ const oriented = (points) => {
1907
+ let area = 0;
1908
+ for (let i = 0; i < points.length; i++) {
1909
+ const a = points[i];
1910
+ const b = points[(i + 1) % points.length];
1911
+ area += a[0] * b[1] - b[0] * a[1];
1912
+ }
1913
+ return area < 0 ? points.reverse() : points;
1914
+ };
1915
+ const centreOf = (arc, reference) => {
1916
+ let mid = (arc.from + arc.to) / 2 - reference;
1917
+ mid = ((mid % T3_TAU) + T3_TAU) % T3_TAU;
1918
+ return mid;
1919
+ };
1920
+ const present = [false, false, false, false, false];
1921
+ for (const L of levels) {
1922
+ const groups = [null, null, null, null, null];
1923
+ for (const arc of L.arcs) {
1924
+ if (!groups[arc.tone]) groups[arc.tone] = [];
1925
+ groups[arc.tone].push(arc);
1926
+ present[arc.tone] = true;
1927
+ }
1928
+ for (const list of groups) if (list && list.length > 1) list.sort((x, y) => centreOf(x, geo.light.phi) - centreOf(y, geo.light.phi));
1929
+ L.groups = groups;
1930
+ }
1931
+ const align = (reference, arc) => {
1932
+ const out = { ...arc };
1933
+ const centre = (reference.from + reference.to) / 2;
1934
+ while ((out.from + out.to) / 2 - centre > Math.PI) {
1935
+ out.from -= T3_TAU;
1936
+ out.to -= T3_TAU;
1937
+ }
1938
+ while (centre - (out.from + out.to) / 2 > Math.PI) {
1939
+ out.from += T3_TAU;
1940
+ out.to += T3_TAU;
1941
+ }
1942
+ return out;
1943
+ };
1944
+ const overlap = (x, y) => Math.min(x.to, y.to) - Math.max(x.from, y.from);
1945
+ const sheet = (tone, run) => {
1946
+ const chains = run[0].groups[tone].map((arc) => [arc]);
1947
+ for (let q = 1; q < run.length; q++) {
1948
+ const next = run[q].groups[tone];
1949
+ const used = new Uint8Array(next.length);
1950
+ for (const chain of chains) {
1951
+ const prev = chain[q - 1];
1952
+ let best = -1;
1953
+ let score = -Infinity;
1954
+ for (let j = 0; j < next.length; j++) {
1955
+ if (used[j]) continue;
1956
+ const value = overlap(prev, align(prev, next[j]));
1957
+ if (value > score) {
1958
+ score = value;
1959
+ best = j;
1960
+ }
1961
+ }
1962
+ used[best] = 1;
1963
+ chain.push(align(prev, next[best]));
1964
+ }
1965
+ }
1966
+ chains.forEach((chain) => {
1967
+ const last = run.length - 1;
1968
+ const points = arcPoints(run[0], chain[0].from, chain[0].to);
1969
+ for (let q = 1; q < last; q++) points.push(at(run[q], chain[q].to));
1970
+ points.push(...arcPoints(run[last], chain[last].from, chain[last].to).reverse());
1971
+ for (let q = last - 1; q >= 1; q--) points.push(at(run[q], chain[q].from));
1972
+ buckets[tone] += t3PathOf(oriented(points), true);
1973
+ });
1974
+ };
1975
+ const strip = (tone, a, b) => {
1976
+ const below = a.groups[tone] ?? [];
1977
+ const above = b.groups[tone] ?? [];
1978
+ const whole = (arc) => arc.to - arc.from >= T3_TAU - 1e-6;
1979
+ const seat = (ring, arc) => {
1980
+ const gap = T3_TAU - (arc.to - arc.from);
1981
+ return { from: arc.from - gap / 2, to: arc.to + gap / 2 };
1982
+ };
1983
+ const quad = (lo, hi) => {
1984
+ if (whole(hi) && !whole(lo)) hi = seat(hi, lo);
1985
+ else if (whole(lo) && !whole(hi)) lo = seat(lo, hi);
1986
+ const points = [...arcPoints(a, lo.from, lo.to), ...arcPoints(b, hi.from, hi.to).reverse()];
1987
+ buckets[tone] += t3PathOf(oriented(points), true);
1988
+ };
1989
+ const links = below.map(() => []);
1990
+ const back = above.map(() => []);
1991
+ below.forEach((lo, i) =>
1992
+ above.forEach((hi, j) => {
1993
+ if (overlap(lo, align(lo, hi)) > -0.05) {
1994
+ links[i].push(j);
1995
+ back[j].push(i);
1996
+ }
1997
+ }),
1998
+ );
1999
+ const doneLo = new Uint8Array(below.length);
2000
+ const doneHi = new Uint8Array(above.length);
2001
+ const share = (span, parts, flip) => {
2002
+ if (whole(span)) {
2003
+ const base = parts[0].from;
2004
+ const sorted = parts.map((arc) => {
2005
+ const shift = T3_TAU * Math.floor((arc.from - base) / T3_TAU);
2006
+ return { from: arc.from - shift, to: arc.to - shift };
2007
+ }).sort((x, y) => x.from - y.from);
2008
+ const n = sorted.length;
2009
+ const cuts = sorted.map((arc, q) => {
2010
+ const next = q + 1 < n ? sorted[q + 1].from : sorted[0].from + T3_TAU;
2011
+ return (arc.to + next) / 2;
2012
+ });
2013
+ sorted.forEach((arc, q) => {
2014
+ const piece = { from: q ? cuts[q - 1] : cuts[n - 1] - T3_TAU, to: cuts[q] };
2015
+ if (flip) quad(arc, piece);
2016
+ else quad(piece, arc);
2017
+ });
2018
+ return;
2019
+ }
2020
+ const sorted = parts.map((arc) => align(span, arc)).sort((x, y) => x.from - y.from);
2021
+ const cuts = [span.from];
2022
+ for (let q = 1; q < sorted.length; q++) cuts.push(Math.min(span.to, Math.max(span.from, (sorted[q - 1].to + sorted[q].from) / 2)));
2023
+ cuts.push(span.to);
2024
+ sorted.forEach((arc, q) => {
2025
+ const piece = { from: cuts[q], to: cuts[q + 1] };
2026
+ if (flip) quad(arc, piece);
2027
+ else quad(piece, arc);
2028
+ });
2029
+ };
2030
+ for (let j = 0; j < above.length; j++) {
2031
+ if (back[j].length < 2 || back[j].some((i) => links[i].length !== 1)) continue;
2032
+ share(above[j], back[j].map((i) => below[i]), false);
2033
+ doneHi[j] = 1;
2034
+ for (const i of back[j]) doneLo[i] = 1;
2035
+ }
2036
+ for (let i = 0; i < below.length; i++) {
2037
+ if (doneLo[i] || links[i].length < 2 || links[i].some((j) => back[j].length !== 1)) continue;
2038
+ share(below[i], links[i].map((j) => above[j]), true);
2039
+ doneLo[i] = 1;
2040
+ for (const j of links[i]) doneHi[j] = 1;
2041
+ }
2042
+ for (let i = 0; i < below.length; i++) {
2043
+ if (doneLo[i]) continue;
2044
+ const lo = below[i];
2045
+ let best = -1;
2046
+ let score = -Infinity;
2047
+ for (const j of links[i]) {
2048
+ if (doneHi[j]) continue;
2049
+ const value = overlap(lo, align(lo, above[j]));
2050
+ if (value > score) {
2051
+ score = value;
2052
+ best = j;
2053
+ }
2054
+ }
2055
+ if (best >= 0) {
2056
+ doneHi[best] = 1;
2057
+ quad(lo, align(lo, above[best]));
2058
+ } else {
2059
+ const centre = (lo.from + lo.to) / 2;
2060
+ quad(lo, { from: centre, to: centre });
2061
+ }
2062
+ }
2063
+ for (let j = 0; j < above.length; j++) {
2064
+ if (doneHi[j]) continue;
2065
+ const centre = (above[j].from + above[j].to) / 2;
2066
+ quad({ from: centre, to: centre }, above[j]);
2067
+ }
2068
+ };
2069
+ for (let tone = 0; tone < 5; tone++) {
2070
+ if (!present[tone]) continue;
2071
+ for (let q = 1; q < levels.length; q++) strip(tone, levels[q - 1], levels[q]);
2072
+ }
2073
+ void sheet;
2074
+ }
2075
+
2076
+ function t3EmitLathe(run, cam, f) {
2077
+ const geo = t3Geo(run.F, cam);
2078
+ const k = cam.k;
2079
+ const step = T3_TAU / run.fine;
2080
+ const sheets = ["", "", "", "", ""];
2081
+ const lv = run.levels;
2082
+ const levelAt = (s, r, nr, na) => ({ s, r, nr, na, arcs: t3Arcs(nr, na, geo.view, geo.light) });
2083
+ const same = (a, b) => {
2084
+ if (a.arcs.length !== b.arcs.length) return false;
2085
+ for (let i = 0; i < a.arcs.length; i++) if (a.arcs[i].tone !== b.arcs[i].tone || Boolean(a.arcs[i].full) !== Boolean(b.arcs[i].full)) return false;
2086
+ return true;
2087
+ };
2088
+ const reach = (a, b) => Math.hypot((b.s - a.s) * Math.hypot(geo.SA[0], geo.SA[1]), (b.r - a.r) * k) + Math.abs(b.r - a.r) * k;
2089
+ const levels = [];
2090
+ const wrap = (x, ref) => x + T3_TAU * Math.round((ref - x) / T3_TAU);
2091
+ const close = (a, mid, b) => {
2092
+ const r = Math.max(a.r, b.r) * k;
2093
+ for (let i = 0; i < mid.arcs.length; i++) {
2094
+ const m = mid.arcs[i];
2095
+ if (m.full) continue;
2096
+ for (const key of ["from", "to"]) {
2097
+ const x = m[key];
2098
+ const guess = (wrap(a.arcs[i][key], x) + wrap(b.arcs[i][key], x)) / 2;
2099
+ if (Math.abs(guess - x) * r > 0.25) return false;
2100
+ }
2101
+ }
2102
+ return true;
2103
+ };
2104
+ const refine = (a, b, depth) => {
2105
+ if (depth >= 8 || reach(a, b) < 0.1) {
2106
+ levels.push(b);
2107
+ return;
2108
+ }
2109
+ const m = t3LatheMid(a, b);
2110
+ const mid = levelAt(m.s, m.r, m.nr, m.na);
2111
+ if (same(a, b) && same(a, mid) && close(a, mid, b)) {
2112
+ levels.push(b);
2113
+ return;
2114
+ }
2115
+ refine(a, mid, depth + 1);
2116
+ refine(mid, b, depth + 1);
2117
+ };
2118
+ let previous = levelAt(lv[0], lv[1], lv[2], lv[3]);
2119
+ levels.push(previous);
2120
+ for (let i = 4; i < lv.length; i += 4) {
2121
+ const next = levelAt(lv[i], lv[i + 1], lv[i + 2], lv[i + 3]);
2122
+ refine(previous, next, 0);
2123
+ previous = next;
2124
+ }
2125
+ t3LatheSheets(levels, geo, step, sheets);
2126
+ for (let t = 0; t < 5; t++) f.d[run.role[`s${t}`]] = sheets[t];
2127
+ const pts = t3LatheOutline(run, geo, cam);
2128
+ const outline = t3PathOf(pts, true);
2129
+ f.d[run.role.fill] = outline;
2130
+ f.d[run.role.edge] = outline;
2131
+ t3BoxOf(pts, f.box);
2132
+ t3HullOut(f, pts);
2133
+ let crease = "";
2134
+ const view = geo.view;
2135
+ const ends = [[0, lv[0], lv[1], -1, 4], [1, lv[lv.length - 4], lv[lv.length - 3], 1, lv.length - 8]];
2136
+ for (const [end, s, r, sign, near] of ends) {
2137
+ const role = run.role[`k${end}`];
2138
+ if (role === undefined) continue;
2139
+ const face = sign * geo.aV;
2140
+ if (!(face > 1e-9) || r <= 1e-9) {
2141
+ f.d[role] = "";
2142
+ continue;
2143
+ }
2144
+ const n = run.capCount;
2145
+ const buf = t3Scratch("c", 2 * n);
2146
+ const table = t3Table(n);
2147
+ for (let j = 0; j < n; j++) {
2148
+ const c = table[2 * j];
2149
+ const q = table[2 * j + 1];
2150
+ buf[2 * j] = geo.O[0] + s * geo.SA[0] + r * (c * geo.SU[0] + q * geo.SV[0]);
2151
+ buf[2 * j + 1] = geo.O[1] + s * geo.SA[1] + r * (c * geo.SU[1] + q * geo.SV[1]);
2152
+ }
2153
+ f.d[role] = t3Path(buf, n, true);
2154
+ f.q[role] = t3Level(sign * geo.aL, T3_LB);
2155
+ const minor = 2 * r * k * Math.abs(face);
2156
+ f.w[role] = minor < 0.4 ? Math.round(minor * 100) / 100 : -1;
2157
+ if (run.role.crease === undefined) continue;
2158
+ const nr = lv[near + 2];
2159
+ const na = lv[near + 3];
2160
+ let lo;
2161
+ let hi;
2162
+ if (view.flat < 1e-9) {
2163
+ if (!(na * view.a > 1e-9)) continue;
2164
+ lo = 0;
2165
+ hi = T3_TAU;
2166
+ } else {
2167
+ const cc = -(na * view.a) / (nr * view.flat);
2168
+ if (cc >= 1) continue;
2169
+ if (cc <= -1) {
2170
+ lo = view.phi;
2171
+ hi = view.phi + T3_TAU;
2172
+ } else {
2173
+ const d = Math.acos(cc);
2174
+ lo = view.phi - d;
2175
+ hi = view.phi + d;
2176
+ }
2177
+ }
2178
+ const count = Math.min(400, Math.max(4, Math.ceil(((hi - lo) * r * k) / 1.5)));
2179
+ const arc = t3Scratch("c", 2 * count + 2);
2180
+ for (let j = 0; j <= count; j++) t3At(geo, s, r, lo + ((hi - lo) * j) / count, arc, 2 * j);
2181
+ crease += t3Path(arc, count + 1, false);
2182
+ }
2183
+ if (run.role.crease !== undefined) f.d[run.role.crease] = crease;
2184
+ if (run.role.inner !== undefined) {
2185
+ let inner = "";
2186
+ for (const seam of run.seams) inner += t3SeamArc(geo, seam.s, seam.r, seam.slope, k);
2187
+ f.d[run.role.inner] = inner;
2188
+ }
2189
+ if (run.ribs) {
2190
+ const lines = t3State.detail < 0.01 ? t3None : t3RibLines(geo, run.F, cam, run.ribs, 0, 0);
2191
+ f.levels = t3Faded(f, run, "r", lines, lines.d.length);
2192
+ }
2193
+ }
2194
+
2195
+ const T3_SHINE = (() => {
2196
+ const l = Math.hypot(-0.55, -0.835);
2197
+ return [-0.55 / l, -0.835 / l];
2198
+ })();
2199
+
2200
+ function t3RouteOf(flat) {
2201
+ const n = flat.length / 3;
2202
+ const along = new Float64Array(n);
2203
+ for (let i = 1; i < n; i++) along[i] = along[i - 1] + Math.hypot(flat[3 * i] - flat[3 * i - 3], flat[3 * i + 1] - flat[3 * i - 2], flat[3 * i + 2] - flat[3 * i - 1]);
2204
+ return { flat, n, along, total: along[n - 1] };
2205
+ }
2206
+
2207
+ function t3PointAlong(route, at) {
2208
+ const { flat, n, along } = route;
2209
+ if (at <= 0) return [flat[0], flat[1], flat[2]];
2210
+ if (at >= along[n - 1]) return [flat[3 * n - 3], flat[3 * n - 2], flat[3 * n - 1]];
2211
+ let lo = 0;
2212
+ let hi = n - 1;
2213
+ while (hi - lo > 1) {
2214
+ const mid = (lo + hi) >> 1;
2215
+ if (along[mid] <= at) lo = mid;
2216
+ else hi = mid;
2217
+ }
2218
+ const span = along[hi] - along[lo] || 1;
2219
+ const t = (at - along[lo]) / span;
2220
+ return [flat[3 * lo] + (flat[3 * hi] - flat[3 * lo]) * t, flat[3 * lo + 1] + (flat[3 * hi + 1] - flat[3 * lo + 1]) * t, flat[3 * lo + 2] + (flat[3 * hi + 2] - flat[3 * lo + 2]) * t];
2221
+ }
2222
+
2223
+ function t3Cut(route, from, to) {
2224
+ const { flat, n, along } = route;
2225
+ const out = [];
2226
+ for (let i = 1; i < n; i++) {
2227
+ const start = along[i - 1];
2228
+ const end = along[i];
2229
+ if (end >= from && start <= to) {
2230
+ const length = end - start || 1;
2231
+ const t0 = Math.max(0, (from - start) / length);
2232
+ const t1 = Math.min(1, (to - start) / length);
2233
+ const at = (t) => [flat[3 * i - 3] + (flat[3 * i] - flat[3 * i - 3]) * t, flat[3 * i - 2] + (flat[3 * i + 1] - flat[3 * i - 2]) * t, flat[3 * i - 1] + (flat[3 * i + 2] - flat[3 * i - 1]) * t];
2234
+ if (!out.length) out.push(at(t0));
2235
+ out.push(at(t1));
2236
+ }
2237
+ }
2238
+ return out;
2239
+ }
2240
+
2241
+ function t3TangentAlong(route, at) {
2242
+ const a = t3PointAlong(route, Math.max(0, at - 0.6));
2243
+ const b = t3PointAlong(route, Math.min(route.total, at + 0.6));
2244
+ return t3Unit([b[0] - a[0], b[1] - a[1], b[2] - a[2]]);
2245
+ }
2246
+
2247
+ const t3RouteCache = new WeakMap();
2248
+
2249
+ function t3PrepareTube(run, part, data) {
2250
+ const spec = part.spec;
2251
+ const source = data.routes[spec.route];
2252
+ let route = t3RouteCache.get(source);
2253
+ if (!route) {
2254
+ route = t3RouteOf(source.points);
2255
+ t3RouteCache.set(source, route);
2256
+ }
2257
+ const total = route.total;
2258
+ const r = source.r;
2259
+ const step = Math.max(0.05, Math.min(source.spacing, total / 4) * 0.5);
2260
+ const lead = 0.5;
2261
+ run.r = r;
2262
+ run.wide = Boolean(source.wide);
2263
+ run.hue = source.hue ?? null;
2264
+ const partOf = (start, end) => {
2265
+ const points = t3Cut(route, Math.max(0, start), Math.min(total, end));
2266
+ let walked = Math.max(0, start);
2267
+ const count = points.length;
2268
+ const P3 = new Float64Array(3 * count);
2269
+ const D3 = new Float64Array(3 * count);
2270
+ for (let i = 0; i < count; i++) {
2271
+ if (i > 0) walked += Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1], points[i][2] - points[i - 1][2]);
2272
+ const a = t3PointAlong(route, Math.max(0, Math.min(total, walked - step)));
2273
+ const b = t3PointAlong(route, Math.max(0, Math.min(total, walked + step)));
2274
+ for (let c = 0; c < 3; c++) {
2275
+ P3[3 * i + c] = points[i][c];
2276
+ D3[3 * i + c] = b[c] - a[c];
2277
+ }
2278
+ }
2279
+ return { count, P3, D3 };
2280
+ };
2281
+ const openStart = spec.open0 && spec.from > 0;
2282
+ const openEnd = spec.open1 && spec.to < total;
2283
+ run.body = partOf(openStart ? spec.from - lead : spec.from, openEnd ? spec.to + lead : spec.to);
2284
+ run.stripe = openStart || openEnd ? partOf(openStart ? spec.from - 2 * lead : spec.from, openEnd ? spec.to + 2 * lead : spec.to) : run.body;
2285
+ const ends = [!spec.open0 && (spec.from <= 1e-6 || spec.gap0), !spec.open1 && (spec.to >= total - 1e-6 || spec.gap1)];
2286
+ run.capped = [ends[0] && (spec.gap0 || spec.caps[0]), ends[1] && (spec.gap1 || spec.caps[1])];
2287
+ const P3 = run.body.P3;
2288
+ const last = run.body.count - 1;
2289
+ const vec = (i, j) => t3Unit([P3[3 * i] - P3[3 * j], P3[3 * i + 1] - P3[3 * j + 1], P3[3 * i + 2] - P3[3 * j + 2]]);
2290
+ run.tips = [vec(1, 0), vec(last, last - 1)];
2291
+ run.rings = null;
2292
+ if (source.rings) {
2293
+ const { pitch = 3, twist = 0, cross = false, fade = [0.1, 0.55], phase = 0, steps = 16 } = source.rings;
2294
+ const lo = openStart ? spec.from - 2 * lead : spec.from;
2295
+ const hi = openEnd ? spec.to + 2 * lead : spec.to;
2296
+ const margin = twist ? 0 : Math.min(pitch * 0.35, (hi - lo) / 2);
2297
+ const from = Math.max(lo, !openStart ? lo + margin : lo);
2298
+ const to = Math.min(hi, !openEnd ? hi - margin : hi);
2299
+ const segments = [];
2300
+ if (to > from) {
2301
+ const hands = twist ? (cross ? [1, -1] : [1]) : [0];
2302
+ const reach = Math.abs(twist) * r;
2303
+ const first = Math.ceil((from - reach - phase) / pitch);
2304
+ const lastIndex = Math.floor((to + reach - phase) / pitch);
2305
+ const surface = (t, theta) => {
2306
+ const at = Math.max(0, Math.min(total, t));
2307
+ return { c: t3PointAlong(route, at), T: t3TangentAlong(route, at), theta };
2308
+ };
2309
+ for (let index = first; index <= lastIndex; index++) {
2310
+ const station = phase + index * pitch;
2311
+ for (const hand of hands) {
2312
+ const samples = [];
2313
+ for (let k = 0; k <= steps; k++) {
2314
+ const theta = (k / steps) * Math.PI;
2315
+ const t = station + hand * twist * r * (k / steps - 0.5);
2316
+ samples.push({ t, theta });
2317
+ }
2318
+ for (let k = 0; k < steps; k++) {
2319
+ let a = samples[k];
2320
+ let b = samples[k + 1];
2321
+ if ((a.t < from && b.t < from) || (a.t > to && b.t > to)) continue;
2322
+ const clipTo = (x, y, edge) => ({ t: edge, theta: x.theta + ((y.theta - x.theta) * (edge - x.t)) / (y.t - x.t || 1e-9) });
2323
+ if (a.t < from) a = clipTo(a, b, from);
2324
+ if (b.t < from) b = clipTo(b, a, from);
2325
+ if (a.t > to) a = clipTo(a, b, to);
2326
+ if (b.t > to) b = clipTo(b, a, to);
2327
+ segments.push([surface(a.t, a.theta), surface(b.t, b.theta)]);
2328
+ }
2329
+ }
2330
+ }
2331
+ }
2332
+ run.rings = { segments, fade, tone: source.rings.tone ?? "lo" };
2333
+ }
2334
+ }
2335
+
2336
+ function t3Sides(part, cam, r) {
2337
+ const m = cam.m;
2338
+ const k = cam.k;
2339
+ const n = part.count;
2340
+ const out = { n, flat: new Float64Array(2 * n), left: new Float64Array(2 * n), right: new Float64Array(2 * n), shineA: new Float64Array(2 * n), shineB: new Float64Array(2 * n), shadeA: new Float64Array(2 * n), shadeB: new Float64Array(2 * n), frames: [] };
2341
+ const half = r * k;
2342
+ for (let i = 0; i < n; i++) {
2343
+ const x = part.P3[3 * i];
2344
+ const y = part.P3[3 * i + 1];
2345
+ const z = part.P3[3 * i + 2];
2346
+ const px = cam.ox + m[0] * x + m[1] * y + m[2] * z;
2347
+ const py = cam.oy + m[3] * x + m[4] * y + m[5] * z;
2348
+ const dx = m[0] * part.D3[3 * i] + m[1] * part.D3[3 * i + 1] + m[2] * part.D3[3 * i + 2];
2349
+ const dy = m[3] * part.D3[3 * i] + m[4] * part.D3[3 * i + 1] + m[5] * part.D3[3 * i + 2];
2350
+ const l = Math.hypot(dx, dy) || 1;
2351
+ const nx = -dy / l;
2352
+ const ny = dx / l;
2353
+ out.flat[2 * i] = px;
2354
+ out.flat[2 * i + 1] = py;
2355
+ out.frames.push({ p: [px, py], nx, ny, half });
2356
+ out.left[2 * i] = px + nx * half;
2357
+ out.left[2 * i + 1] = py + ny * half;
2358
+ out.right[2 * i] = px - nx * half;
2359
+ out.right[2 * i + 1] = py - ny * half;
2360
+ const toward = nx * T3_SHINE[0] + ny * T3_SHINE[1];
2361
+ const shineAt = half * 0.44 * toward;
2362
+ const shineHalf = half * 0.24;
2363
+ out.shineA[2 * i] = px + nx * (shineAt + shineHalf);
2364
+ out.shineA[2 * i + 1] = py + ny * (shineAt + shineHalf);
2365
+ out.shineB[2 * i] = px + nx * (shineAt - shineHalf);
2366
+ out.shineB[2 * i + 1] = py + ny * (shineAt - shineHalf);
2367
+ const shadeAt = -half * 0.62 * toward;
2368
+ const shadeHalf = half * 0.2;
2369
+ out.shadeA[2 * i] = px + nx * (shadeAt + shadeHalf);
2370
+ out.shadeA[2 * i + 1] = py + ny * (shadeAt + shadeHalf);
2371
+ out.shadeB[2 * i] = px + nx * (shadeAt - shadeHalf);
2372
+ out.shadeB[2 * i + 1] = py + ny * (shadeAt - shadeHalf);
2373
+ }
2374
+ return out;
2375
+ }
2376
+
2377
+ const t3Pts = (buf, n) => {
2378
+ const out = new Array(n);
2379
+ for (let i = 0; i < n; i++) out[i] = [buf[2 * i], buf[2 * i + 1]];
2380
+ return out;
2381
+ };
2382
+
2383
+ function t3TubeCap(cam, centre, tangent, radius, inward) {
2384
+ const V = cam.V;
2385
+ const m = cam.m;
2386
+ let n1 = [tangent[1] * V[2] - tangent[2] * V[1], tangent[2] * V[0] - tangent[0] * V[2], tangent[0] * V[1] - tangent[1] * V[0]];
2387
+ if (Math.hypot(n1[0], n1[1], n1[2]) < 1e-6) {
2388
+ const helper = Math.abs(tangent[2]) > 0.9 ? [1, 0, 0] : [0, 0, 1];
2389
+ n1 = [helper[1] * tangent[2] - helper[2] * tangent[1], helper[2] * tangent[0] - helper[0] * tangent[2], helper[0] * tangent[1] - helper[1] * tangent[0]];
2390
+ }
2391
+ n1 = t3Unit(n1);
2392
+ const n2 = t3Unit([tangent[1] * n1[2] - tangent[2] * n1[1], tangent[2] * n1[0] - tangent[0] * n1[2], tangent[0] * n1[1] - tangent[1] * n1[0]]);
2393
+ const steps = 28;
2394
+ const proj = (p) => [cam.ox + m[0] * p[0] + m[1] * p[1] + m[2] * p[2], cam.oy + m[3] * p[0] + m[4] * p[1] + m[5] * p[2]];
2395
+ const ring = new Array(steps);
2396
+ for (let index = 0; index < steps; index++) {
2397
+ const angle = (index / steps) * T3_TAU;
2398
+ const cs = radius * Math.cos(angle);
2399
+ const sn = radius * Math.sin(angle);
2400
+ ring[index] = proj([centre[0] + n1[0] * cs + n2[0] * sn, centre[1] + n1[1] * cs + n2[1] * sn, centre[2] + n1[2] * cs + n2[2] * sn]);
2401
+ }
2402
+ const C = proj(centre);
2403
+ const probe = proj([centre[0] + n2[0], centre[1] + n2[1], centre[2] + n2[2]]);
2404
+ const along = (probe[0] - C[0]) * inward[0] + (probe[1] - C[1]) * inward[1];
2405
+ const from = along > 0 ? steps / 2 : 0;
2406
+ const half = (start) => Array.from({ length: steps / 2 + 1 }, (_, index) => ring[(start + index) % steps]);
2407
+ return { ring, arc: half(from), inner: half((from + steps / 2) % steps) };
2408
+ }
2409
+
2410
+ function t3StripeEnd(half, centre, nx, ny, from, to) {
2411
+ const offset = (p) => (p[0] - centre[0]) * nx + (p[1] - centre[1]) * ny;
2412
+ const values = half.map(offset);
2413
+ const lo = Math.min(from, to);
2414
+ const hi = Math.max(from, to);
2415
+ const points = [];
2416
+ const cross = (k, level) => {
2417
+ const t = (level - values[k]) / (values[k + 1] - values[k] || 1e-9);
2418
+ return [half[k][0] + (half[k + 1][0] - half[k][0]) * t, half[k][1] + (half[k + 1][1] - half[k][1]) * t];
2419
+ };
2420
+ for (let k = 0; k < half.length; k++) {
2421
+ if (values[k] >= lo && values[k] <= hi) points.push(half[k]);
2422
+ if (k < half.length - 1) for (const level of [lo, hi]) if ((values[k] - level) * (values[k + 1] - level) < 0) points.push(cross(k, level));
2423
+ }
2424
+ return points
2425
+ .map((p) => [p, offset(p)])
2426
+ .sort((x, y) => (from > to ? y[1] - x[1] : x[1] - y[1]))
2427
+ .map(([p]) => p);
2428
+ }
2429
+
2430
+ function t3EmitTube(run, cam, f) {
2431
+ const r = run.r;
2432
+ const sides = t3Sides(run.body, cam, r);
2433
+ const bands = run.stripe === run.body ? sides : t3Sides(run.stripe, cam, r);
2434
+ const n = sides.n;
2435
+ const left = t3Pts(sides.left, n);
2436
+ const right = t3Pts(sides.right, n);
2437
+ const loop = left.concat(right.slice().reverse());
2438
+ let area = 0;
2439
+ for (let i = 0; i < loop.length; i++) {
2440
+ const a = loop[i];
2441
+ const b = loop[(i + 1) % loop.length];
2442
+ area += a[0] * b[1] - b[0] * a[1];
2443
+ }
2444
+ const sign = Math.sign(area);
2445
+ let body = t3PathOf(loop, true);
2446
+ let edges = t3PathOf(left, false) + t3PathOf(right, false);
2447
+ const tips = [null, null];
2448
+ const last = n - 1;
2449
+ const quads = [];
2450
+ for (let i = 0; i < last; i++) quads.push([left[i], left[i + 1], right[i + 1], right[i]]);
2451
+ for (const side of [0, 1]) {
2452
+ if (!run.capped[side]) continue;
2453
+ const at = side ? last : 0;
2454
+ const other = side ? last - 1 : 1;
2455
+ const tangent = run.tips[side];
2456
+ const outward = side ? tangent : [-tangent[0], -tangent[1], -tangent[2]];
2457
+ const inward = [sides.flat[2 * other] - sides.flat[2 * at], sides.flat[2 * other + 1] - sides.flat[2 * at + 1]];
2458
+ const centre = [run.body.P3[3 * at], run.body.P3[3 * at + 1], run.body.P3[3 * at + 2]];
2459
+ const { ring, arc, inner } = t3TubeCap(cam, centre, tangent, r, inward);
2460
+ let ringArea = 0;
2461
+ for (let i = 0; i < ring.length; i++) {
2462
+ const a = ring[i];
2463
+ const b = ring[(i + 1) % ring.length];
2464
+ ringArea += a[0] * b[1] - b[0] * a[1];
2465
+ }
2466
+ body += t3PathOf(Math.sign(ringArea) === sign ? ring : ring.slice().reverse(), true);
2467
+ edges += t3PathOf(arc, false);
2468
+ const V = cam.V;
2469
+ const open = outward[0] * V[0] + outward[1] * V[1] + outward[2] * V[2] > 0;
2470
+ if (open) edges += t3PathOf(inner, false);
2471
+ tips[side] = { half: open ? inner : arc, frame: sides.frames[at] };
2472
+ quads.push(ring);
2473
+ }
2474
+ f.d[run.role.body] = body;
2475
+ f.d[run.role.edges] = edges;
2476
+ if (run.wide) {
2477
+ const toward = (frame) => frame.nx * T3_SHINE[0] + frame.ny * T3_SHINE[1];
2478
+ const shineOffsets = (frame) => [frame.half * 0.44 * toward(frame) + frame.half * 0.24, frame.half * 0.44 * toward(frame) - frame.half * 0.24];
2479
+ const shadeOffsets = (frame) => [-frame.half * 0.62 * toward(frame) + frame.half * 0.2, -frame.half * 0.62 * toward(frame) - frame.half * 0.2];
2480
+ const bn = bands.n;
2481
+ const band = (A, B, offsets) => {
2482
+ const head = tips[1] ? t3StripeEnd(tips[1].half, tips[1].frame.p, tips[1].frame.nx, tips[1].frame.ny, offsets(tips[1].frame)[0], offsets(tips[1].frame)[1]) : [];
2483
+ const tail = tips[0] ? t3StripeEnd(tips[0].half, tips[0].frame.p, tips[0].frame.nx, tips[0].frame.ny, offsets(tips[0].frame)[1], offsets(tips[0].frame)[0]) : [];
2484
+ return t3PathOf([...t3Pts(A, bn), ...head, ...t3Pts(B, bn).reverse(), ...tail], true);
2485
+ };
2486
+ f.d[run.role.shine] = band(bands.shineA, bands.shineB, shineOffsets);
2487
+ f.d[run.role.shade] = band(bands.shadeA, bands.shadeB, shadeOffsets);
2488
+ }
2489
+ if (run.rings) {
2490
+ const lines = t3Lines;
2491
+ lines.d.length = 0;
2492
+ lines.alpha.length = 0;
2493
+ lines.key.length = 0;
2494
+ lines.key.length = 0;
2495
+ const V = cam.V;
2496
+ const m = cam.m;
2497
+ const seg = new Float64Array(4);
2498
+ const surface = (sample, out, index) => {
2499
+ const T = sample.T;
2500
+ let n1 = [T[1] * V[2] - T[2] * V[1], T[2] * V[0] - T[0] * V[2], T[0] * V[1] - T[1] * V[0]];
2501
+ const l1 = Math.hypot(n1[0], n1[1], n1[2]);
2502
+ if (l1 < 1e-6) return null;
2503
+ n1 = [n1[0] / l1, n1[1] / l1, n1[2] / l1];
2504
+ let n2 = t3Unit([T[1] * n1[2] - T[2] * n1[1], T[2] * n1[0] - T[0] * n1[2], T[0] * n1[1] - T[1] * n1[0]]);
2505
+ if (n2[0] * V[0] + n2[1] * V[1] + n2[2] * V[2] < 0) n2 = [-n2[0], -n2[1], -n2[2]];
2506
+ const cs = Math.cos(sample.theta);
2507
+ const sn = Math.sin(sample.theta);
2508
+ const normal = [n1[0] * cs + n2[0] * sn, n1[1] * cs + n2[1] * sn, n1[2] * cs + n2[2] * sn];
2509
+ const p = [sample.c[0] + normal[0] * r, sample.c[1] + normal[1] * r, sample.c[2] + normal[2] * r];
2510
+ out[index] = cam.ox + m[0] * p[0] + m[1] * p[1] + m[2] * p[2];
2511
+ out[index + 1] = cam.oy + m[3] * p[0] + m[4] * p[1] + m[5] * p[2];
2512
+ return normal[0] * V[0] + normal[1] * V[1] + normal[2] * V[2];
2513
+ };
2514
+ const fade = run.rings.fade;
2515
+ let index = -1;
2516
+ for (const [a, b] of run.rings.segments) {
2517
+ index++;
2518
+ const fa = surface(a, seg, 0);
2519
+ const fb = surface(b, seg, 2);
2520
+ if (fa === null || fb === null) continue;
2521
+ const alpha = fade ? t3Step(fade[0], fade[1], (fa + fb) / 2) : 1;
2522
+ if (alpha * T3_LEVELS < 0.5) continue;
2523
+ lines.d.push(t3Path(seg, 2, false));
2524
+ lines.alpha.push(alpha);
2525
+ lines.key.push(index);
2526
+ }
2527
+ f.levels = t3Faded(f, run, "r", lines, lines.d.length);
2528
+ }
2529
+ const all = left.concat(right);
2530
+ t3BoxOf(all, f.box);
2531
+ for (const q of quads) for (const p of q) {
2532
+ if (p[0] < f.box[0]) f.box[0] = p[0];
2533
+ if (p[1] < f.box[1]) f.box[1] = p[1];
2534
+ if (p[0] > f.box[2]) f.box[2] = p[0];
2535
+ if (p[1] > f.box[3]) f.box[3] = p[1];
2536
+ }
2537
+ const list = f.quadList && f.quadList.length === quads.length ? f.quadList : (f.quadList = quads.map(() => [new Float64Array(0), 0]));
2538
+ for (let qi = 0; qi < quads.length; qi++) {
2539
+ const q = quads[qi];
2540
+ let entry = list[qi];
2541
+ if (entry[0].length < 2 * q.length) entry[0] = new Float64Array(2 * q.length);
2542
+ const buf = entry[0];
2543
+ for (let i = 0; i < q.length; i++) {
2544
+ buf[2 * i] = q[i][0];
2545
+ buf[2 * i + 1] = q[i][1];
2546
+ }
2547
+ entry[1] = q.length;
2548
+ }
2549
+ f.quadN = quads.length;
2550
+ f.quads = f.quadList;
2551
+ f.hullN = 0;
2552
+ }
2553
+
2554
+ function t3Emit(run, cam, out) {
2555
+ const f = out ?? t3Frame(run);
2556
+ if (run.kind === "prism") t3EmitPrism(run, cam, f);
2557
+ else if (run.kind === "round") t3EmitRound(run, cam, f);
2558
+ else if (run.kind === "ball") t3EmitBall(run, cam, f);
2559
+ else if (run.kind === "lathe") t3EmitLathe(run, cam, f);
2560
+ else if (run.kind === "tube") {
2561
+ t3EmitTube(run, cam, f);
2562
+ return f;
2563
+ } else if (run.kind === "fixed") {
2564
+ t3EmitFixed(run, cam, f);
2565
+ return f;
2566
+ }
2567
+ t3EmitPlanes(run, cam, f);
2568
+ t3EmitDots(run, cam, f);
2569
+ return f;
2570
+ }
2571
+
2572
+ function t3Overlap(a, b, pad) {
2573
+ return a[0] < b[2] - pad && b[0] < a[2] - pad && a[1] < b[3] - pad && b[1] < a[3] - pad;
2574
+ }
2575
+
2576
+ function t3Apart(A, an, B, bn, pad) {
2577
+ for (let pass = 0; pass < 2; pass++) {
2578
+ const P = pass ? B : A;
2579
+ const pn = pass ? bn : an;
2580
+ for (let i = 0; i < pn; i++) {
2581
+ const j = (i + 1) % pn;
2582
+ const nx = P[2 * j + 1] - P[2 * i + 1];
2583
+ const ny = P[2 * i] - P[2 * j];
2584
+ const l = Math.hypot(nx, ny);
2585
+ if (l < 1e-9) continue;
2586
+ let a0 = Infinity;
2587
+ let a1 = -Infinity;
2588
+ for (let q = 0; q < an; q++) {
2589
+ const v = (A[2 * q] * nx + A[2 * q + 1] * ny) / l;
2590
+ if (v < a0) a0 = v;
2591
+ if (v > a1) a1 = v;
2592
+ }
2593
+ let b0 = Infinity;
2594
+ let b1 = -Infinity;
2595
+ for (let q = 0; q < bn; q++) {
2596
+ const v = (B[2 * q] * nx + B[2 * q + 1] * ny) / l;
2597
+ if (v < b0) b0 = v;
2598
+ if (v > b1) b1 = v;
2599
+ }
2600
+ if (a1 < b0 + pad || b1 < a0 + pad) return true;
2601
+ }
2602
+ }
2603
+ return false;
2604
+ }
2605
+
2606
+ function t3Disjoint(fa, fb, pad) {
2607
+ const qa = fa.quadN ? fa.quads : null;
2608
+ const qb = fb.quadN ? fb.quads : null;
2609
+ if (!qa && !qb) return t3Apart(fa.hull, fa.hullN, fb.hull, fb.hullN, pad);
2610
+ const listA = qa ? fa.quadList : [[fa.hull, fa.hullN]];
2611
+ const listB = qb ? fb.quadList : [[fb.hull, fb.hullN]];
2612
+ for (const [A, an] of listA) for (const [B, bn] of listB) if (!t3Apart(A, an, B, bn, pad)) return false;
2613
+ return true;
2614
+ }
2615
+
2616
+ function t3Support(cloud, R, t, dir, world) {
2617
+ let best = -Infinity;
2618
+ let at = 0;
2619
+ const n = cloud.length / 3;
2620
+ const dx = R[0] * dir[0] + R[3] * dir[1] + R[6] * dir[2];
2621
+ const dy = R[1] * dir[0] + R[4] * dir[1] + R[7] * dir[2];
2622
+ const dz = R[2] * dir[0] + R[5] * dir[1] + R[8] * dir[2];
2623
+ for (let i = 0; i < n; i++) {
2624
+ const v = cloud[3 * i] * dx + cloud[3 * i + 1] * dy + cloud[3 * i + 2] * dz;
2625
+ if (v > best) {
2626
+ best = v;
2627
+ at = i;
2628
+ }
2629
+ }
2630
+ const x = cloud[3 * at];
2631
+ const y = cloud[3 * at + 1];
2632
+ const z = cloud[3 * at + 2];
2633
+ world[0] = R[0] * x + R[1] * y + R[2] * z + t[0];
2634
+ world[1] = R[3] * x + R[4] * y + R[5] * z + t[1];
2635
+ world[2] = R[6] * x + R[7] * y + R[8] * z + t[2];
2636
+ return best + dir[0] * t[0] + dir[1] * t[1] + dir[2] * t[2];
2637
+ }
2638
+
2639
+ function t3Gjk(cloudA, camA, cloudB, camB, warm, rounds = 8, hints = null) {
2640
+ let w = warm && (warm[0] || warm[1] || warm[2]) ? [warm[0], warm[1], warm[2]] : null;
2641
+ const a = [0, 0, 0];
2642
+ const b = [0, 0, 0];
2643
+ if (!w) {
2644
+ t3Support(cloudA, camA.R, camA.t, [1, 0, 0], a);
2645
+ t3Support(cloudB, camB.R, camB.t, [-1, 0, 0], b);
2646
+ w = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
2647
+ }
2648
+ let margin = -Infinity;
2649
+ let best = w;
2650
+ const step = (limit) => {
2651
+ for (let it = 0; it < limit; it++) {
2652
+ const l = Math.hypot(w[0], w[1], w[2]);
2653
+ if (l < 1e-12) break;
2654
+ const n = [w[0] / l, w[1] / l, w[2] / l];
2655
+ const hiA = t3Support(cloudA, camA.R, camA.t, n, a);
2656
+ const loB = -t3Support(cloudB, camB.R, camB.t, [-n[0], -n[1], -n[2]], b);
2657
+ const value = loB - hiA;
2658
+ if (value > margin) {
2659
+ margin = value;
2660
+ best = n;
2661
+ }
2662
+ const s = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
2663
+ const ds = [w[0] - s[0], w[1] - s[1], w[2] - s[2]];
2664
+ const dd = ds[0] * ds[0] + ds[1] * ds[1] + ds[2] * ds[2];
2665
+ if (dd < 1e-14) break;
2666
+ const tt = Math.max(0, Math.min(1, (w[0] * ds[0] + w[1] * ds[1] + w[2] * ds[2]) / dd));
2667
+ w = [w[0] - ds[0] * tt, w[1] - ds[1] * tt, w[2] - ds[2] * tt];
2668
+ }
2669
+ };
2670
+ step(rounds);
2671
+ if (margin < 0) step(24);
2672
+ if (margin < 0.05 && hints) {
2673
+ if (hints.centroids) {
2674
+ const [ca, cb] = hints.centroids;
2675
+ const wa = [camA.R[0] * ca[0] + camA.R[1] * ca[1] + camA.R[2] * ca[2] + camA.t[0], camA.R[3] * ca[0] + camA.R[4] * ca[1] + camA.R[5] * ca[2] + camA.t[1], camA.R[6] * ca[0] + camA.R[7] * ca[1] + camA.R[8] * ca[2] + camA.t[2]];
2676
+ const wb = [camB.R[0] * cb[0] + camB.R[1] * cb[1] + camB.R[2] * cb[2] + camB.t[0], camB.R[3] * cb[0] + camB.R[4] * cb[1] + camB.R[5] * cb[2] + camB.t[1], camB.R[6] * cb[0] + camB.R[7] * cb[1] + camB.R[8] * cb[2] + camB.t[2]];
2677
+ const n = t3Unit([wb[0] - wa[0], wb[1] - wa[1], wb[2] - wa[2]]);
2678
+ const hiA = t3Support(cloudA, camA.R, camA.t, n, a);
2679
+ const loB = -t3Support(cloudB, camB.R, camB.t, [-n[0], -n[1], -n[2]], b);
2680
+ if (loB - hiA > margin) {
2681
+ margin = loB - hiA;
2682
+ best = n;
2683
+ }
2684
+ }
2685
+ for (const [axis, cam] of hints) {
2686
+ const R = cam.R;
2687
+ const w0 = [R[0] * axis[0] + R[1] * axis[1] + R[2] * axis[2], R[3] * axis[0] + R[4] * axis[1] + R[5] * axis[2], R[6] * axis[0] + R[7] * axis[1] + R[8] * axis[2]];
2688
+ for (const sign of [1, -1]) {
2689
+ const n = [w0[0] * sign, w0[1] * sign, w0[2] * sign];
2690
+ const hiA = t3Support(cloudA, camA.R, camA.t, n, a);
2691
+ const loB = -t3Support(cloudB, camB.R, camB.t, [-n[0], -n[1], -n[2]], b);
2692
+ if (loB - hiA > margin) {
2693
+ margin = loB - hiA;
2694
+ best = n;
2695
+ }
2696
+ }
2697
+ }
2698
+ }
2699
+ const l = Math.hypot(best[0], best[1], best[2]) || 1;
2700
+ return { n: [best[0] / l, best[1] / l, best[2] / l], margin };
2701
+ }
2702
+
2703
+ function t3Order(layer, frames, cams, previous, stats, scene) {
2704
+ const parts = layer.parts;
2705
+ const count = parts.length;
2706
+ const local = layer.local;
2707
+ const rank = layer._rank ?? (layer._rank = new Int32Array(count));
2708
+ if (previous) for (let at = 0; at < count; at++) rank[local.get(previous[at])] = at;
2709
+ else for (let at = 0; at < count; at++) rank[local.get(layer.rest[at])] = at;
2710
+ let groupEdges = 0;
2711
+ if (layer.groupPairs) for (const gp of layer.groupPairs) groupEdges += gp.a.length * gp.b.length;
2712
+ const maxEdges = layer.planar.length / 5 + (layer.framed ? layer.framed.length / 6 : 0) + groupEdges + layer.dynamic.length / 2 + (layer.free ? layer.free.length * count : 0) + 1;
2713
+ const head = layer._head ?? (layer._head = new Int32Array(count));
2714
+ const next = layer._next && layer._next.length >= maxEdges ? layer._next : (layer._next = new Int32Array(maxEdges));
2715
+ const to = layer._to && layer._to.length >= maxEdges ? layer._to : (layer._to = new Int32Array(maxEdges));
2716
+ const live = layer._live && layer._live.length >= maxEdges ? layer._live : (layer._live = new Uint8Array(maxEdges));
2717
+ const indeg = layer._indeg ?? (layer._indeg = new Int32Array(count));
2718
+ head.fill(-1);
2719
+ indeg.fill(0);
2720
+ let edges = 0;
2721
+ const addEdge = (back, front) => {
2722
+ to[edges] = front;
2723
+ next[edges] = head[back];
2724
+ live[edges] = 1;
2725
+ head[back] = edges;
2726
+ indeg[front]++;
2727
+ edges++;
2728
+ };
2729
+ const runs = scene ? scene.runs : null;
2730
+ const groupOf = (global) => (runs ? runs[global].group : -1);
2731
+ const planar = layer.planar;
2732
+ for (let p = 0; p < planar.length; p += 5) {
2733
+ const i = planar[p];
2734
+ const j = planar[p + 1];
2735
+ const fi = frames[i];
2736
+ const fj = frames[j];
2737
+ if (!t3Overlap(fi.box, fj.box, -0.05)) continue;
2738
+ const g = groupOf(i);
2739
+ const V = g >= 0 ? cams[g].V : cams.world.V;
2740
+ const s = planar[p + 2] * V[0] + planar[p + 3] * V[1] + planar[p + 4] * V[2];
2741
+ if (s < 1e-7 && s > -1e-7) continue;
2742
+ const li = local.get(i);
2743
+ const lj = local.get(j);
2744
+ if (s > 0) addEdge(li, lj);
2745
+ else addEdge(lj, li);
2746
+ }
2747
+ const framed = layer.framed;
2748
+ if (framed)
2749
+ for (let p = 0; p < framed.length; p += 6) {
2750
+ const i = framed[p];
2751
+ const j = framed[p + 1];
2752
+ if (!t3Overlap(frames[i].box, frames[j].box, -0.05)) continue;
2753
+ const g = framed[p + 2];
2754
+ const V = g >= 0 ? cams[g].V : cams.world.V;
2755
+ const s = framed[p + 3] * V[0] + framed[p + 4] * V[1] + framed[p + 5] * V[2];
2756
+ if (s < 1e-7 && s > -1e-7) continue;
2757
+ const li = local.get(i);
2758
+ const lj = local.get(j);
2759
+ if (s > 0) addEdge(li, lj);
2760
+ else addEdge(lj, li);
2761
+ }
2762
+ if (layer.groupPairs)
2763
+ for (const gp of layer.groupPairs) {
2764
+ const union = (list, box) => {
2765
+ box[0] = Infinity;
2766
+ box[1] = Infinity;
2767
+ box[2] = -Infinity;
2768
+ box[3] = -Infinity;
2769
+ for (let q = 0; q < list.length; q++) {
2770
+ const b = frames[list[q]].box;
2771
+ if (b[0] < box[0]) box[0] = b[0];
2772
+ if (b[1] < box[1]) box[1] = b[1];
2773
+ if (b[2] > box[2]) box[2] = b[2];
2774
+ if (b[3] > box[3]) box[3] = b[3];
2775
+ }
2776
+ };
2777
+ union(gp.a, gp.boxA);
2778
+ union(gp.b, gp.boxB);
2779
+ if (!t3Overlap(gp.boxA, gp.boxB, -0.05)) continue;
2780
+ const V = gp.frame >= 0 ? cams[gp.frame].V : cams.world.V;
2781
+ const s = gp.n[0] * V[0] + gp.n[1] * V[1] + gp.n[2] * V[2];
2782
+ if (s < 1e-7 && s > -1e-7) continue;
2783
+ for (let x = 0; x < gp.a.length; x++) {
2784
+ const i = gp.a[x];
2785
+ const bi = frames[i].box;
2786
+ if (!t3Overlap(bi, gp.boxB, -0.05)) continue;
2787
+ for (let y = 0; y < gp.b.length; y++) {
2788
+ const j = gp.b[y];
2789
+ if (!t3Overlap(bi, frames[j].box, -0.05)) continue;
2790
+ if (s > 0) addEdge(local.get(i), local.get(j));
2791
+ else addEdge(local.get(j), local.get(i));
2792
+ }
2793
+ }
2794
+ }
2795
+ const dynamic = layer.dynamic;
2796
+ let unseparated = 0;
2797
+ let solved = 0;
2798
+ const rel = layer.rel ?? (layer.rel = new Map());
2799
+ const cross = (i, j) => {
2800
+ const ri = runs[i];
2801
+ const rj = runs[j];
2802
+ const ci = ri.group >= 0 ? cams[ri.group] : cams.world;
2803
+ const cj = rj.group >= 0 ? cams[rj.group] : cams.world;
2804
+ const A = ci.R;
2805
+ const B = cj.R;
2806
+ const dx = cj.t[0] - ci.t[0];
2807
+ const dy = cj.t[1] - ci.t[1];
2808
+ const dz = cj.t[2] - ci.t[2];
2809
+ const key = i * 65536 + j;
2810
+ let memo = rel.get(key);
2811
+ if (!memo) {
2812
+ memo = { pose: new Float64Array(12), n: [0, 0, 0], margin: -Infinity, fresh: false };
2813
+ rel.set(key, memo);
2814
+ }
2815
+ const pose = memo.pose;
2816
+ let same = memo.fresh;
2817
+ let at = 0;
2818
+ for (let c = 0; c < 3; c++)
2819
+ for (let r = 0; r < 3; r++) {
2820
+ const v = A[c] * B[r] + A[3 + c] * B[3 + r] + A[6 + c] * B[6 + r];
2821
+ if (same && Math.abs(pose[at] - v) > 1e-12) same = false;
2822
+ pose[at++] = v;
2823
+ }
2824
+ for (let c = 0; c < 3; c++) {
2825
+ const v = A[c] * dx + A[3 + c] * dy + A[6 + c] * dz;
2826
+ if (same && Math.abs(pose[at] - v) > 1e-9) same = false;
2827
+ pose[at++] = v;
2828
+ }
2829
+ let nx;
2830
+ let ny;
2831
+ let nz;
2832
+ if (same) {
2833
+ const n = memo.n;
2834
+ nx = A[0] * n[0] + A[1] * n[1] + A[2] * n[2];
2835
+ ny = A[3] * n[0] + A[4] * n[1] + A[5] * n[2];
2836
+ nz = A[6] * n[0] + A[7] * n[1] + A[8] * n[2];
2837
+ } else {
2838
+ const n = memo.n;
2839
+ const warm = memo.fresh ? [A[0] * n[0] + A[1] * n[1] + A[2] * n[2], A[3] * n[0] + A[4] * n[1] + A[5] * n[2], A[6] * n[0] + A[7] * n[1] + A[8] * n[2]] : null;
2840
+ const hints = [...ri.axes.map((axis) => [axis, ci]), ...rj.axes.map((axis) => [axis, cj]), [[0, 0, 1], cams.world]];
2841
+ hints.centroids = [ri.centroid, rj.centroid];
2842
+ const hit = t3Gjk(ri.cloud, ci, rj.cloud, cj, warm, 8, hints);
2843
+ solved++;
2844
+ nx = hit.n[0];
2845
+ ny = hit.n[1];
2846
+ nz = hit.n[2];
2847
+ n[0] = A[0] * nx + A[3] * ny + A[6] * nz;
2848
+ n[1] = A[1] * nx + A[4] * ny + A[7] * nz;
2849
+ n[2] = A[2] * nx + A[5] * ny + A[8] * nz;
2850
+ memo.margin = hit.margin;
2851
+ memo.fresh = true;
2852
+ }
2853
+ if (memo.margin < -0.08) unseparated++;
2854
+ const V = cams.world.V;
2855
+ return nx * V[0] + ny * V[1] + nz * V[2];
2856
+ };
2857
+ for (let p = 0; p < dynamic.length; p += 2) {
2858
+ const i = dynamic[p];
2859
+ const j = dynamic[p + 1];
2860
+ if (!t3Overlap(frames[i].box, frames[j].box, -0.05)) continue;
2861
+ const s = cross(i, j);
2862
+ if (s < 1e-7 && s > -1e-7) continue;
2863
+ const li = local.get(i);
2864
+ const lj = local.get(j);
2865
+ if (s > 0) addEdge(li, lj);
2866
+ else addEdge(lj, li);
2867
+ }
2868
+ const loose = layer.free;
2869
+ if (loose && loose.length) {
2870
+ const isLoose = layer.looseAt;
2871
+ for (let a = 0; a < loose.length; a++) {
2872
+ const i = loose[a];
2873
+ const gi = runs[i].group;
2874
+ const bi = frames[i].box;
2875
+ for (let b = 0; b < count; b++) {
2876
+ const j = parts[b];
2877
+ if (j === i || runs[j].group === gi) continue;
2878
+ if (isLoose[b] && j < i) continue;
2879
+ if (!t3Overlap(bi, frames[j].box, -0.05)) continue;
2880
+ const s = cross(i, j);
2881
+ if (s < 1e-7 && s > -1e-7) continue;
2882
+ const li = local.get(i);
2883
+ if (s > 0) addEdge(li, b);
2884
+ else addEdge(b, li);
2885
+ }
2886
+ }
2887
+ }
2888
+ const heap = layer._heap ?? (layer._heap = new Int32Array(count));
2889
+ const done = layer._done ?? (layer._done = new Uint8Array(count));
2890
+ done.fill(0);
2891
+ let size = 0;
2892
+ const less = (a, b) => rank[a] < rank[b];
2893
+ const push = (v) => {
2894
+ let i = size++;
2895
+ heap[i] = v;
2896
+ while (i > 0) {
2897
+ const p = (i - 1) >> 1;
2898
+ if (!less(heap[i], heap[p])) break;
2899
+ const tmp = heap[i];
2900
+ heap[i] = heap[p];
2901
+ heap[p] = tmp;
2902
+ i = p;
2903
+ }
2904
+ };
2905
+ const pop = () => {
2906
+ const top = heap[0];
2907
+ heap[0] = heap[--size];
2908
+ let i = 0;
2909
+ for (;;) {
2910
+ const l = 2 * i + 1;
2911
+ const r = l + 1;
2912
+ let s = i;
2913
+ if (l < size && less(heap[l], heap[s])) s = l;
2914
+ if (r < size && less(heap[r], heap[s])) s = r;
2915
+ if (s === i) break;
2916
+ const tmp = heap[i];
2917
+ heap[i] = heap[s];
2918
+ heap[s] = tmp;
2919
+ i = s;
2920
+ }
2921
+ return top;
2922
+ };
2923
+ for (let v = 0; v < count; v++) if (indeg[v] === 0) push(v);
2924
+ const out = new Int32Array(count);
2925
+ let placed = 0;
2926
+ let forced = 0;
2927
+ let refined = 0;
2928
+ let tested = false;
2929
+ const mark = layer._mark && layer._mark.length >= count ? layer._mark : (layer._mark = new Uint8Array(count));
2930
+ const via = layer._via && layer._via.length >= count ? layer._via : (layer._via = new Int32Array(count));
2931
+ const iter = layer._iter && layer._iter.length >= count ? layer._iter : (layer._iter = new Int32Array(count));
2932
+ const stack = layer._stack && layer._stack.length >= count ? layer._stack : (layer._stack = new Int32Array(count));
2933
+ const checked = layer._checked && layer._checked.length >= maxEdges ? layer._checked : (layer._checked = new Uint8Array(maxEdges));
2934
+ checked.fill(0, 0, edges);
2935
+ const breakCycle = () => {
2936
+ mark.fill(0, 0, count);
2937
+ for (let root = 0; root < count; root++) {
2938
+ if (done[root] || mark[root]) continue;
2939
+ let top = 0;
2940
+ stack[top++] = root;
2941
+ mark[root] = 1;
2942
+ iter[root] = head[root];
2943
+ while (top) {
2944
+ const v = stack[top - 1];
2945
+ let e = iter[v];
2946
+ while (e >= 0 && (!live[e] || done[to[e]] || mark[to[e]] === 2)) e = next[e];
2947
+ if (e < 0) {
2948
+ mark[v] = 2;
2949
+ top--;
2950
+ continue;
2951
+ }
2952
+ iter[v] = next[e];
2953
+ const w = to[e];
2954
+ if (mark[w] === 1) {
2955
+ let dropped = false;
2956
+ let x = v;
2957
+ let edge = e;
2958
+ for (;;) {
2959
+ if (!checked[edge]) {
2960
+ checked[edge] = 1;
2961
+ const a = x;
2962
+ const b = to[edge];
2963
+ if (t3Disjoint(frames[parts[a]], frames[parts[b]], 0.1)) {
2964
+ live[edge] = 0;
2965
+ indeg[b]--;
2966
+ refined++;
2967
+ dropped = true;
2968
+ if (indeg[b] === 0) push(b);
2969
+ }
2970
+ }
2971
+ if (x === w) break;
2972
+ edge = via[x];
2973
+ let from = -1;
2974
+ for (let s = 0; s < top; s++) if (stack[s] === x) from = s > 0 ? stack[s - 1] : -1;
2975
+ if (from < 0) break;
2976
+ x = from;
2977
+ }
2978
+ return dropped ? 1 : 0;
2979
+ }
2980
+ via[w] = e;
2981
+ mark[w] = 1;
2982
+ iter[w] = head[w];
2983
+ stack[top++] = w;
2984
+ }
2985
+ }
2986
+ return -1;
2987
+ };
2988
+ while (placed < count) {
2989
+ if (size === 0) {
2990
+ let broken = 0;
2991
+ for (let tries = 0; tries < 64 && size === 0; tries++) {
2992
+ const result = breakCycle();
2993
+ if (result <= 0) break;
2994
+ broken++;
2995
+ }
2996
+ if (size) continue;
2997
+ void broken;
2998
+ void tested;
2999
+ let pick = -1;
3000
+ for (let v = 0; v < count; v++) if (!done[v] && (pick < 0 || indeg[v] < indeg[pick] || (indeg[v] === indeg[pick] && rank[v] < rank[pick]))) pick = v;
3001
+ forced++;
3002
+ indeg[pick] = 0;
3003
+ push(pick);
3004
+ }
3005
+ const v = pop();
3006
+ if (done[v]) continue;
3007
+ done[v] = 1;
3008
+ out[placed++] = parts[v];
3009
+ for (let e = head[v]; e >= 0; e = next[e]) {
3010
+ if (!live[e]) continue;
3011
+ const w = to[e];
3012
+ if (done[w]) continue;
3013
+ if (--indeg[w] === 0) push(w);
3014
+ }
3015
+ }
3016
+ if (stats) {
3017
+ stats.edges = (stats.edges ?? 0) + edges;
3018
+ stats.forced = (stats.forced ?? 0) + forced;
3019
+ stats.refined = (stats.refined ?? 0) + refined;
3020
+ stats.unseparated = (stats.unseparated ?? 0) + unseparated;
3021
+ stats.solved = (stats.solved ?? 0) + solved;
3022
+ }
3023
+ return out;
3024
+ }
3025
+
3026
+ function t3Keep(values) {
3027
+ const n = values.length;
3028
+ const tails = [];
3029
+ const tailAt = [];
3030
+ const back = new Int32Array(n).fill(-1);
3031
+ for (let i = 0; i < n; i++) {
3032
+ let lo = 0;
3033
+ let hi = tails.length;
3034
+ while (lo < hi) {
3035
+ const mid = (lo + hi) >> 1;
3036
+ if (tails[mid] < values[i]) lo = mid + 1;
3037
+ else hi = mid;
3038
+ }
3039
+ tails[lo] = values[i];
3040
+ tailAt[lo] = i;
3041
+ back[i] = lo > 0 ? tailAt[lo - 1] : -1;
3042
+ }
3043
+ const keep = new Uint8Array(n);
3044
+ let k = tails.length ? tailAt[tails.length - 1] : -1;
3045
+ while (k >= 0) {
3046
+ keep[k] = 1;
3047
+ k = back[k];
3048
+ }
3049
+ return keep;
3050
+ }
3051
+
3052
+ function t3Cams(scene, values) {
3053
+ const poses = t3Poses(scene.groups, values);
3054
+ const cams = poses.map((pose) => t3Camera(scene.P, pose));
3055
+ cams.world = t3Camera(scene.P, null);
3056
+ cams.poses = poses;
3057
+ return cams;
3058
+ }
3059
+
3060
+ function t3Mount(stage, data, { onHold = null, onFrame = null, follow = null, painter = "svg" } = {}) {
3061
+ void painter;
3062
+ const layerEls = new Map([...stage.querySelectorAll("svg[data-live]")].map((svg) => [svg.dataset.live, svg]));
3063
+ const first = layerEls.values().next().value;
3064
+ const viewWidth = first && first.viewBox && first.viewBox.baseVal ? first.viewBox.baseVal.width : 0;
3065
+ const unitOf = () => {
3066
+ const width = first ? first.getBoundingClientRect().width : 0;
3067
+ return viewWidth && width ? Math.round((width / viewWidth) * 100) / 100 : 1;
3068
+ };
3069
+ let unit = unitOf();
3070
+ let scene = t3Prepare(data, { unit });
3071
+ const runs = () => scene.runs;
3072
+ const count = data.parts.length;
3073
+ const nodes = new Array(count);
3074
+ const bound = new Array(count);
3075
+ const cache = new Array(count);
3076
+ const containers = new Map();
3077
+ for (const [name, svg] of layerEls) containers.set(name, svg.querySelector(".iso-parts") ?? svg);
3078
+ for (const run of scene.runs) {
3079
+ const layer = data.layers[run.layer];
3080
+ const container = containers.get(layer.name);
3081
+ const node = container ? container.querySelector(`[data-p="${run.index}"]`) : null;
3082
+ nodes[run.index] = node;
3083
+ const map = {};
3084
+ if (node) {
3085
+ map.move = node;
3086
+ node.querySelectorAll("[data-r]").forEach((el) => (map[el.getAttribute("data-r")] = el));
3087
+ }
3088
+ bound[run.index] = run.roles.map((role) => map[role.name] ?? null);
3089
+ cache[run.index] = run.roles.map(() => ({ d: null, q: null, b: null, f: null, w: null, t: null, o: null, cx: null, cy: null }));
3090
+ }
3091
+ const frames = scene.runs.map((run) => t3Frame(run));
3092
+ const values = Object.fromEntries(data.groups.map((g) => [g.name, data.values ? data.values[g.name] ?? 0 : 0]));
3093
+ let lastPoses = null;
3094
+ let cams = null;
3095
+ const orders = scene.layers.map((layer) => Int32Array.from(layer.rest));
3096
+ const changes = [];
3097
+ const stats = { ms: 0, writes: 0, moved: 0, forced: 0, refined: 0, unseparated: 0, emitted: 0 };
3098
+ let writes = 0;
3099
+ const write = (index) => {
3100
+ const run = scene.runs[index];
3101
+ const f = frames[index];
3102
+ const els = bound[index];
3103
+ const memo = cache[index];
3104
+ for (let r = 0; r < run.roles.length; r++) {
3105
+ const el = els[r];
3106
+ if (!el) continue;
3107
+ const role = run.roles[r];
3108
+ const c = memo[r];
3109
+ const type = role.type;
3110
+ if (type === "path" || type === "face" || type === "slot") {
3111
+ const d = f.d[r];
3112
+ if (c.d !== d) {
3113
+ el.setAttribute("d", d);
3114
+ c.d = d;
3115
+ writes++;
3116
+ }
3117
+ if (type === "face") {
3118
+ const q = f.q[r];
3119
+ if (c.q !== q) {
3120
+ const band = Math.min(3, Math.floor(q / T3_Q));
3121
+ const mix = (q - T3_Q * band) / T3_Q;
3122
+ if (c.b !== band) {
3123
+ el.setAttribute("data-b", String(band));
3124
+ c.b = band;
3125
+ writes++;
3126
+ }
3127
+ if (c.f !== mix) {
3128
+ if (mix) el.style.setProperty("--f", String(mix));
3129
+ else el.style.removeProperty("--f");
3130
+ c.f = mix;
3131
+ writes++;
3132
+ }
3133
+ c.q = q;
3134
+ }
3135
+ const w = f.w[r];
3136
+ if (c.w !== w) {
3137
+ if (w < 0) el.style.removeProperty("stroke-width");
3138
+ else el.style.strokeWidth = String(w);
3139
+ c.w = w;
3140
+ writes++;
3141
+ }
3142
+ } else if (type === "slot") {
3143
+ const o = f.o[r];
3144
+ if (c.o !== o) {
3145
+ el.setAttribute("opacity", String(Math.round(o * 1000) / 1000));
3146
+ c.o = o;
3147
+ writes++;
3148
+ }
3149
+ }
3150
+ } else if (type === "xform" || type === "self" || type === "plane") {
3151
+ const t = f.t[r];
3152
+ if (c.t !== t) {
3153
+ if (t) el.setAttribute("transform", t);
3154
+ else el.removeAttribute("transform");
3155
+ c.t = t;
3156
+ writes++;
3157
+ }
3158
+ if (type === "plane") {
3159
+ const o = f.o[r];
3160
+ if (c.o !== o) {
3161
+ el.setAttribute("opacity", String(o));
3162
+ c.o = o;
3163
+ writes++;
3164
+ }
3165
+ }
3166
+ } else if (type === "dot") {
3167
+ const x = Math.round(f.cx[r] * 100) / 100;
3168
+ const y = Math.round(f.cy[r] * 100) / 100;
3169
+ const o = f.o[r];
3170
+ if (c.cx !== x) {
3171
+ el.setAttribute("cx", String(x));
3172
+ c.cx = x;
3173
+ writes++;
3174
+ }
3175
+ if (c.cy !== y) {
3176
+ el.setAttribute("cy", String(y));
3177
+ c.cy = y;
3178
+ writes++;
3179
+ }
3180
+ if (c.o !== o) {
3181
+ el.setAttribute("fill-opacity", String(o));
3182
+ c.o = o;
3183
+ writes++;
3184
+ }
3185
+ }
3186
+ }
3187
+ };
3188
+ const samePose = (a, b) => {
3189
+ if (!a || !b) return false;
3190
+ for (let i = 0; i < 9; i++) if (a.R[i] !== b.R[i]) return false;
3191
+ return a.t[0] === b.t[0] && a.t[1] === b.t[1] && a.t[2] === b.t[2];
3192
+ };
3193
+ let lastUnit = unit;
3194
+ let moved = 0;
3195
+ const render = () => {
3196
+ const t0 = performance.now();
3197
+ writes = 0;
3198
+ moved = 0;
3199
+ cams = t3Cams(scene, values);
3200
+ t3State.detail = detail;
3201
+ const poses = cams.poses;
3202
+ const fresh = lastUnit !== unit || !lastPoses;
3203
+ let emitted = 0;
3204
+ for (const run of scene.runs) {
3205
+ const g = run.group;
3206
+ const changed = fresh || (g >= 0 ? !samePose(poses[g], lastPoses[g]) : false);
3207
+ if (!changed) continue;
3208
+ t3Emit(run, g >= 0 ? cams[g] : cams.world, frames[run.index]);
3209
+ write(run.index);
3210
+ emitted++;
3211
+ }
3212
+ lastPoses = poses;
3213
+ lastUnit = unit;
3214
+ const orderStats = { forced: 0, refined: 0, unseparated: 0, edges: 0 };
3215
+ scene.layers.forEach((layer, index) => {
3216
+ const previous = orders[index];
3217
+ const order = t3Order(layer, frames, cams, previous, orderStats, scene);
3218
+ let same = true;
3219
+ for (let j = 0; j < order.length; j++)
3220
+ if (order[j] !== previous[j]) {
3221
+ same = false;
3222
+ break;
3223
+ }
3224
+ if (same) return;
3225
+ const container = containers.get(layer.name);
3226
+ const position = new Map();
3227
+ previous.forEach((global, at) => position.set(global, at));
3228
+ const keep = t3Keep(Array.from(order, (global) => position.get(global)));
3229
+ let after = null;
3230
+ const shifted = [];
3231
+ for (let j = order.length - 1; j >= 0; j--) {
3232
+ const node = nodes[order[j]];
3233
+ if (!keep[j] && node && container) {
3234
+ container.insertBefore(node, after);
3235
+ moved++;
3236
+ shifted.push(scene.runs[order[j]].name);
3237
+ }
3238
+ if (node) after = node;
3239
+ }
3240
+ orders[index] = order;
3241
+ if (shifted.length) changes.push({ layer: layer.name, moved: shifted });
3242
+ });
3243
+ stats.ms = performance.now() - t0;
3244
+ stats.writes = writes;
3245
+ stats.moved = moved;
3246
+ stats.forced = orderStats.forced;
3247
+ stats.refined = orderStats.refined;
3248
+ stats.unseparated = orderStats.unseparated;
3249
+ stats.emitted = emitted;
3250
+ t3State.detail = 1;
3251
+ if (onFrame) onFrame(controller);
3252
+ return { ...stats };
3253
+ };
3254
+ let detail = 1;
3255
+ const set = (next, options) => {
3256
+ if (options && options.detail !== undefined) {
3257
+ const value = Math.max(0, Math.min(1, options.detail));
3258
+ if (value !== detail) {
3259
+ detail = value;
3260
+ lastPoses = null;
3261
+ }
3262
+ }
3263
+ if (typeof next === "number") {
3264
+ const g = data.groups.find((group) => group.kind === "turn");
3265
+ if (g) values[g.name] = next;
3266
+ } else if (next) for (const [name, value] of Object.entries(next)) values[name] = value;
3267
+ if (follow) Object.assign(values, follow({ ...values }));
3268
+ return render();
3269
+ };
3270
+ let unwrap = new Map();
3271
+ const angleAt = (clientX, clientY, z = 0, groupName) => {
3272
+ const svg = first;
3273
+ const box = svg.getBoundingClientRect();
3274
+ const vb = svg.viewBox.baseVal;
3275
+ const x = vb.x + ((clientX - box.left) / box.width) * vb.width;
3276
+ const y = vb.y + ((clientY - box.top) / box.height) * vb.height;
3277
+ const c = t3Camera(scene.P, null);
3278
+ const p = (x - c.ox) / c.k;
3279
+ const q = (y - c.oy + z * c.ce * c.k) / (c.se * c.k);
3280
+ const wx = p * c.sa + q * c.ca;
3281
+ const wy = -p * c.ca + q * c.sa;
3282
+ const group = data.groups.find((g) => g.name === groupName) ?? data.groups.find((g) => g.kind === "turn");
3283
+ const pivot = group ? group.origin : [0, 0, 0];
3284
+ const raw = (Math.atan2(wy - pivot[1], wx - pivot[0]) * 180) / Math.PI;
3285
+ const key = group ? group.name : "";
3286
+ const last = unwrap.get(key);
3287
+ let angle = raw;
3288
+ if (last !== undefined) angle = last + ((((raw - last) % 360) + 540) % 360) - 180;
3289
+ unwrap.set(key, angle);
3290
+ return angle;
3291
+ };
3292
+ const observer = typeof ResizeObserver === "function" && first
3293
+ ? new ResizeObserver(() => {
3294
+ const next = unitOf();
3295
+ if (Math.abs(next - unit) < 0.05) return;
3296
+ unit = next;
3297
+ scene = t3Prepare(data, { unit });
3298
+ render();
3299
+ })
3300
+ : null;
3301
+ if (observer) observer.observe(first);
3302
+ if (follow) Object.assign(values, follow({ ...values }));
3303
+ const controller = {
3304
+ set,
3305
+ render,
3306
+ angleAt,
3307
+ get values() {
3308
+ return { ...values };
3309
+ },
3310
+ cover: (names, options) => t3Cover(controller, names, options),
3311
+ order: (name) => {
3312
+ const index = name === undefined ? 0 : scene.layers.findIndex((layer) => layer.name === name);
3313
+ return index < 0 ? [] : Array.from(orders[index], (global) => scene.runs[global].name);
3314
+ },
3315
+ orderIndex: (name) => {
3316
+ const index = name === undefined ? 0 : scene.layers.findIndex((layer) => layer.name === name);
3317
+ return index < 0 ? null : orders[index];
3318
+ },
3319
+ events: () => changes.splice(0, changes.length),
3320
+ stats: () => ({ ...stats }),
3321
+ frames: () => frames,
3322
+ cams: () => cams,
3323
+ scene: () => scene,
3324
+ pose: (name) => t3PoseOf(scene, cams, name),
3325
+ detach: (name) => {
3326
+ const index = scene.groups.findIndex((g) => g.name === name);
3327
+ const pose = t3PoseOf(scene, cams, name);
3328
+ const o = scene.groups[index].origin;
3329
+ return { world: true, R: pose.R, t: [pose.t[0] - o[0], pose.t[1] - o[1], pose.t[2] - o[2]] };
3330
+ },
3331
+ anchor: (name, point, direction) => t3Anchor(scene, cams, name, point, direction),
3332
+ destroy: () => {
3333
+ if (observer) observer.disconnect();
3334
+ if (typeof window !== "undefined" && window.__isoTurn && window.__isoTurn.controller === controller) delete window.__isoTurn;
3335
+ },
3336
+ };
3337
+ const hold = () => {
3338
+ if (onHold) onHold();
3339
+ };
3340
+ if (typeof window !== "undefined") {
3341
+ window.__isoTurn = {
3342
+ controller,
3343
+ set(next) {
3344
+ hold();
3345
+ return set(next);
3346
+ },
3347
+ get() {
3348
+ hold();
3349
+ return { ...values };
3350
+ },
3351
+ order(name) {
3352
+ hold();
3353
+ return controller.order(name);
3354
+ },
3355
+ events() {
3356
+ hold();
3357
+ return controller.events();
3358
+ },
3359
+ stats() {
3360
+ hold();
3361
+ return controller.stats();
3362
+ },
3363
+ layers() {
3364
+ hold();
3365
+ return scene.layers.map((layer) => layer.name);
3366
+ },
3367
+ };
3368
+ }
3369
+ render();
3370
+ return controller;
3371
+ }
3372
+
3373
+ function t3Enclose(points, edges) {
3374
+ const hull = t3Hull(points);
3375
+ const n = hull.length;
3376
+ if (n < 3) return [];
3377
+ let area = 0;
3378
+ for (let i = 0; i < n; i++) area += hull[i][0] * hull[(i + 1) % n][1] - hull[(i + 1) % n][0] * hull[i][1];
3379
+ const sign = area >= 0 ? 1 : -1;
3380
+ let lines = [];
3381
+ for (let i = 0; i < n; i++) {
3382
+ const p = hull[i];
3383
+ const q = hull[(i + 1) % n];
3384
+ const dx = q[0] - p[0];
3385
+ const dy = q[1] - p[1];
3386
+ const l = Math.hypot(dx, dy);
3387
+ if (l < 1e-9) continue;
3388
+ const nx = (sign * dy) / l;
3389
+ const ny = (-sign * dx) / l;
3390
+ lines.push([nx, ny, -(nx * p[0] + ny * p[1])]);
3391
+ }
3392
+ const meet = (A, B) => {
3393
+ const det = A[0] * B[1] - A[1] * B[0];
3394
+ if (Math.abs(det) < 1e-12) return null;
3395
+ return [(-A[2] * B[1] + B[2] * A[1]) / det, (-A[0] * B[2] + B[0] * A[2]) / det];
3396
+ };
3397
+ while (lines.length > edges) {
3398
+ let best = -1;
3399
+ let cost = Infinity;
3400
+ const m = lines.length;
3401
+ for (let i = 0; i < m; i++) {
3402
+ const prev = lines[(i - 1 + m) % m];
3403
+ const self = lines[i];
3404
+ const next = lines[(i + 1) % m];
3405
+ const turn = prev[0] * next[1] - prev[1] * next[0];
3406
+ if (sign * turn <= 1e-9) continue;
3407
+ const apex = meet(prev, next);
3408
+ const a = meet(prev, self);
3409
+ const b = meet(self, next);
3410
+ if (!apex || !a || !b) continue;
3411
+ const added = Math.abs((a[0] - apex[0]) * (b[1] - apex[1]) - (a[1] - apex[1]) * (b[0] - apex[0])) / 2;
3412
+ if (added < cost) {
3413
+ cost = added;
3414
+ best = i;
3415
+ }
3416
+ }
3417
+ if (best < 0) break;
3418
+ lines.splice(best, 1);
3419
+ }
3420
+ return lines;
3421
+ }
3422
+
3423
+ function t3Cover(controller, names, { polygons = 6, edges = 10, pad = 0.3, layers = null } = {}) {
3424
+ const uCoverEdge = new Float32Array(polygons * edges * 3);
3425
+ const uCoverCount = new Float32Array(polygons);
3426
+ const scene = controller.scene();
3427
+ const frames = controller.frames();
3428
+ const sets = Array.isArray(names) ? (names.length && Array.isArray(names[0]) ? names : [names]) : [[names]];
3429
+ const candidates = [];
3430
+ const taken = new Set();
3431
+ for (const list of sets) {
3432
+ const targets = scene.runs.filter((run) => list.includes(run.name));
3433
+ if (!targets.length) continue;
3434
+ const box = [Infinity, Infinity, -Infinity, -Infinity];
3435
+ for (const run of targets) {
3436
+ const b = frames[run.index].box;
3437
+ box[0] = Math.min(box[0], b[0]);
3438
+ box[1] = Math.min(box[1], b[1]);
3439
+ box[2] = Math.max(box[2], b[2]);
3440
+ box[3] = Math.max(box[3], b[3]);
3441
+ }
3442
+ const home = targets[0].layer;
3443
+ scene.layers.forEach((layer, index) => {
3444
+ const order = controller.orderIndex(layer.name);
3445
+ if (!order) return;
3446
+ let from = 0;
3447
+ if (index === home) {
3448
+ for (let at = 0; at < order.length; at++) if (targets.some((run) => run.index === order[at])) from = at + 1;
3449
+ } else if (!(layers && layers.includes(layer.name)) || index < home) return;
3450
+ for (let at = from; at < order.length; at++) {
3451
+ const run = scene.runs[order[at]];
3452
+ if (list.includes(run.name) || taken.has(run.index)) continue;
3453
+ const f = frames[run.index];
3454
+ const b = f.box;
3455
+ if (!(b[0] < box[2] + pad && box[0] < b[2] + pad && b[1] < box[3] + pad && box[1] < b[3] + pad)) continue;
3456
+ if (!targets.some((target) => !t3Disjoint(frames[target.index], f, -pad))) continue;
3457
+ taken.add(run.index);
3458
+ const share = (Math.min(b[2], box[2]) - Math.max(b[0], box[0])) * (Math.min(b[3], box[3]) - Math.max(b[1], box[1]));
3459
+ candidates.push({ run, f, share });
3460
+ }
3461
+ });
3462
+ }
3463
+ candidates.sort((a, b) => b.share - a.share);
3464
+ const parts = [];
3465
+ let slot = 0;
3466
+ for (const { run, f } of candidates) {
3467
+ if (slot >= polygons) break;
3468
+ const points = [];
3469
+ if (f.quadN) for (const [buf, count] of f.quadList) for (let i = 0; i < count; i++) points.push([buf[2 * i], buf[2 * i + 1]]);
3470
+ else for (let i = 0; i < f.hullN; i++) points.push([f.hull[2 * i], f.hull[2 * i + 1]]);
3471
+ const lines = t3Enclose(points, edges);
3472
+ if (!lines.length) continue;
3473
+ lines.forEach((line, e) => {
3474
+ const at = (slot * edges + e) * 3;
3475
+ uCoverEdge[at] = line[0];
3476
+ uCoverEdge[at + 1] = line[1];
3477
+ uCoverEdge[at + 2] = line[2] - pad;
3478
+ });
3479
+ uCoverCount[slot] = lines.length;
3480
+ parts.push(run.name);
3481
+ slot++;
3482
+ }
3483
+ return { uCoverEdge, uCoverCount, dropped: Math.max(0, candidates.length - slot), parts };
3484
+ }
3485
+
3486
+ function t3PoseOf(scene, cams, name) {
3487
+ const index = scene.groups.findIndex((g) => g.name === name);
3488
+ const cam = index >= 0 && cams ? cams[index] : null;
3489
+ return { R: cam ? Array.from(cam.R) : Array.from(T3_EYE), t: cam ? [cam.t[0], cam.t[1], cam.t[2]] : [0, 0, 0] };
3490
+ }
3491
+
3492
+ function t3Anchor(scene, cams, name, point, direction) {
3493
+ const index = name === null || name === undefined ? -1 : scene.groups.findIndex((g) => g.name === name);
3494
+ const cam = index >= 0 ? cams[index] : cams.world;
3495
+ const o = index >= 0 ? scene.groups[index].origin : [0, 0, 0];
3496
+ const R = cam.R;
3497
+ const q = [point[0] - o[0], point[1] - o[1], point[2] - o[2]];
3498
+ const world = [R[0] * q[0] + R[1] * q[1] + R[2] * q[2] + cam.t[0], R[3] * q[0] + R[4] * q[1] + R[5] * q[2] + cam.t[1], R[6] * q[0] + R[7] * q[1] + R[8] * q[2] + cam.t[2]];
3499
+ const w = cams.world;
3500
+ const screen = [w.ox + w.m[0] * world[0] + w.m[1] * world[1] + w.m[2] * world[2], w.oy + w.m[3] * world[0] + w.m[4] * world[1] + w.m[5] * world[2]];
3501
+ const out = { world, screen };
3502
+ if (direction) out.direction = [R[0] * direction[0] + R[1] * direction[1] + R[2] * direction[2], R[3] * direction[0] + R[4] * direction[1] + R[5] * direction[2], R[6] * direction[0] + R[7] * direction[1] + R[8] * direction[2]];
3503
+ return out;
3504
+ }
3505
+
3506
+ function t3FloorOf(scene, name, R = T3_EYE) {
3507
+ const index = scene.groups.findIndex((g) => g.name === name);
3508
+ let low = Infinity;
3509
+ for (const run of scene.runs) {
3510
+ if (run.group !== index) continue;
3511
+ const c = run.cloud;
3512
+ for (let i = 0; i < c.length; i += 3) {
3513
+ const z = R[6] * c[i] + R[7] * c[i + 1] + R[8] * c[i + 2];
3514
+ if (z < low) low = z;
3515
+ }
3516
+ }
3517
+ return low;
3518
+ }
3519
+
3520
+ function t3Compose(A, B) {
3521
+ return Array.from(t3Mul(A, B));
3522
+ }
3523
+
3524
+ function t3PoseUniforms(controller, group, prefix = "uGroup") {
3525
+ const scene = controller.scene();
3526
+ const index = scene.groups.findIndex((g) => g.name === group);
3527
+ const cams = controller.cams();
3528
+ const cam = index >= 0 && cams ? cams[index] : null;
3529
+ const R = cam ? cam.R : T3_EYE;
3530
+ const t = cam ? cam.t : [0, 0, 0];
3531
+ const o = index >= 0 ? scene.groups[index].origin : [0, 0, 0];
3532
+ const T = [t[0] - (R[0] * o[0] + R[1] * o[1] + R[2] * o[2]), t[1] - (R[3] * o[0] + R[4] * o[1] + R[5] * o[2]), t[2] - (R[6] * o[0] + R[7] * o[1] + R[8] * o[2])];
3533
+ return { [`${prefix}X`]: [R[0], R[3], R[6]], [`${prefix}Y`]: [R[1], R[4], R[7]], [`${prefix}Z`]: [R[2], R[5], R[8]], [`${prefix}T`]: T };
3534
+ }
3535
+
3536
+ const T3_MAT_KEYS = ["paper", "face", "top", "shade-0", "shade-1", "shade-2", "shade-3", "lit-top", "lit-shade", "hi", "mid", "lo", "faint", "dot", "lit"];
3537
+ const T3_MAT_TUBE = ["body", "shine", "shade", "edge"];
3538
+
3539
+ const T3_MATERIALS = {
3540
+ gold: {
3541
+ light: ["#d3bb88", "#e4d2a8", "#f1e5c6", "#a1844f", "#bc9f69", "#d3bb88", "#e4d2a8", "#f8efd8", "#dfcb9c", "#4b3920", "#86693f", "#a98c5b", "#c1a676", "#4a381f", "#2a1d0c", "#cdb07a", "#f3e8cc", "#8e7044", "#4b3920"],
3542
+ dark: ["#3f3521", "#4b3f28", "#5a4c31", "#2b2416", "#352c1b", "#3f3521", "#4b3f28", "#6a5a3b", "#4f4329", "#e6d2a3", "#8a7550", "#5e4e33", "#463a26", "#e6d2a3", "#fff3d6", "#4a3e27", "#8c7853", "#251f13", "#e6d2a3"],
3543
+ },
3544
+ chrome: {
3545
+ light: ["#8e959c", "#c3c8cd", "#f7f8f9", "#a2a9b0", "#6f767d", "#454b52", "#cacfd4", "#ffffff", "#bcc2c8", "#202428", "#585e65", "#878d94", "#a9afb5", "#2a2e33", "#000000", "#8b9299", "#fbfcfd", "#3c4248", "#202428"],
3546
+ dark: ["#2a2e32", "#4a5056", "#6e757c", "#2e3236", "#24272b", "#1a1d20", "#4a5056", "#848b92", "#3a3f44", "#dfe3e7", "#7c838a", "#4d5359", "#363b40", "#e4e7ea", "#ffffff", "#34393e", "#9aa1a8", "#16191b", "#dfe3e7"],
3547
+ },
3548
+ steel: {
3549
+ light: ["#b8bdc1", "#cdd1d4", "#e5e8ea", "#8f959b", "#a4aaaf", "#b8bdc1", "#cdd1d4", "#f1f3f4", "#c6cacd", "#2b2f33", "#6b7177", "#959ba1", "#aeb3b8", "#33373b", "#000000", "#b3b8bd", "#eef0f2", "#7d8389", "#2b2f33"],
3550
+ dark: ["#2c3034", "#34393d", "#3e4348", "#1f2225", "#25292c", "#2c3034", "#34393d", "#4a5056", "#33383c", "#d0d4d8", "#727980", "#4a5056", "#33383d", "#d8dce0", "#ffffff", "#30353a", "#5c6369", "#1a1d20", "#d0d4d8"],
3551
+ },
3552
+ gunmetal: {
3553
+ light: ["#444a51", "#50575e", "#5f666e", "#2f3439", "#393f45", "#444a51", "#50575e", "#6d757d", "#4b5258", "#0f1113", "#1d2125", "#272b30", "#30353a", "#b0b5ba", "#000000", "#3d4349", "#6e757c", "#24282c", "#0f1113"],
3554
+ dark: ["#1d2125", "#22272b", "#2a2f34", "#15181b", "#191c20", "#1d2125", "#22272b", "#323840", "#20252a", "#a9b0b7", "#5c636a", "#3a4046", "#2a2f34", "#b8bec4", "#ffffff", "#1f2327", "#3a4046", "#111316", "#a9b0b7"],
3555
+ },
3556
+ rubber: {
3557
+ light: ["#2a2b2e", "#34363a", "#46484d", "#1b1c1e", "#222326", "#2a2b2e", "#34363a", "#55575c", "#2f3134", "#070708", "#111214", "#18191b", "#1f2023", "#6a6c70", "#000000", "#27282b", "#5b5d62", "#121314", "#050506"],
3558
+ dark: ["#121213", "#161618", "#1c1c1f", "#0c0c0d", "#0f0f10", "#121213", "#161618", "#232327", "#141416", "#8c8e92", "#4a4c50", "#2c2d30", "#1f2023", "#9a9ca0", "#ffffff", "#131315", "#2b2c30", "#0a0a0b", "#8c8e92"],
3559
+ },
3560
+ brass: {
3561
+ light: ["#bd9646", "#d2ad5b", "#e6c87e", "#8a6a2c", "#a47f38", "#bd9646", "#d2ad5b", "#f0d796", "#c8a252", "#3f2e0e", "#765a24", "#9a7a3c", "#b08c46", "#3a2a0c", "#1f1606", "#b8913f", "#efd795", "#7d5f26", "#3f2e0e"],
3562
+ dark: ["#3d3017", "#49391c", "#574523", "#2a210f", "#332813", "#3d3017", "#49391c", "#66522b", "#45371b", "#e9cf8a", "#8e7438", "#5e4c25", "#44371c", "#e9cf8a", "#fff0c8", "#433419", "#8a6f36", "#21190b", "#e9cf8a"],
3563
+ },
3564
+ copper: {
3565
+ light: ["#b3633e", "#c9784f", "#df9670", "#7c4128", "#9a5132", "#b3633e", "#c9784f", "#eaa985", "#be6d46", "#3a1a0c", "#6e3820", "#925037", "#a65d3f", "#3a1a0c", "#1c0b04", "#ad5f3b", "#f0b896", "#6e3720", "#3a1a0c"],
3566
+ dark: ["#3d1f13", "#492617", "#582e1c", "#2a160d", "#331a10", "#3d1f13", "#492617", "#673722", "#432215", "#eaa985", "#8f5236", "#5f3624", "#45271a", "#eaa985", "#ffe0cc", "#42221a", "#8a4c31", "#1f0f08", "#eaa985"],
3567
+ },
3568
+ };
3569
+
3570
+ function t3MaterialCss() {
3571
+ const rules = [];
3572
+ const block = (values) => T3_MAT_KEYS.map((key, i) => `--anatomy-${key}:${values[i]}`).concat(T3_MAT_TUBE.map((key, i) => `--mat-${key}:${values[T3_MAT_KEYS.length + i]}`)).join(";");
3573
+ for (const [name, { light, dark }] of Object.entries(T3_MATERIALS)) {
3574
+ rules.push(`[data-mat="${name}"]{${block(dark)}}`);
3575
+ rules.push(`.iso[data-theme="light"] [data-mat="${name}"]{${block(light)}}`);
3576
+ rules.push(`.iso[data-theme="dark"] [data-mat="${name}"]{${block(dark)}}`);
3577
+ }
3578
+ rules.push("[data-mat] .tb-body{fill:var(--mat-body)}");
3579
+ rules.push("[data-mat] .tb-shine{fill:var(--mat-shine)}");
3580
+ rules.push("[data-mat] .tb-shade{fill:var(--mat-shade)}");
3581
+ rules.push("[data-mat] .tb-edge{stroke:var(--mat-edge)}");
3582
+ return rules.join("\n");
3583
+ }
3584
+
3585
+ function t3Css() {
3586
+ const rules = [];
3587
+ const ramp = "--tn-0:var(--anatomy-shade-0);--tn-1:var(--anatomy-shade-1);--tn-2:var(--anatomy-shade-2);--tn-3:var(--anatomy-shade-3);--tn-4:var(--anatomy-top)";
3588
+ rules.push(`.iso-live{${ramp}}`);
3589
+ rules.push(`.iso-live [data-mat]{${ramp}}`);
3590
+ rules.push(".iso-live [data-lit]{--tn-0:var(--anatomy-lit-shade);--tn-1:var(--anatomy-lit-shade);--tn-2:var(--anatomy-lit-shade);--tn-3:var(--anatomy-lit-shade);--tn-4:var(--anatomy-lit-top)}");
3591
+ const colour = "color-mix(in srgb,var(--ta) calc(100% - var(--f,0) * 100%),var(--tb))";
3592
+ rules.push(`.iso-tn{stroke-width:.4;stroke-linejoin:round;--ta:var(--tn-0);--tb:var(--tn-1);fill:${colour};stroke:${colour}}`);
3593
+ for (let i = 1; i < 4; i++) rules.push(`.iso-tn[data-b="${i}"]{--ta:var(--tn-${i});--tb:var(--tn-${i + 1})}`);
3594
+ rules.push(t3MaterialCss());
3595
+ for (const hue of ["red", "green", "blue"]) rules.push(`.tb[data-hue="${hue}"]{--tb-ink:var(--anatomy-${hue})}`);
3596
+ rules.push(".tb[data-hue] .tb-body{fill:color-mix(in srgb,var(--tb-ink) 54%,var(--anatomy-shade-2))}");
3597
+ rules.push(".tb[data-hue] .tb-shine{fill:color-mix(in srgb,var(--tb-ink) 16%,var(--anatomy-top))}");
3598
+ rules.push(".tb[data-hue] .tb-shade{fill:color-mix(in srgb,var(--tb-ink) 30%,var(--anatomy-hi))}");
3599
+ rules.push(".tb[data-hue] .tb-edge{stroke:color-mix(in srgb,var(--tb-ink) 38%,var(--anatomy-hi))}");
3600
+ rules.push(".tb[data-hue][data-mat] .tb-body{fill:color-mix(in srgb,var(--tb-ink) 62%,var(--mat-body))}");
3601
+ rules.push(".tb[data-hue][data-mat] .tb-shine{fill:color-mix(in srgb,var(--tb-ink) 22%,var(--mat-shine))}");
3602
+ rules.push(".tb[data-hue][data-mat] .tb-shade{fill:color-mix(in srgb,var(--tb-ink) 46%,var(--mat-shade))}");
3603
+ rules.push(".tb[data-hue][data-mat] .tb-edge{stroke:color-mix(in srgb,var(--tb-ink) 30%,var(--mat-edge))}");
3604
+ rules.push(".iso-turn{position:relative}");
3605
+ rules.push(".iso-turn>.iso-layer{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}");
3606
+ return rules.join("\n");
3607
+ }
3608
+
3609
+ export const TURN = { materials: Object.keys(T3_MATERIALS), materialCss: t3MaterialCss, rotation: (axis, degrees) => Array.from(t3Rot(axis, degrees)), quat: (q) => Array.from(t3Quat(q)), compose: t3Compose, floorOf: t3FloorOf, anchor: t3Anchor, camera: t3Camera, poses: t3PosesMap, prepare: t3Prepare, emit: t3Emit, order: t3Order, mount: t3Mount, cover: t3Cover, poseUniforms: t3PoseUniforms, css: t3Css, frame: t3Frame, cams: t3Cams, gjk: t3Gjk, hintsOf: (ri, ci, rj, cj, world) => Object.assign([...ri.axes.map((axis) => [axis, ci]), ...rj.axes.map((axis) => [axis, cj]), [[0, 0, 1], world]], { centroids: [ri.centroid, rj.centroid] }), disjoint: t3Disjoint, levels: T3_LEVELS, q: T3_Q, exact: (on) => { t3State.exact = Boolean(on); } };
3610
+ export const TURN_CSS = t3Css();