@inweb/viewer-three 26.10.6 → 26.11.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.
- package/dist/plugins/components/AxesHelperComponent.js +23 -1
- package/dist/plugins/components/AxesHelperComponent.js.map +1 -1
- package/dist/plugins/components/AxesHelperComponent.min.js +1 -1
- package/dist/plugins/components/AxesHelperComponent.module.js +24 -2
- package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.js +18 -0
- package/dist/plugins/components/ExtentsHelperComponent.js.map +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.min.js +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.module.js +19 -1
- package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -1
- package/dist/plugins/loaders/GLTFCloudLoader.js +2 -3
- package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -1
- package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -1
- package/dist/plugins/loaders/GLTFCloudLoader.module.js +2 -3
- package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +1 -1
- package/dist/plugins/loaders/GLTFFileLoader.js +2499 -0
- package/dist/plugins/loaders/GLTFFileLoader.js.map +1 -0
- package/dist/plugins/loaders/GLTFFileLoader.min.js +24 -0
- package/dist/plugins/loaders/GLTFFileLoader.module.js +74 -0
- package/dist/plugins/loaders/GLTFFileLoader.module.js.map +1 -0
- package/dist/plugins/loaders/IFCXLoader.js +5 -7
- package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
- package/dist/plugins/loaders/IFCXLoader.min.js +1 -1
- package/dist/plugins/loaders/IFCXLoader.module.js +5 -7
- package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
- package/dist/plugins/loaders/PotreeLoader.js +1 -2
- package/dist/plugins/loaders/PotreeLoader.js.map +1 -1
- package/dist/plugins/loaders/PotreeLoader.min.js +1 -1
- package/dist/plugins/loaders/PotreeLoader.module.js +1 -2
- package/dist/plugins/loaders/PotreeLoader.module.js.map +1 -1
- package/dist/viewer-three.js +770 -2777
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +3 -3
- package/dist/viewer-three.module.js +609 -206
- package/dist/viewer-three.module.js.map +1 -1
- package/lib/Viewer/Viewer.d.ts +27 -20
- package/lib/Viewer/commands/GetSelected2.d.ts +2 -0
- package/lib/Viewer/commands/SelectModel.d.ts +1 -1
- package/lib/Viewer/commands/SetSelected2.d.ts +2 -0
- package/lib/Viewer/components/index.d.ts +6 -6
- package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +0 -1
- package/lib/Viewer/loaders/GLTFBinaryExtension.d.ts +5 -0
- package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +2 -2
- package/lib/Viewer/loaders/{GLTFFileLoader.d.ts → GLTFFileDynamicLoader.d.ts} +7 -1
- package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +4 -3
- package/lib/Viewer/loaders/RangesLoader.d.ts +15 -0
- package/lib/Viewer/loaders/index.d.ts +22 -14
- package/lib/Viewer/models/IModelImpl.d.ts +6 -8
- package/lib/Viewer/models/ModelImpl.d.ts +3 -5
- package/package.json +5 -5
- package/plugins/components/AxesHelperComponent.ts +31 -2
- package/plugins/components/ExtentsHelperComponent.ts +25 -0
- package/plugins/loaders/GLTFCloudLoader.ts +2 -3
- package/{src/Viewer → plugins}/loaders/GLTFFileLoader.ts +21 -12
- package/plugins/loaders/IFCX/IFCXCloudLoader.ts +5 -5
- package/plugins/loaders/IFCX/IFCXFileLoader.ts +3 -4
- package/plugins/loaders/Potree/PotreeFileLoader.ts +3 -4
- package/src/Viewer/Viewer.ts +120 -88
- package/src/Viewer/commands/ClearSelected.ts +3 -1
- package/src/Viewer/commands/GetModels.ts +1 -1
- package/src/Viewer/commands/GetSelected.ts +2 -2
- package/src/Viewer/commands/GetSelected2.ts +34 -0
- package/src/Viewer/commands/HideSelected.ts +3 -1
- package/src/Viewer/commands/SelectModel.ts +5 -5
- package/src/Viewer/commands/SetSelected.ts +9 -10
- package/src/Viewer/commands/SetSelected2.ts +42 -0
- package/src/Viewer/commands/ZoomToObjects.ts +5 -6
- package/src/Viewer/commands/ZoomToSelected.ts +3 -1
- package/src/Viewer/commands/index.ts +4 -0
- package/src/Viewer/components/CameraComponent.ts +6 -1
- package/src/Viewer/components/ExtentsComponent.ts +4 -1
- package/src/Viewer/components/SelectionComponent.ts +2 -0
- package/src/Viewer/components/index.ts +6 -6
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +253 -22
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +20 -10
- package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +3 -1
- package/src/Viewer/loaders/GLTFBinaryExtension.ts +91 -0
- package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +13 -19
- package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +145 -0
- package/src/Viewer/loaders/GLTFLoadingManager.ts +5 -4
- package/src/Viewer/loaders/RangesLoader.ts +95 -0
- package/src/Viewer/loaders/index.ts +24 -16
- package/src/Viewer/models/IModelImpl.ts +8 -9
- package/src/Viewer/models/ModelImpl.ts +30 -17
|
@@ -26,13 +26,12 @@ import type { Viewer } from "../Viewer";
|
|
|
26
26
|
import { zoomTo } from "./ZoomTo";
|
|
27
27
|
|
|
28
28
|
export function zoomToObjects(viewer: Viewer, handles: string[] = []): void {
|
|
29
|
-
const
|
|
30
|
-
const objects = [];
|
|
31
|
-
viewer.scene.traverseVisible((child) => {
|
|
32
|
-
if (handleSet.has(child.userData?.handle)) objects.push(child);
|
|
33
|
-
});
|
|
29
|
+
const extents = new Box3();
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
viewer.models.forEach((model) => {
|
|
32
|
+
const objects = model.getObjectsByHandles(handles);
|
|
33
|
+
objects.forEach((object) => extents.expandByObject(object));
|
|
34
|
+
});
|
|
36
35
|
if (extents.isEmpty()) extents.copy(viewer.extents);
|
|
37
36
|
|
|
38
37
|
zoomTo(viewer, extents);
|
|
@@ -26,7 +26,9 @@ import type { Viewer } from "../Viewer";
|
|
|
26
26
|
import { zoomTo } from "./ZoomTo";
|
|
27
27
|
|
|
28
28
|
export function zoomToSelected(viewer: Viewer): void {
|
|
29
|
-
const extents =
|
|
29
|
+
const extents = new Box3();
|
|
30
|
+
|
|
31
|
+
viewer.selected.forEach((object) => extents.expandByObject(object));
|
|
30
32
|
if (extents.isEmpty()) extents.copy(viewer.extents);
|
|
31
33
|
|
|
32
34
|
zoomTo(viewer, extents);
|
|
@@ -32,6 +32,7 @@ import { explode, collect } from "./Explode";
|
|
|
32
32
|
import { getDefaultViewPositions } from "./GetDefaultViewPositions";
|
|
33
33
|
import { getModels } from "./GetModels";
|
|
34
34
|
import { getSelected } from "./GetSelected";
|
|
35
|
+
import { getSelected2 } from "./GetSelected2";
|
|
35
36
|
import { hideSelected } from "./HideSelected";
|
|
36
37
|
import { isolateSelected } from "./IsolateSelected";
|
|
37
38
|
import { regenerateAll } from "./RegenerateAll";
|
|
@@ -41,6 +42,7 @@ import { setActiveDragger } from "./SetActiveDragger";
|
|
|
41
42
|
import { setDefaultViewPosition } from "./SetDefaultViewPosition";
|
|
42
43
|
import { setMarkupColor } from "./SetMarkupColor";
|
|
43
44
|
import { setSelected } from "./SetSelected";
|
|
45
|
+
import { setSelected2 } from "./SetSelected2";
|
|
44
46
|
import { showAll } from "./ShowAll";
|
|
45
47
|
import { zoomToExtents } from "./ZoomToExtents";
|
|
46
48
|
import { zoomToObjects } from "./ZoomToObjects";
|
|
@@ -86,6 +88,7 @@ commands.registerCommand("explode", explode);
|
|
|
86
88
|
commands.registerCommand("getDefaultViewPositions", getDefaultViewPositions);
|
|
87
89
|
commands.registerCommand("getModels", getModels);
|
|
88
90
|
commands.registerCommand("getSelected", getSelected);
|
|
91
|
+
commands.registerCommand("getSelected2", getSelected2);
|
|
89
92
|
commands.registerCommand("hideSelected", hideSelected);
|
|
90
93
|
commands.registerCommand("isolateSelected", isolateSelected);
|
|
91
94
|
commands.registerCommand("regenerateAll", regenerateAll);
|
|
@@ -95,6 +98,7 @@ commands.registerCommand("setActiveDragger", setActiveDragger);
|
|
|
95
98
|
commands.registerCommand("setDefaultViewPosition", setDefaultViewPosition);
|
|
96
99
|
commands.registerCommand("setMarkupColor", setMarkupColor);
|
|
97
100
|
commands.registerCommand("setSelected", setSelected);
|
|
101
|
+
commands.registerCommand("setSelected2", setSelected2);
|
|
98
102
|
commands.registerCommand("showAll", showAll);
|
|
99
103
|
commands.registerCommand("zoomToExtents", zoomToExtents);
|
|
100
104
|
commands.registerCommand("zoomToObjects", zoomToObjects);
|
|
@@ -119,9 +119,14 @@ export class CameraComponent implements IComponent {
|
|
|
119
119
|
};
|
|
120
120
|
|
|
121
121
|
geometryEnd = () => {
|
|
122
|
+
// do not change the camera after opening the second file in assembly
|
|
123
|
+
if (this.viewer.models.length > 1) {
|
|
124
|
+
this.switchCamera(this.viewer.camera);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
let camera: any;
|
|
123
129
|
|
|
124
|
-
// TODO: do not change the camera and target after opening the second model in "append" mode
|
|
125
130
|
this.viewer.scene.traverse((object: any) => {
|
|
126
131
|
if (object.isCamera)
|
|
127
132
|
if (!camera) camera = object;
|
|
@@ -53,8 +53,11 @@ export class ExtentsComponent implements IComponent {
|
|
|
53
53
|
syncExtents = () => {
|
|
54
54
|
const extents = new Box3();
|
|
55
55
|
this.viewer.models.forEach((model) => model.getExtents(extents));
|
|
56
|
-
|
|
57
56
|
this.viewer.extents.copy(extents);
|
|
57
|
+
|
|
58
|
+
// do not change target after opening the second file in assembly
|
|
59
|
+
if (this.viewer.models.length > 1) return;
|
|
60
|
+
|
|
58
61
|
this.viewer.extents.getCenter(this.viewer.target);
|
|
59
62
|
};
|
|
60
63
|
}
|
|
@@ -84,7 +84,9 @@ export class SelectionComponent implements IComponent {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
this.viewer.update();
|
|
87
|
+
|
|
87
88
|
this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
|
|
89
|
+
this.viewer.emitEvent({ type: "select2", data: undefined, handles: this.viewer.getSelected2() });
|
|
88
90
|
};
|
|
89
91
|
|
|
90
92
|
onDoubleClick = (event: MouseEvent) => {
|
|
@@ -41,8 +41,8 @@ import { ResetComponent } from "./ResetComponent";
|
|
|
41
41
|
*
|
|
42
42
|
* 1. Define a component class implements {@link IComponent}.
|
|
43
43
|
* 2. Define a constructor with a `viewer` parameter and add mouse event listeners for the specified viewer.
|
|
44
|
-
* 3. Define the component logic in the event listeners. For example, listen for the `
|
|
45
|
-
*
|
|
44
|
+
* 3. Define the component logic in the event listeners. For example, listen for the `geometryend` event and
|
|
45
|
+
* implement post-processing logic for the model.
|
|
46
46
|
* 4. Override {@link IComponent.dispose} and remove mouse event listeners from the viewer.
|
|
47
47
|
* 5. Register component provider in the components registry by calling the
|
|
48
48
|
* {@link components.registerComponent}.
|
|
@@ -57,15 +57,15 @@ import { ResetComponent } from "./ResetComponent";
|
|
|
57
57
|
*
|
|
58
58
|
* constructor(viewer: Viewer) {
|
|
59
59
|
* this.viewer = viewer;
|
|
60
|
-
* this.viewer.addEventListener("
|
|
60
|
+
* this.viewer.addEventListener("geometryend", this.onGeometryEnd);
|
|
61
61
|
* }
|
|
62
62
|
*
|
|
63
63
|
* override dispose() {
|
|
64
|
-
* this.viewer.removeEventListener("
|
|
64
|
+
* this.viewer.removeEventListener("geometryend", this.onGeometryEnd);
|
|
65
65
|
* }
|
|
66
66
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* onGeometryEnd = (event: MouseEvent) => {
|
|
68
|
+
* this.viewer.executeCommand("zoomToExtents");
|
|
69
69
|
* };
|
|
70
70
|
* }
|
|
71
71
|
*
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
LineLoop,
|
|
11
11
|
Group,
|
|
12
12
|
Vector3,
|
|
13
|
+
Vector2,
|
|
13
14
|
Quaternion,
|
|
14
15
|
Matrix4,
|
|
15
16
|
Box3,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
import { GL_CONSTANTS } from "./GltfStructure.js";
|
|
27
28
|
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
28
29
|
|
|
30
|
+
const STRUCTURE_ID_SEPARATOR = ":";
|
|
31
|
+
|
|
29
32
|
//#AI-GENERATED using Gemini 2.5 Pro, Claude-4-sonnet
|
|
30
33
|
//#Reviewed and adapted by dborysov@opendesign.com
|
|
31
34
|
|
|
@@ -42,6 +45,7 @@ export class DynamicGltfLoader {
|
|
|
42
45
|
geometryerror: [],
|
|
43
46
|
update: [],
|
|
44
47
|
geometrymemory: [],
|
|
48
|
+
optimizationprogress: [],
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
this.loadDistance = 100;
|
|
@@ -98,7 +102,6 @@ export class DynamicGltfLoader {
|
|
|
98
102
|
this.newOptimizedObjects = new Set();
|
|
99
103
|
this.oldOptimizeObjects = new Set();
|
|
100
104
|
|
|
101
|
-
this.maxConcurrentChunks = 8;
|
|
102
105
|
this.activeChunkLoads = 0;
|
|
103
106
|
this.chunkQueue = [];
|
|
104
107
|
|
|
@@ -113,6 +116,8 @@ export class DynamicGltfLoader {
|
|
|
113
116
|
// Merged geometry tracking
|
|
114
117
|
this.mergedObjectMap = new Map(); // objectId -> {mergedObject, startIndex, endIndex, vertexCount}
|
|
115
118
|
this.mergedGeometryVisibility = new Map(); // mergedObject -> visibility array
|
|
119
|
+
|
|
120
|
+
this._webglInfoCache = null; // { renderer, vendor }
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
setVisibleEdges(visible) {
|
|
@@ -244,6 +249,136 @@ export class DynamicGltfLoader {
|
|
|
244
249
|
console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
|
|
245
250
|
}
|
|
246
251
|
|
|
252
|
+
getStats() {
|
|
253
|
+
let totalObjects = 0;
|
|
254
|
+
let renderedObjects = 0;
|
|
255
|
+
let totalTriangles = 0;
|
|
256
|
+
let renderedTriangles = 0;
|
|
257
|
+
let totalLines = 0;
|
|
258
|
+
let renderedLines = 0;
|
|
259
|
+
let totalEdges = 0;
|
|
260
|
+
let renderedEdges = 0;
|
|
261
|
+
|
|
262
|
+
this.scene.traverse((object) => {
|
|
263
|
+
totalObjects++;
|
|
264
|
+
|
|
265
|
+
const geometry = object.geometry;
|
|
266
|
+
if (!geometry) return;
|
|
267
|
+
|
|
268
|
+
let triCount = 0;
|
|
269
|
+
if (geometry.index) {
|
|
270
|
+
triCount = Math.floor(geometry.index.count / 3);
|
|
271
|
+
} else if (geometry.attributes && geometry.attributes.position) {
|
|
272
|
+
triCount = Math.floor(geometry.attributes.position.count / 3);
|
|
273
|
+
}
|
|
274
|
+
totalTriangles += triCount;
|
|
275
|
+
|
|
276
|
+
let lineCount = 0;
|
|
277
|
+
if (geometry.index) {
|
|
278
|
+
lineCount = Math.floor(geometry.index.count / 2);
|
|
279
|
+
} else if (geometry.attributes && geometry.attributes.position) {
|
|
280
|
+
lineCount = Math.floor(geometry.attributes.position.count / 2);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
|
|
284
|
+
if (object.userData.isEdge) {
|
|
285
|
+
totalEdges += lineCount;
|
|
286
|
+
} else {
|
|
287
|
+
totalLines += lineCount;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (object.visible !== false) {
|
|
292
|
+
if (object.isMesh || object.isLine || object.isPoints) {
|
|
293
|
+
renderedObjects++;
|
|
294
|
+
if (object.isMesh) {
|
|
295
|
+
renderedTriangles += triCount;
|
|
296
|
+
} else if (object.type === "Line" || object.type === "LineSegments" || object.type === "LineLoop") {
|
|
297
|
+
if (object.userData.isEdge) {
|
|
298
|
+
renderedEdges += lineCount;
|
|
299
|
+
} else {
|
|
300
|
+
renderedLines += lineCount;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const geometryCount = this.geometryCache ? this.geometryCache.size : 0;
|
|
308
|
+
const geometryMemoryBytes = Array.from(this.geometryCache?.values?.() || []).reduce((a, b) => a + b, 0);
|
|
309
|
+
|
|
310
|
+
const uniqueMaterialIds = new Set();
|
|
311
|
+
const uniqueTextureIds = new Set();
|
|
312
|
+
if (Array.isArray(this.structures)) {
|
|
313
|
+
for (const structure of this.structures) {
|
|
314
|
+
console.log(structure.materialCache.values());
|
|
315
|
+
try {
|
|
316
|
+
for (const entry of structure.materialCache.values()) {
|
|
317
|
+
if (entry?.mesh?.uuid) uniqueMaterialIds.add(entry.mesh.uuid);
|
|
318
|
+
if (entry?.points?.uuid) uniqueMaterialIds.add(entry.points.uuid);
|
|
319
|
+
if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
|
|
320
|
+
}
|
|
321
|
+
} catch (exp) {
|
|
322
|
+
console.error("Error adding material to uniqueMaterialIds", exp);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const materialCount = uniqueMaterialIds.size;
|
|
327
|
+
const textureCount = uniqueTextureIds.size;
|
|
328
|
+
const estimatedGpuMemoryBytes = geometryMemoryBytes;
|
|
329
|
+
|
|
330
|
+
if (!this._webglInfoCache) {
|
|
331
|
+
try {
|
|
332
|
+
const gl = this.renderer.getContext();
|
|
333
|
+
const dbgInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
334
|
+
if (dbgInfo) {
|
|
335
|
+
const rendererStr = gl.getParameter(dbgInfo.UNMASKED_RENDERER_WEBGL);
|
|
336
|
+
const vendorStr = gl.getParameter(dbgInfo.UNMASKED_VENDOR_WEBGL);
|
|
337
|
+
this._webglInfoCache = { renderer: rendererStr, vendor: vendorStr };
|
|
338
|
+
} else {
|
|
339
|
+
this._webglInfoCache = { renderer: null, vendor: null };
|
|
340
|
+
}
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error("Error getting webgl info", e);
|
|
343
|
+
this._webglInfoCache = { renderer: null, vendor: null };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const size = new Vector2();
|
|
348
|
+
if (this.renderer && this.renderer.getSize) {
|
|
349
|
+
this.renderer.getSize(size);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
scene: {
|
|
354
|
+
beforeOptimization: {
|
|
355
|
+
objects: totalObjects - renderedObjects,
|
|
356
|
+
triangles: totalTriangles - renderedTriangles,
|
|
357
|
+
lines: totalLines - renderedLines,
|
|
358
|
+
edges: totalEdges - renderedEdges,
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
afterOptimization: {
|
|
362
|
+
objects: renderedObjects,
|
|
363
|
+
triangles: renderedTriangles,
|
|
364
|
+
lines: renderedLines,
|
|
365
|
+
edges: renderedEdges,
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
memory: {
|
|
369
|
+
geometries: { count: geometryCount, bytes: geometryMemoryBytes },
|
|
370
|
+
textures: { count: textureCount },
|
|
371
|
+
materials: { count: materialCount },
|
|
372
|
+
totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
|
|
373
|
+
},
|
|
374
|
+
system: {
|
|
375
|
+
webglRenderer: this._webglInfoCache?.renderer || "",
|
|
376
|
+
webglVendor: this._webglInfoCache?.vendor || "",
|
|
377
|
+
viewport: { width: size.x || 0, height: size.y || 0 },
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
247
382
|
async loadNode(nodeId, onLoadFinishCb) {
|
|
248
383
|
const node = this.nodes.get(nodeId);
|
|
249
384
|
if (!node || node.loaded || node.loading) return;
|
|
@@ -431,7 +566,7 @@ export class DynamicGltfLoader {
|
|
|
431
566
|
if (node.handle) {
|
|
432
567
|
mesh.userData.handle = node.handle;
|
|
433
568
|
} else {
|
|
434
|
-
mesh.userData.handle = `${node.structure.id}
|
|
569
|
+
mesh.userData.handle = `${node.structure.id}${STRUCTURE_ID_SEPARATOR}${mesh.userData.handle}`;
|
|
435
570
|
}
|
|
436
571
|
if (mesh.material.name === "edges") {
|
|
437
572
|
mesh.userData.isEdge = true;
|
|
@@ -607,7 +742,7 @@ export class DynamicGltfLoader {
|
|
|
607
742
|
|
|
608
743
|
let handle = null;
|
|
609
744
|
if (nodeDef.extras?.handle) {
|
|
610
|
-
handle = `${structure.id}
|
|
745
|
+
handle = `${structure.id}${STRUCTURE_ID_SEPARATOR}${nodeDef.extras.handle}`;
|
|
611
746
|
}
|
|
612
747
|
|
|
613
748
|
if (nodeDef.camera !== undefined) {
|
|
@@ -628,7 +763,7 @@ export class DynamicGltfLoader {
|
|
|
628
763
|
if (nodeDef.extras) {
|
|
629
764
|
nodeGroup.userData = { ...nodeDef.extras };
|
|
630
765
|
if (nodeGroup.userData.handle) {
|
|
631
|
-
nodeGroup.userData.handle = `${structure.id}
|
|
766
|
+
nodeGroup.userData.handle = `${structure.id}${STRUCTURE_ID_SEPARATOR}${nodeGroup.userData.handle}`;
|
|
632
767
|
}
|
|
633
768
|
}
|
|
634
769
|
|
|
@@ -680,7 +815,7 @@ export class DynamicGltfLoader {
|
|
|
680
815
|
}
|
|
681
816
|
|
|
682
817
|
if (meshDef.extras && meshDef.extras.handle) {
|
|
683
|
-
handle = `${structure.id}
|
|
818
|
+
handle = `${structure.id}${STRUCTURE_ID_SEPARATOR}${meshDef.extras.handle}`;
|
|
684
819
|
}
|
|
685
820
|
|
|
686
821
|
this.nodes.set(uniqueNodeId, {
|
|
@@ -785,13 +920,13 @@ export class DynamicGltfLoader {
|
|
|
785
920
|
}
|
|
786
921
|
|
|
787
922
|
async loadNodes() {
|
|
788
|
-
console.time("
|
|
923
|
+
console.time("Process nodes");
|
|
789
924
|
await this.processNodes();
|
|
790
|
-
console.timeEnd("
|
|
925
|
+
console.timeEnd("Process nodes");
|
|
791
926
|
|
|
792
|
-
console.time("
|
|
927
|
+
console.time("Optimize scene");
|
|
793
928
|
await this.optimizeScene();
|
|
794
|
-
console.timeEnd("
|
|
929
|
+
console.timeEnd("Optimize scene");
|
|
795
930
|
}
|
|
796
931
|
|
|
797
932
|
cleanupPartialLoad() {
|
|
@@ -955,7 +1090,7 @@ export class DynamicGltfLoader {
|
|
|
955
1090
|
}
|
|
956
1091
|
|
|
957
1092
|
createVisibilityMaterial(material) {
|
|
958
|
-
//
|
|
1093
|
+
// Apply shader directly to the original material
|
|
959
1094
|
material.onBeforeCompile = (shader) => {
|
|
960
1095
|
shader.vertexShader = shader.vertexShader.replace(
|
|
961
1096
|
"#include <common>",
|
|
@@ -991,7 +1126,7 @@ export class DynamicGltfLoader {
|
|
|
991
1126
|
);
|
|
992
1127
|
};
|
|
993
1128
|
|
|
994
|
-
//
|
|
1129
|
+
// Force recompilation of material
|
|
995
1130
|
material.needsUpdate = true;
|
|
996
1131
|
|
|
997
1132
|
return material;
|
|
@@ -1162,7 +1297,7 @@ export class DynamicGltfLoader {
|
|
|
1162
1297
|
}
|
|
1163
1298
|
this.handleToObjects.get(fullHandle).add(object);
|
|
1164
1299
|
|
|
1165
|
-
object.userData.structureId = object.userData.handle.split(
|
|
1300
|
+
object.userData.structureId = object.userData.handle.split(STRUCTURE_ID_SEPARATOR)[0];
|
|
1166
1301
|
}
|
|
1167
1302
|
|
|
1168
1303
|
getObjectsByHandle(handle) {
|
|
@@ -1237,11 +1372,32 @@ export class DynamicGltfLoader {
|
|
|
1237
1372
|
this.originalObjects.add(object);
|
|
1238
1373
|
}
|
|
1239
1374
|
|
|
1240
|
-
|
|
1375
|
+
yieldToUI() {
|
|
1376
|
+
return new Promise((resolve) => {
|
|
1377
|
+
requestAnimationFrame(() => {
|
|
1378
|
+
setTimeout(resolve, 0);
|
|
1379
|
+
});
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
async optimizeScene() {
|
|
1384
|
+
console.log("Starting scene optimization...");
|
|
1385
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1386
|
+
phase: "start",
|
|
1387
|
+
progress: 0,
|
|
1388
|
+
message: "Starting optimization...",
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1241
1391
|
this.originalObjects.clear();
|
|
1242
1392
|
this.originalObjectsToSelection.clear();
|
|
1243
1393
|
const structureGroups = new Map();
|
|
1244
1394
|
|
|
1395
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1396
|
+
phase: "collecting",
|
|
1397
|
+
progress: 5,
|
|
1398
|
+
message: "Collecting scene objects...",
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1245
1401
|
this.scene.traverse((object) => {
|
|
1246
1402
|
if (object.userData.structureId) {
|
|
1247
1403
|
const structureId = object.userData.structureId;
|
|
@@ -1274,32 +1430,78 @@ export class DynamicGltfLoader {
|
|
|
1274
1430
|
}
|
|
1275
1431
|
});
|
|
1276
1432
|
|
|
1433
|
+
let processedGroups = 0;
|
|
1434
|
+
const totalGroups = structureGroups.size;
|
|
1435
|
+
|
|
1436
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1437
|
+
phase: "merging",
|
|
1438
|
+
progress: 10,
|
|
1439
|
+
message: `Merging ${totalGroups} structure groups...`,
|
|
1440
|
+
current: 0,
|
|
1441
|
+
total: totalGroups,
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1277
1444
|
for (const group of structureGroups.values()) {
|
|
1278
1445
|
group.mapMeshes.clear();
|
|
1279
1446
|
group.mapLines.clear();
|
|
1280
1447
|
group.mapLineSegments.clear();
|
|
1281
1448
|
group.mapPoints.clear();
|
|
1282
1449
|
|
|
1283
|
-
this.mergeMeshGroups(group.meshes, group.rootGroup);
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1286
|
-
this.
|
|
1450
|
+
await this.mergeMeshGroups(group.meshes, group.rootGroup);
|
|
1451
|
+
await this.yieldToUI();
|
|
1452
|
+
|
|
1453
|
+
await this.mergeLineGroups(group.lines, group.rootGroup);
|
|
1454
|
+
await this.yieldToUI();
|
|
1455
|
+
|
|
1456
|
+
await this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
|
|
1457
|
+
await this.yieldToUI();
|
|
1458
|
+
|
|
1459
|
+
await this.mergePointsGroups(group.points, group.rootGroup);
|
|
1460
|
+
|
|
1461
|
+
processedGroups++;
|
|
1462
|
+
const progress = 10 + Math.round((processedGroups / totalGroups) * 80);
|
|
1463
|
+
|
|
1464
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1465
|
+
phase: "merging",
|
|
1466
|
+
progress,
|
|
1467
|
+
message: `Processing structure ${processedGroups}/${totalGroups}...`,
|
|
1468
|
+
current: processedGroups,
|
|
1469
|
+
total: totalGroups,
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
console.log(`Optimization progress: ${processedGroups}/${totalGroups} structure groups processed (${progress}%)`);
|
|
1473
|
+
|
|
1474
|
+
await this.yieldToUI();
|
|
1287
1475
|
}
|
|
1288
1476
|
|
|
1477
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1478
|
+
phase: "finalizing",
|
|
1479
|
+
progress: 95,
|
|
1480
|
+
message: "Finalizing optimization...",
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1289
1483
|
this.originalObjects.forEach((obj) => {
|
|
1290
1484
|
obj.visible = false;
|
|
1291
1485
|
if (!(obj instanceof Points) && !obj.userData.isEdge) {
|
|
1292
1486
|
this.originalObjectsToSelection.add(obj);
|
|
1293
1487
|
}
|
|
1294
1488
|
});
|
|
1489
|
+
|
|
1295
1490
|
this.initializeObjectVisibility();
|
|
1296
1491
|
|
|
1297
1492
|
console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
|
|
1298
1493
|
|
|
1494
|
+
this.dispatchEvent("optimizationprogress", {
|
|
1495
|
+
phase: "complete",
|
|
1496
|
+
progress: 100,
|
|
1497
|
+
message: `Optimization complete! ${this.maxObjectId} objects processed.`,
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1299
1500
|
this.dispatchEvent("update");
|
|
1300
1501
|
}
|
|
1301
1502
|
|
|
1302
|
-
mergeMeshGroups(materialGroups, rootGroup) {
|
|
1503
|
+
async mergeMeshGroups(materialGroups, rootGroup) {
|
|
1504
|
+
let processedGroups = 0;
|
|
1303
1505
|
for (const group of materialGroups) {
|
|
1304
1506
|
if (!group.material) {
|
|
1305
1507
|
console.warn("Skipping mesh group with null material");
|
|
@@ -1399,6 +1601,11 @@ export class DynamicGltfLoader {
|
|
|
1399
1601
|
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1400
1602
|
}
|
|
1401
1603
|
});
|
|
1604
|
+
|
|
1605
|
+
processedGroups++;
|
|
1606
|
+
if (processedGroups % 5 === 0) {
|
|
1607
|
+
await this.yieldToUI();
|
|
1608
|
+
}
|
|
1402
1609
|
} catch (error) {
|
|
1403
1610
|
console.error("Failed to merge meshes for material:", error);
|
|
1404
1611
|
group.objects.forEach((mesh) => {
|
|
@@ -1408,7 +1615,8 @@ export class DynamicGltfLoader {
|
|
|
1408
1615
|
}
|
|
1409
1616
|
}
|
|
1410
1617
|
|
|
1411
|
-
mergeLineGroups(materialGroups, rootGroup) {
|
|
1618
|
+
async mergeLineGroups(materialGroups, rootGroup) {
|
|
1619
|
+
let processedGroups = 0;
|
|
1412
1620
|
for (const group of materialGroups) {
|
|
1413
1621
|
if (group.objects.length === 0) continue;
|
|
1414
1622
|
|
|
@@ -1433,7 +1641,9 @@ export class DynamicGltfLoader {
|
|
|
1433
1641
|
const indices = [];
|
|
1434
1642
|
let vertexOffset = 0;
|
|
1435
1643
|
|
|
1644
|
+
let isEdge = false;
|
|
1436
1645
|
group.objects.forEach((line) => {
|
|
1646
|
+
isEdge = line.userData.isEdge;
|
|
1437
1647
|
const geometry = line.geometry;
|
|
1438
1648
|
const positionAttr = geometry.attributes.position;
|
|
1439
1649
|
const vertexCount = positionAttr.count;
|
|
@@ -1442,7 +1652,6 @@ export class DynamicGltfLoader {
|
|
|
1442
1652
|
if (!this.objectIdToIndex.has(handle)) {
|
|
1443
1653
|
this.objectIdToIndex.set(handle, this.maxObjectId++);
|
|
1444
1654
|
}
|
|
1445
|
-
// const objectId = this.objectIdToIndex.get(handle);
|
|
1446
1655
|
|
|
1447
1656
|
objectMapping.set(line, {
|
|
1448
1657
|
startVertexIndex: currentVertexOffset,
|
|
@@ -1497,6 +1706,8 @@ export class DynamicGltfLoader {
|
|
|
1497
1706
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1498
1707
|
|
|
1499
1708
|
const mergedLine = new LineSegments(geometry, visibilityMaterial);
|
|
1709
|
+
mergedLine.userData.isEdge = isEdge;
|
|
1710
|
+
|
|
1500
1711
|
const mergedObjects = [mergedLine];
|
|
1501
1712
|
if (this.useVAO) {
|
|
1502
1713
|
this.createVAO(mergedLine);
|
|
@@ -1522,10 +1733,16 @@ export class DynamicGltfLoader {
|
|
|
1522
1733
|
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1523
1734
|
}
|
|
1524
1735
|
});
|
|
1736
|
+
|
|
1737
|
+
processedGroups++;
|
|
1738
|
+
if (processedGroups % 5 === 0) {
|
|
1739
|
+
await this.yieldToUI();
|
|
1740
|
+
}
|
|
1525
1741
|
}
|
|
1526
1742
|
}
|
|
1527
1743
|
|
|
1528
|
-
mergeLineSegmentGroups(materialGroups, rootGroup) {
|
|
1744
|
+
async mergeLineSegmentGroups(materialGroups, rootGroup) {
|
|
1745
|
+
let processedGroups = 0;
|
|
1529
1746
|
for (const group of materialGroups) {
|
|
1530
1747
|
if (!group.material) {
|
|
1531
1748
|
console.warn("Skipping line segment group with null material");
|
|
@@ -1538,8 +1755,10 @@ export class DynamicGltfLoader {
|
|
|
1538
1755
|
const handles = new Set();
|
|
1539
1756
|
const objectMapping = new Map();
|
|
1540
1757
|
let currentVertexOffset = 0;
|
|
1758
|
+
let isEdge = false;
|
|
1541
1759
|
|
|
1542
1760
|
for (const line of group.objects) {
|
|
1761
|
+
isEdge = line.userData.isEdge;
|
|
1543
1762
|
const geometry = line.geometry.clone();
|
|
1544
1763
|
line.updateWorldMatrix(true, false);
|
|
1545
1764
|
geometry.applyMatrix4(line.matrixWorld);
|
|
@@ -1585,6 +1804,7 @@ export class DynamicGltfLoader {
|
|
|
1585
1804
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1586
1805
|
|
|
1587
1806
|
const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
|
|
1807
|
+
mergedLine.userData.isEdge = isEdge;
|
|
1588
1808
|
|
|
1589
1809
|
if (this.useVAO) {
|
|
1590
1810
|
this.createVAO(mergedLine);
|
|
@@ -1618,6 +1838,11 @@ export class DynamicGltfLoader {
|
|
|
1618
1838
|
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1619
1839
|
}
|
|
1620
1840
|
});
|
|
1841
|
+
|
|
1842
|
+
processedGroups++;
|
|
1843
|
+
if (processedGroups % 5 === 0) {
|
|
1844
|
+
await this.yieldToUI();
|
|
1845
|
+
}
|
|
1621
1846
|
} catch (error) {
|
|
1622
1847
|
console.warn("Failed to merge line segments for material:", error);
|
|
1623
1848
|
group.objects.forEach((line) => {
|
|
@@ -1627,7 +1852,8 @@ export class DynamicGltfLoader {
|
|
|
1627
1852
|
}
|
|
1628
1853
|
}
|
|
1629
1854
|
|
|
1630
|
-
mergePointsGroups(materialGroups, rootGroup) {
|
|
1855
|
+
async mergePointsGroups(materialGroups, rootGroup) {
|
|
1856
|
+
let processedGroups = 0;
|
|
1631
1857
|
for (const group of materialGroups) {
|
|
1632
1858
|
if (!group.material) {
|
|
1633
1859
|
console.warn("Skipping points group with null material");
|
|
@@ -1678,6 +1904,11 @@ export class DynamicGltfLoader {
|
|
|
1678
1904
|
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1679
1905
|
}
|
|
1680
1906
|
});
|
|
1907
|
+
|
|
1908
|
+
processedGroups++;
|
|
1909
|
+
if (processedGroups % 5 === 0) {
|
|
1910
|
+
await this.yieldToUI();
|
|
1911
|
+
}
|
|
1681
1912
|
} catch (error) {
|
|
1682
1913
|
console.warn("Failed to merge points for material:", error);
|
|
1683
1914
|
group.objects.forEach((points) => {
|
|
@@ -30,7 +30,6 @@ import { DynamicGltfLoader } from "./DynamicGltfLoader.js";
|
|
|
30
30
|
|
|
31
31
|
export class DynamicModelImpl extends ModelImpl {
|
|
32
32
|
public gltfLoader: DynamicGltfLoader;
|
|
33
|
-
public modelId: number;
|
|
34
33
|
|
|
35
34
|
override getExtents(target: Box3): Box3 {
|
|
36
35
|
return target.union(this.gltfLoader.getTotalGeometryExtent());
|
|
@@ -53,35 +52,46 @@ export class DynamicModelImpl extends ModelImpl {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
override getObjectsByHandles(handles: string | string[]): Object3D[] {
|
|
56
|
-
const
|
|
55
|
+
const ownHandles = this.getOwnHandles(handles);
|
|
56
|
+
if (ownHandles.length === 0) return [];
|
|
57
|
+
|
|
58
|
+
const handlesSet = new Set(ownHandles);
|
|
59
|
+
|
|
57
60
|
const objects = [];
|
|
58
61
|
handlesSet.forEach((handle) => {
|
|
59
|
-
|
|
60
|
-
const handles = this.gltfLoader.handleToObjects.get(handle2) || [];
|
|
61
|
-
objects.push(...Array.from(handles));
|
|
62
|
+
objects.push(...this.gltfLoader.getObjectsByHandle(handle));
|
|
62
63
|
});
|
|
64
|
+
|
|
63
65
|
return objects;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
override getHandlesByObjects(objects: Object3D | Object3D[]): string[] {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
+
const ownObjects = this.getOwnObjects(objects);
|
|
70
|
+
if (ownObjects.length === 0) return [];
|
|
71
|
+
|
|
72
|
+
const handleSet = new Set<string>();
|
|
73
|
+
ownObjects.forEach((object) => {
|
|
74
|
+
const handle = object.userData.handle;
|
|
75
|
+
if (handle) handleSet.add(handle);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return Array.from(handleSet);
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
override hideObjects(objects: Object3D | Object3D[]): this {
|
|
72
|
-
const handles =
|
|
82
|
+
const handles = this.getHandlesByObjects(objects);
|
|
73
83
|
this.gltfLoader.hideObjects(handles);
|
|
74
84
|
return this;
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
override isolateObjects(objects: Object3D | Object3D[]): this {
|
|
78
|
-
const handles =
|
|
88
|
+
const handles = this.getHandlesByObjects(objects);
|
|
79
89
|
this.gltfLoader.isolateObjects(new Set(handles));
|
|
80
90
|
return this;
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
override showObjects(objects: Object3D | Object3D[]): this {
|
|
84
|
-
const handles =
|
|
94
|
+
const handles = this.getHandlesByObjects(objects);
|
|
85
95
|
this.gltfLoader.showObjects(handles);
|
|
86
96
|
return this;
|
|
87
97
|
}
|