@ifc-lite/clash 1.6.8 → 1.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.
Files changed (85) hide show
  1. package/dist/adapters/step.d.ts.map +1 -1
  2. package/dist/adapters/step.js +19 -2
  3. package/dist/adapters/step.js.map +1 -1
  4. package/dist/analysis.d.ts +37 -1
  5. package/dist/analysis.d.ts.map +1 -1
  6. package/dist/analysis.js +39 -0
  7. package/dist/analysis.js.map +1 -1
  8. package/dist/contact/narrow-phase.d.ts +6 -1
  9. package/dist/contact/narrow-phase.d.ts.map +1 -1
  10. package/dist/contact/narrow-phase.js +65 -1
  11. package/dist/contact/narrow-phase.js.map +1 -1
  12. package/dist/contact/shared-faces.d.ts +5 -1
  13. package/dist/contact/shared-faces.d.ts.map +1 -1
  14. package/dist/contact/shared-faces.js +49 -1
  15. package/dist/contact/shared-faces.js.map +1 -1
  16. package/dist/contact/tri-tri.d.ts +33 -4
  17. package/dist/contact/tri-tri.d.ts.map +1 -1
  18. package/dist/contact/tri-tri.js +37 -11
  19. package/dist/contact/tri-tri.js.map +1 -1
  20. package/dist/duplicate-metric.d.ts +74 -0
  21. package/dist/duplicate-metric.d.ts.map +1 -0
  22. package/dist/duplicate-metric.js +117 -0
  23. package/dist/duplicate-metric.js.map +1 -0
  24. package/dist/duplicate-sets.d.ts +47 -0
  25. package/dist/duplicate-sets.d.ts.map +1 -0
  26. package/dist/duplicate-sets.js +124 -0
  27. package/dist/duplicate-sets.js.map +1 -0
  28. package/dist/duplicates.d.ts +99 -11
  29. package/dist/duplicates.d.ts.map +1 -1
  30. package/dist/duplicates.js +270 -76
  31. package/dist/duplicates.js.map +1 -1
  32. package/dist/engine-ts/depth.d.ts +99 -0
  33. package/dist/engine-ts/depth.d.ts.map +1 -0
  34. package/dist/engine-ts/depth.js +172 -0
  35. package/dist/engine-ts/depth.js.map +1 -0
  36. package/dist/engine-ts/kernel.d.ts +3 -1
  37. package/dist/engine-ts/kernel.d.ts.map +1 -1
  38. package/dist/engine-ts/narrow.d.ts +7 -1
  39. package/dist/engine-ts/narrow.d.ts.map +1 -1
  40. package/dist/engine-ts/narrow.js +53 -95
  41. package/dist/engine-ts/narrow.js.map +1 -1
  42. package/dist/engine-ts/obb.d.ts +116 -0
  43. package/dist/engine-ts/obb.d.ts.map +1 -0
  44. package/dist/engine-ts/obb.js +331 -0
  45. package/dist/engine-ts/obb.js.map +1 -0
  46. package/dist/engine-ts/orchestrator.d.ts.map +1 -1
  47. package/dist/engine-ts/orchestrator.js +10 -13
  48. package/dist/engine-ts/orchestrator.js.map +1 -1
  49. package/dist/engine-ts/tri-mesh.d.ts +84 -22
  50. package/dist/engine-ts/tri-mesh.d.ts.map +1 -1
  51. package/dist/engine-ts/tri-mesh.js +151 -48
  52. package/dist/engine-ts/tri-mesh.js.map +1 -1
  53. package/dist/engine-ts/ts-kernel.d.ts.map +1 -1
  54. package/dist/engine-ts/ts-kernel.js +1 -0
  55. package/dist/engine-ts/ts-kernel.js.map +1 -1
  56. package/dist/engine-wasm/wasm-kernel.d.ts.map +1 -1
  57. package/dist/engine-wasm/wasm-kernel.js +7 -0
  58. package/dist/engine-wasm/wasm-kernel.js.map +1 -1
  59. package/dist/grouping.d.ts +4 -0
  60. package/dist/grouping.d.ts.map +1 -1
  61. package/dist/grouping.js +18 -43
  62. package/dist/grouping.js.map +1 -1
  63. package/dist/index.d.ts +12 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +12 -2
  66. package/dist/index.js.map +1 -1
  67. package/dist/math/aabb.d.ts +10 -1
  68. package/dist/math/aabb.d.ts.map +1 -1
  69. package/dist/math/aabb.js +33 -13
  70. package/dist/math/aabb.js.map +1 -1
  71. package/dist/shape-signature.d.ts +41 -0
  72. package/dist/shape-signature.d.ts.map +1 -0
  73. package/dist/shape-signature.js +113 -0
  74. package/dist/shape-signature.js.map +1 -0
  75. package/dist/triage.d.ts.map +1 -1
  76. package/dist/triage.js +11 -3
  77. package/dist/triage.js.map +1 -1
  78. package/dist/types.d.ts +60 -0
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/dist/union-find.d.ts +11 -0
  82. package/dist/union-find.d.ts.map +1 -0
  83. package/dist/union-find.js +44 -0
  84. package/dist/union-find.js.map +1 -0
  85. package/package.json +7 -6
