@expofp/geometry 3.26.0 → 3.27.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.
@@ -8,7 +8,7 @@ import { type RectLike } from './rect.js';
8
8
  * have a defined elevation and they differ by more than `elevationTolerance`.
9
9
  * @param a - first line segment, defined by `p0` and `p1`
10
10
  * @param b - second line segment, defined by `p0` and `p1`
11
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
11
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
12
12
  * @returns `{ point, onLine1, onLine2 }`, or `null` when the lines are parallel or elevation-incompatible
13
13
  */
14
14
  export declare function lineIntersection(a: LineLike, b: LineLike, elevationTolerance?: number): {
@@ -23,7 +23,7 @@ export declare function lineIntersection(a: LineLike, b: LineLike, elevationTole
23
23
  * by more than `elevationTolerance`, returns an empty array.
24
24
  * @param line - the segment, defined by `p0` and `p1`
25
25
  * @param rect - the rect (possibly rotated; a `Box` is accepted as an unrotated rect)
26
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
26
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
27
27
  * @returns the intersection points (0–2 for a convex rect)
28
28
  */
29
29
  export declare function intersectLineRect(line: LineLike, rect: RectLike, elevationTolerance?: number): Point[];
@@ -1,7 +1,5 @@
1
- import { pointLerp } from './point.js';
1
+ import { ELEVATION_TOLERANCE, pointLerp } from './point.js';
2
2
  import { rectCorners } from './rect.js';
3
- /** Default tolerance for elevation comparison. */
4
- const DEFAULT_ELEVATION_TOLERANCE = 1e-3;
5
3
  /**
6
4
  * Computes the crossing of lines `a` and `b` using the parametric form. Returns the crossing
7
5
  * point plus flags indicating whether the crossing lies within each segment's `[0, 1]` parameter
@@ -9,10 +7,10 @@ const DEFAULT_ELEVATION_TOLERANCE = 1e-3;
9
7
  * have a defined elevation and they differ by more than `elevationTolerance`.
10
8
  * @param a - first line segment, defined by `p0` and `p1`
11
9
  * @param b - second line segment, defined by `p0` and `p1`
12
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
10
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
13
11
  * @returns `{ point, onLine1, onLine2 }`, or `null` when the lines are parallel or elevation-incompatible
14
12
  */
15
- export function lineIntersection(a, b, elevationTolerance = DEFAULT_ELEVATION_TOLERANCE) {
13
+ export function lineIntersection(a, b, elevationTolerance = ELEVATION_TOLERANCE) {
16
14
  if (a.elevation != null &&
17
15
  b.elevation != null &&
18
16
  Math.abs(a.elevation - b.elevation) > elevationTolerance) {
@@ -35,7 +33,7 @@ export function lineIntersection(a, b, elevationTolerance = DEFAULT_ELEVATION_TO
35
33
  * flags). See `intersectLineRect` for the higher-level helper.
36
34
  * @param a - first segment, defined by `p0` and `p1`
37
35
  * @param b - second segment, defined by `p0` and `p1`
38
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
36
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
39
37
  * @returns the intersection {@link Point}, or `null` if the segments are parallel or non-crossing
40
38
  */
41
39
  function segmentIntersection(a, b, elevationTolerance) {
@@ -49,10 +47,10 @@ function segmentIntersection(a, b, elevationTolerance) {
49
47
  * by more than `elevationTolerance`, returns an empty array.
50
48
  * @param line - the segment, defined by `p0` and `p1`
51
49
  * @param rect - the rect (possibly rotated; a `Box` is accepted as an unrotated rect)
52
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
50
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
53
51
  * @returns the intersection points (0–2 for a convex rect)
54
52
  */
55
- export function intersectLineRect(line, rect, elevationTolerance = DEFAULT_ELEVATION_TOLERANCE) {
53
+ export function intersectLineRect(line, rect, elevationTolerance = ELEVATION_TOLERANCE) {
56
54
  if (line.elevation != null &&
57
55
  rect.elevation != null &&
58
56
  Math.abs(line.elevation - rect.elevation) > elevationTolerance) {
@@ -69,7 +69,7 @@ export declare class Line {
69
69
  /**
70
70
  * Points where this line crosses `rect`.
71
71
  * @param rect - the rect (possibly rotated; a `Box` is accepted as an unrotated rect)
72
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
72
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
73
73
  * @returns the intersection points
74
74
  */
75
75
  intersectRect(rect: RectLike, elevationTolerance?: number): Point[];
@@ -87,7 +87,7 @@ export declare class Line {
87
87
  /**
88
88
  * Intersection with another segment `other` on the infinite lines.
89
89
  * @param other - the other segment
90
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
90
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
91
91
  * @returns the crossing data, or `null` when the lines are parallel or elevations are incompatible
92
92
  */
93
93
  intersect(other: LineLike, elevationTolerance?: number): {
package/dist/lib/line.js CHANGED
@@ -81,7 +81,7 @@ export class Line {
81
81
  /**
82
82
  * Points where this line crosses `rect`.
83
83
  * @param rect - the rect (possibly rotated; a `Box` is accepted as an unrotated rect)
84
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
84
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
85
85
  * @returns the intersection points
86
86
  */
87
87
  intersectRect(rect, elevationTolerance) {
@@ -99,7 +99,7 @@ export class Line {
99
99
  /**
100
100
  * Intersection with another segment `other` on the infinite lines.
101
101
  * @param other - the other segment
102
- * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to `1e-3`
102
+ * @param elevationTolerance - maximum elevation difference for a crossing to be reported; defaults to {@link ELEVATION_TOLERANCE}
103
103
  * @returns the crossing data, or `null` when the lines are parallel or elevations are incompatible
104
104
  */
105
105
  intersect(other, elevationTolerance) {
@@ -24,6 +24,8 @@ export declare class Mesh {
24
24
  private _indices;
25
25
  /** Cached axis-aligned bounding box (xy only). */
26
26
  private _bounds;
27
+ /** Cached planarity: whether every vertex sits on one plane in z. */
28
+ private _planar;
27
29
  /**
28
30
  * Constructs a mesh from a vertex list and triangle index triplets.
29
31
  * @param vertices - source vertices; each is cloned into a {@link Point} (default `[]`)
@@ -45,6 +47,18 @@ export declare class Mesh {
45
47
  * @returns the bounding box
46
48
  */
47
49
  get bounds(): Box;
50
+ /**
51
+ * Whether this mesh is flat — every vertex sits on one plane in z, within
52
+ * {@link ELEVATION_TOLERANCE}, so it has no extent worth shading along that axis. An empty mesh is
53
+ * planar. Cached at {@link set}, so reading it is free.
54
+ *
55
+ * This is the predicate that separates a lifted-but-flat mesh from a volume: the former is placed
56
+ * correctly by draw order alone, while the latter needs depth testing and shading to read as solid.
57
+ * The comparison is deliberately approximate — see {@link meshIsPlanar} for why exact equality is
58
+ * the riskier choice.
59
+ * @returns true when the mesh has no meaningful z extent
60
+ */
61
+ get isPlanar(): boolean;
48
62
  /**
49
63
  * Mutates this mesh in place, replacing vertices/indices and refreshing the cached bounds.
50
64
  * Vertices are copied into fresh {@link Point}s — do not optimize this clone away; transform
package/dist/lib/mesh.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Box } from './box.js';
2
- import { Point } from './point.js';
2
+ import { ELEVATION_TOLERANCE, Point } from './point.js';
3
3
  /**
4
4
  * A 3D triangle mesh: vertices carry an optional z coordinate, plus triangle index triplets.
5
5
  * Immutable value object: readonly getters; transforms return a new instance unless a `target`
@@ -14,6 +14,8 @@ export class Mesh {
14
14
  _indices = [];
15
15
  /** Cached axis-aligned bounding box (xy only). */
16
16
  _bounds = new Box();
17
+ /** Cached planarity: whether every vertex sits on one plane in z. */
18
+ _planar = true;
17
19
  /**
18
20
  * Constructs a mesh from a vertex list and triangle index triplets.
19
21
  * @param vertices - source vertices; each is cloned into a {@link Point} (default `[]`)
@@ -43,6 +45,20 @@ export class Mesh {
43
45
  get bounds() {
44
46
  return this._bounds;
45
47
  }
48
+ /**
49
+ * Whether this mesh is flat — every vertex sits on one plane in z, within
50
+ * {@link ELEVATION_TOLERANCE}, so it has no extent worth shading along that axis. An empty mesh is
51
+ * planar. Cached at {@link set}, so reading it is free.
52
+ *
53
+ * This is the predicate that separates a lifted-but-flat mesh from a volume: the former is placed
54
+ * correctly by draw order alone, while the latter needs depth testing and shading to read as solid.
55
+ * The comparison is deliberately approximate — see {@link meshIsPlanar} for why exact equality is
56
+ * the riskier choice.
57
+ * @returns true when the mesh has no meaningful z extent
58
+ */
59
+ get isPlanar() {
60
+ return this._planar;
61
+ }
46
62
  /**
47
63
  * Mutates this mesh in place, replacing vertices/indices and refreshing the cached bounds.
48
64
  * Vertices are copied into fresh {@link Point}s — do not optimize this clone away; transform
@@ -54,6 +70,7 @@ export class Mesh {
54
70
  set(vertices, indices) {
55
71
  this._vertices = vertices.map((v) => new Point(v.x, v.y, v.z ?? 0));
56
72
  this._indices = indices;
73
+ this._planar = meshIsPlanar(this._vertices);
57
74
  this._bounds = meshBounds(this);
58
75
  return this;
59
76
  }
@@ -114,6 +131,33 @@ export function meshBounds(m) {
114
131
  }
115
132
  return Box.fromMinMax({ x: minX, y: minY }, { x: maxX, y: maxY });
116
133
  }
134
+ /**
135
+ * Whether every vertex sits on one plane in z, within {@link ELEVATION_TOLERANCE}.
136
+ *
137
+ * Approximate, not exact, and the asymmetry is why. Calling a flat mesh non-planar is the expensive
138
+ * mistake: it would be shaded and depth-tested as a volume, and a flat face whose computed normal
139
+ * points away from the camera renders black. Calling a mesh with sub-tolerance z extent planar costs
140
+ * nothing, because there is nothing there to shade. Exporters and float round-trips produce exactly
141
+ * that kind of noise — a `1e-17` where the author meant zero — and strict equality would take the
142
+ * expensive branch on it.
143
+ *
144
+ * Every vertex is compared against the *first* one rather than against its neighbor, so a mesh that
145
+ * ramps gently is correctly a volume even though no two adjacent vertices differ by much.
146
+ *
147
+ * Vertices have already been normalized to carry a z by {@link Mesh.set}, so no defaulting is needed.
148
+ * @param vertices - the mesh's vertices
149
+ * @returns true when the vertices have no meaningful z extent
150
+ */
151
+ function meshIsPlanar(vertices) {
152
+ if (vertices.length === 0)
153
+ return true;
154
+ const z = vertices[0].z;
155
+ for (let i = 1; i < vertices.length; i++) {
156
+ if (Math.abs(vertices[i].z - z) > ELEVATION_TOLERANCE)
157
+ return false;
158
+ }
159
+ return true;
160
+ }
117
161
  export function meshMerge(meshes, target) {
118
162
  const vertices = [];
119
163
  const indices = [];
@@ -1,3 +1,15 @@
1
+ /**
2
+ * How far apart two `z` values may be and still count as the same plane, in plan units.
3
+ *
4
+ * Absolute rather than relative, because the question is about the plan's own scale: a thousandth of
5
+ * a unit is far below anything drawable, and far above the noise an exporter or a float round-trip
6
+ * introduces. Deliberate thinness is *not* noise — a slab authored half a unit thick is a volume and
7
+ * is treated as one.
8
+ *
9
+ * Shared so the package cannot answer "same plane?" two different ways: it gates elevation in
10
+ * {@link lineIntersection} and planarity in {@link Mesh.isPlanar}.
11
+ */
12
+ export declare const ELEVATION_TOLERANCE = 0.1;
1
13
  /** A 2D point/vector `{ x, y }`. */
2
14
  export interface Point2Like {
3
15
  /** Horizontal coordinate. */
package/dist/lib/point.js CHANGED
@@ -1,4 +1,16 @@
1
1
  import { fromPolar } from './angles.js';
2
+ /**
3
+ * How far apart two `z` values may be and still count as the same plane, in plan units.
4
+ *
5
+ * Absolute rather than relative, because the question is about the plan's own scale: a thousandth of
6
+ * a unit is far below anything drawable, and far above the noise an exporter or a float round-trip
7
+ * introduces. Deliberate thinness is *not* noise — a slab authored half a unit thick is a volume and
8
+ * is treated as one.
9
+ *
10
+ * Shared so the package cannot answer "same plane?" two different ways: it gates elevation in
11
+ * {@link lineIntersection} and planarity in {@link Mesh.isPlanar}.
12
+ */
13
+ export const ELEVATION_TOLERANCE = 1e-1;
2
14
  /**
3
15
  * Mutable point/vector — the low-level building block other primitives and the point free functions
4
16
  * write into. `width`/`height` alias `x`/`y` so a point used as a size reads naturally.
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@expofp/geometry",
3
- "version": "3.26.0",
3
+ "version": "3.27.1",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/expofp/efp-app.git",
7
+ "directory": "packages/geometry"
8
+ },
4
9
  "type": "module",
5
10
  "description": "ExpoFP SDK internal: shared geometry primitives",
6
11
  "homepage": "https://developer.expofp.com/",