@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
package/dist/booleans/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { orientBoundaries, parseContours, contoursIntersect, assembleRegions, signedArea, negligibleArea, regionArea, toPath, parseSubpaths, writePath, signChanges, sideCoeffs, evalSeg, extentOf, keepCorners, windingAt, reverseContour, sampleContour, subSeg, endSide } from '../chunk-4Y67BZL5.js';
|
|
2
|
+
import '../chunk-CE7IY7TB.js';
|
|
3
|
+
import { resolveCorners, DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, pointInPolygon } from '../chunk-EAKE3N7W.js';
|
|
4
|
+
import '../chunk-755Y4YZS.js';
|
|
5
|
+
import { PATH_Z, PATH_Q, elevateQuadraticToCubic, PATH_C, PATH_L, PATH_M } from '../chunk-B53BERKG.js';
|
|
5
6
|
import polygonClipping from 'polygon-clipping';
|
|
6
7
|
|
|
7
8
|
// src/booleans/adapter.ts
|
|
8
|
-
function pathToMultiPolygon(
|
|
9
|
+
function pathToMultiPolygon(given, opts = {}) {
|
|
10
|
+
const path = resolveCorners(given);
|
|
9
11
|
if (path.kind === "rect") {
|
|
10
12
|
const { x, y, width, height } = path;
|
|
11
13
|
const ring = [
|
|
@@ -214,27 +216,297 @@ function multiPolygonToPath(mp) {
|
|
|
214
216
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
// src/booleans/arrangement.ts
|
|
220
|
+
var VertexPool = class {
|
|
221
|
+
constructor(eps) {
|
|
222
|
+
this.eps = eps;
|
|
223
|
+
this.cell = eps * 2;
|
|
224
|
+
}
|
|
225
|
+
eps;
|
|
226
|
+
xs = [];
|
|
227
|
+
ys = [];
|
|
228
|
+
cells = /* @__PURE__ */ new Map();
|
|
229
|
+
cell;
|
|
230
|
+
id(x, y) {
|
|
231
|
+
const cx = Math.floor(x / this.cell), cy = Math.floor(y / this.cell);
|
|
232
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
233
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
234
|
+
for (const v2 of this.cells.get(`${cx + dx},${cy + dy}`) ?? []) {
|
|
235
|
+
if (Math.abs(this.xs[v2] - x) <= this.eps && Math.abs(this.ys[v2] - y) <= this.eps) return v2;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const v = this.xs.length;
|
|
240
|
+
this.xs.push(x);
|
|
241
|
+
this.ys.push(y);
|
|
242
|
+
const key = `${cx},${cy}`;
|
|
243
|
+
const bucket = this.cells.get(key);
|
|
244
|
+
if (bucket) bucket.push(v);
|
|
245
|
+
else this.cells.set(key, [v]);
|
|
246
|
+
return v;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function segmentsOf(inputs) {
|
|
250
|
+
const segs = [];
|
|
251
|
+
let extent = 0;
|
|
252
|
+
inputs.forEach((mp, input) => {
|
|
253
|
+
for (const polygon of mp) {
|
|
254
|
+
for (const ring of polygon) {
|
|
255
|
+
for (let k = 0; k + 1 < ring.length; k++) {
|
|
256
|
+
const [ax, ay] = ring[k];
|
|
257
|
+
const [bx, by] = ring[k + 1];
|
|
258
|
+
if (ax === bx && ay === by) continue;
|
|
259
|
+
extent = Math.max(extent, Math.abs(ax), Math.abs(ay), Math.abs(bx), Math.abs(by));
|
|
260
|
+
segs.push({
|
|
261
|
+
ax,
|
|
262
|
+
ay,
|
|
263
|
+
bx,
|
|
264
|
+
by,
|
|
265
|
+
input,
|
|
266
|
+
minX: Math.min(ax, bx),
|
|
267
|
+
maxX: Math.max(ax, bx),
|
|
268
|
+
minY: Math.min(ay, by),
|
|
269
|
+
maxY: Math.max(ay, by),
|
|
270
|
+
cuts: []
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return { segs, extent };
|
|
277
|
+
}
|
|
278
|
+
function cutAtPoint(seg, px, py, eps) {
|
|
279
|
+
const rx = seg.bx - seg.ax, ry = seg.by - seg.ay;
|
|
280
|
+
const len2 = rx * rx + ry * ry;
|
|
281
|
+
const t = ((px - seg.ax) * rx + (py - seg.ay) * ry) / len2;
|
|
282
|
+
if (t <= 0 || t >= 1) return;
|
|
283
|
+
const ex = seg.ax + t * rx - px, ey = seg.ay + t * ry - py;
|
|
284
|
+
if (ex * ex + ey * ey <= eps * eps) seg.cuts.push({ t, x: px, y: py });
|
|
285
|
+
}
|
|
286
|
+
function cutPair(s, o, eps) {
|
|
287
|
+
const rx = s.bx - s.ax, ry = s.by - s.ay;
|
|
288
|
+
const sx = o.bx - o.ax, sy = o.by - o.ay;
|
|
289
|
+
const d = rx * sy - ry * sx;
|
|
290
|
+
const lenR = Math.hypot(rx, ry), lenS = Math.hypot(sx, sy);
|
|
291
|
+
if (Math.abs(d) > 1e-12 * lenR * lenS) {
|
|
292
|
+
const qx = o.ax - s.ax, qy = o.ay - s.ay;
|
|
293
|
+
const t = (qx * sy - qy * sx) / d;
|
|
294
|
+
const u = (qx * ry - qy * rx) / d;
|
|
295
|
+
const tolT = eps / lenR, tolU = eps / lenS;
|
|
296
|
+
if (t > -tolT && t < 1 + tolT && u > -tolU && u < 1 + tolU) {
|
|
297
|
+
const tc = Math.min(1, Math.max(0, t));
|
|
298
|
+
const x = s.ax + tc * rx, y = s.ay + tc * ry;
|
|
299
|
+
s.cuts.push({ t: tc, x, y });
|
|
300
|
+
o.cuts.push({ t: Math.min(1, Math.max(0, u)), x, y });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
cutAtPoint(s, o.ax, o.ay, eps);
|
|
304
|
+
cutAtPoint(s, o.bx, o.by, eps);
|
|
305
|
+
cutAtPoint(o, s.ax, s.ay, eps);
|
|
306
|
+
cutAtPoint(o, s.bx, s.by, eps);
|
|
307
|
+
}
|
|
308
|
+
function cutAll(segs, eps) {
|
|
309
|
+
const order = segs.map((_, i) => i).sort((i, j) => segs[i].minX - segs[j].minX);
|
|
310
|
+
for (let a = 0; a < order.length; a++) {
|
|
311
|
+
const s = segs[order[a]];
|
|
312
|
+
for (let b = a + 1; b < order.length; b++) {
|
|
313
|
+
const o = segs[order[b]];
|
|
314
|
+
if (o.minX > s.maxX + eps) break;
|
|
315
|
+
if (o.minY > s.maxY + eps || o.maxY < s.minY - eps) continue;
|
|
316
|
+
cutPair(s, o, eps);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function linkFaces(xs, ys, origin) {
|
|
321
|
+
const out = xs.map(() => []);
|
|
322
|
+
const angle = [];
|
|
323
|
+
for (let h = 0; h < origin.length; h++) {
|
|
324
|
+
const u = origin[h], v = origin[h ^ 1];
|
|
325
|
+
out[u].push(h);
|
|
326
|
+
angle[h] = Math.atan2(ys[v] - ys[u], xs[v] - xs[u]);
|
|
327
|
+
}
|
|
328
|
+
const slot = [];
|
|
329
|
+
for (const list of out) {
|
|
330
|
+
list.sort((a, b) => angle[a] - angle[b]);
|
|
331
|
+
list.forEach((h, k) => {
|
|
332
|
+
slot[h] = k;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return origin.map((_, h) => {
|
|
336
|
+
const list = out[origin[h ^ 1]];
|
|
337
|
+
return list[(slot[h ^ 1] - 1 + list.length) % list.length];
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function buildArrangement(inputs) {
|
|
341
|
+
const { segs, extent } = segmentsOf(inputs);
|
|
342
|
+
const eps = 1e-9 * Math.max(1, extent);
|
|
343
|
+
cutAll(segs, eps);
|
|
344
|
+
const pool = new VertexPool(eps);
|
|
345
|
+
const edges = /* @__PURE__ */ new Map();
|
|
346
|
+
for (const seg of segs) {
|
|
347
|
+
const points = [{ t: 0, x: seg.ax, y: seg.ay }, ...seg.cuts, { t: 1, x: seg.bx, y: seg.by }].sort((a, b) => a.t - b.t);
|
|
348
|
+
const bit = 1n << BigInt(seg.input);
|
|
349
|
+
let prev = pool.id(points[0].x, points[0].y);
|
|
350
|
+
for (let k = 1; k < points.length; k++) {
|
|
351
|
+
const id = pool.id(points[k].x, points[k].y);
|
|
352
|
+
if (id === prev) continue;
|
|
353
|
+
const u = Math.min(prev, id), v = Math.max(prev, id);
|
|
354
|
+
const key = `${u},${v}`;
|
|
355
|
+
const edge = edges.get(key);
|
|
356
|
+
if (edge) edge.owners ^= bit;
|
|
357
|
+
else edges.set(key, { u, v, owners: bit });
|
|
358
|
+
prev = id;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const origin = [];
|
|
362
|
+
const owners = [];
|
|
363
|
+
for (const { u, v, owners: o } of edges.values()) {
|
|
364
|
+
if (o === 0n) continue;
|
|
365
|
+
origin.push(u, v);
|
|
366
|
+
owners.push(o);
|
|
367
|
+
}
|
|
368
|
+
return { xs: pool.xs, ys: pool.ys, origin, next: linkFaces(pool.xs, pool.ys, origin), owners };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/booleans/arrangementFaces.ts
|
|
372
|
+
function components(arr) {
|
|
373
|
+
const parent = arr.xs.map((_, i) => i);
|
|
374
|
+
const find = (v) => {
|
|
375
|
+
while (parent[v] !== v) {
|
|
376
|
+
parent[v] = parent[parent[v]];
|
|
377
|
+
v = parent[v];
|
|
378
|
+
}
|
|
379
|
+
return v;
|
|
380
|
+
};
|
|
381
|
+
for (let h = 0; h < arr.origin.length; h += 2) parent[find(arr.origin[h])] = find(arr.origin[h + 1]);
|
|
382
|
+
return arr.xs.map((_, v) => find(v));
|
|
383
|
+
}
|
|
384
|
+
function traceCycles(arr, compOf) {
|
|
385
|
+
const cycleOf = new Array(arr.origin.length).fill(-1);
|
|
386
|
+
const cycles = [];
|
|
387
|
+
for (let start = 0; start < arr.origin.length; start++) {
|
|
388
|
+
if (cycleOf[start] !== -1) continue;
|
|
389
|
+
const c = { edges: [], area: 0, component: compOf[arr.origin[start]], minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };
|
|
390
|
+
let h = start;
|
|
391
|
+
do {
|
|
392
|
+
cycleOf[h] = cycles.length;
|
|
393
|
+
c.edges.push(h);
|
|
394
|
+
const x0 = arr.xs[arr.origin[h]], y0 = arr.ys[arr.origin[h]];
|
|
395
|
+
const x1 = arr.xs[arr.origin[h ^ 1]], y1 = arr.ys[arr.origin[h ^ 1]];
|
|
396
|
+
c.area += (x0 * y1 - x1 * y0) / 2;
|
|
397
|
+
c.minX = Math.min(c.minX, x0);
|
|
398
|
+
c.maxX = Math.max(c.maxX, x0);
|
|
399
|
+
c.minY = Math.min(c.minY, y0);
|
|
400
|
+
c.maxY = Math.max(c.maxY, y0);
|
|
401
|
+
h = arr.next[h];
|
|
402
|
+
} while (h !== start);
|
|
403
|
+
cycles.push(c);
|
|
404
|
+
}
|
|
405
|
+
return { cycles, cycleOf };
|
|
406
|
+
}
|
|
407
|
+
function coverAt(arr, compOf, component, px, py) {
|
|
408
|
+
let mask = 0n;
|
|
409
|
+
for (let h = 0; h < arr.origin.length; h += 2) {
|
|
410
|
+
const u = arr.origin[h], v = arr.origin[h + 1];
|
|
411
|
+
if (compOf[u] === component) continue;
|
|
412
|
+
const y0 = arr.ys[u], y1 = arr.ys[v];
|
|
413
|
+
if (y0 > py === y1 > py) continue;
|
|
414
|
+
const x = arr.xs[u] + (py - y0) / (y1 - y0) * (arr.xs[v] - arr.xs[u]);
|
|
415
|
+
if (x < px) mask ^= arr.owners[h >> 1];
|
|
416
|
+
}
|
|
417
|
+
return mask;
|
|
418
|
+
}
|
|
419
|
+
function ringOf(arr, cycle) {
|
|
420
|
+
const ring = cycle.edges.map((h) => [arr.xs[arr.origin[h]], arr.ys[arr.origin[h]]]);
|
|
421
|
+
ring.push([ring[0][0], ring[0][1]]);
|
|
422
|
+
return ring;
|
|
423
|
+
}
|
|
424
|
+
function ringHolds(ring, px, py) {
|
|
425
|
+
let inside = false;
|
|
426
|
+
for (let k = 0; k + 1 < ring.length; k++) {
|
|
427
|
+
const [x0, y0] = ring[k];
|
|
428
|
+
const [x1, y1] = ring[k + 1];
|
|
429
|
+
if (y0 > py !== y1 > py && px < x0 + (py - y0) / (y1 - y0) * (x1 - x0)) inside = !inside;
|
|
430
|
+
}
|
|
431
|
+
return inside;
|
|
432
|
+
}
|
|
433
|
+
function popcount(mask) {
|
|
434
|
+
let c = 0;
|
|
435
|
+
for (let m = mask; m; m &= m - 1n) c++;
|
|
436
|
+
return c;
|
|
437
|
+
}
|
|
438
|
+
function arrangementFaces(inputs) {
|
|
439
|
+
const arr = buildArrangement(inputs);
|
|
440
|
+
const compOf = components(arr);
|
|
441
|
+
const { cycles, cycleOf } = traceCycles(arr, compOf);
|
|
442
|
+
const outerOf = /* @__PURE__ */ new Map();
|
|
443
|
+
cycles.forEach((c, i) => {
|
|
444
|
+
const best = outerOf.get(c.component);
|
|
445
|
+
if (best === void 0 || c.area < cycles[best].area) outerOf.set(c.component, i);
|
|
446
|
+
});
|
|
447
|
+
const label = new Array(cycles.length);
|
|
448
|
+
for (const [component, outer] of outerOf) {
|
|
449
|
+
const v = arr.origin[cycles[outer].edges[0]];
|
|
450
|
+
label[outer] = coverAt(arr, compOf, component, arr.xs[v], arr.ys[v]);
|
|
451
|
+
const queue = [outer];
|
|
452
|
+
while (queue.length > 0) {
|
|
453
|
+
const f = queue.pop();
|
|
454
|
+
for (const h of cycles[f].edges) {
|
|
455
|
+
const g = cycleOf[h ^ 1];
|
|
456
|
+
if (label[g] !== void 0) continue;
|
|
457
|
+
label[g] = label[f] ^ arr.owners[h >> 1];
|
|
458
|
+
queue.push(g);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
let extent = 1;
|
|
463
|
+
for (let v = 0; v < arr.xs.length; v++) extent = Math.max(extent, Math.abs(arr.xs[v]), Math.abs(arr.ys[v]));
|
|
464
|
+
const minArea = 1e-12 * extent * extent;
|
|
465
|
+
const faces2 = /* @__PURE__ */ new Map();
|
|
466
|
+
cycles.forEach((c, i) => {
|
|
467
|
+
if (c.area > minArea && label[i]) faces2.set(i, { mask: label[i], polygon: [ringOf(arr, c)] });
|
|
468
|
+
});
|
|
469
|
+
for (const [component, outer] of outerOf) {
|
|
470
|
+
const v = arr.origin[cycles[outer].edges[0]];
|
|
471
|
+
const px = arr.xs[v], py = arr.ys[v];
|
|
472
|
+
let host = -1;
|
|
473
|
+
cycles.forEach((c, i) => {
|
|
474
|
+
if (c.component === component || c.area <= minArea) return;
|
|
475
|
+
if (px < c.minX || px > c.maxX || py < c.minY || py > c.maxY) return;
|
|
476
|
+
if (host !== -1 && cycles[host].area <= c.area) return;
|
|
477
|
+
if (ringHolds(ringOf(arr, c), px, py)) host = i;
|
|
478
|
+
});
|
|
479
|
+
faces2.get(host)?.polygon.push(ringOf(arr, cycles[outer]));
|
|
480
|
+
}
|
|
481
|
+
const left = (f) => Math.min(...f.polygon[0].map((p) => p[0]));
|
|
482
|
+
const low = (f) => Math.min(...f.polygon[0].map((p) => p[1]));
|
|
483
|
+
return [...faces2.values()].sort((a, b) => popcount(a.mask) - popcount(b.mask) || (a.mask < b.mask ? -1 : a.mask > b.mask ? 1 : 0) || left(a) - left(b) || low(a) - low(b));
|
|
484
|
+
}
|
|
485
|
+
|
|
217
486
|
// src/booleans/ops.ts
|
|
487
|
+
function toPath2(mp, sources) {
|
|
488
|
+
return keepCorners(sources, multiPolygonToPath(mp));
|
|
489
|
+
}
|
|
218
490
|
function pathUnion(...paths) {
|
|
219
491
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
220
492
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
221
493
|
const [head, ...rest] = mps;
|
|
222
|
-
return
|
|
494
|
+
return toPath2(polygonClipping.union(head, ...rest), paths);
|
|
223
495
|
}
|
|
224
496
|
function pathIntersect(...paths) {
|
|
225
497
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
226
498
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
227
499
|
const [head, ...rest] = mps;
|
|
228
|
-
return
|
|
500
|
+
return toPath2(polygonClipping.intersection(head, ...rest), paths);
|
|
229
501
|
}
|
|
230
502
|
function pathSubtract(a, b) {
|
|
231
|
-
return
|
|
503
|
+
return toPath2(polygonClipping.difference(pathToMultiPolygon(a), pathToMultiPolygon(b)), [a, b]);
|
|
232
504
|
}
|
|
233
505
|
function pathExclude(...paths) {
|
|
234
506
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
235
507
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
236
508
|
const [head, ...rest] = mps;
|
|
237
|
-
return
|
|
509
|
+
return toPath2(polygonClipping.xor(head, ...rest), paths);
|
|
238
510
|
}
|
|
239
511
|
function pathDivide(...paths) {
|
|
240
512
|
return pathDividePieces(...paths).map((p) => p.path);
|
|
@@ -242,37 +514,29 @@ function pathDivide(...paths) {
|
|
|
242
514
|
function pathCrop(...paths) {
|
|
243
515
|
return pathCropPieces(...paths).map((p) => p.path);
|
|
244
516
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const n = mps.length;
|
|
252
|
-
const out = [];
|
|
253
|
-
const subsets = [];
|
|
254
|
-
for (let mask = 1; mask < 1 << n; mask++) subsets.push(mask);
|
|
255
|
-
subsets.sort((a, b) => popcount(a) - popcount(b) || a - b);
|
|
256
|
-
for (const mask of subsets) {
|
|
257
|
-
const inside = [];
|
|
258
|
-
const outside = [];
|
|
517
|
+
function faces(paths) {
|
|
518
|
+
const clean = paths.map((p) => {
|
|
519
|
+
const mp = pathToMultiPolygon(p);
|
|
520
|
+
return mp.length === 0 ? mp : polygonClipping.union(mp);
|
|
521
|
+
});
|
|
522
|
+
return arrangementFaces(clean).map(({ mask, polygon }) => {
|
|
259
523
|
const sources = [];
|
|
260
|
-
for (let i = 0; i <
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (region.length === 0) continue;
|
|
272
|
-
}
|
|
273
|
-
out.push({ path: multiPolygonToPath(region), sources });
|
|
524
|
+
for (let i = 0; i < paths.length; i++) if (mask & 1n << BigInt(i)) sources.push(i);
|
|
525
|
+
return { polygon, sources };
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function pathDividePieces(...paths) {
|
|
529
|
+
const regions = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const { polygon, sources } of faces(paths)) {
|
|
531
|
+
const key = sources.join(",");
|
|
532
|
+
const region = regions.get(key);
|
|
533
|
+
if (region) region.polygons.push(polygon);
|
|
534
|
+
else regions.set(key, { polygons: [polygon], sources });
|
|
274
535
|
}
|
|
275
|
-
return
|
|
536
|
+
return [...regions.values()].map(({ polygons, sources }) => ({ path: toPath2(polygons, paths), sources }));
|
|
537
|
+
}
|
|
538
|
+
function pathFacePieces(...paths) {
|
|
539
|
+
return faces(paths).map(({ polygon, sources }) => ({ path: toPath2([polygon], paths), sources }));
|
|
276
540
|
}
|
|
277
541
|
function pathTrim(...paths) {
|
|
278
542
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
@@ -280,7 +544,7 @@ function pathTrim(...paths) {
|
|
|
280
544
|
let cover = null;
|
|
281
545
|
for (let i = mps.length - 1; i >= 0; i--) {
|
|
282
546
|
const shown = cover ? polygonClipping.difference(mps[i], cover) : mps[i];
|
|
283
|
-
if (shown.length > 0) out.unshift({ path:
|
|
547
|
+
if (shown.length > 0) out.unshift({ path: toPath2(shown, paths), sources: [i] });
|
|
284
548
|
cover = cover ? polygonClipping.union(cover, mps[i]) : mps[i];
|
|
285
549
|
}
|
|
286
550
|
return out;
|
|
@@ -299,14 +563,6 @@ function pathMerge(paths, keyOf) {
|
|
|
299
563
|
sources: g.map((p) => p.sources[0])
|
|
300
564
|
}).sort((a, b) => top(a) - top(b));
|
|
301
565
|
}
|
|
302
|
-
function popcount(n) {
|
|
303
|
-
let c = 0;
|
|
304
|
-
while (n) {
|
|
305
|
-
n &= n - 1;
|
|
306
|
-
c++;
|
|
307
|
-
}
|
|
308
|
-
return c;
|
|
309
|
-
}
|
|
310
566
|
function pathCropPieces(...paths) {
|
|
311
567
|
if (paths.length < 2) return [];
|
|
312
568
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
@@ -314,158 +570,150 @@ function pathCropPieces(...paths) {
|
|
|
314
570
|
const out = [];
|
|
315
571
|
for (let i = 0; i < mps.length - 1; i++) {
|
|
316
572
|
const clipped = polygonClipping.intersection(mps[i], mask);
|
|
317
|
-
if (clipped.length > 0) out.push({ path:
|
|
573
|
+
if (clipped.length > 0) out.push({ path: toPath2(clipped, paths), sources: [i] });
|
|
318
574
|
}
|
|
319
575
|
return out;
|
|
320
576
|
}
|
|
321
|
-
function trianglesToPath(vertices, indices) {
|
|
322
|
-
const triangles = [];
|
|
323
|
-
for (let i = 0; i + 2 < indices.length; i += 3) {
|
|
324
|
-
const a = indices[i] * 2, b = indices[i + 1] * 2, c = indices[i + 2] * 2;
|
|
325
|
-
const ax = vertices[a], ay = vertices[a + 1];
|
|
326
|
-
const bx = vertices[b], by = vertices[b + 1];
|
|
327
|
-
const cx = vertices[c], cy = vertices[c + 1];
|
|
328
|
-
if ((bx - ax) * (cy - ay) - (cx - ax) * (by - ay) === 0) continue;
|
|
329
|
-
triangles.push([[[[ax, ay], [bx, by], [cx, cy], [ax, ay]]]]);
|
|
330
|
-
}
|
|
331
|
-
if (triangles.length === 0) return multiPolygonToPath([]);
|
|
332
|
-
const [head, ...rest] = triangles;
|
|
333
|
-
return multiPolygonToPath(polygonClipping.union(head, ...rest));
|
|
334
|
-
}
|
|
335
577
|
|
|
336
|
-
// src/booleans/
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function derivSeg(s, t) {
|
|
351
|
-
const n = s.length - 1;
|
|
352
|
-
const d = [];
|
|
353
|
-
for (let i = 0; i < n; i++) d.push({ x: n * (s[i + 1].x - s[i].x), y: n * (s[i + 1].y - s[i].y) });
|
|
354
|
-
return d.length === 1 ? d[0] : evalSeg(d, t);
|
|
355
|
-
}
|
|
356
|
-
function splitSeg(s, t) {
|
|
357
|
-
const pts = (c) => {
|
|
358
|
-
const out = [];
|
|
359
|
-
for (let i = 0; i < c.length; i += 2) out.push({ x: c[i], y: c[i + 1] });
|
|
360
|
-
return out;
|
|
361
|
-
};
|
|
362
|
-
if (s.length === 2) {
|
|
363
|
-
const m = evalSeg(s, t);
|
|
364
|
-
return [[s[0], m], [m, s[1]]];
|
|
578
|
+
// src/booleans/meshJunctions.ts
|
|
579
|
+
var ON_EDGE = 2 ** -20;
|
|
580
|
+
function junctionRings(vertices, triangles) {
|
|
581
|
+
const n = vertices.length / 2;
|
|
582
|
+
let scale = 0, minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
583
|
+
for (let i = 0; i < n; i++) {
|
|
584
|
+
const x = vertices[i * 2], y = vertices[i * 2 + 1];
|
|
585
|
+
scale = Math.max(scale, Math.abs(x), Math.abs(y));
|
|
586
|
+
minX = Math.min(minX, x);
|
|
587
|
+
minY = Math.min(minY, y);
|
|
588
|
+
maxX = Math.max(maxX, x);
|
|
589
|
+
maxY = Math.max(maxY, y);
|
|
365
590
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
591
|
+
const eps = scale * ON_EDGE;
|
|
592
|
+
const cell = Math.max(maxX - minX, maxY - minY, eps) / Math.max(1, Math.sqrt(n));
|
|
593
|
+
const grid = /* @__PURE__ */ new Map();
|
|
594
|
+
const key = (cx, cy) => `${cx},${cy}`;
|
|
595
|
+
for (const i of new Set(triangles)) {
|
|
596
|
+
const k = key(Math.floor((vertices[i * 2] - minX) / cell), Math.floor((vertices[i * 2 + 1] - minY) / cell));
|
|
597
|
+
const bucket = grid.get(k);
|
|
598
|
+
if (bucket) bucket.push(i);
|
|
599
|
+
else grid.set(k, [i]);
|
|
369
600
|
}
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const n = s.length === 2 ? 1 : 16;
|
|
389
|
-
for (let i = 0; i < n; i++) {
|
|
390
|
-
const p = evalSeg(s, i / n);
|
|
391
|
-
out.push(p.x, p.y);
|
|
601
|
+
const onEdge = (p, q) => {
|
|
602
|
+
const px = vertices[p * 2], py = vertices[p * 2 + 1];
|
|
603
|
+
const dx = vertices[q * 2] - px, dy = vertices[q * 2 + 1] - py;
|
|
604
|
+
const len2 = dx * dx + dy * dy;
|
|
605
|
+
if (len2 === 0) return [];
|
|
606
|
+
const len = Math.sqrt(len2);
|
|
607
|
+
const hits = [];
|
|
608
|
+
const x0 = Math.floor((Math.min(px, px + dx) - eps - minX) / cell), x1 = Math.floor((Math.max(px, px + dx) + eps - minX) / cell);
|
|
609
|
+
const y0 = Math.floor((Math.min(py, py + dy) - eps - minY) / cell), y1 = Math.floor((Math.max(py, py + dy) + eps - minY) / cell);
|
|
610
|
+
for (let cx = x0; cx <= x1; cx++) {
|
|
611
|
+
for (let cy = y0; cy <= y1; cy++) {
|
|
612
|
+
for (const i of grid.get(key(cx, cy)) ?? []) {
|
|
613
|
+
const vx = vertices[i * 2] - px, vy = vertices[i * 2 + 1] - py;
|
|
614
|
+
const t = (vx * dx + vy * dy) / len2;
|
|
615
|
+
if (t * len <= eps || (1 - t) * len <= eps) continue;
|
|
616
|
+
if (Math.abs(vx * dy - vy * dx) / len <= eps) hits.push({ i, t });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
392
619
|
}
|
|
620
|
+
return hits.sort((a, b) => a.t - b.t).map((h) => h.i);
|
|
621
|
+
};
|
|
622
|
+
const rings = [];
|
|
623
|
+
for (let k = 0; k + 2 < triangles.length; k += 3) {
|
|
624
|
+
const ring = [];
|
|
625
|
+
for (let e = 0; e < 3; e++) {
|
|
626
|
+
const p = triangles[k + e], q = triangles[k + (e + 1) % 3];
|
|
627
|
+
for (const i of [p, ...onEdge(p, q)]) ring.push([vertices[i * 2], vertices[i * 2 + 1]]);
|
|
628
|
+
}
|
|
629
|
+
ring.push(ring[0]);
|
|
630
|
+
rings.push(ring);
|
|
393
631
|
}
|
|
394
|
-
return
|
|
395
|
-
}
|
|
396
|
-
function signedArea(c) {
|
|
397
|
-
const pts = sampleContour(c);
|
|
398
|
-
let a = 0;
|
|
399
|
-
for (let i = 0; i < pts.length; i += 2) {
|
|
400
|
-
const j = (i + 2) % pts.length;
|
|
401
|
-
a += pts[i] * pts[j + 1] - pts[j] * pts[i + 1];
|
|
402
|
-
}
|
|
403
|
-
return a / 2;
|
|
404
|
-
}
|
|
405
|
-
function sideCoeffs(s, o, d) {
|
|
406
|
-
return s.map((p) => d.x * (p.y - o.y) - d.y * (p.x - o.x));
|
|
407
|
-
}
|
|
408
|
-
function bernstein(f, t) {
|
|
409
|
-
const u = 1 - t;
|
|
410
|
-
if (f.length === 2) return u * f[0] + t * f[1];
|
|
411
|
-
if (f.length === 3) return u * u * f[0] + 2 * u * t * f[1] + t * t * f[2];
|
|
412
|
-
return u * u * u * f[0] + 3 * u * u * t * f[1] + 3 * u * t * t * f[2] + t * t * t * f[3];
|
|
632
|
+
return rings;
|
|
413
633
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
634
|
+
|
|
635
|
+
// src/booleans/meshOutline.ts
|
|
636
|
+
function meshOutline(rings) {
|
|
637
|
+
const points = /* @__PURE__ */ new Map();
|
|
638
|
+
const edges = /* @__PURE__ */ new Map();
|
|
639
|
+
const keyOf = (p) => {
|
|
640
|
+
const k = `${p[0]},${p[1]}`;
|
|
641
|
+
if (!points.has(k)) points.set(k, p);
|
|
642
|
+
return k;
|
|
643
|
+
};
|
|
644
|
+
for (const ring of rings) {
|
|
645
|
+
const keys = ring.slice(0, -1).map(keyOf);
|
|
646
|
+
if (ringArea(ring) < 0) keys.reverse();
|
|
647
|
+
for (let i = 0; i < keys.length; i++) {
|
|
648
|
+
const a = keys[i], b = keys[(i + 1) % keys.length];
|
|
649
|
+
if (a === b) continue;
|
|
650
|
+
const back = `${b}|${a}`;
|
|
651
|
+
const n = edges.get(back);
|
|
652
|
+
if (n) {
|
|
653
|
+
if (n === 1) edges.delete(back);
|
|
654
|
+
else edges.set(back, n - 1);
|
|
655
|
+
} else {
|
|
656
|
+
const fwd = `${a}|${b}`;
|
|
657
|
+
edges.set(fwd, (edges.get(fwd) ?? 0) + 1);
|
|
658
|
+
}
|
|
430
659
|
}
|
|
431
|
-
out.push({ t: (lo + hi) / 2, before: slo, after: shi });
|
|
432
660
|
}
|
|
433
|
-
|
|
661
|
+
const loops = walkLoops(edges).map((loop) => loop.map((k) => points.get(k)));
|
|
662
|
+
return loops.length === 0 ? null : bridge(loops);
|
|
434
663
|
}
|
|
435
|
-
function
|
|
436
|
-
|
|
437
|
-
const n
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
C = d[0];
|
|
443
|
-
} else {
|
|
444
|
-
A = d[0] - 2 * d[1] + d[2];
|
|
445
|
-
B = 2 * (d[1] - d[0]);
|
|
446
|
-
C = d[0];
|
|
664
|
+
function walkLoops(edges) {
|
|
665
|
+
const out = /* @__PURE__ */ new Map();
|
|
666
|
+
for (const [edge, n] of edges) {
|
|
667
|
+
const [a, b] = edge.split("|");
|
|
668
|
+
let list = out.get(a);
|
|
669
|
+
if (!list) out.set(a, list = []);
|
|
670
|
+
for (let i = 0; i < n; i++) list.push(b);
|
|
447
671
|
}
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
672
|
+
const loops = [];
|
|
673
|
+
for (const start of out.keys()) {
|
|
674
|
+
while ((out.get(start)?.length ?? 0) > 0) {
|
|
675
|
+
const loop = [start];
|
|
676
|
+
let at = out.get(start).pop();
|
|
677
|
+
while (at !== start) {
|
|
678
|
+
loop.push(at);
|
|
679
|
+
const next = out.get(at);
|
|
680
|
+
at = next.pop();
|
|
681
|
+
}
|
|
682
|
+
loops.push(loop);
|
|
458
683
|
}
|
|
459
684
|
}
|
|
460
|
-
return
|
|
685
|
+
return loops;
|
|
686
|
+
}
|
|
687
|
+
function bridge(loops) {
|
|
688
|
+
const entered = loops.map((loop) => {
|
|
689
|
+
let k = 0;
|
|
690
|
+
for (let i = 1; i < loop.length; i++) if (loop[i][0] < loop[k][0] || loop[i][0] === loop[k][0] && loop[i][1] < loop[k][1]) k = i;
|
|
691
|
+
return [...loop.slice(k), ...loop.slice(0, k)];
|
|
692
|
+
});
|
|
693
|
+
entered.sort((a, b) => a[0][0] - b[0][0] || a[0][1] - b[0][1]);
|
|
694
|
+
const ring = [];
|
|
695
|
+
for (const loop of entered) ring.push(...loop, loop[0]);
|
|
696
|
+
for (let i = entered.length - 2; i >= 0; i--) ring.push(entered[i][0]);
|
|
697
|
+
return ring;
|
|
698
|
+
}
|
|
699
|
+
function ringArea(ring) {
|
|
700
|
+
let a = 0;
|
|
701
|
+
for (let i = 0; i + 1 < ring.length; i++) a += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];
|
|
702
|
+
return a;
|
|
461
703
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
704
|
+
|
|
705
|
+
// src/booleans/trianglesToPath.ts
|
|
706
|
+
function trianglesToPath(vertices, indices) {
|
|
707
|
+
const kept = [];
|
|
708
|
+
for (let i = 0; i + 2 < indices.length; i += 3) {
|
|
709
|
+
const a = indices[i] * 2, b = indices[i + 1] * 2, c = indices[i + 2] * 2;
|
|
710
|
+
const ax = vertices[a], ay = vertices[a + 1];
|
|
711
|
+
if ((vertices[b] - ax) * (vertices[c + 1] - ay) - (vertices[c] - ax) * (vertices[b + 1] - ay) === 0) continue;
|
|
712
|
+
kept.push(indices[i], indices[i + 1], indices[i + 2]);
|
|
467
713
|
}
|
|
468
|
-
return
|
|
714
|
+
if (kept.length === 0) return multiPolygonToPath([]);
|
|
715
|
+
const outline = meshOutline(junctionRings(vertices, kept));
|
|
716
|
+
return multiPolygonToPath(outline ? polygonClipping.union([[outline]]) : []);
|
|
469
717
|
}
|
|
470
718
|
|
|
471
719
|
// src/booleans/cutCrossings.ts
|
|
@@ -508,144 +756,6 @@ function crossingsOnSegment(boundaries, cut, k, out) {
|
|
|
508
756
|
});
|
|
509
757
|
}
|
|
510
758
|
|
|
511
|
-
// src/booleans/contours.ts
|
|
512
|
-
function parseContours(path) {
|
|
513
|
-
const fillsOpen = path.kind === "rect" || fillsOpenSubpaths(path);
|
|
514
|
-
return parseSubpaths(path).filter((s) => s.closed || fillsOpen).map((s) => s.segs);
|
|
515
|
-
}
|
|
516
|
-
function parseSubpaths(path, implicitClose = true) {
|
|
517
|
-
if (path.kind === "rect") {
|
|
518
|
-
const { x, y, width: w, height: h } = path;
|
|
519
|
-
const p = [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }];
|
|
520
|
-
return [{ segs: p.map((q, i) => [q, p[(i + 1) % 4]]), closed: true }];
|
|
521
|
-
}
|
|
522
|
-
const out = [];
|
|
523
|
-
let cur = [];
|
|
524
|
-
let start = { x: 0, y: 0 };
|
|
525
|
-
let pen = start;
|
|
526
|
-
const finish = (closed) => {
|
|
527
|
-
if (cur.length > 0 && (closed || implicitClose) && (pen.x !== start.x || pen.y !== start.y)) {
|
|
528
|
-
cur.push([pen, start]);
|
|
529
|
-
}
|
|
530
|
-
if (cur.length > 0) out.push({ segs: cur, closed });
|
|
531
|
-
cur = [];
|
|
532
|
-
pen = start;
|
|
533
|
-
};
|
|
534
|
-
const c = path.coords;
|
|
535
|
-
let k = 0;
|
|
536
|
-
for (const cmd of path.commands) {
|
|
537
|
-
if (cmd === PATH_M) {
|
|
538
|
-
finish(false);
|
|
539
|
-
start = pen = { x: c[k], y: c[k + 1] };
|
|
540
|
-
k += 2;
|
|
541
|
-
} else if (cmd === PATH_L) {
|
|
542
|
-
const e = { x: c[k], y: c[k + 1] };
|
|
543
|
-
if (e.x !== pen.x || e.y !== pen.y) cur.push([pen, e]);
|
|
544
|
-
pen = e;
|
|
545
|
-
k += 2;
|
|
546
|
-
} else if (cmd === PATH_Q) {
|
|
547
|
-
const e = { x: c[k + 2], y: c[k + 3] };
|
|
548
|
-
cur.push([pen, { x: c[k], y: c[k + 1] }, e]);
|
|
549
|
-
pen = e;
|
|
550
|
-
k += 4;
|
|
551
|
-
} else if (cmd === PATH_C) {
|
|
552
|
-
const e = { x: c[k + 4], y: c[k + 5] };
|
|
553
|
-
cur.push([pen, { x: c[k], y: c[k + 1] }, { x: c[k + 2], y: c[k + 3] }, e]);
|
|
554
|
-
pen = e;
|
|
555
|
-
k += 6;
|
|
556
|
-
} else if (cmd === PATH_Z) {
|
|
557
|
-
finish(true);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
finish(false);
|
|
561
|
-
return out;
|
|
562
|
-
}
|
|
563
|
-
function contoursIntersect(contours, tol) {
|
|
564
|
-
const edges = [];
|
|
565
|
-
contours.forEach((contour, ci) => {
|
|
566
|
-
const pts = [];
|
|
567
|
-
for (const s of contour) {
|
|
568
|
-
let n = 1;
|
|
569
|
-
if (s.length > 2) {
|
|
570
|
-
let len = 0;
|
|
571
|
-
for (let i = 0; i + 1 < s.length; i++) len += Math.hypot(s[i + 1].x - s[i].x, s[i + 1].y - s[i].y);
|
|
572
|
-
n = Math.min(64, Math.max(2, Math.ceil(Math.sqrt(len / Math.max(tol, 1e-3)))));
|
|
573
|
-
}
|
|
574
|
-
for (let i = 0; i < n; i++) pts.push(evalSeg(s, i / n));
|
|
575
|
-
}
|
|
576
|
-
for (let i = 0; i < pts.length; i++) {
|
|
577
|
-
const p = pts[i], q = pts[(i + 1) % pts.length];
|
|
578
|
-
edges.push({ x0: p.x, y0: p.y, x1: q.x, y1: q.y, c: ci, i, n: pts.length });
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
for (let i = 0; i < edges.length; i++) {
|
|
582
|
-
const e = edges[i];
|
|
583
|
-
for (let j = i + 1; j < edges.length; j++) {
|
|
584
|
-
const f = edges[j];
|
|
585
|
-
if (e.c === f.c && (j - i === 1 || i === 0 && f.i === f.n - 1)) continue;
|
|
586
|
-
if (Math.max(e.x0, e.x1) < Math.min(f.x0, f.x1) || Math.max(f.x0, f.x1) < Math.min(e.x0, e.x1)) continue;
|
|
587
|
-
if (Math.max(e.y0, e.y1) < Math.min(f.y0, f.y1) || Math.max(f.y0, f.y1) < Math.min(e.y0, e.y1)) continue;
|
|
588
|
-
if (segmentsCross(e.x0, e.y0, e.x1, e.y1, f.x0, f.y0, f.x1, f.y1)) return true;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
function windingAt(q, contours) {
|
|
594
|
-
const dir = { x: 1, y: 0 };
|
|
595
|
-
let w = 0;
|
|
596
|
-
for (const contour of contours) {
|
|
597
|
-
for (const s of contour) {
|
|
598
|
-
for (const c of signChanges(sideCoeffs(s, q, dir), true)) {
|
|
599
|
-
if (evalSeg(s, c.t).x > q.x) w += c.after > 0 ? 1 : -1;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return w;
|
|
604
|
-
}
|
|
605
|
-
function extentOf(contours) {
|
|
606
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
607
|
-
for (const c of contours) for (const s of c) for (const p of s) {
|
|
608
|
-
minX = Math.min(minX, p.x);
|
|
609
|
-
maxX = Math.max(maxX, p.x);
|
|
610
|
-
minY = Math.min(minY, p.y);
|
|
611
|
-
maxY = Math.max(maxY, p.y);
|
|
612
|
-
}
|
|
613
|
-
return Math.hypot(maxX - minX, maxY - minY);
|
|
614
|
-
}
|
|
615
|
-
function negligibleArea(contours) {
|
|
616
|
-
const extent = extentOf(contours);
|
|
617
|
-
return extent * extent * EPS * EPS;
|
|
618
|
-
}
|
|
619
|
-
function orientBoundaries(contours, fillRule) {
|
|
620
|
-
const filled = (q) => {
|
|
621
|
-
const w = windingAt(q, contours);
|
|
622
|
-
return fillRule === "evenodd" ? (w & 1) !== 0 : w !== 0;
|
|
623
|
-
};
|
|
624
|
-
const eps = Math.max(extentOf(contours) * 1e-6, 1e-9);
|
|
625
|
-
const out = [];
|
|
626
|
-
for (const contour of contours) {
|
|
627
|
-
let probe = contour[0];
|
|
628
|
-
let best = -1;
|
|
629
|
-
for (const s of contour) {
|
|
630
|
-
const len = Math.hypot(s[s.length - 1].x - s[0].x, s[s.length - 1].y - s[0].y);
|
|
631
|
-
if (len > best) {
|
|
632
|
-
best = len;
|
|
633
|
-
probe = s;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
const p = evalSeg(probe, 0.5);
|
|
637
|
-
let tan = derivSeg(probe, 0.5);
|
|
638
|
-
const tl = Math.hypot(tan.x, tan.y);
|
|
639
|
-
if (tl === 0) continue;
|
|
640
|
-
tan = { x: tan.x / tl, y: tan.y / tl };
|
|
641
|
-
const left = filled({ x: p.x - tan.y * eps, y: p.y + tan.x * eps });
|
|
642
|
-
const right = filled({ x: p.x + tan.y * eps, y: p.y - tan.x * eps });
|
|
643
|
-
if (left && !right) out.push(contour);
|
|
644
|
-
else if (right && !left) out.push(reverseContour(contour));
|
|
645
|
-
}
|
|
646
|
-
return out;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
759
|
// src/booleans/cutPolyline.ts
|
|
650
760
|
function distinctPoints(cut) {
|
|
651
761
|
const out = [];
|
|
@@ -839,36 +949,6 @@ function arcBetween(contour, from, to, whole) {
|
|
|
839
949
|
return out;
|
|
840
950
|
}
|
|
841
951
|
|
|
842
|
-
// src/booleans/cutRegions.ts
|
|
843
|
-
function assembleRegions(outers, holes) {
|
|
844
|
-
const regions = outers.map((o) => ({ loops: [o], ring: sampleContour(o), area: signedArea(o) }));
|
|
845
|
-
for (const h of holes) {
|
|
846
|
-
const hp = evalSeg(h[0], 0.5);
|
|
847
|
-
let best = null;
|
|
848
|
-
for (const r of regions) {
|
|
849
|
-
if (pointInPolygon(r.ring, hp.x, hp.y) && (!best || r.area < best.area)) best = r;
|
|
850
|
-
}
|
|
851
|
-
best?.loops.push(h);
|
|
852
|
-
}
|
|
853
|
-
return regions.map((r) => r.loops);
|
|
854
|
-
}
|
|
855
|
-
var regionArea = (r) => r.reduce((s, c) => s + signedArea(c), 0);
|
|
856
|
-
var toPath = (loops, fillRule) => writePath(loops.map((segs) => ({ segs, closed: true })), fillRule);
|
|
857
|
-
function writePath(subpaths, fillRule) {
|
|
858
|
-
const cmds = [];
|
|
859
|
-
const xs = [];
|
|
860
|
-
for (const { segs, closed } of subpaths) {
|
|
861
|
-
cmds.push(PATH_M);
|
|
862
|
-
xs.push(segs[0][0].x, segs[0][0].y);
|
|
863
|
-
for (const s of segs) {
|
|
864
|
-
cmds.push(s.length === 2 ? PATH_L : s.length === 3 ? PATH_Q : PATH_C);
|
|
865
|
-
for (let i = 1; i < s.length; i++) xs.push(s[i].x, s[i].y);
|
|
866
|
-
}
|
|
867
|
-
if (closed) cmds.push(PATH_Z);
|
|
868
|
-
}
|
|
869
|
-
return { kind: "polygon", commands: new Uint8Array(cmds), coords: new Float32Array(xs), fillRule };
|
|
870
|
-
}
|
|
871
|
-
|
|
872
952
|
// src/booleans/splitBySegment.ts
|
|
873
953
|
function splitPathBySegment(path, a, b, opts = {}) {
|
|
874
954
|
return splitPathByPolyline(path, [a, b], opts);
|
|
@@ -1012,7 +1092,7 @@ function snipPathsAtCrossings(paths, opts = {}) {
|
|
|
1012
1092
|
});
|
|
1013
1093
|
}
|
|
1014
1094
|
});
|
|
1015
|
-
return cut ? pieces : null;
|
|
1095
|
+
return cut ? pieces.map((piece) => piece.kind === "polygon" ? keepCorners([path], piece) : piece) : null;
|
|
1016
1096
|
});
|
|
1017
1097
|
}
|
|
1018
1098
|
function cutLinesOf(path, tol) {
|
|
@@ -1033,6 +1113,6 @@ function cutLinesOf(path, tol) {
|
|
|
1033
1113
|
});
|
|
1034
1114
|
}
|
|
1035
1115
|
|
|
1036
|
-
export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
|
|
1116
|
+
export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathFacePieces, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
|
|
1037
1117
|
//# sourceMappingURL=index.js.map
|
|
1038
1118
|
//# sourceMappingURL=index.js.map
|