@weasel-js/geom 1.10.0 → 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/booleans/index.d.ts +22 -3
- package/dist/booleans/index.js +41 -2
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-MDT7F32A.js → chunk-7WFFCHRL.js} +7 -5
- package/dist/chunk-7WFFCHRL.js.map +1 -0
- package/dist/{chunk-WQMYOCFB.js → chunk-IDXY2NCA.js} +24 -14
- 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/cubicMath-BW-HJrCE.d.ts +16 -0
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/index.js +1 -1
- package/dist/index.d.ts +516 -208
- package/dist/index.js +1184 -541
- package/dist/index.js.map +1 -1
- package/dist/{path-Bf9KeVxC.d.ts → path-ljHxobyu.d.ts} +1 -1
- package/dist/tessellate/index.d.ts +4 -1
- package/dist/tessellate/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-MDT7F32A.js.map +0 -1
- package/dist/chunk-WQMYOCFB.js.map +0 -1
- package/dist/chunk-XS6ERH3X.js +0 -34
- package/dist/chunk-XS6ERH3X.js.map +0 -1
- package/dist/cubicMath-CFXbq6vp.d.ts +0 -116
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/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;
|
|
@@ -137,4 +137,23 @@ declare function splitPathByPolyline(path: Path, cut: readonly Point[], opts?: S
|
|
|
137
137
|
*/
|
|
138
138
|
declare function snipPathByPolyline(path: Path, cut: readonly Point[]): Path[] | null;
|
|
139
139
|
|
|
140
|
-
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { elevateQuadraticToCubic, splitQuadraticAt, splitCubicAt } from '../chunk-URQMDR6Q.js';
|
|
2
|
-
import { DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, segmentsCross, pointInPolygon } from '../chunk-
|
|
2
|
+
import { DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, segmentsCross, pointInPolygon } from '../chunk-IDXY2NCA.js';
|
|
3
3
|
import { EPS } from '../chunk-755Y4YZS.js';
|
|
4
4
|
import { PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M } from '../chunk-OXH3NOXC.js';
|
|
5
5
|
import polygonClipping from 'polygon-clipping';
|
|
@@ -994,6 +994,45 @@ function snipStations(sub, cut) {
|
|
|
994
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));
|
|
995
995
|
}
|
|
996
996
|
|
|
997
|
-
|
|
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 };
|
|
998
1037
|
//# sourceMappingURL=index.js.map
|
|
999
1038
|
//# sourceMappingURL=index.js.map
|