@ifc-lite/renderer 1.38.1 → 1.39.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/dist/pointcloud/point-cloud-renderer.d.ts +15 -6
- package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.js +7 -4
- package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
- package/dist/pointcloud/point-cloud-uniforms.d.ts +14 -2
- package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-uniforms.js +35 -2
- package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
- package/dist/pointcloud/point-pipeline.d.ts +3 -2
- package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
- package/dist/pointcloud/point-pipeline.js +8 -5
- package/dist/pointcloud/point-pipeline.js.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.js +20 -17
- package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
- package/dist/scene.d.ts +18 -0
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +86 -17
- package/dist/scene.js.map +1 -1
- package/package.json +2 -2
package/dist/scene.js
CHANGED
|
@@ -30,6 +30,10 @@ export class Scene {
|
|
|
30
30
|
meshDataMap = new Map(); // Map expressId -> MeshData[] (for lazy buffer creation, accumulates multiple pieces)
|
|
31
31
|
boundingBoxes = new Map(); // Map expressId -> bounding box (computed lazily)
|
|
32
32
|
texturedMeshes = []; // #961: IFC surface-textured meshes (own buffers/texture/bindGroup)
|
|
33
|
+
/** #1781: GPU textures shared across meshes, keyed by `MeshTextureRef.textureId`
|
|
34
|
+
* (one `IfcImageTexture` → one upload, sampled by every face set mapping it).
|
|
35
|
+
* Refcounted: entries die when the last referencing mesh is removed / on clear(). */
|
|
36
|
+
sharedTextures = new Map();
|
|
33
37
|
texturedDevice; // #961: cached for textured-mesh re-upload on translate
|
|
34
38
|
instancedTemplates = []; // GPU-instancing: unique templates + per-occurrence buffers (fed by addInstancedShard)
|
|
35
39
|
instancedVisible = true; // GPU-instancing: hidden in Types view mode (instanced geometry is class-0 occurrences)
|
|
@@ -964,10 +968,10 @@ export class Scene {
|
|
|
964
968
|
// otherwise a flat-colour copy would be drawn over the texture. Still
|
|
965
969
|
// register them in meshDataMap (addMeshData) so CPU picking/bbox/frame work.
|
|
966
970
|
let renderable = meshDataArray;
|
|
967
|
-
if (meshDataArray.some((m) =>
|
|
971
|
+
if (meshDataArray.some((m) => Scene.hasRenderableTexture(m))) {
|
|
968
972
|
renderable = [];
|
|
969
973
|
for (const meshData of meshDataArray) {
|
|
970
|
-
if (
|
|
974
|
+
if (Scene.hasRenderableTexture(meshData)) {
|
|
971
975
|
this.createTexturedMesh(meshData, device, pipeline);
|
|
972
976
|
this.addMeshData(meshData);
|
|
973
977
|
}
|
|
@@ -1135,7 +1139,7 @@ export class Scene {
|
|
|
1135
1139
|
tm.vertexBuffer.destroy();
|
|
1136
1140
|
tm.indexBuffer.destroy();
|
|
1137
1141
|
tm.uniformBuffer.destroy();
|
|
1138
|
-
|
|
1142
|
+
this.releaseTexturedMeshTexture(tm);
|
|
1139
1143
|
this.texturedMeshes.splice(i, 1);
|
|
1140
1144
|
removedDedicated = true;
|
|
1141
1145
|
}
|
|
@@ -1321,7 +1325,7 @@ export class Scene {
|
|
|
1321
1325
|
// re-interleave + re-upload the moved textured parts (paired by expressId,
|
|
1322
1326
|
// in creation order). Without this a moved textured entity renders stale.
|
|
1323
1327
|
if (this.texturedDevice && this.texturedMeshes.length > 0) {
|
|
1324
|
-
const texturedData = meshDataList.filter((md) =>
|
|
1328
|
+
const texturedData = meshDataList.filter((md) => Scene.hasRenderableTexture(md));
|
|
1325
1329
|
if (texturedData.length > 0) {
|
|
1326
1330
|
const entries = this.texturedMeshes.filter((tm) => tm.expressId === expressId);
|
|
1327
1331
|
for (let i = 0; i < entries.length && i < texturedData.length; i++) {
|
|
@@ -1519,7 +1523,7 @@ export class Scene {
|
|
|
1519
1523
|
// #961: textured meshes render from their own GPU vertex buffer — re-upload
|
|
1520
1524
|
// the rotated parts so they don't render stale (mirrors the translate path).
|
|
1521
1525
|
if (this.texturedDevice && this.texturedMeshes.length > 0) {
|
|
1522
|
-
const texturedData = meshDataList.filter((md) =>
|
|
1526
|
+
const texturedData = meshDataList.filter((md) => Scene.hasRenderableTexture(md));
|
|
1523
1527
|
if (texturedData.length > 0) {
|
|
1524
1528
|
const entries = this.texturedMeshes.filter((tm) => tm.expressId === expressId);
|
|
1525
1529
|
for (let i = 0; i < entries.length && i < texturedData.length; i++) {
|
|
@@ -3098,6 +3102,15 @@ export class Scene {
|
|
|
3098
3102
|
* The per-frame uniform (viewProj/section/flags + colour tint) is written by
|
|
3099
3103
|
* the renderer each frame, mirroring how colour batches are driven.
|
|
3100
3104
|
*/
|
|
3105
|
+
/** True when the mesh can render through the textured pipeline: UVs plus
|
|
3106
|
+
* either a Rust-decoded image (#961) or a viewer-resolved ImageBitmap for
|
|
3107
|
+
* an external `IfcImageTexture` reference (#1781). A `textureRef` whose
|
|
3108
|
+
* image was NOT resolved (missing zip sibling) renders as ordinary
|
|
3109
|
+
* flat-colour geometry instead. */
|
|
3110
|
+
static hasRenderableTexture(meshData) {
|
|
3111
|
+
return Boolean(meshData.uvs) &&
|
|
3112
|
+
Boolean(meshData.texture || (meshData.textureRef && meshData.textureBitmap));
|
|
3113
|
+
}
|
|
3101
3114
|
/**
|
|
3102
3115
|
* Interleave a textured mesh's vertices into the stride-36 layout
|
|
3103
3116
|
* `[px,py,pz, nx,ny,nz, entityId(u32), u,v]`. Shared by initial upload and
|
|
@@ -3106,7 +3119,7 @@ export class Scene {
|
|
|
3106
3119
|
*/
|
|
3107
3120
|
interleaveTexturedVertices(meshData) {
|
|
3108
3121
|
const uvs = meshData.uvs;
|
|
3109
|
-
if (!meshData
|
|
3122
|
+
if (!Scene.hasRenderableTexture(meshData) || !uvs)
|
|
3110
3123
|
return null;
|
|
3111
3124
|
const positions = meshData.positions;
|
|
3112
3125
|
const normals = meshData.normals;
|
|
@@ -3137,8 +3150,10 @@ export class Scene {
|
|
|
3137
3150
|
}
|
|
3138
3151
|
createTexturedMesh(meshData, device, pipeline) {
|
|
3139
3152
|
const tex = meshData.texture;
|
|
3153
|
+
const ref = meshData.textureRef;
|
|
3154
|
+
const bitmap = meshData.textureBitmap;
|
|
3140
3155
|
const interleaved = this.interleaveTexturedVertices(meshData);
|
|
3141
|
-
if (!tex ||
|
|
3156
|
+
if (!interleaved || !(tex || (ref && bitmap)))
|
|
3142
3157
|
return;
|
|
3143
3158
|
this.texturedDevice = device; // reused by translateMeshesForEntity re-upload
|
|
3144
3159
|
const vertexBuffer = device.createBuffer({
|
|
@@ -3151,17 +3166,48 @@ export class Scene {
|
|
|
3151
3166
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
3152
3167
|
});
|
|
3153
3168
|
device.queue.writeBuffer(indexBuffer, 0, meshData.indices);
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3169
|
+
let texture;
|
|
3170
|
+
let sharedTextureKey;
|
|
3171
|
+
if (tex) {
|
|
3172
|
+
// #961: upload the Rust-decoded RGBA8 verbatim — no image decoding in JS.
|
|
3173
|
+
texture = device.createTexture({
|
|
3174
|
+
size: { width: tex.width, height: tex.height },
|
|
3175
|
+
format: 'rgba8unorm',
|
|
3176
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
3177
|
+
});
|
|
3178
|
+
device.queue.writeTexture({ texture }, tex.rgba, { bytesPerRow: tex.width * 4, rowsPerImage: tex.height }, { width: tex.width, height: tex.height });
|
|
3179
|
+
}
|
|
3180
|
+
else {
|
|
3181
|
+
// #1781: external image texture — the viewer decoded the `.ifcZIP`
|
|
3182
|
+
// sibling to an ImageBitmap once per textureId; upload it ONCE and share
|
|
3183
|
+
// the GPU texture across every mesh sampling it (real files map one
|
|
3184
|
+
// 4096² image from dozens of face sets — per-mesh copies would be GBs).
|
|
3185
|
+
const refKey = ref.textureId;
|
|
3186
|
+
const bmp = bitmap;
|
|
3187
|
+
let entry = this.sharedTextures.get(refKey);
|
|
3188
|
+
if (!entry) {
|
|
3189
|
+
const gpuTex = device.createTexture({
|
|
3190
|
+
size: { width: bmp.width, height: bmp.height },
|
|
3191
|
+
format: 'rgba8unorm',
|
|
3192
|
+
// RENDER_ATTACHMENT is required by copyExternalImageToTexture.
|
|
3193
|
+
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
3194
|
+
GPUTextureUsage.COPY_DST |
|
|
3195
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
3196
|
+
});
|
|
3197
|
+
device.queue.copyExternalImageToTexture({ source: bmp }, { texture: gpuTex }, { width: bmp.width, height: bmp.height });
|
|
3198
|
+
entry = { texture: gpuTex, refs: 0 };
|
|
3199
|
+
this.sharedTextures.set(refKey, entry);
|
|
3200
|
+
}
|
|
3201
|
+
entry.refs++;
|
|
3202
|
+
texture = entry.texture;
|
|
3203
|
+
sharedTextureKey = refKey;
|
|
3204
|
+
}
|
|
3205
|
+
const repeatS = tex ? tex.repeatS : ref.repeatS;
|
|
3206
|
+
const repeatT = tex ? tex.repeatT : ref.repeatT;
|
|
3161
3207
|
const wrap = (repeat) => (repeat ? 'repeat' : 'clamp-to-edge');
|
|
3162
3208
|
const sampler = device.createSampler({
|
|
3163
|
-
addressModeU: wrap(
|
|
3164
|
-
addressModeV: wrap(
|
|
3209
|
+
addressModeU: wrap(repeatS),
|
|
3210
|
+
addressModeV: wrap(repeatT),
|
|
3165
3211
|
magFilter: 'linear',
|
|
3166
3212
|
minFilter: 'linear',
|
|
3167
3213
|
mipmapFilter: 'linear',
|
|
@@ -3181,8 +3227,26 @@ export class Scene {
|
|
|
3181
3227
|
sampler,
|
|
3182
3228
|
bindGroup,
|
|
3183
3229
|
color: meshData.color,
|
|
3230
|
+
...(sharedTextureKey !== undefined ? { sharedTextureKey } : {}),
|
|
3184
3231
|
});
|
|
3185
3232
|
}
|
|
3233
|
+
/** Release a textured mesh's GPU texture: shared (#1781) entries decrement
|
|
3234
|
+
* the registry refcount and die with their LAST reference; per-mesh (#961)
|
|
3235
|
+
* uploads are destroyed outright. */
|
|
3236
|
+
releaseTexturedMeshTexture(tm) {
|
|
3237
|
+
if (tm.sharedTextureKey === undefined) {
|
|
3238
|
+
tm.texture.destroy();
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
const entry = this.sharedTextures.get(tm.sharedTextureKey);
|
|
3242
|
+
if (!entry)
|
|
3243
|
+
return;
|
|
3244
|
+
entry.refs--;
|
|
3245
|
+
if (entry.refs <= 0) {
|
|
3246
|
+
entry.texture.destroy();
|
|
3247
|
+
this.sharedTextures.delete(tm.sharedTextureKey);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3186
3250
|
clear() {
|
|
3187
3251
|
for (const mesh of this.meshes)
|
|
3188
3252
|
destroyGpuResources(mesh);
|
|
@@ -3192,9 +3256,14 @@ export class Scene {
|
|
|
3192
3256
|
tm.vertexBuffer.destroy();
|
|
3193
3257
|
tm.indexBuffer.destroy();
|
|
3194
3258
|
tm.uniformBuffer.destroy();
|
|
3195
|
-
|
|
3259
|
+
this.releaseTexturedMeshTexture(tm);
|
|
3196
3260
|
}
|
|
3197
3261
|
this.texturedMeshes = [];
|
|
3262
|
+
// Belt-and-braces: refcounting above should have emptied the registry;
|
|
3263
|
+
// destroy any straggler so clear() can never leak a shared GPU texture.
|
|
3264
|
+
for (const entry of this.sharedTextures.values())
|
|
3265
|
+
entry.texture.destroy();
|
|
3266
|
+
this.sharedTextures.clear();
|
|
3198
3267
|
// GPU-instancing templates own their vertex/index/instance buffers.
|
|
3199
3268
|
for (const it of this.instancedTemplates) {
|
|
3200
3269
|
it.vertexBuffer.destroy();
|