@ifc-lite/renderer 1.41.0 → 1.43.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 (57) hide show
  1. package/dist/camera-fit-policy.d.ts.map +1 -1
  2. package/dist/camera-fit-policy.js +18 -7
  3. package/dist/camera-fit-policy.js.map +1 -1
  4. package/dist/camera.d.ts.map +1 -1
  5. package/dist/camera.js +77 -7
  6. package/dist/camera.js.map +1 -1
  7. package/dist/deviation/deviation-pipeline.d.ts.map +1 -1
  8. package/dist/deviation/deviation-pipeline.js +21 -4
  9. package/dist/deviation/deviation-pipeline.js.map +1 -1
  10. package/dist/index.d.ts +179 -35
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +478 -672
  13. package/dist/index.js.map +1 -1
  14. package/dist/math.d.ts +10 -1
  15. package/dist/math.d.ts.map +1 -1
  16. package/dist/math.js +107 -12
  17. package/dist/math.js.map +1 -1
  18. package/dist/model-bounds-tracker.d.ts +96 -0
  19. package/dist/model-bounds-tracker.d.ts.map +1 -0
  20. package/dist/model-bounds-tracker.js +147 -0
  21. package/dist/model-bounds-tracker.js.map +1 -0
  22. package/dist/pointcloud/point-cloud-node.d.ts.map +1 -1
  23. package/dist/pointcloud/point-cloud-node.js +24 -4
  24. package/dist/pointcloud/point-cloud-node.js.map +1 -1
  25. package/dist/render-degradation.d.ts +77 -0
  26. package/dist/render-degradation.d.ts.map +1 -0
  27. package/dist/render-degradation.js +52 -0
  28. package/dist/render-degradation.js.map +1 -0
  29. package/dist/render-section-draw.d.ts +51 -0
  30. package/dist/render-section-draw.d.ts.map +1 -0
  31. package/dist/render-section-draw.js +66 -0
  32. package/dist/render-section-draw.js.map +1 -0
  33. package/dist/render-section-plane.d.ts +128 -0
  34. package/dist/render-section-plane.d.ts.map +1 -0
  35. package/dist/render-section-plane.js +298 -0
  36. package/dist/render-section-plane.js.map +1 -0
  37. package/dist/renderer-overlays.d.ts +144 -0
  38. package/dist/renderer-overlays.d.ts.map +1 -0
  39. package/dist/renderer-overlays.js +257 -0
  40. package/dist/renderer-overlays.js.map +1 -0
  41. package/dist/renderer-symbolic-overlays.d.ts +52 -0
  42. package/dist/renderer-symbolic-overlays.d.ts.map +1 -0
  43. package/dist/renderer-symbolic-overlays.js +120 -0
  44. package/dist/renderer-symbolic-overlays.js.map +1 -0
  45. package/dist/scene.d.ts +103 -1
  46. package/dist/scene.d.ts.map +1 -1
  47. package/dist/scene.js +374 -104
  48. package/dist/scene.js.map +1 -1
  49. package/dist/section-2d-overlay.d.ts +17 -0
  50. package/dist/section-2d-overlay.d.ts.map +1 -1
  51. package/dist/section-2d-overlay.js +62 -0
  52. package/dist/section-2d-overlay.js.map +1 -1
  53. package/dist/visual-enhancement.d.ts +33 -0
  54. package/dist/visual-enhancement.d.ts.map +1 -0
  55. package/dist/visual-enhancement.js +46 -0
  56. package/dist/visual-enhancement.js.map +1 -0
  57. package/package.json +3 -3
