@ifc-lite/clash 2.3.1 → 2.4.2

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 (57) hide show
  1. package/dist/adapters/step.js +1 -0
  2. package/dist/adapters/step.js.map +1 -1
  3. package/dist/analysis.d.ts +6 -6
  4. package/dist/analysis.d.ts.map +1 -1
  5. package/dist/analysis.js +30 -29
  6. package/dist/analysis.js.map +1 -1
  7. package/dist/contact/narrow-phase.js +1 -1
  8. package/dist/engine-ts/broad.d.ts.map +1 -1
  9. package/dist/engine-ts/broad.js +11 -10
  10. package/dist/engine-ts/broad.js.map +1 -1
  11. package/dist/engine-ts/depth.d.ts +83 -11
  12. package/dist/engine-ts/depth.d.ts.map +1 -1
  13. package/dist/engine-ts/depth.js +134 -67
  14. package/dist/engine-ts/depth.js.map +1 -1
  15. package/dist/engine-ts/kernel.d.ts +2 -0
  16. package/dist/engine-ts/kernel.d.ts.map +1 -1
  17. package/dist/engine-ts/narrow.d.ts +8 -0
  18. package/dist/engine-ts/narrow.d.ts.map +1 -1
  19. package/dist/engine-ts/narrow.js +24 -15
  20. package/dist/engine-ts/narrow.js.map +1 -1
  21. package/dist/engine-ts/obb-detect.d.ts +3 -0
  22. package/dist/engine-ts/obb-detect.d.ts.map +1 -0
  23. package/dist/engine-ts/obb-detect.js +298 -0
  24. package/dist/engine-ts/obb-detect.js.map +1 -0
  25. package/dist/engine-ts/obb.d.ts +14 -23
  26. package/dist/engine-ts/obb.d.ts.map +1 -1
  27. package/dist/engine-ts/obb.js +35 -140
  28. package/dist/engine-ts/obb.js.map +1 -1
  29. package/dist/engine-ts/orchestrator.d.ts.map +1 -1
  30. package/dist/engine-ts/orchestrator.js +59 -15
  31. package/dist/engine-ts/orchestrator.js.map +1 -1
  32. package/dist/engine-ts/tri-mesh.d.ts +10 -7
  33. package/dist/engine-ts/tri-mesh.d.ts.map +1 -1
  34. package/dist/engine-ts/tri-mesh.js +34 -28
  35. package/dist/engine-ts/tri-mesh.js.map +1 -1
  36. package/dist/engine-ts/ts-kernel.d.ts.map +1 -1
  37. package/dist/engine-ts/ts-kernel.js +1 -0
  38. package/dist/engine-ts/ts-kernel.js.map +1 -1
  39. package/dist/engine-wasm/wasm-kernel.d.ts.map +1 -1
  40. package/dist/engine-wasm/wasm-kernel.js +10 -1
  41. package/dist/engine-wasm/wasm-kernel.js.map +1 -1
  42. package/dist/math/aabb.d.ts +9 -0
  43. package/dist/math/aabb.d.ts.map +1 -1
  44. package/dist/math/aabb.js +13 -0
  45. package/dist/math/aabb.js.map +1 -1
  46. package/dist/math/generated/plato.g.d.ts +11 -2
  47. package/dist/math/generated/plato.g.d.ts.map +1 -1
  48. package/dist/math/generated/plato.g.js +347 -190
  49. package/dist/math/generated/plato.g.js.map +1 -1
  50. package/dist/math/triangle-intersect.d.ts +12 -6
  51. package/dist/math/triangle-intersect.d.ts.map +1 -1
  52. package/dist/math/triangle-intersect.js +12 -6
  53. package/dist/math/triangle-intersect.js.map +1 -1
  54. package/dist/types.d.ts +15 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/package.json +10 -10
