@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.
@@ -0,0 +1,269 @@
1
+ import { Box } from './box.js';
2
+ import { intersectLineRect } from './intersections.js';
3
+ import { Point } from './point.js';
4
+ import { Polygon } from './polygon.js';
5
+ const rot = (r) => r.rotation ?? 0;
6
+ const elev = (r) => r.elevation ?? 0;
7
+ /**
8
+ * A rectangle that may be rotated about its center (center + size + rotation + elevation; the
9
+ * unrotated box is not exposed). Immutable value object: readonly getters; transforms return new
10
+ * unless a method `target` is passed. Rotation in radians, positive = clockwise (y-down).
11
+ */
12
+ export class Rect {
13
+ /** Marker so callers can narrow the type at runtime. */
14
+ isRect = true;
15
+ /** Internal mutable backing for the center point. */
16
+ _center = new Point();
17
+ /** Internal mutable backing for the size vector (x = width, y = height). */
18
+ _size = new Point();
19
+ /** Internal mutable rotation in radians. */
20
+ _rotation = 0;
21
+ /** Internal mutable elevation (z). */
22
+ _elevation = 0;
23
+ /**
24
+ * Constructs a {@link Rect} from center, size, rotation, and elevation.
25
+ * @param center - center point `{ x, y }`
26
+ * @param size - dimensions `{ x: width, y: height }`
27
+ * @param rotation - rotation in radians, positive = clockwise (y-down); defaults to 0
28
+ * @param elevation - planar elevation (z); defaults to 0
29
+ */
30
+ constructor(center, size, rotation = 0, elevation = 0) {
31
+ this.set(center.x, center.y, size.x, size.y, rotation, elevation);
32
+ }
33
+ /**
34
+ * Constructs a {@link Rect} from a min/max corner pair.
35
+ * @param min - minimum (top-left) corner `{ x, y }`
36
+ * @param max - maximum (bottom-right) corner `{ x, y }`
37
+ * @param rotation - rotation in radians; defaults to 0
38
+ * @param elevation - planar elevation (z); defaults to 0
39
+ * @returns a new {@link Rect} whose center is the midpoint of min/max
40
+ */
41
+ static fromMinMax(min, max, rotation = 0, elevation = 0) {
42
+ return new Rect({ x: (min.x + max.x) / 2, y: (min.y + max.y) / 2 }, { x: Math.abs(max.x - min.x), y: Math.abs(max.y - min.y) }, rotation, elevation);
43
+ }
44
+ /**
45
+ * The center point of this rect.
46
+ * @returns a readonly `{ x, y }` view of the center
47
+ */
48
+ get center() {
49
+ return this._center;
50
+ }
51
+ /**
52
+ * The size (width/height) of this rect. `width` aliases `x`; `height` aliases `y`.
53
+ * @returns a readonly point view with `width` and `height` accessors
54
+ */
55
+ get size() {
56
+ return this._size;
57
+ }
58
+ /**
59
+ * Rotation in radians (positive = clockwise, y-down).
60
+ * @returns the current rotation
61
+ */
62
+ get rotation() {
63
+ return this._rotation;
64
+ }
65
+ /**
66
+ * Planar elevation (z coordinate of the rect plane).
67
+ * @returns the current elevation
68
+ */
69
+ get elevation() {
70
+ return this._elevation;
71
+ }
72
+ /**
73
+ * Returns the four corners of this rect, rotated about its center.
74
+ * @returns array of four {@link Point} instances: top-left, top-right, bottom-right, bottom-left
75
+ */
76
+ corners() {
77
+ return rectCorners(this);
78
+ }
79
+ /**
80
+ * The axis-aligned bounding box that contains this (possibly rotated) rect.
81
+ * @returns a {@link Box} representing the AABB
82
+ */
83
+ get bounds() {
84
+ return rectBounds(this);
85
+ }
86
+ /**
87
+ * Converts this rect to a triangulated {@link Polygon}, embedding the elevation as `z`.
88
+ * @returns a {@link Polygon} with four vertices and two triangle indices
89
+ */
90
+ toPolygon() {
91
+ return rectToPolygon(this);
92
+ }
93
+ /**
94
+ * Returns whether a point lies inside this (possibly rotated) rect.
95
+ * @param p - the point to test `{ x, y }`
96
+ * @returns `true` if `p` is inside or on the boundary of this rect
97
+ */
98
+ containsPoint(p) {
99
+ return rectContainsPoint(this, p);
100
+ }
101
+ /**
102
+ * Returns a new rect translated by `offset`. If `target` is provided, writes into `target`
103
+ * and returns it instead of allocating.
104
+ * @param offset - translation vector `{ x, y }`
105
+ * @param target - optional rect to mutate in place
106
+ * @returns the translated rect
107
+ */
108
+ translate(offset, target) {
109
+ return rectTranslate(this, offset, target);
110
+ }
111
+ /**
112
+ * Returns a new rect with rotation increased by `angle`. If `target` is provided, writes into
113
+ * `target` and returns it instead of allocating.
114
+ * @param angle - angle to add in radians
115
+ * @param target - optional rect to mutate in place
116
+ * @returns the rotated rect
117
+ */
118
+ rotate(angle, target) {
119
+ return rectRotate(this, angle, target);
120
+ }
121
+ /**
122
+ * Returns a new rect scaled by `factor` about `origin`: the center moves toward/away from `origin`
123
+ * by `factor` and the size is multiplied by `factor`; rotation and elevation are preserved.
124
+ * If `target` is provided, writes into it instead of allocating.
125
+ * @param factor - uniform scale or per-axis `{ x, y }` scale
126
+ * @param origin - the fixed point of the scaling; defaults to this rect's center
127
+ * @param target - optional rect to mutate in place
128
+ * @returns the scaled rect
129
+ */
130
+ scale(factor, origin = this._center, target) {
131
+ return rectScale(this, factor, origin, target);
132
+ }
133
+ /**
134
+ * Points where `line` crosses this rect. Delegates to {@link intersectLineRect}.
135
+ * @param line - the segment
136
+ * @returns the intersection points
137
+ */
138
+ intersectLine(line) {
139
+ return intersectLineRect(line, this);
140
+ }
141
+ /**
142
+ * Returns a copy of this rect. If `target` is provided, writes into `target` and returns it.
143
+ * @param target - optional rect to mutate in place
144
+ * @returns the cloned rect
145
+ */
146
+ clone(target) {
147
+ return (target ?? new Rect(this._center, this._size)).set(this._center.x, this._center.y, this._size.x, this._size.y, this._rotation, this._elevation);
148
+ }
149
+ /**
150
+ * Mutates this rect in place (center, size, rotation, elevation). Prefer the immutable
151
+ * transforms; use this (or a transform `target`) only for hot-path reuse.
152
+ * @param cx - center x
153
+ * @param cy - center y
154
+ * @param sx - size x (width)
155
+ * @param sy - size y (height)
156
+ * @param rotation - rotation in radians
157
+ * @param elevation - planar elevation (z)
158
+ * @returns `this` for chaining
159
+ */
160
+ set(cx, cy, sx, sy, rotation, elevation) {
161
+ this._center.set(cx, cy);
162
+ this._size.set(sx, sy);
163
+ this._rotation = rotation;
164
+ this._elevation = elevation;
165
+ return this;
166
+ }
167
+ }
168
+ /**
169
+ * Returns the four corners of a {@link RectLike}, rotated about its center.
170
+ * Order: top-left, top-right, bottom-right, bottom-left.
171
+ * @param r - the rect-like to compute corners for
172
+ * @returns array of four {@link Point} instances
173
+ */
174
+ export function rectCorners(r) {
175
+ const hx = r.size.x / 2;
176
+ const hy = r.size.y / 2;
177
+ const a = rot(r);
178
+ const cos = Math.cos(a);
179
+ const sin = Math.sin(a);
180
+ const cx = r.center.x;
181
+ const cy = r.center.y;
182
+ const corner = (ox, oy) => new Point(cx + ox * cos - oy * sin, cy + ox * sin + oy * cos);
183
+ return [corner(-hx, -hy), corner(hx, -hy), corner(hx, hy), corner(-hx, hy)];
184
+ }
185
+ /**
186
+ * Returns the axis-aligned bounding box (AABB) that contains all four corners of a
187
+ * {@link RectLike}.
188
+ * @param r - the rect-like to compute bounds for
189
+ * @returns a {@link Box} representing the AABB
190
+ */
191
+ export function rectBounds(r) {
192
+ return Box.fromUnion(...rectCorners(r).map((p) => Box.fromMinMax(p, p)));
193
+ }
194
+ /**
195
+ * Converts a {@link RectLike} to a triangulated {@link Polygon} via fan-triangulation, embedding
196
+ * the elevation as `z` on each vertex. Rect corners form a convex quad, so
197
+ * {@link Polygon.fromConvexRing} is valid here.
198
+ * @param r - the rect-like to convert
199
+ * @returns a {@link Polygon} with four vertices and two triangle indices
200
+ */
201
+ export function rectToPolygon(r) {
202
+ return Polygon.fromConvexRing(rectCorners(r), elev(r));
203
+ }
204
+ /**
205
+ * Returns whether a point lies inside a (possibly rotated) {@link RectLike} by transforming the
206
+ * point into the rect's local axis-aligned space.
207
+ * @param r - the rect-like to test against
208
+ * @param p - the point to test `{ x, y }`
209
+ * @returns `true` if `p` is inside or on the boundary of `r`
210
+ */
211
+ export function rectContainsPoint(r, p) {
212
+ const a = -rot(r);
213
+ const cos = Math.cos(a);
214
+ const sin = Math.sin(a);
215
+ const dx = p.x - r.center.x;
216
+ const dy = p.y - r.center.y;
217
+ const lx = dx * cos - dy * sin;
218
+ const ly = dx * sin + dy * cos;
219
+ return Math.abs(lx) <= r.size.x / 2 && Math.abs(ly) <= r.size.y / 2;
220
+ }
221
+ /**
222
+ * Returns a new rect translated by `offset`. If `target` is provided, writes into `target` and
223
+ * returns it instead of allocating a new instance.
224
+ * @param r - the source rect-like
225
+ * @param offset - translation vector `{ x, y }`
226
+ * @param target - optional {@link Rect} to mutate in place
227
+ * @returns the translated rect
228
+ */
229
+ export function rectTranslate(r, offset, target) {
230
+ const cx = r.center.x + offset.x;
231
+ const cy = r.center.y + offset.y;
232
+ return target
233
+ ? target.set(cx, cy, r.size.x, r.size.y, rot(r), elev(r))
234
+ : new Rect({ x: cx, y: cy }, r.size, rot(r), elev(r));
235
+ }
236
+ /**
237
+ * Returns a new rect with rotation increased by `angle`. If `target` is provided, writes into
238
+ * `target` and returns it instead of allocating a new instance.
239
+ * @param r - the source rect-like
240
+ * @param angle - angle to add in radians
241
+ * @param target - optional {@link Rect} to mutate in place
242
+ * @returns the rotated rect
243
+ */
244
+ export function rectRotate(r, angle, target) {
245
+ return target
246
+ ? target.set(r.center.x, r.center.y, r.size.x, r.size.y, rot(r) + angle, elev(r))
247
+ : new Rect(r.center, r.size, rot(r) + angle, elev(r));
248
+ }
249
+ /**
250
+ * Returns a new rect scaled by `factor` about `origin`: the center moves toward/away from `origin`
251
+ * by `factor` and the size is multiplied by `factor`; rotation and elevation are preserved. If
252
+ * `target` is provided, writes into it instead of allocating a new instance.
253
+ * @param r - the source rect-like
254
+ * @param factor - uniform scale or per-axis `{ x, y }` scale
255
+ * @param origin - the fixed point of the scaling; defaults to `r`'s center
256
+ * @param target - optional {@link Rect} to mutate in place
257
+ * @returns the scaled rect
258
+ */
259
+ export function rectScale(r, factor, origin = r.center, target) {
260
+ const fx = typeof factor === 'number' ? factor : factor.x;
261
+ const fy = typeof factor === 'number' ? factor : factor.y;
262
+ const cx = origin.x + (r.center.x - origin.x) * fx;
263
+ const cy = origin.y + (r.center.y - origin.y) * fy;
264
+ const sx = r.size.x * fx;
265
+ const sy = r.size.y * fy;
266
+ return target
267
+ ? target.set(cx, cy, sx, sy, rot(r), elev(r))
268
+ : new Rect({ x: cx, y: cy }, { x: sx, y: sy }, rot(r), elev(r));
269
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@expofp/geometry",
3
+ "version": "3.8.0",
4
+ "type": "module",
5
+ "description": "ExpoFP SDK internal: shared geometry primitives",
6
+ "homepage": "https://developer.expofp.com/",
7
+ "license": "MIT",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ "./package.json": "./package.json",
16
+ ".": {
17
+ "@expofp/source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "dependencies": {
24
+ "tslib": "^2.3.0"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "!**/*.tsbuildinfo",
29
+ "!**/*.map"
30
+ ]
31
+ }