@expofp/geometry 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +42 -0
- package/dist/lib/angles.d.ts +36 -0
- package/dist/lib/angles.js +50 -0
- package/dist/lib/box.d.ts +282 -0
- package/dist/lib/box.js +362 -0
- package/dist/lib/geo.d.ts +98 -0
- package/dist/lib/geo.js +159 -0
- package/dist/lib/intersections.d.ts +30 -0
- package/dist/lib/intersections.js +72 -0
- package/dist/lib/line.d.ts +134 -0
- package/dist/lib/line.js +167 -0
- package/dist/lib/mesh.d.ts +166 -0
- package/dist/lib/mesh.js +162 -0
- package/dist/lib/point.d.ts +206 -0
- package/dist/lib/point.js +237 -0
- package/dist/lib/polygon.d.ts +169 -0
- package/dist/lib/polygon.js +220 -0
- package/dist/lib/rect.d.ts +200 -0
- package/dist/lib/rect.js +269 -0
- package/package.json +31 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Box } from './box.js';
|
|
2
|
+
import { type TriangleIndex } from './mesh.js';
|
|
3
|
+
import { type Point2Like, type PointLike } from './point.js';
|
|
4
|
+
/** Structural polygon: 2D vertices plus triangle indices and an optional elevation. */
|
|
5
|
+
export interface PolygonLike {
|
|
6
|
+
/** Polygon vertices (2D — xy only). */
|
|
7
|
+
readonly vertices: readonly Point2Like[];
|
|
8
|
+
/** Triangle index triplets. */
|
|
9
|
+
readonly indices: readonly TriangleIndex[];
|
|
10
|
+
/** Elevation in 3D space (default 0). */
|
|
11
|
+
readonly elevation?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A planar triangulated polygon: 2D vertices (`{ x, y }`), triangle index triplets, and an
|
|
15
|
+
* `elevation` scalar that positions the plane in 3D space. Immutable value object: readonly
|
|
16
|
+
* getters; transforms return a new instance unless a `target` is passed.
|
|
17
|
+
*
|
|
18
|
+
* Polygon structurally satisfies `MeshLike` so its transform methods can forward to the
|
|
19
|
+
* canonical mesh free functions (`meshTranslate`, `meshScale`, `meshRotate`) without duplicating
|
|
20
|
+
* any transform logic.
|
|
21
|
+
*/
|
|
22
|
+
export declare class Polygon {
|
|
23
|
+
/** Marker so callers can narrow the type at runtime. */
|
|
24
|
+
readonly isPolygon = true;
|
|
25
|
+
/** Internal mutable 2D vertex array. */
|
|
26
|
+
private _vertices;
|
|
27
|
+
/** Internal triangle index array. */
|
|
28
|
+
private _indices;
|
|
29
|
+
/** Elevation in 3D space. */
|
|
30
|
+
private _elevation;
|
|
31
|
+
/** Cached axis-aligned bounding box (xy only). */
|
|
32
|
+
private _bounds;
|
|
33
|
+
/**
|
|
34
|
+
* Constructs a polygon from a 2D vertex list, triangle index triplets, and an optional
|
|
35
|
+
* elevation.
|
|
36
|
+
* @param vertices - source vertices; stored as `{ x, y }` (z is dropped) (default `[]`)
|
|
37
|
+
* @param indices - triangle index triplets referencing positions in `vertices` (default `[]`)
|
|
38
|
+
* @param elevation - elevation of the plane in 3D space (default 0)
|
|
39
|
+
*/
|
|
40
|
+
constructor(vertices?: readonly Point2Like[], indices?: readonly TriangleIndex[], elevation?: number);
|
|
41
|
+
/**
|
|
42
|
+
* Fan-triangulates an ordered convex ring into a {@link Polygon}.
|
|
43
|
+
*
|
|
44
|
+
* Generates triangles `[0, i, i+1]` for `i` in `1 .. n-2`. This triangulation is only valid
|
|
45
|
+
* for **convex** input rings — fan triangulation produces incorrect results for concave rings.
|
|
46
|
+
* For a concave or arbitrary shape, construct `Polygon` directly with explicit `indices`.
|
|
47
|
+
*
|
|
48
|
+
* Use `containsPoint` on the resulting polygon (via `polygonContainsPoint`) to test inclusion.
|
|
49
|
+
* @param points - ordered convex polygon vertices (CCW or CW)
|
|
50
|
+
* @param elevation - elevation of the plane in 3D space (default 0)
|
|
51
|
+
* @returns a new polygon, or an empty polygon when fewer than 3 points are given
|
|
52
|
+
*/
|
|
53
|
+
static fromConvexRing(points: readonly Point2Like[], elevation?: number): Polygon;
|
|
54
|
+
/**
|
|
55
|
+
* Merges two or more polygons into a single {@link Polygon}, offsetting each polygon's
|
|
56
|
+
* triangle indices by the cumulative vertex count so they remain valid. Forwards to
|
|
57
|
+
* {@link meshMerge}.
|
|
58
|
+
* @param polygons - polygons to merge
|
|
59
|
+
* @returns a new merged polygon (elevation taken from first polygon, or 0)
|
|
60
|
+
*/
|
|
61
|
+
static merge(...polygons: PolygonLike[]): Polygon;
|
|
62
|
+
/**
|
|
63
|
+
* Readonly array of polygon vertices as `{ x, y }` objects.
|
|
64
|
+
* @returns the vertex array
|
|
65
|
+
*/
|
|
66
|
+
get vertices(): readonly Point2Like[];
|
|
67
|
+
/**
|
|
68
|
+
* Readonly array of triangle index triplets.
|
|
69
|
+
* @returns the index array
|
|
70
|
+
*/
|
|
71
|
+
get indices(): readonly TriangleIndex[];
|
|
72
|
+
/**
|
|
73
|
+
* Elevation of this polygon's plane in 3D space.
|
|
74
|
+
* @returns the elevation
|
|
75
|
+
*/
|
|
76
|
+
get elevation(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Cached axis-aligned bounding {@link Box} of this polygon (xy only).
|
|
79
|
+
* @returns the bounding box
|
|
80
|
+
*/
|
|
81
|
+
get bounds(): Box;
|
|
82
|
+
/**
|
|
83
|
+
* Total area of this polygon (sum of absolute triangle areas). See {@link polygonArea}.
|
|
84
|
+
* @returns the area in square units
|
|
85
|
+
*/
|
|
86
|
+
get area(): number;
|
|
87
|
+
/**
|
|
88
|
+
* Returns true when `point` lies inside or on the boundary of this polygon.
|
|
89
|
+
* See {@link polygonContainsPoint}.
|
|
90
|
+
* @param point - the point to test
|
|
91
|
+
* @param opts - options controlling tolerance
|
|
92
|
+
* @param opts.areaTolerance - relative tolerance per triangle (0–1); default 0.01
|
|
93
|
+
* @returns true when `point` is contained
|
|
94
|
+
*/
|
|
95
|
+
containsPoint(point: Point2Like, opts?: {
|
|
96
|
+
areaTolerance?: number;
|
|
97
|
+
}): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Returns a new polygon translated by `offset`. Forwards to {@link meshTranslate}.
|
|
100
|
+
* The elevation is preserved on the result.
|
|
101
|
+
* @param offset - 2D translation vector
|
|
102
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
103
|
+
* @returns translated polygon
|
|
104
|
+
*/
|
|
105
|
+
translate(offset: Point2Like, target?: Polygon): Polygon;
|
|
106
|
+
/**
|
|
107
|
+
* Returns a new polygon scaled by `factor` about `origin`. Forwards to {@link meshScale}.
|
|
108
|
+
* The elevation is preserved on the result.
|
|
109
|
+
* @param factor - uniform scale or per-axis `{ x, y }` scale
|
|
110
|
+
* @param origin - the fixed point of the scaling; defaults to the bounding-box center
|
|
111
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
112
|
+
* @returns scaled polygon
|
|
113
|
+
*/
|
|
114
|
+
scale(factor: number | Point2Like, origin?: Point2Like, target?: Polygon): Polygon;
|
|
115
|
+
/**
|
|
116
|
+
* Returns a new polygon rotated by `angle` radians about `origin`. Forwards to
|
|
117
|
+
* {@link meshRotate}.
|
|
118
|
+
* The elevation is preserved on the result.
|
|
119
|
+
* @param angle - rotation angle in radians (positive = clockwise in y-down space)
|
|
120
|
+
* @param origin - the pivot of the rotation; defaults to the bounding-box center
|
|
121
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
122
|
+
* @returns rotated polygon
|
|
123
|
+
*/
|
|
124
|
+
rotate(angle: number, origin?: Point2Like, target?: Polygon): Polygon;
|
|
125
|
+
/**
|
|
126
|
+
* Mutates this polygon in place, replacing vertices/indices and optionally the elevation, then
|
|
127
|
+
* refreshing the cached bounds. Vertices are stored as 2D `{ x, y }` — any z from the input is
|
|
128
|
+
* dropped. Do not optimize this clone away; the mesh transform free functions rely on it to avoid
|
|
129
|
+
* aliasing shared vertices. When `elevation` is omitted the current elevation is preserved (this
|
|
130
|
+
* keeps the 2-arg `target.set(v, i)` forwarding path in the mesh free functions intact). Use
|
|
131
|
+
* only for hot-path reuse.
|
|
132
|
+
* @param vertices - new vertex list (z ignored)
|
|
133
|
+
* @param indices - new triangle index list
|
|
134
|
+
* @param elevation - new elevation; defaults to the current elevation when omitted
|
|
135
|
+
* @returns `this`
|
|
136
|
+
*/
|
|
137
|
+
set(vertices: readonly PointLike[], indices: readonly TriangleIndex[], elevation?: number): this;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Total signed area of the polygon, computed as the sum of the absolute areas of each triangle
|
|
141
|
+
* in the index list (fan/shoelace per triangle). The result is always non-negative regardless of
|
|
142
|
+
* vertex winding order.
|
|
143
|
+
* @param p - polygon or polygon-like object
|
|
144
|
+
* @returns the total area in square units
|
|
145
|
+
*/
|
|
146
|
+
export declare function polygonArea(p: PolygonLike): number;
|
|
147
|
+
/**
|
|
148
|
+
* Returns true when `point` lies inside or on the boundary of polygon `p`. The test iterates
|
|
149
|
+
* over every triangle in `p.indices` and checks whether `point` lies within any of them using a
|
|
150
|
+
* signed-area (barycentric) test. Works for both convex shapes and arbitrary triangulated meshes.
|
|
151
|
+
* Zero-area (degenerate) triangles are skipped — they have no interior and never contain a point.
|
|
152
|
+
*
|
|
153
|
+
* The optional `areaTolerance` (default `0.01`, i.e. 1%) expands each triangle's containment
|
|
154
|
+
* boundary by a margin proportional to its area, admitting points that lie just outside the
|
|
155
|
+
* strict geometric boundary.
|
|
156
|
+
* @param p - polygon or polygon-like object
|
|
157
|
+
* @param point - the point to test
|
|
158
|
+
* @param opts - options controlling the tolerance
|
|
159
|
+
* @param opts.areaTolerance - relative slack per triangle, as a fraction of that triangle's own
|
|
160
|
+
* area; default `0.01` (1%), which absorbs float/GPS boundary noise. `0` is the strict geometric
|
|
161
|
+
* test. Intended range is small (`~[0, 0.1]`); values `>= 1` let the slack reach or exceed a
|
|
162
|
+
* triangle's area and admit points well outside the polygon, so they are not meaningful for
|
|
163
|
+
* containment.
|
|
164
|
+
* @returns true when `point` is contained within any triangle (within tolerance)
|
|
165
|
+
*/
|
|
166
|
+
export declare function polygonContainsPoint(p: PolygonLike, point: Point2Like, opts?: {
|
|
167
|
+
areaTolerance?: number;
|
|
168
|
+
}): boolean;
|
|
169
|
+
//# sourceMappingURL=polygon.d.ts.map
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Box } from './box.js';
|
|
2
|
+
import { meshBounds, meshMerge, meshRotate, meshScale, meshTranslate, } from './mesh.js';
|
|
3
|
+
/**
|
|
4
|
+
* A planar triangulated polygon: 2D vertices (`{ x, y }`), triangle index triplets, and an
|
|
5
|
+
* `elevation` scalar that positions the plane in 3D space. Immutable value object: readonly
|
|
6
|
+
* getters; transforms return a new instance unless a `target` is passed.
|
|
7
|
+
*
|
|
8
|
+
* Polygon structurally satisfies `MeshLike` so its transform methods can forward to the
|
|
9
|
+
* canonical mesh free functions (`meshTranslate`, `meshScale`, `meshRotate`) without duplicating
|
|
10
|
+
* any transform logic.
|
|
11
|
+
*/
|
|
12
|
+
export class Polygon {
|
|
13
|
+
/** Marker so callers can narrow the type at runtime. */
|
|
14
|
+
isPolygon = true;
|
|
15
|
+
/** Internal mutable 2D vertex array. */
|
|
16
|
+
_vertices = [];
|
|
17
|
+
/** Internal triangle index array. */
|
|
18
|
+
_indices = [];
|
|
19
|
+
/** Elevation in 3D space. */
|
|
20
|
+
_elevation;
|
|
21
|
+
/** Cached axis-aligned bounding box (xy only). */
|
|
22
|
+
_bounds = new Box();
|
|
23
|
+
/**
|
|
24
|
+
* Constructs a polygon from a 2D vertex list, triangle index triplets, and an optional
|
|
25
|
+
* elevation.
|
|
26
|
+
* @param vertices - source vertices; stored as `{ x, y }` (z is dropped) (default `[]`)
|
|
27
|
+
* @param indices - triangle index triplets referencing positions in `vertices` (default `[]`)
|
|
28
|
+
* @param elevation - elevation of the plane in 3D space (default 0)
|
|
29
|
+
*/
|
|
30
|
+
constructor(vertices = [], indices = [], elevation = 0) {
|
|
31
|
+
this._elevation = elevation;
|
|
32
|
+
this.set(vertices, indices);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Fan-triangulates an ordered convex ring into a {@link Polygon}.
|
|
36
|
+
*
|
|
37
|
+
* Generates triangles `[0, i, i+1]` for `i` in `1 .. n-2`. This triangulation is only valid
|
|
38
|
+
* for **convex** input rings — fan triangulation produces incorrect results for concave rings.
|
|
39
|
+
* For a concave or arbitrary shape, construct `Polygon` directly with explicit `indices`.
|
|
40
|
+
*
|
|
41
|
+
* Use `containsPoint` on the resulting polygon (via `polygonContainsPoint`) to test inclusion.
|
|
42
|
+
* @param points - ordered convex polygon vertices (CCW or CW)
|
|
43
|
+
* @param elevation - elevation of the plane in 3D space (default 0)
|
|
44
|
+
* @returns a new polygon, or an empty polygon when fewer than 3 points are given
|
|
45
|
+
*/
|
|
46
|
+
static fromConvexRing(points, elevation = 0) {
|
|
47
|
+
if (points.length < 3)
|
|
48
|
+
return new Polygon([], [], elevation);
|
|
49
|
+
const indices = [];
|
|
50
|
+
for (let i = 1; i <= points.length - 2; i++) {
|
|
51
|
+
indices.push([0, i, i + 1]);
|
|
52
|
+
}
|
|
53
|
+
return new Polygon(points, indices, elevation);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Merges two or more polygons into a single {@link Polygon}, offsetting each polygon's
|
|
57
|
+
* triangle indices by the cumulative vertex count so they remain valid. Forwards to
|
|
58
|
+
* {@link meshMerge}.
|
|
59
|
+
* @param polygons - polygons to merge
|
|
60
|
+
* @returns a new merged polygon (elevation taken from first polygon, or 0)
|
|
61
|
+
*/
|
|
62
|
+
static merge(...polygons) {
|
|
63
|
+
return meshMerge(polygons, new Polygon([], [], polygons[0]?.elevation ?? 0));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Readonly array of polygon vertices as `{ x, y }` objects.
|
|
67
|
+
* @returns the vertex array
|
|
68
|
+
*/
|
|
69
|
+
get vertices() {
|
|
70
|
+
return this._vertices;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Readonly array of triangle index triplets.
|
|
74
|
+
* @returns the index array
|
|
75
|
+
*/
|
|
76
|
+
get indices() {
|
|
77
|
+
return this._indices;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Elevation of this polygon's plane in 3D space.
|
|
81
|
+
* @returns the elevation
|
|
82
|
+
*/
|
|
83
|
+
get elevation() {
|
|
84
|
+
return this._elevation;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Cached axis-aligned bounding {@link Box} of this polygon (xy only).
|
|
88
|
+
* @returns the bounding box
|
|
89
|
+
*/
|
|
90
|
+
get bounds() {
|
|
91
|
+
return this._bounds;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Total area of this polygon (sum of absolute triangle areas). See {@link polygonArea}.
|
|
95
|
+
* @returns the area in square units
|
|
96
|
+
*/
|
|
97
|
+
get area() {
|
|
98
|
+
return polygonArea(this);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns true when `point` lies inside or on the boundary of this polygon.
|
|
102
|
+
* See {@link polygonContainsPoint}.
|
|
103
|
+
* @param point - the point to test
|
|
104
|
+
* @param opts - options controlling tolerance
|
|
105
|
+
* @param opts.areaTolerance - relative tolerance per triangle (0–1); default 0.01
|
|
106
|
+
* @returns true when `point` is contained
|
|
107
|
+
*/
|
|
108
|
+
containsPoint(point, opts) {
|
|
109
|
+
return polygonContainsPoint(this, point, opts);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns a new polygon translated by `offset`. Forwards to {@link meshTranslate}.
|
|
113
|
+
* The elevation is preserved on the result.
|
|
114
|
+
* @param offset - 2D translation vector
|
|
115
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
116
|
+
* @returns translated polygon
|
|
117
|
+
*/
|
|
118
|
+
translate(offset, target) {
|
|
119
|
+
return meshTranslate(this, offset, target ?? new Polygon([], [], this._elevation));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns a new polygon scaled by `factor` about `origin`. Forwards to {@link meshScale}.
|
|
123
|
+
* The elevation is preserved on the result.
|
|
124
|
+
* @param factor - uniform scale or per-axis `{ x, y }` scale
|
|
125
|
+
* @param origin - the fixed point of the scaling; defaults to the bounding-box center
|
|
126
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
127
|
+
* @returns scaled polygon
|
|
128
|
+
*/
|
|
129
|
+
scale(factor, origin = this._bounds.center, target) {
|
|
130
|
+
return meshScale(this, factor, origin, target ?? new Polygon([], [], this._elevation));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns a new polygon rotated by `angle` radians about `origin`. Forwards to
|
|
134
|
+
* {@link meshRotate}.
|
|
135
|
+
* The elevation is preserved on the result.
|
|
136
|
+
* @param angle - rotation angle in radians (positive = clockwise in y-down space)
|
|
137
|
+
* @param origin - the pivot of the rotation; defaults to the bounding-box center
|
|
138
|
+
* @param target - optional polygon to write into instead of allocating a new one
|
|
139
|
+
* @returns rotated polygon
|
|
140
|
+
*/
|
|
141
|
+
rotate(angle, origin = this._bounds.center, target) {
|
|
142
|
+
return meshRotate(this, angle, origin, target ?? new Polygon([], [], this._elevation));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Mutates this polygon in place, replacing vertices/indices and optionally the elevation, then
|
|
146
|
+
* refreshing the cached bounds. Vertices are stored as 2D `{ x, y }` — any z from the input is
|
|
147
|
+
* dropped. Do not optimize this clone away; the mesh transform free functions rely on it to avoid
|
|
148
|
+
* aliasing shared vertices. When `elevation` is omitted the current elevation is preserved (this
|
|
149
|
+
* keeps the 2-arg `target.set(v, i)` forwarding path in the mesh free functions intact). Use
|
|
150
|
+
* only for hot-path reuse.
|
|
151
|
+
* @param vertices - new vertex list (z ignored)
|
|
152
|
+
* @param indices - new triangle index list
|
|
153
|
+
* @param elevation - new elevation; defaults to the current elevation when omitted
|
|
154
|
+
* @returns `this`
|
|
155
|
+
*/
|
|
156
|
+
set(vertices, indices, elevation = this._elevation) {
|
|
157
|
+
this._vertices = vertices.map((v) => ({ x: v.x, y: v.y }));
|
|
158
|
+
this._indices = indices;
|
|
159
|
+
this._elevation = elevation;
|
|
160
|
+
this._bounds = meshBounds(this);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Total signed area of the polygon, computed as the sum of the absolute areas of each triangle
|
|
166
|
+
* in the index list (fan/shoelace per triangle). The result is always non-negative regardless of
|
|
167
|
+
* vertex winding order.
|
|
168
|
+
* @param p - polygon or polygon-like object
|
|
169
|
+
* @returns the total area in square units
|
|
170
|
+
*/
|
|
171
|
+
export function polygonArea(p) {
|
|
172
|
+
let area = 0;
|
|
173
|
+
for (const [ai, bi, ci] of p.indices) {
|
|
174
|
+
const va = p.vertices[ai], vb = p.vertices[bi], vc = p.vertices[ci];
|
|
175
|
+
area += Math.abs((vb.x - va.x) * (vc.y - va.y) - (vc.x - va.x) * (vb.y - va.y)) / 2;
|
|
176
|
+
}
|
|
177
|
+
return area;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Returns true when `point` lies inside or on the boundary of polygon `p`. The test iterates
|
|
181
|
+
* over every triangle in `p.indices` and checks whether `point` lies within any of them using a
|
|
182
|
+
* signed-area (barycentric) test. Works for both convex shapes and arbitrary triangulated meshes.
|
|
183
|
+
* Zero-area (degenerate) triangles are skipped — they have no interior and never contain a point.
|
|
184
|
+
*
|
|
185
|
+
* The optional `areaTolerance` (default `0.01`, i.e. 1%) expands each triangle's containment
|
|
186
|
+
* boundary by a margin proportional to its area, admitting points that lie just outside the
|
|
187
|
+
* strict geometric boundary.
|
|
188
|
+
* @param p - polygon or polygon-like object
|
|
189
|
+
* @param point - the point to test
|
|
190
|
+
* @param opts - options controlling the tolerance
|
|
191
|
+
* @param opts.areaTolerance - relative slack per triangle, as a fraction of that triangle's own
|
|
192
|
+
* area; default `0.01` (1%), which absorbs float/GPS boundary noise. `0` is the strict geometric
|
|
193
|
+
* test. Intended range is small (`~[0, 0.1]`); values `>= 1` let the slack reach or exceed a
|
|
194
|
+
* triangle's area and admit points well outside the polygon, so they are not meaningful for
|
|
195
|
+
* containment.
|
|
196
|
+
* @returns true when `point` is contained within any triangle (within tolerance)
|
|
197
|
+
*/
|
|
198
|
+
export function polygonContainsPoint(p, point, opts) {
|
|
199
|
+
const tol = opts?.areaTolerance ?? 0.01;
|
|
200
|
+
for (const [ai, bi, ci] of p.indices) {
|
|
201
|
+
const va = p.vertices[ai], vb = p.vertices[bi], vc = p.vertices[ci];
|
|
202
|
+
const triArea = Math.abs((vb.x - va.x) * (vc.y - va.y) - (vc.x - va.x) * (vb.y - va.y)) / 2;
|
|
203
|
+
// A zero-area triangle (single-point or collinear vertices) has no interior, so it
|
|
204
|
+
// contains nothing. Skip it: otherwise threshold = 0 and the barycentric test below
|
|
205
|
+
// sees every d = 0 and reports the entire plane as contained.
|
|
206
|
+
if (triArea === 0)
|
|
207
|
+
continue;
|
|
208
|
+
const threshold = tol * triArea;
|
|
209
|
+
// Barycentric signed-area test: the point is inside when d1, d2, d3 all share the
|
|
210
|
+
// same sign (or lie within the threshold margin).
|
|
211
|
+
const d1 = (point.x - vb.x) * (va.y - vb.y) - (va.x - vb.x) * (point.y - vb.y);
|
|
212
|
+
const d2 = (point.x - vc.x) * (vb.y - vc.y) - (vb.x - vc.x) * (point.y - vc.y);
|
|
213
|
+
const d3 = (point.x - va.x) * (vc.y - va.y) - (vc.x - va.x) * (point.y - va.y);
|
|
214
|
+
const hasNeg = d1 < -threshold || d2 < -threshold || d3 < -threshold;
|
|
215
|
+
const hasPos = d1 > threshold || d2 > threshold || d3 > threshold;
|
|
216
|
+
if (!(hasNeg && hasPos))
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Box } from './box.js';
|
|
2
|
+
import type { LineLike } from './line.js';
|
|
3
|
+
import { Point, type Point2Like, type SizeLike } from './point.js';
|
|
4
|
+
import { Polygon } from './polygon.js';
|
|
5
|
+
/** Structural rotatable rect. */
|
|
6
|
+
export interface RectLike {
|
|
7
|
+
/** Center point. */
|
|
8
|
+
readonly center: Point2Like;
|
|
9
|
+
/** Width/height. */
|
|
10
|
+
readonly size: Point2Like;
|
|
11
|
+
/** Rotation in radians; positive = clockwise (y-down). Defaults to 0. */
|
|
12
|
+
readonly rotation?: number;
|
|
13
|
+
/** Planar elevation (z). Defaults to 0. */
|
|
14
|
+
readonly elevation?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A rectangle that may be rotated about its center (center + size + rotation + elevation; the
|
|
18
|
+
* unrotated box is not exposed). Immutable value object: readonly getters; transforms return new
|
|
19
|
+
* unless a method `target` is passed. Rotation in radians, positive = clockwise (y-down).
|
|
20
|
+
*/
|
|
21
|
+
export declare class Rect {
|
|
22
|
+
/** Marker so callers can narrow the type at runtime. */
|
|
23
|
+
readonly isRect = true;
|
|
24
|
+
/** Internal mutable backing for the center point. */
|
|
25
|
+
private readonly _center;
|
|
26
|
+
/** Internal mutable backing for the size vector (x = width, y = height). */
|
|
27
|
+
private readonly _size;
|
|
28
|
+
/** Internal mutable rotation in radians. */
|
|
29
|
+
private _rotation;
|
|
30
|
+
/** Internal mutable elevation (z). */
|
|
31
|
+
private _elevation;
|
|
32
|
+
/**
|
|
33
|
+
* Constructs a {@link Rect} from center, size, rotation, and elevation.
|
|
34
|
+
* @param center - center point `{ x, y }`
|
|
35
|
+
* @param size - dimensions `{ x: width, y: height }`
|
|
36
|
+
* @param rotation - rotation in radians, positive = clockwise (y-down); defaults to 0
|
|
37
|
+
* @param elevation - planar elevation (z); defaults to 0
|
|
38
|
+
*/
|
|
39
|
+
constructor(center: Point2Like, size: Point2Like, rotation?: number, elevation?: number);
|
|
40
|
+
/**
|
|
41
|
+
* Constructs a {@link Rect} from a min/max corner pair.
|
|
42
|
+
* @param min - minimum (top-left) corner `{ x, y }`
|
|
43
|
+
* @param max - maximum (bottom-right) corner `{ x, y }`
|
|
44
|
+
* @param rotation - rotation in radians; defaults to 0
|
|
45
|
+
* @param elevation - planar elevation (z); defaults to 0
|
|
46
|
+
* @returns a new {@link Rect} whose center is the midpoint of min/max
|
|
47
|
+
*/
|
|
48
|
+
static fromMinMax(min: Point2Like, max: Point2Like, rotation?: number, elevation?: number): Rect;
|
|
49
|
+
/**
|
|
50
|
+
* The center point of this rect.
|
|
51
|
+
* @returns a readonly `{ x, y }` view of the center
|
|
52
|
+
*/
|
|
53
|
+
get center(): Readonly<Point2Like>;
|
|
54
|
+
/**
|
|
55
|
+
* The size (width/height) of this rect. `width` aliases `x`; `height` aliases `y`.
|
|
56
|
+
* @returns a readonly point view with `width` and `height` accessors
|
|
57
|
+
*/
|
|
58
|
+
get size(): SizeLike;
|
|
59
|
+
/**
|
|
60
|
+
* Rotation in radians (positive = clockwise, y-down).
|
|
61
|
+
* @returns the current rotation
|
|
62
|
+
*/
|
|
63
|
+
get rotation(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Planar elevation (z coordinate of the rect plane).
|
|
66
|
+
* @returns the current elevation
|
|
67
|
+
*/
|
|
68
|
+
get elevation(): number;
|
|
69
|
+
/**
|
|
70
|
+
* Returns the four corners of this rect, rotated about its center.
|
|
71
|
+
* @returns array of four {@link Point} instances: top-left, top-right, bottom-right, bottom-left
|
|
72
|
+
*/
|
|
73
|
+
corners(): Point[];
|
|
74
|
+
/**
|
|
75
|
+
* The axis-aligned bounding box that contains this (possibly rotated) rect.
|
|
76
|
+
* @returns a {@link Box} representing the AABB
|
|
77
|
+
*/
|
|
78
|
+
get bounds(): Box;
|
|
79
|
+
/**
|
|
80
|
+
* Converts this rect to a triangulated {@link Polygon}, embedding the elevation as `z`.
|
|
81
|
+
* @returns a {@link Polygon} with four vertices and two triangle indices
|
|
82
|
+
*/
|
|
83
|
+
toPolygon(): Polygon;
|
|
84
|
+
/**
|
|
85
|
+
* Returns whether a point lies inside this (possibly rotated) rect.
|
|
86
|
+
* @param p - the point to test `{ x, y }`
|
|
87
|
+
* @returns `true` if `p` is inside or on the boundary of this rect
|
|
88
|
+
*/
|
|
89
|
+
containsPoint(p: Point2Like): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Returns a new rect translated by `offset`. If `target` is provided, writes into `target`
|
|
92
|
+
* and returns it instead of allocating.
|
|
93
|
+
* @param offset - translation vector `{ x, y }`
|
|
94
|
+
* @param target - optional rect to mutate in place
|
|
95
|
+
* @returns the translated rect
|
|
96
|
+
*/
|
|
97
|
+
translate(offset: Point2Like, target?: Rect): Rect;
|
|
98
|
+
/**
|
|
99
|
+
* Returns a new rect with rotation increased by `angle`. If `target` is provided, writes into
|
|
100
|
+
* `target` and returns it instead of allocating.
|
|
101
|
+
* @param angle - angle to add in radians
|
|
102
|
+
* @param target - optional rect to mutate in place
|
|
103
|
+
* @returns the rotated rect
|
|
104
|
+
*/
|
|
105
|
+
rotate(angle: number, target?: Rect): Rect;
|
|
106
|
+
/**
|
|
107
|
+
* Returns a new rect scaled by `factor` about `origin`: the center moves toward/away from `origin`
|
|
108
|
+
* by `factor` and the size is multiplied by `factor`; rotation and elevation are preserved.
|
|
109
|
+
* If `target` is provided, writes into it instead of allocating.
|
|
110
|
+
* @param factor - uniform scale or per-axis `{ x, y }` scale
|
|
111
|
+
* @param origin - the fixed point of the scaling; defaults to this rect's center
|
|
112
|
+
* @param target - optional rect to mutate in place
|
|
113
|
+
* @returns the scaled rect
|
|
114
|
+
*/
|
|
115
|
+
scale(factor: number | Point2Like, origin?: Point2Like, target?: Rect): Rect;
|
|
116
|
+
/**
|
|
117
|
+
* Points where `line` crosses this rect. Delegates to {@link intersectLineRect}.
|
|
118
|
+
* @param line - the segment
|
|
119
|
+
* @returns the intersection points
|
|
120
|
+
*/
|
|
121
|
+
intersectLine(line: LineLike): Point[];
|
|
122
|
+
/**
|
|
123
|
+
* Returns a copy of this rect. If `target` is provided, writes into `target` and returns it.
|
|
124
|
+
* @param target - optional rect to mutate in place
|
|
125
|
+
* @returns the cloned rect
|
|
126
|
+
*/
|
|
127
|
+
clone(target?: Rect): Rect;
|
|
128
|
+
/**
|
|
129
|
+
* Mutates this rect in place (center, size, rotation, elevation). Prefer the immutable
|
|
130
|
+
* transforms; use this (or a transform `target`) only for hot-path reuse.
|
|
131
|
+
* @param cx - center x
|
|
132
|
+
* @param cy - center y
|
|
133
|
+
* @param sx - size x (width)
|
|
134
|
+
* @param sy - size y (height)
|
|
135
|
+
* @param rotation - rotation in radians
|
|
136
|
+
* @param elevation - planar elevation (z)
|
|
137
|
+
* @returns `this` for chaining
|
|
138
|
+
*/
|
|
139
|
+
set(cx: number, cy: number, sx: number, sy: number, rotation: number, elevation: number): this;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns the four corners of a {@link RectLike}, rotated about its center.
|
|
143
|
+
* Order: top-left, top-right, bottom-right, bottom-left.
|
|
144
|
+
* @param r - the rect-like to compute corners for
|
|
145
|
+
* @returns array of four {@link Point} instances
|
|
146
|
+
*/
|
|
147
|
+
export declare function rectCorners(r: RectLike): Point[];
|
|
148
|
+
/**
|
|
149
|
+
* Returns the axis-aligned bounding box (AABB) that contains all four corners of a
|
|
150
|
+
* {@link RectLike}.
|
|
151
|
+
* @param r - the rect-like to compute bounds for
|
|
152
|
+
* @returns a {@link Box} representing the AABB
|
|
153
|
+
*/
|
|
154
|
+
export declare function rectBounds(r: RectLike): Box;
|
|
155
|
+
/**
|
|
156
|
+
* Converts a {@link RectLike} to a triangulated {@link Polygon} via fan-triangulation, embedding
|
|
157
|
+
* the elevation as `z` on each vertex. Rect corners form a convex quad, so
|
|
158
|
+
* {@link Polygon.fromConvexRing} is valid here.
|
|
159
|
+
* @param r - the rect-like to convert
|
|
160
|
+
* @returns a {@link Polygon} with four vertices and two triangle indices
|
|
161
|
+
*/
|
|
162
|
+
export declare function rectToPolygon(r: RectLike): Polygon;
|
|
163
|
+
/**
|
|
164
|
+
* Returns whether a point lies inside a (possibly rotated) {@link RectLike} by transforming the
|
|
165
|
+
* point into the rect's local axis-aligned space.
|
|
166
|
+
* @param r - the rect-like to test against
|
|
167
|
+
* @param p - the point to test `{ x, y }`
|
|
168
|
+
* @returns `true` if `p` is inside or on the boundary of `r`
|
|
169
|
+
*/
|
|
170
|
+
export declare function rectContainsPoint(r: RectLike, p: Point2Like): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Returns a new rect translated by `offset`. If `target` is provided, writes into `target` and
|
|
173
|
+
* returns it instead of allocating a new instance.
|
|
174
|
+
* @param r - the source rect-like
|
|
175
|
+
* @param offset - translation vector `{ x, y }`
|
|
176
|
+
* @param target - optional {@link Rect} to mutate in place
|
|
177
|
+
* @returns the translated rect
|
|
178
|
+
*/
|
|
179
|
+
export declare function rectTranslate(r: RectLike, offset: Point2Like, target?: Rect): Rect;
|
|
180
|
+
/**
|
|
181
|
+
* Returns a new rect with rotation increased by `angle`. If `target` is provided, writes into
|
|
182
|
+
* `target` and returns it instead of allocating a new instance.
|
|
183
|
+
* @param r - the source rect-like
|
|
184
|
+
* @param angle - angle to add in radians
|
|
185
|
+
* @param target - optional {@link Rect} to mutate in place
|
|
186
|
+
* @returns the rotated rect
|
|
187
|
+
*/
|
|
188
|
+
export declare function rectRotate(r: RectLike, angle: number, target?: Rect): Rect;
|
|
189
|
+
/**
|
|
190
|
+
* Returns a new rect scaled by `factor` about `origin`: the center moves toward/away from `origin`
|
|
191
|
+
* by `factor` and the size is multiplied by `factor`; rotation and elevation are preserved. If
|
|
192
|
+
* `target` is provided, writes into it instead of allocating a new instance.
|
|
193
|
+
* @param r - the source rect-like
|
|
194
|
+
* @param factor - uniform scale or per-axis `{ x, y }` scale
|
|
195
|
+
* @param origin - the fixed point of the scaling; defaults to `r`'s center
|
|
196
|
+
* @param target - optional {@link Rect} to mutate in place
|
|
197
|
+
* @returns the scaled rect
|
|
198
|
+
*/
|
|
199
|
+
export declare function rectScale(r: RectLike, factor: number | Point2Like, origin?: Point2Like, target?: Rect): Rect;
|
|
200
|
+
//# sourceMappingURL=rect.d.ts.map
|