@@ -0,0 +1,298 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { sub, cross, dot, scale } from '../math/vec3.js';
5
+ import { OBB_EPS } from './obb.js';
6
+ function normalize(v) {
7
+ const len = Math.sqrt(dot(v, v));
8
+ if (!(len > OBB_EPS))
9
+ return null;
10
+ return [v[0] / len, v[1] / len, v[2] / len];
11
+ }
12
+ /** Flip `n` so its largest-magnitude component is positive, so a face and its
13
+ * antipodal opposite face collapse to the same canonical axis direction. Ties
14
+ * broken in x, y, z order — identical to the Rust `canonical`. */
15
+ function canonical(n) {
16
+ const ax = Math.abs(n[0]);
17
+ const ay = Math.abs(n[1]);
18
+ const az = Math.abs(n[2]);
19
+ let idx = 0;
20
+ if (ay > ax && ay >= az)
21
+ idx = 1;
22
+ else if (az > ax && az > ay)
23
+ idx = 2;
24
+ return n[idx] < 0 ? [-n[0], -n[1], -n[2]] : n;
25
+ }
26
+ /**
27
+ * f32-ULP scale factor for a "worst-case" single-precision coordinate: for a
28
+ * value with magnitude in `[2, 4)` the true float32 ULP is `2^-22`, and for
29
+ * larger magnitudes the ULP only grows. Same constant (and reasoning) as
30
+ * `F32_ULP_SCALE` in `./depth.js`, and unlike the copies in `../contact/` it
31
+ * really is scaling the same quantity — the f32 quantisation of the same
32
+ * vertex buffer. It is still local because `./depth.js` imports THIS module
33
+ * (`obbPenetration`, `isThroughPenetration`), so importing the constant
34
+ * back out of it would close an import cycle for a one-line literal.
35
+ */
36
+ const F32_ULP_SCALE = 1 / 4_194_304; // 2^-22
37
+ /**
38
+ * Bound, in radians, on the direction error of a face normal computed as
39
+ * `cross(b - a, c - a)` from vertices that arrived as f32.
40
+ *
41
+ * The vertices carry an absolute coordinate error of about
42
+ * `max|coord| * 2^-22` (`F32_ULP_SCALE`, the same f32-ULP bound the
43
+ * penetration floor uses), so each edge does too. `|cross| = |e1||e2|sin t`,
44
+ * and perturbing the edges by `d` tilts the normal by at most
45
+ * `d * (|e1| + |e2|) / |cross|`. The denominator makes this a property of the
46
+ * TRIANGLE's conditioning rather than a constant: a sliver, or a face with
47
+ * one very short edge, resolves its normal far less sharply than a
48
+ * well-shaped one at the same distance from the origin.
49
+ *
50
+ * This is the quantity an absolute `OBB_EPS` was standing in for, and a fixed
51
+ * bound is simultaneously too loose near the origin and far too tight away
52
+ * from it: a 0.05 m thick panel 1 km out resolves its normals to ~2.7e-4, so
53
+ * `|dot|` between two genuinely perpendicular faces exceeded `1e-6` and the
54
+ * box stopped being recognised as a box purely because it had been
55
+ * translated (#5355).
56
+ */
57
+ function normalAngleError(a, b, c) {
58
+ const e1 = sub(b, a);
59
+ const e2 = sub(c, a);
60
+ let maxAbs = 0;
61
+ for (const v of [a, b, c]) {
62
+ for (const k of v) {
63
+ const m = Math.abs(k);
64
+ if (m > maxAbs)
65
+ maxAbs = m;
66
+ }
67
+ }
68
+ const coordErr = Math.max(maxAbs, 1) * F32_ULP_SCALE;
69
+ const n = cross(e1, e2);
70
+ const nLen = Math.sqrt(dot(n, n));
71
+ if (!(nLen > 0))
72
+ return [Infinity, coordErr];
73
+ const e1Len = Math.sqrt(dot(e1, e1));
74
+ const e2Len = Math.sqrt(dot(e2, e2));
75
+ return [(coordErr * (e1Len + e2Len)) / nLen, coordErr];
76
+ }
77
+ /**
78
+ * Detect whether `mesh` is a rectangular box: every (non-degenerate)
79
+ * triangle's normal falls into exactly 3 mutually orthogonal canonical
80
+ * directions, and every vertex of the triangles in a direction group lies on
81
+ * one of exactly two offset planes along that direction. That combination —
82
+ * 3 orthogonal face families, 2 planes each — forces the mesh to be a closed
83
+ * rectangular box (it rejects, for example, an axis-aligned L-shape, whose
84
+ * notch adds a third offset plane on two of the axes). Triangulation-
85
+ * independent: subdividing a box's faces adds triangles but never a new
86
+ * canonical direction or a third offset plane, so the detection (and the
87
+ * resulting `Obb`) is identical at any tessellation.
88
+ *
89
+ * Returns `null` — not a best-effort guess — when the check fails, so a
90
+ * caller that only trusts a non-null result never certifies a shape this
91
+ * function could not confirm.
92
+ */
93
+ /**
94
+ * Largest angle (radians) that f32 noise may widen a box-recognition
95
+ * tolerance to (#5474). The noise bounds are worst cases: for a thin face far
96
+ * from the origin they can exceed the 45 deg at which a box's perpendicular
97
+ * face families stop being told apart at all. Noise may widen a tolerance
98
+ * only up to this angle; data that disagrees by more is not certified a box
99
+ * (the conservative outcome: the caller falls back to the labelled AABB
100
+ * estimate). Exact data far out still certifies, because the tests compare
101
+ * the MEASURED agreement against the tolerance. Same literal as the Rust
102
+ * `MAX_NOISE_ANGLE`.
103
+ */
104
+ const MAX_NOISE_ANGLE = 0.1;
105
+ export function detectObb(mesh) {
106
+ if (mesh.count === 0)
107
+ return null;
108
+ // Everything position-dependent is measured from a point ON the mesh, not
109
+ // from the world origin (#5474). The box's centre used to be rebuilt as
110
+ // `sum_i c_i * axis_i` from offsets `dot(v, axis_i)` of ABSOLUTE vertex
111
+ // coordinates, so any error in the axes was multiplied by the element's
112
+ // distance from the origin: a 0.05 m panel 123 m out reported a 23 mm depth
113
+ // for a 20 mm overlap, and 1 km out a 65 mm one. Measured from `p0`, the
114
+ // lever arm is the element's own size.
115
+ const p0 = mesh.tri(0)[0];
116
+ const groups = [];
117
+ // Per family: the direction error bound of its representative (the first
118
+ // triangle's normal, used only to decide membership), and the area-weighted
119
+ // sum of its triangles' normals with the numerator of that sum's direction
120
+ // error bound (see `familyAxes`).
121
+ const repErr = [];
122
+ const sums = [];
123
+ const errNum = [];
124
+ const groupOfTri = new Array(mesh.count).fill(-1);
125
+ let coordErr = 0;
126
+ let reach = 0;
127
+ for (let t = 0; t < mesh.count; t += 1) {
128
+ const [a, b, c] = mesh.tri(t);
129
+ for (const v of [a, b, c]) {
130
+ const d = sub(v, p0);
131
+ const r = Math.sqrt(dot(d, d));
132
+ if (r > reach)
133
+ reach = r;
134
+ }
135
+ const raw = cross(sub(b, a), sub(c, a));
136
+ const n = normalize(raw);
137
+ if (!n)
138
+ continue; // degenerate triangle: contributes no face-normal evidence
139
+ const cn = canonical(n);
140
+ const [err, triCoordErr] = normalAngleError(a, b, c);
141
+ if (triCoordErr > coordErr)
142
+ coordErr = triCoordErr;
143
+ let gi = -1;
144
+ for (let g = 0; g < groups.length; g += 1) {
145
+ // Two normals of the same face agree to within the sum of their own
146
+ // direction errors (`1 - cos e <= e^2 / 2`). `OBB_EPS` stays as the
147
+ // floor, so a mesh at the origin groups exactly as before; far out, a
148
+ // thin face's triangles no longer split into a 4th "family" and
149
+ // decertify a perfect box (#5474).
150
+ const e = Math.min(repErr[g] + err, MAX_NOISE_ANGLE);
151
+ if (1 - dot(groups[g], cn) <= Math.max(OBB_EPS, 0.5 * e * e)) {
152
+ gi = g;
153
+ break;
154
+ }
155
+ }
156
+ if (gi === -1) {
157
+ if (groups.length >= 3)
158
+ return null; // a 4th face-normal family: not a box
159
+ groups.push(cn);
160
+ repErr.push(err);
161
+ sums.push([0, 0, 0]);
162
+ errNum.push(0);
163
+ gi = groups.length - 1;
164
+ }
165
+ // `raw` is twice the triangle's area along its normal, so the sum is
166
+ // area-weighted; oriented to the representative so opposite faces add
167
+ // rather than cancel.
168
+ const sign = dot(raw, groups[gi]) < 0 ? -1 : 1;
169
+ const sum = sums[gi];
170
+ for (let k = 0; k < 3; k += 1)
171
+ sum[k] += sign * raw[k];
172
+ errNum[gi] += err * Math.sqrt(dot(raw, raw));
173
+ groupOfTri[t] = gi;
174
+ }
175
+ if (groups.length !== 3)
176
+ return null;
177
+ const frame = familyAxes(sums, errNum);
178
+ if (!frame)
179
+ return null;
180
+ const [axes, axisErr] = frame;
181
+ for (let i = 0; i < 3; i += 1) {
182
+ for (let j = i + 1; j < 3; j += 1) {
183
+ // The families' own normals must be perpendicular, to within the sum of
184
+ // their direction errors; `OBB_EPS` stays as a FLOOR so a mesh at the
185
+ // origin is judged as strictly as before #5355.
186
+ const ni = scale(sums[i], 1 / Math.sqrt(dot(sums[i], sums[i])));
187
+ const nj = scale(sums[j], 1 / Math.sqrt(dot(sums[j], sums[j])));
188
+ const tol = Math.max(Math.min(axisErr[i] + axisErr[j], MAX_NOISE_ANGLE), OBB_EPS);
189
+ if (Math.abs(dot(ni, nj)) > tol)
190
+ return null;
191
+ }
192
+ }
193
+ const minOff = [Infinity, Infinity, Infinity];
194
+ const maxOff = [-Infinity, -Infinity, -Infinity];
195
+ for (let t = 0; t < mesh.count; t += 1) {
196
+ const gi = groupOfTri[t];
197
+ if (gi === -1)
198
+ continue;
199
+ const [a, b, c] = mesh.tri(t);
200
+ for (const v of [a, b, c]) {
201
+ const o = dot(sub(v, p0), axes[gi]);
202
+ if (o < minOff[gi])
203
+ minOff[gi] = o;
204
+ if (o > maxOff[gi])
205
+ maxOff[gi] = o;
206
+ }
207
+ }
208
+ // Reject a 3rd offset plane on any axis (e.g. an L-shaped footprint). A
209
+ // vertex on one of the two planes is off it by its own f32 rounding
210
+ // (`coordErr` bounds the difference of two vertices' roundings) plus the
211
+ // axis's direction error across the mesh (`reach` from `p0`); the relative
212
+ // `OBB_EPS` term is the floor that applied before #5474.
213
+ for (let t = 0; t < mesh.count; t += 1) {
214
+ const gi = groupOfTri[t];
215
+ if (gi === -1)
216
+ continue;
217
+ const [a, b, c] = mesh.tri(t);
218
+ const scaleOff = Math.max(Math.max(1, Math.abs(minOff[gi])), Math.abs(maxOff[gi]));
219
+ const tol = Math.max(OBB_EPS * scaleOff, coordErr + Math.min(axisErr[gi], MAX_NOISE_ANGLE) * reach);
220
+ for (const v of [a, b, c]) {
221
+ const o = dot(sub(v, p0), axes[gi]);
222
+ const nearMin = Math.abs(o - minOff[gi]) <= tol;
223
+ const nearMax = Math.abs(o - maxOff[gi]) <= tol;
224
+ if (!nearMin && !nearMax)
225
+ return null;
226
+ }
227
+ }
228
+ const half = [0, 0, 0];
229
+ const c0 = [0, 0, 0];
230
+ for (let i = 0; i < 3; i += 1) {
231
+ half[i] = (maxOff[i] - minOff[i]) / 2;
232
+ c0[i] = (maxOff[i] + minOff[i]) / 2;
233
+ // Reject a zero-thickness "box": a face family whose triangles are all
234
+ // coplanar passes the 2-plane test above (`minOff === maxOff`) with no
235
+ // positive extent along that axis — an open shell (review: #2536).
236
+ if (!(half[i] > OBB_EPS))
237
+ return null;
238
+ }
239
+ const center = [
240
+ p0[0] + c0[0] * axes[0][0] + c0[1] * axes[1][0] + c0[2] * axes[2][0],
241
+ p0[1] + c0[0] * axes[0][1] + c0[1] * axes[1][1] + c0[2] * axes[2][1],
242
+ p0[2] + c0[0] * axes[0][2] + c0[1] * axes[1][2] + c0[2] * axes[2][2],
243
+ ];
244
+ return { center, axes: [axes[0], axes[1], axes[2]], half };
245
+ }
246
+ /**
247
+ * The box frame from the three families' area-weighted normal sums, with a
248
+ * direction error bound per axis (#5474).
249
+ *
250
+ * Each family's sum is dominated by its largest faces, which resolve their
251
+ * normal most sharply (`errNum / |sum|` is the area-weighted mean of the
252
+ * triangles' `normalAngleError`). The frame is then made EXACTLY orthonormal,
253
+ * most precise family first: its axis is its own normalised sum, the next is
254
+ * Gram-Schmidt'ed against it, the last is their cross product. A thin panel's
255
+ * 5 cm side faces therefore inherit the precision of its 3 m faces instead of
256
+ * contributing their own, and a centre rebuilt from the frame cannot pick up
257
+ * a non-orthogonality error. Ties in precision keep family order, so the
258
+ * frame is deterministic. Bit-identical to the Rust `family_axes`.
259
+ */
260
+ function familyAxes(sums, errNum) {
261
+ const err = [0, 0, 0];
262
+ for (let g = 0; g < 3; g += 1) {
263
+ const len = Math.sqrt(dot(sums[g], sums[g]));
264
+ if (!(len > 0))
265
+ return null;
266
+ err[g] = errNum[g] / len;
267
+ }
268
+ const order = [0, 1, 2];
269
+ // Stable insertion sort on the error bound (3 elements).
270
+ for (let i = 1; i < 3; i += 1) {
271
+ let j = i;
272
+ while (j > 0 && err[order[j]] < err[order[j - 1]]) {
273
+ [order[j], order[j - 1]] = [order[j - 1], order[j]];
274
+ j -= 1;
275
+ }
276
+ }
277
+ const [f0, f1, f2] = order;
278
+ const a0 = normalize(sums[f0]);
279
+ if (!a0)
280
+ return null;
281
+ const a1 = normalize(sub(sums[f1], scale(a0, dot(sums[f1], a0))));
282
+ if (!a1)
283
+ return null;
284
+ let a2 = cross(a0, a1);
285
+ if (dot(a2, sums[f2]) < 0)
286
+ a2 = scale(a2, -1);
287
+ const axes = [];
288
+ axes[f0] = a0;
289
+ axes[f1] = a1;
290
+ axes[f2] = a2;
291
+ // An orthogonalised axis is as precise as the axes it was built from.
292
+ const axisErr = [0, 0, 0];
293
+ axisErr[f0] = err[f0];
294
+ axisErr[f1] = err[f0] + err[f1];
295
+ axisErr[f2] = err[f0] + err[f1];
296
+ return [axes, axisErr];
297
+ }
298
+ //# sourceMappingURL=obb-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obb-detect.js","sourceRoot":"","sources":["../../src/engine-ts/obb-detect.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAY/D,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,OAAO,EAA2B,MAAM,UAAU,CAAC;AAE5D,SAAS,SAAS,CAAC,CAAO;IACxB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED;;kEAEkE;AAClE,SAAS,SAAS,CAAC,CAAO;IACxB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;QAAE,GAAG,GAAG,CAAC,CAAC;SAC5B,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;QAAE,GAAG,GAAG,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,aAAa,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,QAAQ;AAE7C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,gBAAgB,CAAC,CAAO,EAAE,CAAO,EAAE,CAAO;IACjD,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrB,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,GAAG,MAAM;gBAAE,MAAM,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,aAAa,CAAC;IACrD,MAAM,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;;;GAUG;AACH,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,wEAAwE;IACxE,4EAA4E;IAC5E,yEAAyE;IACzE,uCAAuC;IACvC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,yEAAyE;IACzE,4EAA4E;IAC5E,2EAA2E;IAC3E,kCAAkC;IAClC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAW,EAAE,CAAC;IACxB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAa,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,GAAG,KAAK;gBAAE,KAAK,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,SAAS,CAAC,2DAA2D;QAC7E,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,IAAI,WAAW,GAAG,QAAQ;YAAE,QAAQ,GAAG,WAAW,CAAC;QACnD,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,oEAAoE;YACpE,oEAAoE;YACpE,sEAAsE;YACtE,gEAAgE;YAChE,mCAAmC;YACnC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,eAAe,CAAC,CAAC;YACrD,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC7D,EAAE,GAAG,CAAC,CAAC;gBACP,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,CAAC,sCAAsC;YAC3E,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,EAAE,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,qEAAqE;QACrE,sEAAsE;QACtE,sBAAsB;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,wEAAwE;YACxE,sEAAsE;YACtE,gDAAgD;YAChD,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChE,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;YAClF,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG;gBAAE,OAAO,IAAI,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAA6B,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxE,MAAM,MAAM,GAA6B,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,SAAS;QACxB,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACpC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC;gBAAE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC;gBAAE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,wEAAwE;IACxE,oEAAoE;IACpE,yEAAyE;IACzE,2EAA2E;IAC3E,yDAAyD;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,SAAS;QACxB,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACnF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,GAAG,KAAK,CAAC,CAAC;QACpG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC;YAChD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC;YAChD,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;QACxC,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAA6B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,EAAE,GAA6B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACtC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpC,uEAAuE;QACvE,uEAAuE;QACvE,mEAAmE;QACnE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;IACxC,CAAC;IACD,MAAM,MAAM,GAAS;QACnB,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrE,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,UAAU,CAAC,IAAY,EAAE,MAAgB;IAChD,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5B,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAC3B,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACxB,yDAAyD;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;IACH,CAAC;IACD,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC;IAC3B,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,IAAI,EAAE,GAAG,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACvB,IAAI,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC;QAAE,EAAE,GAAG,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAW,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;IACd,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;IACd,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;IACd,sEAAsE;IACtE,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACzB,CAAC"}
@@ -32,31 +32,21 @@ export interface MeshLike {
32
32
  readonly count: number;
33
33
  tri(t: number): [Vec3, Vec3, Vec3];
34
34
  }
