@inweb/viewer-three 26.7.2 → 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.
@@ -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
  }
@@ -62,10 +62,8 @@ export class DynamicModelImpl extends ModelImpl {
62
62
  }
63
63
 
64
64
  override hideObjects(objects: Object3D | Object3D[]): this {
65
- this.getOwnObjects(objects)
66
- .map((object) => object.userData.handle)
67
- .forEach((handle) => this.gltfLoader.hiddenHandles.add(handle));
68
- this.gltfLoader.syncHiddenObjects();
65
+ const handles = this.getHandlesByObjects(objects);
66
+ this.gltfLoader.hideObjects(handles);
69
67
  return this;
70
68
  }
71
69
 
@@ -76,26 +74,23 @@ export class DynamicModelImpl extends ModelImpl {
76
74
  }
77
75
 
78
76
  override showObjects(objects: Object3D | Object3D[]): this {
79
- this.getOwnObjects(objects)
80
- .map((object) => object.userData.handle)
81
- .forEach((handle) => this.gltfLoader.hiddenHandles.delete(handle));
82
- this.gltfLoader.syncHiddenObjects();
77
+ const handles = this.getHandlesByObjects(objects);
78
+ this.gltfLoader.showObjects(handles);
83
79
  return this;
84
80
  }
85
81
 
86
82
  override showAllObjects(): this {
87
- this.gltfLoader.hiddenHandles.clear();
88
- this.gltfLoader.syncHiddenObjects();
83
+ this.gltfLoader.showAllHiddenObjects();
89
84
  return this;
90
85
  }
91
86
 
92
87
  override showOriginalObjects(objects: Object3D | Object3D[]): this {
93
- this.getOwnObjects(objects).forEach((object) => (object.visible = true));
88
+ this.gltfLoader.showOriginalObjects(objects);
94
89
  return this;
95
90
  }
96
91
 
97
92
  override hideOriginalObjects(objects: Object3D | Object3D[]): this {
98
- this.getOwnObjects(objects).forEach((object) => (object.visible = false));
93
+ this.gltfLoader.hideOriginalObjects(objects);
99
94
  return this;
100
95
  }
101
96
  }