@@ -1,12 +1,47 @@
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
+ /**
5
+ * Duplicate / fully-overlapping element detection (#1280).
6
+ *
7
+ * The first thing people do when reviewing a single discipline model is hunt for
8
+ * accidentally duplicated or coincident objects — re-imported geometry, a wall
9
+ * pasted twice, a column modelled on top of another. That is *not* a discipline
10
+ * clash, so it gets its own lightweight pass: AABB proximity decides *what* is
11
+ * reported, and a tessellation-invariant shape signature (surface area +
12
+ * enclosed volume) decides only whether a reported pair is labelled an exact
13
+ * duplicate. No narrow-phase triangle-vs-triangle work. The broad phase is a
14
+ * one-axis sort-and-sweep, which handles mixed-scale models correctly (no grid
15
+ * cell size to mis-tune), so it is cheap enough to run on every load; the
16
+ * signature is computed lazily, only for pairs that already coincide. Matching
17
+ * on AABBs has a known limit for nested solids — see the "nested-solids limit"
18
+ * section on {@link findDuplicates} for exactly what it can and cannot tell
19
+ * apart.
20
+ *
21
+ * "Same object" is decided by {@link boxDistance} against `positionTolerance`, a
22
+ * plain distance in metres. It used to be AABB intersection-over-union ≥ 0.9,
23
+ * which is a ratio and so carried no physical tolerance at all: the same setting
24
+ * allowed 5 mm across a DN100 pipe and 421 mm across an 8 m slab (see
25
+ * `DuplicateOptions.iouThreshold`).
26
+ *
27
+ * Output is a normal {@link ClashResult} (rule id `duplicates`) so the existing
28
+ * panel, grouping and BCF export render it with no special-casing. It is
29
+ * *pairwise*: N coincident copies of one object yield N(N−1)/2 clashes. For a
30
+ * reader-facing list use `groupDuplicateSets`, which collapses those pairs into
31
+ * one finding per coincident set.
32
+ */
4
33
  import { center, overlapBounds } from './math/aabb.js';
34
+ import { boxDistance, boxesTouch, minExtent, similarity } from './duplicate-metric.js';
35
+ import { sameShape, shapeSignature } from './shape-signature.js';
5
36
  import { isExcluded, qualifiedKey } from './exclude.js';
37
+ import { summarizeClashes } from './analysis.js';
6
38
  const DEFAULTS = {
39
+ positionTolerance: 0.01,
40
+ exactTolerance: 0.001,
41
+ /** Legacy IoU mode only. */
7
42
  iouThreshold: 0.9,
43
+ /** Legacy IoU mode only. */
8
44
  exactThreshold: 0.99,
9
- positionTolerance: 0.01,
10
45
  };