35
- /**
36
- * Detect whether `mesh` is a rectangular box: every (non-degenerate)
37
- * triangle's normal falls into exactly 3 mutually orthogonal canonical
38
- * directions, and every vertex of the triangles in a direction group lies on
39
- * one of exactly two offset planes along that direction. That combination —
40
- * 3 orthogonal face families, 2 planes each — forces the mesh to be a closed
41
- * rectangular box (it rejects, for example, an axis-aligned L-shape, whose
42
- * notch adds a third offset plane on two of the axes). Triangulation-
43
- * independent: subdividing a box's faces adds triangles but never a new
44
- * canonical direction or a third offset plane, so the detection (and the
45
- * resulting `Obb`) is identical at any tessellation.
46
- *
47
- * Returns `null` — not a best-effort guess — when the check fails, so a
48
- * caller that only trusts a non-null result never certifies a shape this
49
- * function could not confirm.
50
- */
51
- export declare function detectObb(mesh: MeshLike): Obb | null;
52
35
  /**
53
36
  * Bound, in f64 ulps, on the absolute error of one component of the cross
54
37
  * product of two UNIT vectors (each component is a product-difference of
55
38
  * magnitude-<=1 terms: ~2 ulps), with headroom for the normalisation and the
56
39
  * per-projection dot rounding it feeds. Shared by the axis noise bound in
57
- * {@link obbPenetrationDepth}; same literal in the Rust kernel.
40
+ * {@link obbPenetration}; same literal in the Rust kernel.
58
41
  */
