@inweb/viewer-three 26.7.6 → 26.8.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.
@@ -1,11 +1,11 @@
1
- import { LineBasicMaterial, MeshBasicMaterial, Object3D, WebGLRenderTarget } from "three";
1
+ import { LineBasicMaterial, MeshPhongMaterial, Object3D, WebGLRenderTarget } from "three";
2
2
  import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
3
3
  import { IComponent, ResizeEvent } from "@inweb/viewer-core";
4
4
  import { Viewer } from "../Viewer";
5
5
  export declare class HighlighterComponent implements IComponent {
6
6
  protected viewer: Viewer;
7
7
  renderTarget: WebGLRenderTarget;
8
- highlightMaterial: MeshBasicMaterial;
8
+ highlightMaterial: MeshPhongMaterial;
9
9
  outlineMaterial: LineMaterial;
10
10
  highlightLineMaterial: LineBasicMaterial;
11
11
  highlightLineGlowMaterial: LineMaterial;
@@ -1,10 +1,8 @@
1
- import { Group } from "three";
2
1
  import { ILoader, LoadParams } from "@inweb/viewer-core";
3
2
  import { Viewer } from "../Viewer";
4
3
  import { DynamicGltfLoader } from "./DynamicGltfLoader/DynamicGltfLoader.js";
5
4
  export declare class GLTFCloudDynamicLoader implements ILoader {
6
5
  viewer: Viewer;
7
- scene: Group;
8
6
  gltfLoader: DynamicGltfLoader;
9
7
  requestId: number;
10
8
  constructor(viewer: Viewer);
@@ -11,7 +11,7 @@ import { ILoadersRegistry } from "@inweb/viewer-core";
11
11
  *
12
12
  * The loader should do:
13
13
  *
14
- * - Load scene from file. The scene must be a Three.js object of type `Object3D` or a descendant of it.
14
+ * - Load raw data from file and convert it to the `Three.js` scene.
15
15
  * - Add scene to the viewer `scene`.
16
16
  * - Create `ModelImpl` for the scene and to the viewer `models` list.
17
17
  * - Synchronize viewer options and overlay.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "26.7.6",
3
+ "version": "26.8.0",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,10 +35,10 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~26.7.6",
39
- "@inweb/eventemitter2": "~26.7.6",
40
- "@inweb/markup": "~26.7.6",
41
- "@inweb/viewer-core": "~26.7.6"
38
+ "@inweb/client": "~26.8.0",
39
+ "@inweb/eventemitter2": "~26.8.0",
40
+ "@inweb/markup": "~26.8.0",
41
+ "@inweb/viewer-core": "~26.8.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/three": "^0.173.0",
@@ -30,6 +30,7 @@ export function setSelected(viewer: Viewer, handles: string[] = []): void {
30
30
 
31
31
  viewer.models.forEach((model) => {
32
32
  const objects = model.getObjectsByHandles(handles);
33
+ model.showObjects(objects);
33
34
  selection.select(objects, model);
34
35
  });
35
36
 
@@ -80,9 +80,9 @@ commands.registerCommand("applyModelTransform", applyModelTransform);
80
80
  commands.registerCommand("clearMarkup", clearMarkup);
81
81
  commands.registerCommand("clearSelected", clearSelected);
82
82
  commands.registerCommand("clearSlices", clearSlices);
83
+ commands.registerCommand("collect", collect);
83
84
  commands.registerCommand("createPreview", createPreview);
84
85
  commands.registerCommand("explode", explode);
85
- commands.registerCommand("collect", collect);
86
86
  commands.registerCommand("getDefaultViewPositions", getDefaultViewPositions);
87
87
  commands.registerCommand("getModels", getModels);
88
88
  commands.registerCommand("getSelected", getSelected);
@@ -25,7 +25,7 @@ import {
25
25
  Color,
26
26
  EdgesGeometry,
27
27
  LineBasicMaterial,
28
- MeshBasicMaterial,
28
+ MeshPhongMaterial,
29
29
  Object3D,
30
30
  RGBAFormat,
31
31
  UnsignedByteType,
@@ -43,7 +43,7 @@ import { HighlighterUtils } from "./HighlighterUtils";
43
43
  export class HighlighterComponent implements IComponent {
44
44
  protected viewer: Viewer;
45
45
  public renderTarget: WebGLRenderTarget;
46
- public highlightMaterial: MeshBasicMaterial;
46
+ public highlightMaterial: MeshPhongMaterial;
47
47
  public outlineMaterial: LineMaterial;
48
48
  public highlightLineMaterial: LineBasicMaterial;
49
49
  public highlightLineGlowMaterial: LineMaterial;
@@ -76,6 +76,8 @@ export class HighlighterComponent implements IComponent {
76
76
  }
77
77
 
78
78
  highlight(objects: Object3D | Object3D[]) {
79
+ const { edgesVisibility } = this.viewer.options;
80
+
79
81
  if (!Array.isArray(objects)) objects = [objects];
80
82
  if (!objects.length) return;
81
83
 
@@ -93,25 +95,27 @@ export class HighlighterComponent implements IComponent {
93
95
  wireframe.position.copy(object.position);
94
96
  wireframe.rotation.copy(object.rotation);
95
97
  wireframe.scale.copy(object.scale);
98
+ wireframe.visible = edgesVisibility;
96
99
 
97
100
  object.parent.add(wireframe);
101
+ object.userData.highlightWireframe = wireframe;
98
102
 
99
- object.userData.highlightwireframe = wireframe;
100
103
  object.userData.originalMaterial = object.material;
101
104
  object.material = this.highlightLineMaterial;
102
105
  object.isHighlighted = true;
103
106
  } else if (object.isMesh) {
104
- const edgesGeometry = new EdgesGeometry(object.geometry, 30);
107
+ const edgesGeometry = new EdgesGeometry(object.geometry, 60);
105
108
  const lineGeometry = new LineSegmentsGeometry().fromEdgesGeometry(edgesGeometry);
106
109
 
107
110
  const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
108
111
  wireframe.position.copy(object.position);
109
112
  wireframe.rotation.copy(object.rotation);
110
113
  wireframe.scale.copy(object.scale);
114
+ wireframe.visible = edgesVisibility;
111
115
 
112
116
  object.parent.add(wireframe);
117
+ object.userData.highlightWireframe = wireframe;
113
118
 
114
- object.userData.highlightwireframe = wireframe;
115
119
  object.userData.originalMaterial = object.material;
116
120
  object.material = this.highlightMaterial;
117
121
  object.isHighlighted = true;
@@ -128,29 +132,35 @@ export class HighlighterComponent implements IComponent {
128
132
 
129
133
  object.isHighlighted = false;
130
134
  object.material = object.userData.originalMaterial;
131
- object.userData.highlightwireframe.removeFromParent();
135
+ object.userData.highlightWireframe.removeFromParent();
132
136
 
133
137
  delete object.userData.originalMaterial;
134
- delete object.userData.highlightwireframe;
138
+ delete object.userData.highlightWireframe;
135
139
  });
136
140
  }
137
141
 
138
142
  geometryEnd = () => {
139
- const { facesColor, facesTransparancy, edgesColor } = this.viewer.options;
143
+ const { facesColor, facesTransparancy, edgesColor, edgesOverlap, facesOverlap } = this.viewer.options;
140
144
 
141
- this.highlightMaterial = new MeshBasicMaterial({
145
+ this.highlightMaterial = new MeshPhongMaterial({
142
146
  color: new Color(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255),
143
147
  transparent: true,
144
148
  opacity: (255 - facesTransparancy) / 255,
145
- depthTest: false,
146
- depthWrite: false,
149
+ depthTest: !facesOverlap,
150
+ depthWrite: !facesOverlap,
151
+ specular: 0x222222,
152
+ shininess: 10,
153
+ reflectivity: 0.05,
154
+ polygonOffset: true,
155
+ polygonOffsetFactor: 1,
156
+ polygonOffsetUnits: 1,
147
157
  });
148
158
 
149
159
  this.outlineMaterial = new LineMaterial({
150
160
  color: new Color(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255),
151
161
  linewidth: 1.5,
152
- depthTest: false,
153
- depthWrite: false,
162
+ depthTest: !edgesOverlap,
163
+ depthWrite: !edgesOverlap,
154
164
  resolution: new Vector2(window.innerWidth, window.innerHeight),
155
165
  });
156
166
 
@@ -165,21 +175,33 @@ export class HighlighterComponent implements IComponent {
165
175
  linewidth: 5,
166
176
  transparent: true,
167
177
  opacity: 0.8,
168
- depthTest: true,
169
- depthWrite: true,
178
+ depthTest: !edgesOverlap,
179
+ depthWrite: !edgesOverlap,
170
180
  resolution: new Vector2(window.innerWidth, window.innerHeight),
171
181
  });
172
182
  };
173
183
 
174
184
  optionsChange = () => {
175
- const { facesColor, facesTransparancy, edgesColor } = this.viewer.options;
185
+ const { facesColor, facesTransparancy, edgesColor, edgesVisibility, edgesOverlap, facesOverlap } =
186
+ this.viewer.options;
176
187
 
177
188
  this.highlightMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
178
189
  this.highlightMaterial.opacity = (255 - facesTransparancy) / 255;
190
+ this.highlightMaterial.depthTest = !facesOverlap;
191
+ this.highlightMaterial.depthWrite = !facesOverlap;
192
+
179
193
  this.outlineMaterial.color.setRGB(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255);
194
+ this.outlineMaterial.depthTest = !edgesOverlap;
195
+ this.outlineMaterial.depthWrite = !edgesOverlap;
196
+
180
197
  this.highlightLineMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
181
198
  this.highlightLineGlowMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
182
199
 
200
+ this.viewer.selected.forEach((selected) => {
201
+ const wireframe = selected.userData.highlightWireframe;
202
+ if (wireframe) wireframe.visible = edgesVisibility;
203
+ });
204
+
183
205
  this.viewer.update();
184
206
  };
185
207
 
@@ -36,20 +36,10 @@ export class LightComponent implements IComponent {
36
36
  constructor(viewer: Viewer) {
37
37
  this.viewer = viewer;
38
38
 
39
- this.ambientLight = new AmbientLight(0xffffff, 1);
40
- this.viewer.scene.add(this.ambientLight);
41
-
42
- this.directionalLight = new DirectionalLight(0xffffff, 1);
43
- this.directionalLight.position.set(0.5, 0, 0.866); // ~60º
44
- this.viewer.scene.add(this.directionalLight);
45
-
39
+ this.ambientLight = new AmbientLight(0xffffff, 1.0);
40
+ this.directionalLight = new DirectionalLight(0xffffff, 1.0);
46
41
  this.frontLight = new DirectionalLight(0xffffff, 1.25);
47
- this.frontLight.position.set(0, 1, 0);
48
- this.viewer.scene.add(this.frontLight);
49
-
50
42
  this.hemisphereLight = new HemisphereLight(0xffffff, 0x444444, 1.25);
51
- this.hemisphereLight.position.set(0, 0, 1);
52
- this.viewer.scene.add(this.hemisphereLight);
53
43
 
54
44
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
55
45
  this.viewer.addEventListener("clear", this.geometryEnd);
@@ -84,15 +74,21 @@ export class LightComponent implements IComponent {
84
74
  const extentsSize = this.viewer.extents.getBoundingSphere(new Sphere()).radius;
85
75
 
86
76
  this.directionalLight.position
87
- .set(0.5, 0, 0.866)
77
+ .set(0.5, 0.866, 0) // ~60º
88
78
  .multiplyScalar(extentsSize * 2)
89
79
  .add(extentsCenter);
90
80
  this.directionalLight.target.position.copy(extentsCenter);
91
81
 
92
- this.frontLight.position.set(0, extentsSize * 2, 0).add(extentsCenter);
82
+ this.frontLight.position
83
+ .set(0, 0, 1)
84
+ .multiplyScalar(extentsSize * 2)
85
+ .add(extentsCenter);
93
86
  this.frontLight.target.position.copy(extentsCenter);
94
87
 
95
- this.hemisphereLight.position.set(0, extentsSize * 3, 0).add(extentsCenter);
88
+ this.hemisphereLight.position
89
+ .set(0, 0, 1)
90
+ .multiplyScalar(extentsSize * 3)
91
+ .add(extentsCenter);
96
92
 
97
93
  this.viewer.scene.add(this.ambientLight);
98
94
  this.viewer.scene.add(this.directionalLight);
@@ -68,7 +68,7 @@ export class SelectionComponent implements IComponent {
68
68
  this.viewer.models.forEach((model) => {
69
69
  const objects = model.getVisibleObjects();
70
70
  const intersects = this.getPointerIntersects(upPosition, objects);
71
- intersections.push(...intersects.map((x) => ({ ...x, model })));
71
+ if (intersects.length > 0) intersections.push({ ...intersects[0], model });
72
72
  });
73
73
  intersections = intersections.sort((a, b) => a.distance - b.distance);
74
74
 
@@ -126,7 +126,6 @@ export class SelectionComponent implements IComponent {
126
126
  if (!Array.isArray(objects)) objects = [objects];
127
127
  if (!objects.length) return;
128
128
 
129
- model.showObjects(objects);
130
129
  model.showOriginalObjects(objects);
131
130
  this.highlighter.highlight(objects);
132
131
 
@@ -86,7 +86,9 @@ export class DynamicGltfLoader {
86
86
  this.mergedPoints = new Set();
87
87
 
88
88
  this.isolatedObjects = [];
89
- this.useVAO = !!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext;
89
+ //!!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext
90
+ this.useVAO = false;
91
+ this.visibleEdges = true;
90
92
 
91
93
  this.handleToOptimizedObjects = new Map();
92
94
 
@@ -95,6 +97,10 @@ export class DynamicGltfLoader {
95
97
  this.oldOptimizeObjects = new Set();
96
98
  }
97
99
 
100
+ setVisibleEdges(visible) {
101
+ this.visibleEdges = visible;
102
+ }
103
+
98
104
  getAvailableMemory() {
99
105
  let memoryLimit = 6 * 1024 * 1024 * 1024;
100
106
  try {
@@ -115,7 +121,7 @@ export class DynamicGltfLoader {
115
121
  console.warn("Error detecting available memory:", error);
116
122
  }
117
123
 
118
- return memoryLimit;
124
+ return memoryLimit / 3;
119
125
  }
120
126
 
121
127
  getAbortController() {
@@ -220,7 +226,7 @@ export class DynamicGltfLoader {
220
226
  console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
221
227
  }
222
228
 
223
- async loadNode(nodeId) {
229
+ async loadNode(nodeId, onLoadFinishCb) {
224
230
  const node = this.nodes.get(nodeId);
225
231
  if (!node || node.loaded || node.loading) return;
226
232
 
@@ -228,38 +234,138 @@ export class DynamicGltfLoader {
228
234
  const meshDef = node.structure.getJson().meshes[node.meshIndex];
229
235
 
230
236
  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
+ const bufferRequests = [];
238
+ const primitiveReqMap = new Map();
239
+ for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
240
+ const primitive = meshDef.primitives[primIdx];
241
+ const reqs = [];
242
+
243
+ if (primitive.attributes.POSITION !== undefined) {
244
+ const accessorIndex = primitive.attributes.POSITION;
245
+ const accessor = node.structure.json.accessors[accessorIndex];
246
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
247
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
248
+ const components = node.structure.getNumComponents(accessor.type);
249
+ const count = accessor.count;
250
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
251
+ reqs.push({
252
+ offset: byteOffset,
253
+ length: byteLength,
254
+ componentType: accessor.componentType,
255
+ accessorIndex,
256
+ type: "position",
257
+ primIdx,
258
+ });
259
+ }
237
260
 
238
261
  if (primitive.attributes.NORMAL !== undefined) {
239
- attributes.set("normal", node.structure.createBufferAttribute(primitive.attributes.NORMAL));
262
+ const accessorIndex = primitive.attributes.NORMAL;
263
+ const accessor = node.structure.json.accessors[accessorIndex];
264
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
265
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
266
+ const components = node.structure.getNumComponents(accessor.type);
267
+ const count = accessor.count;
268
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
269
+ reqs.push({
270
+ offset: byteOffset,
271
+ length: byteLength,
272
+ componentType: accessor.componentType,
273
+ accessorIndex,
274
+ type: "normal",
275
+ primIdx,
276
+ });
240
277
  }
241
278
 
242
279
  if (primitive.attributes.TEXCOORD_0 !== undefined) {
243
- attributes.set("uv", node.structure.createBufferAttribute(primitive.attributes.TEXCOORD_0));
280
+ const accessorIndex = primitive.attributes.TEXCOORD_0;
281
+ const accessor = node.structure.json.accessors[accessorIndex];
282
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
283
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
284
+ const components = node.structure.getNumComponents(accessor.type);
285
+ const count = accessor.count;
286
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
287
+ reqs.push({
288
+ offset: byteOffset,
289
+ length: byteLength,
290
+ componentType: accessor.componentType,
291
+ accessorIndex,
292
+ type: "uv",
293
+ primIdx,
294
+ });
244
295
  }
245
296
 
246
- const loadedAttributes = await Promise.all(
247
- [...attributes.entries()].map(async ([name, promise]) => {
248
- const attribute = await promise;
249
- return [name, attribute];
250
- })
251
- );
297
+ if (primitive.indices !== undefined) {
298
+ const accessorIndex = primitive.indices;
299
+ const accessor = node.structure.json.accessors[accessorIndex];
300
+ const bufferView = node.structure.json.bufferViews[accessor.bufferView];
301
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
302
+ const components = node.structure.getNumComponents(accessor.type);
303
+ const count = accessor.count;
304
+ const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
305
+ reqs.push({
306
+ offset: byteOffset,
307
+ length: byteLength,
308
+ componentType: accessor.componentType,
309
+ accessorIndex,
310
+ type: "index",
311
+ primIdx,
312
+ });
313
+ }
314
+ primitiveReqMap.set(primIdx, reqs);
315
+ bufferRequests.push(...reqs);
316
+ }
252
317
 
253
- loadedAttributes.forEach(([name, attribute]) => {
254
- geometry.setAttribute(name, attribute);
255
- });
318
+ if (bufferRequests.length === 0) {
319
+ node.loaded = true;
320
+ node.loading = false;
321
+ return;
322
+ }
323
+ bufferRequests.sort((a, b) => a.offset - b.offset);
324
+ const minOffset = bufferRequests[0].offset;
325
+ const maxOffset = Math.max(...bufferRequests.map((r) => r.offset + r.length));
326
+ const totalLength = maxOffset - minOffset;
327
+
328
+ const { buffer, relOffset: baseRelOffset } = await node.structure.scheduleRequest({
329
+ offset: minOffset,
330
+ length: totalLength,
331
+ componentType: null,
332
+ });
256
333
 
257
- if (primitive.indices !== undefined) {
258
- const indexAttribute = await node.structure.createBufferAttribute(primitive.indices);
259
- geometry.setIndex(indexAttribute);
334
+ for (const req of bufferRequests) {
335
+ const relOffset = req.offset - minOffset;
336
+ req.data = node.structure.createTypedArray(buffer, baseRelOffset + relOffset, req.length, req.componentType);
337
+ }
338
+
339
+ for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
340
+ const primitive = meshDef.primitives[primIdx];
341
+ const geometry = new BufferGeometry();
342
+ const reqs = primitiveReqMap.get(primIdx);
343
+
344
+ if (primitive.attributes.POSITION !== undefined) {
345
+ const req = reqs.find((r) => r.type === "position" && r.accessorIndex === primitive.attributes.POSITION);
346
+ const accessor = node.structure.json.accessors[primitive.attributes.POSITION];
347
+ const components = node.structure.getNumComponents(accessor.type);
348
+ geometry.setAttribute("position", new BufferAttribute(req.data, components));
260
349
  }
261
350
 
262
- this.currentPrimitiveMode = primitive.mode;
351
+ if (primitive.attributes.NORMAL !== undefined) {
352
+ const req = reqs.find((r) => r.type === "normal" && r.accessorIndex === primitive.attributes.NORMAL);
353
+ const accessor = node.structure.json.accessors[primitive.attributes.NORMAL];
354
+ const components = node.structure.getNumComponents(accessor.type);
355
+ geometry.setAttribute("normal", new BufferAttribute(req.data, components));
356
+ }
357
+
358
+ if (primitive.attributes.TEXCOORD_0 !== undefined) {
359
+ const req = reqs.find((r) => r.type === "uv" && r.accessorIndex === primitive.attributes.TEXCOORD_0);
360
+ const accessor = node.structure.json.accessors[primitive.attributes.TEXCOORD_0];
361
+ const components = node.structure.getNumComponents(accessor.type);
362
+ geometry.setAttribute("uv", new BufferAttribute(req.data, components));
363
+ }
364
+
365
+ if (primitive.indices !== undefined) {
366
+ const req = reqs.find((r) => r.type === "index" && r.accessorIndex === primitive.indices);
367
+ geometry.setIndex(new BufferAttribute(req.data, 1));
368
+ }
263
369
 
264
370
  let material;
265
371
  if (primitive.material !== undefined) {
@@ -274,8 +380,7 @@ export class DynamicGltfLoader {
274
380
  Material.prototype.copy.call(pointsMaterial, material);
275
381
  pointsMaterial.color.copy(material.color);
276
382
  pointsMaterial.map = material.map;
277
- pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
278
-
383
+ pointsMaterial.sizeAttenuation = false;
279
384
  mesh = new Points(geometry, pointsMaterial);
280
385
  } else if (
281
386
  primitive.mode === GL_CONSTANTS.TRIANGLES ||
@@ -284,7 +389,6 @@ export class DynamicGltfLoader {
284
389
  primitive.mode === undefined
285
390
  ) {
286
391
  mesh = new Mesh(geometry, material);
287
-
288
392
  if (primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP) {
289
393
  mesh.drawMode = TriangleStripDrawMode;
290
394
  } else if (primitive.mode === GL_CONSTANTS.TRIANGLE_FAN) {
@@ -297,39 +401,30 @@ export class DynamicGltfLoader {
297
401
  } else if (primitive.mode === GL_CONSTANTS.LINE_LOOP) {
298
402
  mesh = new LineLoop(geometry, material);
299
403
  }
300
-
301
404
  if (node.extras) {
302
405
  mesh.userData = { ...mesh.userData, ...node.extras };
303
406
  }
304
-
305
407
  if (meshDef.extras) {
306
408
  mesh.userData = { ...mesh.userData, ...meshDef.extras };
307
409
  }
308
-
309
410
  if (primitive.extras) {
310
411
  mesh.userData = { ...mesh.userData, ...primitive.extras };
311
412
  }
312
-
313
413
  if (node.handle) {
314
414
  mesh.userData.handle = node.handle;
315
415
  } else {
316
416
  mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
317
417
  }
318
-
319
418
  if (mesh.material.name === "edges") {
320
419
  mesh.userData.isEdge = true;
321
420
  } else {
322
421
  mesh.userData.isEdge = false;
323
422
  }
324
-
325
423
  this.registerObjectWithHandle(mesh, mesh.userData.handle);
326
-
327
424
  mesh.position.copy(node.position);
328
-
329
425
  if (!geometry.attributes.normal) {
330
426
  geometry.computeVertexNormals();
331
427
  }
332
-
333
428
  if (material.aoMap && geometry.attributes.uv) {
334
429
  geometry.setAttribute("uv2", geometry.attributes.uv);
335
430
  }
@@ -339,17 +434,17 @@ export class DynamicGltfLoader {
339
434
  this.scene.add(mesh);
340
435
  }
341
436
  node.object = mesh;
342
-
343
437
  this.totalLoadedObjects++;
344
438
  mesh.visible = this.totalLoadedObjects < this.graphicsObjectLimit;
345
439
  }
346
-
347
440
  node.loaded = true;
348
441
  node.loading = false;
349
-
350
442
  const geometrySize = this.estimateGeometrySize(node.object);
351
443
  this.geometryCache.set(node.object.uuid, geometrySize);
352
444
  this.currentMemoryUsage += geometrySize;
445
+ if (onLoadFinishCb) {
446
+ onLoadFinishCb();
447
+ }
353
448
  } catch (error) {
354
449
  if (error.name !== "AbortError") {
355
450
  console.error(`Error loading node ${nodeId}:`, error);
@@ -474,7 +569,7 @@ export class DynamicGltfLoader {
474
569
  return volumeB - volumeA;
475
570
  });
476
571
 
477
- if (!ignoreEdges) {
572
+ if (!ignoreEdges && this.visibleEdges) {
478
573
  this.nodesToLoad.push(...this.edgeNodes);
479
574
  }
480
575
 
@@ -601,33 +696,13 @@ export class DynamicGltfLoader {
601
696
  async processNodes() {
602
697
  const nodesToLoad = this.nodesToLoad;
603
698
  let loadedCount = 0;
699
+ let lastLoadedCount = 0;
604
700
  const totalNodes = nodesToLoad.length;
605
701
 
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
-
702
+ const loadProgress = async () => {
703
+ loadedCount++;
704
+ if (loadedCount - lastLoadedCount > 1000) {
705
+ lastLoadedCount = loadedCount;
631
706
  this.updateMemoryIndicator();
632
707
  this.dispatchEvent("geometryprogress", {
633
708
  percentage: Math.round((loadedCount / totalNodes) * 100),
@@ -645,6 +720,34 @@ export class DynamicGltfLoader {
645
720
  setTimeout(resolve, 0);
646
721
  });
647
722
  }
723
+ };
724
+
725
+ try {
726
+ const loadOperations = [];
727
+ for (const nodeId of nodesToLoad) {
728
+ if (this.abortController.signal.aborted) {
729
+ throw new DOMException("Loading aborted", "AbortError");
730
+ }
731
+
732
+ const estimatedSize = await this.estimateNodeSize(nodeId);
733
+
734
+ if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
735
+ console.log(`Memory limit reached after loading ${loadedCount} nodes`);
736
+ this.dispatchEvent("geometryerror", {
737
+ message: "Memory limit reached",
738
+ });
739
+ this.dispatchEvent("update");
740
+ return loadedCount;
741
+ }
742
+
743
+ loadOperations.push(this.loadNode(nodeId, loadProgress));
744
+ }
745
+
746
+ for (const structure of this.structures) {
747
+ loadOperations.push(structure.flushBufferRequests());
748
+ }
749
+
750
+ await Promise.all(loadOperations);
648
751
 
649
752
  this.dispatchEvent("geometryend", {
650
753
  totalLoaded: loadedCount,
@@ -1625,4 +1728,23 @@ export class DynamicGltfLoader {
1625
1728
  }
1626
1729
  });
1627
1730
  }
1731
+
1732
+ // Возвращает bounding box для конкретной структуры
1733
+ getStructureGeometryExtent(structureId) {
1734
+ const extent = new Box3();
1735
+ for (const [nodeId, node] of this.nodes.entries()) {
1736
+ if (!node.geometryExtents) continue;
1737
+ if (!nodeId.startsWith(structureId + "_")) continue;
1738
+ if (node.object && this.hiddenHandles && this.hiddenHandles.has(node.object.userData.handle)) continue;
1739
+ const transformedBox = node.geometryExtents.clone();
1740
+ if (node.group && node.group.matrix) {
1741
+ transformedBox.applyMatrix4(node.group.matrix);
1742
+ if (node.group.parent && node.group.parent.matrix) {
1743
+ transformedBox.applyMatrix4(node.group.parent.matrix);
1744
+ }
1745
+ }
1746
+ extent.union(transformedBox);
1747
+ }
1748
+ return extent;
1749
+ }
1628
1750
  }