@weasel-js/geom 1.10.1 → 1.11.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 +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 +15 -10
- package/dist/booleans/index.js +306 -353
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-7WFFCHRL.js → chunk-6XSJY3TC.js} +6 -37
- package/dist/chunk-6XSJY3TC.js.map +1 -0
- package/dist/chunk-7KLJZFBY.js +573 -0
- package/dist/chunk-7KLJZFBY.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-HGF5OVEN.js +129 -0
- package/dist/chunk-HGF5OVEN.js.map +1 -0
- package/dist/chunk-J2O2B54M.js +800 -0
- package/dist/chunk-J2O2B54M.js.map +1 -0
- package/dist/chunk-Q66NA2FU.js +174 -0
- package/dist/chunk-Q66NA2FU.js.map +1 -0
- package/dist/chunk-SASMXDBE.js +443 -0
- package/dist/chunk-SASMXDBE.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 +1468 -284
- package/dist/index.js +5450 -2672
- package/dist/index.js.map +1 -1
- package/dist/nd/index.d.ts +91 -27
- package/dist/nd/index.js +282 -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 +252 -0
- package/dist/warp/index.js +1316 -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
package/dist/nd/index.d.ts
CHANGED
|
@@ -1,30 +1,94 @@
|
|
|
1
|
+
export { P as PORT_REACH, c as cubicAt, p as portControls, a as portCurvePoints } from '../portCurve-C5HDWuuV.js';
|
|
2
|
+
|
|
3
|
+
/** Affine subspaces ("flats") over plain number arrays, in any dimension:
|
|
4
|
+
* the points, lines and planes a snap resolves against. */
|
|
5
|
+
type Vec = readonly number[];
|
|
6
|
+
/** The affine subspace `at + span(dirs)`. `dirs` need not be orthonormal or
|
|
7
|
+
* independent; a zero or repeated direction adds nothing. */
|
|
8
|
+
interface Flat {
|
|
9
|
+
at: Vec;
|
|
10
|
+
dirs: readonly Vec[];
|
|
11
|
+
}
|
|
12
|
+
/** The flat's dimension: the rank of its directions. */
|
|
13
|
+
declare function flatDim(f: Flat): number;
|
|
14
|
+
/** The point of `f` nearest `p`. */
|
|
15
|
+
declare function closestOnFlat(f: Flat, p: Vec): Vec;
|
|
16
|
+
/** The nearest pair of points, one on each flat. Parallel flats have many
|
|
17
|
+
* such pairs; this returns one of them. */
|
|
18
|
+
declare function closestBetweenFlats(a: Flat, b: Flat): {
|
|
19
|
+
onA: Vec;
|
|
20
|
+
onB: Vec;
|
|
21
|
+
};
|
|
22
|
+
/** `a ∩ b`, or null when they miss each other by more than `eps`. */
|
|
23
|
+
declare function intersectFlats(a: Flat, b: Flat, eps?: number): Flat | null;
|
|
24
|
+
|
|
25
|
+
/** The snap resolver: every gesture's snap, in any dimension, as one search
|
|
26
|
+
* over affine constraints on the move `δ`. */
|
|
27
|
+
|
|
28
|
+
/** A `fallback` candidate holds only the axes no `target` claims. */
|
|
29
|
+
type SnapTier = 'target' | 'fallback';
|
|
30
|
+
/** What a probe stands for on the subject; sources match probes by role. */
|
|
31
|
+
type ProbeRole = 'origin' | 'box' | 'geometry' | 'pointer';
|
|
32
|
+
/** A point on the subject that should land on a locus. `role` says which
|
|
33
|
+
* sources may match it. */
|
|
34
|
+
interface SnapProbe {
|
|
35
|
+
id: string;
|
|
36
|
+
at: Vec;
|
|
37
|
+
role: ProbeRole;
|
|
38
|
+
}
|
|
39
|
+
/** One probe paired with one locus. `M` is the mark payload, opaque here. */
|
|
40
|
+
interface SnapCandidate<M> {
|
|
41
|
+
/** `SnapProbe.id`. */
|
|
42
|
+
probe: string;
|
|
43
|
+
locus: Flat;
|
|
44
|
+
/** One interval per locus dir, in multiples of that dir. A solution
|
|
45
|
+
* outside it fails the candidate. */
|
|
46
|
+
span?: readonly (readonly [number, number])[];
|
|
47
|
+
source: string;
|
|
48
|
+
/** Stable tiebreak: lower wins when everything else is equal. */
|
|
49
|
+
order: number;
|
|
50
|
+
tier: SnapTier;
|
|
51
|
+
reachPx: number;
|
|
52
|
+
/** When any required candidate exists, the result includes one of them,
|
|
53
|
+
* whatever its reach. */
|
|
54
|
+
required?: boolean;
|
|
55
|
+
mark?: M;
|
|
56
|
+
}
|
|
57
|
+
/** One frame's snap: the probes, the input subspace, the raw move, and the candidates. */
|
|
58
|
+
interface SnapProblem<M> {
|
|
59
|
+
probes: readonly SnapProbe[];
|
|
60
|
+
/** `H`, the subspace of δ-space the result stays inside. */
|
|
61
|
+
input: Flat;
|
|
62
|
+
/** The unsnapped move. */
|
|
63
|
+
delta0: Vec;
|
|
64
|
+
candidates: readonly SnapCandidate<M>[];
|
|
65
|
+
/** World to screen pixels. A non-finite result puts that position out of reach. */
|
|
66
|
+
toScreen(world: Vec): Vec;
|
|
67
|
+
/** Candidates kept per probe, nearest first. Default 24. */
|
|
68
|
+
poolSize?: number;
|
|
69
|
+
/** World-space tolerance for "on the locus". Default 1e-6. */
|
|
70
|
+
eps?: number;
|
|
71
|
+
}
|
|
72
|
+
/** The move `resolveSnap` chose and the candidates it meets. */
|
|
73
|
+
interface SnapResolution<M> {
|
|
74
|
+
/** `closestOnFlat(input, delta0)` when nothing snaps. */
|
|
75
|
+
delta: Vec;
|
|
76
|
+
members: readonly SnapCandidate<M>[];
|
|
77
|
+
/** Every in-reach candidate the result meets: the ones whose marks show. */
|
|
78
|
+
satisfied: readonly SnapCandidate<M>[];
|
|
79
|
+
/** How many dimensions of `H` the result pins. */
|
|
80
|
+
codim: number;
|
|
81
|
+
}
|
|
82
|
+
/** The move nearest `delta0` within `H` that meets the best-ranked set of
|
|
83
|
+
* candidates in reach, or `delta0` projected into `H` when none is. */
|
|
84
|
+
declare function resolveSnap<M>(problem: SnapProblem<M>): SnapResolution<M>;
|
|
85
|
+
|
|
1
86
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The rule is stated once, over loose components, and published as is from
|
|
7
|
-
* `./nd`; the 2D barrel and `./3d` each wrap it in their own currency. Every
|
|
8
|
-
* operation here is closed on the plane z = 0, so a planar problem answered
|
|
9
|
-
* through the 3D wrapper returns the same numbers as through the 2D one, not an
|
|
10
|
-
* approximation of them.
|
|
11
|
-
*/
|
|
12
|
-
/** How far a control point reaches toward the other end, as a fraction of the
|
|
13
|
-
* straight-line distance between the two. */
|
|
14
|
-
declare const PORT_REACH = 0.4;
|
|
15
|
-
/**
|
|
16
|
-
* `[c1, c2]` for the cubic from `a` to `b`.
|
|
17
|
-
*
|
|
18
|
-
* A null normal falls back to a plain lerp along the chord, which is what makes
|
|
19
|
-
* an unported waypoint curve smoothly instead of kinking. The two ends are not
|
|
20
|
-
* symmetric: `outNormal` points the way the curve departs, while `inNormal`
|
|
21
|
-
* points out of the receiving port, so `c2` is pushed *along* it to arrive
|
|
22
|
-
* against it.
|
|
87
|
+
* The line through `about` whose direction, measured in the plane `basis`
|
|
88
|
+
* spans (as an angle from `basis[0]` toward `basis[1]`), is the multiple of
|
|
89
|
+
* `step` nearest the direction from `about` to `toward`: a constrained
|
|
90
|
+
* gesture's input subspace, in any dimension. `basis` is orthonormal.
|
|
23
91
|
*/
|
|
24
|
-
declare function
|
|
25
|
-
/** A port curve sampled into `samples` points, excluding its start. */
|
|
26
|
-
declare function portCurvePoints(a: readonly number[], b: readonly number[], outNormal: readonly number[] | null | undefined, inNormal: readonly number[] | null | undefined, samples: number, reach?: number): number[][];
|
|
27
|
-
/** A cubic evaluated at `t`, component-wise, in whatever dimension it was given. */
|
|
28
|
-
declare function cubicAt(p0: readonly number[], p1: readonly number[], p2: readonly number[], p3: readonly number[], t: number): number[];
|
|
92
|
+
declare function stepLine(about: Vec, toward: Vec, basis: readonly [Vec, Vec], step: number): Flat;
|
|
29
93
|
|
|
30
|
-
export {
|
|
94
|
+
export { type Flat, type ProbeRole, type SnapCandidate, type SnapProbe, type SnapProblem, type SnapResolution, type SnapTier, type Vec, closestBetweenFlats, closestOnFlat, flatDim, intersectFlats, resolveSnap, stepLine };
|
package/dist/nd/index.js
CHANGED
|
@@ -1,3 +1,285 @@
|
|
|
1
1
|
export { PORT_REACH, cubicAt, portControls, portCurvePoints } from '../chunk-YQADYJJ7.js';
|
|
2
|
+
|
|
3
|
+
// src/snap/flat.ts
|
|
4
|
+
var RANK_TOL = 1e-9;
|
|
5
|
+
var PIVOT_TOL = 1e-12;
|
|
6
|
+
function dotv(a, b) {
|
|
7
|
+
let s = 0;
|
|
8
|
+
for (let i = 0; i < a.length; i++) s += a[i] * b[i];
|
|
9
|
+
return s;
|
|
10
|
+
}
|
|
11
|
+
function normv(a) {
|
|
12
|
+
return Math.sqrt(dotv(a, a));
|
|
13
|
+
}
|
|
14
|
+
function isFiniteVec(a) {
|
|
15
|
+
return a.every(Number.isFinite);
|
|
16
|
+
}
|
|
17
|
+
function orthonormalize(vecs, start = []) {
|
|
18
|
+
const basis = start.map((q) => q.slice());
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const d of vecs) {
|
|
21
|
+
const n0 = normv(d);
|
|
22
|
+
if (!(n0 > 0) || !isFiniteVec(d)) continue;
|
|
23
|
+
const v = d.slice();
|
|
24
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
25
|
+
for (const q of basis) {
|
|
26
|
+
const k = dotv(v, q);
|
|
27
|
+
for (let i = 0; i < v.length; i++) v[i] -= k * q[i];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const n = normv(v);
|
|
31
|
+
if (n <= RANK_TOL * n0) continue;
|
|
32
|
+
for (let i = 0; i < v.length; i++) v[i] /= n;
|
|
33
|
+
basis.push(v);
|
|
34
|
+
out.push(v);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function complement(basis, n) {
|
|
39
|
+
const axes = [];
|
|
40
|
+
for (let i = 0; i < n; i++) axes.push(Array.from({ length: n }, (_, j) => i === j ? 1 : 0));
|
|
41
|
+
return orthonormalize(axes, basis);
|
|
42
|
+
}
|
|
43
|
+
function flatDim(f) {
|
|
44
|
+
return orthonormalize(f.dirs).length;
|
|
45
|
+
}
|
|
46
|
+
function closestOnFlat(f, p) {
|
|
47
|
+
const out = f.at.slice();
|
|
48
|
+
for (const q of orthonormalize(f.dirs)) {
|
|
49
|
+
let k = 0;
|
|
50
|
+
for (let i = 0; i < q.length; i++) k += q[i] * (p[i] - f.at[i]);
|
|
51
|
+
for (let i = 0; i < out.length; i++) out[i] += k * q[i];
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function solveLeastSquares(cols, rhs) {
|
|
56
|
+
const k = cols.length;
|
|
57
|
+
const m = cols.map((a) => [...cols.map((b) => dotv(a, b)), dotv(a, rhs)]);
|
|
58
|
+
let maxDiag = 0;
|
|
59
|
+
for (let i = 0; i < k; i++) maxDiag = Math.max(maxDiag, Math.abs(m[i][i]));
|
|
60
|
+
const tol = PIVOT_TOL * (maxDiag || 1);
|
|
61
|
+
const pivots = [];
|
|
62
|
+
let row = 0;
|
|
63
|
+
for (let col = 0; col < k && row < k; col++) {
|
|
64
|
+
let best = row;
|
|
65
|
+
for (let r = row + 1; r < k; r++) if (Math.abs(m[r][col]) > Math.abs(m[best][col])) best = r;
|
|
66
|
+
if (!(Math.abs(m[best][col]) > tol)) continue;
|
|
67
|
+
[m[row], m[best]] = [m[best], m[row]];
|
|
68
|
+
for (let r = row + 1; r < k; r++) {
|
|
69
|
+
const f = m[r][col] / m[row][col];
|
|
70
|
+
for (let c = col; c <= k; c++) m[r][c] -= f * m[row][c];
|
|
71
|
+
}
|
|
72
|
+
pivots.push([row, col]);
|
|
73
|
+
row++;
|
|
74
|
+
}
|
|
75
|
+
const x = new Array(k).fill(0);
|
|
76
|
+
for (let p = pivots.length - 1; p >= 0; p--) {
|
|
77
|
+
const [r, col] = pivots[p];
|
|
78
|
+
let s = m[r][k];
|
|
79
|
+
for (let c = col + 1; c < k; c++) s -= m[r][c] * x[c];
|
|
80
|
+
x[col] = s / m[r][col];
|
|
81
|
+
}
|
|
82
|
+
return x;
|
|
83
|
+
}
|
|
84
|
+
function closestBetweenFlats(a, b) {
|
|
85
|
+
const qa = orthonormalize(a.dirs);
|
|
86
|
+
const qb = orthonormalize(b.dirs);
|
|
87
|
+
const d = b.at.map((v, i) => v - a.at[i]);
|
|
88
|
+
const x = solveLeastSquares([...qa, ...qb.map((q) => q.map((v) => -v))], d);
|
|
89
|
+
const onA = a.at.slice();
|
|
90
|
+
const onB = b.at.slice();
|
|
91
|
+
qa.forEach((q, j) => q.forEach((v, i) => {
|
|
92
|
+
onA[i] += x[j] * v;
|
|
93
|
+
}));
|
|
94
|
+
qb.forEach((q, j) => q.forEach((v, i) => {
|
|
95
|
+
onB[i] += x[qa.length + j] * v;
|
|
96
|
+
}));
|
|
97
|
+
return { onA, onB };
|
|
98
|
+
}
|
|
99
|
+
function intersectFlats(a, b, eps = 1e-6) {
|
|
100
|
+
const { onA, onB } = closestBetweenFlats(a, b);
|
|
101
|
+
let gap2 = 0;
|
|
102
|
+
for (let i = 0; i < onA.length; i++) gap2 += (onA[i] - onB[i]) ** 2;
|
|
103
|
+
if (!(Math.sqrt(gap2) <= eps) || !isFiniteVec(onA)) return null;
|
|
104
|
+
const n = a.at.length;
|
|
105
|
+
const normals = orthonormalize([
|
|
106
|
+
...complement(orthonormalize(a.dirs), n),
|
|
107
|
+
...complement(orthonormalize(b.dirs), n)
|
|
108
|
+
]);
|
|
109
|
+
return { at: onA, dirs: complement(normals, n) };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/snap/resolve.ts
|
|
113
|
+
var DEFAULT_POOL = 24;
|
|
114
|
+
var DEFAULT_EPS = 1e-6;
|
|
115
|
+
var SAME_PX = 1e-9;
|
|
116
|
+
var plus = (a, b) => a.map((v, i) => v + b[i]);
|
|
117
|
+
var minus = (a, b) => a.map((v, i) => v - b[i]);
|
|
118
|
+
var isFiniteVec2 = (a) => a.every(Number.isFinite);
|
|
119
|
+
function distance(a, b) {
|
|
120
|
+
let s = 0;
|
|
121
|
+
for (let i = 0; i < a.length; i++) s += (a[i] - b[i]) ** 2;
|
|
122
|
+
return Math.sqrt(s);
|
|
123
|
+
}
|
|
124
|
+
function makeMeasure(problem, base, eps) {
|
|
125
|
+
const home = /* @__PURE__ */ new Map();
|
|
126
|
+
return {
|
|
127
|
+
eps,
|
|
128
|
+
move(probeId, probe, delta) {
|
|
129
|
+
let h = home.get(probeId);
|
|
130
|
+
if (!h) home.set(probeId, h = problem.toScreen(plus(probe, base)));
|
|
131
|
+
const d = distance(problem.toScreen(plus(probe, delta)), h);
|
|
132
|
+
return Number.isFinite(d) ? d : NaN;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function spanHolds(c, world, eps) {
|
|
137
|
+
if (!c.span) return true;
|
|
138
|
+
const u = solveLeastSquares(c.locus.dirs, minus(world, c.locus.at));
|
|
139
|
+
return c.span.every(([lo, hi], i) => u[i] >= lo - eps && u[i] <= hi + eps);
|
|
140
|
+
}
|
|
141
|
+
function meets(m, c, probe, delta) {
|
|
142
|
+
const measured = m.move(c.probe, probe, delta);
|
|
143
|
+
if (!c.required && (Number.isNaN(measured) || !(measured <= c.reachPx))) return null;
|
|
144
|
+
const px = Number.isNaN(measured) ? Infinity : measured;
|
|
145
|
+
return spanHolds(c, plus(probe, delta), m.eps) ? px : null;
|
|
146
|
+
}
|
|
147
|
+
function liveCandidates(problem, m) {
|
|
148
|
+
const probes = new Map(problem.probes.map((p) => [p.id, p.at]));
|
|
149
|
+
const live = [];
|
|
150
|
+
for (const c of problem.candidates) {
|
|
151
|
+
const probe = probes.get(c.probe);
|
|
152
|
+
if (!probe || !isFiniteVec2(probe) || !isFiniteVec2(c.locus.at)) continue;
|
|
153
|
+
const within = intersectFlats(problem.input, { at: minus(c.locus.at, probe), dirs: c.locus.dirs }, m.eps);
|
|
154
|
+
if (!within) continue;
|
|
155
|
+
const px = meets(m, c, probe, closestOnFlat(within, problem.delta0));
|
|
156
|
+
if (px !== null) live.push({ c, probe, within, px });
|
|
157
|
+
}
|
|
158
|
+
return live;
|
|
159
|
+
}
|
|
160
|
+
function pool(live, size) {
|
|
161
|
+
const byProbe = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const l of live) {
|
|
163
|
+
const list = byProbe.get(l.c.probe) ?? [];
|
|
164
|
+
list.push(l);
|
|
165
|
+
byProbe.set(l.c.probe, list);
|
|
166
|
+
}
|
|
167
|
+
const out = [];
|
|
168
|
+
for (const list of byProbe.values()) {
|
|
169
|
+
list.sort((a, b) => Math.abs(a.px - b.px) > SAME_PX ? a.px - b.px : a.c.order - b.c.order);
|
|
170
|
+
out.push(...list.slice(0, size));
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
function compareOrders(a, b) {
|
|
175
|
+
const oa = a.map((l) => l.c.order).sort((x, y) => x - y);
|
|
176
|
+
const ob = b.map((l) => l.c.order).sort((x, y) => x - y);
|
|
177
|
+
for (let i = 0; i < Math.min(oa.length, ob.length); i++) if (oa[i] !== ob[i]) return oa[i] - ob[i];
|
|
178
|
+
return oa.length - ob.length;
|
|
179
|
+
}
|
|
180
|
+
function compareCombos(a, b) {
|
|
181
|
+
if (a.codim !== b.codim) return b.codim - a.codim;
|
|
182
|
+
if (a.targetCodim !== b.targetCodim) return b.targetCodim - a.targetCodim;
|
|
183
|
+
if (Math.abs(a.px - b.px) > SAME_PX) return a.px - b.px;
|
|
184
|
+
return compareOrders(a.members, b.members);
|
|
185
|
+
}
|
|
186
|
+
function extend(parent, l, hDim, delta0, m) {
|
|
187
|
+
const flat = intersectFlats(parent.flat, l.within, m.eps);
|
|
188
|
+
if (!flat) return null;
|
|
189
|
+
const dim = flatDim(flat);
|
|
190
|
+
if (dim >= parent.dim) return null;
|
|
191
|
+
const delta = closestOnFlat(flat, delta0);
|
|
192
|
+
if (!isFiniteVec2(delta)) return null;
|
|
193
|
+
const members = [...parent.members, l];
|
|
194
|
+
let px = 0;
|
|
195
|
+
for (const mem of members) {
|
|
196
|
+
const p = meets(m, mem.c, mem.probe, delta);
|
|
197
|
+
if (p === null) return null;
|
|
198
|
+
px = Math.max(px, p);
|
|
199
|
+
}
|
|
200
|
+
const targetFlat = l.c.tier === "target" ? intersectFlats(parent.targetFlat, l.within, m.eps) ?? flat : parent.targetFlat;
|
|
201
|
+
return {
|
|
202
|
+
members,
|
|
203
|
+
flat,
|
|
204
|
+
dim,
|
|
205
|
+
targetFlat,
|
|
206
|
+
delta,
|
|
207
|
+
px,
|
|
208
|
+
codim: hDim - dim,
|
|
209
|
+
targetCodim: hDim - flatDim(targetFlat)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function search(candidates, root, delta0, m) {
|
|
213
|
+
const best = { any: root, required: null };
|
|
214
|
+
const consider = (c) => {
|
|
215
|
+
if (compareCombos(c, best.any) < 0) best.any = c;
|
|
216
|
+
if (c.members.some((l) => l.c.required) && (!best.required || compareCombos(c, best.required) < 0)) {
|
|
217
|
+
best.required = c;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const visit = (start, parent) => {
|
|
221
|
+
for (let i = start; i < candidates.length; i++) {
|
|
222
|
+
const next = extend(parent, candidates[i], root.dim, delta0, m);
|
|
223
|
+
if (!next) continue;
|
|
224
|
+
consider(next);
|
|
225
|
+
if (next.dim > 0) visit(i + 1, next);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
visit(0, root);
|
|
229
|
+
return best.required ?? best.any;
|
|
230
|
+
}
|
|
231
|
+
function satisfiedBy(problem, delta, m) {
|
|
232
|
+
const probes = new Map(problem.probes.map((p) => [p.id, p.at]));
|
|
233
|
+
return problem.candidates.filter((c) => {
|
|
234
|
+
const probe = probes.get(c.probe);
|
|
235
|
+
if (!probe) return false;
|
|
236
|
+
const world = plus(probe, delta);
|
|
237
|
+
if (!(distance(closestOnFlat(c.locus, world), world) <= m.eps)) return false;
|
|
238
|
+
return meets(m, c, probe, delta) !== null;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function resolveSnap(problem) {
|
|
242
|
+
const { input, delta0 } = problem;
|
|
243
|
+
let base = closestOnFlat(input, delta0);
|
|
244
|
+
if (!isFiniteVec2(base)) base = isFiniteVec2(input.at) ? input.at : input.at.map(() => 0);
|
|
245
|
+
const m = makeMeasure(problem, base, problem.eps ?? DEFAULT_EPS);
|
|
246
|
+
const hDim = flatDim(input);
|
|
247
|
+
const root = {
|
|
248
|
+
members: [],
|
|
249
|
+
flat: input,
|
|
250
|
+
dim: hDim,
|
|
251
|
+
targetFlat: input,
|
|
252
|
+
delta: base,
|
|
253
|
+
codim: 0,
|
|
254
|
+
targetCodim: 0,
|
|
255
|
+
px: 0
|
|
256
|
+
};
|
|
257
|
+
const live = pool(liveCandidates(problem, m), problem.poolSize ?? DEFAULT_POOL);
|
|
258
|
+
const win = search(live, root, delta0, m);
|
|
259
|
+
return {
|
|
260
|
+
delta: win.delta,
|
|
261
|
+
members: win.members.map((l) => l.c),
|
|
262
|
+
satisfied: satisfiedBy(problem, win.delta, m),
|
|
263
|
+
codim: win.codim
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/snap/stepLine.ts
|
|
268
|
+
function stepLine(about, toward, basis, step) {
|
|
269
|
+
const [u, v] = basis;
|
|
270
|
+
let a = 0;
|
|
271
|
+
let b = 0;
|
|
272
|
+
for (let i = 0; i < about.length; i++) {
|
|
273
|
+
const d = toward[i] - about[i];
|
|
274
|
+
a += d * u[i];
|
|
275
|
+
b += d * v[i];
|
|
276
|
+
}
|
|
277
|
+
const theta = a === 0 && b === 0 ? 0 : Math.round(Math.atan2(b, a) / step) * step;
|
|
278
|
+
const c = Math.cos(theta);
|
|
279
|
+
const s = Math.sin(theta);
|
|
280
|
+
return { at: about, dirs: [u.map((x, i) => x * c + v[i] * s)] };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export { closestBetweenFlats, closestOnFlat, flatDim, intersectFlats, resolveSnap, stepLine };
|
|
2
284
|
//# sourceMappingURL=index.js.map
|
|
3
285
|
//# sourceMappingURL=index.js.map
|
package/dist/nd/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
1
|
+
{"version":3,"sources":["../../src/snap/flat.ts","../../src/snap/resolve.ts","../../src/snap/stepLine.ts"],"names":["isFiniteVec"],"mappings":"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","file":"index.js","sourcesContent":["/** Affine subspaces (\"flats\") over plain number arrays, in any dimension:\n * the points, lines and planes a snap resolves against. */\n\nexport type Vec = readonly number[];\n\n/** The affine subspace `at + span(dirs)`. `dirs` need not be orthonormal or\n * independent; a zero or repeated direction adds nothing. */\nexport interface Flat {\n at: Vec;\n dirs: readonly Vec[];\n}\n\n/** A direction left with less than this fraction of its length after the\n * ones before it are taken out counts as dependent on them. */\nconst RANK_TOL = 1e-9;\n/** Pivots below this fraction of the largest diagonal entry count as zero. */\nconst PIVOT_TOL = 1e-12;\n\nfunction dotv(a: Vec, b: Vec): number {\n let s = 0;\n for (let i = 0; i < a.length; i++) s += a[i] * b[i];\n return s;\n}\n\nfunction normv(a: Vec): number {\n return Math.sqrt(dotv(a, a));\n}\n\nfunction isFiniteVec(a: Vec): boolean {\n return a.every(Number.isFinite);\n}\n\n/** An orthonormal basis of `span(vecs)`, built by Gram-Schmidt with a second\n * pass for stability. Non-finite and dependent vectors are dropped. */\nexport function orthonormalize(vecs: readonly Vec[], start: readonly Vec[] = []): number[][] {\n const basis: number[][] = start.map((q) => q.slice());\n const out: number[][] = [];\n for (const d of vecs) {\n const n0 = normv(d);\n if (!(n0 > 0) || !isFiniteVec(d)) continue;\n const v = d.slice();\n for (let pass = 0; pass < 2; pass++) {\n for (const q of basis) {\n const k = dotv(v, q);\n for (let i = 0; i < v.length; i++) v[i] -= k * q[i];\n }\n }\n const n = normv(v);\n if (n <= RANK_TOL * n0) continue;\n for (let i = 0; i < v.length; i++) v[i] /= n;\n basis.push(v);\n out.push(v);\n }\n return out;\n}\n\n/** An orthonormal basis of the orthogonal complement of the orthonormal `basis` in R^n. */\nfunction complement(basis: readonly Vec[], n: number): number[][] {\n const axes: number[][] = [];\n for (let i = 0; i < n; i++) axes.push(Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)));\n return orthonormalize(axes, basis);\n}\n\n/** The flat's dimension: the rank of its directions. */\nexport function flatDim(f: Flat): number {\n return orthonormalize(f.dirs).length;\n}\n\n/** The point of `f` nearest `p`. */\nexport function closestOnFlat(f: Flat, p: Vec): Vec {\n const out = f.at.slice();\n for (const q of orthonormalize(f.dirs)) {\n let k = 0;\n for (let i = 0; i < q.length; i++) k += q[i] * (p[i] - f.at[i]);\n for (let i = 0; i < out.length; i++) out[i] += k * q[i];\n }\n return out;\n}\n\n/**\n * The `x` minimizing `|Σ x_j cols_j − rhs|`, through the normal equations.\n * A rank-deficient system has many minimizers; this returns the one whose\n * free unknowns are zero.\n */\nexport function solveLeastSquares(cols: readonly Vec[], rhs: Vec): number[] {\n const k = cols.length;\n const m = cols.map((a) => [...cols.map((b) => dotv(a, b)), dotv(a, rhs)]);\n let maxDiag = 0;\n for (let i = 0; i < k; i++) maxDiag = Math.max(maxDiag, Math.abs(m[i][i]));\n const tol = PIVOT_TOL * (maxDiag || 1);\n const pivots: [row: number, col: number][] = [];\n let row = 0;\n for (let col = 0; col < k && row < k; col++) {\n let best = row;\n for (let r = row + 1; r < k; r++) if (Math.abs(m[r][col]) > Math.abs(m[best][col])) best = r;\n if (!(Math.abs(m[best][col]) > tol)) continue;\n [m[row], m[best]] = [m[best], m[row]];\n for (let r = row + 1; r < k; r++) {\n const f = m[r][col] / m[row][col];\n for (let c = col; c <= k; c++) m[r][c] -= f * m[row][c];\n }\n pivots.push([row, col]);\n row++;\n }\n const x = new Array<number>(k).fill(0);\n for (let p = pivots.length - 1; p >= 0; p--) {\n const [r, col] = pivots[p];\n let s = m[r][k];\n for (let c = col + 1; c < k; c++) s -= m[r][c] * x[c];\n x[col] = s / m[r][col];\n }\n return x;\n}\n\n/** The nearest pair of points, one on each flat. Parallel flats have many\n * such pairs; this returns one of them. */\nexport function closestBetweenFlats(a: Flat, b: Flat): { onA: Vec; onB: Vec } {\n const qa = orthonormalize(a.dirs);\n const qb = orthonormalize(b.dirs);\n const d = b.at.map((v, i) => v - a.at[i]);\n const x = solveLeastSquares([...qa, ...qb.map((q) => q.map((v) => -v))], d);\n const onA = a.at.slice();\n const onB = b.at.slice();\n qa.forEach((q, j) => q.forEach((v, i) => { onA[i] += x[j] * v; }));\n qb.forEach((q, j) => q.forEach((v, i) => { onB[i] += x[qa.length + j] * v; }));\n return { onA, onB };\n}\n\n/** `a ∩ b`, or null when they miss each other by more than `eps`. */\nexport function intersectFlats(a: Flat, b: Flat, eps = 1e-6): Flat | null {\n const { onA, onB } = closestBetweenFlats(a, b);\n let gap2 = 0;\n for (let i = 0; i < onA.length; i++) gap2 += (onA[i] - onB[i]) ** 2;\n if (!(Math.sqrt(gap2) <= eps) || !isFiniteVec(onA)) return null;\n const n = a.at.length;\n const normals = orthonormalize([\n ...complement(orthonormalize(a.dirs), n),\n ...complement(orthonormalize(b.dirs), n),\n ]);\n return { at: onA, dirs: complement(normals, n) };\n}\n","/** The snap resolver: every gesture's snap, in any dimension, as one search\n * over affine constraints on the move `δ`. */\n\nimport { closestOnFlat, flatDim, intersectFlats, solveLeastSquares, type Flat, type Vec } from './flat';\n\n/** A `fallback` candidate holds only the axes no `target` claims. */\nexport type SnapTier = 'target' | 'fallback';\n/** What a probe stands for on the subject; sources match probes by role. */\nexport type ProbeRole = 'origin' | 'box' | 'geometry' | 'pointer';\n\n/** A point on the subject that should land on a locus. `role` says which\n * sources may match it. */\nexport interface SnapProbe {\n id: string;\n at: Vec;\n role: ProbeRole;\n}\n\n/** One probe paired with one locus. `M` is the mark payload, opaque here. */\nexport interface SnapCandidate<M> {\n /** `SnapProbe.id`. */\n probe: string;\n locus: Flat;\n /** One interval per locus dir, in multiples of that dir. A solution\n * outside it fails the candidate. */\n span?: readonly (readonly [number, number])[];\n source: string;\n /** Stable tiebreak: lower wins when everything else is equal. */\n order: number;\n tier: SnapTier;\n reachPx: number;\n /** When any required candidate exists, the result includes one of them,\n * whatever its reach. */\n required?: boolean;\n mark?: M;\n}\n\n/** One frame's snap: the probes, the input subspace, the raw move, and the candidates. */\nexport interface SnapProblem<M> {\n probes: readonly SnapProbe[];\n /** `H`, the subspace of δ-space the result stays inside. */\n input: Flat;\n /** The unsnapped move. */\n delta0: Vec;\n candidates: readonly SnapCandidate<M>[];\n /** World to screen pixels. A non-finite result puts that position out of reach. */\n toScreen(world: Vec): Vec;\n /** Candidates kept per probe, nearest first. Default 24. */\n poolSize?: number;\n /** World-space tolerance for \"on the locus\". Default 1e-6. */\n eps?: number;\n}\n\n/** The move `resolveSnap` chose and the candidates it meets. */\nexport interface SnapResolution<M> {\n /** `closestOnFlat(input, delta0)` when nothing snaps. */\n delta: Vec;\n members: readonly SnapCandidate<M>[];\n /** Every in-reach candidate the result meets: the ones whose marks show. */\n satisfied: readonly SnapCandidate<M>[];\n /** How many dimensions of `H` the result pins. */\n codim: number;\n}\n\n/** A candidate that can be met on its own: its probe, and `H ∩ C` in δ-space. */\ninterface Live<M> {\n c: SnapCandidate<M>;\n probe: Vec;\n within: Flat;\n px: number;\n}\n\ninterface Combo<M> {\n members: Live<M>[];\n flat: Flat;\n dim: number;\n targetFlat: Flat;\n delta: Vec;\n codim: number;\n targetCodim: number;\n px: number;\n}\n\nconst DEFAULT_POOL = 24;\nconst DEFAULT_EPS = 1e-6;\n/** Screen displacements closer than this rank as equal, so order decides. */\nconst SAME_PX = 1e-9;\n\nconst plus = (a: Vec, b: Vec): Vec => a.map((v, i) => v + b[i]);\nconst minus = (a: Vec, b: Vec): Vec => a.map((v, i) => v - b[i]);\nconst isFiniteVec = (a: Vec): boolean => a.every(Number.isFinite);\n\nfunction distance(a: Vec, b: Vec): number {\n let s = 0;\n for (let i = 0; i < a.length; i++) s += (a[i] - b[i]) ** 2;\n return Math.sqrt(s);\n}\n\n/** What the resolver needs to measure a candidate against a trial δ. */\ninterface Measure {\n /** Screen pixels the probe moves going from δ0 (projected into `H`) to\n * `delta`; NaN when either end has no finite screen position. */\n move(probeId: string, probe: Vec, delta: Vec): number;\n eps: number;\n}\n\nfunction makeMeasure<M>(problem: SnapProblem<M>, base: Vec, eps: number): Measure {\n const home = new Map<string, Vec>();\n return {\n eps,\n move(probeId, probe, delta) {\n let h = home.get(probeId);\n if (!h) home.set(probeId, (h = problem.toScreen(plus(probe, base))));\n const d = distance(problem.toScreen(plus(probe, delta)), h);\n return Number.isFinite(d) ? d : NaN;\n },\n };\n}\n\nfunction spanHolds<M>(c: SnapCandidate<M>, world: Vec, eps: number): boolean {\n if (!c.span) return true;\n const u = solveLeastSquares(c.locus.dirs, minus(world, c.locus.at));\n return c.span.every(([lo, hi], i) => u[i] >= lo - eps && u[i] <= hi + eps);\n}\n\n/** The probe's screen move under `delta`, or null when it is out of reach or off the span. */\nfunction meets<M>(m: Measure, c: SnapCandidate<M>, probe: Vec, delta: Vec): number | null {\n const measured = m.move(c.probe, probe, delta);\n if (!c.required && (Number.isNaN(measured) || !(measured <= c.reachPx))) return null;\n const px = Number.isNaN(measured) ? Infinity : measured;\n return spanHolds(c, plus(probe, delta), m.eps) ? px : null;\n}\n\nfunction liveCandidates<M>(problem: SnapProblem<M>, m: Measure): Live<M>[] {\n const probes = new Map(problem.probes.map((p) => [p.id, p.at]));\n const live: Live<M>[] = [];\n for (const c of problem.candidates) {\n const probe = probes.get(c.probe);\n if (!probe || !isFiniteVec(probe) || !isFiniteVec(c.locus.at)) continue;\n const within = intersectFlats(problem.input, { at: minus(c.locus.at, probe), dirs: c.locus.dirs }, m.eps);\n if (!within) continue;\n const px = meets(m, c, probe, closestOnFlat(within, problem.delta0));\n if (px !== null) live.push({ c, probe, within, px });\n }\n return live;\n}\n\nfunction pool<M>(live: Live<M>[], size: number): Live<M>[] {\n const byProbe = new Map<string, Live<M>[]>();\n for (const l of live) {\n const list = byProbe.get(l.c.probe) ?? [];\n list.push(l);\n byProbe.set(l.c.probe, list);\n }\n const out: Live<M>[] = [];\n for (const list of byProbe.values()) {\n list.sort((a, b) => (Math.abs(a.px - b.px) > SAME_PX ? a.px - b.px : a.c.order - b.c.order));\n out.push(...list.slice(0, size));\n }\n return out;\n}\n\nfunction compareOrders<M>(a: Live<M>[], b: Live<M>[]): number {\n const oa = a.map((l) => l.c.order).sort((x, y) => x - y);\n const ob = b.map((l) => l.c.order).sort((x, y) => x - y);\n for (let i = 0; i < Math.min(oa.length, ob.length); i++) if (oa[i] !== ob[i]) return oa[i] - ob[i];\n return oa.length - ob.length;\n}\n\n/** Negative when `a` ranks ahead of `b`. */\nfunction compareCombos<M>(a: Combo<M>, b: Combo<M>): number {\n if (a.codim !== b.codim) return b.codim - a.codim;\n if (a.targetCodim !== b.targetCodim) return b.targetCodim - a.targetCodim;\n if (Math.abs(a.px - b.px) > SAME_PX) return a.px - b.px;\n return compareOrders(a.members, b.members);\n}\n\n/** `parent` plus `l`, or null when `l` adds no constraint or breaks a member's reach or span. */\nfunction extend<M>(parent: Combo<M>, l: Live<M>, hDim: number, delta0: Vec, m: Measure): Combo<M> | null {\n const flat = intersectFlats(parent.flat, l.within, m.eps);\n if (!flat) return null;\n const dim = flatDim(flat);\n if (dim >= parent.dim) return null;\n const delta = closestOnFlat(flat, delta0);\n if (!isFiniteVec(delta)) return null;\n const members = [...parent.members, l];\n let px = 0;\n for (const mem of members) {\n const p = meets(m, mem.c, mem.probe, delta);\n if (p === null) return null;\n px = Math.max(px, p);\n }\n const targetFlat = l.c.tier === 'target'\n ? intersectFlats(parent.targetFlat, l.within, m.eps) ?? flat\n : parent.targetFlat;\n return {\n members, flat, dim, targetFlat, delta, px,\n codim: hDim - dim,\n targetCodim: hDim - flatDim(targetFlat),\n };\n}\n\n/** The best combination; when any can include a required candidate, the best of those. */\nfunction search<M>(candidates: Live<M>[], root: Combo<M>, delta0: Vec, m: Measure): Combo<M> {\n const best: { any: Combo<M>; required: Combo<M> | null } = { any: root, required: null };\n const consider = (c: Combo<M>) => {\n if (compareCombos(c, best.any) < 0) best.any = c;\n if (c.members.some((l) => l.c.required) && (!best.required || compareCombos(c, best.required) < 0)) {\n best.required = c;\n }\n };\n const visit = (start: number, parent: Combo<M>) => {\n for (let i = start; i < candidates.length; i++) {\n const next = extend(parent, candidates[i], root.dim, delta0, m);\n if (!next) continue;\n consider(next);\n if (next.dim > 0) visit(i + 1, next);\n }\n };\n visit(0, root);\n return best.required ?? best.any;\n}\n\nfunction satisfiedBy<M>(problem: SnapProblem<M>, delta: Vec, m: Measure): SnapCandidate<M>[] {\n const probes = new Map(problem.probes.map((p) => [p.id, p.at]));\n return problem.candidates.filter((c) => {\n const probe = probes.get(c.probe);\n if (!probe) return false;\n const world = plus(probe, delta);\n if (!(distance(closestOnFlat(c.locus, world), world) <= m.eps)) return false;\n return meets(m, c, probe, delta) !== null;\n });\n}\n\n/** The move nearest `delta0` within `H` that meets the best-ranked set of\n * candidates in reach, or `delta0` projected into `H` when none is. */\nexport function resolveSnap<M>(problem: SnapProblem<M>): SnapResolution<M> {\n const { input, delta0 } = problem;\n let base = closestOnFlat(input, delta0);\n if (!isFiniteVec(base)) base = isFiniteVec(input.at) ? input.at : input.at.map(() => 0);\n const m = makeMeasure(problem, base, problem.eps ?? DEFAULT_EPS);\n const hDim = flatDim(input);\n const root: Combo<M> = {\n members: [], flat: input, dim: hDim, targetFlat: input, delta: base, codim: 0, targetCodim: 0, px: 0,\n };\n const live = pool(liveCandidates(problem, m), problem.poolSize ?? DEFAULT_POOL);\n const win = search(live, root, delta0, m);\n return {\n delta: win.delta,\n members: win.members.map((l) => l.c),\n satisfied: satisfiedBy(problem, win.delta, m),\n codim: win.codim,\n };\n}\n","import type { Flat, Vec } from './flat';\n\n/**\n * The line through `about` whose direction, measured in the plane `basis`\n * spans (as an angle from `basis[0]` toward `basis[1]`), is the multiple of\n * `step` nearest the direction from `about` to `toward`: a constrained\n * gesture's input subspace, in any dimension. `basis` is orthonormal.\n */\nexport function stepLine(about: Vec, toward: Vec, basis: readonly [Vec, Vec], step: number): Flat {\n const [u, v] = basis;\n let a = 0;\n let b = 0;\n for (let i = 0; i < about.length; i++) {\n const d = toward[i] - about[i];\n a += d * u[i];\n b += d * v[i];\n }\n const theta = a === 0 && b === 0 ? 0 : Math.round(Math.atan2(b, a) / step) * step;\n const c = Math.cos(theta);\n const s = Math.sin(theta);\n return { at: about, dirs: [u.map((x, i) => x * c + v[i] * s)] };\n}\n"]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/** A 2D point, the shape the point-taking path helpers read and return. */
|
|
2
|
+
interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/** Point on the cubic Bezier (p0, p1, p2, p3) at parameter t. The point-shaped
|
|
7
|
+
* face of geom's `cubicEvalAt`. */
|
|
8
|
+
declare function cubicPointAt(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point;
|
|
9
|
+
/** De Casteljau subdivision of a cubic at parameter t. The point-shaped face
|
|
10
|
+
* of geom's `splitCubicAt`; `left[3]` and `right[0]` are the point at t. */
|
|
11
|
+
declare function splitCubicAtT(p0: Point, p1: Point, p2: Point, p3: Point, t: number): {
|
|
12
|
+
left: [Point, Point, Point, Point];
|
|
13
|
+
right: [Point, Point, Point, Point];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** The six `corner-shape` keywords, from most convex to most concave. */
|
|
17
|
+
type CornerKeyword = 'square' | 'squircle' | 'round' | 'bevel' | 'scoop' | 'notch';
|
|
18
|
+
/** A corner's shape: a keyword, or a finite `superellipse()` parameter. */
|
|
19
|
+
type CornerShape = CornerKeyword | number;
|
|
20
|
+
/** The keywords in the order cycling through them steps. */
|
|
21
|
+
declare const CORNER_KEYWORDS: readonly CornerKeyword[];
|
|
22
|
+
/** The `superellipse()` parameter of `shape`. */
|
|
23
|
+
declare function cornerShapeK(shape: CornerShape): number;
|
|
24
|
+
/** The keyword whose parameter is exactly `k`, or null. */
|
|
25
|
+
declare function cornerKeywordOf(k: number): CornerKeyword | null;
|
|
26
|
+
/** Whether `v` is a keyword or a finite number. */
|
|
27
|
+
declare function isCornerShape(v: unknown): v is CornerShape;
|
|
28
|
+
/** The keyword after `shape` in {@link CORNER_KEYWORDS}, wrapping; from a
|
|
29
|
+
* number, the first keyword less convex than it. */
|
|
30
|
+
declare function nextCornerShape(shape: CornerShape): CornerKeyword;
|
|
31
|
+
/** Largest distance, in units of the corner's setback, the fitted cubics may
|
|
32
|
+
* stray from the true superellipse. */
|
|
33
|
+
declare const CORNER_FIT_TOLERANCE = 0.001;
|
|
34
|
+
/** The cut a corner shape spans: from `start` to `end`, the two points where
|
|
35
|
+
* it leaves the edges, around `outer`, the corner the edges meet at. */
|
|
36
|
+
interface CornerShapeFrame {
|
|
37
|
+
start: Point;
|
|
38
|
+
end: Point;
|
|
39
|
+
outer: Point;
|
|
40
|
+
}
|
|
41
|
+
/** One piece of a corner's outline, continuing from where the last ended. */
|
|
42
|
+
type CornerPiece = {
|
|
43
|
+
kind: 'line';
|
|
44
|
+
to: Point;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'cubic';
|
|
47
|
+
c1: Point;
|
|
48
|
+
c2: Point;
|
|
49
|
+
to: Point;
|
|
50
|
+
};
|
|
51
|
+
/** The center the convex curves are spanned from: across the cut from `outer`. */
|
|
52
|
+
declare function cornerCenter(frame: CornerShapeFrame): Point;
|
|
53
|
+
/**
|
|
54
|
+
* The outline of a corner of parameter `k` across `frame`, from its start to
|
|
55
|
+
* its end. Exact at `k = 0` (a line), `±1` (the cubic quarter arc the kit
|
|
56
|
+
* draws circles with) and `±∞` (two lines, through the outer corner or the
|
|
57
|
+
* center); fitted with cubics within `tolerance` of the setback elsewhere.
|
|
58
|
+
*/
|
|
59
|
+
declare function cornerShapePieces(k: number, frame: CornerShapeFrame, tolerance?: number): CornerPiece[];
|
|
60
|
+
/** The point on the true curve of parameter `k` across `frame` at `s`, from
|
|
61
|
+
* 0 at its start to 1 at its end. Finite `k` only. */
|
|
62
|
+
declare function cornerShapePoint(k: number, frame: CornerShapeFrame, s: number): Point;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Per-anchor corner specs: the stored half of corner shapes. A path carries
|
|
66
|
+
* them in `PolygonPath.corners`, indexed by flat anchor index, and every
|
|
67
|
+
* reader of its geometry sees them expanded (`resolveCorners`).
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/** The corner at one anchor: its shape, and its setback, how far along each
|
|
71
|
+
* edge from the anchor the cut starts. A `round` corner between edges at a
|
|
72
|
+
* right angle is a circle of that radius. */
|
|
73
|
+
interface CornerSpec {
|
|
74
|
+
shape: CornerShape;
|
|
75
|
+
radius: number;
|
|
76
|
+
}
|
|
77
|
+
/** The corner cut at flat anchor `index`, or null. A zero radius cuts nothing. */
|
|
78
|
+
declare function cornerAt(path: PolygonPath, index: number): CornerSpec | null;
|
|
79
|
+
/** The spec stored at flat anchor `index`, radius zero included: a shape
|
|
80
|
+
* picked before any radius is dragged out. */
|
|
81
|
+
declare function cornerSpecAt(path: PolygonPath, index: number): CornerSpec | null;
|
|
82
|
+
/** Whether any anchor of `path` carries a corner with a radius. */
|
|
83
|
+
declare function hasCorners(path: {
|
|
84
|
+
kind: string;
|
|
85
|
+
corners?: PolygonPath['corners'];
|
|
86
|
+
}): boolean;
|
|
87
|
+
/** `corners` without trailing empty slots, or undefined when none is left. */
|
|
88
|
+
declare function compactCorners(corners: readonly (CornerSpec | null | undefined)[]): (CornerSpec | null)[] | undefined;
|
|
89
|
+
/** `path` without its corner specs: the bare anchors the specs decorate. */
|
|
90
|
+
declare function withoutCorners(path: PolygonPath): PolygonPath;
|
|
91
|
+
/** `path` with the corner at flat anchor `index` set to `spec`, or cleared
|
|
92
|
+
* when `spec` is null. A zero radius keeps the shape and cuts nothing.
|
|
93
|
+
* Never mutates `path`. */
|
|
94
|
+
declare function withCorner(path: PolygonPath, index: number, spec: CornerSpec | null): PolygonPath;
|
|
95
|
+
/** `path` with every corner radius multiplied by `factor`, as a uniform
|
|
96
|
+
* scale of its geometry would carry them. */
|
|
97
|
+
declare function scaleCorners(path: PolygonPath, factor: number): PolygonPath;
|
|
98
|
+
/**
|
|
99
|
+
* The corners of a path `t` of the way from `a` to `b`, which share their
|
|
100
|
+
* anchors. A corner whose shape both ends agree on, or that one end leaves
|
|
101
|
+
* sharp, grows its radius between them; one whose shape changes takes the
|
|
102
|
+
* nearer end's.
|
|
103
|
+
*/
|
|
104
|
+
declare function lerpCorners(a: PolygonPath['corners'], b: PolygonPath['corners'], t: number): (CornerSpec | null)[] | undefined;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Path data model: the vector-graphics primitive every weasel package uses as
|
|
108
|
+
* its canonical shape. An SVG-style command stream covers lines, polygons,
|
|
109
|
+
* beziers and multi-contour shapes (the inner hole of an "O") under one type.
|
|
110
|
+
*
|
|
111
|
+
* Storage: command codes in a `Uint8Array`, parameters in a `Float32Array`.
|
|
112
|
+
* Each command consumes a fixed number of coords (`PATH_CMD_LENGTHS`); walkers
|
|
113
|
+
* step both arrays in lockstep.
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/** Fill rule used by polygon path hit-testing and `ctx.fill()`. */
|
|
117
|
+
type PathFillRule = 'nonzero' | 'evenodd';
|
|
118
|
+
/** Whether a fill closes an open subpath with a straight edge back to its
|
|
119
|
+
* start, as SVG and canvas do (`'close'`), or leaves it unfilled (`'none'`). */
|
|
120
|
+
type PathOpenFill = 'close' | 'none';
|
|
121
|
+
/**
|
|
122
|
+
* Polygon path with arbitrary contours and optional bezier segments.
|
|
123
|
+
* Multi-contour: each `M` opens a new subpath; `Z` closes the current one.
|
|
124
|
+
* An open subpath (no `Z`) strokes as a polyline; `openFill` says whether it
|
|
125
|
+
* fills.
|
|
126
|
+
*/
|
|
127
|
+
interface PolygonPath {
|
|
128
|
+
kind: 'polygon';
|
|
129
|
+
commands: Uint8Array;
|
|
130
|
+
coords: Float32Array;
|
|
131
|
+
fillRule: PathFillRule;
|
|
132
|
+
/** Absent, the engine default: see `setDefaultOpenFill`. */
|
|
133
|
+
openFill?: PathOpenFill;
|
|
134
|
+
/**
|
|
135
|
+
* Corner shapes, indexed by flat anchor index (`enumerateAnchors` order):
|
|
136
|
+
* the anchor at each non-null slot is cut in a `corner-shape`
|
|
137
|
+
* (`cornerShape.ts`). The commands and coords hold the bare anchors; `resolveCorners`
|
|
138
|
+
* expands the specs into the geometry everything draws, hits, and bounds.
|
|
139
|
+
*/
|
|
140
|
+
corners?: readonly (CornerSpec | null)[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Axis-aligned rectangle. Fast path for the (very common) case where the
|
|
144
|
+
* shape is just a rect — preserves O(1) bounds and hit-test, avoids the
|
|
145
|
+
* polygon kernel entirely.
|
|
146
|
+
*/
|
|
147
|
+
interface RectPath {
|
|
148
|
+
kind: 'rect';
|
|
149
|
+
x: number;
|
|
150
|
+
y: number;
|
|
151
|
+
width: number;
|
|
152
|
+
height: number;
|
|
153
|
+
}
|
|
154
|
+
/** Canonical path shape — either an axis-aligned rect (fast path) or a polygon command stream. */
|
|
155
|
+
type Path = PolygonPath | RectPath;
|
|
156
|
+
|
|
157
|
+
export { withoutCorners as A, type CornerSpec as C, type PolygonPath as P, type RectPath as R, type Path as a, type Point as b, type PathOpenFill as c, type PathFillRule as d, type CornerPiece as e, CORNER_FIT_TOLERANCE as f, CORNER_KEYWORDS as g, type CornerKeyword as h, type CornerShape as i, type CornerShapeFrame as j, compactCorners as k, cornerAt as l, cornerCenter as m, cornerKeywordOf as n, cornerShapeK as o, cornerShapePieces as p, cornerShapePoint as q, cornerSpecAt as r, cubicPointAt as s, hasCorners as t, isCornerShape as u, lerpCorners as v, nextCornerShape as w, scaleCorners as x, splitCubicAtT as y, withCorner as z };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cubic control points that make a curve leave a port along its normal and
|
|
3
|
+
* arrive at the next against that port's normal — what makes a routed edge read
|
|
4
|
+
* as plugged into a box rather than aimed at it.
|
|
5
|
+
*
|
|
6
|
+
* The rule is stated once, over loose components, and published as is from
|
|
7
|
+
* `./nd`; the 2D barrel and `./3d` each wrap it in their own currency. Every
|
|
8
|
+
* operation here is closed on the plane z = 0, so a planar problem answered
|
|
9
|
+
* through the 3D wrapper returns the same numbers as through the 2D one, not an
|
|
10
|
+
* approximation of them.
|
|
11
|
+
*/
|
|
12
|
+
/** How far a control point reaches toward the other end, as a fraction of the
|
|
13
|
+
* straight-line distance between the two. */
|
|
14
|
+
declare const PORT_REACH = 0.4;
|
|
15
|
+
/**
|
|
16
|
+
* `[c1, c2]` for the cubic from `a` to `b`.
|
|
17
|
+
*
|
|
18
|
+
* A null normal falls back to a plain lerp along the chord, which is what makes
|
|
19
|
+
* an unported waypoint curve smoothly instead of kinking. The two ends are not
|
|
20
|
+
* symmetric: `outNormal` points the way the curve departs, while `inNormal`
|
|
21
|
+
* points out of the receiving port, so `c2` is pushed *along* it to arrive
|
|
22
|
+
* against it.
|
|
23
|
+
*/
|
|
24
|
+
declare function portControls(a: readonly number[], b: readonly number[], outNormal: readonly number[] | null | undefined, inNormal: readonly number[] | null | undefined, reach?: number): [number[], number[]];
|
|
25
|
+
/** A port curve sampled into `samples` points, excluding its start. */
|
|
26
|
+
declare function portCurvePoints(a: readonly number[], b: readonly number[], outNormal: readonly number[] | null | undefined, inNormal: readonly number[] | null | undefined, samples: number, reach?: number): number[][];
|
|
27
|
+
/** A cubic evaluated at `t`, component-wise, in whatever dimension it was given. */
|
|
28
|
+
declare function cubicAt(p0: readonly number[], p1: readonly number[], p2: readonly number[], p3: readonly number[], t: number): number[];
|
|
29
|
+
|
|
30
|
+
export { PORT_REACH as P, portCurvePoints as a, cubicAt as c, portControls as p };
|