@math.gl/geometry 5.0.0-alpha.19 → 5.0.0-alpha.20

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.
Files changed (69) hide show
  1. package/LICENSE +95 -54
  2. package/README.md +9 -0
  3. package/THIRD-PARTY-NOTICES.md +18 -0
  4. package/dist/culling/index.cjs +22 -30
  5. package/dist/culling/index.cjs.map +3 -3
  6. package/dist/culling/index.d.ts +1 -0
  7. package/dist/culling/index.d.ts.map +1 -1
  8. package/dist/culling/index.js +1 -0
  9. package/dist/culling/index.js.map +1 -1
  10. package/dist/culling/lib/algorithms/bounding-box-from-points.js +1 -1
  11. package/dist/culling/lib/algorithms/bounding-box-from-points.js.map +1 -1
  12. package/dist/culling/lib/algorithms/bounding-sphere-from-points.js +1 -1
  13. package/dist/culling/lib/algorithms/bounding-sphere-from-points.js.map +1 -1
  14. package/dist/culling/lib/algorithms/compute-eigen-decomposition.js +1 -1
  15. package/dist/culling/lib/algorithms/compute-eigen-decomposition.js.map +1 -1
  16. package/dist/culling/lib/algorithms/oriented-bounding-box-quaternion.d.ts +7 -0
  17. package/dist/culling/lib/algorithms/oriented-bounding-box-quaternion.d.ts.map +1 -0
  18. package/dist/culling/lib/algorithms/oriented-bounding-box-quaternion.js +26 -0
  19. package/dist/culling/lib/algorithms/oriented-bounding-box-quaternion.js.map +1 -0
  20. package/dist/culling/lib/bounding-volumes/bounding-sphere.js +1 -1
  21. package/dist/culling/lib/bounding-volumes/bounding-sphere.js.map +1 -1
  22. package/dist/culling/lib/bounding-volumes/oriented-bounding-box.d.ts +1 -7
  23. package/dist/culling/lib/bounding-volumes/oriented-bounding-box.d.ts.map +1 -1
  24. package/dist/culling/lib/bounding-volumes/oriented-bounding-box.js +1 -30
  25. package/dist/culling/lib/bounding-volumes/oriented-bounding-box.js.map +1 -1
  26. package/dist/culling/lib/culling-volume.js +1 -1
  27. package/dist/culling/lib/culling-volume.js.map +1 -1
  28. package/dist/culling/lib/perspective-off-center-frustum.js +1 -1
  29. package/dist/culling/lib/perspective-off-center-frustum.js.map +1 -1
  30. package/dist/culling/lib/plane.js +1 -1
  31. package/dist/culling/lib/plane.js.map +1 -1
  32. package/dist/culling/lib/ray.js +1 -1
  33. package/dist/culling/lib/ray.js.map +1 -1
  34. package/dist/index.cjs +126 -0
  35. package/dist/index.cjs.map +2 -2
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/lib/hinge-tree.d.ts +33 -0
  41. package/dist/lib/hinge-tree.d.ts.map +1 -0
  42. package/dist/lib/hinge-tree.js +144 -0
  43. package/dist/lib/hinge-tree.js.map +1 -0
  44. package/dist/polygon/index.cjs +34 -11
  45. package/dist/polygon/index.cjs.map +1 -1
  46. package/dist/polygon/index.d.ts +1 -1
  47. package/dist/polygon/index.d.ts.map +1 -1
  48. package/dist/polygon/index.js.map +1 -1
  49. package/dist/polygon/subdivide-polyline.d.ts +17 -3
  50. package/dist/polygon/subdivide-polyline.d.ts.map +1 -1
  51. package/dist/polygon/subdivide-polyline.js +44 -12
  52. package/dist/polygon/subdivide-polyline.js.map +1 -1
  53. package/package.json +6 -4
  54. package/src/culling/index.ts +5 -0
  55. package/src/culling/lib/algorithms/bounding-box-from-points.ts +1 -1
  56. package/src/culling/lib/algorithms/bounding-sphere-from-points.ts +1 -1
  57. package/src/culling/lib/algorithms/compute-eigen-decomposition.ts +1 -1
  58. package/src/culling/lib/algorithms/oriented-bounding-box-quaternion.ts +32 -0
  59. package/src/culling/lib/bounding-volumes/bounding-sphere.ts +1 -1
  60. package/src/culling/lib/bounding-volumes/oriented-bounding-box.ts +2 -37
  61. package/src/culling/lib/culling-volume.ts +1 -1
  62. package/src/culling/lib/perspective-frustum.ts +1 -1
  63. package/src/culling/lib/perspective-off-center-frustum.ts +1 -1
  64. package/src/culling/lib/plane.ts +1 -1
  65. package/src/culling/lib/ray.ts +1 -1
  66. package/src/index.ts +3 -0
  67. package/src/lib/hinge-tree.ts +170 -0
  68. package/src/polygon/index.ts +5 -1
  69. package/src/polygon/subdivide-polyline.ts +76 -13