11
46
  export const DUPLICATES_RULE = {
12
47
  id: 'duplicates',
@@ -14,103 +49,229 @@ export const DUPLICATES_RULE = {
14
49
  a: '*',
15
50
  mode: 'hard',
16
51
  };
17
- function aabbVolume(b) {
18
- const dx = Math.max(0, b.max[0] - b.min[0]);
19
- const dy = Math.max(0, b.max[1] - b.min[1]);
20
- const dz = Math.max(0, b.max[2] - b.min[2]);
21
- return dx * dy * dz;
22
- }
23
- /** Intersection-over-union of two AABBs (0 when disjoint). */
24
- function aabbIoU(a, b) {
25
- const ox = Math.min(a.max[0], b.max[0]) - Math.max(a.min[0], b.min[0]);
26
- const oy = Math.min(a.max[1], b.max[1]) - Math.max(a.min[1], b.min[1]);
27
- const oz = Math.min(a.max[2], b.max[2]) - Math.max(a.min[2], b.min[2]);
28
- if (ox <= 0 || oy <= 0 || oz <= 0)
29
- return 0;
30
- const inter = ox * oy * oz;
31
- const union = aabbVolume(a) + aabbVolume(b) - inter;
32
- return union > 0 ? inter / union : 0;
33
- }
34
- function aabbApproxEqual(a, b, tol) {
35
- for (let i = 0; i < 3; i += 1) {
36
- if (Math.abs(a.min[i] - b.min[i]) > tol)
37
- return false;
38
- if (Math.abs(a.max[i] - b.max[i]) > tol)
39
- return false;
40
- }
41
- return true;
42
- }
43
- /** Similarity in `[0,1]`: AABB IoU, falling back to box-equality for degenerate
44
- * (zero-volume / planar) elements where IoU is undefined. */
45
- function similarity(a, b, tol) {
46
- const iou = aabbIoU(a, b);
47
- if (iou > 0)
48
- return iou;
49
- // Both (near) degenerate: an exact box match still means "same place".
50
- if (aabbVolume(a) <= 0 || aabbVolume(b) <= 0) {
51
- return aabbApproxEqual(a, b, tol) ? 1 : 0;
52
- }
53
- return 0;
54
- }
55
- /** Shortest dimension of a box — the depth one element is embedded in another. */
56
- function minExtent(b) {
57
- return Math.min(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]);
58
- }
59
- function triCount(el) {
60
- return Math.floor(el.indices.length / 3);
61
- }
62
52
  function toRef(el) {
63
53
  return { key: el.key, ref: el.ref, model: el.model, tag: el.tag, name: el.name };
64
54
  }
65
- function buildSummary(clashes) {
66
- const byRule = {};
67
- const byTypePair = {};
68
- const bySeverity = { critical: 0, major: 0, minor: 0, info: 0 };
69
- for (const c of clashes) {
70
- byRule[c.rule] = (byRule[c.rule] ?? 0) + 1;
71
- const pair = [c.a.tag, c.b.tag].sort().join(' vs ');
72
- byTypePair[pair] = (byTypePair[pair] ?? 0) + 1;
73
- bySeverity[c.severity] += 1;
74
- }
75
- return { total: clashes.length, byRule, byTypePair, bySeverity };
76
- }
77
55
  /**
78
56
  * Find duplicate / fully-overlapping elements. Returns a {@link ClashResult}
79
- * where each clash is a near-coincident pair: severity `major` for an exact
80
- * duplicate (same triangle count + near-identical box), `minor` for a looser
81
- * overlap candidate.
57
+ * where each clash is a near-coincident pair. `settings.tolerance` is
58
+ * `positionTolerance`, the value that actually decided the matches.
59
+ *
60
+ * Severity says exactly this much:
61
+ * - `major` — some pair of the two elements' boxes coincides within
62
+ * `exactTolerance` **and** the two elements' meshes — summed over the
63
+ * several parts a multi-material / CSG element emits — agree to within 5% on
64
+ * both surface area and enclosed volume. Read it as "the same object, in the
65
+ * same place"; it survives one copy being re-tessellated, and it does not
66
+ * depend on which of a multi-part element's part pairings the sweep visited
67
+ * first.
68
+ * - `minor` — near-coincident, but something differs: the boxes are further
69
+ * apart than `exactTolerance`, the shapes disagree, or one element carries no
70
+ * measurable geometry.
71
+ *
72
+ * What `major` still cannot tell you: the signature is two scalars over the
73
+ * surface, so two genuinely different solids that happen to agree in both area
74
+ * and volume to within 5% are indistinguishable here, as are an element and its
75
+ * mirror image.
76
+ *
77
+ * ## The nested-solids limit
78
+ *
79
+ * Matching remains AABB-only, so an element nested inside another — a pipe in a
80
+ * sleeve, a duct in a shaft, an assembly and the envelope drawn round it — is,
81
+ * in principle, something this pass cannot tell from a duplicate. Two gates
82
+ * already narrow that to a documented residual, and `duplicates.test.ts` pins
83
+ * each case:
84
+ *
85
+ * - Nesting has clearance, and {@link boxDistance} measures clearance. Anything
86
+ * looser than ~7 mm of annulus is more than `positionTolerance` away and is
87
+ * never reported at all: a DN100 pipe in a DN125 sleeve is 17.7 mm away, a
88
+ * 400×300 duct in a 500×400 shaft is 70.7 mm.
89
+ * - Tighter nesting *is* reported, but only ever as `minor`. Reaching `major`
90
+ * additionally requires the two meshes to agree on surface area and enclosed
91
+ * volume within 5%, which distinct solids sharing a box do not: a 50 mm and a
92
+ * 55 mm tube are 9.1% / 17.4% apart, a railing and its envelope box 73.2% /
93
+ * 88.4%.
94
+ *
95
+ * So the residual is exactly this: a pair nested within a few millimetres is
96
+ * listed as a `minor` candidate overlap. That is the intended reading of
97
+ * `minor` — "these two coincide, and this pass will not claim they are the same
98
+ * object" — and a reviewer still sees it, which for a coincidence hunt is the
99
+ * safe direction.
100
+ *
101
+ * Measured across five public models (duplex, AC20-FZK-Haus, Office_A_20110811,
102
+ * dental_clinic, Infra-Bridge), *no* reported pair was a nested one: all 33
103
+ * findings are same-type pairs of equal bounds, and the count of nested or
104
+ * cross-type pairs stays 0 even at a 250 mm tolerance, 25× the default. Note
105
+ * what that does not cover — none of the five carries pipe or duct segments, so
106
+ * the pipe-in-sleeve case is evidenced only by the constructed fixtures above.
107
+ *
108
+ * Closing the residual would need a narrow phase, which this pass deliberately
109
+ * does not run, and the cheap substitutes are worse rather than merely weaker.
110
+ * An area-weighted centroid plus surface inertia — the obvious candidate, and
111
+ * the same O(triangles) cost as the signature (3.6 ms vs 2.6 ms over
112
+ * dental_clinic's 236,795 triangles) — discriminates *less* on every nested
113
+ * case measured, contributes nothing at all when the nesting is concentric
114
+ * (identical centroids for pipe-in-sleeve and pipe-plus-insulation), and is not
115
+ * tessellation-invariant: it reads 39.6% apart on one box against a finer
116
+ * triangulation of the same box, which would break the exact-duplicate case
117
+ * this signature exists to keep together. A voxel occupancy sample would
118
+ * separate nested from coincident, but it needs a cell size — reintroducing the
119
+ * scale-dependent knob that replacing the IoU gate removed.
82
120
  */