59
42
  export declare const AXIS_NOISE_ULPS = 8;
43
+ /** An OBB-OBB minimum translation depth and the UNIT axis it was measured
44
+ * along: the depth's precision floor is the pair's f32 noise projected onto
45
+ * that axis (#5405). Mirrors the Rust `ObbPenetration`. */
46
+ export interface ObbPenetration {
47
+ depth: number;
48
+ axis: Vec3;
49
+ }
60
50
  /**
61
51
  * Exact penetration depth between two oriented boxes: the minimum overlap
62
52
  * over the 15 canonical OBB-OBB separating-axis candidates (each box's 3 face
@@ -70,8 +60,9 @@ export declare const AXIS_NOISE_ULPS = 8;
70
60
  * the ~ulp-level absolute error of the cross product into a direction error
71
61
  * of up to `AXIS_NOISE_ULPS * EPS / |L|` radians. Each candidate's verdict
72
62
  * therefore carries a SCALE-RELATIVE noise bound (see `testAxis`), and a
73
- * verdict inside its own noise band is skipped — not trusted to separate,
74
- * not trusted as a depth (review: #2536; the same
63
+ * verdict inside its own noise band is never trusted to separate, and
64
+ * contributes a depth candidate of zero rather than being dropped from the
65
+ * minimum (#5355) (review: #2536; the same
75
66
  * tolerance-from-the-wrong-quantity class as #2598/#2600/#2529 — an earlier
76
67
  * ABSOLUTE `len > 1e-6` guard here both divided by lengths whose noise
77
68
  * dwarfs a thin overlap at large operand scale, and hard-dropped axes that
@@ -86,7 +77,7 @@ export declare const AXIS_NOISE_ULPS = 8;
86
77
  * `null` (caller falls back to the AABB estimate) rather than reporting a
87
78
  * wrong depth.
88
79
  */