@@ -0,0 +1,257 @@
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 { SectionPlaneRenderer } from './section-plane.js';
5
+ import { Section2DOverlayRenderer } from './section-2d-overlay.js';
6
+ import { SymbolicOverlays } from './renderer-symbolic-overlays.js';
7
+ import { aabbEdgeLineList } from './aabb-edges.js';
8
+ import { projectedBoundsRange } from './render-section-plane.js';
9
+ import { drawSectionOverlays } from './render-section-draw.js';
10
+ export class RendererOverlays {
11
+ host;
12
+ sectionPlaneRenderer = null;
13
+ section2DOverlayRenderer = null;
14
+ // Overlay/section-cut line colour, kept here so it survives a
15
+ // pre-init call and a section2DOverlayRenderer re-creation (re-applied below).
16
+ overlayLineColor = [0, 0, 0, 1];
17
+ symbolic;
18
+ constructor(host) {
19
+ this.host = host;
20
+ this.symbolic = new SymbolicOverlays(host);
21
+ }
22
+ /**
23
+ * Create the overlay GPU objects. Called from `Renderer.init()` once the
24
+ * device and the main pipeline (for its sample count) exist.
25
+ */
26
+ init(device, format, sampleCount) {
27
+ this.sectionPlaneRenderer = new SectionPlaneRenderer(device, format, sampleCount);
28
+ this.section2DOverlayRenderer = new Section2DOverlayRenderer(device, format, sampleCount);
29
+ // Re-apply any colour set before this (re)creation so it isn't lost.
30
+ this.section2DOverlayRenderer.setOverlayLineColor(this.overlayLineColor);
31
+ this.symbolic.init(device, format, sampleCount);
32
+ }
33
+ /** Release every overlay GPU resource. Idempotent, like `Renderer.destroy()`. */
34
+ destroy() {
35
+ this.sectionPlaneRenderer?.destroy();
36
+ this.sectionPlaneRenderer = null;
37
+ this.section2DOverlayRenderer?.dispose();
38
+ this.section2DOverlayRenderer = null;
39
+ this.symbolic.destroy();
40
+ }
41
+ /**
42
+ * Draw every overlay into the frame's render pass, in the documented order.
43
+ * Called from the encode region right before `pass.end()`.
44
+ */
45
+ draw(pass, ctx) {
46
+ const { viewProj, camera } = ctx;
47
+ drawSectionOverlays(pass, this.sectionPlaneRenderer, this.section2DOverlayRenderer, ctx);
48
+ // Standalone IFC annotation overlay (issue #653). The line
49
+ // vertices were pre-lifted to world space at upload time, so
50
+ // this draw happens regardless of whether a section plane is
51
+ // active — annotations are a free-floating "drawing layer"
52
+ // that sits at each annotation's storey elevation.
53
+ //
54
+ // This block was previously nested inside the `if (options.sectionPlane && ...)`
55
+ // guard above, contradicting its own comment. Loading an
56
+ // annotation-only model with no section plane meant the entire
57
+ // overlay was skipped at draw time even though 9000+ vertices
58
+ // had been uploaded successfully. Pulled out to its own block.
59
+ //
60
+ // Order: fills (background) → lines (outlines on top) →
61
+ // texts (labels above everything).
62
+ this.symbolic.drawFills(pass, viewProj);
63
+ if (this.section2DOverlayRenderer?.hasAnnotationLines3D()) {
64
+ this.section2DOverlayRenderer.drawAnnotationLines3D(pass, viewProj);
65
+ }
66
+ if (this.section2DOverlayRenderer?.hasAlignmentLines3D()) {
67
+ this.section2DOverlayRenderer.drawAlignmentLines3D(pass, viewProj);
68
+ }
69
+ if (this.section2DOverlayRenderer?.hasGridLines3D()) {
70
+ this.section2DOverlayRenderer.drawGridLines3D(pass, viewProj);
71
+ }
72
+ if (this.section2DOverlayRenderer?.hasDxfLines3D()) {
73
+ this.section2DOverlayRenderer.drawDxfLines3D(pass, viewProj);
74
+ }
75
+ if (this.section2DOverlayRenderer?.hasClashBoxLines3D()) {
76
+ this.section2DOverlayRenderer.drawClashBoxLines3D(pass, viewProj);
77
+ }
78
+ this.symbolic.drawTexts(pass, viewProj, ctx.canvasWidth, ctx.canvasHeight, camera);
79
+ }
80
+ /** See `Renderer.uploadSection2DOverlay` for the published contract. */
81
+ uploadSection2DOverlay(polygons, lines, axis, position, // 0-100 percentage
82
+ sectionRange, // Same storey-based range as section plane
83
+ flipped = false, customPlane) {
84
+ // Rendering is dirty-flag gated, so every path that actually CHANGES
85
+ // overlay geometry has to request a frame or the new drawing only
86
+ // appears when something unrelated next dirties the viewport (#2442).
87
+ // The two early returns below leave the geometry untouched, so they
88
+ // correctly ask for nothing — matching `uploadGridLines3D` and friends.
89
+ if (!this.section2DOverlayRenderer)
90
+ return;
91
+ if (customPlane) {
92
+ // Custom-plane path: planePosition / axis are unused — the
93
+ // basis the cap shader needs travels in `customPlane`. We pass
94
+ // 0 for `planePosition` and the existing `axis` so the cardinal
95
+ // shader code path that callers depend on (e.g. legacy SVG
96
+ // export) keeps working when customPlane is omitted.
97
+ this.section2DOverlayRenderer.uploadDrawing(polygons, lines, axis, 0, flipped, customPlane);
98
+ this.host.requestRender();
99
+ return;
100
+ }
101
+ // Same range formula as the clip plane (`resolveSectionPlaneFrame`),
102
+ // shared rather than copied — but deliberately evaluated against the
103
+ // UN-ROTATED axis normal, which is what makes the two agree instead of
104
+ // merely look alike (#2447).
105
+ //
106
+ // `planePosition` is not a plane distance here: `transform2Dto3D` lifts
107
+ // the cardinal drawing onto an AXIS-ALIGNED plane at that world
108
+ // coordinate (`side` -> `[planePosition, y, x]`), and the polygons it
109
+ // lifts were cut on that same axis-aligned plane upstream. Feeding it
110
+ // the rotated plane's distance would move the cap off the geometry it
111
+ // was cut from. A rotated or face-picked plane reaches the cap through
112
+ // `customPlane` above, which carries its own basis.
113
+ const axisNormal = axis === 'side' ? [1, 0, 0] : axis === 'down' ? [0, 1, 0] : [0, 0, 1];
114
+ const modelBounds = this.host.getModelBounds();
115
+ // Allow upload if either sectionRange has both values, or modelBounds exists as fallback
116
+ const hasFullRange = sectionRange?.min !== undefined && sectionRange?.max !== undefined;
117
+ if (!hasFullRange && !modelBounds)
118
+ return;
119
+ const axisRange = modelBounds ? projectedBoundsRange(modelBounds.min, modelBounds.max, axisNormal) : null;
120
+ const minVal = sectionRange?.min ?? axisRange.min;
121
+ const maxVal = sectionRange?.max ?? axisRange.max;
122
+ const planePosition = minVal + (position / 100) * (maxVal - minVal);
123
+ this.section2DOverlayRenderer.uploadDrawing(polygons, lines, axis, planePosition, flipped);
124
+ this.host.requestRender();
125
+ }
126
+ /** See `Renderer.clearSection2DOverlay` for the published contract. */
127
+ clearSection2DOverlay() {
128
+ if (this.section2DOverlayRenderer) {
129
+ this.section2DOverlayRenderer.clearGeometry();
130
+ this.host.requestRender();
131
+ }
132
+ }
133
+ /** See `Renderer.setOverlayLineColor` for the published contract. */
134
+ setOverlayLineColor(color) {
135
+ // Persist here so a pre-init call (and any later overlay
136
+ // re-creation) keeps the colour — init() re-applies this.overlayLineColor.
137
+ this.overlayLineColor = color;
138
+ this.section2DOverlayRenderer?.setOverlayLineColor(color);
139
+ this.host.requestRender();
140
+ }
141
+ /** See `Renderer.uploadAnnotationLines3D` for the published contract. */
142
+ uploadAnnotationLines3D(vertices) {
143
+ if (!this.section2DOverlayRenderer)
144
+ return;
145
+ this.section2DOverlayRenderer.uploadAnnotationLines3D(vertices);
146
+ // Contribute annotation extents to modelBounds + camera sceneBounds
147
+ // so an annotation-only model (no IfcProduct meshes — common for
148
+ // separate "annotation sheets") gets framed by Home / fit-to-view
149
+ // AND has correct near/far clipping. Without sceneBounds the camera
150
+ // frustum doesn't include the annotation cluster and they're clipped
151
+ // away even when the camera is pointed at them. Mirror the
152
+ // point-cloud upload path (`addPointClouds`, `setPointClouds`) which
153
+ // does the same thing.
154
+ this.host.expandModelBoundsWithFlatVertices(vertices, 3);
155
+ this.host.syncCameraSceneBounds();
156
+ this.host.requestRender();
157
+ }
158
+ /** See `Renderer.clearAnnotationLines3D` for the published contract. */
159
+ clearAnnotationLines3D() {
160
+ if (this.section2DOverlayRenderer) {
161
+ this.section2DOverlayRenderer.clearAnnotationLines3D();
162
+ this.host.requestRender();
163
+ }
164
+ }
165
+ /** See `Renderer.uploadAlignmentLines3D` for the published contract. */
166
+ uploadAlignmentLines3D(vertices) {
167
+ if (!this.section2DOverlayRenderer)
168
+ return;
169
+ this.section2DOverlayRenderer.uploadAlignmentLines3D(vertices);
170
+ // Frame alignment-only files the same way annotation overlays are
171
+ // framed (see uploadAnnotationLines3D).
172
+ this.host.expandModelBoundsWithFlatVertices(vertices, 3);
173
+ this.host.syncCameraSceneBounds();
174
+ this.host.requestRender();
175
+ }
176
+ /** See `Renderer.clearAlignmentLines3D` for the published contract. */
177
+ clearAlignmentLines3D() {
178
+ if (this.section2DOverlayRenderer) {
179
+ this.section2DOverlayRenderer.clearAlignmentLines3D();
180
+ this.host.requestRender();
181
+ }
182
+ }
183
+ /**
184
+ * See `Renderer.uploadGridLines3D` for the published contract. Unlike
185
+ * alignment, grids do NOT expand model bounds: they're behind a visibility
186
+ * toggle, so toggling them on must not reframe the camera.
187
+ */
188
+ uploadGridLines3D(vertices) {
189
+ if (!this.section2DOverlayRenderer)
190
+ return;
191
+ this.section2DOverlayRenderer.uploadGridLines3D(vertices);
192
+ this.host.requestRender();
193
+ }
194
+ /** See `Renderer.clearGridLines3D` for the published contract. */
195
+ clearGridLines3D() {
196
+ if (this.section2DOverlayRenderer) {
197
+ this.section2DOverlayRenderer.clearGridLines3D();
198
+ this.host.requestRender();
199
+ }
200
+ }
201
+ /** See `Renderer.uploadDxfLines3D` for the published contract. */
202
+ uploadDxfLines3D(vertices) {
203
+ if (!this.section2DOverlayRenderer)
204
+ return;
205
+ this.section2DOverlayRenderer.uploadDxfLines3D(vertices);
206
+ this.host.requestRender();
207
+ }
208
+ /** See `Renderer.clearDxfLines3D` for the published contract. */
209
+ clearDxfLines3D() {
210
+ if (this.section2DOverlayRenderer) {
211
+ this.section2DOverlayRenderer.clearDxfLines3D();
212
+ this.host.requestRender();
213
+ }
214
+ }
215
+ /** See `Renderer.setClashOverlapBox` for the published contract. */
216
+ setClashOverlapBox(box) {
217
+ if (!this.section2DOverlayRenderer)
218
+ return;
219
+ if (!box) {
220
+ this.section2DOverlayRenderer.clearClashBoxLines3D();
221
+ this.host.requestRender();
222
+ return;
223
+ }
224
+ this.section2DOverlayRenderer.setClashBoxLineColor(box.color);
225
+ this.section2DOverlayRenderer.uploadClashBoxLines3D(aabbEdgeLineList(box.min, box.max));
226
+ this.host.requestRender();
227
+ }
228
+ /**
229
+ * See `Renderer.setClashContactLines` for the published contract. Shares the
230
+ * clash-box line buffer, so only one of this / setClashOverlapBox shows.
231
+ */
232
+ setClashContactLines(lines) {
233
+ if (!this.section2DOverlayRenderer)
234
+ return;
235
+ if (!lines || lines.vertices.length === 0) {
236
+ this.section2DOverlayRenderer.clearClashBoxLines3D();
237
+ this.host.requestRender();
238
+ return;
239
+ }
240
+ this.section2DOverlayRenderer.setClashBoxLineColor(lines.color);
241
+ this.section2DOverlayRenderer.uploadClashBoxLines3D(lines.vertices);
242
+ this.host.requestRender();
243
+ }
244
+ /** See `Renderer.uploadAnnotationFills3D` for the published contract. */
245
+ uploadAnnotationFills3D(fills) {
246
+ this.symbolic.uploadFills(fills);
247
+ }
248
+ /** See `Renderer.uploadAnnotationTexts3D` for the published contract. */
249
+ uploadAnnotationTexts3D(texts) {
250
+ this.symbolic.uploadTexts(texts);
251
+ }
252
+ /** See `Renderer.hasSection2DOverlay` for the published contract. */
253
+ hasSection2DOverlay() {
254
+ return this.section2DOverlayRenderer?.hasGeometry() ?? false;
255
+ }
256
+ }
257
+ //# sourceMappingURL=renderer-overlays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-overlays.js","sourceRoot":"","sources":["../src/renderer-overlays.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The symbolic annotation overlay: filled IfcAnnotation regions and world-space
3
+ * text labels (issue #653).
4
+ *
5
+ * Split out of `RendererOverlays` because these are a genuinely separate pair of
6
+ * GPU objects — `SymbolicFillPipeline` and `SymbolicTextPipeline` own their own
7
+ * buffers, sampler and glyph-atlas texture, and share no state whatsoever with
8
+ * the `Section2DOverlayRenderer` that backs the section cap and every 3D line
9
+ * layer. `RendererOverlays` composes this and drives the draw order, because the
10
+ * order is the one thing the two families do share: fills paint underneath the
11
+ * line layers, text labels on top of everything.
12
+ */
13
+ import type { Camera } from './camera.js';
14
+ import { type SymbolicFillInput, type SymbolicTextInput } from './symbolic-overlay-pipelines.js';
15
+ /**
16
+ * The model-bounds bookkeeping the symbolic uploads borrow from `Renderer`.
17
+ * Structurally narrower than `OverlayHost` on purpose: this module never reads
18
+ * the bounds back, it only grows them and asks for a frame.
19
+ */
20
+ export interface SymbolicOverlayHost {
21
+ expandModelBoundsWithFlatVertices(positions: Float32Array, stride: number): void;
22
+ syncCameraSceneBounds(): void;
23
+ requestRender(): void;
24
+ }
25
+ export declare class SymbolicOverlays {
26
+ private readonly host;
27
+ private fillPipeline;
28
+ private textPipeline;
29
+ constructor(host: SymbolicOverlayHost);
30
+ /**
31
+ * Share the device + presentation format AND the MSAA sample count +
32
+ * objectId attachment shape with the rest of the renderer so these
33
+ * composite into the same RGBA pass without WebGPU pass-compatibility
34
+ * validation errors.
35
+ */
36
+ init(device: GPUDevice, format: GPUTextureFormat, sampleCount: number): void;
37
+ /**
38
+ * These pipelines own their own GPU buffers, sampler, and atlas texture —
39
+ * recreating the viewer without releasing them leaks resources on every
40
+ * reload.
41
+ */
42
+ destroy(): void;
43
+ /** Background layer: painted before the 3D line overlays. */
44
+ drawFills(pass: GPURenderPassEncoder, viewProj: Float32Array): void;
45
+ /** Label layer: painted after everything else. */
46
+ drawTexts(pass: GPURenderPassEncoder, viewProj: Float32Array, canvasWidth: number, canvasHeight: number, camera: Camera): void;
47
+ /** See `Renderer.uploadAnnotationFills3D` for the published contract. */
48
+ uploadFills(fills: readonly SymbolicFillInput[]): void;
49
+ /** See `Renderer.uploadAnnotationTexts3D` for the published contract. */
50
+ uploadTexts(texts: readonly SymbolicTextInput[]): void;
51
+ }
52
+ //# sourceMappingURL=renderer-symbolic-overlays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-symbolic-overlays.d.ts","sourceRoot":"","sources":["../src/renderer-symbolic-overlays.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAGH,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACzB,MAAM,iCAAiC,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,iCAAiC,CAAC,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACjF,qBAAqB,IAAI,IAAI,CAAC;IAC9B,aAAa,IAAI,IAAI,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAIb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,YAAY,CAAqC;IACzD,OAAO,CAAC,YAAY,CAAqC;gBAE5B,IAAI,EAAE,mBAAmB;IAEtD;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAK5E;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAOf,6DAA6D;IAC7D,SAAS,CAAC,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAMnE,kDAAkD;IAClD,SAAS,CACL,IAAI,EAAE,oBAAoB,EAC1B,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACf,IAAI;IAuCP,yEAAyE;IACzE,WAAW,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI;IAqBtD,yEAAyE;IACzE,WAAW,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI;CAkBzD"}
@@ -0,0 +1,120 @@
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 { SymbolicFillPipeline, SymbolicTextPipeline, } from './symbolic-overlay-pipelines.js';
5
+ export class SymbolicOverlays {
6
+ host;
7
+ fillPipeline = null;
8
+ textPipeline = null;
9
+ constructor(host) {
10
+ this.host = host;
11
+ }
12
+ /**
13
+ * Share the device + presentation format AND the MSAA sample count +
14
+ * objectId attachment shape with the rest of the renderer so these
15
+ * composite into the same RGBA pass without WebGPU pass-compatibility
16
+ * validation errors.
17
+ */
18
+ init(device, format, sampleCount) {
19
+ this.fillPipeline = new SymbolicFillPipeline(device, format, sampleCount);
20
+ this.textPipeline = new SymbolicTextPipeline(device, format, sampleCount);
21
+ }
22
+ /**
23
+ * These pipelines own their own GPU buffers, sampler, and atlas texture —
24
+ * recreating the viewer without releasing them leaks resources on every
25
+ * reload.
26
+ */
27
+ destroy() {
28
+ this.fillPipeline?.destroy();
29
+ this.fillPipeline = null;
30
+ this.textPipeline?.destroy();
31
+ this.textPipeline = null;
32
+ }
33
+ /** Background layer: painted before the 3D line overlays. */
34
+ drawFills(pass, viewProj) {
35
+ if (this.fillPipeline?.hasGeometry()) {
36
+ this.fillPipeline.render(pass, viewProj);
37
+ }
38
+ }
39
+ /** Label layer: painted after everything else. */
40
+ drawTexts(pass, viewProj, canvasWidth, canvasHeight, camera) {
41
+ if (!this.textPipeline?.hasGeometry())
42
+ return;
43
+ // Pass viewport pixel dimensions so the shader can scale glyphs
44
+ // to a constant on-screen size (BIMvision-style annotations)
45
+ // regardless of camera distance or authored text height.
46
+ //
47
+ // Also pass the screen-aligned camera basis (right, up) so
48
+ // billboarded glyphs (grid bubble tags) can face the camera
49
+ // in any orientation — top-down, eye-level, oblique alike.
50
+ const camPos = camera.getPosition();
51
+ const camTgt = camera.getTarget();
52
+ const camUpVec = camera.getUp();
53
+ // Forward = normalize(target - position).
54
+ let fx = camTgt.x - camPos.x;
55
+ let fy = camTgt.y - camPos.y;
56
+ let fz = camTgt.z - camPos.z;
57
+ let flen = Math.hypot(fx, fy, fz) || 1;
58
+ fx /= flen;
59
+ fy /= flen;
60
+ fz /= flen;
61
+ // Right = normalize(cross(forward, world-up)).
62
+ let rx = fy * camUpVec.z - fz * camUpVec.y;
63
+ let ry = fz * camUpVec.x - fx * camUpVec.z;
64
+ let rz = fx * camUpVec.y - fy * camUpVec.x;
65
+ let rlen = Math.hypot(rx, ry, rz) || 1;
66
+ rx /= rlen;
67
+ ry /= rlen;
68
+ rz /= rlen;
69
+ // True up = normalize(cross(right, forward)) — guaranteed
70
+ // perpendicular to both, defines screen-space vertical.
71
+ const ux = ry * fz - rz * fy;
72
+ const uy = rz * fx - rx * fz;
73
+ const uz = rx * fy - ry * fx;
74
+ this.textPipeline.render(pass, viewProj, canvasWidth, canvasHeight, [rx, ry, rz], [ux, uy, uz]);
75
+ }
76
+ /** See `Renderer.uploadAnnotationFills3D` for the published contract. */
77
+ uploadFills(fills) {
78
+ if (!this.fillPipeline)
79
+ return;
80
+ this.fillPipeline.upload(fills);
81
+ // Contribute fill extents to modelBounds — see uploadAnnotationLines3D.
82
+ for (const fill of fills) {
83
+ const pts = fill.points;
84
+ if (pts.length === 0)
85
+ continue;
86
+ // points are flat [x,z,x,z,...]; lift to (x, fill.worldY, z) per
87
+ // vertex so we expand bounds in the same world space the renderer draws in.
88
+ const lifted = new Float32Array((pts.length / 2) * 3);
89
+ for (let i = 0, j = 0; i < pts.length; i += 2, j += 3) {
90
+ lifted[j] = pts[i];
91
+ lifted[j + 1] = fill.worldY;
92
+ lifted[j + 2] = pts[i + 1];
93
+ }
94
+ this.host.expandModelBoundsWithFlatVertices(lifted, 3);
95
+ }
96
+ this.host.syncCameraSceneBounds();
97
+ this.host.requestRender();
98
+ }
99
+ /** See `Renderer.uploadAnnotationTexts3D` for the published contract. */
100
+ uploadTexts(texts) {
101
+ if (!this.textPipeline)
102
+ return;
103
+ this.textPipeline.upload(texts);
104
+ // Text origins are single points; pack them into a flat buffer and
105
+ // expand bounds. Glyph extents are small enough that origin-only
106
+ // suffices for framing.
107
+ if (texts.length > 0) {
108
+ const buf = new Float32Array(texts.length * 3);
109
+ for (let i = 0; i < texts.length; i++) {
110
+ buf[i * 3 + 0] = texts[i].worldPos[0];
111
+ buf[i * 3 + 1] = texts[i].worldPos[1];
112
+ buf[i * 3 + 2] = texts[i].worldPos[2];
113
+ }
114
+ this.host.expandModelBoundsWithFlatVertices(buf, 3);
115
+ this.host.syncCameraSceneBounds();
116
+ }
117
+ this.host.requestRender();
118
+ }
119
+ }
120
+ //# sourceMappingURL=renderer-symbolic-overlays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer-symbolic-overlays.js","sourceRoot":"","sources":["../src/renderer-symbolic-overlays.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAgB/D,OAAO,EACH,oBAAoB,EACpB,oBAAoB,GAGvB,MAAM,iCAAiC,CAAC;AAazC,MAAM,OAAO,gBAAgB;IAII;IAHrB,YAAY,GAAgC,IAAI,CAAC;IACjD,YAAY,GAAgC,IAAI,CAAC;IAEzD,YAA6B,IAAyB;QAAzB,SAAI,GAAJ,IAAI,CAAqB;IAAG,CAAC;IAE1D;;;;;OAKG;IACH,IAAI,CAAC,MAAiB,EAAE,MAAwB,EAAE,WAAmB;QACjE,IAAI,CAAC,YAAY,GAAG,IAAI,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QAC1E,IAAI,CAAC,YAAY,GAAG,IAAI,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9E,CAAC;IAED;;;;OAIG;IACH,OAAO;QACH,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED,6DAA6D;IAC7D,SAAS,CAAC,IAA0B,EAAE,QAAsB;QACxD,IAAI,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,CAAC;YACnC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED,kDAAkD;IAClD,SAAS,CACL,IAA0B,EAC1B,QAAsB,EACtB,WAAmB,EACnB,YAAoB,EACpB,MAAc;QAEd,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE;YAAE,OAAO;QAC9C,gEAAgE;QAChE,6DAA6D;QAC7D,yDAAyD;QACzD,EAAE;QACF,2DAA2D;QAC3D,4DAA4D;QAC5D,2DAA2D;QAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,0CAA0C;QAC1C,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QAC7B,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QAC7B,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QACvC,EAAE,IAAI,IAAI,CAAC;QAAC,EAAE,IAAI,IAAI,CAAC;QAAC,EAAE,IAAI,IAAI,CAAC;QACnC,+CAA+C;QAC/C,IAAI,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;QAC3C,IAAI,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;QAC3C,IAAI,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;QAC3C,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QACvC,EAAE,IAAI,IAAI,CAAC;QAAC,EAAE,IAAI,IAAI,CAAC;QAAC,EAAE,IAAI,IAAI,CAAC;QACnC,0DAA0D;QAC1D,wDAAwD;QACxD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,YAAY,CAAC,MAAM,CACpB,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EACZ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CACf,CAAC;IACN,CAAC;IAED,yEAAyE;IACzE,WAAW,CAAC,KAAmC;QAC3C,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC/B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,wEAAwE;QACxE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;YACxB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAC/B,iEAAiE;YACjE,4EAA4E;YAC5E,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpD,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC5B,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,iCAAiC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;IAC9B,CAAC;IAED,yEAAyE;IACzE,WAAW,CAAC,KAAmC;QAC3C,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC/B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,mEAAmE;QACnE,iEAAiE;QACjE,wBAAwB;QACxB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACtC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACtC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC1C,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,iCAAiC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;IAC9B,CAAC;CACJ"}
package/dist/scene.d.ts CHANGED
@@ -34,6 +34,23 @@ export interface TexturedMesh {
34
34
  bindGroup: GPUBindGroup;
35
35
  /** Authored tint (multiplies the sampled texel); white = texture passthrough. */
36
36
  color: [number, number, number, number];
37
+ /**
38
+ * The mesh's per-element local frame (`MeshData.origin`, already Y-up) — the
39
+ * renderer must reconstruct `world = origin + position`.
40
+ *
41
+ * The interleaved vertex buffer stores positions RELATIVE to this, which is
42
+ * the whole point of the local frame (#1114): keeping the world magnitude out
43
+ * of f32 so building-scale coordinates don't collapse adjacent vertices into
44
+ * degenerate fans. So it is applied as the draw's model translation, never
45
+ * folded back into the vertex data.
46
+ *
47
+ * `[0, 0, 0]` for the #961 orphan type-geometry path, whose positions are
48
+ * already absolute (`transform_mesh_local`); non-zero for the #1793
49
+ * occurrence path (`apply_submesh_placement` → `transform_mesh_world`), which
50
+ * is what real exporters write. Dropping it drew every textured occurrence
51
+ * collapsed toward the world origin (#1973).
52
+ */
53
+ origin: [number, number, number];
37
54
  /** Set when `texture` lives in the shared registry (#1781: one GPU texture
38
55
  * per `IfcImageTexture`, sampled by many meshes) — released by refcount,
39
56
  * never destroyed per-mesh. Undefined for per-mesh #961 blob/pixel uploads. */
@@ -46,6 +63,11 @@ export interface TexturedMesh {
46
63
  * `drawIndexed(indexCount, instanceCount)`. Buffers are Scene-owned, freed in clear().
47
64
  */
48
65
  export interface InstancedTemplateGPU {
66
+ /** Owning model (federation index). Templates are identified by
67
+ * `(modelIndex, slot)`, so one model's templates can be freed without
68
+ * disturbing another's — see `removeInstancedTemplatesForModel`. Defaults to
69
+ * 0, the single-model / primary case. */
70
+ modelIndex: number;
49
71
  vertexBuffer: GPUBuffer;
50
72
  indexBuffer: GPUBuffer;
51
73
  indexCount: number;
@@ -80,9 +102,22 @@ export declare class Scene {
80
102
  * Refcounted: entries die when the last referencing mesh is removed / on clear(). */
81
103
  private sharedTextures;
82
104
  private texturedDevice?;
105
+ /** GPU-instancing: unique templates + per-occurrence buffers (fed by
106
+ * addInstancedShard). SLOT-STABLE and therefore SPARSE: a per-model removal
107
+ * (`removeInstancedTemplatesForModel`) leaves `undefined` holes instead of
108
+ * splicing, because `InstancedOccurrence.templateIndex` holds the slot by
109
+ * value and a splice would silently repoint every later occurrence at the
110
+ * wrong template. New templates always append at `length`; freed slots are
111
+ * never recycled. Iterate `getInstancedTemplates()` (compacted, cached) for
112
+ * the draw/pick/accounting walks — never this array directly. */
83
113
  private instancedTemplates;
114
+ /** Compacted live view of `instancedTemplates`, rebuilt on mutation. */
115
+ private liveInstancedTemplates;
84
116
  private instancedVisible;
85
117
  private instancedEntityMap;
118
+ /** Compact CPU geometry per template, slot-aligned with `instancedTemplates`
119
+ * (so equally sparse) for CPU consumers. Also emptied wholesale on geometry
120
+ * release — every reader already tolerates a missing entry. */
86
121
  private instancedTemplateCpu;
87
122
  private instancedDevice?;
88
123
  private instancedSelected;
@@ -682,6 +717,46 @@ export declare class Scene {
682
717
  /** GPU-instancing templates for the renderer's instanced draw pass. Empty when
683
718
  * hidden (Types view mode) so the draw loop skips it. */
684
719
  getInstancedTemplates(): readonly InstancedTemplateGPU[];
720
+ /** Rebuild the compacted live-template view after a slot add/remove. */
721
+ private refreshLiveInstancedTemplates;
722
+ /** Model indices that currently own at least one instanced template. Unlike
723
+ * `getInstancedTemplates()` this ignores the Types-view visibility toggle —
724
+ * hiding the pass does not change who owns what. */
725
+ getInstancedModelIndices(): number[];
726
+ /**
727
+ * Free every instanced template owned by `modelIndex`: destroy exactly its
728
+ * own vertex/index/instance buffers, blank its slots (leaving holes, so no
729
+ * other model's `templateIndex` shifts), prune its occurrences from
730
+ * `instancedEntityMap`, and drop the per-id selection/hidden/override
731
+ * bookkeeping for ids that lost their LAST occurrence. Express ids shared
732
+ * with another model — the federated case before id offsetting — keep the
733
+ * surviving model's occurrences.
734
+ *
735
+ * `boundingBoxes` bookkeeping (#2073): an id that ALSO owns flat geometry is
736
+ * a mixed id — `removeMeshesForEntity` owns that cache, so it is left alone
737
+ * here even when the id's last instanced occurrence is pruned. An
738
+ * instanced-only id that loses its last occurrence has nothing left to keep
739
+ * a cached box for, so its entry is dropped — otherwise bbox-raycast and
740
+ * `getBounds()` (post geometry-release) keep finding/sizing an element that
741
+ * no longer has geometry. An id that keeps SOME occurrences (the shared,
742
+ * federated case) has its box recomputed from just the survivors, not left
743
+ * as the stale union that also covered the freed model's occurrences.
744
+ *
745
+ * Returns the number of templates removed (0 for an unknown model, which is a
746
+ * no-op).
747
+ */
748
+ removeInstancedTemplatesForModel(modelIndex: number): number;
749
+ /**
750
+ * Recompute `boundingBoxes[eid]` from exactly `occurrences` (REPLACING any
751
+ * existing entry rather than unioning into it), reading each occurrence's
752
+ * template-local AABB + packed instance matrix the same way
753
+ * `unionInstancedWorldAabb` does at upload time. Used when a model-level
754
+ * template removal (`removeInstancedTemplatesForModel`) prunes some but not
755
+ * all of a shared id's occurrences — a plain union only ever grows, so it
756
+ * cannot shrink to reflect occurrences that no longer exist. Deletes the
757
+ * entry outright if no surviving occurrence has a finite local box (#2073).
758
+ */
759
+ private recomputeInstancedWorldAabb;
685
760
  /**
686
761
  * Decode a per-batch IFNS instancing shard (`processGeometryBatchInstanced`)
687
762
  * and upload its templates for GPU-instanced drawing. Each unique geometry
@@ -698,7 +773,7 @@ export declare class Scene {
698
773
  * type-only dependency on @ifc-lite/geometry and the Scene stays focused on
699
774
  * GPU upload.
700
775
  */
701
- addInstancedShard(device: GPUDevice, shard: DecodedInstancedShard): void;
776
+ addInstancedShard(device: GPUDevice, shard: DecodedInstancedShard, modelIndex?: number): void;
702
777
  /** Transform a template's local AABB by an occurrence's column-major mat4 (read
703
778
  * from the packed instance record at `matOffset`) and union the world box into
704
779
  * boundingBoxes[eid]. Returns the occurrence's world box so the caller can also
@@ -788,7 +863,34 @@ export declare class Scene {
788
863
  * the registry refcount and die with their LAST reference; per-mesh (#961)
789
864
  * uploads are destroyed outright. */
790
865
  private releaseTexturedMeshTexture;
866
+ /**
867
+ * Destroy every GPU-instanced template's buffers and reset all instanced
868
+ * bookkeeping, regardless of owning model. Shared by `clear()` (full reset)
869
+ * — `clearFlatGeometry()` deliberately does NOT call this, so a reshape
870
+ * that still has models present can retain their instanced geometry
871
+ * (#2073).
872
+ */
873
+ private destroyAllInstancedTemplates;
791
874
  clear(): void;
875
+ /**
876
+ * Clear flat/batched geometry (meshes, batches, buckets, textured meshes,
877
+ * colour overlays, streaming state, residency bookkeeping) WITHOUT
878
+ * touching GPU-instanced templates (#2073). A reshape that still has at
879
+ * least one model present should call this instead of `clear()`, then
880
+ * reconcile instanced ownership with `removeInstancedTemplatesForModel`
881
+ * for any model that did NOT survive — that way a still-loaded model's
882
+ * repeated geometry (windows, doors, bolts, ...) stays resident across a
883
+ * visibility toggle / in-place content mutation / federated model add
884
+ * instead of silently vanishing (nothing re-uploads instanced shard bytes
885
+ * after their one-time drain).
886
+ *
887
+ * Bounding boxes are only dropped for ids with NO surviving instanced
888
+ * occurrence — an instanced-only id's box must outlive this call so
889
+ * raycast / measure / section keep working for the geometry that was
890
+ * just retained; a flat-only id's box is stale the moment its mesh data
891
+ * is gone, so it is dropped like everything else here.
892
+ */
893
+ clearFlatGeometry(): void;
792
894
  /**
793
895
  * Calculate bounding box from actual mesh vertex data
794
896
  */