83
121
  export function findDuplicates(elements, options = {}) {
122
+ // A caller that passes an IoU threshold is asking for IoU semantics; honour
123
+ // them (deprecated) rather than reinterpreting the number under a metric where
124
+ // it means nothing. Everyone else — the viewer, the CLI, every caller that
125
+ // passes no thresholds — gets the distance gate.
126
+ const legacyIoU = options.iouThreshold != null || options.exactThreshold != null;
84
127
  const iouThreshold = options.iouThreshold ?? DEFAULTS.iouThreshold;
85
128
  const exactThreshold = options.exactThreshold ?? DEFAULTS.exactThreshold;
86
129
  const positionTolerance = options.positionTolerance ?? DEFAULTS.positionTolerance;
130
+ const exactTolerance = options.exactTolerance ?? DEFAULTS.exactTolerance;
87
131
  const exclusions = options.exclusions;
88
132
  const clashes = [];
89
- const seen = new Set();
133
+ /** clash id index into `clashes`, for the cross-part dedup + upgrade below. */
134
+ const seen = new Map();
135
+ // Shape signatures are only needed for pairs that already coincide within
136
+ // `exactTolerance`, and each element's is O(its triangles). Compute them
137
+ // lazily and keep them, so a model with no coincident pairs never touches a
138
+ // vertex and a duplicated element is measured once however many partners it
139
+ // has.
140
+ //
141
+ // The signature is aggregated over ALL the meshes one element emitted (one
142
+ // per material / CSG part, sharing `(model, ref)`): area and volume both sum
143
+ // over parts. The exact-duplicate label is a statement about the ELEMENT
144
+ // pair, and the several cross-part pairs of one element pair collapse to a
145
+ // single clash id below — so comparing per-part signatures would let
146
+ // whichever part pairing the sweep reached first decide the label. Summing
147
+ // per-part |volume|s counts any overlap between parts twice, but it does so
148
+ // identically on both copies, so the comparison is unaffected.
149
+ const partsOf = new Map();
150
+ for (let i = 0; i < elements.length; i += 1) {
151
+ const k = `${elements[i].model} ${elements[i].ref}`;
152
+ const list = partsOf.get(k);
153
+ if (list)
154
+ list.push(i);
155
+ else
156
+ partsOf.set(k, [i]);
157
+ }
158
+ const signatures = new Map();
159
+ const signatureOf = (i) => {
160
+ const k = `${elements[i].model} ${elements[i].ref}`;
161
+ const cached = signatures.get(k);
162
+ if (cached)
163
+ return cached;
164
+ let area = 0;
165
+ let volume = 0;
166
+ for (const part of partsOf.get(k) ?? [i]) {
167
+ const s = shapeSignature(elements[part]);
168
+ area += s.area;
169
+ volume += s.volume;
170
+ }
171
+ const computed = { area, volume };
172
+ signatures.set(k, computed);
173
+ return computed;
174
+ };
175
+ // A key repeated inside one model across *different* elements is a defect in
176
+ // the file (`ifc-lite validate` reports duplicated GlobalIds), not proof of
177
+ // one element — and "the same object exported twice" is exactly what a
178
+ // duplicate hunt is for. So identity is (model, ref): `key` is the GlobalId,
179
+ // which a broken exporter can repeat, while `ref` is the express id (or its
180
+ // federated remap), which is unique by construction.
181
+ //
182
+ // A key does legitimately repeat across the several meshes one element emits
183
+ // (one per material / CSG part) — but those share the element's `ref` too, so
184
+ // they are still skipped as self-pairs. Only keys carried by two *distinct*
185
+ // refs are ambiguous, and only those get the ref folded into the clash id, so
186
+ // every id a well-formed file produced before is unchanged.
187
+ const ambiguousKeys = new Set();
188
+ {
189
+ const refOfKey = new Map();
190
+ for (const el of elements) {
191
+ const k = `${el.model} ${el.key}`;
192
+ const first = refOfKey.get(k);
193
+ if (first === undefined)
194
+ refOfKey.set(k, el.ref);
195
+ else if (first !== el.ref)
196
+ ambiguousKeys.add(k);
197
+ }
198
+ }
199
+ const pairKey = (el) => {
200
+ const k = `${el.model} ${el.key}`;
201
+ return ambiguousKeys.has(k) ? `${k}#${el.ref}` : k;
202
+ };
90
203
  const consider = (i, j) => {
91
204
  if (i >= j)
92
205
  return; // unordered pairs once
93
206
  const elA = elements[i];
94
207
  const elB = elements[j];
95
- if (elA.key === elB.key && elA.model === elB.model)
208
+ if (elA.ref === elB.ref && elA.model === elB.model)
96
209
  return;
97
210
  if (exclusions &&
98
211
  isExcluded(exclusions, qualifiedKey(elA.model, elA.key), qualifiedKey(elB.model, elB.key))) {
99
212
  return;
100
213
  }
101
- const sim = similarity(elA.bounds, elB.bounds, positionTolerance);
102
- if (sim < iouThreshold)
103
- return;
104
- const sameTris = triCount(elA) > 0 && triCount(elA) === triCount(elB);
105
- const exact = sim >= exactThreshold && sameTris;
214
+ let exact;
215
+ if (legacyIoU) {
216
+ const sim = similarity(elA.bounds, elB.bounds, positionTolerance);
217
+ if (sim < iouThreshold)
218
+ return;
219
+ exact = sim >= exactThreshold;
220
+ }
221
+ else {
222
+ if (!boxesTouch(elA.bounds, elB.bounds))
223
+ return;
224
+ const dist = boxDistance(elA.bounds, elB.bounds);
225
+ // Written as an acceptance (`!(dist <= tol)`), not a rejection
226
+ // (`dist > tol`), so a non-comparable distance abstains instead of
227
+ // asserting a pair. NaN fails every comparison, so bounds carrying NaN
228
+ // pass `boxesTouch` (its own comparisons are false too) and would fall
229
+ // through a `>` rejection and be reported as coincident with elements
230
+ // hundreds of metres away.
231
+ //
232
+ // The deprecated `iouThreshold` branch above is NOT a model for this: it
233
+ // is a rejection (`sim < iouThreshold`), and it happens to abstain on two
234
+ // NaN boxes only because `similarity` clamps them to 0 rather than
235
+ // returning NaN. It does not abstain in general — against a degenerate
236
+ // (zero-volume) element it takes the `aabbApproxEqual` fallback, whose
237
+ // per-axis comparisons are all false against NaN, so it returns 1 and
238
+ // reports the pair even at the default 0.9; and at `iouThreshold` 0 the
239
+ // rejection is false for every pair. That branch is deprecated and
240
+ // unchanged here; the two gates do not agree on non-finite bounds.
241
+ if (!(dist <= positionTolerance))
242
+ return;
243
+ exact = dist <= exactTolerance;
244
+ }
245
+ // Only now, on a pair that already coincides, is the shape worth measuring.
246
+ if (exact)
247
+ exact = sameShape(signatureOf(i), signatureOf(j));
106
248
  const severity = exact ? 'major' : 'minor';
107
- const ka = `${elA.model} ${elA.key}`;
108
- const kb = `${elB.model} ${elB.key}`;
249
+ const ka = pairKey(elA);
250
+ const kb = pairKey(elB);
109
251
  const [lo, hi] = ka < kb ? [ka, kb] : [kb, ka];
110
252
  const id = `duplicates ${lo} ${hi}`;
111
- if (seen.has(id))
253
+ const existing = seen.get(id);
254
+ if (existing !== undefined) {
255
+ // Multi-part elements: every cross-part pair of one element pair carries
256
+ // this id, and which pair arrives first is an artefact of the sweep
257
+ // order. The label must not be: if a later pair shows the copies
258
+ // actually coincide within `exactTolerance` (the shape term is already
259
+ // per-element), upgrade the recorded finding — and carry this pair's
260
+ // geometry, so the record describes the coincident parts rather than
261
+ // the loose cross pairing that happened to be swept first.
262
+ if (severity === 'major' && clashes[existing].severity === 'minor') {
263
+ const bounds = overlapBounds(elA.bounds, elB.bounds);
264
+ clashes[existing] = {
265
+ ...clashes[existing],
266
+ severity,
267
+ distance: -Math.max(0, minExtent(bounds)),
268
+ point: center(bounds),
269
+ bounds,
270
+ };
271
+ }
112
272
  return;
113
- seen.add(id);
273
+ }
274
+ seen.set(id, clashes.length);
114
275
  const bounds = overlapBounds(elA.bounds, elB.bounds);
115
276
  clashes.push({
116
277
  id,
@@ -120,7 +281,10 @@ export function findDuplicates(elements, options = {}) {
120
281
  status: 'hard',
121
282
  // Coincident solids fully embed each other; report the embedded depth so
122
283
  // they read as real overlaps (not zero-distance contacts) and sort first.
284
+ // This is a box dimension, not a mesh measurement, so it is labelled the
285
+ // same as the narrow-phase AABB fallback.
123
286
  distance: -Math.max(0, minExtent(bounds)),
287
+ distanceKind: 'estimate',
124
288
  point: center(bounds),
125
289
  bounds,
126
290
  severity,
@@ -128,10 +292,18 @@ export function findDuplicates(elements, options = {}) {
128
292
  };
129
293
  // Broad phase: one-axis sort-and-sweep over the AABBs. Unlike a fixed-size
130
294
  // hash grid, this makes NO assumption about element scale — so two large
131
- // objects offset by a few metres (still well above the IoU threshold) are
295
+ // objects offset by a few metres (still inside a metre-scale tolerance) are
132
296
  // never skipped just because many small elements shrank an average cell size.
133
297
  // Sweep along the axis with the widest spread of box minima so the active set
134
- // (and thus the comparison count) stays small.
298
+ // (and thus the comparison count) stays small. Eviction drops only boxes that
299
+ // no longer touch on `axis`, which is lossless for the DEFAULT gate — a pair
300
+ // that does not touch is rejected by `boxesTouch` anyway. It is NOT lossless
301
+ // for the deprecated `iouThreshold` branch: `similarity`'s degenerate fallback
302
+ // matches disjoint boxes that are within `tol` per axis, so in legacy mode the
303
+ // sweep can evict a pair that gate would have reported, and whether it does
304
+ // depends on which axis has the widest spread of minima — i.e. on the rest of
305
+ // the model. That axis-dependence predates the distance gate (the sweep is
306
+ // unchanged), so `iouThreshold` still restores the pre-1.7 gate as documented.
135
307
  let axis = 0;
136
308
  let bestSpread = -Infinity;
137
309
  for (let a = 0; a < 3; a += 1) {
@@ -150,7 +322,28 @@ export function findDuplicates(elements, options = {}) {
150
322
  axis = a;
151
323
  }
152
324
  }
153
- const order = elements.map((_, i) => i).sort((x, y) => elements[x].bounds.min[axis] - elements[y].bounds.min[axis]);
325
+ // The sweep key must be a TOTAL order, so it is compared, never subtracted.
326
+ // `a - b` returns NaN for any pair involving a non-finite minimum — NaN bounds
327
+ // from a direct SDK caller, and `+Infinity` from `fromPositions` when no vertex
328
+ // on an axis was finite (it returns the box inverted). A comparator that
329
+ // answers NaN violates the contract `Array.prototype.sort` requires, and V8's
330
+ // TimSort then merges runs against that answer and emits an arbitrary
331
+ // permutation of the WHOLE array: the sweep's eviction sees minima going
332
+ // backwards, drops boxes that are still live, and real duplicates elsewhere in
333
+ // the model silently disappear. One unjudgeable element must cost only itself.
334
+ // Non-finite minima sort last, after every finite one, which is what the gate
335
+ // already does with them — `consider` abstains on a distance it cannot
336
+ // compare, so they contribute nothing wherever they sit, and putting them at
337
+ // the end keeps them out of the active set for the whole finite sweep.
338
+ const sweepKey = (i) => {
339
+ const v = elements[i].bounds.min[axis];
340
+ return Number.isFinite(v) ? v : Number.POSITIVE_INFINITY;
341
+ };
342
+ const order = elements.map((_, i) => i).sort((x, y) => {
343
+ const kx = sweepKey(x);
344
+ const ky = sweepKey(y);
345
+ return kx < ky ? -1 : kx > ky ? 1 : 0;
346
+ });
154
347
  // `active` holds indices whose box still extends past the current box's start
155
348
  // on `axis`; only those can overlap, so we compare against just them.
156
349
  const active = [];
@@ -170,8 +363,9 @@ export function findDuplicates(elements, options = {}) {
170
363
  clashes.sort((x, y) => (x.id < y.id ? -1 : x.id > y.id ? 1 : 0));
171
364
  return {
172
365
  clashes,
173
- summary: buildSummary(clashes),
366
+ summary: summarizeClashes(clashes),
174
367
  rulesRun: [DUPLICATES_RULE],
368
+ ruleCoverage: [{ rule: DUPLICATES_RULE.id, matchedA: elements.length, matchedB: null }],
175
369
  settings: { tolerance: positionTolerance, excludeVoidsAndHosts: exclusions != null },
176
370
  };
177
371
  }
@@ -1 +1 @@
1
- {"version":3,"file":"duplicates.js","sourceRoot":"","sources":["../src/duplicates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"duplicates.js","sourceRoot":"","sources":["../src/duplicates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Depth measurement and the f32-precision floor for the narrow phase
3
+ * (`narrow.ts`). Split out to keep `narrow.ts` under the ~400-line module
4
+ * rule; mirrors `rust/clash/src/depth.rs` structurally so the two kernels
5
+ * stay easy to diff.
6
+ */
7
+ import type { AABB, ClashElement, ClashRule, Vec3 } from '../types.js';
8
+ import type { TriMesh } from './tri-mesh.js';
9
+ import type { NarrowResult } from './narrow.js';
10
+ /**
11
+ * Exact box-box penetration when BOTH meshes are (within tolerance)
12
+ * rectangular boxes, else `null`. `mtd` is the only source of a `'mesh'`
13
+ * label for a distance that used to come from `TriMesh.maxPenetrationInto` —
14
+ * a nearest-crossing-vertex sampling probe that converges to 0 under
15
+ * retessellation instead of to the true depth (see `obb.ts`, `obb.test.ts`).
16
+ *
17
+ * `through` flags a THROUGH-PENETRATION pair — a thin member piercing clean
18
+ * through the other, e.g. a duct through a wall. There, the minimum-
19
+ * translation-distance `obb.ts` computes is dominated by the piercing
20
+ * member's own extent along the shared axis, not by the material actually
21
+ * crossed (review: #2536, reproduced a 5.5x inflation on exactly this
22
+ * shape), so `depthClashResult` reports the caller's AABB estimate for that
23
+ * shape — matching what `main` did before the box-exact metric existed, and
24
+ * honest about not being a measurement. The MTD is still returned (not
25
+ * discarded here) because the f32 floor must see it: which number gets
26
+ * REPORTED is a separate, later decision from whether the pair is
27
+ * measurable at all (see `depthClashResult`).
28
+ */
29
+ export interface BoxPenetration {
30
+ /** Exact box-box minimum-translation depth (Gottschalk 15-axis SAT). */
31
+ mtd: number;
32
+ /** The MTD is inflated by the piercing member's own extent; report the
33
+ * AABB estimate instead (see `isThroughPenetration`). */
34
+ through: boolean;
35
+ }
36
+ export declare function boxPenetration(small: TriMesh, large: TriMesh): BoxPenetration | null;
37
+ /**
38
+ * Deepest penetration of `mesh`'s crossing-triangle VERTICES into `other`:
39
+ * the maximum distance-to-surface of `other` over the vertices of the
40
+ * triangles flagged in `crossFlags` (the pairs the narrow phase saw
41
+ * genuinely crossing `other`) that lie inside `other`. Each vertex is
42
+ * visited once (deduped by vertex index, in index order — bit-identical to
43
+ * the Rust `crossing_vertex_penetration`). Returns 0 when no flagged vertex
44
+ * is inside.
45
+ *
46
+ * This is NOT a depth metric and must never be reported as one — it is the
47
+ * sampling probe PR #2536 was held over (`maxPenetrationInto`): its value is
48
+ * an O(edge length) artifact that converges to 0 under retessellation
49
+ * instead of to the true depth. It survives with exactly one client: the
50
+ * f32 noise-floor gate for a CONTAINED pair (`depthClashResult`), where the
51
+ * question is not "how deep?" but "is any mesh-level penetration measurably
52
+ * above the floor at all?" — sub-floor here means every crossing vertex sits
53
+ * within f32 rounding noise of the other surface, i.e. surfaces authored
54
+ * flush, which no amount of retessellation turns into a real overlap. For
55
+ * that yes/no question the probe's underestimation is harmless:
56
+ * underestimating can only keep a pair BELOW the floor, and the floor is
57
+ * the very thing being tested.
58
+ */
59
+ export declare function crossingVertexPenetration(mesh: TriMesh, other: TriMesh, crossFlags: Uint8Array): number;
60
+ /**
61
+ * Turns the candidate penetration depths into the final `(status,
62
+ * distanceKind, distance)` triple. This is the ONLY place in the narrow
63
+ * phase allowed to build a `'mesh'`- or `'estimate'`-labelled `hard` result
64
+ * off a depth number — every branch in `narrow.ts` that can label a result
65
+ * `'mesh'` off `boxPenetration` (or its AABB-estimate fallback) MUST route
66
+ * through here rather than building the `NarrowResult` literal itself. That
67
+ * is what makes the f32 floor apply to all of them, and what enforces its
68
+ * precedence.
69
+ *
70
+ * THE FLOOR WINS (#2536 rebase decision): a pair below the f32 noise floor
71
+ * is `touch` regardless of how its depth was derived — at that magnitude the
72
+ * number is not measurable either way — so the floor is tested against EVERY
73
+ * candidate depth the pair has, not against whichever one the estimate-vs-
74
+ * mesh selection would report. Three candidates exist:
75
+ *
76
+ * - the AABB `estimate` (always present);
77
+ * - the box MTD, when both elements are certified boxes (`box`);
78
+ * - the crossing-vertex penetration, for a CONTAINED pair with a crossing
79
+ * vertex inside the other solid (`meshEvidence`) — evidence for this gate
80
+ * only, never a reported depth (see `crossingVertexPenetration`).
81
+ *
82
+ * The pair is `hard` only when the SMALLEST available candidate clears the
83
+ * floor. That is what makes the floor unreachable by depth-source selection:
84
+ * a sub-floor box MTD cannot be promoted by the through-penetration guard
85
+ * swapping in a larger AABB estimate; a sub-floor crossing-vertex
86
+ * penetration on a contained pair (surfaces authored flush — the eight
87
+ * Infra-Bridge pairs that moved #2594's 50-hard-clash pin to 58 when this
88
+ * PR's depth rework replaced their noise-scale mesh depth with the
89
+ * fabricated 4 m AABB estimate) cannot be promoted by that estimate; and a
90
+ * sub-floor AABB estimate cannot be promoted by a larger MTD (a through-
91
+ * penetration far from the origin, where the MTD is inflated by the
92
+ * piercing member's own extent). Only a pair already above the floor
93
+ * reaches the selection below, which then merely picks WHICH above-floor
94
+ * reportable number is used and how it is labelled — so a `hard` result's
95
+ * distance clears the floor by construction, whichever quantity it came
96
+ * from.
97
+ */
98
+ export declare function depthClashResult(box: BoxPenetration | null, estimate: number, meshEvidence: number | null, elA: ClashElement, elB: ClashElement, rule: ClashRule, point: Vec3, bounds: AABB): NarrowResult | null;
99
+ //# sourceMappingURL=depth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth.d.ts","sourceRoot":"","sources":["../../src/engine-ts/depth.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ;6DACyD;IACzD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAOpF;AAsDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAevG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,cAAc,GAAG,IAAI,EAC1B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,GACX,YAAY,GAAG,IAAI,CAsBrB"}