89
- export declare function obbPenetrationDepth(a: Obb, b: Obb): number | null;
80
+ export declare function obbPenetration(a: Obb, b: Obb): ObbPenetration | null;
90
81
  /**
91
82
  * Whether `a` and `b` are in a THROUGH-PENETRATION configuration: one box's
92
83
  * cross-section, in the plane perpendicular to one of ITS OWN axes, is fully
@@ -96,7 +87,7 @@ export declare function obbPenetrationDepth(a: Obb, b: Obb): number | null;
96
87
  * ways, so it also holds for the MUTUAL case: two walls crossing at an
97
88
  * X-junction, each piercing the other clean through in thickness.
98
89
  *
99
- * This matters because {@link obbPenetrationDepth} reports the minimum
90
+ * This matters because {@link obbPenetration} reports the minimum
100
91
  * TRANSLATION distance to separate the pair, which for this shape is
101
92
  * dominated by the piercing member's own extent along the piercing axis, not
102
93
  * by how much material it actually crossed (review: #2536 — a 2 m duct
@@ -105,7 +96,7 @@ export declare function obbPenetrationDepth(a: Obb, b: Obb): number | null;
105
96
  *
106
97
  * Tests containment against EACH box's own axes independently (via
107
98
  * {@link piercesAlong}'s general per-axis projection, the same projection
108
- * {@link obbPenetrationDepth} already computes for its 15 SAT candidates) —
99
+ * {@link obbPenetration} already computes for its 15 SAT candidates) —
109
100
  * unlike an earlier version restricted to a frame shared by both boxes'
110
101
  * axes up to sign, this also catches a member piercing through at a generic
111
102
  * relative rotation (e.g. a duct crossing a wall at 15 degrees, review:
@@ -1 +1 @@
1
- {"version":3,"file":"obb.d.ts","sourceRoot":"","sources":["../../src/engine-ts/obb.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC;+DAC+D;AAC/D,eAAO,MAAM,OAAO,WAAO,CAAC;AAE5B,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,IAAI,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,6DAA6D;IAC7D,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;yDACyD;AACzD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;CACpC;AAqBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,GAAG,IAAI,CA4EpD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAoEjE;AAkED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAK5D"}
1
+ {"version":3,"file":"obb.d.ts","sourceRoot":"","sources":["../../src/engine-ts/obb.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC;+DAC+D;AAC/D,eAAO,MAAM,OAAO,WAAO,CAAC;AAE5B,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,IAAI,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,6DAA6D;IAC7D,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;yDACyD;AACzD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;4DAE4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,cAAc,GAAG,IAAI,CAsFpE;AAkED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAK5D"}
@@ -1,138 +1,16 @@
1
1
  /* This Source Code Form is subject to the terms of the Mozilla Public
2
2
  * License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
- import { sub, cross, dot } from '../math/vec3.js';
4
+ import { cross, dot } from '../math/vec3.js';
5
5
  /** Tolerance for normal-direction dedup and offset-plane clustering. Same
6
6
  * literal in the Rust kernel — see `rust/clash/src/obb.rs`. */