@@ -2,7 +2,15 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
4
4
 
5
- /** Configuration for sampling straight source edges through a nonlinear transform. */
5
+ /** A constant-speed source edge, sampled at fractions of its declared length.
6
+ * Endpoints must match the factory inputs exactly. Samples are copied before use.
7
+ */
8
+ export type PolylineEdgeSampler = {
9
+ readonly length: number;
10
+ sample: (fraction: number) => ArrayLike<number>;
11
+ };
12
+
13
+ /** Configuration for sampling source edges through a nonlinear transform. */
6
14
  export type SubdividePolylineOptions = {
7
15
  /** Maps a source position to target coordinates; null or nonfinite output rejects the geometry.
8
16
  * Must be deterministic. A fresh array is passed on every invocation.
@@ -10,11 +18,21 @@ export type SubdividePolylineOptions = {
10
18
  transform: (position: readonly number[]) => ArrayLike<number> | null;
11
19
  /** Positive error tolerance in target-coordinate units, measured across all output components. */
12
20
  tolerance: number;
21
+ /** Optional factory, called once per original edge with independent endpoint copies.
22
+ * Without it, source coordinates interpolate linearly. Must be deterministic.
23
+ */
24
+ createEdgeSampler?: (
25
+ start: readonly number[],
26
+ end: readonly number[],
27
+ segmentIndex: number
28
+ ) => PolylineEdgeSampler;
13
29
  /** Number of source components per vertex. Default 2. */
14
30
  size?: 2 | 3;
15
31
  /** Number of target components per vertex. Defaults to size. */
16
32
  targetSize?: 2 | 3;
17
- /** Optional positive maximum Euclidean edge length in source-coordinate units. */
33
+ /** Positive maximum leaf length: Euclidean source distance by default, or the
34
+ * custom sampler's declared length multiplied by its fraction interval.
35
+ */
18
36
  maxSegmentLength?: number;
19
37
  /** Maximum bisection depth per input edge. Default 16; maximum 30. */
20
38
  maxDepth?: number;
@@ -41,7 +59,8 @@ type Sample = {source: number[]; target: number[]; fraction: number};
41
59
  /** Adaptively subdivides a flat polyline by sampling a supplied coordinate transform.
42
60
  * Each leaf edge checks its quarter, midpoint and three-quarter positions against linear
43
61
  * interpolation of its target endpoints. This is a sampled criterion, not a guaranteed
44
- * bound for arbitrary transforms. Source edges and extra source components interpolate linearly.
62
+ * bound for arbitrary transforms. Source components interpolate linearly unless a custom
63
+ * constant-speed source-edge sampler is supplied.
45
64
  * Explicitly closed rings stay closed. Split seams and clip invalid domains before calling.
46
65
  * @throws RangeError For invalid options/coordinates, rejected transform samples, or exhausted limits.
47
66
  * Exceptions thrown by the transform itself propagate unchanged.
@@ -53,6 +72,7 @@ export function subdividePolyline(
53
72
  const {
54
73
  transform,
55
74
  tolerance,
75
+ createEdgeSampler,
56
76
  size = 2,
57
77
  targetSize = size,
58
78
  maxSegmentLength = Infinity,
@@ -61,6 +81,7 @@ export function subdividePolyline(
61
81
  } = options;
62
82
  if (
63
83
  typeof transform !== 'function' ||
84
+ (createEdgeSampler !== undefined && typeof createEdgeSampler !== 'function') ||
64
85
  !Number.isFinite(tolerance) ||
65
86
  tolerance <= 0 ||
66
87
  (size !== 2 && size !== 3) ||
@@ -113,24 +134,48 @@ export function subdividePolyline(
113
134
  segmentFractions.push(point.fraction);
114
135
  }
115
136
 
116
- function interpolate(a: Sample, b: Sample, fraction: number): Sample {
117
- // Weighted sum avoids overflow from subtracting opposite large finite values.
118
- const source = a.source.map((value, i) => value * (1 - fraction) + b.source[i] * fraction);
119
- return sample(source, a.fraction * (1 - fraction) + b.fraction * fraction);
137
+ function sampledSource(sampler: PolylineEdgeSampler, fraction: number): number[] {
138
+ const value = sampler.sample(fraction);
139
+ if (!value || value.length !== size) throw new RangeError('Invalid source-edge sample size');
140
+ const source = Array.from(value);
141
+ if (!source.every(Number.isFinite)) throw new RangeError('Nonfinite source-edge sample');
142
+ return source;
143
+ }
144
+
145
+ function interpolate(
146
+ a: Sample,
147
+ b: Sample,
148
+ fraction: number,
149
+ sampler?: PolylineEdgeSampler
150
+ ): Sample {
151
+ const originalFraction = a.fraction * (1 - fraction) + b.fraction * fraction;
152
+ // Always sample the original edge, never a new curve fitted to recursive endpoints.
153
+ const source = sampler
154
+ ? sampledSource(sampler, originalFraction)
155
+ : a.source.map((value, i) => value * (1 - fraction) + b.source[i] * fraction);
156
+ return sample(source, originalFraction);
120
157
  }
121
158
 
122
- function subdivide(a: Sample, b: Sample, segment: number, depth: number): void {
159
+ function subdivide(
160
+ a: Sample,
161
+ b: Sample,
162
+ segment: number,
163
+ depth: number,
164
+ sampler?: PolylineEdgeSampler
165
+ ): void {
123
166
  let error = 0;
124
167
  let midpoint: Sample;
125
168
  for (const fraction of [0.25, 0.5, 0.75]) {
126
- const point = interpolate(a, b, fraction);
169
+ const point = interpolate(a, b, fraction, sampler);
127
170
  if (fraction === 0.5) midpoint = point;
128
171
  const delta = point.target.map(
129
172
  (value, i) => value - (a.target[i] * (1 - fraction) + b.target[i] * fraction)
130
173
  );
131
174
  error = Math.max(error, Math.hypot(...delta));
132
175
  }
133
- const sourceLength = Math.hypot(...a.source.map((value, i) => value - b.source[i]));
176
+ const sourceLength = sampler
177
+ ? sampler.length * (b.fraction - a.fraction)
178
+ : Math.hypot(...a.source.map((value, i) => value - b.source[i]));
134
179
  if (error <= tolerance && sourceLength <= maxSegmentLength) {
135
180
  append(b, segment);
136
181
  return;
@@ -138,8 +183,8 @@ export function subdividePolyline(
138
183
  if (depth >= maxDepth) {
139
184
  throw new RangeError(`Polyline subdivision exceeded maxDepth on input edge ${segment}`);
140
185
  }
141
- subdivide(a, midpoint!, segment, depth + 1);
142
- subdivide(midpoint!, b, segment, depth + 1);
186
+ subdivide(a, midpoint!, segment, depth + 1, sampler);
187
+ subdivide(midpoint!, b, segment, depth + 1, sampler);
143
188
  }
144
189
 
145
190
  if (vertexCount) {
@@ -147,7 +192,25 @@ export function subdividePolyline(
147
192
  append(previous, 0);
148
193
  for (let i = 1; i < vertexCount; i++) {
149
194
  const next = sample(sources.slice(i * size, (i + 1) * size), 1);
150
- subdivide({...previous, fraction: 0}, next, i - 1, 0);
195
+ const start = {...previous, fraction: 0};
196
+ const sampler = createEdgeSampler?.(start.source.slice(), next.source.slice(), i - 1);
197
+ if (createEdgeSampler) {
198
+ if (
199
+ !sampler ||
200
+ typeof sampler.sample !== 'function' ||
201
+ !Number.isFinite(sampler.length) ||
202
+ sampler.length < 0
203
+ ) {
204
+ throw new RangeError('Invalid source-edge sampler');
205
+ }
206
+ for (const endpoint of [start, next]) {
207
+ const sampled = sampledSource(sampler, endpoint.fraction);
208
+ if (sampled.some((value, component) => value !== endpoint.source[component])) {
209
+ throw new RangeError('Source-edge sampler endpoints must match input');
210
+ }
211
+ }
212
+ }
213
+ subdivide(start, next, i - 1, 0, sampler);
151
214
  previous = next;
152
215
  }
153
216
  }