@altpsyche/maths 0.9.0 → 0.9.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/dist/figure/boolean.js +66 -16
- package/dist/figure/inside.d.ts +14 -0
- package/dist/figure/inside.js +36 -0
- package/dist/figure/intersect.d.ts +3 -3
- package/dist/figure/intersect.js +120 -12
- package/dist/figure/path.d.ts +3 -0
- package/dist/figure/path.js +9 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/figure/boolean.js
CHANGED
|
@@ -10,12 +10,20 @@
|
|
|
10
10
|
* is what turns a disc taken out of the middle of another disc into a ring: the
|
|
11
11
|
* inner loop is wound against the outer one and the nonzero rule the mark
|
|
12
12
|
* carries leaves it empty.
|
|
13
|
+
*
|
|
14
|
+
* A piece lying along the other path's own edge is decided by which way the two
|
|
15
|
+
* run rather than by which side it is on, because a point on an edge is the one
|
|
16
|
+
* place the winding count has no answer for. Two paths walking a shared stretch
|
|
17
|
+
* the same way have their solid on the same side of it, so the stretch is on the
|
|
18
|
+
* edge of a union and of an overlap and is kept once. Walking it opposite ways
|
|
19
|
+
* puts their solids on opposite sides, so the stretch is inside a union and
|
|
20
|
+
* outside an overlap, and a difference keeps the first path's copy of it.
|
|
13
21
|
*/
|
|
14
22
|
import { vec2 } from '../values/vec2.js';
|
|
15
|
-
import { pointOn } from './path.js';
|
|
23
|
+
import { pointOn, slopeOn } from './path.js';
|
|
16
24
|
import { cutPath } from './cut.js';
|
|
17
25
|
import { curveCrossings } from './intersect.js';
|
|
18
|
-
import { flattenPath, windingAt } from './inside.js';
|
|
26
|
+
import { flattenPath, nearestEdge, windingAt } from './inside.js';
|
|
19
27
|
const TOLERANCE = 1e-6;
|
|
20
28
|
/** A subpath left open closed by the straight run back to where it started,
|
|
21
29
|
* since a path with an open loop has no inside for any of this to ask about. */
|
|
@@ -83,6 +91,22 @@ function cutsBetween(first, second, tolerance) {
|
|
|
83
91
|
}
|
|
84
92
|
return [forFirst, forSecond];
|
|
85
93
|
}
|
|
94
|
+
function place(point) {
|
|
95
|
+
return `(${point.x.toFixed(6)}, ${point.y.toFixed(6)})`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* What a run of pieces that will not close stops with.
|
|
99
|
+
*
|
|
100
|
+
* Handing it back as a loop anyway is the one failure a caller cannot see: the
|
|
101
|
+
* shape drawn is wrong and nothing about it says so. Every input is closed
|
|
102
|
+
* loops, so a run that will not close is this code being wrong rather than the
|
|
103
|
+
* caller, and stopping is what makes that visible on the frame it happens.
|
|
104
|
+
*/
|
|
105
|
+
function refuse(pieces, start, end, tolerance) {
|
|
106
|
+
throw new Error(`the pieces kept do not close into a loop: ${pieces} of them run from ${place(start)} ` +
|
|
107
|
+
`to ${place(end)}, which is ${vec2.distance(end, start).toFixed(6)} apart against a ` +
|
|
108
|
+
`tolerance of ${tolerance}`);
|
|
109
|
+
}
|
|
86
110
|
/**
|
|
87
111
|
* The kept pieces joined into loops, by taking each end to the piece that
|
|
88
112
|
* starts where it finishes.
|
|
@@ -116,7 +140,7 @@ function stitch(pieces, tolerance) {
|
|
|
116
140
|
}
|
|
117
141
|
}
|
|
118
142
|
if (next < 0)
|
|
119
|
-
|
|
143
|
+
refuse(curves.length, start, end, tolerance);
|
|
120
144
|
used[next] = true;
|
|
121
145
|
curves.push(pieces[next].curve);
|
|
122
146
|
end = pieces[next].curve.to;
|
|
@@ -125,10 +149,40 @@ function stitch(pieces, tolerance) {
|
|
|
125
149
|
}
|
|
126
150
|
return loops;
|
|
127
151
|
}
|
|
128
|
-
/**
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Where each piece stands against the other path, read at its middle.
|
|
154
|
+
*
|
|
155
|
+
* The middle stands for the whole piece because the cutting has already put a
|
|
156
|
+
* break wherever the two paths meet, so a piece after it is wholly one thing.
|
|
157
|
+
*/
|
|
158
|
+
function sidesAgainst(pieces, other, tolerance) {
|
|
159
|
+
return pieces.map((piece) => {
|
|
160
|
+
const middle = pointOn(piece.from, piece.curve, 0.5);
|
|
161
|
+
const edge = nearestEdge(other, middle);
|
|
162
|
+
if (edge && edge.gap <= tolerance) {
|
|
163
|
+
return vec2.dot(slopeOn(piece.from, piece.curve, 0.5), edge.heading) >= 0 ? 'along' : 'against';
|
|
164
|
+
}
|
|
165
|
+
return windingAt(other, middle) !== 0 ? 'inside' : 'outside';
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Whether the first path's piece belongs to the answer. */
|
|
169
|
+
function keepsFirst(keep, side) {
|
|
170
|
+
if (side === 'along')
|
|
171
|
+
return keep !== 'difference';
|
|
172
|
+
if (side === 'against')
|
|
173
|
+
return keep === 'difference';
|
|
174
|
+
if (keep === 'intersection')
|
|
175
|
+
return side === 'inside';
|
|
176
|
+
return side === 'outside';
|
|
177
|
+
}
|
|
178
|
+
/** Whether the second path's piece belongs to the answer. A shared stretch is
|
|
179
|
+
* never taken from here, since the first path's copy of it is already in. */
|
|
180
|
+
function keepsSecond(keep, side) {
|
|
181
|
+
if (side === 'along' || side === 'against')
|
|
182
|
+
return false;
|
|
183
|
+
if (keep === 'union')
|
|
184
|
+
return side === 'outside';
|
|
185
|
+
return side === 'inside';
|
|
132
186
|
}
|
|
133
187
|
function combine(first, second, keep, options) {
|
|
134
188
|
const tolerance = options.tolerance ?? TOLERANCE;
|
|
@@ -141,21 +195,17 @@ function combine(first, second, keep, options) {
|
|
|
141
195
|
const [leftCuts, rightCuts] = cutsBetween(left, right, tolerance);
|
|
142
196
|
const leftPieces = piecesOf(cutPath(left, leftCuts, { tolerance }));
|
|
143
197
|
const rightPieces = piecesOf(cutPath(right, rightCuts, { tolerance }));
|
|
144
|
-
const
|
|
145
|
-
const
|
|
198
|
+
const leftSide = sidesAgainst(leftPieces, flattenPath(right, { tolerance }), tolerance);
|
|
199
|
+
const rightSide = sidesAgainst(rightPieces, flattenPath(left, { tolerance }), tolerance);
|
|
146
200
|
const kept = [];
|
|
147
201
|
for (let at = 0; at < leftPieces.length; at++) {
|
|
148
|
-
if (
|
|
202
|
+
if (keepsFirst(keep, leftSide[at]))
|
|
149
203
|
kept.push(leftPieces[at]);
|
|
150
204
|
}
|
|
151
205
|
for (let at = 0; at < rightPieces.length; at++) {
|
|
152
|
-
if (keep
|
|
153
|
-
if (rightInside[at])
|
|
154
|
-
kept.push(reversed(rightPieces[at]));
|
|
206
|
+
if (!keepsSecond(keep, rightSide[at]))
|
|
155
207
|
continue;
|
|
156
|
-
|
|
157
|
-
if (rightInside[at] === (keep === 'intersection'))
|
|
158
|
-
kept.push(rightPieces[at]);
|
|
208
|
+
kept.push(keep === 'difference' ? reversed(rightPieces[at]) : rightPieces[at]);
|
|
159
209
|
}
|
|
160
210
|
return stitch(kept, tolerance);
|
|
161
211
|
}
|
package/dist/figure/inside.d.ts
CHANGED
|
@@ -30,6 +30,20 @@ export declare function flattenPath(path: Path, options?: FlattenOptions): Vec2[
|
|
|
30
30
|
/** How many times the loops wind round a point, counted along the ray heading
|
|
31
31
|
* in the positive x direction. */
|
|
32
32
|
export declare function windingAt(loops: readonly (readonly Vec2[])[], point: Vec2): number;
|
|
33
|
+
/** The nearest straight run of a flattening to a point: how far off it is, and
|
|
34
|
+
* which way that run goes. */
|
|
35
|
+
export interface Edge {
|
|
36
|
+
readonly gap: number;
|
|
37
|
+
readonly heading: Vec2;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Which edge of a flattening a point sits nearest, and which way it runs.
|
|
41
|
+
*
|
|
42
|
+
* This is what tells a piece lying along another path's edge from one merely
|
|
43
|
+
* near it, which the winding count cannot answer because a point on the edge
|
|
44
|
+
* itself is the one place the count has no answer for.
|
|
45
|
+
*/
|
|
46
|
+
export declare function nearestEdge(loops: readonly (readonly Vec2[])[], point: Vec2): Edge | null;
|
|
33
47
|
/**
|
|
34
48
|
* Whether a path holds a point, under the nonzero rule.
|
|
35
49
|
*
|
package/dist/figure/inside.js
CHANGED
|
@@ -18,6 +18,15 @@ const TOLERANCE = 1e-6;
|
|
|
18
18
|
/** How many times a piece may be halved, which a tolerance of zero would
|
|
19
19
|
* otherwise leave unbounded. */
|
|
20
20
|
const DEPTH = 24;
|
|
21
|
+
/**
|
|
22
|
+
* How many points a whole flattening may hold before it is refused.
|
|
23
|
+
*
|
|
24
|
+
* Halving is the only bound the depth gives, and sixteen million points for one
|
|
25
|
+
* piece is a machine out of memory rather than a fine flattening. A circle of
|
|
26
|
+
* radius 1 wants 4096 of these at a tolerance of a millionth, so the room here
|
|
27
|
+
* is a thousandfold.
|
|
28
|
+
*/
|
|
29
|
+
const POINTS = 1_000_000;
|
|
21
30
|
/** How far the two controls sit from the straight run between the ends.
|
|
22
31
|
*
|
|
23
32
|
* The curve itself stays within three quarters of this, so measuring the
|
|
@@ -37,6 +46,10 @@ function walk(from, curve, tolerance, depth, into) {
|
|
|
37
46
|
into.push(curve.to);
|
|
38
47
|
return;
|
|
39
48
|
}
|
|
49
|
+
if (into.length >= POINTS) {
|
|
50
|
+
throw new Error(`a tolerance of ${tolerance} asks for more than ${POINTS} straight runs to stand for one ` +
|
|
51
|
+
`curve, which is finer than this flattens`);
|
|
52
|
+
}
|
|
40
53
|
const a = vec2.lerp(from, curve.control1, 0.5);
|
|
41
54
|
const b = vec2.lerp(curve.control1, curve.control2, 0.5);
|
|
42
55
|
const c = vec2.lerp(curve.control2, curve.to, 0.5);
|
|
@@ -95,6 +108,29 @@ export function windingAt(loops, point) {
|
|
|
95
108
|
}
|
|
96
109
|
return winding;
|
|
97
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Which edge of a flattening a point sits nearest, and which way it runs.
|
|
113
|
+
*
|
|
114
|
+
* This is what tells a piece lying along another path's edge from one merely
|
|
115
|
+
* near it, which the winding count cannot answer because a point on the edge
|
|
116
|
+
* itself is the one place the count has no answer for.
|
|
117
|
+
*/
|
|
118
|
+
export function nearestEdge(loops, point) {
|
|
119
|
+
let nearest = null;
|
|
120
|
+
for (const loop of loops) {
|
|
121
|
+
for (let at = 1; at < loop.length; at++) {
|
|
122
|
+
const from = loop[at - 1];
|
|
123
|
+
const to = loop[at];
|
|
124
|
+
const run = vec2.sub(to, from);
|
|
125
|
+
const square = vec2.dot(run, run);
|
|
126
|
+
const along = square === 0 ? 0 : Math.min(1, Math.max(0, vec2.dot(vec2.sub(point, from), run) / square));
|
|
127
|
+
const gap = vec2.distance(point, vec2.add(from, vec2.scale(run, along)));
|
|
128
|
+
if (!nearest || gap < nearest.gap)
|
|
129
|
+
nearest = { gap, heading: run };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return nearest;
|
|
133
|
+
}
|
|
98
134
|
/**
|
|
99
135
|
* Whether a path holds a point, under the nonzero rule.
|
|
100
136
|
*
|
|
@@ -29,8 +29,8 @@ export interface CrossingOptions {
|
|
|
29
29
|
* Every place two cubics cross, as a fraction along each and the point.
|
|
30
30
|
*
|
|
31
31
|
* Each segment is given the point it starts from, since a segment carries where
|
|
32
|
-
* it ends and not where it began. Two curves
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* it ends and not where it began. Two curves covering the same stretch answer
|
|
33
|
+
* with the two ends of that stretch, so a caller reading the answer as places to
|
|
34
|
+
* cut at gets the stretch marked off rather than chopped into slivers.
|
|
35
35
|
*/
|
|
36
36
|
export declare function curveCrossings(fromFirst: Vec2, first: Cubic, fromSecond: Vec2, second: Cubic, options?: CrossingOptions): Crossing[];
|
package/dist/figure/intersect.js
CHANGED
|
@@ -16,10 +16,15 @@ import { vec2 } from '../values/vec2.js';
|
|
|
16
16
|
* meetings are told apart rather than how sharp one is, since Newton's method
|
|
17
17
|
* supplies the sharpness afterwards. */
|
|
18
18
|
const TOLERANCE = 1e-6;
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
19
|
+
/**
|
|
20
|
+
* How many pairs the search may make before it answers with where it had got to.
|
|
21
|
+
*
|
|
22
|
+
* It counts pairs made rather than pairs looked at, because each one looked at
|
|
23
|
+
* makes up to four more: counting the ones looked at leaves the pile of pairs
|
|
24
|
+
* waiting growing four times faster than it drains, and a tolerance small enough
|
|
25
|
+
* ran the machine out of memory before the count was ever reached.
|
|
26
|
+
*/
|
|
27
|
+
const BUDGET = 200_000;
|
|
23
28
|
/** How deep the halving goes, which a tolerance of zero would otherwise leave
|
|
24
29
|
* unbounded. */
|
|
25
30
|
const DEPTH = 60;
|
|
@@ -117,8 +122,8 @@ function pointAt(hull, along) {
|
|
|
117
122
|
const d = along * along * along;
|
|
118
123
|
return vec2(a * hull[0].x + b * hull[1].x + c * hull[2].x + d * hull[3].x, a * hull[0].y + b * hull[1].y + c * hull[2].y + d * hull[3].y);
|
|
119
124
|
}
|
|
120
|
-
/** Which way the curve is heading,
|
|
121
|
-
*
|
|
125
|
+
/** Which way the curve is heading, read off the four points the search already
|
|
126
|
+
* holds rather than off a piece, so following a pair down allocates nothing. */
|
|
122
127
|
function slopeAt(hull, along) {
|
|
123
128
|
const u = 1 - along;
|
|
124
129
|
const a = 3 * u * u;
|
|
@@ -215,18 +220,121 @@ function clustered(hits) {
|
|
|
215
220
|
point: vec2.scale(group.sum, 1 / group.count),
|
|
216
221
|
}));
|
|
217
222
|
}
|
|
223
|
+
/** How many places the coarse sweep looks at before it decides which part of a
|
|
224
|
+
* curve a point is nearest. */
|
|
225
|
+
const SWEEP = 32;
|
|
226
|
+
/** How hard the curve is turning at a place, which Newton needs because the
|
|
227
|
+
* nearest point moves as the curve bends away from it. */
|
|
228
|
+
function bendAt(hull, along) {
|
|
229
|
+
const u = 1 - along;
|
|
230
|
+
return vec2(6 * (u * (hull[2].x - 2 * hull[1].x + hull[0].x) + along * (hull[3].x - 2 * hull[2].x + hull[1].x)), 6 * (u * (hull[2].y - 2 * hull[1].y + hull[0].y) + along * (hull[3].y - 2 * hull[2].y + hull[1].y)));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Where on a curve a point sits nearest, as a fraction along it, and how far
|
|
234
|
+
* away it is there.
|
|
235
|
+
*
|
|
236
|
+
* A coarse sweep picks which part of the curve to believe, and Newton's method
|
|
237
|
+
* on the distance finishes it, since the nearest point on a cubic is a fifth
|
|
238
|
+
* degree root and solving one is more than this needs. The sweep gives up before
|
|
239
|
+
* Newton when its best is further off than the tolerance plus the most one step
|
|
240
|
+
* of the sweep can be hiding, since no refining brings it under from there.
|
|
241
|
+
*/
|
|
242
|
+
function nearestPlace(hull, point, tolerance) {
|
|
243
|
+
let along = 0;
|
|
244
|
+
let gap = Infinity;
|
|
245
|
+
for (let step = 0; step <= SWEEP; step++) {
|
|
246
|
+
const at = step / SWEEP;
|
|
247
|
+
const away = vec2.distance(pointAt(hull, at), point);
|
|
248
|
+
if (away < gap) {
|
|
249
|
+
gap = away;
|
|
250
|
+
along = at;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const reach = spread(boxOf(hull)) * 2;
|
|
254
|
+
if (gap > tolerance + reach / SWEEP)
|
|
255
|
+
return { along, gap };
|
|
256
|
+
for (let step = 0; step < 12; step++) {
|
|
257
|
+
const away = vec2.sub(pointAt(hull, along), point);
|
|
258
|
+
const heading = slopeAt(hull, along);
|
|
259
|
+
const turning = vec2.dot(heading, heading) + vec2.dot(away, bendAt(hull, along));
|
|
260
|
+
if (Math.abs(turning) < PARALLEL)
|
|
261
|
+
break;
|
|
262
|
+
const next = held(along - vec2.dot(away, heading) / turning);
|
|
263
|
+
const closer = vec2.distance(pointAt(hull, next), point);
|
|
264
|
+
if (!(closer < gap))
|
|
265
|
+
break;
|
|
266
|
+
along = next;
|
|
267
|
+
gap = closer;
|
|
268
|
+
}
|
|
269
|
+
return { along, gap };
|
|
270
|
+
}
|
|
271
|
+
/** How many places along a shared stretch are checked to still be on the other
|
|
272
|
+
* curve before the stretch is believed. */
|
|
273
|
+
const ALONG_SHARED = 12;
|
|
274
|
+
/**
|
|
275
|
+
* The two ends of the stretch two curves cover together, when they cover one.
|
|
276
|
+
*
|
|
277
|
+
* Halving into a stretch like that answers it as a spray of meetings, because
|
|
278
|
+
* every pair of small pieces along it overlaps and the budget runs out before
|
|
279
|
+
* the run is walked. So the stretch is found first, from the ends of each curve
|
|
280
|
+
* that lie on the other, and answered by where it starts and where it ends.
|
|
281
|
+
*
|
|
282
|
+
* Two curves meeting at a single point are not a stretch and are left to the
|
|
283
|
+
* halving, which places one meeting more sharply than a sweep can.
|
|
284
|
+
*/
|
|
285
|
+
function sharedStretch(first, second, tolerance) {
|
|
286
|
+
const ends = [];
|
|
287
|
+
for (const along of [0, 1]) {
|
|
288
|
+
const place = nearestPlace(second, pointAt(first, along), tolerance);
|
|
289
|
+
if (place.gap <= tolerance)
|
|
290
|
+
ends.push({ first: along, second: place.along });
|
|
291
|
+
}
|
|
292
|
+
for (const along of [0, 1]) {
|
|
293
|
+
const place = nearestPlace(first, pointAt(second, along), tolerance);
|
|
294
|
+
if (place.gap <= tolerance)
|
|
295
|
+
ends.push({ first: place.along, second: along });
|
|
296
|
+
}
|
|
297
|
+
if (ends.length < 2)
|
|
298
|
+
return null;
|
|
299
|
+
let low = ends[0];
|
|
300
|
+
let high = ends[0];
|
|
301
|
+
for (const end of ends) {
|
|
302
|
+
if (end.first < low.first)
|
|
303
|
+
low = end;
|
|
304
|
+
if (end.first > high.first)
|
|
305
|
+
high = end;
|
|
306
|
+
}
|
|
307
|
+
const from = pointAt(first, low.first);
|
|
308
|
+
const to = pointAt(first, high.first);
|
|
309
|
+
if (vec2.distance(from, to) <= tolerance)
|
|
310
|
+
return null;
|
|
311
|
+
for (let step = 1; step < ALONG_SHARED; step++) {
|
|
312
|
+
const along = low.first + ((high.first - low.first) * step) / ALONG_SHARED;
|
|
313
|
+
if (nearestPlace(second, pointAt(first, along), tolerance).gap > tolerance)
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
return [
|
|
317
|
+
{ alongFirst: low.first, alongSecond: low.second, point: from },
|
|
318
|
+
{ alongFirst: high.first, alongSecond: high.second, point: to },
|
|
319
|
+
];
|
|
320
|
+
}
|
|
218
321
|
/**
|
|
219
322
|
* Every place two cubics cross, as a fraction along each and the point.
|
|
220
323
|
*
|
|
221
324
|
* Each segment is given the point it starts from, since a segment carries where
|
|
222
|
-
* it ends and not where it began. Two curves
|
|
223
|
-
*
|
|
224
|
-
*
|
|
325
|
+
* it ends and not where it began. Two curves covering the same stretch answer
|
|
326
|
+
* with the two ends of that stretch, so a caller reading the answer as places to
|
|
327
|
+
* cut at gets the stretch marked off rather than chopped into slivers.
|
|
225
328
|
*/
|
|
226
329
|
export function curveCrossings(fromFirst, first, fromSecond, second, options = {}) {
|
|
227
330
|
const tolerance = options.tolerance ?? TOLERANCE;
|
|
228
331
|
const firstHull = hullOf(fromFirst, first);
|
|
229
332
|
const secondHull = hullOf(fromSecond, second);
|
|
333
|
+
if (apart(boxOf(firstHull), boxOf(secondHull), tolerance))
|
|
334
|
+
return [];
|
|
335
|
+
const shared = sharedStretch(firstHull, secondHull, tolerance);
|
|
336
|
+
if (shared)
|
|
337
|
+
return shared;
|
|
230
338
|
const stack = [
|
|
231
339
|
{
|
|
232
340
|
first: firstHull,
|
|
@@ -239,7 +347,7 @@ export function curveCrossings(fromFirst, first, fromSecond, second, options = {
|
|
|
239
347
|
},
|
|
240
348
|
];
|
|
241
349
|
const hits = [];
|
|
242
|
-
let
|
|
350
|
+
let made = 1;
|
|
243
351
|
const record = (pair) => {
|
|
244
352
|
const alongFirst = (pair.firstLow + pair.firstHigh) / 2;
|
|
245
353
|
const alongSecond = (pair.secondLow + pair.secondHigh) / 2;
|
|
@@ -255,12 +363,11 @@ export function curveCrossings(fromFirst, first, fromSecond, second, options = {
|
|
|
255
363
|
};
|
|
256
364
|
while (stack.length > 0) {
|
|
257
365
|
const pair = stack.pop();
|
|
258
|
-
opened += 1;
|
|
259
366
|
const firstBox = boxOf(pair.first);
|
|
260
367
|
const secondBox = boxOf(pair.second);
|
|
261
368
|
if (apart(firstBox, secondBox, tolerance))
|
|
262
369
|
continue;
|
|
263
|
-
if (
|
|
370
|
+
if (made >= BUDGET) {
|
|
264
371
|
record(pair);
|
|
265
372
|
break;
|
|
266
373
|
}
|
|
@@ -291,6 +398,7 @@ export function curveCrossings(fromFirst, first, fromSecond, second, options = {
|
|
|
291
398
|
high: side === 0 ? secondMid : pair.secondHigh,
|
|
292
399
|
}))
|
|
293
400
|
: [{ hull: pair.second, low: pair.secondLow, high: pair.secondHigh }];
|
|
401
|
+
made += firstParts.length * secondParts.length;
|
|
294
402
|
for (const left of firstParts) {
|
|
295
403
|
for (const right of secondParts) {
|
|
296
404
|
stack.push({
|
package/dist/figure/path.d.ts
CHANGED
|
@@ -48,6 +48,9 @@ export declare function arc(centre: Vec2, radius: number, fromAngle: number, toA
|
|
|
48
48
|
/** A point on a cubic, with the segment's own start passed in because a segment
|
|
49
49
|
* carries where it ends and not where it began. */
|
|
50
50
|
export declare function pointOn(from: Vec2, curve: Cubic, along: number): Vec2;
|
|
51
|
+
/** Which way a piece is heading at a fraction along it, which is the derivative
|
|
52
|
+
* of a cubic and so a quadratic over the gaps between neighbouring points. */
|
|
53
|
+
export declare function slopeOn(from: Vec2, curve: Cubic, along: number): Vec2;
|
|
51
54
|
/**
|
|
52
55
|
* One piece cut into two at a fraction, both pieces drawing what the whole
|
|
53
56
|
* drew, by de Casteljau's construction.
|
package/dist/figure/path.js
CHANGED
|
@@ -121,6 +121,15 @@ export function pointOn(from, curve, along) {
|
|
|
121
121
|
const d = along * along * along;
|
|
122
122
|
return vec2(a * from.x + b * curve.control1.x + c * curve.control2.x + d * curve.to.x, a * from.y + b * curve.control1.y + c * curve.control2.y + d * curve.to.y);
|
|
123
123
|
}
|
|
124
|
+
/** Which way a piece is heading at a fraction along it, which is the derivative
|
|
125
|
+
* of a cubic and so a quadratic over the gaps between neighbouring points. */
|
|
126
|
+
export function slopeOn(from, curve, along) {
|
|
127
|
+
const u = 1 - along;
|
|
128
|
+
const a = 3 * u * u;
|
|
129
|
+
const b = 6 * u * along;
|
|
130
|
+
const c = 3 * along * along;
|
|
131
|
+
return vec2(a * (curve.control1.x - from.x) + b * (curve.control2.x - curve.control1.x) + c * (curve.to.x - curve.control2.x), a * (curve.control1.y - from.y) + b * (curve.control2.y - curve.control1.y) + c * (curve.to.y - curve.control2.y));
|
|
132
|
+
}
|
|
124
133
|
/**
|
|
125
134
|
* One piece cut into two at a fraction, both pieces drawing what the whole
|
|
126
135
|
* drew, by de Casteljau's construction.
|
package/dist/index.d.ts
CHANGED
|
@@ -20,14 +20,14 @@ export { mat3 } from './values/mat3.js';
|
|
|
20
20
|
export type { Mat3 } from './values/mat3.js';
|
|
21
21
|
export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
|
|
22
22
|
export type { Key, Track, TrackValue, Tracks } from './timing/track.js';
|
|
23
|
-
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, splitCurve, straight, transformPath } from './figure/path.js';
|
|
23
|
+
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
|
|
24
24
|
export type { Cubic, Path, Subpath } from './figure/path.js';
|
|
25
25
|
export { pathFromData } from './figure/path-data.js';
|
|
26
26
|
export { areaOf } from './figure/area.js';
|
|
27
27
|
export { differenceOf, intersectionOf, unionOf } from './figure/boolean.js';
|
|
28
28
|
export type { BooleanOptions } from './figure/boolean.js';
|
|
29
|
-
export { containsPoint, flattenPath, windingAt } from './figure/inside.js';
|
|
30
|
-
export type { FlattenOptions } from './figure/inside.js';
|
|
29
|
+
export { containsPoint, flattenPath, nearestEdge, windingAt } from './figure/inside.js';
|
|
30
|
+
export type { Edge, FlattenOptions } from './figure/inside.js';
|
|
31
31
|
export { cutPath } from './figure/cut.js';
|
|
32
32
|
export type { Cut, CutOptions } from './figure/cut.js';
|
|
33
33
|
export { curveCrossings } from './figure/intersect.js';
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,11 @@ export { vec3 } from './values/vec3.js';
|
|
|
14
14
|
export { interval } from './values/interval.js';
|
|
15
15
|
export { mat3 } from './values/mat3.js';
|
|
16
16
|
export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
|
|
17
|
-
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, splitCurve, straight, transformPath } from './figure/path.js';
|
|
17
|
+
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
|
|
18
18
|
export { pathFromData } from './figure/path-data.js';
|
|
19
19
|
export { areaOf } from './figure/area.js';
|
|
20
20
|
export { differenceOf, intersectionOf, unionOf } from './figure/boolean.js';
|
|
21
|
-
export { containsPoint, flattenPath, windingAt } from './figure/inside.js';
|
|
21
|
+
export { containsPoint, flattenPath, nearestEdge, windingAt } from './figure/inside.js';
|
|
22
22
|
export { cutPath } from './figure/cut.js';
|
|
23
23
|
export { curveCrossings } from './figure/intersect.js';
|
|
24
24
|
export { byAspect, fractionOf, matchingAspect, resolveExtent, viewMatrix } from './figure/extent.js';
|
package/package.json
CHANGED