@ifc-lite/renderer 1.47.0 → 1.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/clash-solid-pipeline.d.ts +34 -0
  2. package/dist/clash-solid-pipeline.d.ts.map +1 -0
  3. package/dist/clash-solid-pipeline.js +193 -0
  4. package/dist/clash-solid-pipeline.js.map +1 -0
  5. package/dist/deviation/deviation-computer.d.ts +87 -0
  6. package/dist/deviation/deviation-computer.d.ts.map +1 -0
  7. package/dist/deviation/deviation-computer.js +201 -0
  8. package/dist/deviation/deviation-computer.js.map +1 -0
  9. package/dist/index.d.ts +22 -46
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +42 -141
  12. package/dist/index.js.map +1 -1
  13. package/dist/renderer-overlays.d.ts +4 -0
  14. package/dist/renderer-overlays.d.ts.map +1 -1
  15. package/dist/renderer-overlays.js +19 -0
  16. package/dist/renderer-overlays.js.map +1 -1
  17. package/dist/shaders/sky.wgsl.d.ts +1 -1
  18. package/dist/shaders/sky.wgsl.d.ts.map +1 -1
  19. package/dist/shaders/sky.wgsl.js +7 -1
  20. package/dist/shaders/sky.wgsl.js.map +1 -1
  21. package/dist/snap-corner.d.ts +44 -0
  22. package/dist/snap-corner.d.ts.map +1 -0
  23. package/dist/snap-corner.js +111 -0
  24. package/dist/snap-corner.js.map +1 -0
  25. package/dist/snap-detector.d.ts +8 -4
  26. package/dist/snap-detector.d.ts.map +1 -1
  27. package/dist/snap-detector.js +60 -58
  28. package/dist/snap-detector.js.map +1 -1
  29. package/dist/snap-edge-runs.d.ts +75 -0
  30. package/dist/snap-edge-runs.d.ts.map +1 -0
  31. package/dist/snap-edge-runs.js +233 -0
  32. package/dist/snap-edge-runs.js.map +1 -0
  33. package/dist/snap-geometry-cache.d.ts +17 -11
  34. package/dist/snap-geometry-cache.d.ts.map +1 -1
  35. package/dist/snap-geometry-cache.js +115 -141
  36. package/dist/snap-geometry-cache.js.map +1 -1
  37. package/dist/snap-weld.d.ts +66 -0
  38. package/dist/snap-weld.d.ts.map +1 -0
  39. package/dist/snap-weld.js +150 -0
  40. package/dist/snap-weld.js.map +1 -0
  41. package/package.json +2 -2
