@weasel-js/geom 1.10.1 → 1.11.1
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 +9 -1
- package/dist/3d/index.d.ts +29 -2
- package/dist/3d/index.js +49 -1
- package/dist/3d/index.js.map +1 -1
- package/dist/booleans/index.d.ts +18 -11
- package/dist/booleans/index.js +432 -352
- package/dist/booleans/index.js.map +1 -1
- package/dist/chunk-4Y67BZL5.js +573 -0
- package/dist/chunk-4Y67BZL5.js.map +1 -0
- package/dist/chunk-6FGQML7D.js +152 -0
- package/dist/chunk-6FGQML7D.js.map +1 -0
- package/dist/{chunk-7WFFCHRL.js → chunk-7F66PQCJ.js} +6 -37
- package/dist/chunk-7F66PQCJ.js.map +1 -0
- package/dist/chunk-B53BERKG.js +386 -0
- package/dist/chunk-B53BERKG.js.map +1 -0
- package/dist/chunk-CE7IY7TB.js +104 -0
- package/dist/chunk-CE7IY7TB.js.map +1 -0
- package/dist/chunk-EAKE3N7W.js +804 -0
- package/dist/chunk-EAKE3N7W.js.map +1 -0
- package/dist/chunk-LRVPK7HI.js +491 -0
- package/dist/chunk-LRVPK7HI.js.map +1 -0
- package/dist/chunk-Q66NA2FU.js +174 -0
- package/dist/chunk-Q66NA2FU.js.map +1 -0
- package/dist/chunk-UI3FUWXM.js +533 -0
- package/dist/chunk-UI3FUWXM.js.map +1 -0
- package/dist/curvature-PkoLduGc.d.ts +454 -0
- package/dist/curves/index.d.ts +14 -5
- package/dist/curves/index.js +33 -111
- package/dist/curves/index.js.map +1 -1
- package/dist/index.d.ts +1511 -283
- package/dist/index.js +5755 -2674
- package/dist/index.js.map +1 -1
- package/dist/nd/index.d.ts +92 -27
- package/dist/nd/index.js +297 -0
- package/dist/nd/index.js.map +1 -1
- package/dist/path-BW8d_2RY.d.ts +157 -0
- package/dist/portCurve-C5HDWuuV.d.ts +30 -0
- package/dist/tessellate/index.d.ts +15 -4
- package/dist/tessellate/index.js +21 -277
- package/dist/tessellate/index.js.map +1 -1
- package/dist/warp/index.d.ts +254 -0
- package/dist/warp/index.js +1448 -0
- package/dist/warp/index.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-7WFFCHRL.js.map +0 -1
- package/dist/chunk-IDXY2NCA.js +0 -177
- package/dist/chunk-IDXY2NCA.js.map +0 -1
- package/dist/chunk-NJ3IGAKR.js +0 -16
- package/dist/chunk-NJ3IGAKR.js.map +0 -1
- package/dist/chunk-OXH3NOXC.js +0 -62
- package/dist/chunk-OXH3NOXC.js.map +0 -1
- package/dist/chunk-URQMDR6Q.js +0 -80
- package/dist/chunk-URQMDR6Q.js.map +0 -1
- package/dist/cubicMath-BW-HJrCE.d.ts +0 -16
- package/dist/path-ljHxobyu.d.ts +0 -44
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { segmentEnds, isStraightSegment, anchorsToPath, pathToAnchors } from './chunk-Q66NA2FU.js';
|
|
2
|
+
import { cubicPointAt } from './chunk-B53BERKG.js';
|
|
3
|
+
|
|
4
|
+
// src/paths/curvature.ts
|
|
5
|
+
function cubicDerivativeAt(p0, p1, p2, p3, t) {
|
|
6
|
+
const u = 1 - t;
|
|
7
|
+
return {
|
|
8
|
+
x: 3 * u * u * (p1.x - p0.x) + 6 * u * t * (p2.x - p1.x) + 3 * t * t * (p3.x - p2.x),
|
|
9
|
+
y: 3 * u * u * (p1.y - p0.y) + 6 * u * t * (p2.y - p1.y) + 3 * t * t * (p3.y - p2.y)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function cubicSecondDerivativeAt(p0, p1, p2, p3, t) {
|
|
13
|
+
const u = 1 - t;
|
|
14
|
+
return {
|
|
15
|
+
x: 6 * u * (p2.x - 2 * p1.x + p0.x) + 6 * t * (p3.x - 2 * p2.x + p1.x),
|
|
16
|
+
y: 6 * u * (p2.y - 2 * p1.y + p0.y) + 6 * t * (p3.y - 2 * p2.y + p1.y)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function cubicCurvatureAt(p0, p1, p2, p3, t) {
|
|
20
|
+
const d1 = cubicDerivativeAt(p0, p1, p2, p3, t);
|
|
21
|
+
const d2 = cubicSecondDerivativeAt(p0, p1, p2, p3, t);
|
|
22
|
+
const den = Math.pow(d1.x * d1.x + d1.y * d1.y, 1.5);
|
|
23
|
+
if (den < 1e-12) return 0;
|
|
24
|
+
return (d1.x * d2.y - d1.y * d2.x) / den;
|
|
25
|
+
}
|
|
26
|
+
var END_NUDGE = 1e-3;
|
|
27
|
+
function sampleCount(opts) {
|
|
28
|
+
return Math.max(2, Math.round(opts.samplesPerSegment ?? 24));
|
|
29
|
+
}
|
|
30
|
+
function combTeeth(source, opts) {
|
|
31
|
+
const samples = sampleCount(opts);
|
|
32
|
+
const teeth = [];
|
|
33
|
+
for (let i = 0; i < samples; i++) teeth.push(toothAt(source, i / (samples - 1), opts.scale));
|
|
34
|
+
return teeth;
|
|
35
|
+
}
|
|
36
|
+
function cubicCurvatureSource(p0, p1, p2, p3) {
|
|
37
|
+
return {
|
|
38
|
+
pointAt: (t) => cubicPointAt(p0, p1, p2, p3, t),
|
|
39
|
+
tangentAt: (t) => cubicDerivativeAt(p0, p1, p2, p3, t),
|
|
40
|
+
curvatureAt: (t) => cubicCurvatureAt(p0, p1, p2, p3, t)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function curvatureComb(set, opts) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let sub = 0; sub < set.anchors.length; sub++) {
|
|
46
|
+
const count = set.anchors[sub].length - (set.closed[sub] ? 0 : 1);
|
|
47
|
+
for (let segIdx = 0; segIdx < count; segIdx++) {
|
|
48
|
+
const ends = segmentEnds(set.anchors, set.closed, sub, segIdx);
|
|
49
|
+
if (!ends || isStraightSegment(ends.a, ends.b)) continue;
|
|
50
|
+
const { a, b } = ends;
|
|
51
|
+
const source = cubicCurvatureSource(a, a.outHandle ?? a, b.inHandle ?? b, b);
|
|
52
|
+
out.push({ sub, segIdx, teeth: combTeeth(source, opts) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function toothAt(source, t, scale) {
|
|
58
|
+
const at = source.pointAt(t);
|
|
59
|
+
let d = source.tangentAt(t);
|
|
60
|
+
let k = source.curvatureAt(t);
|
|
61
|
+
if (Math.hypot(d.x, d.y) < 1e-9) {
|
|
62
|
+
const inner = t < 0.5 ? t + END_NUDGE : t - END_NUDGE;
|
|
63
|
+
d = source.tangentAt(inner);
|
|
64
|
+
k = source.curvatureAt(inner);
|
|
65
|
+
}
|
|
66
|
+
const len = Math.hypot(d.x, d.y);
|
|
67
|
+
if (len < 1e-12) return { x: at.x, y: at.y, tipX: at.x, tipY: at.y, curvature: 0 };
|
|
68
|
+
const nx = -d.y / len;
|
|
69
|
+
const ny = d.x / len;
|
|
70
|
+
return { x: at.x, y: at.y, tipX: at.x - nx * k * scale, tipY: at.y - ny * k * scale, curvature: k };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/paths/eulerSpiral.ts
|
|
74
|
+
var STEPS = 48;
|
|
75
|
+
var MAX_ANGLE = Math.PI * 0.95;
|
|
76
|
+
var clampAngle = (a) => Math.max(-MAX_ANGLE, Math.min(MAX_ANGLE, a));
|
|
77
|
+
function integrateEuler(k0, k1, upTo = 1, k2 = 0, k3 = 0) {
|
|
78
|
+
const n = Math.max(2, Math.ceil(STEPS * upTo / 2) * 2);
|
|
79
|
+
const h = upTo / n;
|
|
80
|
+
let x = 0;
|
|
81
|
+
let y = 0;
|
|
82
|
+
for (let i = 0; i <= n; i++) {
|
|
83
|
+
const u = i * h - 0.5;
|
|
84
|
+
const phi = k0 * u + u * u * (k1 / 2 + u * (k2 / 6 + u * k3 / 24));
|
|
85
|
+
const w = i === 0 || i === n ? 1 : i % 2 === 1 ? 4 : 2;
|
|
86
|
+
x += w * Math.cos(phi);
|
|
87
|
+
y += w * Math.sin(phi);
|
|
88
|
+
}
|
|
89
|
+
return { x: x * h / 3, y: y * h / 3 };
|
|
90
|
+
}
|
|
91
|
+
function startError(k0, k1, k2, k3, th0) {
|
|
92
|
+
const c = integrateEuler(k0, k1, 1, k2, k3);
|
|
93
|
+
return -k0 / 2 + k1 / 8 - k2 / 48 + k3 / 384 - Math.atan2(c.y, c.x) - th0;
|
|
94
|
+
}
|
|
95
|
+
function fitEulerSegment(th0, th1, k2 = 0, k3 = 0) {
|
|
96
|
+
const t0 = clampAngle(th0);
|
|
97
|
+
const t1 = clampAngle(th1);
|
|
98
|
+
const k0 = t1 - t0 - k2 / 24;
|
|
99
|
+
let k1 = 6 * (t0 + t1) - k3 / 40;
|
|
100
|
+
for (let iter = 0; iter < 12; iter++) {
|
|
101
|
+
const e = startError(k0, k1, k2, k3, t0);
|
|
102
|
+
if (Math.abs(e) < 1e-12) break;
|
|
103
|
+
const d = (startError(k0, k1 + 1e-6, k2, k3, t0) - e) / 1e-6;
|
|
104
|
+
if (!Number.isFinite(d) || Math.abs(d) < 1e-12) break;
|
|
105
|
+
k1 -= e / d;
|
|
106
|
+
}
|
|
107
|
+
const c = integrateEuler(k0, k1, 1, k2, k3);
|
|
108
|
+
return { k0, k1, k2, k3, ratio: Math.hypot(c.x, c.y), chordAngle: Math.atan2(c.y, c.x) };
|
|
109
|
+
}
|
|
110
|
+
function placeEulerSegment(fit, p0, p1) {
|
|
111
|
+
const chord = Math.hypot(p1.x - p0.x, p1.y - p0.y);
|
|
112
|
+
const length = fit.ratio > 1e-9 ? chord / fit.ratio : chord;
|
|
113
|
+
const rotation = Math.atan2(p1.y - p0.y, p1.x - p0.x) - fit.chordAngle;
|
|
114
|
+
return { ...fit, p0, p1, length, rotation };
|
|
115
|
+
}
|
|
116
|
+
function eulerPointAt(seg, s) {
|
|
117
|
+
const c = integrateEuler(seg.k0, seg.k1, s, seg.k2, seg.k3);
|
|
118
|
+
const cos = Math.cos(seg.rotation);
|
|
119
|
+
const sin = Math.sin(seg.rotation);
|
|
120
|
+
return {
|
|
121
|
+
x: seg.p0.x + seg.length * (c.x * cos - c.y * sin),
|
|
122
|
+
y: seg.p0.y + seg.length * (c.x * sin + c.y * cos)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function eulerAngleAt(seg, s) {
|
|
126
|
+
const u = s - 0.5;
|
|
127
|
+
return seg.rotation + seg.k0 * u + u * u * (seg.k1 / 2 + u * (seg.k2 / 6 + u * seg.k3 / 24));
|
|
128
|
+
}
|
|
129
|
+
var bendAt = (f, u) => f.k0 + u * (f.k1 + u * (f.k2 / 2 + u * f.k3 / 6));
|
|
130
|
+
function eulerCurvatureAt(seg, s) {
|
|
131
|
+
if (seg.length < 1e-12) return 0;
|
|
132
|
+
return bendAt(seg, s - 0.5) / seg.length;
|
|
133
|
+
}
|
|
134
|
+
function eulerCurvatureDerivativesAt(seg, s) {
|
|
135
|
+
if (seg.length < 1e-12) return [0, 0];
|
|
136
|
+
const u = s - 0.5;
|
|
137
|
+
return [
|
|
138
|
+
(seg.k1 + u * (seg.k2 + u * seg.k3 / 2)) / seg.length ** 2,
|
|
139
|
+
(seg.k2 + u * seg.k3) / seg.length ** 3
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
var SAMPLES = 16;
|
|
143
|
+
function fitHandleLengths(p0, p1, u0, u1, samples, ts) {
|
|
144
|
+
let m00 = 0, m01 = 0, m11 = 0, r0 = 0, r1 = 0;
|
|
145
|
+
samples.forEach((q, i) => {
|
|
146
|
+
const t = ts[i];
|
|
147
|
+
const mt = 1 - t;
|
|
148
|
+
const b0 = mt * mt * mt + 3 * mt * mt * t;
|
|
149
|
+
const b3 = 3 * mt * t * t + t * t * t;
|
|
150
|
+
const ex = q.x - (b0 * p0.x + b3 * p1.x);
|
|
151
|
+
const ey = q.y - (b0 * p0.y + b3 * p1.y);
|
|
152
|
+
const f1 = 3 * mt * mt * t;
|
|
153
|
+
const f2 = 3 * mt * t * t;
|
|
154
|
+
const ax = f1 * u0.x, ay = f1 * u0.y;
|
|
155
|
+
const bx = -f2 * u1.x, by = -f2 * u1.y;
|
|
156
|
+
m00 += ax * ax + ay * ay;
|
|
157
|
+
m01 += ax * bx + ay * by;
|
|
158
|
+
m11 += bx * bx + by * by;
|
|
159
|
+
r0 += ax * ex + ay * ey;
|
|
160
|
+
r1 += bx * ex + by * ey;
|
|
161
|
+
});
|
|
162
|
+
const det = m00 * m11 - m01 * m01;
|
|
163
|
+
if (Math.abs(det) < 1e-12) return null;
|
|
164
|
+
const a = (r0 * m11 - r1 * m01) / det;
|
|
165
|
+
const b = (m00 * r1 - m01 * r0) / det;
|
|
166
|
+
return a > 0 && b > 0 && Number.isFinite(a) && Number.isFinite(b) ? [a, b] : null;
|
|
167
|
+
}
|
|
168
|
+
function nearerT(p, q, t) {
|
|
169
|
+
const mt = 1 - t;
|
|
170
|
+
const at = (c0, c1, c2, c3) => mt * mt * mt * c0 + 3 * mt * mt * t * c1 + 3 * mt * t * t * c2 + t * t * t * c3;
|
|
171
|
+
const d1 = (c0, c1, c2, c3) => 3 * (mt * mt * (c1 - c0) + 2 * mt * t * (c2 - c1) + t * t * (c3 - c2));
|
|
172
|
+
const d2 = (c0, c1, c2, c3) => 6 * (mt * (c2 - 2 * c1 + c0) + t * (c3 - 2 * c2 + c1));
|
|
173
|
+
const ex = at(p[0].x, p[1].x, p[2].x, p[3].x) - q.x;
|
|
174
|
+
const ey = at(p[0].y, p[1].y, p[2].y, p[3].y) - q.y;
|
|
175
|
+
const dx = d1(p[0].x, p[1].x, p[2].x, p[3].x);
|
|
176
|
+
const dy = d1(p[0].y, p[1].y, p[2].y, p[3].y);
|
|
177
|
+
const ddx = d2(p[0].x, p[1].x, p[2].x, p[3].x);
|
|
178
|
+
const ddy = d2(p[0].y, p[1].y, p[2].y, p[3].y);
|
|
179
|
+
const den = dx * dx + dy * dy + ex * ddx + ey * ddy;
|
|
180
|
+
if (Math.abs(den) < 1e-12) return t;
|
|
181
|
+
return Math.min(1, Math.max(0, t - (ex * dx + ey * dy) / den));
|
|
182
|
+
}
|
|
183
|
+
function eulerSegmentCubic(seg) {
|
|
184
|
+
const { p0, p1 } = seg;
|
|
185
|
+
const a0 = eulerAngleAt(seg, 0);
|
|
186
|
+
const a1 = eulerAngleAt(seg, 1);
|
|
187
|
+
const u0 = { x: Math.cos(a0), y: Math.sin(a0) };
|
|
188
|
+
const u1 = { x: Math.cos(a1), y: Math.sin(a1) };
|
|
189
|
+
const samples = [];
|
|
190
|
+
const ts = [];
|
|
191
|
+
for (let i = 1; i < SAMPLES; i++) {
|
|
192
|
+
samples.push(eulerPointAt(seg, i / SAMPLES));
|
|
193
|
+
ts.push(i / SAMPLES);
|
|
194
|
+
}
|
|
195
|
+
const chord = Math.hypot(p1.x - p0.x, p1.y - p0.y);
|
|
196
|
+
let lengths = [chord / 3, chord / 3];
|
|
197
|
+
const handles = ([a, b]) => ({
|
|
198
|
+
c1: { x: p0.x + a * u0.x, y: p0.y + a * u0.y },
|
|
199
|
+
c2: { x: p1.x - b * u1.x, y: p1.y - b * u1.y }
|
|
200
|
+
});
|
|
201
|
+
for (let round = 0; round < 4; round++) {
|
|
202
|
+
const fit = fitHandleLengths(p0, p1, u0, u1, samples, ts);
|
|
203
|
+
if (!fit) break;
|
|
204
|
+
lengths = fit;
|
|
205
|
+
const { c1, c2 } = handles(lengths);
|
|
206
|
+
const ctrl = [p0, c1, c2, p1];
|
|
207
|
+
samples.forEach((q, i) => {
|
|
208
|
+
ts[i] = nearerT(ctrl, q, ts[i]);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return handles(lengths);
|
|
212
|
+
}
|
|
213
|
+
function subEulerSegment(seg, s0, s1) {
|
|
214
|
+
const d = s1 - s0;
|
|
215
|
+
const m = (s0 + s1) / 2 - 0.5;
|
|
216
|
+
const k0 = bendAt(seg, m) * d;
|
|
217
|
+
const k1 = (seg.k1 + m * (seg.k2 + m * seg.k3 / 2)) * d ** 2;
|
|
218
|
+
const k2 = (seg.k2 + m * seg.k3) * d ** 3;
|
|
219
|
+
const k3 = seg.k3 * d ** 4;
|
|
220
|
+
const c = integrateEuler(k0, k1, 1, k2, k3);
|
|
221
|
+
const fit = { k0, k1, k2, k3, ratio: Math.hypot(c.x, c.y), chordAngle: Math.atan2(c.y, c.x) };
|
|
222
|
+
return placeEulerSegment(fit, eulerPointAt(seg, s0), eulerPointAt(seg, s1));
|
|
223
|
+
}
|
|
224
|
+
function eulerCubicError(seg, c1, c2) {
|
|
225
|
+
const ctrl = [seg.p0, c1, c2, seg.p1];
|
|
226
|
+
let worst = 0;
|
|
227
|
+
for (const s of [0.2, 0.4, 0.5, 0.6, 0.8]) {
|
|
228
|
+
const q = eulerPointAt(seg, s);
|
|
229
|
+
let t = s;
|
|
230
|
+
for (let i = 0; i < 4; i++) t = nearerT(ctrl, q, t);
|
|
231
|
+
const mt = 1 - t;
|
|
232
|
+
const x = mt * mt * mt * seg.p0.x + 3 * mt * mt * t * c1.x + 3 * mt * t * t * c2.x + t * t * t * seg.p1.x;
|
|
233
|
+
const y = mt * mt * mt * seg.p0.y + 3 * mt * mt * t * c1.y + 3 * mt * t * t * c2.y + t * t * t * seg.p1.y;
|
|
234
|
+
worst = Math.max(worst, Math.hypot(x - q.x, y - q.y));
|
|
235
|
+
}
|
|
236
|
+
return worst;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/paths/spiro.ts
|
|
240
|
+
var SPIRO_KINDS = ["smooth", "g4", "corner", "left", "right"];
|
|
241
|
+
var TAU = Math.PI * 2;
|
|
242
|
+
var wrap = (a) => a - TAU * Math.round(a / TAU);
|
|
243
|
+
var angleOf = (p, q) => Math.atan2(q.y - p.y, q.x - p.x);
|
|
244
|
+
var distance = (p, q) => Math.hypot(q.x - p.x, q.y - p.y);
|
|
245
|
+
function isSpiroKind(kind) {
|
|
246
|
+
return SPIRO_KINDS.includes(kind);
|
|
247
|
+
}
|
|
248
|
+
function buildSystem(points, closed) {
|
|
249
|
+
const n = points.length;
|
|
250
|
+
const count = closed ? n : n - 1;
|
|
251
|
+
const ends = [];
|
|
252
|
+
const straight = [];
|
|
253
|
+
const slot = [];
|
|
254
|
+
const cubic = [];
|
|
255
|
+
let unknowns = 0;
|
|
256
|
+
for (let i = 0; i < count; i++) {
|
|
257
|
+
const j = (i + 1) % n;
|
|
258
|
+
ends.push(j);
|
|
259
|
+
const line = points[i].kind === "right" || points[j].kind === "left" || distance(points[i], points[j]) < 1e-9;
|
|
260
|
+
straight.push(line);
|
|
261
|
+
slot.push(line ? -1 : unknowns);
|
|
262
|
+
const higher = !line && (points[i].kind === "g4" || points[j].kind === "g4");
|
|
263
|
+
cubic.push(higher);
|
|
264
|
+
if (!line) unknowns += higher ? 4 : 2;
|
|
265
|
+
}
|
|
266
|
+
return { points, ends, straight, slot, cubic, unknowns };
|
|
267
|
+
}
|
|
268
|
+
function sides(sys, closed, i) {
|
|
269
|
+
const n = sys.points.length;
|
|
270
|
+
const count = sys.ends.length;
|
|
271
|
+
const side = (seg) => {
|
|
272
|
+
if (seg < 0 || seg >= count) return { kind: "none" };
|
|
273
|
+
if (sys.straight[seg]) return { kind: "line", angle: angleOf(sys.points[seg], sys.points[sys.ends[seg]]) };
|
|
274
|
+
return { kind: "curve", seg };
|
|
275
|
+
};
|
|
276
|
+
const into = i > 0 ? i - 1 : closed ? n - 1 : -1;
|
|
277
|
+
const outOf = closed || i < n - 1 ? i : -1;
|
|
278
|
+
return [side(into), side(outOf)];
|
|
279
|
+
}
|
|
280
|
+
function fitSegment(sys, s, x) {
|
|
281
|
+
const p0 = sys.points[s];
|
|
282
|
+
const p1 = sys.points[sys.ends[s]];
|
|
283
|
+
const chord = angleOf(p0, p1);
|
|
284
|
+
const k = sys.slot[s];
|
|
285
|
+
const fit = sys.cubic[s] ? fitEulerSegment(wrap(x[k] - chord), wrap(x[k + 1] - chord), x[k + 2], x[k + 3]) : fitEulerSegment(wrap(x[k] - chord), wrap(x[k + 1] - chord));
|
|
286
|
+
const seg = placeEulerSegment(fit, p0, p1);
|
|
287
|
+
const startAngle = eulerAngleAt(seg, 0);
|
|
288
|
+
return { seg, chord: distance(p0, p1), startAngle, endAngle: eulerAngleAt(seg, 1), cubic: sys.cubic[s] };
|
|
289
|
+
}
|
|
290
|
+
var bend = (f, at) => eulerCurvatureAt(f.seg, at) * f.chord;
|
|
291
|
+
var twist = (f, at) => eulerCurvatureDerivativesAt(f.seg, at)[1] * f.chord ** 3;
|
|
292
|
+
function g4Join(fa, fb) {
|
|
293
|
+
const scale = (fa.chord + fb.chord) / 2;
|
|
294
|
+
const [a1, a2] = eulerCurvatureDerivativesAt(fa.seg, 1);
|
|
295
|
+
const [b1, b2] = eulerCurvatureDerivativesAt(fb.seg, 0);
|
|
296
|
+
return [
|
|
297
|
+
wrap(fa.endAngle - fb.startAngle),
|
|
298
|
+
(eulerCurvatureAt(fa.seg, 1) - eulerCurvatureAt(fb.seg, 0)) * scale,
|
|
299
|
+
(a1 - b1) * scale ** 2,
|
|
300
|
+
(a2 - b2) * scale ** 3
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
function residuals(sys, closed, fits) {
|
|
304
|
+
const out = [];
|
|
305
|
+
const fitOf = (side) => fits[side.seg];
|
|
306
|
+
for (let i = 0; i < sys.points.length; i++) {
|
|
307
|
+
const [a, b] = sides(sys, closed, i);
|
|
308
|
+
const kind = sys.points[i].kind;
|
|
309
|
+
const ac = a.kind === "curve";
|
|
310
|
+
const bc = b.kind === "curve";
|
|
311
|
+
if (!ac && !bc) continue;
|
|
312
|
+
if (kind === "g4" && ac && bc) {
|
|
313
|
+
out.push(...g4Join(fitOf(a), fitOf(b)));
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (kind === "smooth" && ac && bc) {
|
|
317
|
+
const fa = fitOf(a), fb = fitOf(b);
|
|
318
|
+
out.push(...g4Join(fa, fb).slice(0, 2));
|
|
319
|
+
if (fa.cubic) out.push(twist(fa, 1));
|
|
320
|
+
if (fb.cubic) out.push(twist(fb, 0));
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const [side, other, at] of [[a, b, 1], [b, a, 0]]) {
|
|
324
|
+
if (side.kind !== "curve") continue;
|
|
325
|
+
const f = fitOf(side);
|
|
326
|
+
const angle = at === 1 ? f.endAngle : f.startAngle;
|
|
327
|
+
const followsLine = other.kind === "line" && kind !== "corner";
|
|
328
|
+
out.push(followsLine ? wrap(angle - other.angle) : bend(f, at));
|
|
329
|
+
if (f.cubic) out.push(twist(f, at));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
function initialGuess(sys, closed) {
|
|
335
|
+
const x = new Array(sys.unknowns).fill(0);
|
|
336
|
+
const n = sys.points.length;
|
|
337
|
+
const tangentAt = (i, seg) => {
|
|
338
|
+
const chord = angleOf(sys.points[seg], sys.points[sys.ends[seg]]);
|
|
339
|
+
const [a, b] = sides(sys, closed, i);
|
|
340
|
+
if (sys.points[i].kind === "corner" || a.kind === "none" || b.kind === "none") return chord;
|
|
341
|
+
if (a.kind === "line") return a.angle;
|
|
342
|
+
if (b.kind === "line") return b.angle;
|
|
343
|
+
const prev = sys.points[(i - 1 + n) % n];
|
|
344
|
+
const next = sys.points[(i + 1) % n];
|
|
345
|
+
const pin = angleOf(prev, sys.points[i]);
|
|
346
|
+
const pout = angleOf(sys.points[i], next);
|
|
347
|
+
return pin + wrap(pout - pin) / 2;
|
|
348
|
+
};
|
|
349
|
+
for (let s = 0; s < sys.ends.length; s++) {
|
|
350
|
+
const k = sys.slot[s];
|
|
351
|
+
if (k < 0) continue;
|
|
352
|
+
x[k] = tangentAt(s, s);
|
|
353
|
+
x[k + 1] = tangentAt(sys.ends[s], s);
|
|
354
|
+
}
|
|
355
|
+
return x;
|
|
356
|
+
}
|
|
357
|
+
function solveLinear(m, r) {
|
|
358
|
+
const n = r.length;
|
|
359
|
+
for (let c = 0; c < n; c++) {
|
|
360
|
+
let p = c;
|
|
361
|
+
for (let i = c + 1; i < n; i++) if (Math.abs(m[i][c]) > Math.abs(m[p][c])) p = i;
|
|
362
|
+
if (Math.abs(m[p][c]) < 1e-14) return null;
|
|
363
|
+
[m[c], m[p]] = [m[p], m[c]];
|
|
364
|
+
[r[c], r[p]] = [r[p], r[c]];
|
|
365
|
+
for (let i = c + 1; i < n; i++) {
|
|
366
|
+
const f = m[i][c] / m[c][c];
|
|
367
|
+
if (f === 0) continue;
|
|
368
|
+
for (let j = c; j < n; j++) m[i][j] -= f * m[c][j];
|
|
369
|
+
r[i] -= f * r[c];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const out = new Array(n).fill(0);
|
|
373
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
374
|
+
let v = r[i];
|
|
375
|
+
for (let j = i + 1; j < n; j++) v -= m[i][j] * out[j];
|
|
376
|
+
out[i] = v / m[i][i];
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
var norm = (v) => Math.sqrt(v.reduce((s, e) => s + e * e, 0));
|
|
381
|
+
function solveSpiro(points, closed) {
|
|
382
|
+
if (points.length < 2) return [];
|
|
383
|
+
const sys = buildSystem(points, closed && points.length > 2);
|
|
384
|
+
const isClosed = closed && points.length > 2;
|
|
385
|
+
let x = initialGuess(sys, isClosed);
|
|
386
|
+
const fitAll = (v) => sys.ends.map((_, s) => sys.slot[s] < 0 ? null : fitSegment(sys, s, v));
|
|
387
|
+
let fits = fitAll(x);
|
|
388
|
+
let r = residuals(sys, isClosed, fits);
|
|
389
|
+
for (let iter = 0; iter < 40 && sys.unknowns > 0; iter++) {
|
|
390
|
+
const err = norm(r);
|
|
391
|
+
if (err < 1e-10) break;
|
|
392
|
+
const jac = r.map(() => new Array(sys.unknowns).fill(0));
|
|
393
|
+
for (let s = 0; s < sys.ends.length; s++) {
|
|
394
|
+
const k = sys.slot[s];
|
|
395
|
+
if (k < 0) continue;
|
|
396
|
+
for (let u = k; u < k + (sys.cubic[s] ? 4 : 2); u++) {
|
|
397
|
+
const h = 1e-6;
|
|
398
|
+
const xs = x.slice();
|
|
399
|
+
xs[u] += h;
|
|
400
|
+
const moved = fits.slice();
|
|
401
|
+
moved[s] = fitSegment(sys, s, xs);
|
|
402
|
+
const rs = residuals(sys, isClosed, moved);
|
|
403
|
+
for (let e = 0; e < r.length; e++) jac[e][u] = (rs[e] - r[e]) / h;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const step = solveLinear(jac, r.map((v) => -v));
|
|
407
|
+
if (!step) break;
|
|
408
|
+
let t = 1;
|
|
409
|
+
let accepted = false;
|
|
410
|
+
for (let tries = 0; tries < 12; tries++, t /= 2) {
|
|
411
|
+
const xt = x.map((v, i) => v + t * step[i]);
|
|
412
|
+
const ft = fitAll(xt);
|
|
413
|
+
const rt = residuals(sys, isClosed, ft);
|
|
414
|
+
if (norm(rt) < err) {
|
|
415
|
+
x = xt;
|
|
416
|
+
fits = ft;
|
|
417
|
+
r = rt;
|
|
418
|
+
accepted = true;
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (!accepted) break;
|
|
423
|
+
}
|
|
424
|
+
return sys.ends.map((end, s) => ({
|
|
425
|
+
from: { x: points[s].x, y: points[s].y },
|
|
426
|
+
to: { x: points[end].x, y: points[end].y },
|
|
427
|
+
spiral: sys.slot[s] < 0 ? null : fits[s].seg
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/paths/spiroPath.ts
|
|
432
|
+
var MAX_PIECES = 16;
|
|
433
|
+
function cubicPieces(spiral, tol) {
|
|
434
|
+
let best = [];
|
|
435
|
+
for (let m = 1; m <= MAX_PIECES; m++) {
|
|
436
|
+
best = [];
|
|
437
|
+
let fits = true;
|
|
438
|
+
for (let j = 0; j < m; j++) {
|
|
439
|
+
const piece = m === 1 ? spiral : subEulerSegment(spiral, j / m, (j + 1) / m);
|
|
440
|
+
const { c1, c2 } = eulerSegmentCubic(piece);
|
|
441
|
+
best.push({ end: piece, c1, c2 });
|
|
442
|
+
if (eulerCubicError(piece, c1, c2) > tol * spiral.length) fits = false;
|
|
443
|
+
}
|
|
444
|
+
if (fits) break;
|
|
445
|
+
}
|
|
446
|
+
return best;
|
|
447
|
+
}
|
|
448
|
+
function contourAnchors(c, tol) {
|
|
449
|
+
const anchors = [];
|
|
450
|
+
const at = [];
|
|
451
|
+
const segments = solveSpiro(c.points, c.closed);
|
|
452
|
+
let arriving = null;
|
|
453
|
+
c.points.forEach((p, i) => {
|
|
454
|
+
at.push(anchors.length);
|
|
455
|
+
anchors.push({ x: p.x, y: p.y, ...arriving ? { inHandle: arriving } : {} });
|
|
456
|
+
arriving = null;
|
|
457
|
+
const spiral = segments[i]?.spiral;
|
|
458
|
+
if (!spiral) return;
|
|
459
|
+
const pieces = cubicPieces(spiral, tol);
|
|
460
|
+
pieces.forEach((piece, j) => {
|
|
461
|
+
anchors[anchors.length - 1].outHandle = { x: piece.c1.x, y: piece.c1.y };
|
|
462
|
+
const c2 = { x: piece.c2.x, y: piece.c2.y };
|
|
463
|
+
if (j < pieces.length - 1) anchors.push({ x: piece.end.p1.x, y: piece.end.p1.y, inHandle: c2 });
|
|
464
|
+
else arriving = c2;
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
if (arriving) anchors[0].inHandle = arriving;
|
|
468
|
+
return { anchors, at };
|
|
469
|
+
}
|
|
470
|
+
function spiroToPath(contours, options = {}) {
|
|
471
|
+
const tol = options.tolerance ?? 1e-4;
|
|
472
|
+
const subs = [];
|
|
473
|
+
const closed = [];
|
|
474
|
+
const at = [];
|
|
475
|
+
let base = 0;
|
|
476
|
+
for (const c of contours) {
|
|
477
|
+
if (c.points.length === 0) continue;
|
|
478
|
+
const written = contourAnchors(c, tol);
|
|
479
|
+
subs.push(written.anchors);
|
|
480
|
+
closed.push(c.closed && c.points.length > 2);
|
|
481
|
+
at.push(...written.at.map((i) => i + base));
|
|
482
|
+
base += written.anchors.length;
|
|
483
|
+
}
|
|
484
|
+
return { path: anchorsToPath(subs, closed, options.like), at };
|
|
485
|
+
}
|
|
486
|
+
function spiroContoursOf(path, kinds, at) {
|
|
487
|
+
if (kinds.length !== at.length || at.length === 0) return null;
|
|
488
|
+
const { anchors, closed } = pathToAnchors(path);
|
|
489
|
+
const contours = [];
|
|
490
|
+
let flat = 0;
|
|
491
|
+
let k = 0;
|
|
492
|
+
for (let s = 0; s < anchors.length; s++) {
|
|
493
|
+
const sub = anchors[s];
|
|
494
|
+
if (at[k] !== flat) return null;
|
|
495
|
+
const points = [];
|
|
496
|
+
while (k < at.length && at[k] < flat + sub.length) {
|
|
497
|
+
if (k > 0 && at[k] <= at[k - 1]) return null;
|
|
498
|
+
const a = sub[at[k] - flat];
|
|
499
|
+
points.push({ x: a.x, y: a.y, kind: kinds[k] });
|
|
500
|
+
k++;
|
|
501
|
+
}
|
|
502
|
+
contours.push({ points, closed: closed[s] });
|
|
503
|
+
flat += sub.length;
|
|
504
|
+
}
|
|
505
|
+
return k === at.length ? contours : null;
|
|
506
|
+
}
|
|
507
|
+
function spiroControlPath(contours) {
|
|
508
|
+
return anchorsToPath(
|
|
509
|
+
contours.map((c) => c.points.map((p) => ({ x: p.x, y: p.y }))),
|
|
510
|
+
contours.map((c) => c.closed)
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
var key = (x, y) => `${Math.round(x * 1e3)},${Math.round(y * 1e3)}`;
|
|
514
|
+
function spiroContoursFromControl(control, before, fallback = "smooth") {
|
|
515
|
+
const { anchors, closed } = pathToAnchors(control);
|
|
516
|
+
const old = before.flatMap((c) => c.points);
|
|
517
|
+
const count = anchors.reduce((n, sub) => n + sub.length, 0);
|
|
518
|
+
const byPlace = /* @__PURE__ */ new Map();
|
|
519
|
+
for (const p of old) if (!byPlace.has(key(p.x, p.y))) byPlace.set(key(p.x, p.y), p.kind);
|
|
520
|
+
let i = 0;
|
|
521
|
+
return anchors.map((sub, s) => ({
|
|
522
|
+
closed: closed[s],
|
|
523
|
+
points: sub.map((a) => {
|
|
524
|
+
const kind = count === old.length ? old[i].kind : byPlace.get(key(a.x, a.y)) ?? fallback;
|
|
525
|
+
i++;
|
|
526
|
+
return { x: a.x, y: a.y, kind };
|
|
527
|
+
})
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export { SPIRO_KINDS, combTeeth, cubicCurvatureAt, cubicCurvatureSource, cubicDerivativeAt, curvatureComb, eulerAngleAt, eulerCurvatureAt, eulerCurvatureDerivativesAt, eulerPointAt, fitEulerSegment, isSpiroKind, placeEulerSegment, solveSpiro, spiroContoursFromControl, spiroContoursOf, spiroControlPath, spiroToPath };
|
|
532
|
+
//# sourceMappingURL=chunk-UI3FUWXM.js.map
|
|
533
|
+
//# sourceMappingURL=chunk-UI3FUWXM.js.map
|