@ifc-lite/renderer 1.15.3 → 1.17.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.
- package/README.md +73 -15
- package/dist/constants.d.ts +8 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +8 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -37
- package/dist/index.js.map +1 -1
- package/dist/pipeline.d.ts +11 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +65 -13
- package/dist/pipeline.js.map +1 -1
- package/dist/section-2d-overlay.d.ts +22 -7
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +219 -42
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-cap-style.d.ts +37 -0
- package/dist/section-cap-style.d.ts.map +1 -0
- package/dist/section-cap-style.js +38 -0
- package/dist/section-cap-style.js.map +1 -0
- package/dist/section-plane.d.ts +0 -3
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +33 -6
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/main.wgsl.d.ts +1 -1
- package/dist/shaders/main.wgsl.d.ts.map +1 -1
- package/dist/shaders/main.wgsl.js +22 -7
- package/dist/shaders/main.wgsl.js.map +1 -1
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ifc-lite/renderer
|
|
2
2
|
|
|
3
|
-
WebGPU-based 3D
|
|
3
|
+
WebGPU-based 3D renderer for IFClite. Zero-copy from WASM linear memory to GPU buffers, hardware frustum culling, GPU-accelerated picking, section planes, multi-model federation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,32 +8,90 @@ WebGPU-based 3D rendering engine for IFClite. Provides GPU-accelerated rendering
|
|
|
8
8
|
npm install @ifc-lite/renderer
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Render an IFC model
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Renderer } from '@ifc-lite/renderer';
|
|
15
|
+
import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
15
16
|
|
|
16
17
|
const renderer = new Renderer(canvas);
|
|
17
|
-
|
|
18
|
+
const geometry = new GeometryProcessor();
|
|
19
|
+
await Promise.all([renderer.init(), geometry.init()]);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
renderer.
|
|
21
|
-
renderer.
|
|
21
|
+
const meshes = await geometry.process(new Uint8Array(buffer));
|
|
22
|
+
renderer.loadGeometry(meshes);
|
|
23
|
+
renderer.requestRender();
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
`loadGeometry()` accepts a `GeometryResult` from `@ifc-lite/geometry` or a raw `MeshData[]`. The renderer keeps geometry in GPU buffers; subsequent `requestRender()` calls coalesce into a single frame.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
## Pick an entity
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
canvas.addEventListener('click', async (e) => {
|
|
32
|
+
const rect = canvas.getBoundingClientRect();
|
|
33
|
+
const hit = await renderer.pick(e.clientX - rect.left, e.clientY - rect.top);
|
|
34
|
+
|
|
35
|
+
if (hit) {
|
|
36
|
+
console.log(`Clicked expressId ${hit.expressId} at`, hit.point);
|
|
37
|
+
renderer.setSelection([hit.expressId]);
|
|
38
|
+
renderer.requestRender();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For exact world-space hits with surface normals, use `raycastScene(x, y)` — slower but returns the precise intersection point + normal.
|
|
44
|
+
|
|
45
|
+
## Section planes
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Cut the model with an axis-aligned section plane
|
|
49
|
+
renderer.setSectionPlane({
|
|
50
|
+
axis: 'down', // 'side' | 'down' | 'front' (X / Y / Z)
|
|
51
|
+
position: 3.0, // metres along axis
|
|
52
|
+
enabled: true,
|
|
53
|
+
flipped: false,
|
|
54
|
+
});
|
|
55
|
+
renderer.requestRender();
|
|
56
|
+
|
|
57
|
+
// Disable
|
|
58
|
+
renderer.setSectionPlane(null);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Visibility + colour overrides
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Hide a list of entities
|
|
65
|
+
renderer.setHiddenEntities(new Set([12345, 12346, 12347]));
|
|
66
|
+
|
|
67
|
+
// Solo a subset (everything else gets the ghost treatment)
|
|
68
|
+
renderer.setIsolatedEntities(new Set([42]));
|
|
69
|
+
|
|
70
|
+
// Tint specific entities
|
|
71
|
+
renderer.setColorOverrides(new Map([
|
|
72
|
+
[42, [1, 0, 0, 1]], // RGBA — bright red
|
|
73
|
+
[99, [0, 1, 0, 0.4]], // semi-transparent green
|
|
74
|
+
]));
|
|
75
|
+
renderer.requestRender();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Multi-model federation
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { federationRegistry } from '@ifc-lite/renderer';
|
|
82
|
+
|
|
83
|
+
// Register each model with a unique ID offset
|
|
84
|
+
federationRegistry.registerModel('arch', maxArchExpressId);
|
|
85
|
+
federationRegistry.registerModel('struct', maxStructExpressId);
|
|
86
|
+
|
|
87
|
+
// Now picks return globalIds; resolve back to (modelId, expressId)
|
|
88
|
+
const hit = await renderer.pick(x, y);
|
|
89
|
+
const { modelId, expressId } = federationRegistry.fromGlobalId(hit.expressId);
|
|
90
|
+
```
|
|
33
91
|
|
|
34
92
|
## API
|
|
35
93
|
|
|
36
|
-
See the [Rendering Guide](
|
|
94
|
+
See the [Rendering Guide](https://louistrue.github.io/ifc-lite/guide/rendering/) and [API Reference](https://louistrue.github.io/ifc-lite/api/typescript/#ifc-literenderer).
|
|
37
95
|
|
|
38
96
|
## License
|
|
39
97
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -83,8 +83,14 @@ export declare const PIPELINE_CONSTANTS: {
|
|
|
83
83
|
readonly FLAGS_BYTE_OFFSET: 176;
|
|
84
84
|
/** Default MSAA sample count */
|
|
85
85
|
readonly DEFAULT_SAMPLE_COUNT: 4;
|
|
86
|
-
/**
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Depth/stencil format used by RenderPipeline. Switched from depth32float
|
|
88
|
+
* to depth24plus-stencil8 when the 3D section work introduced a render
|
|
89
|
+
* pass shared by the main opaque, the section-plane preview, and the 2D
|
|
90
|
+
* overlay cap — WebGPU requires a single depth-stencil format across all
|
|
91
|
+
* pipelines that write to the same pass attachment.
|
|
92
|
+
*/
|
|
93
|
+
readonly DEPTH_FORMAT: "depth24plus-stencil8";
|
|
88
94
|
};
|
|
89
95
|
export declare const LIGHTING_CONSTANTS: {
|
|
90
96
|
readonly SUN_LIGHT: {
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAIA;;GAEG;AAMH,eAAO,MAAM,gBAAgB;IAE3B,0DAA0D;;IAE1D,6CAA6C;;IAI7C,kCAAkC;;IAElC,4CAA4C;;IAE5C,2BAA2B;;IAE3B,uBAAuB;;IAEvB,mCAAmC;;IAEnC,sDAAsD;;;;;;;;;;;;;;;;;;;;IAYtD,iDAAiD;;IAEjD,2CAA2C;;IAE3C,uCAAuC;;IAIvC,wDAAwD;;IAExD,wDAAwD;;IAExD,2CAA2C;;IAI3C,uCAAuC;;IAEvC,qCAAqC;;IAIrC,0CAA0C;;IAE1C,6CAA6C;;IAE7C,yCAAyC;;;;;;;CAKjC,CAAC;AAMX,eAAO,MAAM,aAAa;IACxB,qDAAqD;;IAErD,uCAAuC;;IAEvC,yBAAyB;;CAEjB,CAAC;AAMX,eAAO,MAAM,eAAe;IAC1B,+CAA+C;;CAEvC,CAAC;AAMX,eAAO,MAAM,kBAAkB;IAE7B,gCAAgC;;IAEhC,8CAA8C;;IAI9C,gCAAgC;;IAIhC
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAIA;;GAEG;AAMH,eAAO,MAAM,gBAAgB;IAE3B,0DAA0D;;IAE1D,6CAA6C;;IAI7C,kCAAkC;;IAElC,4CAA4C;;IAE5C,2BAA2B;;IAE3B,uBAAuB;;IAEvB,mCAAmC;;IAEnC,sDAAsD;;;;;;;;;;;;;;;;;;;;IAYtD,iDAAiD;;IAEjD,2CAA2C;;IAE3C,uCAAuC;;IAIvC,wDAAwD;;IAExD,wDAAwD;;IAExD,2CAA2C;;IAI3C,uCAAuC;;IAEvC,qCAAqC;;IAIrC,0CAA0C;;IAE1C,6CAA6C;;IAE7C,yCAAyC;;;;;;;CAKjC,CAAC;AAMX,eAAO,MAAM,aAAa;IACxB,qDAAqD;;IAErD,uCAAuC;;IAEvC,yBAAyB;;CAEjB,CAAC;AAMX,eAAO,MAAM,eAAe;IAC1B,+CAA+C;;CAEvC,CAAC;AAMX,eAAO,MAAM,kBAAkB;IAE7B,gCAAgC;;IAEhC,8CAA8C;;IAI9C,gCAAgC;;IAIhC;;;;;;OAMG;;CAEK,CAAC;AAMX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgBrB,CAAC;AAMX,eAAO,MAAM,eAAe;IAC1B,oFAAoF;;IAEpF,yEAAyE;;IAEzE,oFAAoF;;IAEpF,+BAA+B;;CAEvB,CAAC;AAMX,eAAO,MAAM,cAAc;IACzB,4DAA4D;;IAE5D,yBAAyB;;CAEjB,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -93,8 +93,14 @@ export const PIPELINE_CONSTANTS = {
|
|
|
93
93
|
/** Default MSAA sample count */
|
|
94
94
|
DEFAULT_SAMPLE_COUNT: 4,
|
|
95
95
|
// Depth buffer
|
|
96
|
-
/**
|
|
97
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Depth/stencil format used by RenderPipeline. Switched from depth32float
|
|
98
|
+
* to depth24plus-stencil8 when the 3D section work introduced a render
|
|
99
|
+
* pass shared by the main opaque, the section-plane preview, and the 2D
|
|
100
|
+
* overlay cap — WebGPU requires a single depth-stencil format across all
|
|
101
|
+
* pipelines that write to the same pass attachment.
|
|
102
|
+
*/
|
|
103
|
+
DEPTH_FORMAT: 'depth24plus-stencil8',
|
|
98
104
|
};
|
|
99
105
|
// ============================================================================
|
|
100
106
|
// Lighting Constants (for shaders)
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;GAEG;AAEH,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,iBAAiB;IACjB,0DAA0D;IAC1D,cAAc,EAAE,IAAI;IACpB,6CAA6C;IAC7C,sBAAsB,EAAE,KAAK;IAE7B,kBAAkB;IAClB,kCAAkC;IAClC,kBAAkB,EAAE,GAAG;IACvB,4CAA4C;IAC5C,iBAAiB,EAAE,IAAI;IACvB,2BAA2B;IAC3B,oBAAoB,EAAE,KAAK;IAC3B,uBAAuB;IACvB,gBAAgB,EAAE,KAAK;IACvB,mCAAmC;IACnC,cAAc,EAAE,GAAG;IACnB,sDAAsD;IACtD,wBAAwB,EAAE,OAAO;IAEjC,uBAAuB;IACvB,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAW;IACnD,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAW;IAC7C,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAW;IACzC,WAAW,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,aAAa;IACvC,YAAY,EAAE,GAAG;IACjB,WAAW,EAAE,MAAM;IAEnB,kBAAkB;IAClB,iDAAiD;IACjD,kBAAkB,EAAE,KAAK;IACzB,2CAA2C;IAC3C,oBAAoB,EAAE,KAAK;IAC3B,uCAAuC;IACvC,uBAAuB,EAAE,EAAE;IAE3B,yBAAyB;IACzB,wDAAwD;IACxD,OAAO,EAAE,IAAI;IACb,wDAAwD;IACxD,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI;IACvB,2CAA2C;IAC3C,cAAc,EAAE,IAAI;IAEpB,YAAY;IACZ,uCAAuC;IACvC,0BAA0B,EAAE,GAAG;IAC/B,qCAAqC;IACrC,wBAAwB,EAAE,GAAG;IAE7B,gBAAgB;IAChB,0CAA0C;IAC1C,uBAAuB,EAAE,GAAG;IAC5B,6CAA6C;IAC7C,wBAAwB,EAAE,GAAG;IAC7B,yCAAyC;IACzC,mBAAmB,EAAE,GAAG;IAExB,yBAAyB;IACzB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAW;CACtD,CAAC;AAEX,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,qDAAqD;IACrD,SAAS,EAAE,GAAG;IACd,uCAAuC;IACvC,uBAAuB,EAAE,CAAC;IAC1B,yBAAyB;IACzB,SAAS,EAAE,EAAE;CACL,CAAC;AAEX,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,+CAA+C;IAC/C,wBAAwB,EAAE,IAAI;CACtB,CAAC;AAEX,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,8DAA8D;IAC9D,gCAAgC;IAChC,mBAAmB,EAAE,GAAG;IACxB,8CAA8C;IAC9C,iBAAiB,EAAE,GAAG;IAEtB,OAAO;IACP,gCAAgC;IAChC,oBAAoB,EAAE,CAAC;IAEvB,eAAe;IACf
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;GAEG;AAEH,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,iBAAiB;IACjB,0DAA0D;IAC1D,cAAc,EAAE,IAAI;IACpB,6CAA6C;IAC7C,sBAAsB,EAAE,KAAK;IAE7B,kBAAkB;IAClB,kCAAkC;IAClC,kBAAkB,EAAE,GAAG;IACvB,4CAA4C;IAC5C,iBAAiB,EAAE,IAAI;IACvB,2BAA2B;IAC3B,oBAAoB,EAAE,KAAK;IAC3B,uBAAuB;IACvB,gBAAgB,EAAE,KAAK;IACvB,mCAAmC;IACnC,cAAc,EAAE,GAAG;IACnB,sDAAsD;IACtD,wBAAwB,EAAE,OAAO;IAEjC,uBAAuB;IACvB,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAW;IACnD,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAW;IAC7C,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAW;IACzC,WAAW,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,aAAa;IACvC,YAAY,EAAE,GAAG;IACjB,WAAW,EAAE,MAAM;IAEnB,kBAAkB;IAClB,iDAAiD;IACjD,kBAAkB,EAAE,KAAK;IACzB,2CAA2C;IAC3C,oBAAoB,EAAE,KAAK;IAC3B,uCAAuC;IACvC,uBAAuB,EAAE,EAAE;IAE3B,yBAAyB;IACzB,wDAAwD;IACxD,OAAO,EAAE,IAAI;IACb,wDAAwD;IACxD,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI;IACvB,2CAA2C;IAC3C,cAAc,EAAE,IAAI;IAEpB,YAAY;IACZ,uCAAuC;IACvC,0BAA0B,EAAE,GAAG;IAC/B,qCAAqC;IACrC,wBAAwB,EAAE,GAAG;IAE7B,gBAAgB;IAChB,0CAA0C;IAC1C,uBAAuB,EAAE,GAAG;IAC5B,6CAA6C;IAC7C,wBAAwB,EAAE,GAAG;IAC7B,yCAAyC;IACzC,mBAAmB,EAAE,GAAG;IAExB,yBAAyB;IACzB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAW;CACtD,CAAC;AAEX,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,qDAAqD;IACrD,SAAS,EAAE,GAAG;IACd,uCAAuC;IACvC,uBAAuB,EAAE,CAAC;IAC1B,yBAAyB;IACzB,SAAS,EAAE,EAAE;CACL,CAAC;AAEX,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,+CAA+C;IAC/C,wBAAwB,EAAE,IAAI;CACtB,CAAC;AAEX,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,8DAA8D;IAC9D,gCAAgC;IAChC,mBAAmB,EAAE,GAAG;IACxB,8CAA8C;IAC9C,iBAAiB,EAAE,GAAG;IAEtB,OAAO;IACP,gCAAgC;IAChC,oBAAoB,EAAE,CAAC;IAEvB,eAAe;IACf;;;;;;OAMG;IACH,YAAY,EAAE,sBAA+B;CACrC,CAAC;AAEX,+EAA+E;AAC/E,mCAAmC;AACnC,+EAA+E;AAE/E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,6CAA6C;IAC7C,SAAS,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAW;IAC9C,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAW;IACjD,SAAS,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAW;IAE/C,oBAAoB;IACpB,SAAS,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAW;IAC/C,YAAY,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAW;IAEnD,cAAc;IACd,iBAAiB,EAAE,IAAI;IACvB,qBAAqB,EAAE,IAAI;IAC3B,sBAAsB,EAAE,IAAI;IAC5B,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,GAAG;CACR,CAAC;AAEX,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,oFAAoF;IACpF,yBAAyB,EAAE,GAAG;IAC9B,yEAAyE;IACzE,wBAAwB,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;IAC3C,oFAAoF;IACpF,gBAAgB,EAAE,CAAC,GAAG,CAAC;IACvB,+BAA+B;IAC/B,eAAe,EAAE,CAAC;CACV,CAAC;AAEX,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,4DAA4D;IAC5D,sBAAsB,EAAE,GAAG;IAC3B,yBAAyB;IACzB,aAAa,EAAE,YAAqB;CAC5B,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ export { Picker } from './picker.js';
|
|
|
10
10
|
export { MathUtils } from './math.js';
|
|
11
11
|
export { SectionPlaneRenderer } from './section-plane.js';
|
|
12
12
|
export { Section2DOverlayRenderer } from './section-2d-overlay.js';
|
|
13
|
-
export
|
|
13
|
+
export { DEFAULT_CAP_STYLE, HATCH_PATTERN_IDS } from './section-cap-style.js';
|
|
14
|
+
export type { SectionCapStyle, HatchPatternId } from './section-cap-style.js';
|
|
15
|
+
export type { Section2DOverlayOptions, Section2DOverlayCapStyle, CutPolygon2D, DrawingLine2D } from './section-2d-overlay.js';
|
|
14
16
|
export { Raycaster } from './raycaster.js';
|
|
15
17
|
export { SnapDetector, SnapType } from './snap-detector.js';
|
|
16
18
|
export { BVH } from './bvh.js';
|
|
@@ -52,6 +54,7 @@ export declare class Renderer {
|
|
|
52
54
|
private lastRenderErrorTime;
|
|
53
55
|
private readonly RENDER_ERROR_THROTTLE_MS;
|
|
54
56
|
private _renderRequested;
|
|
57
|
+
private _loggedSectionBounds;
|
|
55
58
|
private readonly uniformScratch;
|
|
56
59
|
private readonly uniformScratchU32;
|
|
57
60
|
constructor(canvas: HTMLCanvasElement);
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,10 @@ export { Picker } from './picker.js';
|
|
|
12
12
|
export { MathUtils } from './math.js';
|
|
13
13
|
export { SectionPlaneRenderer } from './section-plane.js';
|
|
14
14
|
export { Section2DOverlayRenderer } from './section-2d-overlay.js';
|
|
15
|
+
// Section cap styling (hatch pattern ids + default colours). The cap itself
|
|
16
|
+
// is now rendered by Section2DOverlayRenderer's fill pass; this module just
|
|
17
|
+
// holds the styling primitives shared with the store and UI.
|
|
18
|
+
export { DEFAULT_CAP_STYLE, HATCH_PATTERN_IDS } from './section-cap-style.js';
|
|
15
19
|
export { Raycaster } from './raycaster.js';
|
|
16
20
|
export { SnapDetector, SnapType } from './snap-detector.js';
|
|
17
21
|
export { BVH } from './bvh.js';
|
|
@@ -32,6 +36,7 @@ import { FrustumUtils } from '@ifc-lite/spatial';
|
|
|
32
36
|
import { deduplicateMeshes } from '@ifc-lite/geometry';
|
|
33
37
|
import { SectionPlaneRenderer } from './section-plane.js';
|
|
34
38
|
import { Section2DOverlayRenderer } from './section-2d-overlay.js';
|
|
39
|
+
import { DEFAULT_CAP_STYLE, HATCH_PATTERN_IDS } from './section-cap-style.js';
|
|
35
40
|
import { PickingManager } from './picking-manager.js';
|
|
36
41
|
import { RaycastEngine } from './raycast-engine.js';
|
|
37
42
|
import { PostProcessor } from './post-processor.js';
|
|
@@ -68,6 +73,9 @@ export class Renderer {
|
|
|
68
73
|
// Dirty flag: set by requestRender(), consumed by the animation loop.
|
|
69
74
|
// Centralises all render scheduling — callers never call render() directly.
|
|
70
75
|
_renderRequested = false;
|
|
76
|
+
// One-shot log guard — prints Y-up clip bounds on first section-enable so
|
|
77
|
+
// users can confirm the slider is operating on the intended range.
|
|
78
|
+
_loggedSectionBounds = false;
|
|
71
79
|
// Pooled per-frame buffers to avoid GC pressure from per-batch Float32Array allocations
|
|
72
80
|
// A single 192-byte uniform buffer (48 floats) is reused for all batches/meshes within a frame
|
|
73
81
|
uniformScratch = new Float32Array(48);
|
|
@@ -545,6 +553,30 @@ export class Renderer {
|
|
|
545
553
|
const hasHiddenFilter = options.hiddenIds && options.hiddenIds.size > 0;
|
|
546
554
|
const hasIsolatedFilter = options.isolatedIds !== null && options.isolatedIds !== undefined;
|
|
547
555
|
const hasVisibilityFiltering = hasHiddenFilter || hasIsolatedFilter;
|
|
556
|
+
// Per-frame alpha overrides for X-Ray mode. See RenderOptions.transparencyOverrides.
|
|
557
|
+
// Callers supply a fresh Map when contents change (same convention as hiddenIds/isolatedIds).
|
|
558
|
+
const txOverrides = options.transparencyOverrides;
|
|
559
|
+
const hasTxOverrides = txOverrides != null && txOverrides.size > 0;
|
|
560
|
+
const alphaForMesh = (expressId, fallback) => {
|
|
561
|
+
if (!hasTxOverrides)
|
|
562
|
+
return fallback;
|
|
563
|
+
const a = txOverrides.get(expressId);
|
|
564
|
+
return a !== undefined ? a : fallback;
|
|
565
|
+
};
|
|
566
|
+
// alphaForBatch walks batch.expressIds per frame. For typical batch sizes
|
|
567
|
+
// the cost is well below noise vs. the GPU work, and avoiding a cache
|
|
568
|
+
// removes the stale-on-mutation bug class entirely.
|
|
569
|
+
const alphaForBatch = (batch, fallback) => {
|
|
570
|
+
if (!hasTxOverrides)
|
|
571
|
+
return fallback;
|
|
572
|
+
let minAlpha = Infinity;
|
|
573
|
+
for (const eid of batch.expressIds) {
|
|
574
|
+
const a = txOverrides.get(eid);
|
|
575
|
+
if (a !== undefined && a < minAlpha)
|
|
576
|
+
minAlpha = a;
|
|
577
|
+
}
|
|
578
|
+
return minAlpha === Infinity ? fallback : minAlpha;
|
|
579
|
+
};
|
|
548
580
|
// PERFORMANCE FIX: Use batch-level visibility filtering instead of creating individual meshes
|
|
549
581
|
// Only create individual meshes for selected elements (for highlighting)
|
|
550
582
|
// Batches are filtered at render time - fully visible batches render normally,
|
|
@@ -594,7 +626,7 @@ export class Renderer {
|
|
|
594
626
|
const opaqueMeshes = [];
|
|
595
627
|
const transparentMeshes = [];
|
|
596
628
|
for (const mesh of meshes) {
|
|
597
|
-
const alpha = mesh.color[3];
|
|
629
|
+
const alpha = alphaForMesh(mesh.expressId, mesh.color[3]);
|
|
598
630
|
const transparency = mesh.material?.transparency ?? 0.0;
|
|
599
631
|
const isTransparent = alpha < 0.99 || transparency > 0.01;
|
|
600
632
|
if (isTransparent) {
|
|
@@ -629,21 +661,16 @@ export class Renderer {
|
|
|
629
661
|
};
|
|
630
662
|
}
|
|
631
663
|
if (options.sectionPlane) {
|
|
632
|
-
// Get model bounds from
|
|
664
|
+
// Get model bounds from batched meshes. We deliberately EXCLUDE
|
|
665
|
+
// individual meshes (`this.scene.getMeshes()`) here: those are
|
|
666
|
+
// created lazily for selection highlighting and can live at
|
|
667
|
+
// unexpected world positions (e.g. legacy transforms, overlay
|
|
668
|
+
// helpers), which would inflate the bounds range and make
|
|
669
|
+
// "1% of the slider" span the entire real model — producing
|
|
670
|
+
// the reported symptom where the model pops from fully visible
|
|
671
|
+
// to fully invisible across a tiny slider range.
|
|
633
672
|
const boundsMin = { x: Infinity, y: Infinity, z: Infinity };
|
|
634
673
|
const boundsMax = { x: -Infinity, y: -Infinity, z: -Infinity };
|
|
635
|
-
// Check individual meshes
|
|
636
|
-
for (const mesh of meshes) {
|
|
637
|
-
if (mesh.bounds) {
|
|
638
|
-
boundsMin.x = Math.min(boundsMin.x, mesh.bounds.min[0]);
|
|
639
|
-
boundsMin.y = Math.min(boundsMin.y, mesh.bounds.min[1]);
|
|
640
|
-
boundsMin.z = Math.min(boundsMin.z, mesh.bounds.min[2]);
|
|
641
|
-
boundsMax.x = Math.max(boundsMax.x, mesh.bounds.max[0]);
|
|
642
|
-
boundsMax.y = Math.max(boundsMax.y, mesh.bounds.max[1]);
|
|
643
|
-
boundsMax.z = Math.max(boundsMax.z, mesh.bounds.max[2]);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Check batched meshes (most geometry is here!)
|
|
647
674
|
const batchedMeshes = this.scene.getBatchedMeshes();
|
|
648
675
|
for (const batch of batchedMeshes) {
|
|
649
676
|
if (batch.bounds) {
|
|
@@ -655,6 +682,21 @@ export class Renderer {
|
|
|
655
682
|
boundsMax.z = Math.max(boundsMax.z, batch.bounds.max[2]);
|
|
656
683
|
}
|
|
657
684
|
}
|
|
685
|
+
// If no batched meshes have bounds yet (streaming, degenerate
|
|
686
|
+
// models), fall back to individual meshes so at least the
|
|
687
|
+
// slider has a workable range.
|
|
688
|
+
if (!Number.isFinite(boundsMin.x)) {
|
|
689
|
+
for (const mesh of meshes) {
|
|
690
|
+
if (mesh.bounds) {
|
|
691
|
+
boundsMin.x = Math.min(boundsMin.x, mesh.bounds.min[0]);
|
|
692
|
+
boundsMin.y = Math.min(boundsMin.y, mesh.bounds.min[1]);
|
|
693
|
+
boundsMin.z = Math.min(boundsMin.z, mesh.bounds.min[2]);
|
|
694
|
+
boundsMax.x = Math.max(boundsMax.x, mesh.bounds.max[0]);
|
|
695
|
+
boundsMax.y = Math.max(boundsMax.y, mesh.bounds.max[1]);
|
|
696
|
+
boundsMax.z = Math.max(boundsMax.z, mesh.bounds.max[2]);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
658
700
|
// Fallback if no bounds found
|
|
659
701
|
if (!Number.isFinite(boundsMin.x)) {
|
|
660
702
|
boundsMin.x = boundsMin.y = boundsMin.z = -100;
|
|
@@ -703,15 +745,57 @@ export class Renderer {
|
|
|
703
745
|
normal[2] /= len;
|
|
704
746
|
}
|
|
705
747
|
}
|
|
706
|
-
// Get axis-specific range
|
|
707
|
-
//
|
|
748
|
+
// Get axis-specific range. The renderer's own `boundsMin/Max`
|
|
749
|
+
// are computed from the GPU vertex buffers this frame, so
|
|
750
|
+
// they are guaranteed to be in the same Y-up world space as
|
|
751
|
+
// `input.worldPos` in the shader. `options.sectionPlane.min/max`
|
|
752
|
+
// comes from the UI via `coordinateInfo.shiftedBounds` and can
|
|
753
|
+
// be stale during streaming or outright wrong during model
|
|
754
|
+
// load (initialised to {0,0,0} before the first bounds update)
|
|
755
|
+
// — using those directly was the cause of the "slider moves
|
|
756
|
+
// 1% and the whole model disappears" bug.
|
|
757
|
+
//
|
|
758
|
+
// Policy: always use the renderer's own bounds for the Y-up
|
|
759
|
+
// range. Only honour the UI override when it is a valid,
|
|
760
|
+
// non-degenerate range that lies INSIDE the actual mesh
|
|
761
|
+
// bounds (e.g. storey filtering from the level picker).
|
|
708
762
|
const axisIdx = options.sectionPlane.axis === 'side' ? 'x' : options.sectionPlane.axis === 'down' ? 'y' : 'z';
|
|
709
|
-
|
|
710
|
-
|
|
763
|
+
let minVal = boundsMin[axisIdx];
|
|
764
|
+
let maxVal = boundsMax[axisIdx];
|
|
765
|
+
const uiMin = options.sectionPlane.min;
|
|
766
|
+
const uiMax = options.sectionPlane.max;
|
|
767
|
+
if (Number.isFinite(uiMin) &&
|
|
768
|
+
Number.isFinite(uiMax) &&
|
|
769
|
+
uiMax - uiMin > 1e-6 &&
|
|
770
|
+
uiMin >= minVal - 1e-3 &&
|
|
771
|
+
uiMax <= maxVal + 1e-3) {
|
|
772
|
+
minVal = uiMin;
|
|
773
|
+
maxVal = uiMax;
|
|
774
|
+
}
|
|
711
775
|
// Calculate plane distance from position percentage
|
|
712
776
|
const range = maxVal - minVal;
|
|
713
777
|
const distance = minVal + (options.sectionPlane.position / 100) * range;
|
|
714
778
|
sectionPlaneData = { normal, distance, enabled: true };
|
|
779
|
+
// One-shot diagnostic: when section first becomes active,
|
|
780
|
+
// log the exact bounds + distance the shader will use.
|
|
781
|
+
// This is the fastest way to confirm "bounds mismatch" bugs
|
|
782
|
+
// without asking the user to run a debugger.
|
|
783
|
+
if (!this._loggedSectionBounds) {
|
|
784
|
+
this._loggedSectionBounds = true;
|
|
785
|
+
console.info('[Section] Y-up bounds used for clip:', {
|
|
786
|
+
axis: options.sectionPlane.axis,
|
|
787
|
+
axisIdx,
|
|
788
|
+
bounds: {
|
|
789
|
+
min: { x: boundsMin.x, y: boundsMin.y, z: boundsMin.z },
|
|
790
|
+
max: { x: boundsMax.x, y: boundsMax.y, z: boundsMax.z },
|
|
791
|
+
},
|
|
792
|
+
uiOverride: { min: uiMin, max: uiMax },
|
|
793
|
+
used: { min: minVal, max: maxVal },
|
|
794
|
+
position: options.sectionPlane.position,
|
|
795
|
+
distance,
|
|
796
|
+
batchedMeshCount: this.scene.getBatchedMeshes().length,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
715
799
|
}
|
|
716
800
|
}
|
|
717
801
|
// Reuse pooled scratch buffer for per-mesh uniform writes
|
|
@@ -730,7 +814,8 @@ export class Renderer {
|
|
|
730
814
|
meshBuf[32] = mesh.color[0];
|
|
731
815
|
meshBuf[33] = mesh.color[1];
|
|
732
816
|
meshBuf[34] = mesh.color[2];
|
|
733
|
-
|
|
817
|
+
// Selected meshes always keep their own alpha so highlights stay opaque
|
|
818
|
+
meshBuf[35] = isSelected ? mesh.color[3] : alphaForMesh(mesh.expressId, mesh.color[3]);
|
|
734
819
|
meshBuf[36] = mesh.material?.metallic ?? 0.0;
|
|
735
820
|
meshBuf[37] = mesh.material?.roughness ?? 0.6;
|
|
736
821
|
meshBuf[38] = 0;
|
|
@@ -749,8 +834,11 @@ export class Renderer {
|
|
|
749
834
|
meshBuf[43] = 0;
|
|
750
835
|
}
|
|
751
836
|
// Flags (offset 44-47 as u32)
|
|
837
|
+
// flags.y packs: bit 0 = sectionEnabled, bit 1 = flipped
|
|
752
838
|
meshFlags[0] = isSelected ? 1 : 0;
|
|
753
|
-
meshFlags[1] =
|
|
839
|
+
meshFlags[1] =
|
|
840
|
+
(sectionPlaneData?.enabled ? 1 : 0) |
|
|
841
|
+
(options.sectionPlane?.flipped ? 2 : 0);
|
|
754
842
|
meshFlags[2] = edgeEnabledU32;
|
|
755
843
|
meshFlags[3] = edgeIntensityMilliU32;
|
|
756
844
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, meshBuf);
|
|
@@ -784,6 +872,11 @@ export class Renderer {
|
|
|
784
872
|
depthClearValue: 0.0, // Reverse-Z: clear to 0.0 (far plane)
|
|
785
873
|
depthLoadOp: 'clear',
|
|
786
874
|
depthStoreOp: 'store',
|
|
875
|
+
// Stencil is cleared here and preserved for the cap pass
|
|
876
|
+
// that runs right after in the same frame.
|
|
877
|
+
stencilClearValue: 0,
|
|
878
|
+
stencilLoadOp: 'clear',
|
|
879
|
+
stencilStoreOp: 'store',
|
|
787
880
|
},
|
|
788
881
|
});
|
|
789
882
|
pass.setPipeline(this.pipeline.getPipeline());
|
|
@@ -860,7 +953,7 @@ export class Renderer {
|
|
|
860
953
|
continue; // Don't add batch to render list
|
|
861
954
|
}
|
|
862
955
|
}
|
|
863
|
-
const alpha = batch.color[3];
|
|
956
|
+
const alpha = alphaForBatch(batch, batch.color[3]);
|
|
864
957
|
if (alpha < 0.99) {
|
|
865
958
|
transparentBatches.push(batch);
|
|
866
959
|
}
|
|
@@ -918,8 +1011,16 @@ export class Renderer {
|
|
|
918
1011
|
tpl[42] = 0;
|
|
919
1012
|
tpl[43] = 0;
|
|
920
1013
|
}
|
|
921
|
-
|
|
922
|
-
|
|
1014
|
+
// flags layout (main shader):
|
|
1015
|
+
// x = isSelected (0/1)
|
|
1016
|
+
// y = sectionEnabled bitfield:
|
|
1017
|
+
// bit 0 = enabled, bit 1 = flipped
|
|
1018
|
+
// z = edgeEnabled (0/1)
|
|
1019
|
+
// w = edgeIntensityMilli
|
|
1020
|
+
tplFlags[0] = 0;
|
|
1021
|
+
tplFlags[1] =
|
|
1022
|
+
(sectionPlaneData?.enabled ? 1 : 0) |
|
|
1023
|
+
(options.sectionPlane?.flipped ? 2 : 0);
|
|
923
1024
|
tplFlags[2] = edgeEnabledU32;
|
|
924
1025
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
925
1026
|
// Helper function to render a batch — patches color into the shared template
|
|
@@ -930,7 +1031,7 @@ export class Renderer {
|
|
|
930
1031
|
tpl[32] = batch.color[0];
|
|
931
1032
|
tpl[33] = batch.color[1];
|
|
932
1033
|
tpl[34] = batch.color[2];
|
|
933
|
-
tpl[35] = batch.color[3];
|
|
1034
|
+
tpl[35] = alphaForBatch(batch, batch.color[3]);
|
|
934
1035
|
device.queue.writeBuffer(batch.uniformBuffer, 0, tpl);
|
|
935
1036
|
// Single draw call for entire batch!
|
|
936
1037
|
pass.setBindGroup(0, batch.bindGroup);
|
|
@@ -945,19 +1046,25 @@ export class Renderer {
|
|
|
945
1046
|
}
|
|
946
1047
|
// PERFORMANCE FIX: Render partially visible batches as sub-batches (not individual meshes!)
|
|
947
1048
|
// This is the key optimization: instead of 10,000+ individual draw calls,
|
|
948
|
-
// we create cached sub-batches with only visible elements and render them as single draw calls
|
|
1049
|
+
// we create cached sub-batches with only visible elements and render them as single draw calls.
|
|
1050
|
+
// We also collect resolved opaque sub-batches so the section cap pass below can
|
|
1051
|
+
// include them in its parity count — otherwise hidden/isolated opaque geometry
|
|
1052
|
+
// would show open, un-capped cut holes.
|
|
1053
|
+
const opaqueSubBatches = [];
|
|
949
1054
|
if (partiallyVisibleBatches.length > 0) {
|
|
950
1055
|
for (const { sourceBatchKey, colorKey, visibleIds, color } of partiallyVisibleBatches) {
|
|
951
1056
|
// Get or create a cached sub-batch for this visibility state
|
|
952
1057
|
const subBatch = this.scene.getOrCreatePartialBatch(sourceBatchKey, colorKey, visibleIds, device, this.pipeline);
|
|
953
1058
|
if (subBatch) {
|
|
954
|
-
// Use opaque or transparent pipeline based on alpha
|
|
955
|
-
|
|
1059
|
+
// Use opaque or transparent pipeline based on resolved alpha
|
|
1060
|
+
// (not the parent batch's color[3] — that ignores transparencyOverrides)
|
|
1061
|
+
const isTransparent = alphaForBatch(subBatch, color[3]) < 0.99;
|
|
956
1062
|
if (isTransparent) {
|
|
957
1063
|
pass.setPipeline(this.pipeline.getTransparentPipeline());
|
|
958
1064
|
}
|
|
959
1065
|
else {
|
|
960
1066
|
pass.setPipeline(this.pipeline.getPipeline());
|
|
1067
|
+
opaqueSubBatches.push(subBatch);
|
|
961
1068
|
}
|
|
962
1069
|
// Render the sub-batch as a single draw call
|
|
963
1070
|
renderBatch(subBatch);
|
|
@@ -970,14 +1077,31 @@ export class Renderer {
|
|
|
970
1077
|
// Placed AFTER partial batches so depth buffer is complete for both full
|
|
971
1078
|
// and partial batches. Uses 'equal' depth compare — only paints where
|
|
972
1079
|
// original geometry wrote depth, so hidden entities never leak through.
|
|
1080
|
+
//
|
|
1081
|
+
// flags.x bit 1 = overlay: tells the shader to preserve baseColor.a
|
|
1082
|
+
// (the overlay pipeline now has src-alpha blending so low-alpha ghost
|
|
1083
|
+
// tints composite correctly against the opaque pass) AND skip the
|
|
1084
|
+
// glass-fresnel branch (which is meant for real glass materials and
|
|
1085
|
+
// would whiten low-alpha colour overrides at grazing angles).
|
|
973
1086
|
const overrideBatches = this.scene.getOverrideBatches();
|
|
974
1087
|
if (overrideBatches.length > 0) {
|
|
975
1088
|
pass.setPipeline(this.pipeline.getOverlayPipeline());
|
|
1089
|
+
tplFlags[0] = 2; // set overlay bit for the duration of these draws
|
|
976
1090
|
for (const batch of overrideBatches) {
|
|
977
1091
|
renderBatch(batch);
|
|
978
1092
|
}
|
|
1093
|
+
tplFlags[0] = 0; // restore for any downstream use of the template
|
|
979
1094
|
pass.setPipeline(this.pipeline.getPipeline());
|
|
980
1095
|
}
|
|
1096
|
+
// Filled, hatched 3D cut surfaces are now rendered by
|
|
1097
|
+
// Section2DOverlayRenderer using the exact polygons from
|
|
1098
|
+
// SectionCutter (triangle-plane intersection). The old
|
|
1099
|
+
// stencil-parity SectionCapRenderer is no longer in the
|
|
1100
|
+
// render loop — parity XOR on non-manifold IFC geometry
|
|
1101
|
+
// leaks stencil bits into empty sky, and no amount of
|
|
1102
|
+
// bounded quads or second-bit gating fixed that robustly.
|
|
1103
|
+
// See the 2D-overlay draw call further below in this same
|
|
1104
|
+
// render pass, which now emits the cap.
|
|
981
1105
|
// Prepare selected meshes once, then render them LAST so transparent batches
|
|
982
1106
|
// don't overwrite highlight color (glass otherwise appears unhighlighted).
|
|
983
1107
|
const visibleSelectedIds = new Set();
|
|
@@ -1042,7 +1166,7 @@ export class Renderer {
|
|
|
1042
1166
|
tpl[32] = mesh.color[0];
|
|
1043
1167
|
tpl[33] = mesh.color[1];
|
|
1044
1168
|
tpl[34] = mesh.color[2];
|
|
1045
|
-
tpl[35] = mesh.color[3];
|
|
1169
|
+
tpl[35] = alphaForMesh(mesh.expressId, mesh.color[3]);
|
|
1046
1170
|
tpl[36] = mesh.material?.metallic ?? 0.0;
|
|
1047
1171
|
tpl[37] = mesh.material?.roughness ?? 0.6;
|
|
1048
1172
|
tpl[38] = 0;
|
|
@@ -1060,7 +1184,9 @@ export class Renderer {
|
|
|
1060
1184
|
tpl[43] = 0;
|
|
1061
1185
|
}
|
|
1062
1186
|
tplFlags[0] = 0;
|
|
1063
|
-
tplFlags[1] =
|
|
1187
|
+
tplFlags[1] =
|
|
1188
|
+
(sectionPlaneData?.enabled ? 1 : 0) |
|
|
1189
|
+
(options.sectionPlane?.flipped ? 2 : 0);
|
|
1064
1190
|
tplFlags[2] = edgeEnabledU32;
|
|
1065
1191
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
1066
1192
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
@@ -1116,7 +1242,9 @@ export class Renderer {
|
|
|
1116
1242
|
tpl[43] = 0;
|
|
1117
1243
|
}
|
|
1118
1244
|
tplFlags[0] = 1; // isSelected
|
|
1119
|
-
tplFlags[1] =
|
|
1245
|
+
tplFlags[1] =
|
|
1246
|
+
(sectionPlaneData?.enabled ? 1 : 0) |
|
|
1247
|
+
(options.sectionPlane?.flipped ? 2 : 0);
|
|
1120
1248
|
tplFlags[2] = edgeEnabledU32;
|
|
1121
1249
|
tplFlags[3] = edgeIntensityMilliU32;
|
|
1122
1250
|
device.queue.writeBuffer(mesh.uniformBuffer, 0, tpl);
|
|
@@ -1162,7 +1290,9 @@ export class Renderer {
|
|
|
1162
1290
|
const instancedMeshes = this.scene.getInstancedMeshes();
|
|
1163
1291
|
if (instancedMeshes.length > 0) {
|
|
1164
1292
|
// Update instanced pipeline uniforms
|
|
1165
|
-
this.instancedPipeline.updateUniforms(viewProj, sectionPlaneData
|
|
1293
|
+
this.instancedPipeline.updateUniforms(viewProj, sectionPlaneData
|
|
1294
|
+
? { ...sectionPlaneData, flipped: options.sectionPlane?.flipped === true }
|
|
1295
|
+
: undefined);
|
|
1166
1296
|
// Switch to instanced pipeline
|
|
1167
1297
|
pass.setPipeline(this.instancedPipeline.getPipeline());
|
|
1168
1298
|
for (const instancedMesh of instancedMeshes) {
|
|
@@ -1191,15 +1321,38 @@ export class Renderer {
|
|
|
1191
1321
|
min: options.sectionPlane.min,
|
|
1192
1322
|
max: options.sectionPlane.max,
|
|
1193
1323
|
});
|
|
1194
|
-
// Draw 2D section overlay on the section plane (when section is
|
|
1324
|
+
// Draw 2D section overlay on the section plane (when section is
|
|
1325
|
+
// active, not preview). The overlay is also the 3D SECTION CAP:
|
|
1326
|
+
// its polygon fills come from `SectionCutter` (exact triangle-
|
|
1327
|
+
// plane intersection), and the new fill shader applies the
|
|
1328
|
+
// user's screen-space hatch + colour directly on those
|
|
1329
|
+
// polygons. This replaces the old stencil-parity cap, which
|
|
1330
|
+
// bled hatch into empty sky on non-manifold IFC geometry —
|
|
1331
|
+
// the polygons here are mathematically correct, so the cap
|
|
1332
|
+
// silhouette matches the 2D drawing exactly.
|
|
1195
1333
|
if (options.sectionPlane.enabled && this.section2DOverlayRenderer?.hasGeometry()) {
|
|
1334
|
+
const o = options.sectionPlane;
|
|
1335
|
+
const showFills = o.showCap !== false;
|
|
1336
|
+
const showOutlines = o.showOutlines !== false;
|
|
1337
|
+
const style = { ...DEFAULT_CAP_STYLE, ...(o.capStyle ?? {}) };
|
|
1196
1338
|
this.section2DOverlayRenderer.draw(pass, {
|
|
1197
|
-
axis:
|
|
1198
|
-
position:
|
|
1339
|
+
axis: o.axis,
|
|
1340
|
+
position: o.position,
|
|
1199
1341
|
bounds: modelBounds,
|
|
1200
1342
|
viewProj,
|
|
1201
|
-
min:
|
|
1202
|
-
max:
|
|
1343
|
+
min: o.min,
|
|
1344
|
+
max: o.max,
|
|
1345
|
+
showFills,
|
|
1346
|
+
showOutlines,
|
|
1347
|
+
capStyle: showFills ? {
|
|
1348
|
+
fillColor: style.fillColor,
|
|
1349
|
+
strokeColor: style.strokeColor,
|
|
1350
|
+
patternId: HATCH_PATTERN_IDS[style.pattern],
|
|
1351
|
+
spacingPx: style.spacingPx,
|
|
1352
|
+
angleRad: style.angleRad,
|
|
1353
|
+
widthPx: style.widthPx,
|
|
1354
|
+
secondaryAngleRad: style.secondaryAngleRad,
|
|
1355
|
+
} : undefined,
|
|
1203
1356
|
});
|
|
1204
1357
|
}
|
|
1205
1358
|
}
|
|
@@ -1214,7 +1367,9 @@ export class Renderer {
|
|
|
1214
1367
|
});
|
|
1215
1368
|
this.postProcessor.apply(encoder, {
|
|
1216
1369
|
targetView: textureView,
|
|
1217
|
-
|
|
1370
|
+
// Depth-only view required because depth24plus-stencil8
|
|
1371
|
+
// cannot be sampled as texture_depth_* with aspect 'all'.
|
|
1372
|
+
depthView: this.pipeline.getDepthOnlyTextureView(),
|
|
1218
1373
|
objectIdView: this.pipeline.getObjectIdTextureView(),
|
|
1219
1374
|
contactQuality: contactEnabled && visualEnhancement.contactShading.quality === 'high' ? 'high' : 'low',
|
|
1220
1375
|
radius: Math.min(3.0, Math.max(1.0, visualEnhancement.contactShading.radius)),
|
|
@@ -1433,6 +1588,8 @@ export class Renderer {
|
|
|
1433
1588
|
destroy() {
|
|
1434
1589
|
// Scene mesh GPU buffers
|
|
1435
1590
|
this.scene.clear();
|
|
1591
|
+
// Re-arm the section-bounds diagnostic log for the next model.
|
|
1592
|
+
this._loggedSectionBounds = false;
|
|
1436
1593
|
// Render pipelines (textures + uniform buffers)
|
|
1437
1594
|
this.pipeline?.destroy();
|
|
1438
1595
|
this.pipeline = null;
|