@weasel-js/geom 1.9.4 → 1.10.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 +132 -16
- package/dist/3d/index.js +1 -1
- package/dist/booleans/index.d.ts +63 -29
- package/dist/booleans/index.js +86 -11
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-N5SYTHMR.js → chunk-755Y4YZS.js} +2 -2
- package/dist/chunk-755Y4YZS.js.map +1 -0
- package/dist/{chunk-NL423YYZ.js → chunk-7WFFCHRL.js} +8 -6
- package/dist/{chunk-NL423YYZ.js.map → chunk-7WFFCHRL.js.map} +1 -1
- package/dist/{chunk-X6GJRIQD.js → chunk-IDXY2NCA.js} +38 -16
- package/dist/chunk-IDXY2NCA.js.map +1 -0
- package/dist/chunk-NJ3IGAKR.js +16 -0
- package/dist/chunk-NJ3IGAKR.js.map +1 -0
- package/dist/{chunk-E4TJRGJD.js → chunk-OXH3NOXC.js} +2 -2
- package/dist/chunk-OXH3NOXC.js.map +1 -0
- package/dist/cubicMath-BW-HJrCE.d.ts +16 -0
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/index.js +2 -2
- package/dist/index.d.ts +899 -135
- package/dist/index.js +1545 -123
- package/dist/index.js.map +1 -1
- package/dist/{path-D0CoudGM.d.ts → path-ljHxobyu.d.ts} +10 -11
- package/dist/tessellate/index.d.ts +11 -12
- package/dist/tessellate/index.js +22 -5
- package/dist/tessellate/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-E4TJRGJD.js.map +0 -1
- package/dist/chunk-HPVINMOM.js +0 -34
- package/dist/chunk-HPVINMOM.js.map +0 -1
- package/dist/chunk-N5SYTHMR.js.map +0 -1
- package/dist/chunk-X6GJRIQD.js.map +0 -1
- package/dist/cubicMath-CpCvuEl8.d.ts +0 -37
package/README.md
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
# @weasel-js/geom
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`Path`
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- `./curves` — alternate curve representations (cubic and quadratic Bezier, NURBS, Spiro)
|
|
9
|
-
- `./tessellate` — fill tessellation, polylines and trimming; needs the optional peer `earcut`
|
|
10
|
-
- `./booleans` — polygon booleans and path splitting; needs the optional peer `polygon-clipping`
|
|
11
|
-
- `./3d` — vectors, quaternions, 4x4 matrices and ray intersection
|
|
12
|
-
- `./nd` — the port-curve rule (`portControls`, `portCurvePoints`, `PORT_REACH`) over plain number arrays, for any dimension
|
|
3
|
+
The 2D geometry kernel the rest of weasel is built on: affine transforms, boxes,
|
|
4
|
+
Bezier math, the `Path` shape type and everything that operates on it, and
|
|
5
|
+
easing curves. It is plain functions over numbers and typed arrays, with no
|
|
6
|
+
DOM, no React, and no runtime dependencies in the root entry.
|
|
13
7
|
|
|
14
8
|
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
15
9
|
scene-graph canvas kit for React. See the
|
|
16
|
-
[API reference](https://orochi235.github.io/weasel/
|
|
10
|
+
[API reference](https://orochi235.github.io/weasel/#pkg/geom).
|
|
17
11
|
|
|
18
12
|
## Install
|
|
19
13
|
|
|
@@ -21,13 +15,135 @@ scene-graph canvas kit for React. See the
|
|
|
21
15
|
npm install @weasel-js/geom
|
|
22
16
|
```
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
The `./tessellate` entry uses the optional peer `earcut`, and `./booleans` uses
|
|
19
|
+
the optional peer `polygon-clipping`. Install a peer only if you import the
|
|
20
|
+
entry that needs it.
|
|
21
|
+
|
|
22
|
+
## Value types
|
|
23
|
+
|
|
24
|
+
Most functions take loose `x, y` numbers rather than point objects, so a hot
|
|
25
|
+
loop allocates nothing. The types that do exist are small:
|
|
26
|
+
|
|
27
|
+
| Type | Shape | Notes |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `Mat3` | `[a, b, c, d, e, f]` | A 2D affine in canvas and `DOMMatrix` order: `x' = a·x + c·y + e`, `y' = b·x + d·y + f`. |
|
|
30
|
+
| `Box` | `[minX, minY, maxX, maxY]` | An axis-aligned box as a tuple. |
|
|
31
|
+
| `Rect` | `{ x, y, width, height }` | The same box in object form. |
|
|
32
|
+
| `View` | `{ x, y, scale: { x, y } }` | A viewport: the world point drawn at the canvas's top-left corner, and pixels per world unit on each axis. |
|
|
33
|
+
| `GlMat3` | nine-element `Float64Array` or `Float32Array` | The same affine laid out column-major for a WebGL `uniformMatrix3fv` upload. |
|
|
34
|
+
| `Path` | `RectPath \| PolygonPath` | A shape. See below. |
|
|
35
|
+
|
|
36
|
+
`translate`, `scale`, `rotate`, `rotateAboutPoint`, and `boxToBox` build a
|
|
37
|
+
`Mat3`. `multiply(m, n)` composes two, applying `n` first. `invert` returns
|
|
38
|
+
`null` for a singular matrix, and `applyToPoint` maps one point.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { applyToPoint, invert, multiply, scale, translate } from '@weasel-js/geom';
|
|
42
|
+
|
|
43
|
+
// World to screen for a view at (10, 20), zoomed 2x: translate, then scale.
|
|
44
|
+
const worldToScreen = multiply(scale(2, 2), translate(-10, -20));
|
|
45
|
+
applyToPoint(worldToScreen, 15, 25); // [10, 10]
|
|
46
|
+
|
|
47
|
+
const screenToWorld = invert(worldToScreen); // Mat3 | null
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `mat3` namespace holds the `GlMat3` operations (`mat3.fromAffine`,
|
|
51
|
+
`mat3.toAffine`, `mat3.multiply`, `mat3.toUniform`, and others), and
|
|
52
|
+
`viewToMat3(view)` builds the world-to-screen `GlMat3` for a `View`.
|
|
53
|
+
|
|
54
|
+
## Paths
|
|
55
|
+
|
|
56
|
+
A `Path` is the shape type every weasel package shares. It is either:
|
|
57
|
+
|
|
58
|
+
- A `RectPath`, `{ kind: 'rect', x, y, width, height }`, for the common case of
|
|
59
|
+
an axis-aligned rectangle.
|
|
60
|
+
- A `PolygonPath`, an SVG-style command stream: command codes (`M`, `L`, `C`,
|
|
61
|
+
`Q`, `Z`) in a `Uint8Array`, their coordinates in a parallel `Float32Array`,
|
|
62
|
+
and a `fillRule`. It covers polylines, polygons, Bezier outlines, and shapes
|
|
63
|
+
with holes.
|
|
64
|
+
|
|
65
|
+
What operates on a path:
|
|
66
|
+
|
|
67
|
+
- **Building.** `PathBuilder` for move, line, curve, and close calls;
|
|
68
|
+
`rectPath`, `polygonFromPoints`, `polylineFromPoints`, `ellipsePath`,
|
|
69
|
+
`regularPolygonPath`, `starPath`, `linePath`, `circlePath`, and
|
|
70
|
+
`roundRectPath` for common shapes; `pathFromD` to parse an SVG `d` string;
|
|
71
|
+
`schneiderFit` to fit cubic curves through sampled points.
|
|
72
|
+
- **Measuring.** `boundsOfPath`, `unionBoundsPath`, `pathSignedArea`,
|
|
73
|
+
`pointAlongPath`.
|
|
74
|
+
- **Transforming.** `translatePath`, `transformPath`, `scalePathToBounds`,
|
|
75
|
+
`reversePath`.
|
|
76
|
+
- **Hit-testing.** `pointInPath` for the filled region, `strokeHitTest` for
|
|
77
|
+
the outline, `pathContainsRect`, `pathIntersectsRect`, and the polygon
|
|
78
|
+
variants for marquee and lasso selection, and `nearestOnPath` and
|
|
79
|
+
`pathDistanceToPoint` for the closest point.
|
|
80
|
+
- **Editing.** `pathToAnchors` and `anchorsToPath` convert between the command
|
|
81
|
+
stream and a list of anchors with control handles, the form a pen tool
|
|
82
|
+
edits. `splitSubpaths` and `splitByClosure` take a compound path apart.
|
|
83
|
+
- **Walking the stream.** `forEachSegment` visits each command with its
|
|
84
|
+
coordinate offset and the pen position before it.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import {
|
|
88
|
+
boundsOfPath, pathFromD, pointInPath, rotateAboutPoint, strokeHitTest, transformPath,
|
|
89
|
+
} from '@weasel-js/geom';
|
|
90
|
+
|
|
91
|
+
const triangle = pathFromD('M0 0 L100 0 L50 80 Z');
|
|
92
|
+
|
|
93
|
+
boundsOfPath(triangle); // { kind: 'rect', x: 0, y: 0, width: 100, height: 80 }
|
|
94
|
+
pointInPath(triangle, 50, 20); // true: inside the fill
|
|
95
|
+
strokeHitTest(triangle, 50, 3, 4); // true: within 4 units of the outline
|
|
96
|
+
|
|
97
|
+
const turned = transformPath(triangle, rotateAboutPoint(50, 40, Math.PI / 2));
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Below the path level, the curve functions work on one segment at a time:
|
|
101
|
+
`cubicEvalAt`, `splitCubicAt`, `cubicBounds`, `flattenCubic`,
|
|
102
|
+
`nearestOnCubic`, and their quadratic and line counterparts.
|
|
103
|
+
|
|
104
|
+
## Easings
|
|
105
|
+
|
|
106
|
+
An `EasingFn` maps progress `t` in 0..1 to eased progress. The package exports
|
|
107
|
+
the standard families by name (`easeOutCubic`, `easeInOutSine`,
|
|
108
|
+
`easeOutBounce`, and so on), the same set keyed by name in `EASINGS`, and
|
|
109
|
+
`cubicBezierEasing(x1, y1, x2, y2)` for CSS-style control points.
|
|
110
|
+
|
|
111
|
+
`EasingSpec` is an easing as a value that can be stored or serialized: a
|
|
112
|
+
function, a name, or `{ bezier: [x1, y1, x2, y2] }`. `resolveEasing` turns any
|
|
113
|
+
of them into the function.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { resolveEasing } from '@weasel-js/geom';
|
|
117
|
+
|
|
118
|
+
const ease = resolveEasing('easeOutCubic');
|
|
119
|
+
ease(0.5); // 0.875
|
|
120
|
+
|
|
121
|
+
const custom = resolveEasing({ bezier: [0.4, 0, 0.2, 1] });
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Subpath entries
|
|
125
|
+
|
|
126
|
+
| Entry | What it adds | Optional peer |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `@weasel-js/geom/curves` | Curve representations that read one shared anchor list and each produce a `PolygonPath`: `bezierCubic`, `bezierQuadratic`, `nurbs`, and `spiro`, also keyed by kind in `CURVE_REPS`. Each can evaluate a point and the curvature at a parameter. | none |
|
|
129
|
+
| `@weasel-js/geom/tessellate` | `tessellate` turns a path's fill into a triangle `Mesh` (vertices and indices). `extractPolylines` flattens each contour to straight segments, and `trimPolyline` shortens one from either end. | `earcut`, used by `tessellate` |
|
|
130
|
+
| `@weasel-js/geom/booleans` | Boolean operations on path fills: `pathUnion`, `pathIntersect`, `pathSubtract`, `pathExclude`, `pathDivide`, `pathCrop`, `pathTrim`, and `pathMerge`. Also cutting a path with a line or polyline: `splitPathBySegment`, `splitPathByPolyline`, `snipPathByPolyline`. | `polygon-clipping` |
|
|
131
|
+
| `@weasel-js/geom/3d` | The same kind of math one dimension up: `Vec3` and quaternion helpers, `Mat4` with `compose`, `perspective`, and `lookAt`, and ray intersection against planes and axis-aligned boxes. | none |
|
|
132
|
+
| `@weasel-js/geom/nd` | The port-curve rule over plain number arrays of any length: `portControls` returns the two cubic control points that make a curve leave one port along its normal and arrive against the next port's normal, and `portCurvePoints` samples that curve. | none |
|
|
133
|
+
|
|
134
|
+
The `./3d` entry reuses names from the root (`dot`, `scale`, `invert`,
|
|
135
|
+
`identity`), so import from one or the other, or alias at the import.
|
|
136
|
+
|
|
137
|
+
Boolean operations work on what a fill covers, so curves are flattened first
|
|
138
|
+
and the result is a `PolygonPath` of straight segments.
|
|
25
139
|
|
|
26
140
|
```ts
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
141
|
+
import { ellipsePath, rectPath } from '@weasel-js/geom';
|
|
142
|
+
import { pathSubtract } from '@weasel-js/geom/booleans';
|
|
143
|
+
|
|
144
|
+
const plate = rectPath(0, 0, 100, 60);
|
|
145
|
+
const hole = ellipsePath({ x: 30, y: 10, width: 40, height: 40 });
|
|
146
|
+
const cut = pathSubtract(plate, hole); // the plate with the ellipse removed
|
|
31
147
|
```
|
|
32
148
|
|
|
33
149
|
## License
|
package/dist/3d/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PORT_REACH, portControls, portCurvePoints, cubicAt } from '../chunk-YQADYJJ7.js';
|
|
2
2
|
export { PORT_REACH } from '../chunk-YQADYJJ7.js';
|
|
3
|
-
import { SINGULAR_RATIO } from '../chunk-
|
|
3
|
+
import { SINGULAR_RATIO } from '../chunk-755Y4YZS.js';
|
|
4
4
|
|
|
5
5
|
// src/3d/vec3.ts
|
|
6
6
|
function add(a, b) {
|
package/dist/booleans/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { P as Point } from '../cubicMath-
|
|
1
|
+
import { a as PolygonPath, P as Path } from '../path-ljHxobyu.js';
|
|
2
|
+
import { P as Point } from '../cubicMath-BW-HJrCE.js';
|
|
3
3
|
|
|
4
4
|
/** Union of N paths. Commutative. Returns an empty path if all inputs are empty. */
|
|
5
5
|
declare function pathUnion(...paths: Path[]): PolygonPath;
|
|
@@ -9,30 +9,48 @@ declare function pathIntersect(...paths: Path[]): PolygonPath;
|
|
|
9
9
|
declare function pathSubtract(a: Path, b: Path): PolygonPath;
|
|
10
10
|
/** Symmetric difference (XOR) of N paths. Commutative. */
|
|
11
11
|
declare function pathExclude(...paths: Path[]): PolygonPath;
|
|
12
|
+
/** One path of a result that keeps its pieces apart, and the inputs it came
|
|
13
|
+
* from: their indices in the call, ascending, so the last is the topmost of
|
|
14
|
+
* inputs given back to front. */
|
|
15
|
+
interface BooleanPiece {
|
|
16
|
+
path: PolygonPath;
|
|
17
|
+
sources: number[];
|
|
18
|
+
}
|
|
19
|
+
/** {@link pathDividePieces} as bare paths. */
|
|
20
|
+
declare function pathDivide(...paths: Path[]): PolygonPath[];
|
|
21
|
+
/** {@link pathCropPieces} as bare paths. */
|
|
22
|
+
declare function pathCrop(...paths: Path[]): PolygonPath[];
|
|
12
23
|
/**
|
|
13
24
|
* Fracture N paths along every intersection into the maximal set of
|
|
14
|
-
* non-overlapping regions. Returns one
|
|
25
|
+
* non-overlapping regions. Returns one piece per region, naming every input
|
|
26
|
+
* that covers it. For N=2 these are the three Illustrator "Divide" outputs
|
|
27
|
+
* (A−B, B−A, A∩B).
|
|
15
28
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* and no others. By construction the emitted regions are pairwise disjoint
|
|
19
|
-
* and their union equals `pathUnion(A1..AN)`.
|
|
20
|
-
*
|
|
21
|
-
* For N=2 this collapses to the three Illustrator "Divide" outputs
|
|
22
|
-
* (A−B, B−A, A∩B). For N=3 up to 7 regions are emitted. The 2^N − 1
|
|
23
|
-
* subset count limits this to small N in practice; passing more than ~8
|
|
24
|
-
* inputs is a misuse — the polygon-clipping kernel dominates anyway.
|
|
29
|
+
* One region per non-empty subset of the inputs, so the 2^N − 1 subset count
|
|
30
|
+
* limits this to small N; passing more than ~8 inputs is a misuse.
|
|
25
31
|
*/
|
|
26
|
-
declare function
|
|
32
|
+
declare function pathDividePieces(...paths: Path[]): BooleanPiece[];
|
|
27
33
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* Empty results (a source that lies entirely outside the mask) are
|
|
33
|
-
* filtered. With `<2` inputs the result is empty.
|
|
34
|
+
* Remove from each path what the paths after it cover (Illustrator "Trim"):
|
|
35
|
+
* given back to front, every piece is the part of its input left showing.
|
|
36
|
+
* One piece per input in input order, less the inputs wholly hidden.
|
|
34
37
|
*/
|
|
35
|
-
declare function
|
|
38
|
+
declare function pathTrim(...paths: Path[]): BooleanPiece[];
|
|
39
|
+
/**
|
|
40
|
+
* {@link pathTrim}, then unite the pieces of the inputs `keyOf` gives the same
|
|
41
|
+
* key (Illustrator "Merge", keyed by paint). Keys compare with `===`. A piece
|
|
42
|
+
* names every input left showing in it, and the pieces are ordered by their
|
|
43
|
+
* topmost one. Pieces that share a key and do not touch come back as one path
|
|
44
|
+
* of several contours.
|
|
45
|
+
*/
|
|
46
|
+
declare function pathMerge(paths: readonly Path[], keyOf: (index: number) => unknown): BooleanPiece[];
|
|
47
|
+
/**
|
|
48
|
+
* Clip each non-topmost path to the topmost path (Illustrator "Crop"), which
|
|
49
|
+
* acts purely as the mask and is discarded. Returns up to `N - 1` pieces in
|
|
50
|
+
* input order, each naming the one input it was clipped from; a source
|
|
51
|
+
* entirely outside the mask is dropped.
|
|
52
|
+
*/
|
|
53
|
+
declare function pathCropPieces(...paths: Path[]): BooleanPiece[];
|
|
36
54
|
|
|
37
55
|
/**
|
|
38
56
|
* Union the triangles of an indexed mesh (`vertices` as x, y pairs; `indices`
|
|
@@ -74,16 +92,13 @@ interface SplitBySegmentOptions {
|
|
|
74
92
|
*/
|
|
75
93
|
declare function splitPathBySegment(path: Path, a: Point, b: Point, opts?: SplitBySegmentOptions): Path[] | null;
|
|
76
94
|
/**
|
|
77
|
-
* Split `path` along the open polyline `cut` into closed pieces (Knife).
|
|
78
|
-
* freehand cut is a polyline through its samples.
|
|
95
|
+
* Split `path` along the open polyline `cut` into closed pieces (Knife).
|
|
79
96
|
*
|
|
80
97
|
* Only chords the cut crosses end to end are cut: a stretch of the cut that
|
|
81
98
|
* starts or stops inside the fill cuts nothing, and nothing beyond the cut's
|
|
82
|
-
* ends is touched
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* its quadratic and cubic segments; the new edges follow the cut as straight
|
|
86
|
-
* lines, bends included.
|
|
99
|
+
* ends is touched. Curves are split at the crossing parameters by de Casteljau
|
|
100
|
+
* subdivision, so every piece keeps its quadratic and cubic segments; the new
|
|
101
|
+
* edges follow the cut as straight lines, bends included.
|
|
87
102
|
*
|
|
88
103
|
* A loop the cut draws inside the fill, where it crosses its own track, cuts
|
|
89
104
|
* out the region it encloses: that region is returned as its own piece, with
|
|
@@ -91,7 +106,7 @@ declare function splitPathBySegment(path: Path, a: Point, b: Point, opts?: Split
|
|
|
91
106
|
* running through a loop leaves the loop uncut. A closed cut (its last point
|
|
92
107
|
* equal to its first) is one such loop or, where it crosses the boundary, is
|
|
93
108
|
* cut as the chords it makes. Chords that cross each other are cut one after
|
|
94
|
-
* another
|
|
109
|
+
* another.
|
|
95
110
|
*
|
|
96
111
|
* Each connected piece the cut produces is returned as its own path, holes
|
|
97
112
|
* included. Contours the cut misses entirely are returned together as one
|
|
@@ -122,4 +137,23 @@ declare function splitPathByPolyline(path: Path, cut: readonly Point[], opts?: S
|
|
|
122
137
|
*/
|
|
123
138
|
declare function snipPathByPolyline(path: Path, cut: readonly Point[]): Path[] | null;
|
|
124
139
|
|
|
125
|
-
|
|
140
|
+
/** Options for {@link snipPathsAtCrossings}. */
|
|
141
|
+
interface SnipAtCrossingsOptions {
|
|
142
|
+
/** How closely a cutting path's curves are followed when they are flattened
|
|
143
|
+
* to find where they cross. Default 0.05. */
|
|
144
|
+
flattenTolerance?: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cut the stroke of every path in `paths` wherever another of them crosses
|
|
148
|
+
* it, as {@link snipPathByPolyline} cuts along one polyline: an open subpath
|
|
149
|
+
* becomes open runs between crossings, and a closed one is opened at them.
|
|
150
|
+
* A path is not cut where it crosses itself.
|
|
151
|
+
*
|
|
152
|
+
* Answers one entry per path, in order: its pieces, or `null` when no other
|
|
153
|
+
* path crosses it. The cutting paths' curves are flattened, so a crossing on
|
|
154
|
+
* a curve is found to within `flattenTolerance`; the pieces keep their own
|
|
155
|
+
* segment types.
|
|
156
|
+
*/
|
|
157
|
+
declare function snipPathsAtCrossings(paths: readonly Path[], opts?: SnipAtCrossingsOptions): (Path[] | null)[];
|
|
158
|
+
|
|
159
|
+
export { type BooleanPiece, type MultiPolygon, type Pair, type PathToMultiPolygonOptions, type Polygon, type Ring, type SnipAtCrossingsOptions, type SplitBySegmentOptions, multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
|
package/dist/booleans/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { elevateQuadraticToCubic, splitQuadraticAt, splitCubicAt } from '../chunk-URQMDR6Q.js';
|
|
2
|
-
import { DEFAULT_FLATTEN_TOLERANCE, flattenCubic, segmentsCross, pointInPolygon } from '../chunk-
|
|
3
|
-
import { EPS } from '../chunk-
|
|
4
|
-
import { PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M } from '../chunk-
|
|
2
|
+
import { DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, segmentsCross, pointInPolygon } from '../chunk-IDXY2NCA.js';
|
|
3
|
+
import { EPS } from '../chunk-755Y4YZS.js';
|
|
4
|
+
import { PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M } from '../chunk-OXH3NOXC.js';
|
|
5
5
|
import polygonClipping from 'polygon-clipping';
|
|
6
6
|
|
|
7
7
|
// src/booleans/adapter.ts
|
|
@@ -24,7 +24,9 @@ function pathToMultiPolygon(path, opts = {}) {
|
|
|
24
24
|
let startX = 0, startY = 0;
|
|
25
25
|
let ci = 0;
|
|
26
26
|
const { commands, coords } = path;
|
|
27
|
-
const
|
|
27
|
+
const fillsOpen = fillsOpenSubpaths(path);
|
|
28
|
+
const finalizeCurrent = (closed = fillsOpen) => {
|
|
29
|
+
if (!closed) current = null;
|
|
28
30
|
if (!current || current.length === 0) return;
|
|
29
31
|
const first = current[0];
|
|
30
32
|
const last = current[current.length - 1];
|
|
@@ -82,7 +84,7 @@ function pathToMultiPolygon(path, opts = {}) {
|
|
|
82
84
|
break;
|
|
83
85
|
}
|
|
84
86
|
case PATH_Z: {
|
|
85
|
-
finalizeCurrent();
|
|
87
|
+
finalizeCurrent(true);
|
|
86
88
|
cx = startX;
|
|
87
89
|
cy = startY;
|
|
88
90
|
break;
|
|
@@ -235,9 +237,15 @@ function pathExclude(...paths) {
|
|
|
235
237
|
return multiPolygonToPath(polygonClipping.xor(head, ...rest));
|
|
236
238
|
}
|
|
237
239
|
function pathDivide(...paths) {
|
|
240
|
+
return pathDividePieces(...paths).map((p) => p.path);
|
|
241
|
+
}
|
|
242
|
+
function pathCrop(...paths) {
|
|
243
|
+
return pathCropPieces(...paths).map((p) => p.path);
|
|
244
|
+
}
|
|
245
|
+
function pathDividePieces(...paths) {
|
|
238
246
|
if (paths.length === 0) return [];
|
|
239
247
|
if (paths.length === 1) {
|
|
240
|
-
return [multiPolygonToPath(pathToMultiPolygon(paths[0]))];
|
|
248
|
+
return [{ path: multiPolygonToPath(pathToMultiPolygon(paths[0])), sources: [0] }];
|
|
241
249
|
}
|
|
242
250
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
243
251
|
const n = mps.length;
|
|
@@ -248,7 +256,9 @@ function pathDivide(...paths) {
|
|
|
248
256
|
for (const mask of subsets) {
|
|
249
257
|
const inside = [];
|
|
250
258
|
const outside = [];
|
|
259
|
+
const sources = [];
|
|
251
260
|
for (let i = 0; i < n; i++) {
|
|
261
|
+
if (mask & 1 << i) sources.push(i);
|
|
252
262
|
(mask & 1 << i ? inside : outside).push(mps[i]);
|
|
253
263
|
}
|
|
254
264
|
const [insideHead, ...insideRest] = inside;
|
|
@@ -260,10 +270,35 @@ function pathDivide(...paths) {
|
|
|
260
270
|
region = polygonClipping.difference(region, outsideUnion);
|
|
261
271
|
if (region.length === 0) continue;
|
|
262
272
|
}
|
|
263
|
-
out.push(multiPolygonToPath(region));
|
|
273
|
+
out.push({ path: multiPolygonToPath(region), sources });
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
function pathTrim(...paths) {
|
|
278
|
+
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
279
|
+
const out = [];
|
|
280
|
+
let cover = null;
|
|
281
|
+
for (let i = mps.length - 1; i >= 0; i--) {
|
|
282
|
+
const shown = cover ? polygonClipping.difference(mps[i], cover) : mps[i];
|
|
283
|
+
if (shown.length > 0) out.unshift({ path: multiPolygonToPath(shown), sources: [i] });
|
|
284
|
+
cover = cover ? polygonClipping.union(cover, mps[i]) : mps[i];
|
|
264
285
|
}
|
|
265
286
|
return out;
|
|
266
287
|
}
|
|
288
|
+
function pathMerge(paths, keyOf) {
|
|
289
|
+
const groups = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const piece of pathTrim(...paths)) {
|
|
291
|
+
const key = keyOf(piece.sources[0]);
|
|
292
|
+
const group = groups.get(key);
|
|
293
|
+
if (group) group.push(piece);
|
|
294
|
+
else groups.set(key, [piece]);
|
|
295
|
+
}
|
|
296
|
+
const top = (p) => p.sources[p.sources.length - 1];
|
|
297
|
+
return [...groups.values()].map((g) => g.length === 1 ? g[0] : {
|
|
298
|
+
path: pathUnion(...g.map((p) => p.path)),
|
|
299
|
+
sources: g.map((p) => p.sources[0])
|
|
300
|
+
}).sort((a, b) => top(a) - top(b));
|
|
301
|
+
}
|
|
267
302
|
function popcount(n) {
|
|
268
303
|
let c = 0;
|
|
269
304
|
while (n) {
|
|
@@ -272,14 +307,14 @@ function popcount(n) {
|
|
|
272
307
|
}
|
|
273
308
|
return c;
|
|
274
309
|
}
|
|
275
|
-
function
|
|
310
|
+
function pathCropPieces(...paths) {
|
|
276
311
|
if (paths.length < 2) return [];
|
|
277
312
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
278
313
|
const mask = mps[mps.length - 1];
|
|
279
314
|
const out = [];
|
|
280
315
|
for (let i = 0; i < mps.length - 1; i++) {
|
|
281
316
|
const clipped = polygonClipping.intersection(mps[i], mask);
|
|
282
|
-
if (clipped.length > 0) out.push(multiPolygonToPath(clipped));
|
|
317
|
+
if (clipped.length > 0) out.push({ path: multiPolygonToPath(clipped), sources: [i] });
|
|
283
318
|
}
|
|
284
319
|
return out;
|
|
285
320
|
}
|
|
@@ -475,7 +510,8 @@ function crossingsOnSegment(boundaries, cut, k, out) {
|
|
|
475
510
|
|
|
476
511
|
// src/booleans/contours.ts
|
|
477
512
|
function parseContours(path) {
|
|
478
|
-
|
|
513
|
+
const fillsOpen = path.kind === "rect" || fillsOpenSubpaths(path);
|
|
514
|
+
return parseSubpaths(path).filter((s) => s.closed || fillsOpen).map((s) => s.segs);
|
|
479
515
|
}
|
|
480
516
|
function parseSubpaths(path, implicitClose = true) {
|
|
481
517
|
if (path.kind === "rect") {
|
|
@@ -958,6 +994,45 @@ function snipStations(sub, cut) {
|
|
|
958
994
|
return groups.filter((g) => g.length % 2 === 1).map((g) => g[0]).filter((st) => sub.closed || !same(st.pt, first) && !same(st.pt, end));
|
|
959
995
|
}
|
|
960
996
|
|
|
961
|
-
|
|
997
|
+
// src/booleans/snipAtCrossings.ts
|
|
998
|
+
function snipPathsAtCrossings(paths, opts = {}) {
|
|
999
|
+
const tol = opts.flattenTolerance ?? 0.05;
|
|
1000
|
+
const cuts = paths.map((p) => cutLinesOf(p, tol));
|
|
1001
|
+
return paths.map((path, i) => {
|
|
1002
|
+
let pieces = [path];
|
|
1003
|
+
let cut = false;
|
|
1004
|
+
cuts.forEach((lines, j) => {
|
|
1005
|
+
if (j === i) return;
|
|
1006
|
+
for (const line of lines) {
|
|
1007
|
+
pieces = pieces.flatMap((piece) => {
|
|
1008
|
+
const snipped = snipPathByPolyline(piece, line);
|
|
1009
|
+
if (!snipped) return [piece];
|
|
1010
|
+
cut = true;
|
|
1011
|
+
return snipped;
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
return cut ? pieces : null;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
function cutLinesOf(path, tol) {
|
|
1019
|
+
return parseSubpaths(path, false).map(({ segs, closed }) => {
|
|
1020
|
+
const flat = [segs[0][0].x, segs[0][0].y];
|
|
1021
|
+
for (const s of segs) {
|
|
1022
|
+
if (s.length === 2) flat.push(s[1].x, s[1].y);
|
|
1023
|
+
else if (s.length === 3) flattenQuadratic(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, tol, flat);
|
|
1024
|
+
else flattenCubic(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, s[3].x, s[3].y, tol, flat);
|
|
1025
|
+
}
|
|
1026
|
+
const pts = [];
|
|
1027
|
+
for (let k = 0; k < flat.length; k += 2) pts.push({ x: flat[k], y: flat[k + 1] });
|
|
1028
|
+
if (closed) {
|
|
1029
|
+
const end = pts[pts.length - 1];
|
|
1030
|
+
if (end.x !== pts[0].x || end.y !== pts[0].y) pts.push({ ...pts[0] });
|
|
1031
|
+
}
|
|
1032
|
+
return pts;
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
|
|
962
1037
|
//# sourceMappingURL=index.js.map
|
|
963
1038
|
//# sourceMappingURL=index.js.map
|