7
7
  export const OBB_EPS = 1e-6;
8
- function normalize(v) {
9
- const len = Math.sqrt(dot(v, v));
10
- if (!(len > OBB_EPS))
11
- return null;
12
- return [v[0] / len, v[1] / len, v[2] / len];
13
- }
14
- /** Flip `n` so its largest-magnitude component is positive, so a face and its
15
- * antipodal opposite face collapse to the same canonical axis direction. Ties
16
- * broken in x, y, z order — identical to the Rust `canonical`. */
17
- function canonical(n) {
18
- const ax = Math.abs(n[0]);
19
- const ay = Math.abs(n[1]);
20
- const az = Math.abs(n[2]);
21
- let idx = 0;
22
- if (ay > ax && ay >= az)
23
- idx = 1;
24
- else if (az > ax && az > ay)
25
- idx = 2;
26
- return n[idx] < 0 ? [-n[0], -n[1], -n[2]] : n;
27
- }
28
- /**
29
- * Detect whether `mesh` is a rectangular box: every (non-degenerate)
30
- * triangle's normal falls into exactly 3 mutually orthogonal canonical
31
- * directions, and every vertex of the triangles in a direction group lies on
32
- * one of exactly two offset planes along that direction. That combination —
33
- * 3 orthogonal face families, 2 planes each — forces the mesh to be a closed
34
- * rectangular box (it rejects, for example, an axis-aligned L-shape, whose
35
- * notch adds a third offset plane on two of the axes). Triangulation-
36
- * independent: subdividing a box's faces adds triangles but never a new
37
- * canonical direction or a third offset plane, so the detection (and the
38
- * resulting `Obb`) is identical at any tessellation.
39
- *
40
- * Returns `null` — not a best-effort guess — when the check fails, so a
41
- * caller that only trusts a non-null result never certifies a shape this
42
- * function could not confirm.
43
- */
44
- export function detectObb(mesh) {
45
- if (mesh.count === 0)
46
- return null;
47
- const groups = [];
48
- const groupOfTri = new Array(mesh.count).fill(-1);
49
- for (let t = 0; t < mesh.count; t += 1) {
50
- const [a, b, c] = mesh.tri(t);
51
- const n = normalize(cross(sub(b, a), sub(c, a)));
52
- if (!n)
53
- continue; // degenerate triangle: contributes no face-normal evidence
54
- const cn = canonical(n);
55
- let gi = -1;
56
- for (let g = 0; g < groups.length; g += 1) {
57
- if (dot(groups[g], cn) > 1 - OBB_EPS) {
58
- gi = g;
59
- break;
60
- }
61
- }
62
- if (gi === -1) {
63
- if (groups.length >= 3)
64
- return null; // a 4th face-normal family: not a box
65
- groups.push(cn);
66
- gi = groups.length - 1;
67
- }
68
- groupOfTri[t] = gi;
69
- }
70
- if (groups.length !== 3)
71
- return null;
72
- for (let i = 0; i < 3; i += 1) {
73
- for (let j = i + 1; j < 3; j += 1) {
74
- if (Math.abs(dot(groups[i], groups[j])) > OBB_EPS)
75
- return null;
76
- }
77
- }
78
- const minOff = [Infinity, Infinity, Infinity];
79
- const maxOff = [-Infinity, -Infinity, -Infinity];
80
- for (let t = 0; t < mesh.count; t += 1) {
81
- const gi = groupOfTri[t];
82
- if (gi === -1)
83
- continue;
84
- const [a, b, c] = mesh.tri(t);
85
- for (const v of [a, b, c]) {
86
- const o = dot(v, groups[gi]);
87
- if (o < minOff[gi])
88
- minOff[gi] = o;
89
- if (o > maxOff[gi])
90
- maxOff[gi] = o;
91
- }
92
- }
93
- // Reject a 3rd offset plane on any axis (e.g. an L-shaped footprint).
94
- for (let t = 0; t < mesh.count; t += 1) {
95
- const gi = groupOfTri[t];
96
- if (gi === -1)
97
- continue;
98
- const [a, b, c] = mesh.tri(t);
99
- for (const v of [a, b, c]) {
100
- const o = dot(v, groups[gi]);
101
- const scale = Math.max(1, Math.abs(minOff[gi]), Math.abs(maxOff[gi]));
102
- const nearMin = Math.abs(o - minOff[gi]) <= OBB_EPS * scale;
103
- const nearMax = Math.abs(o - maxOff[gi]) <= OBB_EPS * scale;
104
- if (!nearMin && !nearMax)
105
- return null;
106
- }
107
- }
108
- const half = [0, 0, 0];
109
- const c0 = [0, 0, 0];
110
- for (let i = 0; i < 3; i += 1) {
111
- half[i] = (maxOff[i] - minOff[i]) / 2;
112
- c0[i] = (maxOff[i] + minOff[i]) / 2;
113
- // Reject a zero-thickness "box": a face family whose triangles are all
114
- // coplanar passes the 2-plane test above (minOff == maxOff, so both
115
- // `nearMin` and `nearMax` hold for every vertex) with no positive
116
- // extent along that axis. An open shell (a slab exported without its
117
- // top face, or partial `IfcTriangulatedFaceSet` geometry) can produce
118
- // exactly this — 3 orthogonal families, but one degenerate — and must
119
- // not be certified as a box (review: #2536).
120
- if (!(half[i] > OBB_EPS))
121
- return null;
122
- }
123
- const center = [
124
- c0[0] * groups[0][0] + c0[1] * groups[1][0] + c0[2] * groups[2][0],
125
- c0[0] * groups[0][1] + c0[1] * groups[1][1] + c0[2] * groups[2][1],
126
- c0[0] * groups[0][2] + c0[1] * groups[1][2] + c0[2] * groups[2][2],
127
- ];
128
- return { center, axes: [groups[0], groups[1], groups[2]], half };
129
- }
130
8
  /**
131
9
  * Bound, in f64 ulps, on the absolute error of one component of the cross
132
10
  * product of two UNIT vectors (each component is a product-difference of
133
11
  * magnitude-<=1 terms: ~2 ulps), with headroom for the normalisation and the
134
12
  * per-projection dot rounding it feeds. Shared by the axis noise bound in
135
- * {@link obbPenetrationDepth}; same literal in the Rust kernel.
13
+ * {@link obbPenetration}; same literal in the Rust kernel.
136
14
  */