@@ -0,0 +1,34 @@
1
+ /** One flat opaque colour for the whole solid — BIMcollab-style, not per-triangle. */
2
+ export interface ClashSolidInput {
3
+ /** World-space vertex positions, flat `[x, y, z, …]`. */
4
+ positions: Float32Array | Float64Array;
5
+ /** Triangle indices into `positions / 3`. */
6
+ indices: Uint32Array;
7
+ /** Straight-alpha RGBA in [0..1]. Alpha 1 renders fully opaque. */
8
+ color: [number, number, number, number];
9
+ }
10
+ /**
11
+ * Expand an indexed triangle mesh + single colour into the flat
12
+ * pos+color-per-vertex stream `SYMBOLIC_FILL_WGSL` expects, matching
13
+ * `triangulateFillTo`'s non-indexed triangle-list convention.
14
+ */
15
+ export declare function expandTriangles(input: ClashSolidInput): Float32Array;
16
+ export declare class ClashSolidPipeline {
17
+ private readonly device;
18
+ private readonly format;
19
+ private readonly sampleCount;
20
+ private pipeline;
21
+ private bindGroupLayout;
22
+ private uniformBuffer;
23
+ private bindGroup;
24
+ private vertexBuffer;
25
+ private vertexCount;
26
+ constructor(device: GPUDevice, presentationFormat: GPUTextureFormat, sampleCount?: number);
27
+ private init;
28
+ /** Upload the solid mesh. Pass `null` to clear. */
29
+ upload(input: ClashSolidInput | null): void;
30
+ hasGeometry(): boolean;
31
+ render(pass: GPURenderPassEncoder, viewProj: Float32Array): void;
32
+ destroy(): void;
33
+ }
34
+ //# sourceMappingURL=clash-solid-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clash-solid-pipeline.d.ts","sourceRoot":"","sources":["../src/clash-solid-pipeline.ts"],"names":[],"mappings":"AA+BA,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC;IACvC,6CAA6C;IAC7C,OAAO,EAAE,WAAW,CAAC;IACrB,mEAAmE;IACnE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAkBpE;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,WAAW,CAAK;gBAEZ,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,GAAE,MAAU;IAM5F,OAAO,CAAC,IAAI;IAmFZ,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI;IAuB3C,WAAW,IAAI,OAAO;IAItB,MAAM,CAAC,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAUhE,OAAO,IAAI,IAAI;CAUhB"}
@@ -0,0 +1,193 @@
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
+ /**
5
+ * The focused clash's INTERSECTION SOLID — the true overlap volume of a
6
+ * clashing pair, rendered opaque so it reads as a distinct shape rather than
7
+ * an abstract wireframe box (the BIMcollab Zoom / Solibri presentation the
8
+ * viewer's own overlap-box overlay was compared against and found "not
9
+ * helpful, hard to see").
10
+ *
11
+ * Deliberately its own tiny pipeline rather than a reuse of
12
+ * `SymbolicFillPipeline`: the vertex layout and shader (`SYMBOLIC_FILL_WGSL`,
13
+ * pos.xyz + color.rgba, straight `viewProj * pos`) are identical and shared,
14
+ * but the depth/blend state cannot be — the fill pipeline is a coplanar DECAL
15
+ * (`depthWriteEnabled: false`, negative bias, alpha-blended) for flat
16
+ * annotation regions that sit ON a real surface. The intersection solid is a
17
+ * real enclosed 3D volume: it must depth-test AND depth-write like ordinary
18
+ * opaque geometry so its own front/back triangles resolve correctly and it is
19
+ * properly occluded by unrelated opaque geometry in front of it, while still
20
+ * drawing on top of (and being visible through) the two ghosted parent
21
+ * elements — ghosted geometry renders with `depthWriteEnabled: false`
22
+ * (`pipeline.ts`), so it never occludes anything drawn after it in the same
23
+ * pass, including this.
24
+ */
25
+ import { SYMBOLIC_FILL_WGSL } from './shaders/symbolic-overlay.wgsl.js';
26
+ import { PIPELINE_CONSTANTS } from './constants.js';
27
+ const VERTEX_STRIDE_BYTES = (3 + 4) * 4; // pos.xyz + color.rgba, 4 bytes each
28
+ /**
29
+ * Expand an indexed triangle mesh + single colour into the flat
30
+ * pos+color-per-vertex stream `SYMBOLIC_FILL_WGSL` expects, matching
31
+ * `triangulateFillTo`'s non-indexed triangle-list convention.
32
+ */
33
+ export function expandTriangles(input) {
34
+ const { positions, indices, color } = input;
35
+ const triCount = Math.floor(indices.length / 3);
36
+ const out = new Float32Array(triCount * 3 * 7);
37
+ let w = 0;
38
+ for (let t = 0; t < triCount; t += 1) {
39
+ for (let k = 0; k < 3; k += 1) {
40
+ const vi = indices[t * 3 + k] * 3;
41
+ out[w++] = positions[vi];
42
+ out[w++] = positions[vi + 1];
43
+ out[w++] = positions[vi + 2];
44
+ out[w++] = color[0];
45
+ out[w++] = color[1];
46
+ out[w++] = color[2];
47
+ out[w++] = color[3];
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ export class ClashSolidPipeline {
53
+ device;
54
+ format;
55
+ sampleCount;
56
+ pipeline = null;
57
+ bindGroupLayout = null;
58
+ uniformBuffer = null;
59
+ bindGroup = null;
60
+ vertexBuffer = null;
61
+ vertexCount = 0;
62
+ constructor(device, presentationFormat, sampleCount = 1) {
63
+ this.device = device;
64
+ this.format = presentationFormat;
65
+ this.sampleCount = sampleCount;
66
+ }
67
+ init() {
68
+ if (this.pipeline)
69
+ return;
70
+ this.bindGroupLayout = this.device.createBindGroupLayout({
71
+ label: 'clash-solid-bgl',
72
+ entries: [
73
+ { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
74
+ ],
75
+ });
76
+ const module = this.device.createShaderModule({
77
+ label: 'clash-solid-shader',
78
+ code: SYMBOLIC_FILL_WGSL,
79
+ });
80
+ this.pipeline = this.device.createRenderPipeline({
81
+ label: 'clash-solid-pipeline',
82
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }),
83
+ vertex: {
84
+ module,
85
+ entryPoint: 'vs_main',
86
+ buffers: [
87
+ {
88
+ arrayStride: VERTEX_STRIDE_BYTES,
89
+ attributes: [
90
+ { shaderLocation: 0, offset: 0, format: 'float32x3' },
91
+ { shaderLocation: 1, offset: 3 * 4, format: 'float32x4' },
92
+ ],
93
+ },
94
+ ],
95
+ },
96
+ fragment: {
97
+ module,
98
+ entryPoint: 'fs_main',
99
+ // Same two-target layout as every other overlay pipeline sharing this
100
+ // pass: presentation colour + the picker objectId, write-masked off so
101
+ // the opaque pass's picker IDs underneath survive.
102
+ targets: [
103
+ {
104
+ format: this.format,
105
+ blend: {
106
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
107
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
108
+ },
109
+ writeMask: GPUColorWrite.ALL,
110
+ },
111
+ { format: 'rgba8unorm', writeMask: 0 },
112
+ ],
113
+ },
114
+ // `cullMode: 'none'`: the intersection kernel's output winding isn't a
115
+ // contract the renderer depends on (mirrors the main opaque pipeline's
116
+ // own "IFC winding order varies" choice), and a double-sided draw still
117
+ // looks correct for a convex-ish overlap volume viewed from outside.
118
+ primitive: { topology: 'triangle-list', cullMode: 'none' },
119
+ depthStencil: {
120
+ format: PIPELINE_CONSTANTS.DEPTH_FORMAT,
121
+ // Unlike the fill pipeline's decal, this is a real volume: it must
122
+ // write depth so its own triangles sort against each other and so it
123
+ // is correctly occluded by opaque geometry drawn earlier in the pass.
124
+ depthWriteEnabled: true,
125
+ depthCompare: 'greater', // Reverse-Z, matches the main opaque pipeline.
126
+ // Small decal bias so a face that lands exactly on a parent's own
127
+ // surface (a flush overlap boundary) doesn't z-fight with it.
128
+ depthBias: -2,
129
+ depthBiasSlopeScale: -0.25,
130
+ depthBiasClamp: 0,
131
+ },
132
+ multisample: { count: this.sampleCount },
133
+ });
134
+ this.uniformBuffer = this.device.createBuffer({
135
+ label: 'clash-solid-camera',
136
+ size: 64,
137
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
138
+ });
139
+ this.bindGroup = this.device.createBindGroup({
140
+ label: 'clash-solid-bg',
141
+ layout: this.bindGroupLayout,
142
+ entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }],
143
+ });
144
+ }
145
+ /** Upload the solid mesh. Pass `null` to clear. */
146
+ upload(input) {
147
+ this.init();
148
+ if (this.vertexBuffer) {
149
+ this.vertexBuffer.destroy();
150
+ this.vertexBuffer = null;
151
+ }
152
+ this.vertexCount = 0;
153
+ if (!input || input.indices.length === 0)
154
+ return;
155
+ const data = expandTriangles(input);
156
+ if (data.length === 0)
157
+ return;
158
+ this.vertexBuffer = this.device.createBuffer({
159
+ label: 'clash-solid-vbuf',
160
+ size: data.byteLength,
161
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
162
+ });
163
+ this.device.queue.writeBuffer(this.vertexBuffer, 0, data);
164
+ this.vertexCount = data.length / (VERTEX_STRIDE_BYTES / 4);
165
+ }
166
+ hasGeometry() {
167
+ return this.vertexCount > 0;
168
+ }
169
+ render(pass, viewProj) {
170
+ if (!this.pipeline || !this.uniformBuffer || !this.bindGroup || !this.vertexBuffer)
171
+ return;
172
+ if (this.vertexCount === 0)
173
+ return;
174
+ this.device.queue.writeBuffer(this.uniformBuffer, 0, viewProj);
175
+ pass.setPipeline(this.pipeline);
176
+ pass.setBindGroup(0, this.bindGroup);
177
+ pass.setVertexBuffer(0, this.vertexBuffer);
178
+ pass.draw(this.vertexCount);
179
+ }
180
+ destroy() {
181
+ if (this.vertexBuffer)
182
+ this.vertexBuffer.destroy();
183
+ if (this.uniformBuffer)
184
+ this.uniformBuffer.destroy();
185
+ this.vertexBuffer = null;
186
+ this.uniformBuffer = null;
187
+ this.bindGroup = null;
188
+ this.bindGroupLayout = null;
189
+ this.pipeline = null;
190
+ this.vertexCount = 0;
191
+ }
192
+ }
193
+ //# sourceMappingURL=clash-solid-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clash-solid-pipeline.js","sourceRoot":"","sources":["../src/clash-solid-pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import type { WebGPUDevice } from '../device.js';
2
+ import type { Scene } from '../scene.js';
3
+ import type { PointCloudRenderer } from '../pointcloud/point-cloud-renderer.js';
4
+ /** Tuning knobs for a single `DeviationComputer.compute` run. */
5
+ export interface DeviationComputeOptions {
6
+ /** Clip range applied during compute. 0 → no clip. Default 1m. */
7
+ maxRange?: number;
8
+ forceRebuild?: boolean;
9
+ }
10
+ /**
11
+ * Everything the compute pass borrows from the `Renderer` for the
12
+ * duration of one call. None of it is retained.
13
+ */
14
+ export interface DeviationComputeContext {
15
+ device: WebGPUDevice;
16
+ scene: Scene;
17
+ pointCloudRenderer: PointCloudRenderer | null;
18
+ requestRender: () => void;
19
+ }
20
+ /** Metadata a completed deviation pass reports back to the UI. */
21
+ export interface DeviationComputeResult {
22
+ bvhTriangles: number;
23
+ bvhNodes: number;
24
+ chunksProcessed: number;
25
+ pointsProcessed: number;
26
+ bounds: {
27
+ min: [number, number, number];
28
+ max: [number, number, number];
29
+ } | null;
30
+ suggestedHalfRange: number;
31
+ }
32
+ /**
33
+ * Owns the deviation compute pipeline and its BVH cache.
34
+ *
35
+ * Lifecycle mirrors the `Renderer`'s: `init()` on device creation,
36
+ * `destroy()` before the device goes away.
37
+ */
38
+ export declare class DeviationComputer {
39
+ private pipeline;
40
+ /**
41
+ * Cache of which mesh-set the BVH was built from. We rebuild on
42
+ * `computeDeviations` only when the cached "fingerprint" misses,
43
+ * so re-running deviation against the same model is a fast
44
+ * dispatch — the BVH is multi-second on big BIMs and we don't
45
+ * want to pay that on every slider drag.
46
+ */
47
+ private bvhFingerprint;
48
+ /**
49
+ * Create the compute pipeline for the BIM↔scan deviation heatmap.
50
+ * Lazily owns the per-triangle BVH GPU buffers; idle until the
51
+ * first `compute` call.
52
+ *
53
+ * Idempotent: a second `init()` without an intervening `destroy()`
54
+ * tears down the previous pipeline (releasing its GPU buffers
55
+ * instead of orphaning them) and clears `bvhFingerprint`. Without
56
+ * this, the stale fingerprint would match the next `compute()`
57
+ * call's input, skip re-uploading the BVH into the NEW pipeline,
58
+ * and `dispatch()` would silently no-op every chunk (no BVH
59
+ * uploaded) — reporting a plausible-looking deviation of zero
60
+ * instead of failing loudly.
61
+ */
62
+ init(device: GPUDevice): void;
63
+ /**
64
+ * Compute BIM ↔ scan deviation for every loaded point cloud asset.
65
+ *
66
+ * Walks every triangle in the scene (individual + batched meshes,
67
+ * regardless of which IFC ingest path produced them — STEP, IFCx,
68
+ * GLB, or federated combinations), builds a per-triangle BVH on
69
+ * the GPU, then runs a closest-point compute pass per chunk that
70
+ * writes signed distance into each chunk's deviation buffer.
71
+ *
72
+ * Returns metadata so the UI can populate a histogram + auto-range:
73
+ * the per-asset point count, the suggested ±range from the 95th
74
+ * percentile, and the bbox the BVH was built from.
75
+ *
76
+ * Idempotent: re-running with the same mesh set reuses the GPU
77
+ * BVH (the BVH build dominates wall time on big BIMs). Pass
78
+ * `forceRebuild: true` to invalidate.
79
+ */
80
+ compute(opts: DeviationComputeOptions, ctx: DeviationComputeContext): Promise<DeviationComputeResult>;
81
+ /**
82
+ * Release the compute pipeline + cached BVH GPU buffers and forget
83
+ * the fingerprint, so a re-`init()` on a fresh device rebuilds.
84
+ */
85
+ destroy(): void;
86
+ }
87
+ //# sourceMappingURL=deviation-computer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviation-computer.d.ts","sourceRoot":"","sources":["../../src/deviation/deviation-computer.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AA2ChF,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,aAAa,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE;QAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,kBAAkB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,QAAQ,CAAkC;IAClD;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,CAAuB;IAE7C;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAM7B;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CACT,IAAI,EAAE,uBAAuB,EAC7B,GAAG,EAAE,uBAAuB,GAC7B,OAAO,CAAC,sBAAsB,CAAC;IAuFlC;;;OAGG;IACH,OAAO,IAAI,IAAI;CAKlB"}
@@ -0,0 +1,201 @@
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
+ /**
5
+ * BIM ↔ scan deviation as a composed collaborator (issue #2425).
6
+ *
7
+ * Owns the whole deviation concern's state — the `DeviationPipeline`
8
+ * and the BVH-reuse fingerprint — so the `Renderer` holds one field
9
+ * instead of two and `computeDeviations` becomes a delegation. Nothing
10
+ * outside this file reads either piece of state: the render loop, the
11
+ * diagnostics counters and the bounds tracker are all untouched by it,
12
+ * which is what makes this seam separable at all.
13
+ *
14
+ * The collaborator borrows (never owns) the device, the point-cloud
15
+ * renderer and the scene — they arrive per call in `DeviationComputeContext`.
16
+ */
17
+ import { DeviationPipeline } from './deviation-pipeline.js';
18
+ import { buildTriangleBVH } from './triangle-bvh.js';
19
+ /**
20
+ * Build a deterministic fingerprint of the BVH input mesh set so
21
+ * `Renderer.computeDeviations` can skip the rebuild when the source
22
+ * geometry hasn't changed. Folds in expressId / modelIndex / position
23
+ * + index lengths per mesh so two distinct mesh sets that happen to
24
+ * share the same aggregate position-length total can't collide on the
25
+ * same fingerprint and reuse a stale BVH.
26
+ */
27
+ function computeBvhFingerprint(meshes) {
28
+ const parts = [String(meshes.length)];
29
+ for (const m of meshes) {
30
+ const id = m.expressId ?? -1;
31
+ const mi = m.modelIndex ?? -1;
32
+ const posLen = m.positions?.length ?? 0;
33
+ const idxLen = m.indices?.length ?? 0;
34
+ parts.push(`${id}:${mi}:${posLen}:${idxLen}`);
35
+ }
36
+ return parts.join('|');
37
+ }
38
+ /**
39
+ * Aggregate every triangle source the scene exposes — individual
40
+ * meshes (created on demand by picking / highlights) AND batched
41
+ * meshes (the streaming geometry path's compact GPU buffers).
42
+ * Both formats arrive as `MeshData`; the BVH builder doesn't care
43
+ * which source they came from.
44
+ */
45
+ function collectAllSceneMeshes(scene) {
46
+ // The Scene keeps every CPU-side MeshData regardless of which
47
+ // ingest path produced it (STEP / IFCx / GLB). One iteration
48
+ // covers individual + batched + multi-piece + multi-model.
49
+ // `forEachMeshData` deduplicates by identity so a colour-merged
50
+ // batch is only added once even if it's indexed under multiple
51
+ // contributor expressIds.
52
+ const out = [];
53
+ scene.forEachMeshData((md) => {
54
+ if (md.positions && md.positions.length > 0)
55
+ out.push(md);
56
+ });
57
+ return out;
58
+ }
59
+ /**
60
+ * Owns the deviation compute pipeline and its BVH cache.
61
+ *
62
+ * Lifecycle mirrors the `Renderer`'s: `init()` on device creation,
63
+ * `destroy()` before the device goes away.
64
+ */
65
+ export class DeviationComputer {
66
+ pipeline = null;
67
+ /**
68
+ * Cache of which mesh-set the BVH was built from. We rebuild on
69
+ * `computeDeviations` only when the cached "fingerprint" misses,
70
+ * so re-running deviation against the same model is a fast
71
+ * dispatch — the BVH is multi-second on big BIMs and we don't
72
+ * want to pay that on every slider drag.
73
+ */
74
+ bvhFingerprint = null;
75
+ /**
76
+ * Create the compute pipeline for the BIM↔scan deviation heatmap.
77
+ * Lazily owns the per-triangle BVH GPU buffers; idle until the
78
+ * first `compute` call.
79
+ *
80
+ * Idempotent: a second `init()` without an intervening `destroy()`
81
+ * tears down the previous pipeline (releasing its GPU buffers
82
+ * instead of orphaning them) and clears `bvhFingerprint`. Without
83
+ * this, the stale fingerprint would match the next `compute()`
84
+ * call's input, skip re-uploading the BVH into the NEW pipeline,
85
+ * and `dispatch()` would silently no-op every chunk (no BVH
86
+ * uploaded) — reporting a plausible-looking deviation of zero
87
+ * instead of failing loudly.
88
+ */
89
+ init(device) {
90
+ this.pipeline?.destroy();
91
+ this.pipeline = new DeviationPipeline(device);
92
+ this.bvhFingerprint = null;
93
+ }
94
+ /**
95
+ * Compute BIM ↔ scan deviation for every loaded point cloud asset.
96
+ *
97
+ * Walks every triangle in the scene (individual + batched meshes,
98
+ * regardless of which IFC ingest path produced them — STEP, IFCx,
99
+ * GLB, or federated combinations), builds a per-triangle BVH on
100
+ * the GPU, then runs a closest-point compute pass per chunk that
101
+ * writes signed distance into each chunk's deviation buffer.
102
+ *
103
+ * Returns metadata so the UI can populate a histogram + auto-range:
104
+ * the per-asset point count, the suggested ±range from the 95th
105
+ * percentile, and the bbox the BVH was built from.
106
+ *
107
+ * Idempotent: re-running with the same mesh set reuses the GPU
108
+ * BVH (the BVH build dominates wall time on big BIMs). Pass
109
+ * `forceRebuild: true` to invalidate.
110
+ */
111
+ async compute(opts, ctx) {
112
+ if (!this.pipeline || !ctx.pointCloudRenderer) {
113
+ throw new Error('Renderer not initialised — call init() first.');
114
+ }
115
+ const meshes = collectAllSceneMeshes(ctx.scene);
116
+ // Fingerprint folds in per-mesh expressId / modelIndex /
117
+ // positions length / triangle count, so two distinct meshes
118
+ // that happen to share an aggregate position-length total
119
+ // can't alias each other. A federation reload that swaps one
120
+ // model for another with the same total triangle count would
121
+ // otherwise reuse the previous BVH and report wrong distances.
122
+ const fingerprint = computeBvhFingerprint(meshes);
123
+ if (opts.forceRebuild || fingerprint !== this.bvhFingerprint) {
124
+ const bvh = buildTriangleBVH(meshes);
125
+ this.pipeline.uploadBvh(bvh);
126
+ this.bvhFingerprint = fingerprint;
127
+ }
128
+ const stats = this.pipeline.getBvhStats();
129
+ const maxRange = opts.maxRange ?? 1.0;
130
+ // Encode every chunk into a single command submit so the GPU
131
+ // can pipeline the dispatches without a CPU round-trip per
132
+ // chunk. Histogram readback is a follow-up — for v1 we emit
133
+ // the deviation buffers and let the splat shader visualise.
134
+ const encoder = ctx.device.getDevice().createCommandEncoder({ label: 'pointcloud-deviation' });
135
+ let chunksProcessed = 0;
136
+ let pointsProcessed = 0;
137
+ const nodes = ctx.pointCloudRenderer.getInternalNodes();
138
+ for (const node of nodes) {
139
+ for (const chunk of node.chunks) {
140
+ const ok = this.pipeline.dispatch(encoder, {
141
+ positionsBuffer: chunk.vertexBuffer,
142
+ deviationsBuffer: chunk.deviationBuffer,
143
+ pointCount: chunk.pointCount,
144
+ maxRange,
145
+ // #1804: chunk positions are stored in the asset's
146
+ // decode-shifted local frame when IfcMapConversion
147
+ // alignment is active; the BVH triangles are world
148
+ // space, so the compute pass must apply the same
149
+ // per-asset matrix the splat shader renders with.
150
+ model: node.model,
151
+ });
152
+ if (ok) {
153
+ chunksProcessed++;
154
+ pointsProcessed += chunk.pointCount;
155
+ }
156
+ }
157
+ }
158
+ ctx.device.getDevice().queue.submit([encoder.finish()]);
159
+ try {
160
+ // Wait until the GPU finishes the dispatches before resolving.
161
+ // Otherwise the caller's "compute done" callback fires before
162
+ // the deviation buffers are actually populated.
163
+ await ctx.device.getDevice().queue.onSubmittedWorkDone();
164
+ }
165
+ finally {
166
+ // The GPU is done reading each chunk's params uniform — free
167
+ // them. Must run even when the await above rejects (e.g. the
168
+ // device is lost mid-submit): the transient params buffers
169
+ // were already created and pushed onto `transientParamsBuffers`
170
+ // above, so skipping this on the rejection path would leak
171
+ // them.
172
+ this.pipeline.releaseTransientParams();
173
+ }
174
+ ctx.requestRender();
175
+ // Suggest a default half-range = max(0.01m, max-extent / 1000).
176
+ // Tighter than the maxRange clip; gives the user a reasonable
177
+ // starting slider position without a histogram readback.
178
+ const bb = stats.bounds;
179
+ const suggestedHalfRange = bb
180
+ ? Math.max(0.01, Math.max(bb.max[0] - bb.min[0], bb.max[1] - bb.min[1], bb.max[2] - bb.min[2]) / 1000)
181
+ : 0.05;
182
+ return {
183
+ bvhTriangles: stats.triangleCount,
184
+ bvhNodes: stats.nodeCount,
185
+ chunksProcessed,
186
+ pointsProcessed,
187
+ bounds: stats.bounds,
188
+ suggestedHalfRange,
189
+ };
190
+ }
191
+ /**
192
+ * Release the compute pipeline + cached BVH GPU buffers and forget
193
+ * the fingerprint, so a re-`init()` on a fresh device rebuilds.
194
+ */
195
+ destroy() {
196
+ this.pipeline?.destroy();
197
+ this.pipeline = null;
198
+ this.bvhFingerprint = null;
199
+ }
200
+ }
201
+ //# sourceMappingURL=deviation-computer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviation-computer.js","sourceRoot":"","sources":["../../src/deviation/deviation-computer.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -69,6 +69,7 @@ import { SnapDetector, type SnapTarget, type SnapOptions, type EdgeLockInput, ty
69
69
  import { type RenderDegradationInfo } from './render-degradation.js';
