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