137
15
  export const AXIS_NOISE_ULPS = 8;
138
16
  /**
@@ -148,8 +26,9 @@ export const AXIS_NOISE_ULPS = 8;
148
26
  * the ~ulp-level absolute error of the cross product into a direction error
149
27
  * of up to `AXIS_NOISE_ULPS * EPS / |L|` radians. Each candidate's verdict
150
28
  * therefore carries a SCALE-RELATIVE noise bound (see `testAxis`), and a
151
- * verdict inside its own noise band is skipped — not trusted to separate,
152
- * not trusted as a depth (review: #2536; the same
29
+ * verdict inside its own noise band is never trusted to separate, and
30
+ * contributes a depth candidate of zero rather than being dropped from the
31
+ * minimum (#5355) (review: #2536; the same
153
32
  * tolerance-from-the-wrong-quantity class as #2598/#2600/#2529 — an earlier
154
33
  * ABSOLUTE `len > 1e-6` guard here both divided by lengths whose noise
155
34
  * dwarfs a thin overlap at large operand scale, and hard-dropped axes that
@@ -164,7 +43,7 @@ export const AXIS_NOISE_ULPS = 8;
164
43
  * `null` (caller falls back to the AABB estimate) rather than reporting a
165
44
  * wrong depth.
166
45
  */
