@inweb/viewer-three 26.6.6 → 26.7.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/plugins/components/AxesHelperComponent.js +5 -5
- 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 +5 -5
- package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.js +15 -7
- 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 +15 -7
- package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -1
- package/dist/plugins/components/LightHelperComponent.js +5 -5
- package/dist/plugins/components/LightHelperComponent.js.map +1 -1
- package/dist/plugins/components/LightHelperComponent.min.js +1 -1
- package/dist/plugins/components/LightHelperComponent.module.js +5 -5
- package/dist/plugins/components/LightHelperComponent.module.js.map +1 -1
- package/dist/plugins/loaders/GLTFCloudLoader.js +4840 -0
- package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -0
- package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -0
- package/dist/plugins/loaders/GLTFCloudLoader.module.js +49 -0
- package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +1 -0
- package/dist/plugins/loaders/IFCXLoader.js +12 -6
- 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 +13 -7
- package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
- package/dist/viewer-three.js +3131 -459
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +2 -2
- package/dist/viewer-three.module.js +2326 -264
- package/dist/viewer-three.module.js.map +1 -1
- package/lib/Viewer/Viewer.d.ts +6 -5
- package/lib/Viewer/components/HighlighterComponent.d.ts +4 -3
- package/lib/Viewer/components/SelectionComponent.d.ts +8 -5
- package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +1 -1
- package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +20 -0
- package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +15 -0
- package/lib/Viewer/model/IModelImpl.d.ts +27 -0
- package/lib/Viewer/model/ModelImpl.d.ts +30 -0
- package/lib/Viewer/model/index.d.ts +2 -0
- package/lib/index.d.ts +1 -0
- package/package.json +11 -7
- package/plugins/components/AxesHelperComponent.ts +5 -5
- package/plugins/components/ExtentsHelperComponent.ts +15 -7
- package/plugins/components/LightHelperComponent.ts +5 -5
- package/{src/Viewer/loaders/GLTFCloudModelLoader.ts → plugins/loaders/GLTFCloudLoader.ts} +15 -12
- package/plugins/loaders/{IFCXCloudFileLoader.ts → IFCXCloudLoader.ts} +8 -4
- package/plugins/loaders/IFCXFileLoader.ts +7 -3
- package/plugins/loaders/IFCXLoader.ts +2 -2
- package/src/Viewer/Viewer.ts +32 -36
- package/src/Viewer/commands/ClearSelected.ts +2 -3
- package/src/Viewer/commands/Explode.ts +1 -47
- package/src/Viewer/commands/GetModels.ts +1 -1
- package/src/Viewer/commands/GetSelected.ts +3 -1
- package/src/Viewer/commands/HideSelected.ts +3 -4
- package/src/Viewer/commands/IsolateSelected.ts +1 -7
- package/src/Viewer/commands/SelectModel.ts +9 -1
- package/src/Viewer/commands/SetSelected.ts +8 -10
- package/src/Viewer/commands/ShowAll.ts +1 -1
- package/src/Viewer/components/BackgroundComponent.ts +1 -0
- package/src/Viewer/components/ExtentsComponent.ts +5 -3
- package/src/Viewer/components/HighlighterComponent.ts +79 -48
- package/src/Viewer/components/SelectionComponent.ts +67 -21
- package/src/Viewer/draggers/CuttingPlaneDragger.ts +7 -3
- package/src/Viewer/draggers/MeasureLineDragger.ts +2 -0
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +1628 -0
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +102 -0
- package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +450 -0
- package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +145 -0
- package/src/Viewer/loaders/GLTFFileLoader.ts +7 -2
- package/src/Viewer/loaders/index.ts +2 -2
- package/src/Viewer/model/IModelImpl.ts +67 -0
- package/src/Viewer/model/ModelImpl.ts +215 -0
- package/src/Viewer/model/index.ts +25 -0
- package/src/index.ts +1 -0
- package/lib/Viewer/loaders/GLTFCloudModelLoader.d.ts +0 -8
|
@@ -0,0 +1,1628 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BufferGeometry,
|
|
3
|
+
PointsMaterial,
|
|
4
|
+
Material,
|
|
5
|
+
Points,
|
|
6
|
+
Mesh,
|
|
7
|
+
TriangleStripDrawMode,
|
|
8
|
+
TriangleFanDrawMode,
|
|
9
|
+
LineSegments,
|
|
10
|
+
Line,
|
|
11
|
+
LineLoop,
|
|
12
|
+
Group,
|
|
13
|
+
Vector3,
|
|
14
|
+
Quaternion,
|
|
15
|
+
Matrix4,
|
|
16
|
+
Box3,
|
|
17
|
+
MeshStandardMaterial,
|
|
18
|
+
Color,
|
|
19
|
+
MathUtils,
|
|
20
|
+
PerspectiveCamera,
|
|
21
|
+
OrthographicCamera,
|
|
22
|
+
DoubleSide,
|
|
23
|
+
NormalBlending,
|
|
24
|
+
BufferAttribute,
|
|
25
|
+
LineBasicMaterial,
|
|
26
|
+
} from "three";
|
|
27
|
+
import { GltfStructure, GL_CONSTANTS } from "./GltfStructure.js";
|
|
28
|
+
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
29
|
+
|
|
30
|
+
export class DynamicGltfLoader {
|
|
31
|
+
constructor(camera, scene, renderer) {
|
|
32
|
+
this.camera = camera;
|
|
33
|
+
this.scene = scene;
|
|
34
|
+
this.renderer = renderer;
|
|
35
|
+
|
|
36
|
+
this.eventHandlers = {
|
|
37
|
+
geometryprogress: [],
|
|
38
|
+
databasechunk: [],
|
|
39
|
+
geometryend: [],
|
|
40
|
+
geometryerror: [],
|
|
41
|
+
update: [],
|
|
42
|
+
geometrymemory: [],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.loadDistance = 100;
|
|
46
|
+
this.unloadDistance = 150;
|
|
47
|
+
this.checkInterval = 1000;
|
|
48
|
+
|
|
49
|
+
this.nodes = new Map();
|
|
50
|
+
this.loadedMeshes = new Map();
|
|
51
|
+
this.nodesToLoad = [];
|
|
52
|
+
this.edgeNodes = [];
|
|
53
|
+
this.structures = [];
|
|
54
|
+
this.structureRoots = new Map();
|
|
55
|
+
|
|
56
|
+
this.memoryLimit = this.getAvailableMemory();
|
|
57
|
+
this.loadedGeometrySize = 0;
|
|
58
|
+
this.geometryCache = new Map();
|
|
59
|
+
this.materialCache = new Map();
|
|
60
|
+
this.textureCache = new Map();
|
|
61
|
+
this.currentMemoryUsage = 0;
|
|
62
|
+
|
|
63
|
+
this.updateMemoryIndicator();
|
|
64
|
+
|
|
65
|
+
this.loadedMaterials = new Map();
|
|
66
|
+
this.abortController = new AbortController();
|
|
67
|
+
|
|
68
|
+
this.batchSize = 10000;
|
|
69
|
+
this.frameDelay = 0;
|
|
70
|
+
|
|
71
|
+
this.graphicsObjectLimit = 10000;
|
|
72
|
+
this.totalLoadedObjects = 0;
|
|
73
|
+
|
|
74
|
+
this.lastUpdateTime = 0;
|
|
75
|
+
this.updateInterval = 1000;
|
|
76
|
+
|
|
77
|
+
this.handleToObjects = new Map();
|
|
78
|
+
|
|
79
|
+
this.originalObjects = new Set();
|
|
80
|
+
this.originalObjectsToSelection = new Set();
|
|
81
|
+
|
|
82
|
+
this.optimizedOriginalMap = new Map();
|
|
83
|
+
this.mergedMesh = new Set();
|
|
84
|
+
this.mergedLines = new Set();
|
|
85
|
+
this.mergedLineSegments = new Set();
|
|
86
|
+
this.mergedPoints = new Set();
|
|
87
|
+
|
|
88
|
+
this.isolatedObjects = [];
|
|
89
|
+
this.useVAO = !!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext;
|
|
90
|
+
|
|
91
|
+
this.handleToOptimizedObjects = new Map();
|
|
92
|
+
|
|
93
|
+
this.hiddenHandles = new Set();
|
|
94
|
+
this.newOptimizedObjects = new Set();
|
|
95
|
+
this.oldOptimizeObjects = new Set();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getAvailableMemory() {
|
|
99
|
+
let memoryLimit = 6 * 1024 * 1024 * 1024;
|
|
100
|
+
try {
|
|
101
|
+
if (navigator.deviceMemory) {
|
|
102
|
+
memoryLimit = navigator.deviceMemory * 1024 * 1024 * 1024;
|
|
103
|
+
} else if (performance.memory) {
|
|
104
|
+
const jsHeapSizeLimit = performance.memory.jsHeapSizeLimit;
|
|
105
|
+
if (jsHeapSizeLimit) {
|
|
106
|
+
memoryLimit = Math.min(memoryLimit, jsHeapSizeLimit);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
memoryLimit = Math.min(memoryLimit, 16 * 1024 * 1024 * 1024);
|
|
111
|
+
memoryLimit = Math.max(memoryLimit, 2 * 1024 * 1024 * 1024);
|
|
112
|
+
|
|
113
|
+
console.log(`Available memory set to ${Math.round(memoryLimit / (1024 * 1024 * 1024))}GB`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn("Error detecting available memory:", error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return memoryLimit;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getAbortController() {
|
|
122
|
+
return this.abortController;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
abortLoading() {
|
|
126
|
+
this.abortController.abort();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
updateMemoryIndicator() {
|
|
130
|
+
this.dispatchEvent("geometrymemory", {
|
|
131
|
+
currentUsage: this.currentMemoryUsage,
|
|
132
|
+
limit: this.memoryLimit,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setMemoryLimit(bytesLimit) {
|
|
137
|
+
// this.memoryLimit = bytesLimit;
|
|
138
|
+
//this.updateMemoryIndicator();
|
|
139
|
+
// console.log(`Memory limit set to ${Math.round(bytesLimit / (1024 * 1024))}MB`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
estimateGeometrySize(nodeGroup) {
|
|
143
|
+
let totalSize = 0;
|
|
144
|
+
nodeGroup.traverse((child) => {
|
|
145
|
+
if (child.geometry) {
|
|
146
|
+
if (this.abortController.signal.aborted) {
|
|
147
|
+
throw new DOMException("Loading aborted", "AbortError");
|
|
148
|
+
}
|
|
149
|
+
const geometry = child.geometry;
|
|
150
|
+
|
|
151
|
+
if (geometry.attributes) {
|
|
152
|
+
Object.values(geometry.attributes).forEach((attribute) => {
|
|
153
|
+
if (attribute && attribute.array) {
|
|
154
|
+
totalSize += attribute.array.byteLength;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (geometry.index && geometry.index.array) {
|
|
160
|
+
totalSize += geometry.index.array.byteLength;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return totalSize;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
recalculateScene() {
|
|
169
|
+
const geometries = [];
|
|
170
|
+
this.scene.traverse((object) => {
|
|
171
|
+
if (this.abortController.signal.aborted) {
|
|
172
|
+
throw new DOMException("Loading aborted", "AbortError");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (object.geometry && !this.geometryCache.has(object.geometry.uuid)) {
|
|
176
|
+
const size = this.estimateGeometrySize(object);
|
|
177
|
+
this.geometryCache.set(object.geometry.uuid, size);
|
|
178
|
+
geometries.push({
|
|
179
|
+
object,
|
|
180
|
+
size,
|
|
181
|
+
distance: object.position.distanceTo(this.camera.position),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (this.abortController.signal.aborted) {
|
|
187
|
+
throw new DOMException("Loading aborted", "AbortError");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
geometries.sort((a, b) => b.distance - a.distance);
|
|
191
|
+
|
|
192
|
+
let currentMemoryUsage = 0;
|
|
193
|
+
for (const geo of geometries) {
|
|
194
|
+
currentMemoryUsage += geo.size;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (currentMemoryUsage > this.memoryLimit) {
|
|
198
|
+
console.log(`Memory usage (${Math.round(currentMemoryUsage / (1024 * 1024))}MB) exceeds limit`);
|
|
199
|
+
|
|
200
|
+
for (const geo of geometries) {
|
|
201
|
+
if (currentMemoryUsage <= this.memoryLimit) break;
|
|
202
|
+
|
|
203
|
+
if (this.abortController.signal.aborted) {
|
|
204
|
+
throw new DOMException("Loading aborted", "AbortError");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const object = geo.object;
|
|
208
|
+
if (object.geometry) {
|
|
209
|
+
currentMemoryUsage -= geo.size;
|
|
210
|
+
this.geometryCache.delete(object.geometry.uuid);
|
|
211
|
+
object.geometry.dispose();
|
|
212
|
+
object.visible = false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.currentMemoryUsage = currentMemoryUsage;
|
|
218
|
+
this.updateMemoryIndicator();
|
|
219
|
+
|
|
220
|
+
console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async loadNode(nodeId) {
|
|
224
|
+
const node = this.nodes.get(nodeId);
|
|
225
|
+
if (!node || node.loaded || node.loading) return;
|
|
226
|
+
|
|
227
|
+
node.loading = true;
|
|
228
|
+
const meshDef = node.structure.getJson().meshes[node.meshIndex];
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
for (const primitive of meshDef.primitives) {
|
|
232
|
+
const positionAccessor = primitive.attributes.POSITION;
|
|
233
|
+
const geometry = new BufferGeometry();
|
|
234
|
+
const attributes = new Map();
|
|
235
|
+
|
|
236
|
+
attributes.set("position", node.structure.createBufferAttribute(positionAccessor));
|
|
237
|
+
|
|
238
|
+
if (primitive.attributes.NORMAL !== undefined) {
|
|
239
|
+
attributes.set("normal", node.structure.createBufferAttribute(primitive.attributes.NORMAL));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (primitive.attributes.TEXCOORD_0 !== undefined) {
|
|
243
|
+
attributes.set("uv", node.structure.createBufferAttribute(primitive.attributes.TEXCOORD_0));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const loadedAttributes = await Promise.all(
|
|
247
|
+
[...attributes.entries()].map(async ([name, promise]) => {
|
|
248
|
+
const attribute = await promise;
|
|
249
|
+
return [name, attribute];
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
loadedAttributes.forEach(([name, attribute]) => {
|
|
254
|
+
geometry.setAttribute(name, attribute);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (primitive.indices !== undefined) {
|
|
258
|
+
const indexAttribute = await node.structure.createBufferAttribute(primitive.indices);
|
|
259
|
+
geometry.setIndex(indexAttribute);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.currentPrimitiveMode = primitive.mode;
|
|
263
|
+
|
|
264
|
+
let material;
|
|
265
|
+
if (primitive.material !== undefined) {
|
|
266
|
+
material = node.structure.materials.get(primitive.material) || this.createDefaultMaterial();
|
|
267
|
+
} else {
|
|
268
|
+
material = this.createDefaultMaterial();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let mesh;
|
|
272
|
+
if (primitive.mode === GL_CONSTANTS.POINTS) {
|
|
273
|
+
const pointsMaterial = new PointsMaterial();
|
|
274
|
+
Material.prototype.copy.call(pointsMaterial, material);
|
|
275
|
+
pointsMaterial.color.copy(material.color);
|
|
276
|
+
pointsMaterial.map = material.map;
|
|
277
|
+
pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
|
|
278
|
+
|
|
279
|
+
mesh = new Points(geometry, pointsMaterial);
|
|
280
|
+
} else if (
|
|
281
|
+
primitive.mode === GL_CONSTANTS.TRIANGLES ||
|
|
282
|
+
primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP ||
|
|
283
|
+
primitive.mode === GL_CONSTANTS.TRIANGLE_FAN ||
|
|
284
|
+
primitive.mode === undefined
|
|
285
|
+
) {
|
|
286
|
+
mesh = new Mesh(geometry, material);
|
|
287
|
+
|
|
288
|
+
if (primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP) {
|
|
289
|
+
mesh.drawMode = TriangleStripDrawMode;
|
|
290
|
+
} else if (primitive.mode === GL_CONSTANTS.TRIANGLE_FAN) {
|
|
291
|
+
mesh.drawMode = TriangleFanDrawMode;
|
|
292
|
+
}
|
|
293
|
+
} else if (primitive.mode === GL_CONSTANTS.LINES) {
|
|
294
|
+
mesh = new LineSegments(geometry, material);
|
|
295
|
+
} else if (primitive.mode === GL_CONSTANTS.LINE_STRIP) {
|
|
296
|
+
mesh = new Line(geometry, material);
|
|
297
|
+
} else if (primitive.mode === GL_CONSTANTS.LINE_LOOP) {
|
|
298
|
+
mesh = new LineLoop(geometry, material);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (node.extras) {
|
|
302
|
+
mesh.userData = { ...mesh.userData, ...node.extras };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (meshDef.extras) {
|
|
306
|
+
mesh.userData = { ...mesh.userData, ...meshDef.extras };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (primitive.extras) {
|
|
310
|
+
mesh.userData = { ...mesh.userData, ...primitive.extras };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (node.handle) {
|
|
314
|
+
mesh.userData.handle = node.handle;
|
|
315
|
+
} else {
|
|
316
|
+
mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (mesh.material.name === "edges") {
|
|
320
|
+
mesh.userData.isEdge = true;
|
|
321
|
+
} else {
|
|
322
|
+
mesh.userData.isEdge = false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.registerObjectWithHandle(mesh, mesh.userData.handle);
|
|
326
|
+
|
|
327
|
+
mesh.position.copy(node.position);
|
|
328
|
+
|
|
329
|
+
if (!geometry.attributes.normal) {
|
|
330
|
+
geometry.computeVertexNormals();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (material.aoMap && geometry.attributes.uv) {
|
|
334
|
+
geometry.setAttribute("uv2", geometry.attributes.uv);
|
|
335
|
+
}
|
|
336
|
+
if (node.group) {
|
|
337
|
+
node.group.add(mesh);
|
|
338
|
+
} else {
|
|
339
|
+
this.scene.add(mesh);
|
|
340
|
+
}
|
|
341
|
+
node.object = mesh;
|
|
342
|
+
|
|
343
|
+
this.totalLoadedObjects++;
|
|
344
|
+
mesh.visible = this.totalLoadedObjects < this.graphicsObjectLimit;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
node.loaded = true;
|
|
348
|
+
node.loading = false;
|
|
349
|
+
|
|
350
|
+
const geometrySize = this.estimateGeometrySize(node.object);
|
|
351
|
+
this.geometryCache.set(node.object.uuid, geometrySize);
|
|
352
|
+
this.currentMemoryUsage += geometrySize;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error.name !== "AbortError") {
|
|
355
|
+
console.error(`Error loading node ${nodeId}:`, error);
|
|
356
|
+
}
|
|
357
|
+
node.loading = false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
unloadNode(nodeId) {
|
|
362
|
+
const node = this.nodes.get(nodeId);
|
|
363
|
+
if (!node || !node.loaded) return;
|
|
364
|
+
|
|
365
|
+
if (node.object) {
|
|
366
|
+
if (node.object.parent) {
|
|
367
|
+
node.object.parent.remove(node.object);
|
|
368
|
+
} else {
|
|
369
|
+
this.scene.remove(node.object);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
node.object.traverse((child) => {
|
|
373
|
+
if (child.geometry) {
|
|
374
|
+
const geometrySize = this.geometryCache.get(child.geometry.uuid) || 0;
|
|
375
|
+
this.currentMemoryUsage -= geometrySize;
|
|
376
|
+
this.geometryCache.delete(child.geometry.uuid);
|
|
377
|
+
child.geometry.dispose();
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
node.object = null;
|
|
382
|
+
node.loaded = false;
|
|
383
|
+
this.updateMemoryIndicator();
|
|
384
|
+
console.log(`Unloaded node: ${nodeId}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
checkDistances() {
|
|
389
|
+
const cameraPosition = this.camera.position;
|
|
390
|
+
|
|
391
|
+
this.nodes.forEach((node, nodeId) => {
|
|
392
|
+
const distance = cameraPosition.distanceTo(node.position);
|
|
393
|
+
|
|
394
|
+
if (node.loaded) {
|
|
395
|
+
if (distance > this.unloadDistance) {
|
|
396
|
+
this.unloadNode(nodeId);
|
|
397
|
+
}
|
|
398
|
+
} else if (!node.loading) {
|
|
399
|
+
if (distance < this.loadDistance) {
|
|
400
|
+
this.loadNode(nodeId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async loadStructure(structures) {
|
|
407
|
+
this.clear();
|
|
408
|
+
|
|
409
|
+
const structureArray = Array.isArray(structures) ? structures : [structures];
|
|
410
|
+
|
|
411
|
+
for (const structure of structureArray) {
|
|
412
|
+
this.structures.push(structure);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (const structure of this.structures) {
|
|
416
|
+
try {
|
|
417
|
+
await structure.loadTextures();
|
|
418
|
+
await structure.loadMaterials();
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error("Error loading materials:", error);
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
await this.processSceneHierarchy();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async processSceneHierarchy() {
|
|
429
|
+
if (this.structures.length === 0) {
|
|
430
|
+
throw new Error("No GLTF structures loaded");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.nodesToLoad = [];
|
|
434
|
+
|
|
435
|
+
let estimatedSize = 0;
|
|
436
|
+
|
|
437
|
+
for (const structure of this.structures) {
|
|
438
|
+
const gltf = structure.getJson();
|
|
439
|
+
|
|
440
|
+
if (!gltf.scenes || !gltf.scenes.length) {
|
|
441
|
+
console.warn("No scenes found in GLTF structure");
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
estimatedSize += gltf.buffers[0].byteLength;
|
|
446
|
+
|
|
447
|
+
const rootGroup = new Group();
|
|
448
|
+
rootGroup.name = `structure_${structure.id}_root`;
|
|
449
|
+
this.scene.add(rootGroup);
|
|
450
|
+
this.structureRoots.set(structure.id, rootGroup);
|
|
451
|
+
|
|
452
|
+
const scene = gltf.scenes[gltf.scene || 0];
|
|
453
|
+
|
|
454
|
+
for (const nodeIndex of scene.nodes) {
|
|
455
|
+
await this.processNodeHierarchy(structure, nodeIndex, rootGroup);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const ignoreEdges = estimatedSize * 2 > this.memoryLimit;
|
|
460
|
+
|
|
461
|
+
this.nodesToLoad.sort((a, b) => {
|
|
462
|
+
const nodeA = this.nodes.get(a);
|
|
463
|
+
const nodeB = this.nodes.get(b);
|
|
464
|
+
|
|
465
|
+
if (!nodeA?.geometryExtents || !nodeB?.geometryExtents) {
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const sizeA = nodeA.geometryExtents.getSize(new Vector3());
|
|
470
|
+
const sizeB = nodeB.geometryExtents.getSize(new Vector3());
|
|
471
|
+
const volumeA = sizeA.x * sizeA.y * sizeA.z;
|
|
472
|
+
const volumeB = sizeB.x * sizeB.y * sizeB.z;
|
|
473
|
+
|
|
474
|
+
return volumeB - volumeA;
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (!ignoreEdges) {
|
|
478
|
+
this.nodesToLoad.push(...this.edgeNodes);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.dispatchEvent("databasechunk", {
|
|
482
|
+
totalNodes: this.nodesToLoad.length,
|
|
483
|
+
structures: this.structures.map((s) => ({
|
|
484
|
+
id: s.id,
|
|
485
|
+
nodeCount: this.nodesToLoad.filter((nodeId) => nodeId.startsWith(s.id)).length,
|
|
486
|
+
})),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async processNodeHierarchy(structure, nodeId, parentGroup) {
|
|
491
|
+
const nodeDef = structure.json.nodes[nodeId];
|
|
492
|
+
let nodeGroup = null;
|
|
493
|
+
|
|
494
|
+
let handle = null;
|
|
495
|
+
if (nodeDef.extras?.handle) {
|
|
496
|
+
handle = `${structure.id}_${nodeDef.extras.handle}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (nodeDef.camera !== undefined) {
|
|
500
|
+
const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
|
|
501
|
+
if (nodeDef.extras) {
|
|
502
|
+
camera.userData = { ...camera.userData, ...nodeDef.extras };
|
|
503
|
+
}
|
|
504
|
+
this.scene.add(camera);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const needsGroup = this.needsGroupForNode(structure, nodeDef);
|
|
509
|
+
|
|
510
|
+
if (needsGroup) {
|
|
511
|
+
nodeGroup = new Group();
|
|
512
|
+
nodeGroup.name = nodeDef.name || `node_${nodeId}`;
|
|
513
|
+
|
|
514
|
+
if (nodeDef.extras) {
|
|
515
|
+
nodeGroup.userData = { ...nodeDef.extras };
|
|
516
|
+
if (nodeGroup.userData.handle) {
|
|
517
|
+
nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (nodeDef.matrix) {
|
|
522
|
+
nodeGroup.matrix.fromArray(nodeDef.matrix);
|
|
523
|
+
nodeGroup.matrixAutoUpdate = false;
|
|
524
|
+
} else if (nodeDef.translation || nodeDef.rotation || nodeDef.scale) {
|
|
525
|
+
const position = nodeDef.translation ? new Vector3().fromArray(nodeDef.translation) : new Vector3();
|
|
526
|
+
const quaternion = nodeDef.rotation ? new Quaternion().fromArray(nodeDef.rotation) : new Quaternion();
|
|
527
|
+
const scale = nodeDef.scale ? new Vector3().fromArray(nodeDef.scale) : new Vector3(1, 1, 1);
|
|
528
|
+
nodeGroup.matrix.compose(position, quaternion, scale);
|
|
529
|
+
nodeGroup.matrixAutoUpdate = false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (parentGroup) {
|
|
533
|
+
parentGroup.add(nodeGroup);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (nodeDef.mesh !== undefined) {
|
|
538
|
+
const nodeMatrix = new Matrix4();
|
|
539
|
+
const uniqueNodeId = `${structure.id}_${nodeId}`;
|
|
540
|
+
const meshDef = structure.json.meshes[nodeDef.mesh];
|
|
541
|
+
const geometryExtents = new Box3();
|
|
542
|
+
|
|
543
|
+
for (const primitive of meshDef.primitives) {
|
|
544
|
+
const positionAccessor = structure.json.accessors[primitive.attributes.POSITION];
|
|
545
|
+
if (positionAccessor && positionAccessor.min && positionAccessor.max) {
|
|
546
|
+
const primitiveBox = new Box3(
|
|
547
|
+
new Vector3().fromArray(positionAccessor.min),
|
|
548
|
+
new Vector3().fromArray(positionAccessor.max)
|
|
549
|
+
);
|
|
550
|
+
geometryExtents.union(primitiveBox);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
let isEdge = false;
|
|
555
|
+
if (meshDef.primitives[0].material !== undefined) {
|
|
556
|
+
const material = structure.json.materials[meshDef.primitives[0].material];
|
|
557
|
+
if (material?.name === "edges") {
|
|
558
|
+
isEdge = true;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!isEdge) {
|
|
563
|
+
this.nodesToLoad.push(uniqueNodeId);
|
|
564
|
+
} else {
|
|
565
|
+
this.edgeNodes.push(uniqueNodeId);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
this.nodes.set(uniqueNodeId, {
|
|
569
|
+
position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
|
|
570
|
+
nodeIndex: nodeId,
|
|
571
|
+
meshIndex: nodeDef.mesh,
|
|
572
|
+
loaded: false,
|
|
573
|
+
loading: false,
|
|
574
|
+
object: null,
|
|
575
|
+
group: nodeGroup || parentGroup,
|
|
576
|
+
structure,
|
|
577
|
+
extras: nodeDef.extras,
|
|
578
|
+
geometryExtents,
|
|
579
|
+
handle,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (nodeDef.children) {
|
|
584
|
+
for (const childId of nodeDef.children) {
|
|
585
|
+
await this.processNodeHierarchy(structure, childId, nodeGroup || parentGroup);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return nodeGroup;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
needsGroupForNode(structure, nodeDef) {
|
|
593
|
+
const hasTransforms = nodeDef.matrix || nodeDef.translation || nodeDef.rotation || nodeDef.scale;
|
|
594
|
+
|
|
595
|
+
const hasMultiplePrimitives =
|
|
596
|
+
nodeDef.mesh !== undefined && structure.json.meshes[nodeDef.mesh].primitives.length > 1;
|
|
597
|
+
|
|
598
|
+
return hasTransforms !== undefined || hasMultiplePrimitives;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async processNodes() {
|
|
602
|
+
const nodesToLoad = this.nodesToLoad;
|
|
603
|
+
let loadedCount = 0;
|
|
604
|
+
const totalNodes = nodesToLoad.length;
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
while (loadedCount < totalNodes) {
|
|
608
|
+
const batch = nodesToLoad.slice(loadedCount, loadedCount + this.batchSize);
|
|
609
|
+
const batchPromises = [];
|
|
610
|
+
|
|
611
|
+
for (const nodeId of batch) {
|
|
612
|
+
if (this.abortController.signal.aborted) {
|
|
613
|
+
throw new DOMException("Loading aborted", "AbortError");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const estimatedSize = await this.estimateNodeSize(nodeId);
|
|
617
|
+
|
|
618
|
+
if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
|
|
619
|
+
console.log(`Memory limit reached after loading ${loadedCount} nodes`);
|
|
620
|
+
this.dispatchEvent("geometryerror", { message: "Memory limit reached" });
|
|
621
|
+
this.dispatchEvent("update");
|
|
622
|
+
return loadedCount;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
batchPromises.push(this.loadNode(nodeId));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
await Promise.all(batchPromises);
|
|
629
|
+
loadedCount += batch.length;
|
|
630
|
+
|
|
631
|
+
this.updateMemoryIndicator();
|
|
632
|
+
this.dispatchEvent("geometryprogress", {
|
|
633
|
+
percentage: Math.round((loadedCount / totalNodes) * 100),
|
|
634
|
+
loaded: loadedCount,
|
|
635
|
+
total: totalNodes,
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const currentTime = Date.now();
|
|
639
|
+
if (currentTime - this.lastUpdateTime >= this.updateInterval) {
|
|
640
|
+
this.dispatchEvent("update");
|
|
641
|
+
this.lastUpdateTime = currentTime;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
await new Promise((resolve) => {
|
|
645
|
+
setTimeout(resolve, 0);
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
this.dispatchEvent("geometryend", {
|
|
650
|
+
totalLoaded: loadedCount,
|
|
651
|
+
totalNodes,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return loadedCount;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
this.dispatchEvent("geometryerror", { error });
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async loadNodes() {
|
|
662
|
+
console.time("process nodes");
|
|
663
|
+
await this.processNodes();
|
|
664
|
+
console.timeEnd("process nodes");
|
|
665
|
+
|
|
666
|
+
console.time("optimize scene");
|
|
667
|
+
await this.optimizeScene();
|
|
668
|
+
console.timeEnd("optimize scene");
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
cleanupPartialLoad() {
|
|
672
|
+
this.nodesToLoad.forEach((nodeId) => {
|
|
673
|
+
const node = this.nodes.get(nodeId);
|
|
674
|
+
if (node && node.loading) {
|
|
675
|
+
this.unloadNode(nodeId);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
createDefaultMaterial() {
|
|
681
|
+
if (this.currentPrimitiveMode === GL_CONSTANTS.POINTS) {
|
|
682
|
+
return new PointsMaterial({
|
|
683
|
+
color: new Color(0x808080),
|
|
684
|
+
size: 0.05,
|
|
685
|
+
sizeAttenuation: true,
|
|
686
|
+
alphaTest: 0.5,
|
|
687
|
+
transparent: true,
|
|
688
|
+
vertexColors: false,
|
|
689
|
+
blending: NormalBlending,
|
|
690
|
+
depthWrite: false,
|
|
691
|
+
depthTest: true,
|
|
692
|
+
});
|
|
693
|
+
} else {
|
|
694
|
+
return new MeshStandardMaterial({
|
|
695
|
+
color: 0x808080,
|
|
696
|
+
metalness: 0.0,
|
|
697
|
+
roughness: 1.0,
|
|
698
|
+
side: DoubleSide,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async estimateNodeSize(nodeId) {
|
|
704
|
+
const node = this.nodes.get(nodeId);
|
|
705
|
+
if (!node) return 0;
|
|
706
|
+
return await node.structure.estimateNodeSize(node.meshIndex);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
getTotalGeometryExtent() {
|
|
710
|
+
const totalExtent = new Box3();
|
|
711
|
+
|
|
712
|
+
for (const node of this.nodes.values()) {
|
|
713
|
+
if (!node.geometryExtents) continue;
|
|
714
|
+
|
|
715
|
+
if (node.object && this.hiddenHandles.has(node.object.userData.handle)) continue;
|
|
716
|
+
|
|
717
|
+
const transformedBox = node.geometryExtents.clone();
|
|
718
|
+
|
|
719
|
+
if (node.group && node.group.matrix) {
|
|
720
|
+
transformedBox.applyMatrix4(node.group.matrix);
|
|
721
|
+
|
|
722
|
+
if (node.group.parent && node.group.parent.matrix) {
|
|
723
|
+
transformedBox.applyMatrix4(node.group.parent.matrix);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
totalExtent.union(transformedBox);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return totalExtent;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
loadCamera(structure, cameraIndex, nodeDef) {
|
|
733
|
+
const cameraDef = structure.getJson().cameras[cameraIndex];
|
|
734
|
+
const params = cameraDef[cameraDef.type];
|
|
735
|
+
|
|
736
|
+
let camera;
|
|
737
|
+
if (cameraDef.type === "perspective") {
|
|
738
|
+
camera = new PerspectiveCamera(
|
|
739
|
+
MathUtils.radToDeg(params.yfov),
|
|
740
|
+
params.aspectRatio || 1,
|
|
741
|
+
params.znear || 1,
|
|
742
|
+
params.zfar || 2e6
|
|
743
|
+
);
|
|
744
|
+
} else if (cameraDef.type === "orthographic") {
|
|
745
|
+
camera = new OrthographicCamera(
|
|
746
|
+
params.xmag / -2,
|
|
747
|
+
params.xmag / 2,
|
|
748
|
+
params.ymag / 2,
|
|
749
|
+
params.ymag / -2,
|
|
750
|
+
params.znear,
|
|
751
|
+
params.zfar
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (nodeDef.matrix) {
|
|
756
|
+
camera.matrix.fromArray(nodeDef.matrix);
|
|
757
|
+
camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
|
|
758
|
+
} else {
|
|
759
|
+
if (nodeDef.translation) {
|
|
760
|
+
camera.position.fromArray(nodeDef.translation);
|
|
761
|
+
}
|
|
762
|
+
if (nodeDef.rotation) {
|
|
763
|
+
camera.quaternion.fromArray(nodeDef.rotation);
|
|
764
|
+
}
|
|
765
|
+
if (nodeDef.scale) {
|
|
766
|
+
camera.scale.fromArray(nodeDef.scale);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return camera;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
clearNodesToLoad() {
|
|
774
|
+
this.nodesToLoad = [];
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async addStructure(loadController) {
|
|
778
|
+
const structure = new GltfStructure();
|
|
779
|
+
await structure.initialize(loadController);
|
|
780
|
+
this.structures.push(structure);
|
|
781
|
+
return structure;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
removeOptimization() {
|
|
785
|
+
this.originalObjects.forEach((obj) => (obj.visible = true));
|
|
786
|
+
|
|
787
|
+
const disposeMerged = (obj) => {
|
|
788
|
+
if (obj.parent) {
|
|
789
|
+
obj.parent.remove(obj);
|
|
790
|
+
}
|
|
791
|
+
if (obj.geometry) {
|
|
792
|
+
obj.geometry.dispose();
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
if (this.structureGroups) {
|
|
797
|
+
for (const group of this.structureGroups.values()) {
|
|
798
|
+
group.meshes.forEach(disposeMerged);
|
|
799
|
+
group.lines.forEach(disposeMerged);
|
|
800
|
+
group.lineSegments.forEach(disposeMerged);
|
|
801
|
+
group.meshes.clear();
|
|
802
|
+
group.lines.clear();
|
|
803
|
+
group.lineSegments.clear();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
this.optimizedOriginalMap.clear();
|
|
807
|
+
this.mergedMesh.clear();
|
|
808
|
+
this.mergedLines.clear();
|
|
809
|
+
this.mergedLineSegments.clear();
|
|
810
|
+
this.originalObjects.clear();
|
|
811
|
+
this.originalObjectsToSelection.clear();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
clear() {
|
|
815
|
+
// Clear all structures
|
|
816
|
+
this.structures.forEach((structure) => {
|
|
817
|
+
if (structure) {
|
|
818
|
+
structure.clear();
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
this.structures = [];
|
|
822
|
+
|
|
823
|
+
// Clear all nodes and unload their objects
|
|
824
|
+
this.nodes.forEach((node) => {
|
|
825
|
+
if (node.object) {
|
|
826
|
+
if (node.object.parent) {
|
|
827
|
+
node.object.parent.remove(node.object);
|
|
828
|
+
}
|
|
829
|
+
if (node.object.geometry) {
|
|
830
|
+
node.object.geometry.dispose();
|
|
831
|
+
}
|
|
832
|
+
if (node.object.material) {
|
|
833
|
+
if (Array.isArray(node.object.material)) {
|
|
834
|
+
node.object.material.forEach((material) => material.dispose());
|
|
835
|
+
} else {
|
|
836
|
+
node.object.material.dispose();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
this.nodes.clear();
|
|
842
|
+
|
|
843
|
+
// Clear all loaded meshes
|
|
844
|
+
this.loadedMeshes.forEach((mesh) => {
|
|
845
|
+
if (mesh.geometry) mesh.geometry.dispose();
|
|
846
|
+
if (mesh.material) {
|
|
847
|
+
if (Array.isArray(mesh.material)) {
|
|
848
|
+
mesh.material.forEach((material) => material.dispose());
|
|
849
|
+
} else {
|
|
850
|
+
mesh.material.dispose();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
this.loadedMeshes.clear();
|
|
855
|
+
|
|
856
|
+
// Clear all structure roots and their children
|
|
857
|
+
this.structureRoots.forEach((rootGroup) => {
|
|
858
|
+
if (rootGroup) {
|
|
859
|
+
rootGroup.traverse((child) => {
|
|
860
|
+
if (child.geometry) child.geometry.dispose();
|
|
861
|
+
if (child.material) {
|
|
862
|
+
if (Array.isArray(child.material)) {
|
|
863
|
+
child.material.forEach((material) => material.dispose());
|
|
864
|
+
} else {
|
|
865
|
+
child.material.dispose();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
if (rootGroup.parent) {
|
|
870
|
+
rootGroup.parent.remove(rootGroup);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
this.structureRoots.clear();
|
|
875
|
+
|
|
876
|
+
// Clear all optimized objects
|
|
877
|
+
this.mergedMesh.forEach((mesh) => {
|
|
878
|
+
if (mesh.geometry) mesh.geometry.dispose();
|
|
879
|
+
if (mesh.material) {
|
|
880
|
+
if (Array.isArray(mesh.material)) {
|
|
881
|
+
mesh.material.forEach((material) => material.dispose());
|
|
882
|
+
} else {
|
|
883
|
+
mesh.material.dispose();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (mesh.parent) mesh.parent.remove(mesh);
|
|
887
|
+
});
|
|
888
|
+
this.mergedMesh.clear();
|
|
889
|
+
|
|
890
|
+
this.mergedLines.forEach((line) => {
|
|
891
|
+
if (line.geometry) line.geometry.dispose();
|
|
892
|
+
if (line.material) line.material.dispose();
|
|
893
|
+
if (line.parent) line.parent.remove(line);
|
|
894
|
+
});
|
|
895
|
+
this.mergedLines.clear();
|
|
896
|
+
|
|
897
|
+
this.mergedLineSegments.forEach((lineSegment) => {
|
|
898
|
+
if (lineSegment.geometry) lineSegment.geometry.dispose();
|
|
899
|
+
if (lineSegment.material) lineSegment.material.dispose();
|
|
900
|
+
if (lineSegment.parent) lineSegment.parent.remove(lineSegment);
|
|
901
|
+
});
|
|
902
|
+
this.mergedLineSegments.clear();
|
|
903
|
+
|
|
904
|
+
this.mergedPoints.forEach((points) => {
|
|
905
|
+
if (points.geometry) points.geometry.dispose();
|
|
906
|
+
if (points.material) points.material.dispose();
|
|
907
|
+
if (points.parent) points.parent.remove(points);
|
|
908
|
+
});
|
|
909
|
+
this.mergedPoints.clear();
|
|
910
|
+
|
|
911
|
+
// Clear all caches
|
|
912
|
+
this.geometryCache.clear();
|
|
913
|
+
this.materialCache.clear();
|
|
914
|
+
this.textureCache.clear();
|
|
915
|
+
this.loadedMaterials.clear();
|
|
916
|
+
|
|
917
|
+
// Clear all maps and sets
|
|
918
|
+
this.nodesToLoad = [];
|
|
919
|
+
this.handleToObjects.clear();
|
|
920
|
+
this.originalObjects.clear();
|
|
921
|
+
this.originalObjectsToSelection.clear();
|
|
922
|
+
this.optimizedOriginalMap.clear();
|
|
923
|
+
this.handleToOptimizedObjects.clear();
|
|
924
|
+
this.hiddenHandles.clear();
|
|
925
|
+
this.newOptimizedObjects.clear();
|
|
926
|
+
this.oldOptimizeObjects.clear();
|
|
927
|
+
this.isolatedObjects = [];
|
|
928
|
+
|
|
929
|
+
// Reset counters and state
|
|
930
|
+
this.totalLoadedObjects = 0;
|
|
931
|
+
this.lastUpdateTime = 0;
|
|
932
|
+
this.currentMemoryUsage = 0;
|
|
933
|
+
this.loadedGeometrySize = 0;
|
|
934
|
+
|
|
935
|
+
this.abortController = new AbortController();
|
|
936
|
+
this.updateMemoryIndicator();
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
setStructureTransform(structureId, matrix) {
|
|
940
|
+
const rootGroup = this.structureRoots.get(structureId);
|
|
941
|
+
if (rootGroup) {
|
|
942
|
+
rootGroup.matrix.copy(matrix);
|
|
943
|
+
rootGroup.matrix.decompose(rootGroup.position, rootGroup.quaternion, rootGroup.scale);
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
getStructureRootGroup(structureId) {
|
|
950
|
+
return this.structureRoots.get(structureId);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
addEventListener(event, handler) {
|
|
954
|
+
if (this.eventHandlers[event]) {
|
|
955
|
+
this.eventHandlers[event].push(handler);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
removeEventListener(event, handler) {
|
|
960
|
+
if (this.eventHandlers[event]) {
|
|
961
|
+
this.eventHandlers[event] = this.eventHandlers[event].filter((h) => h !== handler);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
dispatchEvent(event, data) {
|
|
966
|
+
if (this.eventHandlers[event]) {
|
|
967
|
+
this.eventHandlers[event].forEach((handler) => handler(data));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
registerObjectWithHandle(object, handle) {
|
|
972
|
+
if (!handle) return;
|
|
973
|
+
|
|
974
|
+
const fullHandle = object.userData.handle;
|
|
975
|
+
if (!this.handleToObjects.has(fullHandle)) {
|
|
976
|
+
this.handleToObjects.set(fullHandle, new Set());
|
|
977
|
+
}
|
|
978
|
+
this.handleToObjects.get(fullHandle).add(object);
|
|
979
|
+
|
|
980
|
+
object.userData.structureId = object.userData.handle.split("_")[0];
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
getObjectsByHandle(handle) {
|
|
984
|
+
if (!handle) return [];
|
|
985
|
+
return Array.from(this.handleToObjects.get(handle) || []);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
getHandlesByObjects(objects) {
|
|
989
|
+
if (!objects.length) return [];
|
|
990
|
+
const handles = new Set();
|
|
991
|
+
objects.forEach((obj) => {
|
|
992
|
+
if (this.originalObjects.has(obj)) handles.add(obj.userData.handle);
|
|
993
|
+
});
|
|
994
|
+
return Array.from(handles);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
getMaterialId(material, index) {
|
|
998
|
+
const props = {
|
|
999
|
+
type: material.type,
|
|
1000
|
+
color: material.color?.getHex(),
|
|
1001
|
+
map: material.map?.uuid,
|
|
1002
|
+
transparent: material.transparent,
|
|
1003
|
+
opacity: material.opacity,
|
|
1004
|
+
side: material.side,
|
|
1005
|
+
index: index ? 1 : 0,
|
|
1006
|
+
};
|
|
1007
|
+
return JSON.stringify(props);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
addToMaterialGroup(object, groupsMap, optimizeGroupList) {
|
|
1011
|
+
const VERTEX_LIMIT = 100_000;
|
|
1012
|
+
const INDEX_LIMIT = 100_000;
|
|
1013
|
+
|
|
1014
|
+
const objectGeometryVertexCount = object.geometry.attributes.position.count;
|
|
1015
|
+
const objectGeometryIndexCount = object.geometry.index ? object.geometry.index.count : 0;
|
|
1016
|
+
|
|
1017
|
+
const material = object.material;
|
|
1018
|
+
let materialId = this.getMaterialId(material, object.geometry.index !== null);
|
|
1019
|
+
|
|
1020
|
+
let group;
|
|
1021
|
+
if (!groupsMap.has(materialId)) {
|
|
1022
|
+
group = {
|
|
1023
|
+
material,
|
|
1024
|
+
objects: [object],
|
|
1025
|
+
totalVertices: objectGeometryVertexCount,
|
|
1026
|
+
totalIndices: objectGeometryIndexCount,
|
|
1027
|
+
};
|
|
1028
|
+
groupsMap.set(materialId, group);
|
|
1029
|
+
optimizeGroupList.push(group);
|
|
1030
|
+
} else {
|
|
1031
|
+
group = groupsMap.get(materialId);
|
|
1032
|
+
if (
|
|
1033
|
+
group.totalVertices + objectGeometryVertexCount > VERTEX_LIMIT ||
|
|
1034
|
+
group.totalIndices + objectGeometryIndexCount > INDEX_LIMIT
|
|
1035
|
+
) {
|
|
1036
|
+
const newGroup = {
|
|
1037
|
+
material,
|
|
1038
|
+
objects: [object],
|
|
1039
|
+
totalVertices: objectGeometryVertexCount,
|
|
1040
|
+
totalIndices: objectGeometryIndexCount,
|
|
1041
|
+
};
|
|
1042
|
+
materialId = this.getMaterialId(material, object.geometry.index !== null);
|
|
1043
|
+
groupsMap.set(materialId, newGroup);
|
|
1044
|
+
optimizeGroupList.push(newGroup);
|
|
1045
|
+
} else {
|
|
1046
|
+
group.objects.push(object);
|
|
1047
|
+
group.totalVertices += objectGeometryVertexCount;
|
|
1048
|
+
group.totalIndices += objectGeometryIndexCount;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
this.originalObjects.add(object);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
optimizeScene() {
|
|
1056
|
+
this.originalObjects.clear();
|
|
1057
|
+
this.originalObjectsToSelection.clear();
|
|
1058
|
+
const structureGroups = new Map();
|
|
1059
|
+
|
|
1060
|
+
this.scene.traverse((object) => {
|
|
1061
|
+
if (object.userData.structureId) {
|
|
1062
|
+
const structureId = object.userData.structureId;
|
|
1063
|
+
if (!structureGroups.has(structureId)) {
|
|
1064
|
+
structureGroups.set(structureId, {
|
|
1065
|
+
mapMeshes: new Map(),
|
|
1066
|
+
mapLines: new Map(),
|
|
1067
|
+
mapLineSegments: new Map(),
|
|
1068
|
+
mapPoints: new Map(),
|
|
1069
|
+
|
|
1070
|
+
meshes: [],
|
|
1071
|
+
lines: [],
|
|
1072
|
+
lineSegments: [],
|
|
1073
|
+
points: [],
|
|
1074
|
+
rootGroup: this.structureRoots.get(structureId),
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const group = structureGroups.get(structureId);
|
|
1079
|
+
|
|
1080
|
+
if (object instanceof Mesh) {
|
|
1081
|
+
this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
|
|
1082
|
+
} else if (object instanceof LineSegments) {
|
|
1083
|
+
this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
|
|
1084
|
+
} else if (object instanceof Line) {
|
|
1085
|
+
this.addToMaterialGroup(object, group.mapLines, group.lines);
|
|
1086
|
+
} else if (object instanceof Points) {
|
|
1087
|
+
this.addToMaterialGroup(object, group.mapPoints, group.points);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
for (const group of structureGroups.values()) {
|
|
1093
|
+
group.mapMeshes.clear();
|
|
1094
|
+
group.mapLines.clear();
|
|
1095
|
+
group.mapLineSegments.clear();
|
|
1096
|
+
group.mapPoints.clear();
|
|
1097
|
+
|
|
1098
|
+
this.mergeMeshGroups(group.meshes, group.rootGroup);
|
|
1099
|
+
this.mergeLineGroups(group.lines, group.rootGroup);
|
|
1100
|
+
this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
|
|
1101
|
+
this.mergePointsGroups(group.points, group.rootGroup);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
this.originalObjects.forEach((obj) => {
|
|
1105
|
+
obj.visible = false;
|
|
1106
|
+
if (!(obj instanceof Points) && !obj.userData.isEdge) {
|
|
1107
|
+
this.originalObjectsToSelection.add(obj);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
this.dispatchEvent("update");
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
mergeMeshGroups(materialGroups, rootGroup) {
|
|
1115
|
+
for (const group of materialGroups) {
|
|
1116
|
+
try {
|
|
1117
|
+
const geometries = [];
|
|
1118
|
+
const handles = new Set();
|
|
1119
|
+
const optimizedObjects = [];
|
|
1120
|
+
|
|
1121
|
+
for (const mesh of group.objects) {
|
|
1122
|
+
const geometry = mesh.geometry.clone();
|
|
1123
|
+
mesh.updateWorldMatrix(true, false);
|
|
1124
|
+
geometry.applyMatrix4(mesh.matrixWorld);
|
|
1125
|
+
geometries.push(geometry);
|
|
1126
|
+
|
|
1127
|
+
optimizedObjects.push(mesh);
|
|
1128
|
+
handles.add(mesh.userData.handle);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const mergedObjects = [];
|
|
1132
|
+
|
|
1133
|
+
if (geometries.length > 0) {
|
|
1134
|
+
const mergedGeometry = mergeGeometries(geometries);
|
|
1135
|
+
if (this.useVAO) {
|
|
1136
|
+
this.createVAO(mergedGeometry);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const mergedMesh = new Mesh(mergedGeometry, group.material);
|
|
1140
|
+
rootGroup.add(mergedMesh);
|
|
1141
|
+
|
|
1142
|
+
this.mergedMesh.add(mergedMesh);
|
|
1143
|
+
this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
|
|
1144
|
+
|
|
1145
|
+
mergedObjects.push(mergedMesh);
|
|
1146
|
+
|
|
1147
|
+
geometries.forEach((geometry) => {
|
|
1148
|
+
geometry.dispose();
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
handles.forEach((handle) => {
|
|
1153
|
+
if (this.handleToOptimizedObjects.has(handle)) {
|
|
1154
|
+
const existingObjects = this.handleToOptimizedObjects.get(handle);
|
|
1155
|
+
existingObjects.push(...mergedObjects);
|
|
1156
|
+
this.handleToOptimizedObjects.set(handle, existingObjects);
|
|
1157
|
+
} else {
|
|
1158
|
+
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
console.error("Failed to merge meshes for material:", error);
|
|
1163
|
+
group.objects.forEach((mesh) => {
|
|
1164
|
+
mesh.visible = true;
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
mergeLineGroups(materialGroups, rootGroup) {
|
|
1171
|
+
for (const group of materialGroups) {
|
|
1172
|
+
if (group.objects.length === 0) continue;
|
|
1173
|
+
|
|
1174
|
+
const handles = new Set();
|
|
1175
|
+
let totalVertices = 0;
|
|
1176
|
+
group.objects.map((line) => {
|
|
1177
|
+
handles.add(line.userData.handle);
|
|
1178
|
+
totalVertices += line.geometry.attributes.position.count;
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
const positions = new Float32Array(totalVertices * 3);
|
|
1182
|
+
let posOffset = 0;
|
|
1183
|
+
|
|
1184
|
+
const indices = [];
|
|
1185
|
+
let vertexOffset = 0;
|
|
1186
|
+
|
|
1187
|
+
group.objects.forEach((line) => {
|
|
1188
|
+
const geometry = line.geometry;
|
|
1189
|
+
const positionAttr = geometry.attributes.position;
|
|
1190
|
+
const vertexCount = positionAttr.count;
|
|
1191
|
+
|
|
1192
|
+
line.updateWorldMatrix(true, false);
|
|
1193
|
+
const matrix = line.matrixWorld;
|
|
1194
|
+
const vector = new Vector3();
|
|
1195
|
+
|
|
1196
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
1197
|
+
vector.fromBufferAttribute(positionAttr, i);
|
|
1198
|
+
vector.applyMatrix4(matrix);
|
|
1199
|
+
positions[posOffset++] = vector.x;
|
|
1200
|
+
positions[posOffset++] = vector.y;
|
|
1201
|
+
positions[posOffset++] = vector.z;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
for (let i = 0; i < vertexCount - 1; i++) {
|
|
1205
|
+
indices.push(vertexOffset + i, vertexOffset + i + 1);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
vertexOffset += vertexCount;
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
const geometry = new BufferGeometry();
|
|
1212
|
+
geometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
1213
|
+
geometry.setIndex(indices);
|
|
1214
|
+
geometry.computeBoundingSphere();
|
|
1215
|
+
geometry.computeBoundingBox();
|
|
1216
|
+
|
|
1217
|
+
const mergedLine = new LineSegments(geometry, group.material);
|
|
1218
|
+
const mergedObjects = [mergedLine];
|
|
1219
|
+
if (this.useVAO) {
|
|
1220
|
+
this.createVAO(mergedLine);
|
|
1221
|
+
}
|
|
1222
|
+
rootGroup.add(mergedLine);
|
|
1223
|
+
this.mergedLines.add(mergedLine);
|
|
1224
|
+
this.optimizedOriginalMap.set(mergedLine, group.objects);
|
|
1225
|
+
|
|
1226
|
+
handles.forEach((handle) => {
|
|
1227
|
+
if (this.handleToOptimizedObjects.has(handle)) {
|
|
1228
|
+
const existingObjects = this.handleToOptimizedObjects.get(handle);
|
|
1229
|
+
existingObjects.push(...mergedObjects);
|
|
1230
|
+
this.handleToOptimizedObjects.set(handle, existingObjects);
|
|
1231
|
+
} else {
|
|
1232
|
+
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
mergeLineSegmentGroups(materialGroups, rootGroup) {
|
|
1239
|
+
for (const group of materialGroups) {
|
|
1240
|
+
try {
|
|
1241
|
+
const geometries = [];
|
|
1242
|
+
const optimizedObjects = [];
|
|
1243
|
+
const handles = new Set();
|
|
1244
|
+
|
|
1245
|
+
for (const line of group.objects) {
|
|
1246
|
+
const geometry = line.geometry.clone();
|
|
1247
|
+
line.updateWorldMatrix(true, false);
|
|
1248
|
+
geometry.applyMatrix4(line.matrixWorld);
|
|
1249
|
+
geometries.push(geometry);
|
|
1250
|
+
optimizedObjects.push(line);
|
|
1251
|
+
handles.add(line.userData.handle);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const mergedObjects = [];
|
|
1255
|
+
|
|
1256
|
+
if (geometries.length > 0) {
|
|
1257
|
+
const mergedGeometry = mergeGeometries(geometries, false);
|
|
1258
|
+
const mergedLine = new LineSegments(mergedGeometry, group.material);
|
|
1259
|
+
|
|
1260
|
+
if (this.useVAO) {
|
|
1261
|
+
this.createVAO(mergedLine);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
rootGroup.add(mergedLine);
|
|
1265
|
+
this.mergedLineSegments.add(mergedLine);
|
|
1266
|
+
this.optimizedOriginalMap.set(mergedLine, optimizedObjects);
|
|
1267
|
+
mergedObjects.push(mergedLine);
|
|
1268
|
+
|
|
1269
|
+
geometries.forEach((geometry) => {
|
|
1270
|
+
geometry.dispose();
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
handles.forEach((handle) => {
|
|
1275
|
+
if (this.handleToOptimizedObjects.has(handle)) {
|
|
1276
|
+
const existingObjects = this.handleToOptimizedObjects.get(handle);
|
|
1277
|
+
existingObjects.push(...mergedObjects);
|
|
1278
|
+
this.handleToOptimizedObjects.set(handle, existingObjects);
|
|
1279
|
+
} else {
|
|
1280
|
+
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
console.warn("Failed to merge line segments for material:", error);
|
|
1285
|
+
group.objects.forEach((line) => {
|
|
1286
|
+
line.visible = true;
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
mergePointsGroups(materialGroups, rootGroup) {
|
|
1293
|
+
for (const group of materialGroups) {
|
|
1294
|
+
try {
|
|
1295
|
+
const geometries = [];
|
|
1296
|
+
const optimizedObjects = [];
|
|
1297
|
+
const handles = new Set();
|
|
1298
|
+
|
|
1299
|
+
for (const points of group.objects) {
|
|
1300
|
+
const geometry = points.geometry.clone();
|
|
1301
|
+
points.updateWorldMatrix(true, false);
|
|
1302
|
+
geometry.applyMatrix4(points.matrixWorld);
|
|
1303
|
+
geometries.push(geometry);
|
|
1304
|
+
optimizedObjects.push(points);
|
|
1305
|
+
handles.add(points.userData.handle);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const mergedObjects = [];
|
|
1309
|
+
|
|
1310
|
+
if (geometries.length > 0) {
|
|
1311
|
+
const mergedGeometry = mergeGeometries(geometries, false);
|
|
1312
|
+
const mergedPoints = new Points(mergedGeometry, group.material);
|
|
1313
|
+
|
|
1314
|
+
if (this.useVAO) {
|
|
1315
|
+
this.createVAO(mergedPoints);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
rootGroup.add(mergedPoints);
|
|
1319
|
+
|
|
1320
|
+
this.mergedPoints.add(mergedPoints);
|
|
1321
|
+
this.optimizedOriginalMap.set(mergedPoints, optimizedObjects);
|
|
1322
|
+
mergedObjects.push(mergedPoints);
|
|
1323
|
+
|
|
1324
|
+
geometries.forEach((geometry) => {
|
|
1325
|
+
geometry.dispose();
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
handles.forEach((handle) => {
|
|
1330
|
+
if (this.handleToOptimizedObjects.has(handle)) {
|
|
1331
|
+
const existingObjects = this.handleToOptimizedObjects.get(handle);
|
|
1332
|
+
existingObjects.push(...mergedObjects);
|
|
1333
|
+
this.handleToOptimizedObjects.set(handle, existingObjects);
|
|
1334
|
+
} else {
|
|
1335
|
+
this.handleToOptimizedObjects.set(handle, mergedObjects);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.warn("Failed to merge points for material:", error);
|
|
1340
|
+
group.objects.forEach((points) => {
|
|
1341
|
+
points.visible = true;
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
mergeInSingleSegment(structureId, rootGroup) {
|
|
1348
|
+
const lineSegmentsArray = [...this.mergedLineSegments, ...this.mergedLines].filter(
|
|
1349
|
+
(obj) => obj.userData.structureId === structureId
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
if (lineSegmentsArray.length === 0) return;
|
|
1353
|
+
|
|
1354
|
+
try {
|
|
1355
|
+
const geometriesWithIndex = [];
|
|
1356
|
+
const hasNormals = lineSegmentsArray.some((segment) => segment.geometry.attributes.normal !== undefined);
|
|
1357
|
+
|
|
1358
|
+
lineSegmentsArray.forEach((segment) => {
|
|
1359
|
+
const clonedGeometry = segment.geometry.clone();
|
|
1360
|
+
segment.updateWorldMatrix(true, false);
|
|
1361
|
+
clonedGeometry.applyMatrix4(segment.matrixWorld);
|
|
1362
|
+
|
|
1363
|
+
if (hasNormals && !clonedGeometry.attributes.normal) {
|
|
1364
|
+
clonedGeometry.computeVertexNormals();
|
|
1365
|
+
}
|
|
1366
|
+
if (!hasNormals && clonedGeometry.attributes.normal) {
|
|
1367
|
+
clonedGeometry.deleteAttribute("normal");
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const colorArray = new Float32Array(clonedGeometry.attributes.position.count * 3);
|
|
1371
|
+
for (let i = 0; i < colorArray.length; i += 3) {
|
|
1372
|
+
colorArray[i] = segment.material.color.r;
|
|
1373
|
+
colorArray[i + 1] = segment.material.color.g;
|
|
1374
|
+
colorArray[i + 2] = segment.material.color.b;
|
|
1375
|
+
}
|
|
1376
|
+
clonedGeometry.setAttribute("color", new BufferAttribute(colorArray, 3));
|
|
1377
|
+
|
|
1378
|
+
if (!clonedGeometry.index) {
|
|
1379
|
+
const indices = [];
|
|
1380
|
+
const posCount = clonedGeometry.attributes.position.count;
|
|
1381
|
+
for (let i = 0; i < posCount - 1; i += 2) {
|
|
1382
|
+
indices.push(i, i + 1);
|
|
1383
|
+
}
|
|
1384
|
+
clonedGeometry.setIndex(indices);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
geometriesWithIndex.push(clonedGeometry);
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
const finalGeometry = mergeGeometries(geometriesWithIndex, false);
|
|
1391
|
+
const material = new LineBasicMaterial({
|
|
1392
|
+
vertexColors: true,
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
if (this.useVAO) {
|
|
1396
|
+
this.createVAO(finalGeometry);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const mergedLine = new LineSegments(finalGeometry, material);
|
|
1400
|
+
mergedLine.userData.structureId = structureId;
|
|
1401
|
+
rootGroup.add(mergedLine);
|
|
1402
|
+
this.mergedLineSegments.add(mergedLine);
|
|
1403
|
+
|
|
1404
|
+
lineSegmentsArray.forEach((obj) => {
|
|
1405
|
+
if (obj.parent) {
|
|
1406
|
+
obj.parent.remove(obj);
|
|
1407
|
+
}
|
|
1408
|
+
obj.geometry.dispose();
|
|
1409
|
+
});
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
console.error("Failed to merge geometries:", error);
|
|
1412
|
+
lineSegmentsArray.forEach((obj) => {
|
|
1413
|
+
obj.visible = true;
|
|
1414
|
+
rootGroup.add(obj);
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
showOriginalObjects(objects) {
|
|
1420
|
+
objects.forEach((obj) => {
|
|
1421
|
+
if (this.originalObjects.has(obj)) {
|
|
1422
|
+
obj.visible = true;
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
hideOriginalObjects(objects) {
|
|
1428
|
+
objects.forEach((obj) => {
|
|
1429
|
+
if (this.originalObjects.has(obj)) {
|
|
1430
|
+
obj.visible = false;
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
createVAO(geometry) {
|
|
1436
|
+
if (!this.useVAO) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (geometry.attributes?.position?.count < 1000) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const gl = this.renderer.getContext();
|
|
1445
|
+
const vao = gl.createVertexArray();
|
|
1446
|
+
gl.bindVertexArray(vao);
|
|
1447
|
+
|
|
1448
|
+
for (const name in geometry.attributes) {
|
|
1449
|
+
const attribute = geometry.attributes[name];
|
|
1450
|
+
const buffer = this.renderer.properties.get(attribute).buffer;
|
|
1451
|
+
|
|
1452
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1453
|
+
gl.enableVertexAttribArray(attribute.itemSize);
|
|
1454
|
+
gl.vertexAttribPointer(attribute.itemSize, attribute.itemSize, gl.FLOAT, false, 0, 0);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (geometry.index) {
|
|
1458
|
+
const indexBuffer = this.renderer.properties.get(geometry.index).buffer;
|
|
1459
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
gl.bindVertexArray(null);
|
|
1463
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
1464
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
1465
|
+
|
|
1466
|
+
geometry.vao = vao;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
getOriginalObjectForSelect() {
|
|
1470
|
+
const optimizedOriginals = [];
|
|
1471
|
+
|
|
1472
|
+
for (const obj of this.originalObjectsToSelection) {
|
|
1473
|
+
if (this.hiddenHandles.has(obj.userData.handle)) {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
optimizedOriginals.push(obj);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
return optimizedOriginals;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
isolateObjects(handles) {
|
|
1484
|
+
if (this.hiddenHandles.size !== 0) {
|
|
1485
|
+
this.hiddenHandles.clear();
|
|
1486
|
+
this.syncHiddenObjects();
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
for (const handle of this.handleToOptimizedObjects.keys()) {
|
|
1490
|
+
if (!handles.has(handle)) {
|
|
1491
|
+
this.hiddenHandles.add(handle);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
this.syncHiddenObjects();
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
showAllHiddenObjects() {
|
|
1499
|
+
this.hiddenHandles.clear();
|
|
1500
|
+
this.syncHiddenObjects();
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
hideObjects(handles) {
|
|
1504
|
+
handles.forEach((handle) => {
|
|
1505
|
+
this.hiddenHandles.add(handle);
|
|
1506
|
+
});
|
|
1507
|
+
this.syncHiddenObjects();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
showObjects(handles) {
|
|
1511
|
+
handles.forEach((handle) => {
|
|
1512
|
+
this.hiddenHandles.delete(handle);
|
|
1513
|
+
});
|
|
1514
|
+
this.syncHiddenObjects();
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
syncHiddenObjects() {
|
|
1518
|
+
if (this.oldOptimizeObjects.size !== 0) {
|
|
1519
|
+
for (const obj of this.oldOptimizeObjects) {
|
|
1520
|
+
obj.visible = true;
|
|
1521
|
+
}
|
|
1522
|
+
this.oldOptimizeObjects.clear();
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
if (this.newOptimizedObjects.size !== 0) {
|
|
1526
|
+
for (const obj of this.newOptimizedObjects) {
|
|
1527
|
+
obj.visible = false;
|
|
1528
|
+
obj.geometry.dispose();
|
|
1529
|
+
obj.parent.remove(obj);
|
|
1530
|
+
}
|
|
1531
|
+
this.newOptimizedObjects.clear();
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
if (this.hiddenHandles.size === 0) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
this.hiddenHandles.forEach((handle) => {
|
|
1539
|
+
const objects = this.handleToOptimizedObjects.get(handle);
|
|
1540
|
+
if (objects) {
|
|
1541
|
+
objects.forEach((x) => this.oldOptimizeObjects.add(x));
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
this.oldOptimizeObjects.forEach((optimizedObject) => {
|
|
1546
|
+
optimizedObject.visible = false;
|
|
1547
|
+
|
|
1548
|
+
const originObjects = this.optimizedOriginalMap.get(optimizedObject);
|
|
1549
|
+
const updateListToOptimize = [];
|
|
1550
|
+
originObjects.forEach((obj) => {
|
|
1551
|
+
if (!this.hiddenHandles.has(obj.userData.handle)) {
|
|
1552
|
+
updateListToOptimize.push(obj);
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
const firstObject = updateListToOptimize[0];
|
|
1557
|
+
|
|
1558
|
+
if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
|
|
1559
|
+
const geometries = updateListToOptimize.map((obj) => {
|
|
1560
|
+
const geometry = obj.geometry.clone();
|
|
1561
|
+
obj.updateWorldMatrix(true, false);
|
|
1562
|
+
geometry.applyMatrix4(obj.matrixWorld);
|
|
1563
|
+
return geometry;
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
const newMergedGeometry = mergeGeometries(geometries);
|
|
1567
|
+
const mergedObject =
|
|
1568
|
+
firstObject instanceof Mesh
|
|
1569
|
+
? new Mesh(newMergedGeometry, optimizedObject.material)
|
|
1570
|
+
: new LineSegments(newMergedGeometry, optimizedObject.material);
|
|
1571
|
+
|
|
1572
|
+
mergedObject.visible = true;
|
|
1573
|
+
optimizedObject.parent.add(mergedObject);
|
|
1574
|
+
this.newOptimizedObjects.add(mergedObject);
|
|
1575
|
+
|
|
1576
|
+
geometries.forEach((geometry) => {
|
|
1577
|
+
geometry.dispose();
|
|
1578
|
+
});
|
|
1579
|
+
} else if (firstObject instanceof Line) {
|
|
1580
|
+
let totalVertices = 0;
|
|
1581
|
+
updateListToOptimize.map((line) => {
|
|
1582
|
+
totalVertices += line.geometry.attributes.position.count;
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
const positions = new Float32Array(totalVertices * 3);
|
|
1586
|
+
let posOffset = 0;
|
|
1587
|
+
|
|
1588
|
+
const indices = [];
|
|
1589
|
+
let vertexOffset = 0;
|
|
1590
|
+
|
|
1591
|
+
updateListToOptimize.forEach((line) => {
|
|
1592
|
+
const geometry = line.geometry;
|
|
1593
|
+
const positionAttr = geometry.attributes.position;
|
|
1594
|
+
const vertexCount = positionAttr.count;
|
|
1595
|
+
|
|
1596
|
+
line.updateWorldMatrix(true, false);
|
|
1597
|
+
const matrix = line.matrixWorld;
|
|
1598
|
+
const vector = new Vector3();
|
|
1599
|
+
|
|
1600
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
1601
|
+
vector.fromBufferAttribute(positionAttr, i);
|
|
1602
|
+
vector.applyMatrix4(matrix);
|
|
1603
|
+
positions[posOffset++] = vector.x;
|
|
1604
|
+
positions[posOffset++] = vector.y;
|
|
1605
|
+
positions[posOffset++] = vector.z;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
for (let i = 0; i < vertexCount - 1; i++) {
|
|
1609
|
+
indices.push(vertexOffset + i, vertexOffset + i + 1);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
vertexOffset += vertexCount;
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
const geometry = new BufferGeometry();
|
|
1616
|
+
geometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
1617
|
+
geometry.setIndex(indices);
|
|
1618
|
+
geometry.computeBoundingSphere();
|
|
1619
|
+
geometry.computeBoundingBox();
|
|
1620
|
+
|
|
1621
|
+
const mergedLine = new LineSegments(geometry, optimizedObject.material);
|
|
1622
|
+
mergedLine.visible = true;
|
|
1623
|
+
optimizedObject.parent.add(mergedLine);
|
|
1624
|
+
this.newOptimizedObjects.add(mergedLine);
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
}
|