70
70
  import type { FrameStats } from './render-stats.js';
71
71
  import type { PointCloudAsset } from '@ifc-lite/geometry';
72
+ import { type DeviationComputeOptions, type DeviationComputeResult } from './deviation/deviation-computer.js';
72
73
  /**
73
74
  * Main renderer class
74
75
  */
@@ -185,15 +186,8 @@ export declare class Renderer {
185
186
  /** Retained so a listener registered AFTER the loss still learns of it. */
186
187
  private deviceLostInfo;
187
188
  private deviceLostListeners;
188
- private deviationPipeline;
189
- /**
190
- * Cache of which mesh-set the BVH was built from. We rebuild on
191
- * `computeDeviations` only when the cached "fingerprint" misses,
192
- * so re-running deviation against the same model is a fast
193
- * dispatch — the BVH is multi-second on big BIMs and we don't
194
- * want to pay that on every slider drag.
195
- */
196
- private deviationBvhFingerprint;
189
+ /** BIM ↔ scan deviation: owns the compute pipeline + its BVH cache. */
190
+ private readonly deviationComputer;
197
191
  private readonly visualEnhancementResolver;
198
192
  private readonly modelBoundsTracker;
199
193
  /** Read-only view of the tracked scene AABB (live reference, not a copy). */
@@ -444,43 +438,11 @@ export declare class Renderer {
444
438
  /**
445
439
  * Compute BIM ↔ scan deviation for every loaded point cloud asset.
446
440
  *
447
- * Walks every triangle in the scene (individual + batched meshes,
448
- * regardless of which IFC ingest path produced them — STEP, IFCx,
449
- * GLB, or federated combinations), builds a per-triangle BVH on
450
- * the GPU, then runs a closest-point compute pass per chunk that
451
- * writes signed distance into each chunk's deviation buffer.
452
- *
453
- * Returns metadata so the UI can populate a histogram + auto-range:
454
- * the per-asset point count, the suggested ±range from the 95th
455
- * percentile, and the bbox the BVH was built from.
456
- *
457
- * Idempotent: re-running with the same mesh set reuses the GPU
458
- * BVH (the BVH build dominates wall time on big BIMs). Pass
459
- * `forceRebuild: true` to invalidate.
460
- */
461
- computeDeviations(opts?: {
462
- /** Clip range applied during compute. 0 → no clip. Default 1m. */
463
- maxRange?: number;
464
- forceRebuild?: boolean;
465
- }): Promise<{
466
- bvhTriangles: number;
467
- bvhNodes: number;
468
- chunksProcessed: number;
469
- pointsProcessed: number;
470
- bounds: {
471
- min: [number, number, number];
472
- max: [number, number, number];
473
- } | null;
474
- suggestedHalfRange: number;
475
- }>;
476
- /**
477
- * Aggregate every triangle source the scene exposes — individual
478
- * meshes (created on demand by picking / highlights) AND batched
479
- * meshes (the streaming geometry path's compact GPU buffers).
480
- * Both formats arrive as `MeshData`; the BVH builder doesn't care
481
- * which source they came from.
482
- */
483
- private collectAllSceneMeshes;
441
+ * Delegates to the `DeviationComputer` collaborator, which owns the
442
+ * compute pipeline and the BVH-reuse fingerprint; see
443
+ * `deviation/deviation-computer.ts` for the full contract.
444
+ */
445
+ computeDeviations(opts?: DeviationComputeOptions): Promise<DeviationComputeResult>;
484
446
  /**
485
447
  * Toggle Eye-Dome Lighting and tune its strength.
486
448
  *
@@ -866,6 +828,20 @@ export declare class Renderer {
866
828
  vertices: Float32Array;
867
829
  color: [number, number, number, number];
868
830
  } | null): void;
831
+ /**
832
+ * Draw the focused clash's TRUE INTERSECTION VOLUME — the actual overlap
833
+ * mesh from `clashIntersectionSolid` — as an opaque solid, so the clash
834
+ * reads as a shape rather than a wireframe box or contact line (the
835
+ * BIMcollab Zoom / Solibri presentation). Pass `null` to clear. Independent
836
+ * of `setClashOverlapBox` / `setClashContactLines`: the caller decides
837
+ * which one is current for a given clash (solid when the kernel resolved
838
+ * one, box/lines as the fallback when it didn't).
839
+ */
840
+ setClashIntersectionSolid(solid: {
841
+ positions: Float32Array | Float64Array;
842
+ indices: Uint32Array;
843
+ color: [number, number, number, number];
844
+ } | null): void;
869
845
  /**
870
846
  * Upload filled IfcAnnotation regions for the symbolic overlay
871
847
  * (issue #653). Pass an empty array to clear.