167
- export function obbPenetrationDepth(a, b) {
46
+ export function obbPenetration(a, b) {
168
47
  const T = [b.center[0] - a.center[0], b.center[1] - a.center[1], b.center[2] - a.center[2]];
169
48
  // Operand scale for the per-axis noise bound: the sum of BOTH boxes' three
170
49
  // half-extents plus the center offset's components. A direction error of
@@ -183,6 +62,7 @@ export function obbPenetrationDepth(a, b) {
183
62
  b.half[0] + b.half[1] + b.half[2] +
184
63
  Math.abs(T[0]) + Math.abs(T[1]) + Math.abs(T[2]);
185
64
  let depth = Infinity;
65
+ let depthAxis = [0, 0, 0];
186
66
  function testAxis(L) {
187
67
  const len = Math.sqrt(dot(L, L));
188
68
  // Exactly parallel axes: the cross product is zero and the candidate
@@ -205,22 +85,37 @@ export function obbPenetrationDepth(a, b) {
205
85
  // `AXIS_NOISE_ULPS * EPS / len` radians (cancellation shrinks `|L|` to
206
86
  // sin(angle) but leaves the cross product's ~ulp absolute error intact),
207
87
  // so `overlap` is uncertain by up to `extentSum` times that. A verdict
208
- // inside the band is SKIPPED: in a separating-axis test, dropping a
209
- // candidate can only fail to find a separation — it reports the minimum
210
- // over the remaining axes, every one of which is a valid upper bound on
211
- // the true depth — never invent one, so the result stays conservative
212
- // (it may keep a clash a perfect test would separate, but never
213
- // separates a genuine overlap). Do not "harden" this into returning
214
- // false: that would turn unresolvable noise into a fabricated
88
+ // inside the band may not SEPARATE: in a separating-axis test, declining
89
+ // to separate can only fail to find a separation, never invent one, so
90
+ // the boolean result stays conservative. Do not "harden" this into
91
+ // returning false: that would turn unresolvable noise into a fabricated
215
92
  // separation. Verdicts OUTSIDE the band are kept whatever `len` is —
216
93
  // their own magnitude proves the noise did not decide them.
94
+ //
95
+ // It DOES still contribute a depth candidate, of zero (#5355). The depth
96
+ // is a MINIMUM over candidates, so an axis whose overlap is
97
+ // indistinguishable from zero is the smallest candidate present;
98
+ // dropping it hands the minimum to the next-smallest axis, which for two
99
+ // boxes in flush face contact is a FACE DIMENSION of one of them. #2536
100
+ // fixed WHICH axes fall in the band (an absolute `len > 1e-6` dropped
101
+ // axes that were fine, over-reporting a 0.02 m edge contact as 0.45 m);
102
+ // the remaining half was what dropping does to the minimum, which still
103
+ // reported 0.85 m for a 0.05 m curtain-wall panel lying flush against a
104
+ // mullion.
217
105
  const noise = extentSum * ((AXIS_NOISE_ULPS * Number.EPSILON) / len);
218
- if (Math.abs(overlap) <= noise)
106
+ if (Math.abs(overlap) <= noise) {
107
+ if (depth > 0) {
108
+ depth = 0;
109
+ depthAxis = u;
110
+ }
219
111
  return true;
112
+ }
220
113
  if (overlap <= 0)
221
114
  return false;
222
- if (overlap < depth)
115
+ if (overlap < depth) {
223
116
  depth = overlap;
117
+ depthAxis = u;
118
+ }
224
119
  return true;
225
120
  }
226
121
  for (let i = 0; i < 3; i += 1)
@@ -235,11 +130,11 @@ export function obbPenetrationDepth(a, b) {
235
130
  return null;
236
131
  }
237
132
  }
238
- return depth === Infinity ? null : depth;
133
+ return depth === Infinity ? null : { depth, axis: depthAxis };
239
134
  }
240
135
  /**
241
136
  * Projected radius of `o` onto unit axis `u`: half the length of `o`'s
242
- * shadow on `u`. The same per-axis projection {@link obbPenetrationDepth}'s
137
+ * shadow on `u`. The same per-axis projection {@link obbPenetration}'s
243
138
  * `testAxis` computes for the 15-candidate SAT — factored out here so the
244
139
  * through-penetration containment test below can reuse it for ANY axis, not
245
140
  * only one drawn from a shared a/b frame.
@@ -306,7 +201,7 @@ function piercesAlong(p, q, centerDelta) {
306
201
  * ways, so it also holds for the MUTUAL case: two walls crossing at an
307
202
  * X-junction, each piercing the other clean through in thickness.
308
203
  *
309
- * This matters because {@link obbPenetrationDepth} reports the minimum
204
+ * This matters because {@link obbPenetration} reports the minimum
310
205
  * TRANSLATION distance to separate the pair, which for this shape is
311
206
  * dominated by the piercing member's own extent along the piercing axis, not
312
207
  * by how much material it actually crossed (review: #2536 — a 2 m duct
@@ -315,7 +210,7 @@ function piercesAlong(p, q, centerDelta) {
315
210
  *
316
211
  * Tests containment against EACH box's own axes independently (via
317
212
  * {@link piercesAlong}'s general per-axis projection, the same projection
318
- * {@link obbPenetrationDepth} already computes for its 15 SAT candidates) —
213
+ * {@link obbPenetration} already computes for its 15 SAT candidates) —
319
214
  * unlike an earlier version restricted to a frame shared by both boxes'
320
215
  * axes up to sign, this also catches a member piercing through at a generic
321
216
  * relative rotation (e.g. a duct crossing a wall at 15 degrees, review: