@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.
Files changed (75) hide show
  1. package/dist/plugins/components/AxesHelperComponent.js +5 -5
  2. package/dist/plugins/components/AxesHelperComponent.js.map +1 -1
  3. package/dist/plugins/components/AxesHelperComponent.min.js +1 -1
  4. package/dist/plugins/components/AxesHelperComponent.module.js +5 -5
  5. package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -1
  6. package/dist/plugins/components/ExtentsHelperComponent.js +15 -7
  7. package/dist/plugins/components/ExtentsHelperComponent.js.map +1 -1
  8. package/dist/plugins/components/ExtentsHelperComponent.min.js +1 -1
  9. package/dist/plugins/components/ExtentsHelperComponent.module.js +15 -7
  10. package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -1
  11. package/dist/plugins/components/LightHelperComponent.js +5 -5
  12. package/dist/plugins/components/LightHelperComponent.js.map +1 -1
  13. package/dist/plugins/components/LightHelperComponent.min.js +1 -1
  14. package/dist/plugins/components/LightHelperComponent.module.js +5 -5
  15. package/dist/plugins/components/LightHelperComponent.module.js.map +1 -1
  16. package/dist/plugins/loaders/GLTFCloudLoader.js +4840 -0
  17. package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -0
  18. package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -0
  19. package/dist/plugins/loaders/GLTFCloudLoader.module.js +49 -0
  20. package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +1 -0
  21. package/dist/plugins/loaders/IFCXLoader.js +12 -6
  22. package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
  23. package/dist/plugins/loaders/IFCXLoader.min.js +1 -1
  24. package/dist/plugins/loaders/IFCXLoader.module.js +13 -7
  25. package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
  26. package/dist/viewer-three.js +3131 -459
  27. package/dist/viewer-three.js.map +1 -1
  28. package/dist/viewer-three.min.js +2 -2
  29. package/dist/viewer-three.module.js +2326 -264
  30. package/dist/viewer-three.module.js.map +1 -1
  31. package/lib/Viewer/Viewer.d.ts +6 -5
  32. package/lib/Viewer/components/HighlighterComponent.d.ts +4 -3
  33. package/lib/Viewer/components/SelectionComponent.d.ts +8 -5
  34. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +1 -1
  35. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +20 -0
  36. package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +15 -0
  37. package/lib/Viewer/model/IModelImpl.d.ts +27 -0
  38. package/lib/Viewer/model/ModelImpl.d.ts +30 -0
  39. package/lib/Viewer/model/index.d.ts +2 -0
  40. package/lib/index.d.ts +1 -0
  41. package/package.json +11 -7
  42. package/plugins/components/AxesHelperComponent.ts +5 -5
  43. package/plugins/components/ExtentsHelperComponent.ts +15 -7
  44. package/plugins/components/LightHelperComponent.ts +5 -5
  45. package/{src/Viewer/loaders/GLTFCloudModelLoader.ts → plugins/loaders/GLTFCloudLoader.ts} +15 -12
  46. package/plugins/loaders/{IFCXCloudFileLoader.ts → IFCXCloudLoader.ts} +8 -4
  47. package/plugins/loaders/IFCXFileLoader.ts +7 -3
  48. package/plugins/loaders/IFCXLoader.ts +2 -2
  49. package/src/Viewer/Viewer.ts +32 -36
  50. package/src/Viewer/commands/ClearSelected.ts +2 -3
  51. package/src/Viewer/commands/Explode.ts +1 -47
  52. package/src/Viewer/commands/GetModels.ts +1 -1
  53. package/src/Viewer/commands/GetSelected.ts +3 -1
  54. package/src/Viewer/commands/HideSelected.ts +3 -4
  55. package/src/Viewer/commands/IsolateSelected.ts +1 -7
  56. package/src/Viewer/commands/SelectModel.ts +9 -1
  57. package/src/Viewer/commands/SetSelected.ts +8 -10
  58. package/src/Viewer/commands/ShowAll.ts +1 -1
  59. package/src/Viewer/components/BackgroundComponent.ts +1 -0
  60. package/src/Viewer/components/ExtentsComponent.ts +5 -3
  61. package/src/Viewer/components/HighlighterComponent.ts +79 -48
  62. package/src/Viewer/components/SelectionComponent.ts +67 -21
  63. package/src/Viewer/draggers/CuttingPlaneDragger.ts +7 -3
  64. package/src/Viewer/draggers/MeasureLineDragger.ts +2 -0
  65. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +1628 -0
  66. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +102 -0
  67. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +450 -0
  68. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +145 -0
  69. package/src/Viewer/loaders/GLTFFileLoader.ts +7 -2
  70. package/src/Viewer/loaders/index.ts +2 -2
  71. package/src/Viewer/model/IModelImpl.ts +67 -0
  72. package/src/Viewer/model/ModelImpl.ts +215 -0
  73. package/src/Viewer/model/index.ts +25 -0
  74. package/src/index.ts +1 -0
  75. package/lib/Viewer/loaders/GLTFCloudModelLoader.d.ts +0 -8
@@ -58447,7 +58447,7 @@ void main() {
58447
58447
  this.transformDrag = (event) => {
58448
58448
  this.orbit.enabled = !event.value;
58449
58449
  };
58450
- this.viewerExplode = () => {
58450
+ this.updatePlaneSize = () => {
58451
58451
  this.planeHelper.size = this.viewer.extents.getSize(new Vector3()).length();
58452
58452
  this.viewer.update();
58453
58453
  };
@@ -58476,12 +58476,16 @@ void main() {
58476
58476
  this.transform.addEventListener("change", this.transformChange);
58477
58477
  this.transform.addEventListener("dragging-changed", this.transformDrag);
58478
58478
  this.viewer.helpers.add(this.transform.getHelper());
58479
- this.viewer.on("explode", this.viewerExplode);
58479
+ this.viewer.on("explode", this.updatePlaneSize);
58480
+ this.viewer.on("show", this.updatePlaneSize);
58481
+ this.viewer.on("showall", this.updatePlaneSize);
58480
58482
  this.viewer.canvas.addEventListener("dblclick", this.onDoubleClick, true);
58481
58483
  this.viewer.update();
58482
58484
  }
58483
58485
  dispose() {
58484
- this.viewer.off("explode", this.viewerExplode);
58486
+ this.viewer.off("explode", this.updatePlaneSize);
58487
+ this.viewer.off("show", this.updatePlaneSize);
58488
+ this.viewer.off("showAll", this.updatePlaneSize);
58485
58489
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
58486
58490
  this.transform.removeEventListener("change", this.transformChange);
58487
58491
  this.transform.removeEventListener("dragging-changed", this.transformDrag);
@@ -58663,6 +58667,7 @@ void main() {
58663
58667
  this.viewer.addEventListener("render", this.renderOverlay);
58664
58668
  this.viewer.addEventListener("hide", this.updateSnapper);
58665
58669
  this.viewer.addEventListener("isolate", this.updateSnapper);
58670
+ this.viewer.addEventListener("show", this.updateSnapper);
58666
58671
  this.viewer.addEventListener("showall", this.updateSnapper);
58667
58672
  }
58668
58673
  dispose() {
@@ -58674,6 +58679,7 @@ void main() {
58674
58679
  this.viewer.removeEventListener("render", this.renderOverlay);
58675
58680
  this.viewer.removeEventListener("hide", this.updateSnapper);
58676
58681
  this.viewer.removeEventListener("isolate", this.updateSnapper);
58682
+ this.viewer.removeEventListener("show", this.updateSnapper);
58677
58683
  this.viewer.removeEventListener("showall", this.updateSnapper);
58678
58684
  this.overlay.detach();
58679
58685
  this.overlay.dispose();
@@ -59384,8 +59390,6 @@ void main() {
59384
59390
  ///////////////////////////////////////////////////////////////////////////////
59385
59391
  function clearSelected(viewer) {
59386
59392
  const selection = viewer.getComponent("SelectionComponent");
59387
- if (!selection)
59388
- return;
59389
59393
  selection.clearSelection();
59390
59394
  viewer.update();
59391
59395
  viewer.emitEvent({ type: "select", data: undefined, handles: [] });
@@ -59466,44 +59470,8 @@ void main() {
59466
59470
  // By use of this software, its documentation or related materials, you
59467
59471
  // acknowledge and accept the above terms.
59468
59472
  ///////////////////////////////////////////////////////////////////////////////
59469
- function calcExplodeDepth(object, depth) {
59470
- let res = depth;
59471
- object.children.forEach((x) => {
59472
- const objectDepth = calcExplodeDepth(x, depth + 1);
59473
- if (res < objectDepth)
59474
- res = objectDepth;
59475
- });
59476
- object.originalPosition = object.position.clone();
59477
- object.originalCenter = new Box3().setFromObject(object).getCenter(new Vector3());
59478
- object.isExplodeLocked = depth > 2 && object.children.length === 0;
59479
- return res;
59480
- }
59481
- function explodeModel(scene, scale = 0, coeff = 4) {
59482
- scale /= 100;
59483
- if (!scene.explodeDepth)
59484
- scene.explodeDepth = calcExplodeDepth(scene, 1);
59485
- const maxDepth = scene.explodeDepth;
59486
- const scaledExplodeDepth = scale * maxDepth + 1;
59487
- const explodeDepth = 0 | scaledExplodeDepth;
59488
- const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
59489
- function explodeObject(object, depth) {
59490
- object.position.copy(object.originalPosition);
59491
- if (depth > 0 && depth <= explodeDepth && !object.isExplodeLocked) {
59492
- let objectScale = scale * coeff;
59493
- if (depth === explodeDepth)
59494
- objectScale *= currentSegmentFraction;
59495
- const parentCenter = object.parent.originalCenter;
59496
- const objectCenter = object.originalCenter;
59497
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
59498
- object.position.add(objectOffset);
59499
- }
59500
- object.children.forEach((x) => explodeObject(x, depth + 1));
59501
- }
59502
- explodeObject(scene, 0);
59503
- }
59504
59473
  function explode(viewer, index = 0) {
59505
- viewer.models.forEach((model) => explodeModel(model, index));
59506
- viewer.scene.updateMatrixWorld();
59474
+ viewer.models.forEach((model) => model.explode(index));
59507
59475
  viewer.update();
59508
59476
  viewer.emitEvent({ type: "explode", data: index });
59509
59477
  }
@@ -59664,7 +59632,7 @@ void main() {
59664
59632
  // acknowledge and accept the above terms.
59665
59633
  ///////////////////////////////////////////////////////////////////////////////
59666
59634
  function getModels(viewer) {
59667
- return viewer.models.map((model) => { var _a; return ((_a = model.userData) === null || _a === undefined ? undefined : _a.handle) || ""; }).filter((handle) => handle);
59635
+ return viewer.models.map((model) => model.handle);
59668
59636
  }
59669
59637
 
59670
59638
  ///////////////////////////////////////////////////////////////////////////////
@@ -59690,108 +59658,9 @@ void main() {
59690
59658
  // acknowledge and accept the above terms.
59691
59659
  ///////////////////////////////////////////////////////////////////////////////
59692
59660
  function getSelected(viewer) {
59693
- return viewer.selected.map((object) => { var _a; return (_a = object.userData) === null || _a === undefined ? undefined : _a.handle; }).filter((handle) => handle);
59694
- }
59695
-
59696
- ///////////////////////////////////////////////////////////////////////////////
59697
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
59698
- // All rights reserved.
59699
- //
59700
- // This software and its documentation and related materials are owned by
59701
- // the Alliance. The software may only be incorporated into application
59702
- // programs owned by members of the Alliance, subject to a signed
59703
- // Membership Agreement and Supplemental Software License Agreement with the
59704
- // Alliance. The structure and organization of this software are the valuable
59705
- // trade secrets of the Alliance and its suppliers. The software is also
59706
- // protected by copyright law and international treaty provisions. Application
59707
- // programs incorporating this software must include the following statement
59708
- // with their copyright notices:
59709
- //
59710
- // This application incorporates Open Design Alliance software pursuant to a
59711
- // license agreement with Open Design Alliance.
59712
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
59713
- // All rights reserved.
59714
- //
59715
- // By use of this software, its documentation or related materials, you
59716
- // acknowledge and accept the above terms.
59717
- ///////////////////////////////////////////////////////////////////////////////
59718
- class SelectionComponent {
59719
- constructor(viewer) {
59720
- this.onPointerDown = (event) => {
59721
- if (!event.isPrimary || event.button !== 0)
59722
- return;
59723
- this.getMousePosition(event, this.downPosition);
59724
- };
59725
- this.onPointerUp = (event) => {
59726
- if (!event.isPrimary)
59727
- return;
59728
- const upPosition = this.getMousePosition(event, new Vector2());
59729
- if (this.downPosition.distanceTo(upPosition) !== 0)
59730
- return;
59731
- const intersects = this.getPointerIntersects(upPosition);
59732
- this.clearSelection();
59733
- if (intersects.length > 0)
59734
- this.select(intersects[0].object);
59735
- this.viewer.update();
59736
- this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
59737
- };
59738
- this.onDoubleClick = (event) => {
59739
- if (event.button !== 0)
59740
- return;
59741
- this.viewer.executeCommand("zoomToSelected");
59742
- };
59743
- this.initHighlighter = () => {
59744
- this.highlighter = this.viewer.getComponent("HighlighterComponent");
59745
- };
59746
- this.viewer = viewer;
59747
- this.raycaster = new Raycaster();
59748
- this.downPosition = new Vector2();
59749
- this.viewer.addEventListener("pointerdown", this.onPointerDown);
59750
- this.viewer.addEventListener("pointerup", this.onPointerUp);
59751
- this.viewer.addEventListener("dblclick", this.onDoubleClick);
59752
- this.viewer.addEventListener("initialize", this.initHighlighter);
59753
- }
59754
- dispose() {
59755
- this.viewer.removeEventListener("pointerdown", this.onPointerDown);
59756
- this.viewer.removeEventListener("pointerup", this.onPointerUp);
59757
- this.viewer.removeEventListener("dblclick", this.onDoubleClick);
59758
- this.viewer.removeEventListener("initialize", this.initHighlighter);
59759
- }
59760
- getMousePosition(event, target) {
59761
- return target.set(event.clientX, event.clientY);
59762
- }
59763
- getPointerIntersects(mouse) {
59764
- const rect = this.viewer.canvas.getBoundingClientRect();
59765
- const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
59766
- const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
59767
- const coords = new Vector2(x, y);
59768
- this.raycaster.setFromCamera(coords, this.viewer.camera);
59769
- const objects = [];
59770
- this.viewer.scene.traverseVisible((child) => objects.push(child));
59771
- this.raycaster.params = this.raycaster.params = {
59772
- Mesh: {},
59773
- Line: { threshold: 0.25 },
59774
- Line2: { threshold: 0.25 },
59775
- LOD: {},
59776
- Points: { threshold: 0.1 },
59777
- Sprite: {},
59778
- };
59779
- return this.raycaster.intersectObjects(objects, false);
59780
- }
59781
- select(object) {
59782
- if (object.isSelected)
59783
- return;
59784
- object.isSelected = true;
59785
- this.highlighter.highlight(object);
59786
- this.viewer.selected.push(object);
59787
- }
59788
- clearSelection() {
59789
- this.viewer.selected.forEach((object) => {
59790
- object.isSelected = false;
59791
- this.highlighter.unhighlight(object);
59792
- });
59793
- this.viewer.selected.length = 0;
59794
- }
59661
+ const handles = [];
59662
+ viewer.models.forEach((model) => handles.push(...model.getHandlesByObjects(viewer.selected)));
59663
+ return handles;
59795
59664
  }
59796
59665
 
59797
59666
  ///////////////////////////////////////////////////////////////////////////////
@@ -59817,10 +59686,9 @@ void main() {
59817
59686
  // acknowledge and accept the above terms.
59818
59687
  ///////////////////////////////////////////////////////////////////////////////
59819
59688
  function hideSelected(viewer) {
59820
- viewer.selected.forEach((object) => (object.visible = false));
59821
- const selection = new SelectionComponent(viewer);
59689
+ viewer.models.forEach((model) => model.hideObjects(viewer.selected));
59690
+ const selection = viewer.getComponent("SelectionComponent");
59822
59691
  selection.clearSelection();
59823
- selection.dispose();
59824
59692
  viewer.update();
59825
59693
  viewer.emitEvent({ type: "hide" });
59826
59694
  viewer.emitEvent({ type: "select", data: undefined, handles: [] });
@@ -59849,12 +59717,7 @@ void main() {
59849
59717
  // acknowledge and accept the above terms.
59850
59718
  ///////////////////////////////////////////////////////////////////////////////
59851
59719
  function isolateSelected(viewer) {
59852
- const visibleSet = new Set();
59853
- viewer.selected.forEach((object) => {
59854
- visibleSet.add(object);
59855
- object.traverseAncestors((object2) => visibleSet.add(object2));
59856
- });
59857
- viewer.scene.traverse((object) => (object.visible = visibleSet.has(object)));
59720
+ viewer.models.forEach((model) => model.isolateObjects(viewer.selected));
59858
59721
  viewer.update();
59859
59722
  viewer.emitEvent({ type: "isolate" });
59860
59723
  }
@@ -59943,7 +59806,12 @@ void main() {
59943
59806
  // acknowledge and accept the above terms.
59944
59807
  ///////////////////////////////////////////////////////////////////////////////
59945
59808
  function selectModel(viewer, handle) {
59946
- console.warn("selectModel not implemented");
59809
+ const selection = viewer.getComponent("SelectionComponent");
59810
+ selection.clearSelection();
59811
+ viewer.models
59812
+ .filter((model) => model.handle === handle)
59813
+ .forEach((model) => selection.select(model.getObjects(), model));
59814
+ viewer.update();
59947
59815
  viewer.emit({ type: "select", data: [] });
59948
59816
  }
59949
59817
 
@@ -60022,19 +59890,14 @@ void main() {
60022
59890
  // acknowledge and accept the above terms.
60023
59891
  ///////////////////////////////////////////////////////////////////////////////
60024
59892
  function setSelected(viewer, handles = []) {
60025
- const handleSet = new Set(handles);
60026
- const objects = [];
60027
- viewer.scene.traverseVisible((child) => {
60028
- var _a;
60029
- if (handleSet.has((_a = child.userData) === null || _a === undefined ? undefined : _a.handle))
60030
- objects.push(child);
60031
- });
60032
59893
  const selection = viewer.getComponent("SelectionComponent");
60033
- if (!selection)
60034
- return;
60035
59894
  selection.clearSelection();
60036
- objects.forEach((object) => selection.select(object));
59895
+ viewer.models.forEach((model) => {
59896
+ const objects = model.getObjectsByHandles(handles);
59897
+ selection.select(objects, model);
59898
+ });
60037
59899
  viewer.update();
59900
+ viewer.emitEvent({ type: "show" });
60038
59901
  viewer.emitEvent({ type: "select", data: undefined, handles });
60039
59902
  }
60040
59903
 
@@ -60061,7 +59924,7 @@ void main() {
60061
59924
  // acknowledge and accept the above terms.
60062
59925
  ///////////////////////////////////////////////////////////////////////////////
60063
59926
  function showAll(viewer) {
60064
- viewer.scene.traverse((object) => (object.visible = true));
59927
+ viewer.models.forEach((model) => model.showAllObjects());
60065
59928
  viewer.update();
60066
59929
  viewer.emitEvent({ type: "showall" });
60067
59930
  }
@@ -60280,6 +60143,7 @@ void main() {
60280
60143
  constructor(viewer) {
60281
60144
  this.syncOptions = () => {
60282
60145
  this.backgroundColor.setHex(0xffffff);
60146
+ this.viewer.renderer.setClearColor(this.backgroundColor);
60283
60147
  };
60284
60148
  this.viewer = viewer;
60285
60149
  this.backgroundColor = new Color(0xffffff);
@@ -60389,23 +60253,25 @@ void main() {
60389
60253
  constructor(viewer) {
60390
60254
  this.syncExtents = () => {
60391
60255
  const extents = new Box3();
60392
- this.viewer.scene.traverseVisible((object) => !object.children.length && extents.expandByObject(object));
60256
+ this.viewer.models.forEach((model) => model.getExtents(extents));
60393
60257
  this.viewer.extents.copy(extents);
60394
60258
  };
60395
60259
  this.viewer = viewer;
60396
60260
  this.viewer.addEventListener("databasechunk", this.syncExtents);
60397
60261
  this.viewer.addEventListener("clear", this.syncExtents);
60398
60262
  this.viewer.on("explode", this.syncExtents);
60399
- this.viewer.on("isolate", this.syncExtents);
60400
60263
  this.viewer.on("hide", this.syncExtents);
60264
+ this.viewer.on("isolate", this.syncExtents);
60265
+ this.viewer.on("show", this.syncExtents);
60401
60266
  this.viewer.on("showall", this.syncExtents);
60402
60267
  }
60403
60268
  dispose() {
60404
60269
  this.viewer.removeEventListener("databasechunk", this.syncExtents);
60405
60270
  this.viewer.removeEventListener("clear", this.syncExtents);
60406
60271
  this.viewer.off("explode", this.syncExtents);
60407
- this.viewer.off("isolate", this.syncExtents);
60408
60272
  this.viewer.off("hide", this.syncExtents);
60273
+ this.viewer.off("isolate", this.syncExtents);
60274
+ this.viewer.off("show", this.syncExtents);
60409
60275
  this.viewer.off("showall", this.syncExtents);
60410
60276
  }
60411
60277
  }
@@ -61605,6 +61471,16 @@ void main() {
61605
61471
  this.viewer.update();
61606
61472
  };
61607
61473
  this.viewer = viewer;
61474
+ const gl2 = viewer.canvas.getContext("webgl2");
61475
+ if (gl2) {
61476
+ const size = viewer.renderer.getSize(new Vector2());
61477
+ this.renderTarget = new WebGLRenderTarget(size.x, size.y, {
61478
+ format: RGBAFormat,
61479
+ stencilBuffer: false,
61480
+ samples: 4,
61481
+ type: UnsignedByteType,
61482
+ });
61483
+ }
61608
61484
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
61609
61485
  this.viewer.addEventListener("optionschange", this.optionsChange);
61610
61486
  this.viewer.addEventListener("resize", this.viewerResize);
@@ -61615,52 +61491,210 @@ void main() {
61615
61491
  this.viewer.removeEventListener("optionschange", this.optionsChange);
61616
61492
  this.viewer.removeEventListener("resize", this.viewerResize);
61617
61493
  }
61618
- highlight(object) {
61619
- if (object.isHighlighted)
61494
+ highlight(objects) {
61495
+ if (!Array.isArray(objects))
61496
+ objects = [objects];
61497
+ if (!objects.length)
61498
+ return;
61499
+ objects.forEach((object) => {
61500
+ if (object.isHighlighted)
61501
+ return;
61502
+ if (object.isLine || object.isLineSegments) {
61503
+ const positions = object.geometry.attributes.position.array;
61504
+ const indices = object.geometry.index ? object.geometry.index.array : null;
61505
+ const lineGeometry = indices
61506
+ ? HighlighterUtils.fromIndexedLine(positions, indices)
61507
+ : HighlighterUtils.fromNonIndexedLine(positions, object.isLineSegments);
61508
+ const wireframe = new Wireframe(lineGeometry, this.highlightLineGlowMaterial);
61509
+ wireframe.position.copy(object.position);
61510
+ wireframe.rotation.copy(object.rotation);
61511
+ wireframe.scale.copy(object.scale);
61512
+ object.parent.add(wireframe);
61513
+ object.userData.highlightwireframe = wireframe;
61514
+ object.userData.originalMaterial = object.material;
61515
+ object.material = this.highlightLineMaterial;
61516
+ object.isHighlighted = true;
61517
+ }
61518
+ else if (object.isMesh) {
61519
+ const edgesGeometry = new EdgesGeometry(object.geometry, 30);
61520
+ const lineGeometry = new LineSegmentsGeometry().fromEdgesGeometry(edgesGeometry);
61521
+ const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
61522
+ wireframe.position.copy(object.position);
61523
+ wireframe.rotation.copy(object.rotation);
61524
+ wireframe.scale.copy(object.scale);
61525
+ object.parent.add(wireframe);
61526
+ object.userData.highlightwireframe = wireframe;
61527
+ object.userData.originalMaterial = object.material;
61528
+ object.material = this.highlightMaterial;
61529
+ object.isHighlighted = true;
61530
+ }
61531
+ });
61532
+ }
61533
+ unhighlight(objects) {
61534
+ if (!Array.isArray(objects))
61535
+ objects = [objects];
61536
+ if (!objects.length)
61537
+ return;
61538
+ objects.forEach((object) => {
61539
+ if (!object.isHighlighted)
61540
+ return;
61541
+ object.isHighlighted = false;
61542
+ object.material = object.userData.originalMaterial;
61543
+ object.userData.highlightwireframe.removeFromParent();
61544
+ delete object.userData.originalMaterial;
61545
+ delete object.userData.highlightwireframe;
61546
+ });
61547
+ }
61548
+ viewerResize(event) {
61549
+ var _a, _b;
61550
+ (_a = this.renderTarget) === null || _a === undefined ? undefined : _a.setSize(event.width, event.height);
61551
+ (_b = this.outlineMaterial) === null || _b === undefined ? undefined : _b.resolution.set(event.width, event.height);
61552
+ }
61553
+ }
61554
+
61555
+ ///////////////////////////////////////////////////////////////////////////////
61556
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
61557
+ // All rights reserved.
61558
+ //
61559
+ // This software and its documentation and related materials are owned by
61560
+ // the Alliance. The software may only be incorporated into application
61561
+ // programs owned by members of the Alliance, subject to a signed
61562
+ // Membership Agreement and Supplemental Software License Agreement with the
61563
+ // Alliance. The structure and organization of this software are the valuable
61564
+ // trade secrets of the Alliance and its suppliers. The software is also
61565
+ // protected by copyright law and international treaty provisions. Application
61566
+ // programs incorporating this software must include the following statement
61567
+ // with their copyright notices:
61568
+ //
61569
+ // This application incorporates Open Design Alliance software pursuant to a
61570
+ // license agreement with Open Design Alliance.
61571
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
61572
+ // All rights reserved.
61573
+ //
61574
+ // By use of this software, its documentation or related materials, you
61575
+ // acknowledge and accept the above terms.
61576
+ ///////////////////////////////////////////////////////////////////////////////
61577
+ class SelectionComponent {
61578
+ constructor(viewer) {
61579
+ this.onPointerDown = (event) => {
61580
+ if (!event.isPrimary || event.button !== 0)
61581
+ return;
61582
+ this.getMousePosition(event, this.downPosition);
61583
+ };
61584
+ this.onPointerUp = (event) => {
61585
+ if (!event.isPrimary)
61586
+ return;
61587
+ const upPosition = this.getMousePosition(event, new Vector2());
61588
+ if (upPosition.distanceTo(this.downPosition) !== 0)
61589
+ return;
61590
+ let intersections = [];
61591
+ this.viewer.models.forEach((model) => {
61592
+ const objects = model.getVisibleObjects();
61593
+ const intersects = this.getPointerIntersects(upPosition, objects);
61594
+ intersections.push(...intersects.map((x) => ({ ...x, model })));
61595
+ });
61596
+ intersections = intersections.sort((a, b) => a.distance - b.distance);
61597
+ if (!event.shiftKey)
61598
+ this.clearSelection();
61599
+ if (intersections.length > 0) {
61600
+ const model = intersections[0].model;
61601
+ const handles = model.getHandlesByObjects(intersections[0].object);
61602
+ const objects = model.getObjectsByHandles(handles);
61603
+ if (!event.shiftKey)
61604
+ this.select(objects, model);
61605
+ else
61606
+ this.toggleSelection(objects, model);
61607
+ }
61608
+ this.viewer.update();
61609
+ this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
61610
+ };
61611
+ this.onDoubleClick = (event) => {
61612
+ if (event.button !== 0)
61613
+ return;
61614
+ this.viewer.executeCommand("zoomToSelected");
61615
+ };
61616
+ this.initHighlighter = () => {
61617
+ this.highlighter = this.viewer.getComponent("HighlighterComponent");
61618
+ };
61619
+ this.viewer = viewer;
61620
+ this.raycaster = new Raycaster();
61621
+ this.downPosition = new Vector2();
61622
+ this.viewer.addEventListener("pointerdown", this.onPointerDown);
61623
+ this.viewer.addEventListener("pointerup", this.onPointerUp);
61624
+ this.viewer.addEventListener("dblclick", this.onDoubleClick);
61625
+ this.viewer.addEventListener("initialize", this.initHighlighter);
61626
+ }
61627
+ dispose() {
61628
+ this.viewer.removeEventListener("pointerdown", this.onPointerDown);
61629
+ this.viewer.removeEventListener("pointerup", this.onPointerUp);
61630
+ this.viewer.removeEventListener("dblclick", this.onDoubleClick);
61631
+ this.viewer.removeEventListener("initialize", this.initHighlighter);
61632
+ }
61633
+ getMousePosition(event, target) {
61634
+ return target.set(event.clientX, event.clientY);
61635
+ }
61636
+ getPointerIntersects(mouse, objects) {
61637
+ const rect = this.viewer.canvas.getBoundingClientRect();
61638
+ const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
61639
+ const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
61640
+ const coords = new Vector2(x, y);
61641
+ this.raycaster.setFromCamera(coords, this.viewer.camera);
61642
+ this.raycaster.params = this.raycaster.params = {
61643
+ Mesh: {},
61644
+ Line: { threshold: 0.25 },
61645
+ Line2: { threshold: 0.25 },
61646
+ LOD: {},
61647
+ Points: { threshold: 0.1 },
61648
+ Sprite: {},
61649
+ };
61650
+ return this.raycaster.intersectObjects(objects, false);
61651
+ }
61652
+ select(objects, model) {
61653
+ if (!model) {
61654
+ this.viewer.models.forEach((model) => this.select(objects, model));
61620
61655
  return;
61621
- if (object.isLine || object.isLineSegments) {
61622
- const positions = object.geometry.attributes.position.array;
61623
- const indices = object.geometry.index ? object.geometry.index.array : null;
61624
- const lineGeometry = indices
61625
- ? HighlighterUtils.fromIndexedLine(positions, indices)
61626
- : HighlighterUtils.fromNonIndexedLine(positions, object.isLineSegments);
61627
- const wireframe = new Wireframe(lineGeometry, this.highlightLineGlowMaterial);
61628
- wireframe.position.copy(object.position);
61629
- wireframe.rotation.copy(object.rotation);
61630
- wireframe.scale.copy(object.scale);
61631
- object.parent.add(wireframe);
61632
- object.userData.highlightwireframe = wireframe;
61633
- object.userData.originalMaterial = object.material;
61634
- object.material = this.highlightLineMaterial;
61635
- object.isHighlighted = true;
61636
61656
  }
61637
- else if (object.isMesh) {
61638
- const edgesGeometry = new EdgesGeometry(object.geometry, 30);
61639
- const lineGeometry = new LineSegmentsGeometry().fromEdgesGeometry(edgesGeometry);
61640
- const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
61641
- wireframe.position.copy(object.position);
61642
- wireframe.rotation.copy(object.rotation);
61643
- wireframe.scale.copy(object.scale);
61644
- object.parent.add(wireframe);
61645
- object.userData.highlightwireframe = wireframe;
61646
- object.userData.originalMaterial = object.material;
61647
- object.material = this.highlightMaterial;
61648
- object.isHighlighted = true;
61657
+ if (!Array.isArray(objects))
61658
+ objects = [objects];
61659
+ if (!objects.length)
61660
+ return;
61661
+ model.showObjects(objects);
61662
+ model.showOriginalObjects(objects);
61663
+ this.highlighter.highlight(objects);
61664
+ objects.forEach((object) => this.viewer.selected.push(object));
61665
+ objects.forEach((object) => (object.isSelected = true));
61666
+ }
61667
+ deselect(objects, model) {
61668
+ if (!model) {
61669
+ this.viewer.models.forEach((model) => this.select(objects, model));
61670
+ return;
61649
61671
  }
61672
+ if (!Array.isArray(objects))
61673
+ objects = [objects];
61674
+ if (!objects.length)
61675
+ return;
61676
+ this.highlighter.unhighlight(objects);
61677
+ model.hideOriginalObjects(objects);
61678
+ this.viewer.selected = this.viewer.selected.filter((x) => !objects.includes(x));
61679
+ objects.forEach((object) => (object.isSelected = false));
61650
61680
  }
61651
- unhighlight(object) {
61652
- if (!object.isHighlighted)
61681
+ toggleSelection(objects, model) {
61682
+ if (!Array.isArray(objects))
61683
+ objects = [objects];
61684
+ if (!objects.length)
61653
61685
  return;
61654
- object.isHighlighted = false;
61655
- object.material = object.userData.originalMaterial;
61656
- object.userData.highlightwireframe.removeFromParent();
61657
- delete object.userData.originalMaterial;
61658
- delete object.userData.highlightwireframe;
61686
+ if (objects[0].isSelected)
61687
+ this.deselect(objects, model);
61688
+ else
61689
+ this.select(objects, model);
61659
61690
  }
61660
- viewerResize(event) {
61661
- if (!this.outlineMaterial)
61691
+ clearSelection() {
61692
+ if (!this.viewer.selected.length)
61662
61693
  return;
61663
- this.outlineMaterial.resolution.set(event.width, event.height);
61694
+ this.highlighter.unhighlight(this.viewer.selected);
61695
+ this.viewer.models.forEach((model) => model.hideOriginalObjects(this.viewer.selected));
61696
+ this.viewer.selected.forEach((object) => (object.isSelected = false));
61697
+ this.viewer.selected.length = 0;
61664
61698
  }
61665
61699
  }
61666
61700
 
@@ -61869,73 +61903,362 @@ void main() {
61869
61903
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
61870
61904
 
61871
61905
  /**
61872
- * @param {BufferGeometry} geometry
61873
- * @param {number} drawMode
61906
+ * @param {Array<BufferGeometry>} geometries
61907
+ * @param {Boolean} useGroups
61874
61908
  * @return {BufferGeometry}
61875
61909
  */
61876
- function toTrianglesDrawMode( geometry, drawMode ) {
61910
+ function mergeGeometries( geometries, useGroups = false ) {
61877
61911
 
61878
- if ( drawMode === TrianglesDrawMode ) {
61912
+ const isIndexed = geometries[ 0 ].index !== null;
61879
61913
 
61880
- console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
61881
- return geometry;
61914
+ const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
61915
+ const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
61882
61916
 
61883
- }
61917
+ const attributes = {};
61918
+ const morphAttributes = {};
61884
61919
 
61885
- if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) {
61920
+ const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
61886
61921
 
61887
- let index = geometry.getIndex();
61922
+ const mergedGeometry = new BufferGeometry();
61888
61923
 
61889
- // generate index if not present
61924
+ let offset = 0;
61890
61925
 
61891
- if ( index === null ) {
61926
+ for ( let i = 0; i < geometries.length; ++ i ) {
61892
61927
 
61893
- const indices = [];
61928
+ const geometry = geometries[ i ];
61929
+ let attributesCount = 0;
61894
61930
 
61895
- const position = geometry.getAttribute( 'position' );
61931
+ // ensure that all geometries are indexed, or none
61896
61932
 
61897
- if ( position !== undefined ) {
61933
+ if ( isIndexed !== ( geometry.index !== null ) ) {
61898
61934
 
61899
- for ( let i = 0; i < position.count; i ++ ) {
61935
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );
61936
+ return null;
61900
61937
 
61901
- indices.push( i );
61938
+ }
61902
61939
 
61903
- }
61940
+ // gather attributes, exit early if they're different
61904
61941
 
61905
- geometry.setIndex( indices );
61906
- index = geometry.getIndex();
61942
+ for ( const name in geometry.attributes ) {
61907
61943
 
61908
- } else {
61944
+ if ( ! attributesUsed.has( name ) ) {
61909
61945
 
61910
- console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
61911
- return geometry;
61946
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );
61947
+ return null;
61912
61948
 
61913
61949
  }
61914
61950
 
61915
- }
61951
+ if ( attributes[ name ] === undefined ) attributes[ name ] = [];
61916
61952
 
61917
- //
61953
+ attributes[ name ].push( geometry.attributes[ name ] );
61918
61954
 
61919
- const numberOfTriangles = index.count - 2;
61920
- const newIndices = [];
61955
+ attributesCount ++;
61921
61956
 
61922
- if ( drawMode === TriangleFanDrawMode ) {
61957
+ }
61923
61958
 
61924
- // gl.TRIANGLE_FAN
61959
+ // ensure geometries have the same number of attributes
61925
61960
 
61926
- for ( let i = 1; i <= numberOfTriangles; i ++ ) {
61961
+ if ( attributesCount !== attributesUsed.size ) {
61927
61962
 
61928
- newIndices.push( index.getX( 0 ) );
61929
- newIndices.push( index.getX( i ) );
61930
- newIndices.push( index.getX( i + 1 ) );
61963
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
61964
+ return null;
61931
61965
 
61932
- }
61966
+ }
61933
61967
 
61934
- } else {
61968
+ // gather morph attributes, exit early if they're different
61935
61969
 
61936
- // gl.TRIANGLE_STRIP
61970
+ if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
61937
61971
 
61938
- for ( let i = 0; i < numberOfTriangles; i ++ ) {
61972
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
61973
+ return null;
61974
+
61975
+ }
61976
+
61977
+ for ( const name in geometry.morphAttributes ) {
61978
+
61979
+ if ( ! morphAttributesUsed.has( name ) ) {
61980
+
61981
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' );
61982
+ return null;
61983
+
61984
+ }
61985
+
61986
+ if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
61987
+
61988
+ morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
61989
+
61990
+ }
61991
+
61992
+ if ( useGroups ) {
61993
+
61994
+ let count;
61995
+
61996
+ if ( isIndexed ) {
61997
+
61998
+ count = geometry.index.count;
61999
+
62000
+ } else if ( geometry.attributes.position !== undefined ) {
62001
+
62002
+ count = geometry.attributes.position.count;
62003
+
62004
+ } else {
62005
+
62006
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
62007
+ return null;
62008
+
62009
+ }
62010
+
62011
+ mergedGeometry.addGroup( offset, count, i );
62012
+
62013
+ offset += count;
62014
+
62015
+ }
62016
+
62017
+ }
62018
+
62019
+ // merge indices
62020
+
62021
+ if ( isIndexed ) {
62022
+
62023
+ let indexOffset = 0;
62024
+ const mergedIndex = [];
62025
+
62026
+ for ( let i = 0; i < geometries.length; ++ i ) {
62027
+
62028
+ const index = geometries[ i ].index;
62029
+
62030
+ for ( let j = 0; j < index.count; ++ j ) {
62031
+
62032
+ mergedIndex.push( index.getX( j ) + indexOffset );
62033
+
62034
+ }
62035
+
62036
+ indexOffset += geometries[ i ].attributes.position.count;
62037
+
62038
+ }
62039
+
62040
+ mergedGeometry.setIndex( mergedIndex );
62041
+
62042
+ }
62043
+
62044
+ // merge attributes
62045
+
62046
+ for ( const name in attributes ) {
62047
+
62048
+ const mergedAttribute = mergeAttributes( attributes[ name ] );
62049
+
62050
+ if ( ! mergedAttribute ) {
62051
+
62052
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' );
62053
+ return null;
62054
+
62055
+ }
62056
+
62057
+ mergedGeometry.setAttribute( name, mergedAttribute );
62058
+
62059
+ }
62060
+
62061
+ // merge morph attributes
62062
+
62063
+ for ( const name in morphAttributes ) {
62064
+
62065
+ const numMorphTargets = morphAttributes[ name ][ 0 ].length;
62066
+
62067
+ if ( numMorphTargets === 0 ) break;
62068
+
62069
+ mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
62070
+ mergedGeometry.morphAttributes[ name ] = [];
62071
+
62072
+ for ( let i = 0; i < numMorphTargets; ++ i ) {
62073
+
62074
+ const morphAttributesToMerge = [];
62075
+
62076
+ for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
62077
+
62078
+ morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
62079
+
62080
+ }
62081
+
62082
+ const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge );
62083
+
62084
+ if ( ! mergedMorphAttribute ) {
62085
+
62086
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
62087
+ return null;
62088
+
62089
+ }
62090
+
62091
+ mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
62092
+
62093
+ }
62094
+
62095
+ }
62096
+
62097
+ return mergedGeometry;
62098
+
62099
+ }
62100
+
62101
+ /**
62102
+ * @param {Array<BufferAttribute>} attributes
62103
+ * @return {BufferAttribute}
62104
+ */
62105
+ function mergeAttributes( attributes ) {
62106
+
62107
+ let TypedArray;
62108
+ let itemSize;
62109
+ let normalized;
62110
+ let gpuType = -1;
62111
+ let arrayLength = 0;
62112
+
62113
+ for ( let i = 0; i < attributes.length; ++ i ) {
62114
+
62115
+ const attribute = attributes[ i ];
62116
+
62117
+ if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
62118
+ if ( TypedArray !== attribute.array.constructor ) {
62119
+
62120
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' );
62121
+ return null;
62122
+
62123
+ }
62124
+
62125
+ if ( itemSize === undefined ) itemSize = attribute.itemSize;
62126
+ if ( itemSize !== attribute.itemSize ) {
62127
+
62128
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' );
62129
+ return null;
62130
+
62131
+ }
62132
+
62133
+ if ( normalized === undefined ) normalized = attribute.normalized;
62134
+ if ( normalized !== attribute.normalized ) {
62135
+
62136
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' );
62137
+ return null;
62138
+
62139
+ }
62140
+
62141
+ if ( gpuType === -1 ) gpuType = attribute.gpuType;
62142
+ if ( gpuType !== attribute.gpuType ) {
62143
+
62144
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' );
62145
+ return null;
62146
+
62147
+ }
62148
+
62149
+ arrayLength += attribute.count * itemSize;
62150
+
62151
+ }
62152
+
62153
+ const array = new TypedArray( arrayLength );
62154
+ const result = new BufferAttribute( array, itemSize, normalized );
62155
+ let offset = 0;
62156
+
62157
+ for ( let i = 0; i < attributes.length; ++ i ) {
62158
+
62159
+ const attribute = attributes[ i ];
62160
+ if ( attribute.isInterleavedBufferAttribute ) {
62161
+
62162
+ const tupleOffset = offset / itemSize;
62163
+ for ( let j = 0, l = attribute.count; j < l; j ++ ) {
62164
+
62165
+ for ( let c = 0; c < itemSize; c ++ ) {
62166
+
62167
+ const value = attribute.getComponent( j, c );
62168
+ result.setComponent( j + tupleOffset, c, value );
62169
+
62170
+ }
62171
+
62172
+ }
62173
+
62174
+ } else {
62175
+
62176
+ array.set( attribute.array, offset );
62177
+
62178
+ }
62179
+
62180
+ offset += attribute.count * itemSize;
62181
+
62182
+ }
62183
+
62184
+ if ( gpuType !== undefined ) {
62185
+
62186
+ result.gpuType = gpuType;
62187
+
62188
+ }
62189
+
62190
+ return result;
62191
+
62192
+ }
62193
+
62194
+ /**
62195
+ * @param {BufferGeometry} geometry
62196
+ * @param {number} drawMode
62197
+ * @return {BufferGeometry}
62198
+ */
62199
+ function toTrianglesDrawMode( geometry, drawMode ) {
62200
+
62201
+ if ( drawMode === TrianglesDrawMode ) {
62202
+
62203
+ console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
62204
+ return geometry;
62205
+
62206
+ }
62207
+
62208
+ if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) {
62209
+
62210
+ let index = geometry.getIndex();
62211
+
62212
+ // generate index if not present
62213
+
62214
+ if ( index === null ) {
62215
+
62216
+ const indices = [];
62217
+
62218
+ const position = geometry.getAttribute( 'position' );
62219
+
62220
+ if ( position !== undefined ) {
62221
+
62222
+ for ( let i = 0; i < position.count; i ++ ) {
62223
+
62224
+ indices.push( i );
62225
+
62226
+ }
62227
+
62228
+ geometry.setIndex( indices );
62229
+ index = geometry.getIndex();
62230
+
62231
+ } else {
62232
+
62233
+ console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
62234
+ return geometry;
62235
+
62236
+ }
62237
+
62238
+ }
62239
+
62240
+ //
62241
+
62242
+ const numberOfTriangles = index.count - 2;
62243
+ const newIndices = [];
62244
+
62245
+ if ( drawMode === TriangleFanDrawMode ) {
62246
+
62247
+ // gl.TRIANGLE_FAN
62248
+
62249
+ for ( let i = 1; i <= numberOfTriangles; i ++ ) {
62250
+
62251
+ newIndices.push( index.getX( 0 ) );
62252
+ newIndices.push( index.getX( i ) );
62253
+ newIndices.push( index.getX( i + 1 ) );
62254
+
62255
+ }
62256
+
62257
+ } else {
62258
+
62259
+ // gl.TRIANGLE_STRIP
62260
+
62261
+ for ( let i = 0; i < numberOfTriangles; i ++ ) {
61939
62262
 
61940
62263
  if ( i % 2 === 0 ) {
61941
62264
 
@@ -66554,151 +66877,2514 @@ void main() {
66554
66877
 
66555
66878
  }
66556
66879
 
66557
- }
66880
+ }
66881
+
66882
+ }
66883
+
66884
+ // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
66885
+ box.expandByVector( maxDisplacement );
66886
+
66887
+ }
66888
+
66889
+ geometry.boundingBox = box;
66890
+
66891
+ const sphere = new Sphere();
66892
+
66893
+ box.getCenter( sphere.center );
66894
+ sphere.radius = box.min.distanceTo( box.max ) / 2;
66895
+
66896
+ geometry.boundingSphere = sphere;
66897
+
66898
+ }
66899
+
66900
+ /**
66901
+ * @param {BufferGeometry} geometry
66902
+ * @param {GLTF.Primitive} primitiveDef
66903
+ * @param {GLTFParser} parser
66904
+ * @return {Promise<BufferGeometry>}
66905
+ */
66906
+ function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
66907
+
66908
+ const attributes = primitiveDef.attributes;
66909
+
66910
+ const pending = [];
66911
+
66912
+ function assignAttributeAccessor( accessorIndex, attributeName ) {
66913
+
66914
+ return parser.getDependency( 'accessor', accessorIndex )
66915
+ .then( function ( accessor ) {
66916
+
66917
+ geometry.setAttribute( attributeName, accessor );
66918
+
66919
+ } );
66920
+
66921
+ }
66922
+
66923
+ for ( const gltfAttributeName in attributes ) {
66924
+
66925
+ const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
66926
+
66927
+ // Skip attributes already provided by e.g. Draco extension.
66928
+ if ( threeAttributeName in geometry.attributes ) continue;
66929
+
66930
+ pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
66931
+
66932
+ }
66933
+
66934
+ if ( primitiveDef.indices !== undefined && ! geometry.index ) {
66935
+
66936
+ const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
66937
+
66938
+ geometry.setIndex( accessor );
66939
+
66940
+ } );
66941
+
66942
+ pending.push( accessor );
66943
+
66944
+ }
66945
+
66946
+ if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) {
66947
+
66948
+ console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` );
66949
+
66950
+ }
66951
+
66952
+ assignExtrasToUserData( geometry, primitiveDef );
66953
+
66954
+ computeBounds( geometry, primitiveDef, parser );
66955
+
66956
+ return Promise.all( pending ).then( function () {
66957
+
66958
+ return primitiveDef.targets !== undefined
66959
+ ? addMorphTargets( geometry, primitiveDef.targets, parser )
66960
+ : geometry;
66961
+
66962
+ } );
66963
+
66964
+ }
66965
+
66966
+ ///////////////////////////////////////////////////////////////////////////////
66967
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
66968
+ // All rights reserved.
66969
+ //
66970
+ // This software and its documentation and related materials are owned by
66971
+ // the Alliance. The software may only be incorporated into application
66972
+ // programs owned by members of the Alliance, subject to a signed
66973
+ // Membership Agreement and Supplemental Software License Agreement with the
66974
+ // Alliance. The structure and organization of this software are the valuable
66975
+ // trade secrets of the Alliance and its suppliers. The software is also
66976
+ // protected by copyright law and international treaty provisions. Application
66977
+ // programs incorporating this software must include the following statement
66978
+ // with their copyright notices:
66979
+ //
66980
+ // This application incorporates Open Design Alliance software pursuant to a
66981
+ // license agreement with Open Design Alliance.
66982
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
66983
+ // All rights reserved.
66984
+ //
66985
+ // By use of this software, its documentation or related materials, you
66986
+ // acknowledge and accept the above terms.
66987
+ ///////////////////////////////////////////////////////////////////////////////
66988
+ class GLTFLoadingManager extends LoadingManager {
66989
+ constructor(file, params = {}) {
66990
+ super();
66991
+ this.path = "";
66992
+ this.resourcePath = "";
66993
+ this.fileURL = "";
66994
+ this.dataURLs = new Map();
66995
+ this.path = params.path || "";
66996
+ const externalFiles = params.externalFiles || new Map();
66997
+ if (typeof file === "string") {
66998
+ this.fileURL = file;
66999
+ this.resourcePath = LoaderUtils.extractUrlBase(file);
67000
+ }
67001
+ else {
67002
+ externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
67003
+ externalFiles.set(this.fileURL, file);
67004
+ }
67005
+ externalFiles.forEach((value, key) => {
67006
+ let dataURL;
67007
+ if (typeof value === "string")
67008
+ dataURL = value;
67009
+ else
67010
+ dataURL = URL.createObjectURL(new Blob([value]));
67011
+ this.dataURLs.set(key, dataURL);
67012
+ });
67013
+ this.setURLModifier((url) => {
67014
+ const key = decodeURI(url)
67015
+ .replace(this.path, "")
67016
+ .replace(this.resourcePath, "")
67017
+ .replace(/^(\.?\/)/, "");
67018
+ const dataURL = this.dataURLs.get(key);
67019
+ return dataURL !== null && dataURL !== undefined ? dataURL : url;
67020
+ });
67021
+ }
67022
+ dispose() {
67023
+ this.dataURLs.forEach(URL.revokeObjectURL);
67024
+ }
67025
+ }
67026
+
67027
+ ///////////////////////////////////////////////////////////////////////////////
67028
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
67029
+ // All rights reserved.
67030
+ //
67031
+ // This software and its documentation and related materials are owned by
67032
+ // the Alliance. The software may only be incorporated into application
67033
+ // programs owned by members of the Alliance, subject to a signed
67034
+ // Membership Agreement and Supplemental Software License Agreement with the
67035
+ // Alliance. The structure and organization of this software are the valuable
67036
+ // trade secrets of the Alliance and its suppliers. The software is also
67037
+ // protected by copyright law and international treaty provisions. Application
67038
+ // programs incorporating this software must include the following statement
67039
+ // with their copyright notices:
67040
+ //
67041
+ // This application incorporates Open Design Alliance software pursuant to a
67042
+ // license agreement with Open Design Alliance.
67043
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
67044
+ // All rights reserved.
67045
+ //
67046
+ // By use of this software, its documentation or related materials, you
67047
+ // acknowledge and accept the above terms.
67048
+ ///////////////////////////////////////////////////////////////////////////////
67049
+ /**
67050
+ * Basic model implementation.
67051
+ */
67052
+ class ModelImpl {
67053
+ constructor(scene) {
67054
+ this.handle = "1";
67055
+ this.scene = scene;
67056
+ }
67057
+ dispose() {
67058
+ function disposeMaterial(material) {
67059
+ // if (material.alphaMap) material.alphaMap.dispose();
67060
+ // if (material.anisotropyMap) material.anisotropyMap.dispose();
67061
+ // if (material.aoMap) material.aoMap.dispose();
67062
+ // if (material.bumpMap) material.bumpMap.dispose();
67063
+ // if (material.clearcoatMap) material.clearcoatMap.dispose();
67064
+ // if (material.clearcoatNormalMap) material.clearcoatNormalMap.dispose();
67065
+ // if (material.clearcoatRoughnessMap) material.clearcoatRoughnessMap.dispose();
67066
+ // if (material.displacementMap) material.displacementMap.dispose();
67067
+ // if (material.emissiveMap) material.emissiveMap.dispose();
67068
+ // if (material.gradientMap) material.gradientMap.dispose();
67069
+ // if (material.envMap) material.envMap.dispose();
67070
+ // if (material.iridescenceMap) material.iridescenceMap.dispose();
67071
+ // if (material.lightMap) material.lightMap.dispose();
67072
+ // if (material.map) material.map.dispose();
67073
+ // if (material.metalnessMap) material.metalnessMap.dispose();
67074
+ // if (material.normalMap) material.normalMap.dispose();
67075
+ // if (material.roughnessMap) material.roughnessMap.dispose();
67076
+ // if (material.sheenColorMap) material.sheenColorMap.dispose();
67077
+ // if (material.sheenRoughnessMap) material.sheenRoughnessMap.dispose();
67078
+ // if (material.specularMap) material.specularMap.dispose();
67079
+ // if (material.thicknessMap) material.thicknessMap.dispose();
67080
+ // if (material.transmissionMap) material.transmissionMap.dispose();
67081
+ material.dispose();
67082
+ }
67083
+ function disposeMaterials(material) {
67084
+ const materials = Array.isArray(material) ? material : [material];
67085
+ materials.forEach((material) => disposeMaterial(material));
67086
+ }
67087
+ function disposeObject(object) {
67088
+ if (object.geometry)
67089
+ object.geometry.dispose();
67090
+ if (object.material)
67091
+ disposeMaterials(object.material);
67092
+ }
67093
+ this.scene.traverse(disposeObject);
67094
+ this.scene.clear();
67095
+ }
67096
+ getExtents(target) {
67097
+ this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
67098
+ return target;
67099
+ }
67100
+ getObjects() {
67101
+ const objects = [];
67102
+ this.scene.traverse((object) => objects.push(object));
67103
+ return objects;
67104
+ }
67105
+ getVisibleObjects() {
67106
+ const objects = [];
67107
+ this.scene.traverseVisible((object) => objects.push(object));
67108
+ return objects;
67109
+ }
67110
+ hasObject(object) {
67111
+ while (object) {
67112
+ if (object === this.scene)
67113
+ return true;
67114
+ object = object.parent;
67115
+ }
67116
+ return false;
67117
+ }
67118
+ getOwnObjects(objects) {
67119
+ if (!Array.isArray(objects))
67120
+ objects = [objects];
67121
+ return objects.filter((object) => this.hasObject(object));
67122
+ }
67123
+ getObjectsByHandles(handles) {
67124
+ const handleSet = new Set(handles);
67125
+ const objects = [];
67126
+ this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
67127
+ return objects;
67128
+ }
67129
+ getHandlesByObjects(objects) {
67130
+ if (!Array.isArray(objects))
67131
+ objects = [objects];
67132
+ const handlesSet = new Set();
67133
+ this.getOwnObjects(objects).forEach((object) => handlesSet.add(object.userData.handle));
67134
+ return Array.from(handlesSet);
67135
+ }
67136
+ hideObjects(objects) {
67137
+ if (!Array.isArray(objects))
67138
+ objects = [objects];
67139
+ this.getOwnObjects(objects).forEach((object) => (object.visible = false));
67140
+ return this;
67141
+ }
67142
+ hideAllObjects() {
67143
+ return this.isolateObjects([]);
67144
+ }
67145
+ isolateObjects(objects) {
67146
+ if (!Array.isArray(objects))
67147
+ objects = [objects];
67148
+ const visibleSet = new Set(objects);
67149
+ this.getOwnObjects(objects).forEach((object) => object.traverseAncestors((parent) => visibleSet.add(parent)));
67150
+ this.scene.traverse((object) => (object.visible = visibleSet.has(object)));
67151
+ return this;
67152
+ }
67153
+ showObjects(objects) {
67154
+ if (!Array.isArray(objects))
67155
+ objects = [objects];
67156
+ this.getOwnObjects(objects).forEach((object) => {
67157
+ object.visible = true;
67158
+ object.traverseAncestors((parent) => (parent.visible = true));
67159
+ });
67160
+ return this;
67161
+ }
67162
+ showAllObjects() {
67163
+ this.scene.traverse((object) => (object.visible = true));
67164
+ return this;
67165
+ }
67166
+ showOriginalObjects(objects) {
67167
+ return this;
67168
+ }
67169
+ hideOriginalObjects(objects) {
67170
+ return this;
67171
+ }
67172
+ explode(scale = 0, coeff = 4) {
67173
+ function calcExplodeDepth(object, depth) {
67174
+ let res = depth;
67175
+ object.children.forEach((x) => {
67176
+ const objectDepth = calcExplodeDepth(x, depth + 1);
67177
+ if (res < objectDepth)
67178
+ res = objectDepth;
67179
+ });
67180
+ object.userData.originalPosition = object.position.clone();
67181
+ object.userData.originalCenter = new Box3().setFromObject(object).getCenter(new Vector3());
67182
+ object.userData.isExplodeLocked = depth > 2 && object.children.length === 0;
67183
+ return res;
67184
+ }
67185
+ scale /= 100;
67186
+ if (!this.scene.userData.explodeDepth)
67187
+ this.scene.userData.explodeDepth = calcExplodeDepth(this.scene, 1);
67188
+ const maxDepth = this.scene.userData.explodeDepth;
67189
+ const scaledExplodeDepth = scale * maxDepth + 1;
67190
+ const explodeDepth = 0 | scaledExplodeDepth;
67191
+ const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
67192
+ function explodeObject(object, depth) {
67193
+ object.position.copy(object.userData.originalPosition);
67194
+ if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
67195
+ let objectScale = scale * coeff;
67196
+ if (depth === explodeDepth)
67197
+ objectScale *= currentSegmentFraction;
67198
+ const parentCenter = object.parent.userData.originalCenter;
67199
+ const objectCenter = object.userData.originalCenter;
67200
+ const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
67201
+ object.position.add(objectOffset);
67202
+ }
67203
+ object.children.forEach((x) => explodeObject(x, depth + 1));
67204
+ }
67205
+ explodeObject(this.scene, 0);
67206
+ this.scene.updateMatrixWorld();
67207
+ return this;
67208
+ }
67209
+ }
67210
+
67211
+ ///////////////////////////////////////////////////////////////////////////////
67212
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
67213
+ // All rights reserved.
67214
+ //
67215
+ // This software and its documentation and related materials are owned by
67216
+ // the Alliance. The software may only be incorporated into application
67217
+ // programs owned by members of the Alliance, subject to a signed
67218
+ // Membership Agreement and Supplemental Software License Agreement with the
67219
+ // Alliance. The structure and organization of this software are the valuable
67220
+ // trade secrets of the Alliance and its suppliers. The software is also
67221
+ // protected by copyright law and international treaty provisions. Application
67222
+ // programs incorporating this software must include the following statement
67223
+ // with their copyright notices:
67224
+ //
67225
+ // This application incorporates Open Design Alliance software pursuant to a
67226
+ // license agreement with Open Design Alliance.
67227
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
67228
+ // All rights reserved.
67229
+ //
67230
+ // By use of this software, its documentation or related materials, you
67231
+ // acknowledge and accept the above terms.
67232
+ ///////////////////////////////////////////////////////////////////////////////
67233
+ class GLTFFileLoader extends Loader$1 {
67234
+ constructor(viewer) {
67235
+ super();
67236
+ this.viewer = viewer;
67237
+ }
67238
+ isSupport(file, format) {
67239
+ return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
67240
+ /(gltf|glb)$/i.test(format));
67241
+ }
67242
+ async load(file, format, params) {
67243
+ const manager = new GLTFLoadingManager(file, params);
67244
+ const loader = new GLTFLoader(manager);
67245
+ loader.setPath(manager.path);
67246
+ loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
67247
+ loader.setWithCredentials(params.withCredentials || loader.withCredentials);
67248
+ const progress = (event) => {
67249
+ const { lengthComputable, loaded, total } = event;
67250
+ const progress = lengthComputable ? loaded / total : 1;
67251
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
67252
+ };
67253
+ const gltf = await loader.loadAsync(manager.fileURL, progress);
67254
+ if (!this.viewer.scene)
67255
+ return this;
67256
+ const modelImpl = new ModelImpl(gltf.scene);
67257
+ modelImpl.loader = this;
67258
+ modelImpl.viewer = this.viewer;
67259
+ this.viewer.scene.add(modelImpl.scene);
67260
+ this.viewer.models.push(modelImpl);
67261
+ this.viewer.syncOptions();
67262
+ this.viewer.syncOverlay();
67263
+ this.viewer.update();
67264
+ this.viewer.emitEvent({ type: "databasechunk", data: gltf.scene, file });
67265
+ return this;
67266
+ }
67267
+ }
67268
+
67269
+ ///////////////////////////////////////////////////////////////////////////////
67270
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
67271
+ // All rights reserved.
67272
+ //
67273
+ // This software and its documentation and related materials are owned by
67274
+ // the Alliance. The software may only be incorporated into application
67275
+ // programs owned by members of the Alliance, subject to a signed
67276
+ // Membership Agreement and Supplemental Software License Agreement with the
67277
+ // Alliance. The structure and organization of this software are the valuable
67278
+ // trade secrets of the Alliance and its suppliers. The software is also
67279
+ // protected by copyright law and international treaty provisions. Application
67280
+ // programs incorporating this software must include the following statement
67281
+ // with their copyright notices:
67282
+ //
67283
+ // This application incorporates Open Design Alliance software pursuant to a
67284
+ // license agreement with Open Design Alliance.
67285
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
67286
+ // All rights reserved.
67287
+ //
67288
+ // By use of this software, its documentation or related materials, you
67289
+ // acknowledge and accept the above terms.
67290
+ ///////////////////////////////////////////////////////////////////////////////
67291
+ /**
67292
+ * Dynamic model implementation.
67293
+ */
67294
+ class DynamicModelImpl extends ModelImpl {
67295
+ getExtents(target) {
67296
+ return target.union(this.gltfLoader.getTotalGeometryExtent());
67297
+ }
67298
+ getObjects() {
67299
+ const objects = [];
67300
+ this.gltfLoader.originalObjects.forEach((object) => {
67301
+ objects.push(object);
67302
+ });
67303
+ return objects;
67304
+ }
67305
+ getVisibleObjects() {
67306
+ return this.gltfLoader.getOriginalObjectForSelect();
67307
+ }
67308
+ hasObject(object) {
67309
+ return this.gltfLoader.originalObjects.has(object);
67310
+ }
67311
+ getObjectsByHandles(handles) {
67312
+ const handlesSet = new Set(handles);
67313
+ const objects = [];
67314
+ handlesSet.forEach((handle) => {
67315
+ const handles = this.gltfLoader.handleToObjects.get(handle) || [];
67316
+ objects.push(...Array.from(handles));
67317
+ });
67318
+ return objects;
67319
+ }
67320
+ hideObjects(objects) {
67321
+ this.getOwnObjects(objects)
67322
+ .map((object) => object.userData.handle)
67323
+ .forEach((handle) => this.gltfLoader.hiddenHandles.add(handle));
67324
+ this.gltfLoader.syncHiddenObjects();
67325
+ return this;
67326
+ }
67327
+ isolateObjects(objects) {
67328
+ const handles = this.getHandlesByObjects(objects);
67329
+ this.gltfLoader.isolateObjects(new Set(handles));
67330
+ return this;
67331
+ }
67332
+ showObjects(objects) {
67333
+ this.getOwnObjects(objects)
67334
+ .map((object) => object.userData.handle)
67335
+ .forEach((handle) => this.gltfLoader.hiddenHandles.delete(handle));
67336
+ this.gltfLoader.syncHiddenObjects();
67337
+ return this;
67338
+ }
67339
+ showAllObjects() {
67340
+ this.gltfLoader.hiddenHandles.clear();
67341
+ this.gltfLoader.syncHiddenObjects();
67342
+ return this;
67343
+ }
67344
+ showOriginalObjects(objects) {
67345
+ this.getOwnObjects(objects).forEach((object) => (object.visible = true));
67346
+ return this;
67347
+ }
67348
+ hideOriginalObjects(objects) {
67349
+ this.getOwnObjects(objects).forEach((object) => (object.visible = false));
67350
+ return this;
67351
+ }
67352
+ }
67353
+
67354
+ const GL_COMPONENT_TYPES = {
67355
+ 5120: Int8Array,
67356
+ 5121: Uint8Array,
67357
+ 5122: Int16Array,
67358
+ 5123: Uint16Array,
67359
+ 5125: Uint32Array,
67360
+ 5126: Float32Array,
67361
+ };
67362
+
67363
+ const GL_CONSTANTS = {
67364
+ POINTS: 0,
67365
+ LINES: 1,
67366
+ LINE_LOOP: 2,
67367
+ LINE_STRIP: 3,
67368
+ TRIANGLES: 4,
67369
+ TRIANGLE_STRIP: 5,
67370
+ TRIANGLE_FAN: 6};
67371
+
67372
+ class GltfStructure {
67373
+ constructor(id) {
67374
+ this.id = `${id}`;
67375
+ this.json = null;
67376
+ this.baseUrl = "";
67377
+
67378
+ // Binary manager properties
67379
+ this.loadController = null;
67380
+ // Request batching parameters
67381
+ this.batchDelay = 10;
67382
+ this.maxBatchSize = 5 * 1024 * 1024;
67383
+ this.maxRangesPerRequest = 512;
67384
+
67385
+ // Request queue
67386
+ this.pendingRequests = [];
67387
+ this.batchTimeout = null;
67388
+
67389
+ // Material and texture properties
67390
+ this.textureLoader = new TextureLoader();
67391
+ this.materials = new Map();
67392
+ this.textureCache = new Map();
67393
+ }
67394
+
67395
+ async initialize(loadController) {
67396
+ this.json = await loadController.loadJson();
67397
+ this.baseUrl = await loadController.baseUrl();
67398
+ this.loadController = loadController;
67399
+ }
67400
+
67401
+ clear() {
67402
+ this.json = null;
67403
+ this.baseUrl = "";
67404
+ this.loadController = null;
67405
+ this.pendingRequests = [];
67406
+ if (this.batchTimeout) {
67407
+ clearTimeout(this.batchTimeout);
67408
+ this.batchTimeout = null;
67409
+ }
67410
+
67411
+ // Clear materials and textures
67412
+ this.disposeMaterials();
67413
+ this.textureCache.clear();
67414
+ this.materials.clear();
67415
+ }
67416
+
67417
+ getJson() {
67418
+ return this.json;
67419
+ }
67420
+
67421
+ // Schedule a request for processing
67422
+ scheduleRequest(request) {
67423
+ this.pendingRequests.push(request);
67424
+
67425
+ // Clear existing timeout
67426
+ if (this.batchTimeout) {
67427
+ clearTimeout(this.batchTimeout);
67428
+ }
67429
+
67430
+ // Set new timeout for batch processing
67431
+ this.batchTimeout = setTimeout(() => this.processBatch(), this.batchDelay);
67432
+
67433
+ // Return a promise that will resolve when the data is available
67434
+ return new Promise((resolve, reject) => {
67435
+ request.resolve = resolve;
67436
+ request.reject = reject;
67437
+ });
67438
+ }
67439
+
67440
+ async processBatch() {
67441
+ if (this.pendingRequests.length === 0) return;
67442
+
67443
+ // Take current batch of requests and clear timeout
67444
+ const currentBatch = [...this.pendingRequests];
67445
+ this.pendingRequests = [];
67446
+
67447
+ if (this.batchTimeout) {
67448
+ clearTimeout(this.batchTimeout);
67449
+ this.batchTimeout = null;
67450
+ }
67451
+ try {
67452
+ // Split requests into smaller groups
67453
+ for (let i = 0; i < currentBatch.length; i += this.maxRangesPerRequest) {
67454
+ const batchRequests = currentBatch.slice(i, i + this.maxRangesPerRequest);
67455
+ const buffer = await this.loadController.loadBinaryData(batchRequests);
67456
+
67457
+ let currentOffset = 0;
67458
+ batchRequests.forEach((request) => {
67459
+ const view = this.createTypedArray(buffer, currentOffset, request.length, request.componentType);
67460
+ request.resolve(view);
67461
+ currentOffset += request.length;
67462
+ });
67463
+ }
67464
+ } catch (error) {
67465
+ console.error("Error processing batch:", error);
67466
+ currentBatch.forEach((request) => request.reject(error));
67467
+ }
67468
+
67469
+ if (this.pendingRequests.length > 0) {
67470
+ this.batchTimeout = setTimeout(() => this.processBatch(), this.batchDelay);
67471
+ }
67472
+ }
67473
+
67474
+ getBufferView(byteOffset, byteLength, componentType) {
67475
+ return this.scheduleRequest({
67476
+ offset: byteOffset,
67477
+ length: byteLength,
67478
+ componentType,
67479
+ });
67480
+ }
67481
+
67482
+ createTypedArray(buffer, offset, length, componentType) {
67483
+ try {
67484
+ // Validate parameters
67485
+ if (!buffer || !(buffer instanceof ArrayBuffer)) {
67486
+ throw new Error("Invalid buffer");
67487
+ }
67488
+
67489
+ // Calculate element size for given type
67490
+ let elementSize;
67491
+ switch (componentType) {
67492
+ case 5120:
67493
+ case 5121:
67494
+ elementSize = 1;
67495
+ break; // BYTE, UNSIGNED_BYTE
67496
+ case 5122:
67497
+ case 5123:
67498
+ elementSize = 2;
67499
+ break; // SHORT, UNSIGNED_SHORT
67500
+ case 5125:
67501
+ case 5126:
67502
+ elementSize = 4;
67503
+ break; // UNSIGNED_INT, FLOAT
67504
+ default:
67505
+ throw new Error(`Unsupported component type: ${componentType}`);
67506
+ }
67507
+
67508
+ // Check if requested length is correct
67509
+ const numElements = length / elementSize;
67510
+ if (!Number.isInteger(numElements)) {
67511
+ throw new Error(`Invalid length ${length} for component type ${componentType}`);
67512
+ }
67513
+
67514
+ // Check if buffer is large enough
67515
+ if (length > buffer.byteLength) {
67516
+ throw new Error(`Buffer too small: need ${length} bytes, but buffer is ${buffer.byteLength} bytes`);
67517
+ }
67518
+
67519
+ // Create appropriate typed array
67520
+ const ArrayType = GL_COMPONENT_TYPES[componentType];
67521
+ return new ArrayType(buffer, offset, numElements);
67522
+ } catch (error) {
67523
+ if (error.name !== "AbortError") {
67524
+ console.error("Error creating typed array:", {
67525
+ bufferSize: buffer?.byteLength,
67526
+ offset,
67527
+ length,
67528
+ componentType,
67529
+ error,
67530
+ });
67531
+ }
67532
+ throw error;
67533
+ }
67534
+ }
67535
+
67536
+ async createBufferAttribute(accessorIndex) {
67537
+ if (!this.json) {
67538
+ throw new Error("No GLTF structure loaded");
67539
+ }
67540
+
67541
+ const gltf = this.json;
67542
+ const accessor = gltf.accessors[accessorIndex];
67543
+ const bufferView = gltf.bufferViews[accessor.bufferView];
67544
+
67545
+ try {
67546
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
67547
+ const components = this.getNumComponents(accessor.type);
67548
+ const count = accessor.count;
67549
+ const byteLength = count * components * this.getComponentSize(accessor.componentType);
67550
+
67551
+ const array = await this.getBufferView(byteOffset, byteLength, accessor.componentType);
67552
+
67553
+ const attribute = new BufferAttribute(array, components);
67554
+
67555
+ if (accessor.min !== undefined) attribute.min = accessor.min;
67556
+ if (accessor.max !== undefined) attribute.max = accessor.max;
67557
+
67558
+ return attribute;
67559
+ } catch (error) {
67560
+ if (error.name !== "AbortError") {
67561
+ console.error("Error creating buffer attribute:", {
67562
+ error,
67563
+ accessor,
67564
+ bufferView,
67565
+ });
67566
+ }
67567
+
67568
+ throw error;
67569
+ }
67570
+ }
67571
+
67572
+ getComponentSize(componentType) {
67573
+ switch (componentType) {
67574
+ case 5120: // BYTE
67575
+ case 5121: // UNSIGNED_BYTE
67576
+ return 1;
67577
+ case 5122: // SHORT
67578
+ case 5123: // UNSIGNED_SHORT
67579
+ return 2;
67580
+ case 5125: // UNSIGNED_INT
67581
+ case 5126: // FLOAT
67582
+ return 4;
67583
+ default:
67584
+ throw new Error(`Unknown component type: ${componentType}`);
67585
+ }
67586
+ }
67587
+
67588
+ getNumComponents(type) {
67589
+ switch (type) {
67590
+ case "SCALAR":
67591
+ return 1;
67592
+ case "VEC2":
67593
+ return 2;
67594
+ case "VEC3":
67595
+ return 3;
67596
+ case "VEC4":
67597
+ return 4;
67598
+ case "MAT2":
67599
+ return 4;
67600
+ case "MAT3":
67601
+ return 9;
67602
+ case "MAT4":
67603
+ return 16;
67604
+ default:
67605
+ throw new Error(`Unknown type: ${type}`);
67606
+ }
67607
+ }
67608
+
67609
+ async loadTextures() {
67610
+ if (!this.json.textures) return;
67611
+
67612
+ const loadTexture = async (imageIndex) => {
67613
+ const image = this.json.images[imageIndex];
67614
+
67615
+ if (image.uri) {
67616
+ // Handle base64 or URL
67617
+ if (image.uri.startsWith("data:")) {
67618
+ return await this.textureLoader.loadAsync(image.uri);
67619
+ } else {
67620
+ const fullUrl = this.baseUrl + image.uri;
67621
+ return await this.textureLoader.loadAsync(fullUrl);
67622
+ }
67623
+ } else if (image.bufferView !== undefined) {
67624
+ // Handle embedded binary data
67625
+ const bufferView = this.json.bufferViews[image.bufferView];
67626
+ const array = await this.getBufferView(
67627
+ bufferView.byteOffset || 0,
67628
+ bufferView.byteLength,
67629
+ 5121 // UNSIGNED_BYTE
67630
+ );
67631
+ const blob = new Blob([array], { type: image.mimeType });
67632
+ const url = URL.createObjectURL(blob);
67633
+ const texture = await this.textureLoader.loadAsync(url);
67634
+ URL.revokeObjectURL(url);
67635
+ texture.flipY = false; // GLTF standard
67636
+ return texture;
67637
+ }
67638
+ };
67639
+
67640
+ // Load all textures
67641
+ const texturePromises = [];
67642
+ for (let i = 0; i < this.json.textures.length; i++) {
67643
+ texturePromises.push(
67644
+ loadTexture(this.json.textures[i].source).then((texture) => this.textureCache.set(i, texture))
67645
+ );
67646
+ }
67647
+ await Promise.all(texturePromises);
67648
+ }
67649
+
67650
+ loadMaterials() {
67651
+ if (!this.json.materials) return this.materials;
67652
+
67653
+ for (let i = 0; i < this.json.materials.length; i++) {
67654
+ const materialDef = this.json.materials[i];
67655
+ const material = this.createMaterial(materialDef);
67656
+ this.materials.set(i, material);
67657
+ }
67658
+ return this.materials;
67659
+ }
67660
+
67661
+ createMaterial(materialDef) {
67662
+ const material = new MeshStandardMaterial();
67663
+
67664
+ // Base color
67665
+ if (materialDef.pbrMetallicRoughness) {
67666
+ const pbr = materialDef.pbrMetallicRoughness;
67667
+
67668
+ if (pbr.baseColorFactor) {
67669
+ material.color.fromArray(pbr.baseColorFactor);
67670
+ material.opacity = pbr.baseColorFactor[3];
67671
+ }
67672
+
67673
+ if (pbr.baseColorTexture) {
67674
+ material.map = this.textureCache.get(pbr.baseColorTexture.index);
67675
+ }
67676
+
67677
+ // Metallic and roughness
67678
+ if (pbr.metallicFactor !== undefined) {
67679
+ material.metalness = pbr.metallicFactor;
67680
+ }
67681
+ if (pbr.roughnessFactor !== undefined) {
67682
+ material.roughness = pbr.roughnessFactor;
67683
+ }
67684
+ if (pbr.metallicRoughnessTexture) {
67685
+ material.metalnessMap = this.textureCache.get(pbr.metallicRoughnessTexture.index);
67686
+ material.roughnessMap = material.metalnessMap;
67687
+ }
67688
+ }
67689
+
67690
+ // Normal map
67691
+ if (materialDef.normalTexture) {
67692
+ material.normalMap = this.textureCache.get(materialDef.normalTexture.index);
67693
+ if (materialDef.normalTexture.scale !== undefined) {
67694
+ material.normalScale.set(materialDef.normalTexture.scale, materialDef.normalTexture.scale);
67695
+ }
67696
+ }
67697
+
67698
+ // Emissive
67699
+ if (materialDef.emissiveFactor) {
67700
+ material.emissive.fromArray(materialDef.emissiveFactor);
67701
+ }
67702
+ if (materialDef.emissiveTexture) {
67703
+ material.emissiveMap = this.textureCache.get(materialDef.emissiveTexture.index);
67704
+ }
67705
+
67706
+ // Occlusion
67707
+ if (materialDef.occlusionTexture) {
67708
+ material.aoMap = this.textureCache.get(materialDef.occlusionTexture.index);
67709
+ if (materialDef.occlusionTexture.strength !== undefined) {
67710
+ material.aoMapIntensity = materialDef.occlusionTexture.strength;
67711
+ }
67712
+ }
67713
+
67714
+ // Alpha mode
67715
+ if (materialDef.alphaMode === "BLEND") {
67716
+ material.transparent = true;
67717
+ } else if (materialDef.alphaMode === "MASK") {
67718
+ material.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
67719
+ }
67720
+
67721
+ // Double sided
67722
+ material.side = materialDef.doubleSided ? DoubleSide : FrontSide;
67723
+
67724
+ material.name = materialDef.name;
67725
+
67726
+ return material;
67727
+ }
67728
+
67729
+ disposeMaterials() {
67730
+ // Dispose all textures
67731
+ this.textureCache.forEach((texture) => texture.dispose());
67732
+ this.textureCache.clear();
67733
+
67734
+ // Dispose all materials
67735
+ this.materials.forEach((material) => {
67736
+ if (material.map) material.map.dispose();
67737
+ if (material.lightMap) material.lightMap.dispose();
67738
+ if (material.bumpMap) material.bumpMap.dispose();
67739
+ if (material.normalMap) material.normalMap.dispose();
67740
+ if (material.specularMap) material.specularMap.dispose();
67741
+ if (material.envMap) material.envMap.dispose();
67742
+ if (material.aoMap) material.aoMap.dispose();
67743
+ if (material.metalnessMap) material.metalnessMap.dispose();
67744
+ if (material.roughnessMap) material.roughnessMap.dispose();
67745
+ if (material.emissiveMap) material.emissiveMap.dispose();
67746
+ material.dispose();
67747
+ });
67748
+ this.materials.clear();
67749
+ }
67750
+
67751
+ estimateNodeSize(meshIndex) {
67752
+ if (!this.json.meshes) return 0;
67753
+
67754
+ const meshDef = this.json.meshes[meshIndex];
67755
+ if (!meshDef || !meshDef.primitives) return 0;
67756
+
67757
+ let totalSize = 0;
67758
+
67759
+ // Estimate size for each primitive
67760
+ for (const primitive of meshDef.primitives) {
67761
+ // Check for attributes
67762
+ if (primitive.attributes) {
67763
+ // Calculate attributes size
67764
+ for (const [, accessorIndex] of Object.entries(primitive.attributes)) {
67765
+ if (accessorIndex === undefined) continue;
67766
+
67767
+ const accessor = this.json.accessors[accessorIndex];
67768
+ if (!accessor) continue;
67769
+
67770
+ const numComponents = this.getNumComponents(accessor.type);
67771
+ const bytesPerComponent = this.getComponentSize(accessor.componentType);
67772
+ totalSize += accessor.count * numComponents * bytesPerComponent;
67773
+ }
67774
+ }
67775
+
67776
+ // Calculate indices size if present
67777
+ if (primitive.indices !== undefined) {
67778
+ const accessor = this.json.accessors[primitive.indices];
67779
+ if (accessor) {
67780
+ const bytesPerComponent = this.getComponentSize(accessor.componentType);
67781
+ totalSize += accessor.count * bytesPerComponent;
67782
+ }
67783
+ }
67784
+ }
67785
+
67786
+ return totalSize;
67787
+ }
67788
+ }
67789
+
67790
+ class DynamicGltfLoader {
67791
+ constructor(camera, scene, renderer) {
67792
+ this.camera = camera;
67793
+ this.scene = scene;
67794
+ this.renderer = renderer;
67795
+
67796
+ this.eventHandlers = {
67797
+ geometryprogress: [],
67798
+ databasechunk: [],
67799
+ geometryend: [],
67800
+ geometryerror: [],
67801
+ update: [],
67802
+ geometrymemory: [],
67803
+ };
67804
+
67805
+ this.loadDistance = 100;
67806
+ this.unloadDistance = 150;
67807
+ this.checkInterval = 1000;
67808
+
67809
+ this.nodes = new Map();
67810
+ this.loadedMeshes = new Map();
67811
+ this.nodesToLoad = [];
67812
+ this.edgeNodes = [];
67813
+ this.structures = [];
67814
+ this.structureRoots = new Map();
67815
+
67816
+ this.memoryLimit = this.getAvailableMemory();
67817
+ this.loadedGeometrySize = 0;
67818
+ this.geometryCache = new Map();
67819
+ this.materialCache = new Map();
67820
+ this.textureCache = new Map();
67821
+ this.currentMemoryUsage = 0;
67822
+
67823
+ this.updateMemoryIndicator();
67824
+
67825
+ this.loadedMaterials = new Map();
67826
+ this.abortController = new AbortController();
67827
+
67828
+ this.batchSize = 10000;
67829
+ this.frameDelay = 0;
67830
+
67831
+ this.graphicsObjectLimit = 10000;
67832
+ this.totalLoadedObjects = 0;
67833
+
67834
+ this.lastUpdateTime = 0;
67835
+ this.updateInterval = 1000;
67836
+
67837
+ this.handleToObjects = new Map();
67838
+
67839
+ this.originalObjects = new Set();
67840
+ this.originalObjectsToSelection = new Set();
67841
+
67842
+ this.optimizedOriginalMap = new Map();
67843
+ this.mergedMesh = new Set();
67844
+ this.mergedLines = new Set();
67845
+ this.mergedLineSegments = new Set();
67846
+ this.mergedPoints = new Set();
67847
+
67848
+ this.isolatedObjects = [];
67849
+ this.useVAO = !!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext;
67850
+
67851
+ this.handleToOptimizedObjects = new Map();
67852
+
67853
+ this.hiddenHandles = new Set();
67854
+ this.newOptimizedObjects = new Set();
67855
+ this.oldOptimizeObjects = new Set();
67856
+ }
67857
+
67858
+ getAvailableMemory() {
67859
+ let memoryLimit = 6 * 1024 * 1024 * 1024;
67860
+ try {
67861
+ if (navigator.deviceMemory) {
67862
+ memoryLimit = navigator.deviceMemory * 1024 * 1024 * 1024;
67863
+ } else if (performance.memory) {
67864
+ const jsHeapSizeLimit = performance.memory.jsHeapSizeLimit;
67865
+ if (jsHeapSizeLimit) {
67866
+ memoryLimit = Math.min(memoryLimit, jsHeapSizeLimit);
67867
+ }
67868
+ }
67869
+
67870
+ memoryLimit = Math.min(memoryLimit, 16 * 1024 * 1024 * 1024);
67871
+ memoryLimit = Math.max(memoryLimit, 2 * 1024 * 1024 * 1024);
67872
+
67873
+ console.log(`Available memory set to ${Math.round(memoryLimit / (1024 * 1024 * 1024))}GB`);
67874
+ } catch (error) {
67875
+ console.warn("Error detecting available memory:", error);
67876
+ }
67877
+
67878
+ return memoryLimit;
67879
+ }
67880
+
67881
+ getAbortController() {
67882
+ return this.abortController;
67883
+ }
67884
+
67885
+ abortLoading() {
67886
+ this.abortController.abort();
67887
+ }
67888
+
67889
+ updateMemoryIndicator() {
67890
+ this.dispatchEvent("geometrymemory", {
67891
+ currentUsage: this.currentMemoryUsage,
67892
+ limit: this.memoryLimit,
67893
+ });
67894
+ }
67895
+
67896
+ setMemoryLimit(bytesLimit) {
67897
+ // this.memoryLimit = bytesLimit;
67898
+ //this.updateMemoryIndicator();
67899
+ // console.log(`Memory limit set to ${Math.round(bytesLimit / (1024 * 1024))}MB`);
67900
+ }
67901
+
67902
+ estimateGeometrySize(nodeGroup) {
67903
+ let totalSize = 0;
67904
+ nodeGroup.traverse((child) => {
67905
+ if (child.geometry) {
67906
+ if (this.abortController.signal.aborted) {
67907
+ throw new DOMException("Loading aborted", "AbortError");
67908
+ }
67909
+ const geometry = child.geometry;
67910
+
67911
+ if (geometry.attributes) {
67912
+ Object.values(geometry.attributes).forEach((attribute) => {
67913
+ if (attribute && attribute.array) {
67914
+ totalSize += attribute.array.byteLength;
67915
+ }
67916
+ });
67917
+ }
67918
+
67919
+ if (geometry.index && geometry.index.array) {
67920
+ totalSize += geometry.index.array.byteLength;
67921
+ }
67922
+ }
67923
+ });
67924
+
67925
+ return totalSize;
67926
+ }
67927
+
67928
+ recalculateScene() {
67929
+ const geometries = [];
67930
+ this.scene.traverse((object) => {
67931
+ if (this.abortController.signal.aborted) {
67932
+ throw new DOMException("Loading aborted", "AbortError");
67933
+ }
67934
+
67935
+ if (object.geometry && !this.geometryCache.has(object.geometry.uuid)) {
67936
+ const size = this.estimateGeometrySize(object);
67937
+ this.geometryCache.set(object.geometry.uuid, size);
67938
+ geometries.push({
67939
+ object,
67940
+ size,
67941
+ distance: object.position.distanceTo(this.camera.position),
67942
+ });
67943
+ }
67944
+ });
67945
+
67946
+ if (this.abortController.signal.aborted) {
67947
+ throw new DOMException("Loading aborted", "AbortError");
67948
+ }
67949
+
67950
+ geometries.sort((a, b) => b.distance - a.distance);
67951
+
67952
+ let currentMemoryUsage = 0;
67953
+ for (const geo of geometries) {
67954
+ currentMemoryUsage += geo.size;
67955
+ }
67956
+
67957
+ if (currentMemoryUsage > this.memoryLimit) {
67958
+ console.log(`Memory usage (${Math.round(currentMemoryUsage / (1024 * 1024))}MB) exceeds limit`);
67959
+
67960
+ for (const geo of geometries) {
67961
+ if (currentMemoryUsage <= this.memoryLimit) break;
67962
+
67963
+ if (this.abortController.signal.aborted) {
67964
+ throw new DOMException("Loading aborted", "AbortError");
67965
+ }
67966
+
67967
+ const object = geo.object;
67968
+ if (object.geometry) {
67969
+ currentMemoryUsage -= geo.size;
67970
+ this.geometryCache.delete(object.geometry.uuid);
67971
+ object.geometry.dispose();
67972
+ object.visible = false;
67973
+ }
67974
+ }
67975
+ }
67976
+
67977
+ this.currentMemoryUsage = currentMemoryUsage;
67978
+ this.updateMemoryIndicator();
67979
+
67980
+ console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
67981
+ }
67982
+
67983
+ async loadNode(nodeId) {
67984
+ const node = this.nodes.get(nodeId);
67985
+ if (!node || node.loaded || node.loading) return;
67986
+
67987
+ node.loading = true;
67988
+ const meshDef = node.structure.getJson().meshes[node.meshIndex];
67989
+
67990
+ try {
67991
+ for (const primitive of meshDef.primitives) {
67992
+ const positionAccessor = primitive.attributes.POSITION;
67993
+ const geometry = new BufferGeometry();
67994
+ const attributes = new Map();
67995
+
67996
+ attributes.set("position", node.structure.createBufferAttribute(positionAccessor));
67997
+
67998
+ if (primitive.attributes.NORMAL !== undefined) {
67999
+ attributes.set("normal", node.structure.createBufferAttribute(primitive.attributes.NORMAL));
68000
+ }
68001
+
68002
+ if (primitive.attributes.TEXCOORD_0 !== undefined) {
68003
+ attributes.set("uv", node.structure.createBufferAttribute(primitive.attributes.TEXCOORD_0));
68004
+ }
68005
+
68006
+ const loadedAttributes = await Promise.all(
68007
+ [...attributes.entries()].map(async ([name, promise]) => {
68008
+ const attribute = await promise;
68009
+ return [name, attribute];
68010
+ })
68011
+ );
68012
+
68013
+ loadedAttributes.forEach(([name, attribute]) => {
68014
+ geometry.setAttribute(name, attribute);
68015
+ });
68016
+
68017
+ if (primitive.indices !== undefined) {
68018
+ const indexAttribute = await node.structure.createBufferAttribute(primitive.indices);
68019
+ geometry.setIndex(indexAttribute);
68020
+ }
68021
+
68022
+ this.currentPrimitiveMode = primitive.mode;
68023
+
68024
+ let material;
68025
+ if (primitive.material !== undefined) {
68026
+ material = node.structure.materials.get(primitive.material) || this.createDefaultMaterial();
68027
+ } else {
68028
+ material = this.createDefaultMaterial();
68029
+ }
68030
+
68031
+ let mesh;
68032
+ if (primitive.mode === GL_CONSTANTS.POINTS) {
68033
+ const pointsMaterial = new PointsMaterial();
68034
+ Material.prototype.copy.call(pointsMaterial, material);
68035
+ pointsMaterial.color.copy(material.color);
68036
+ pointsMaterial.map = material.map;
68037
+ pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
68038
+
68039
+ mesh = new Points(geometry, pointsMaterial);
68040
+ } else if (
68041
+ primitive.mode === GL_CONSTANTS.TRIANGLES ||
68042
+ primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP ||
68043
+ primitive.mode === GL_CONSTANTS.TRIANGLE_FAN ||
68044
+ primitive.mode === undefined
68045
+ ) {
68046
+ mesh = new Mesh(geometry, material);
68047
+
68048
+ if (primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP) {
68049
+ mesh.drawMode = TriangleStripDrawMode;
68050
+ } else if (primitive.mode === GL_CONSTANTS.TRIANGLE_FAN) {
68051
+ mesh.drawMode = TriangleFanDrawMode;
68052
+ }
68053
+ } else if (primitive.mode === GL_CONSTANTS.LINES) {
68054
+ mesh = new LineSegments(geometry, material);
68055
+ } else if (primitive.mode === GL_CONSTANTS.LINE_STRIP) {
68056
+ mesh = new Line$1(geometry, material);
68057
+ } else if (primitive.mode === GL_CONSTANTS.LINE_LOOP) {
68058
+ mesh = new LineLoop(geometry, material);
68059
+ }
68060
+
68061
+ if (node.extras) {
68062
+ mesh.userData = { ...mesh.userData, ...node.extras };
68063
+ }
68064
+
68065
+ if (meshDef.extras) {
68066
+ mesh.userData = { ...mesh.userData, ...meshDef.extras };
68067
+ }
68068
+
68069
+ if (primitive.extras) {
68070
+ mesh.userData = { ...mesh.userData, ...primitive.extras };
68071
+ }
68072
+
68073
+ if (node.handle) {
68074
+ mesh.userData.handle = node.handle;
68075
+ } else {
68076
+ mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
68077
+ }
68078
+
68079
+ if (mesh.material.name === "edges") {
68080
+ mesh.userData.isEdge = true;
68081
+ } else {
68082
+ mesh.userData.isEdge = false;
68083
+ }
68084
+
68085
+ this.registerObjectWithHandle(mesh, mesh.userData.handle);
68086
+
68087
+ mesh.position.copy(node.position);
68088
+
68089
+ if (!geometry.attributes.normal) {
68090
+ geometry.computeVertexNormals();
68091
+ }
68092
+
68093
+ if (material.aoMap && geometry.attributes.uv) {
68094
+ geometry.setAttribute("uv2", geometry.attributes.uv);
68095
+ }
68096
+ if (node.group) {
68097
+ node.group.add(mesh);
68098
+ } else {
68099
+ this.scene.add(mesh);
68100
+ }
68101
+ node.object = mesh;
68102
+
68103
+ this.totalLoadedObjects++;
68104
+ mesh.visible = this.totalLoadedObjects < this.graphicsObjectLimit;
68105
+ }
68106
+
68107
+ node.loaded = true;
68108
+ node.loading = false;
68109
+
68110
+ const geometrySize = this.estimateGeometrySize(node.object);
68111
+ this.geometryCache.set(node.object.uuid, geometrySize);
68112
+ this.currentMemoryUsage += geometrySize;
68113
+ } catch (error) {
68114
+ if (error.name !== "AbortError") {
68115
+ console.error(`Error loading node ${nodeId}:`, error);
68116
+ }
68117
+ node.loading = false;
68118
+ }
68119
+ }
68120
+
68121
+ unloadNode(nodeId) {
68122
+ const node = this.nodes.get(nodeId);
68123
+ if (!node || !node.loaded) return;
68124
+
68125
+ if (node.object) {
68126
+ if (node.object.parent) {
68127
+ node.object.parent.remove(node.object);
68128
+ } else {
68129
+ this.scene.remove(node.object);
68130
+ }
68131
+
68132
+ node.object.traverse((child) => {
68133
+ if (child.geometry) {
68134
+ const geometrySize = this.geometryCache.get(child.geometry.uuid) || 0;
68135
+ this.currentMemoryUsage -= geometrySize;
68136
+ this.geometryCache.delete(child.geometry.uuid);
68137
+ child.geometry.dispose();
68138
+ }
68139
+ });
68140
+
68141
+ node.object = null;
68142
+ node.loaded = false;
68143
+ this.updateMemoryIndicator();
68144
+ console.log(`Unloaded node: ${nodeId}`);
68145
+ }
68146
+ }
68147
+
68148
+ checkDistances() {
68149
+ const cameraPosition = this.camera.position;
68150
+
68151
+ this.nodes.forEach((node, nodeId) => {
68152
+ const distance = cameraPosition.distanceTo(node.position);
68153
+
68154
+ if (node.loaded) {
68155
+ if (distance > this.unloadDistance) {
68156
+ this.unloadNode(nodeId);
68157
+ }
68158
+ } else if (!node.loading) {
68159
+ if (distance < this.loadDistance) {
68160
+ this.loadNode(nodeId);
68161
+ }
68162
+ }
68163
+ });
68164
+ }
68165
+
68166
+ async loadStructure(structures) {
68167
+ this.clear();
68168
+
68169
+ const structureArray = Array.isArray(structures) ? structures : [structures];
68170
+
68171
+ for (const structure of structureArray) {
68172
+ this.structures.push(structure);
68173
+ }
68174
+
68175
+ for (const structure of this.structures) {
68176
+ try {
68177
+ await structure.loadTextures();
68178
+ await structure.loadMaterials();
68179
+ } catch (error) {
68180
+ console.error("Error loading materials:", error);
68181
+ throw error;
68182
+ }
68183
+ }
68184
+
68185
+ await this.processSceneHierarchy();
68186
+ }
68187
+
68188
+ async processSceneHierarchy() {
68189
+ if (this.structures.length === 0) {
68190
+ throw new Error("No GLTF structures loaded");
68191
+ }
68192
+
68193
+ this.nodesToLoad = [];
68194
+
68195
+ let estimatedSize = 0;
68196
+
68197
+ for (const structure of this.structures) {
68198
+ const gltf = structure.getJson();
68199
+
68200
+ if (!gltf.scenes || !gltf.scenes.length) {
68201
+ console.warn("No scenes found in GLTF structure");
68202
+ continue;
68203
+ }
68204
+
68205
+ estimatedSize += gltf.buffers[0].byteLength;
68206
+
68207
+ const rootGroup = new Group$1();
68208
+ rootGroup.name = `structure_${structure.id}_root`;
68209
+ this.scene.add(rootGroup);
68210
+ this.structureRoots.set(structure.id, rootGroup);
68211
+
68212
+ const scene = gltf.scenes[gltf.scene || 0];
68213
+
68214
+ for (const nodeIndex of scene.nodes) {
68215
+ await this.processNodeHierarchy(structure, nodeIndex, rootGroup);
68216
+ }
68217
+ }
68218
+
68219
+ const ignoreEdges = estimatedSize * 2 > this.memoryLimit;
68220
+
68221
+ this.nodesToLoad.sort((a, b) => {
68222
+ const nodeA = this.nodes.get(a);
68223
+ const nodeB = this.nodes.get(b);
68224
+
68225
+ if (!nodeA?.geometryExtents || !nodeB?.geometryExtents) {
68226
+ return 0;
68227
+ }
68228
+
68229
+ const sizeA = nodeA.geometryExtents.getSize(new Vector3());
68230
+ const sizeB = nodeB.geometryExtents.getSize(new Vector3());
68231
+ const volumeA = sizeA.x * sizeA.y * sizeA.z;
68232
+ const volumeB = sizeB.x * sizeB.y * sizeB.z;
68233
+
68234
+ return volumeB - volumeA;
68235
+ });
68236
+
68237
+ if (!ignoreEdges) {
68238
+ this.nodesToLoad.push(...this.edgeNodes);
68239
+ }
68240
+
68241
+ this.dispatchEvent("databasechunk", {
68242
+ totalNodes: this.nodesToLoad.length,
68243
+ structures: this.structures.map((s) => ({
68244
+ id: s.id,
68245
+ nodeCount: this.nodesToLoad.filter((nodeId) => nodeId.startsWith(s.id)).length,
68246
+ })),
68247
+ });
68248
+ }
68249
+
68250
+ async processNodeHierarchy(structure, nodeId, parentGroup) {
68251
+ const nodeDef = structure.json.nodes[nodeId];
68252
+ let nodeGroup = null;
68253
+
68254
+ let handle = null;
68255
+ if (nodeDef.extras?.handle) {
68256
+ handle = `${structure.id}_${nodeDef.extras.handle}`;
68257
+ }
68258
+
68259
+ if (nodeDef.camera !== undefined) {
68260
+ const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
68261
+ if (nodeDef.extras) {
68262
+ camera.userData = { ...camera.userData, ...nodeDef.extras };
68263
+ }
68264
+ this.scene.add(camera);
68265
+ return;
68266
+ }
68267
+
68268
+ const needsGroup = this.needsGroupForNode(structure, nodeDef);
68269
+
68270
+ if (needsGroup) {
68271
+ nodeGroup = new Group$1();
68272
+ nodeGroup.name = nodeDef.name || `node_${nodeId}`;
68273
+
68274
+ if (nodeDef.extras) {
68275
+ nodeGroup.userData = { ...nodeDef.extras };
68276
+ if (nodeGroup.userData.handle) {
68277
+ nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
68278
+ }
68279
+ }
68280
+
68281
+ if (nodeDef.matrix) {
68282
+ nodeGroup.matrix.fromArray(nodeDef.matrix);
68283
+ nodeGroup.matrixAutoUpdate = false;
68284
+ } else if (nodeDef.translation || nodeDef.rotation || nodeDef.scale) {
68285
+ const position = nodeDef.translation ? new Vector3().fromArray(nodeDef.translation) : new Vector3();
68286
+ const quaternion = nodeDef.rotation ? new Quaternion().fromArray(nodeDef.rotation) : new Quaternion();
68287
+ const scale = nodeDef.scale ? new Vector3().fromArray(nodeDef.scale) : new Vector3(1, 1, 1);
68288
+ nodeGroup.matrix.compose(position, quaternion, scale);
68289
+ nodeGroup.matrixAutoUpdate = false;
68290
+ }
68291
+
68292
+ if (parentGroup) {
68293
+ parentGroup.add(nodeGroup);
68294
+ }
68295
+ }
68296
+
68297
+ if (nodeDef.mesh !== undefined) {
68298
+ const nodeMatrix = new Matrix4();
68299
+ const uniqueNodeId = `${structure.id}_${nodeId}`;
68300
+ const meshDef = structure.json.meshes[nodeDef.mesh];
68301
+ const geometryExtents = new Box3();
68302
+
68303
+ for (const primitive of meshDef.primitives) {
68304
+ const positionAccessor = structure.json.accessors[primitive.attributes.POSITION];
68305
+ if (positionAccessor && positionAccessor.min && positionAccessor.max) {
68306
+ const primitiveBox = new Box3(
68307
+ new Vector3().fromArray(positionAccessor.min),
68308
+ new Vector3().fromArray(positionAccessor.max)
68309
+ );
68310
+ geometryExtents.union(primitiveBox);
68311
+ }
68312
+ }
68313
+
68314
+ let isEdge = false;
68315
+ if (meshDef.primitives[0].material !== undefined) {
68316
+ const material = structure.json.materials[meshDef.primitives[0].material];
68317
+ if (material?.name === "edges") {
68318
+ isEdge = true;
68319
+ }
68320
+ }
68321
+
68322
+ if (!isEdge) {
68323
+ this.nodesToLoad.push(uniqueNodeId);
68324
+ } else {
68325
+ this.edgeNodes.push(uniqueNodeId);
68326
+ }
68327
+
68328
+ this.nodes.set(uniqueNodeId, {
68329
+ position: nodeGroup ? nodeGroup.position.clone() : new Vector3().setFromMatrixPosition(nodeMatrix),
68330
+ nodeIndex: nodeId,
68331
+ meshIndex: nodeDef.mesh,
68332
+ loaded: false,
68333
+ loading: false,
68334
+ object: null,
68335
+ group: nodeGroup || parentGroup,
68336
+ structure,
68337
+ extras: nodeDef.extras,
68338
+ geometryExtents,
68339
+ handle,
68340
+ });
68341
+ }
68342
+
68343
+ if (nodeDef.children) {
68344
+ for (const childId of nodeDef.children) {
68345
+ await this.processNodeHierarchy(structure, childId, nodeGroup || parentGroup);
68346
+ }
68347
+ }
68348
+
68349
+ return nodeGroup;
68350
+ }
68351
+
68352
+ needsGroupForNode(structure, nodeDef) {
68353
+ const hasTransforms = nodeDef.matrix || nodeDef.translation || nodeDef.rotation || nodeDef.scale;
68354
+
68355
+ const hasMultiplePrimitives =
68356
+ nodeDef.mesh !== undefined && structure.json.meshes[nodeDef.mesh].primitives.length > 1;
68357
+
68358
+ return hasTransforms !== undefined || hasMultiplePrimitives;
68359
+ }
68360
+
68361
+ async processNodes() {
68362
+ const nodesToLoad = this.nodesToLoad;
68363
+ let loadedCount = 0;
68364
+ const totalNodes = nodesToLoad.length;
68365
+
68366
+ try {
68367
+ while (loadedCount < totalNodes) {
68368
+ const batch = nodesToLoad.slice(loadedCount, loadedCount + this.batchSize);
68369
+ const batchPromises = [];
68370
+
68371
+ for (const nodeId of batch) {
68372
+ if (this.abortController.signal.aborted) {
68373
+ throw new DOMException("Loading aborted", "AbortError");
68374
+ }
68375
+
68376
+ const estimatedSize = await this.estimateNodeSize(nodeId);
68377
+
68378
+ if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
68379
+ console.log(`Memory limit reached after loading ${loadedCount} nodes`);
68380
+ this.dispatchEvent("geometryerror", { message: "Memory limit reached" });
68381
+ this.dispatchEvent("update");
68382
+ return loadedCount;
68383
+ }
68384
+
68385
+ batchPromises.push(this.loadNode(nodeId));
68386
+ }
68387
+
68388
+ await Promise.all(batchPromises);
68389
+ loadedCount += batch.length;
68390
+
68391
+ this.updateMemoryIndicator();
68392
+ this.dispatchEvent("geometryprogress", {
68393
+ percentage: Math.round((loadedCount / totalNodes) * 100),
68394
+ loaded: loadedCount,
68395
+ total: totalNodes,
68396
+ });
68397
+
68398
+ const currentTime = Date.now();
68399
+ if (currentTime - this.lastUpdateTime >= this.updateInterval) {
68400
+ this.dispatchEvent("update");
68401
+ this.lastUpdateTime = currentTime;
68402
+ }
68403
+
68404
+ await new Promise((resolve) => {
68405
+ setTimeout(resolve, 0);
68406
+ });
68407
+ }
68408
+
68409
+ this.dispatchEvent("geometryend", {
68410
+ totalLoaded: loadedCount,
68411
+ totalNodes,
68412
+ });
68413
+
68414
+ return loadedCount;
68415
+ } catch (error) {
68416
+ this.dispatchEvent("geometryerror", { error });
68417
+ throw error;
68418
+ }
68419
+ }
68420
+
68421
+ async loadNodes() {
68422
+ console.time("process nodes");
68423
+ await this.processNodes();
68424
+ console.timeEnd("process nodes");
68425
+
68426
+ console.time("optimize scene");
68427
+ await this.optimizeScene();
68428
+ console.timeEnd("optimize scene");
68429
+ }
68430
+
68431
+ cleanupPartialLoad() {
68432
+ this.nodesToLoad.forEach((nodeId) => {
68433
+ const node = this.nodes.get(nodeId);
68434
+ if (node && node.loading) {
68435
+ this.unloadNode(nodeId);
68436
+ }
68437
+ });
68438
+ }
68439
+
68440
+ createDefaultMaterial() {
68441
+ if (this.currentPrimitiveMode === GL_CONSTANTS.POINTS) {
68442
+ return new PointsMaterial({
68443
+ color: new Color(0x808080),
68444
+ size: 0.05,
68445
+ sizeAttenuation: true,
68446
+ alphaTest: 0.5,
68447
+ transparent: true,
68448
+ vertexColors: false,
68449
+ blending: NormalBlending,
68450
+ depthWrite: false,
68451
+ depthTest: true,
68452
+ });
68453
+ } else {
68454
+ return new MeshStandardMaterial({
68455
+ color: 0x808080,
68456
+ metalness: 0.0,
68457
+ roughness: 1.0,
68458
+ side: DoubleSide,
68459
+ });
68460
+ }
68461
+ }
68462
+
68463
+ async estimateNodeSize(nodeId) {
68464
+ const node = this.nodes.get(nodeId);
68465
+ if (!node) return 0;
68466
+ return await node.structure.estimateNodeSize(node.meshIndex);
68467
+ }
68468
+
68469
+ getTotalGeometryExtent() {
68470
+ const totalExtent = new Box3();
68471
+
68472
+ for (const node of this.nodes.values()) {
68473
+ if (!node.geometryExtents) continue;
68474
+
68475
+ if (node.object && this.hiddenHandles.has(node.object.userData.handle)) continue;
68476
+
68477
+ const transformedBox = node.geometryExtents.clone();
68478
+
68479
+ if (node.group && node.group.matrix) {
68480
+ transformedBox.applyMatrix4(node.group.matrix);
68481
+
68482
+ if (node.group.parent && node.group.parent.matrix) {
68483
+ transformedBox.applyMatrix4(node.group.parent.matrix);
68484
+ }
68485
+ }
68486
+ totalExtent.union(transformedBox);
68487
+ }
68488
+
68489
+ return totalExtent;
68490
+ }
68491
+
68492
+ loadCamera(structure, cameraIndex, nodeDef) {
68493
+ const cameraDef = structure.getJson().cameras[cameraIndex];
68494
+ const params = cameraDef[cameraDef.type];
68495
+
68496
+ let camera;
68497
+ if (cameraDef.type === "perspective") {
68498
+ camera = new PerspectiveCamera(
68499
+ MathUtils.radToDeg(params.yfov),
68500
+ params.aspectRatio || 1,
68501
+ params.znear || 1,
68502
+ params.zfar || 2e6
68503
+ );
68504
+ } else if (cameraDef.type === "orthographic") {
68505
+ camera = new OrthographicCamera(
68506
+ params.xmag / -2,
68507
+ params.xmag / 2,
68508
+ params.ymag / 2,
68509
+ params.ymag / -2,
68510
+ params.znear,
68511
+ params.zfar
68512
+ );
68513
+ }
68514
+
68515
+ if (nodeDef.matrix) {
68516
+ camera.matrix.fromArray(nodeDef.matrix);
68517
+ camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
68518
+ } else {
68519
+ if (nodeDef.translation) {
68520
+ camera.position.fromArray(nodeDef.translation);
68521
+ }
68522
+ if (nodeDef.rotation) {
68523
+ camera.quaternion.fromArray(nodeDef.rotation);
68524
+ }
68525
+ if (nodeDef.scale) {
68526
+ camera.scale.fromArray(nodeDef.scale);
68527
+ }
68528
+ }
68529
+
68530
+ return camera;
68531
+ }
68532
+
68533
+ clearNodesToLoad() {
68534
+ this.nodesToLoad = [];
68535
+ }
68536
+
68537
+ async addStructure(loadController) {
68538
+ const structure = new GltfStructure();
68539
+ await structure.initialize(loadController);
68540
+ this.structures.push(structure);
68541
+ return structure;
68542
+ }
68543
+
68544
+ removeOptimization() {
68545
+ this.originalObjects.forEach((obj) => (obj.visible = true));
68546
+
68547
+ const disposeMerged = (obj) => {
68548
+ if (obj.parent) {
68549
+ obj.parent.remove(obj);
68550
+ }
68551
+ if (obj.geometry) {
68552
+ obj.geometry.dispose();
68553
+ }
68554
+ };
68555
+
68556
+ if (this.structureGroups) {
68557
+ for (const group of this.structureGroups.values()) {
68558
+ group.meshes.forEach(disposeMerged);
68559
+ group.lines.forEach(disposeMerged);
68560
+ group.lineSegments.forEach(disposeMerged);
68561
+ group.meshes.clear();
68562
+ group.lines.clear();
68563
+ group.lineSegments.clear();
68564
+ }
68565
+ }
68566
+ this.optimizedOriginalMap.clear();
68567
+ this.mergedMesh.clear();
68568
+ this.mergedLines.clear();
68569
+ this.mergedLineSegments.clear();
68570
+ this.originalObjects.clear();
68571
+ this.originalObjectsToSelection.clear();
68572
+ }
68573
+
68574
+ clear() {
68575
+ // Clear all structures
68576
+ this.structures.forEach((structure) => {
68577
+ if (structure) {
68578
+ structure.clear();
68579
+ }
68580
+ });
68581
+ this.structures = [];
68582
+
68583
+ // Clear all nodes and unload their objects
68584
+ this.nodes.forEach((node) => {
68585
+ if (node.object) {
68586
+ if (node.object.parent) {
68587
+ node.object.parent.remove(node.object);
68588
+ }
68589
+ if (node.object.geometry) {
68590
+ node.object.geometry.dispose();
68591
+ }
68592
+ if (node.object.material) {
68593
+ if (Array.isArray(node.object.material)) {
68594
+ node.object.material.forEach((material) => material.dispose());
68595
+ } else {
68596
+ node.object.material.dispose();
68597
+ }
68598
+ }
68599
+ }
68600
+ });
68601
+ this.nodes.clear();
68602
+
68603
+ // Clear all loaded meshes
68604
+ this.loadedMeshes.forEach((mesh) => {
68605
+ if (mesh.geometry) mesh.geometry.dispose();
68606
+ if (mesh.material) {
68607
+ if (Array.isArray(mesh.material)) {
68608
+ mesh.material.forEach((material) => material.dispose());
68609
+ } else {
68610
+ mesh.material.dispose();
68611
+ }
68612
+ }
68613
+ });
68614
+ this.loadedMeshes.clear();
68615
+
68616
+ // Clear all structure roots and their children
68617
+ this.structureRoots.forEach((rootGroup) => {
68618
+ if (rootGroup) {
68619
+ rootGroup.traverse((child) => {
68620
+ if (child.geometry) child.geometry.dispose();
68621
+ if (child.material) {
68622
+ if (Array.isArray(child.material)) {
68623
+ child.material.forEach((material) => material.dispose());
68624
+ } else {
68625
+ child.material.dispose();
68626
+ }
68627
+ }
68628
+ });
68629
+ if (rootGroup.parent) {
68630
+ rootGroup.parent.remove(rootGroup);
68631
+ }
68632
+ }
68633
+ });
68634
+ this.structureRoots.clear();
68635
+
68636
+ // Clear all optimized objects
68637
+ this.mergedMesh.forEach((mesh) => {
68638
+ if (mesh.geometry) mesh.geometry.dispose();
68639
+ if (mesh.material) {
68640
+ if (Array.isArray(mesh.material)) {
68641
+ mesh.material.forEach((material) => material.dispose());
68642
+ } else {
68643
+ mesh.material.dispose();
68644
+ }
68645
+ }
68646
+ if (mesh.parent) mesh.parent.remove(mesh);
68647
+ });
68648
+ this.mergedMesh.clear();
68649
+
68650
+ this.mergedLines.forEach((line) => {
68651
+ if (line.geometry) line.geometry.dispose();
68652
+ if (line.material) line.material.dispose();
68653
+ if (line.parent) line.parent.remove(line);
68654
+ });
68655
+ this.mergedLines.clear();
68656
+
68657
+ this.mergedLineSegments.forEach((lineSegment) => {
68658
+ if (lineSegment.geometry) lineSegment.geometry.dispose();
68659
+ if (lineSegment.material) lineSegment.material.dispose();
68660
+ if (lineSegment.parent) lineSegment.parent.remove(lineSegment);
68661
+ });
68662
+ this.mergedLineSegments.clear();
68663
+
68664
+ this.mergedPoints.forEach((points) => {
68665
+ if (points.geometry) points.geometry.dispose();
68666
+ if (points.material) points.material.dispose();
68667
+ if (points.parent) points.parent.remove(points);
68668
+ });
68669
+ this.mergedPoints.clear();
68670
+
68671
+ // Clear all caches
68672
+ this.geometryCache.clear();
68673
+ this.materialCache.clear();
68674
+ this.textureCache.clear();
68675
+ this.loadedMaterials.clear();
68676
+
68677
+ // Clear all maps and sets
68678
+ this.nodesToLoad = [];
68679
+ this.handleToObjects.clear();
68680
+ this.originalObjects.clear();
68681
+ this.originalObjectsToSelection.clear();
68682
+ this.optimizedOriginalMap.clear();
68683
+ this.handleToOptimizedObjects.clear();
68684
+ this.hiddenHandles.clear();
68685
+ this.newOptimizedObjects.clear();
68686
+ this.oldOptimizeObjects.clear();
68687
+ this.isolatedObjects = [];
68688
+
68689
+ // Reset counters and state
68690
+ this.totalLoadedObjects = 0;
68691
+ this.lastUpdateTime = 0;
68692
+ this.currentMemoryUsage = 0;
68693
+ this.loadedGeometrySize = 0;
68694
+
68695
+ this.abortController = new AbortController();
68696
+ this.updateMemoryIndicator();
68697
+ }
68698
+
68699
+ setStructureTransform(structureId, matrix) {
68700
+ const rootGroup = this.structureRoots.get(structureId);
68701
+ if (rootGroup) {
68702
+ rootGroup.matrix.copy(matrix);
68703
+ rootGroup.matrix.decompose(rootGroup.position, rootGroup.quaternion, rootGroup.scale);
68704
+ return true;
68705
+ }
68706
+ return false;
68707
+ }
68708
+
68709
+ getStructureRootGroup(structureId) {
68710
+ return this.structureRoots.get(structureId);
68711
+ }
68712
+
68713
+ addEventListener(event, handler) {
68714
+ if (this.eventHandlers[event]) {
68715
+ this.eventHandlers[event].push(handler);
68716
+ }
68717
+ }
68718
+
68719
+ removeEventListener(event, handler) {
68720
+ if (this.eventHandlers[event]) {
68721
+ this.eventHandlers[event] = this.eventHandlers[event].filter((h) => h !== handler);
68722
+ }
68723
+ }
68724
+
68725
+ dispatchEvent(event, data) {
68726
+ if (this.eventHandlers[event]) {
68727
+ this.eventHandlers[event].forEach((handler) => handler(data));
68728
+ }
68729
+ }
68730
+
68731
+ registerObjectWithHandle(object, handle) {
68732
+ if (!handle) return;
68733
+
68734
+ const fullHandle = object.userData.handle;
68735
+ if (!this.handleToObjects.has(fullHandle)) {
68736
+ this.handleToObjects.set(fullHandle, new Set());
68737
+ }
68738
+ this.handleToObjects.get(fullHandle).add(object);
68739
+
68740
+ object.userData.structureId = object.userData.handle.split("_")[0];
68741
+ }
68742
+
68743
+ getObjectsByHandle(handle) {
68744
+ if (!handle) return [];
68745
+ return Array.from(this.handleToObjects.get(handle) || []);
68746
+ }
68747
+
68748
+ getHandlesByObjects(objects) {
68749
+ if (!objects.length) return [];
68750
+ const handles = new Set();
68751
+ objects.forEach((obj) => {
68752
+ if (this.originalObjects.has(obj)) handles.add(obj.userData.handle);
68753
+ });
68754
+ return Array.from(handles);
68755
+ }
68756
+
68757
+ getMaterialId(material, index) {
68758
+ const props = {
68759
+ type: material.type,
68760
+ color: material.color?.getHex(),
68761
+ map: material.map?.uuid,
68762
+ transparent: material.transparent,
68763
+ opacity: material.opacity,
68764
+ side: material.side,
68765
+ index: index ? 1 : 0,
68766
+ };
68767
+ return JSON.stringify(props);
68768
+ }
68769
+
68770
+ addToMaterialGroup(object, groupsMap, optimizeGroupList) {
68771
+ const VERTEX_LIMIT = 100_000;
68772
+ const INDEX_LIMIT = 100_000;
68773
+
68774
+ const objectGeometryVertexCount = object.geometry.attributes.position.count;
68775
+ const objectGeometryIndexCount = object.geometry.index ? object.geometry.index.count : 0;
68776
+
68777
+ const material = object.material;
68778
+ let materialId = this.getMaterialId(material, object.geometry.index !== null);
68779
+
68780
+ let group;
68781
+ if (!groupsMap.has(materialId)) {
68782
+ group = {
68783
+ material,
68784
+ objects: [object],
68785
+ totalVertices: objectGeometryVertexCount,
68786
+ totalIndices: objectGeometryIndexCount,
68787
+ };
68788
+ groupsMap.set(materialId, group);
68789
+ optimizeGroupList.push(group);
68790
+ } else {
68791
+ group = groupsMap.get(materialId);
68792
+ if (
68793
+ group.totalVertices + objectGeometryVertexCount > VERTEX_LIMIT ||
68794
+ group.totalIndices + objectGeometryIndexCount > INDEX_LIMIT
68795
+ ) {
68796
+ const newGroup = {
68797
+ material,
68798
+ objects: [object],
68799
+ totalVertices: objectGeometryVertexCount,
68800
+ totalIndices: objectGeometryIndexCount,
68801
+ };
68802
+ materialId = this.getMaterialId(material, object.geometry.index !== null);
68803
+ groupsMap.set(materialId, newGroup);
68804
+ optimizeGroupList.push(newGroup);
68805
+ } else {
68806
+ group.objects.push(object);
68807
+ group.totalVertices += objectGeometryVertexCount;
68808
+ group.totalIndices += objectGeometryIndexCount;
68809
+ }
68810
+ }
68811
+
68812
+ this.originalObjects.add(object);
68813
+ }
68814
+
68815
+ optimizeScene() {
68816
+ this.originalObjects.clear();
68817
+ this.originalObjectsToSelection.clear();
68818
+ const structureGroups = new Map();
68819
+
68820
+ this.scene.traverse((object) => {
68821
+ if (object.userData.structureId) {
68822
+ const structureId = object.userData.structureId;
68823
+ if (!structureGroups.has(structureId)) {
68824
+ structureGroups.set(structureId, {
68825
+ mapMeshes: new Map(),
68826
+ mapLines: new Map(),
68827
+ mapLineSegments: new Map(),
68828
+ mapPoints: new Map(),
68829
+
68830
+ meshes: [],
68831
+ lines: [],
68832
+ lineSegments: [],
68833
+ points: [],
68834
+ rootGroup: this.structureRoots.get(structureId),
68835
+ });
68836
+ }
68837
+
68838
+ const group = structureGroups.get(structureId);
68839
+
68840
+ if (object instanceof Mesh) {
68841
+ this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
68842
+ } else if (object instanceof LineSegments) {
68843
+ this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
68844
+ } else if (object instanceof Line$1) {
68845
+ this.addToMaterialGroup(object, group.mapLines, group.lines);
68846
+ } else if (object instanceof Points) {
68847
+ this.addToMaterialGroup(object, group.mapPoints, group.points);
68848
+ }
68849
+ }
68850
+ });
68851
+
68852
+ for (const group of structureGroups.values()) {
68853
+ group.mapMeshes.clear();
68854
+ group.mapLines.clear();
68855
+ group.mapLineSegments.clear();
68856
+ group.mapPoints.clear();
68857
+
68858
+ this.mergeMeshGroups(group.meshes, group.rootGroup);
68859
+ this.mergeLineGroups(group.lines, group.rootGroup);
68860
+ this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
68861
+ this.mergePointsGroups(group.points, group.rootGroup);
68862
+ }
68863
+
68864
+ this.originalObjects.forEach((obj) => {
68865
+ obj.visible = false;
68866
+ if (!(obj instanceof Points) && !obj.userData.isEdge) {
68867
+ this.originalObjectsToSelection.add(obj);
68868
+ }
68869
+ });
68870
+
68871
+ this.dispatchEvent("update");
68872
+ }
68873
+
68874
+ mergeMeshGroups(materialGroups, rootGroup) {
68875
+ for (const group of materialGroups) {
68876
+ try {
68877
+ const geometries = [];
68878
+ const handles = new Set();
68879
+ const optimizedObjects = [];
68880
+
68881
+ for (const mesh of group.objects) {
68882
+ const geometry = mesh.geometry.clone();
68883
+ mesh.updateWorldMatrix(true, false);
68884
+ geometry.applyMatrix4(mesh.matrixWorld);
68885
+ geometries.push(geometry);
68886
+
68887
+ optimizedObjects.push(mesh);
68888
+ handles.add(mesh.userData.handle);
68889
+ }
68890
+
68891
+ const mergedObjects = [];
68892
+
68893
+ if (geometries.length > 0) {
68894
+ const mergedGeometry = mergeGeometries(geometries);
68895
+ if (this.useVAO) {
68896
+ this.createVAO(mergedGeometry);
68897
+ }
68898
+
68899
+ const mergedMesh = new Mesh(mergedGeometry, group.material);
68900
+ rootGroup.add(mergedMesh);
68901
+
68902
+ this.mergedMesh.add(mergedMesh);
68903
+ this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
68904
+
68905
+ mergedObjects.push(mergedMesh);
68906
+
68907
+ geometries.forEach((geometry) => {
68908
+ geometry.dispose();
68909
+ });
68910
+ }
68911
+
68912
+ handles.forEach((handle) => {
68913
+ if (this.handleToOptimizedObjects.has(handle)) {
68914
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
68915
+ existingObjects.push(...mergedObjects);
68916
+ this.handleToOptimizedObjects.set(handle, existingObjects);
68917
+ } else {
68918
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
68919
+ }
68920
+ });
68921
+ } catch (error) {
68922
+ console.error("Failed to merge meshes for material:", error);
68923
+ group.objects.forEach((mesh) => {
68924
+ mesh.visible = true;
68925
+ });
68926
+ }
68927
+ }
68928
+ }
68929
+
68930
+ mergeLineGroups(materialGroups, rootGroup) {
68931
+ for (const group of materialGroups) {
68932
+ if (group.objects.length === 0) continue;
68933
+
68934
+ const handles = new Set();
68935
+ let totalVertices = 0;
68936
+ group.objects.map((line) => {
68937
+ handles.add(line.userData.handle);
68938
+ totalVertices += line.geometry.attributes.position.count;
68939
+ });
68940
+
68941
+ const positions = new Float32Array(totalVertices * 3);
68942
+ let posOffset = 0;
68943
+
68944
+ const indices = [];
68945
+ let vertexOffset = 0;
68946
+
68947
+ group.objects.forEach((line) => {
68948
+ const geometry = line.geometry;
68949
+ const positionAttr = geometry.attributes.position;
68950
+ const vertexCount = positionAttr.count;
68951
+
68952
+ line.updateWorldMatrix(true, false);
68953
+ const matrix = line.matrixWorld;
68954
+ const vector = new Vector3();
68955
+
68956
+ for (let i = 0; i < vertexCount; i++) {
68957
+ vector.fromBufferAttribute(positionAttr, i);
68958
+ vector.applyMatrix4(matrix);
68959
+ positions[posOffset++] = vector.x;
68960
+ positions[posOffset++] = vector.y;
68961
+ positions[posOffset++] = vector.z;
68962
+ }
68963
+
68964
+ for (let i = 0; i < vertexCount - 1; i++) {
68965
+ indices.push(vertexOffset + i, vertexOffset + i + 1);
68966
+ }
68967
+
68968
+ vertexOffset += vertexCount;
68969
+ });
68970
+
68971
+ const geometry = new BufferGeometry();
68972
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
68973
+ geometry.setIndex(indices);
68974
+ geometry.computeBoundingSphere();
68975
+ geometry.computeBoundingBox();
68976
+
68977
+ const mergedLine = new LineSegments(geometry, group.material);
68978
+ const mergedObjects = [mergedLine];
68979
+ if (this.useVAO) {
68980
+ this.createVAO(mergedLine);
68981
+ }
68982
+ rootGroup.add(mergedLine);
68983
+ this.mergedLines.add(mergedLine);
68984
+ this.optimizedOriginalMap.set(mergedLine, group.objects);
68985
+
68986
+ handles.forEach((handle) => {
68987
+ if (this.handleToOptimizedObjects.has(handle)) {
68988
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
68989
+ existingObjects.push(...mergedObjects);
68990
+ this.handleToOptimizedObjects.set(handle, existingObjects);
68991
+ } else {
68992
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
68993
+ }
68994
+ });
68995
+ }
68996
+ }
68997
+
68998
+ mergeLineSegmentGroups(materialGroups, rootGroup) {
68999
+ for (const group of materialGroups) {
69000
+ try {
69001
+ const geometries = [];
69002
+ const optimizedObjects = [];
69003
+ const handles = new Set();
69004
+
69005
+ for (const line of group.objects) {
69006
+ const geometry = line.geometry.clone();
69007
+ line.updateWorldMatrix(true, false);
69008
+ geometry.applyMatrix4(line.matrixWorld);
69009
+ geometries.push(geometry);
69010
+ optimizedObjects.push(line);
69011
+ handles.add(line.userData.handle);
69012
+ }
69013
+
69014
+ const mergedObjects = [];
69015
+
69016
+ if (geometries.length > 0) {
69017
+ const mergedGeometry = mergeGeometries(geometries, false);
69018
+ const mergedLine = new LineSegments(mergedGeometry, group.material);
69019
+
69020
+ if (this.useVAO) {
69021
+ this.createVAO(mergedLine);
69022
+ }
69023
+
69024
+ rootGroup.add(mergedLine);
69025
+ this.mergedLineSegments.add(mergedLine);
69026
+ this.optimizedOriginalMap.set(mergedLine, optimizedObjects);
69027
+ mergedObjects.push(mergedLine);
69028
+
69029
+ geometries.forEach((geometry) => {
69030
+ geometry.dispose();
69031
+ });
69032
+ }
69033
+
69034
+ handles.forEach((handle) => {
69035
+ if (this.handleToOptimizedObjects.has(handle)) {
69036
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
69037
+ existingObjects.push(...mergedObjects);
69038
+ this.handleToOptimizedObjects.set(handle, existingObjects);
69039
+ } else {
69040
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
69041
+ }
69042
+ });
69043
+ } catch (error) {
69044
+ console.warn("Failed to merge line segments for material:", error);
69045
+ group.objects.forEach((line) => {
69046
+ line.visible = true;
69047
+ });
69048
+ }
69049
+ }
69050
+ }
69051
+
69052
+ mergePointsGroups(materialGroups, rootGroup) {
69053
+ for (const group of materialGroups) {
69054
+ try {
69055
+ const geometries = [];
69056
+ const optimizedObjects = [];
69057
+ const handles = new Set();
69058
+
69059
+ for (const points of group.objects) {
69060
+ const geometry = points.geometry.clone();
69061
+ points.updateWorldMatrix(true, false);
69062
+ geometry.applyMatrix4(points.matrixWorld);
69063
+ geometries.push(geometry);
69064
+ optimizedObjects.push(points);
69065
+ handles.add(points.userData.handle);
69066
+ }
69067
+
69068
+ const mergedObjects = [];
69069
+
69070
+ if (geometries.length > 0) {
69071
+ const mergedGeometry = mergeGeometries(geometries, false);
69072
+ const mergedPoints = new Points(mergedGeometry, group.material);
69073
+
69074
+ if (this.useVAO) {
69075
+ this.createVAO(mergedPoints);
69076
+ }
69077
+
69078
+ rootGroup.add(mergedPoints);
69079
+
69080
+ this.mergedPoints.add(mergedPoints);
69081
+ this.optimizedOriginalMap.set(mergedPoints, optimizedObjects);
69082
+ mergedObjects.push(mergedPoints);
69083
+
69084
+ geometries.forEach((geometry) => {
69085
+ geometry.dispose();
69086
+ });
69087
+ }
69088
+
69089
+ handles.forEach((handle) => {
69090
+ if (this.handleToOptimizedObjects.has(handle)) {
69091
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
69092
+ existingObjects.push(...mergedObjects);
69093
+ this.handleToOptimizedObjects.set(handle, existingObjects);
69094
+ } else {
69095
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
69096
+ }
69097
+ });
69098
+ } catch (error) {
69099
+ console.warn("Failed to merge points for material:", error);
69100
+ group.objects.forEach((points) => {
69101
+ points.visible = true;
69102
+ });
69103
+ }
69104
+ }
69105
+ }
69106
+
69107
+ mergeInSingleSegment(structureId, rootGroup) {
69108
+ const lineSegmentsArray = [...this.mergedLineSegments, ...this.mergedLines].filter(
69109
+ (obj) => obj.userData.structureId === structureId
69110
+ );
69111
+
69112
+ if (lineSegmentsArray.length === 0) return;
69113
+
69114
+ try {
69115
+ const geometriesWithIndex = [];
69116
+ const hasNormals = lineSegmentsArray.some((segment) => segment.geometry.attributes.normal !== undefined);
69117
+
69118
+ lineSegmentsArray.forEach((segment) => {
69119
+ const clonedGeometry = segment.geometry.clone();
69120
+ segment.updateWorldMatrix(true, false);
69121
+ clonedGeometry.applyMatrix4(segment.matrixWorld);
69122
+
69123
+ if (hasNormals && !clonedGeometry.attributes.normal) {
69124
+ clonedGeometry.computeVertexNormals();
69125
+ }
69126
+ if (!hasNormals && clonedGeometry.attributes.normal) {
69127
+ clonedGeometry.deleteAttribute("normal");
69128
+ }
69129
+
69130
+ const colorArray = new Float32Array(clonedGeometry.attributes.position.count * 3);
69131
+ for (let i = 0; i < colorArray.length; i += 3) {
69132
+ colorArray[i] = segment.material.color.r;
69133
+ colorArray[i + 1] = segment.material.color.g;
69134
+ colorArray[i + 2] = segment.material.color.b;
69135
+ }
69136
+ clonedGeometry.setAttribute("color", new BufferAttribute(colorArray, 3));
69137
+
69138
+ if (!clonedGeometry.index) {
69139
+ const indices = [];
69140
+ const posCount = clonedGeometry.attributes.position.count;
69141
+ for (let i = 0; i < posCount - 1; i += 2) {
69142
+ indices.push(i, i + 1);
69143
+ }
69144
+ clonedGeometry.setIndex(indices);
69145
+ }
69146
+
69147
+ geometriesWithIndex.push(clonedGeometry);
69148
+ });
69149
+
69150
+ const finalGeometry = mergeGeometries(geometriesWithIndex, false);
69151
+ const material = new LineBasicMaterial({
69152
+ vertexColors: true,
69153
+ });
69154
+
69155
+ if (this.useVAO) {
69156
+ this.createVAO(finalGeometry);
69157
+ }
69158
+
69159
+ const mergedLine = new LineSegments(finalGeometry, material);
69160
+ mergedLine.userData.structureId = structureId;
69161
+ rootGroup.add(mergedLine);
69162
+ this.mergedLineSegments.add(mergedLine);
69163
+
69164
+ lineSegmentsArray.forEach((obj) => {
69165
+ if (obj.parent) {
69166
+ obj.parent.remove(obj);
69167
+ }
69168
+ obj.geometry.dispose();
69169
+ });
69170
+ } catch (error) {
69171
+ console.error("Failed to merge geometries:", error);
69172
+ lineSegmentsArray.forEach((obj) => {
69173
+ obj.visible = true;
69174
+ rootGroup.add(obj);
69175
+ });
69176
+ }
69177
+ }
69178
+
69179
+ showOriginalObjects(objects) {
69180
+ objects.forEach((obj) => {
69181
+ if (this.originalObjects.has(obj)) {
69182
+ obj.visible = true;
69183
+ }
69184
+ });
69185
+ }
69186
+
69187
+ hideOriginalObjects(objects) {
69188
+ objects.forEach((obj) => {
69189
+ if (this.originalObjects.has(obj)) {
69190
+ obj.visible = false;
69191
+ }
69192
+ });
69193
+ }
69194
+
69195
+ createVAO(geometry) {
69196
+ if (!this.useVAO) {
69197
+ return;
69198
+ }
66558
69199
 
66559
- }
69200
+ if (geometry.attributes?.position?.count < 1000) {
69201
+ return;
69202
+ }
66560
69203
 
66561
- // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
66562
- box.expandByVector( maxDisplacement );
69204
+ const gl = this.renderer.getContext();
69205
+ const vao = gl.createVertexArray();
69206
+ gl.bindVertexArray(vao);
66563
69207
 
66564
- }
69208
+ for (const name in geometry.attributes) {
69209
+ const attribute = geometry.attributes[name];
69210
+ const buffer = this.renderer.properties.get(attribute).buffer;
66565
69211
 
66566
- geometry.boundingBox = box;
69212
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
69213
+ gl.enableVertexAttribArray(attribute.itemSize);
69214
+ gl.vertexAttribPointer(attribute.itemSize, attribute.itemSize, gl.FLOAT, false, 0, 0);
69215
+ }
66567
69216
 
66568
- const sphere = new Sphere();
69217
+ if (geometry.index) {
69218
+ const indexBuffer = this.renderer.properties.get(geometry.index).buffer;
69219
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
69220
+ }
66569
69221
 
66570
- box.getCenter( sphere.center );
66571
- sphere.radius = box.min.distanceTo( box.max ) / 2;
69222
+ gl.bindVertexArray(null);
69223
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
69224
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
66572
69225
 
66573
- geometry.boundingSphere = sphere;
69226
+ geometry.vao = vao;
69227
+ }
66574
69228
 
66575
- }
69229
+ getOriginalObjectForSelect() {
69230
+ const optimizedOriginals = [];
66576
69231
 
66577
- /**
66578
- * @param {BufferGeometry} geometry
66579
- * @param {GLTF.Primitive} primitiveDef
66580
- * @param {GLTFParser} parser
66581
- * @return {Promise<BufferGeometry>}
66582
- */
66583
- function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
69232
+ for (const obj of this.originalObjectsToSelection) {
69233
+ if (this.hiddenHandles.has(obj.userData.handle)) {
69234
+ continue;
69235
+ }
66584
69236
 
66585
- const attributes = primitiveDef.attributes;
69237
+ optimizedOriginals.push(obj);
69238
+ }
66586
69239
 
66587
- const pending = [];
69240
+ return optimizedOriginals;
69241
+ }
66588
69242
 
66589
- function assignAttributeAccessor( accessorIndex, attributeName ) {
69243
+ isolateObjects(handles) {
69244
+ if (this.hiddenHandles.size !== 0) {
69245
+ this.hiddenHandles.clear();
69246
+ this.syncHiddenObjects();
69247
+ }
66590
69248
 
66591
- return parser.getDependency( 'accessor', accessorIndex )
66592
- .then( function ( accessor ) {
69249
+ for (const handle of this.handleToOptimizedObjects.keys()) {
69250
+ if (!handles.has(handle)) {
69251
+ this.hiddenHandles.add(handle);
69252
+ }
69253
+ }
66593
69254
 
66594
- geometry.setAttribute( attributeName, accessor );
69255
+ this.syncHiddenObjects();
69256
+ }
66595
69257
 
66596
- } );
69258
+ showAllHiddenObjects() {
69259
+ this.hiddenHandles.clear();
69260
+ this.syncHiddenObjects();
69261
+ }
66597
69262
 
66598
- }
69263
+ hideObjects(handles) {
69264
+ handles.forEach((handle) => {
69265
+ this.hiddenHandles.add(handle);
69266
+ });
69267
+ this.syncHiddenObjects();
69268
+ }
66599
69269
 
66600
- for ( const gltfAttributeName in attributes ) {
69270
+ showObjects(handles) {
69271
+ handles.forEach((handle) => {
69272
+ this.hiddenHandles.delete(handle);
69273
+ });
69274
+ this.syncHiddenObjects();
69275
+ }
66601
69276
 
66602
- const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
69277
+ syncHiddenObjects() {
69278
+ if (this.oldOptimizeObjects.size !== 0) {
69279
+ for (const obj of this.oldOptimizeObjects) {
69280
+ obj.visible = true;
69281
+ }
69282
+ this.oldOptimizeObjects.clear();
69283
+ }
66603
69284
 
66604
- // Skip attributes already provided by e.g. Draco extension.
66605
- if ( threeAttributeName in geometry.attributes ) continue;
69285
+ if (this.newOptimizedObjects.size !== 0) {
69286
+ for (const obj of this.newOptimizedObjects) {
69287
+ obj.visible = false;
69288
+ obj.geometry.dispose();
69289
+ obj.parent.remove(obj);
69290
+ }
69291
+ this.newOptimizedObjects.clear();
69292
+ }
66606
69293
 
66607
- pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
69294
+ if (this.hiddenHandles.size === 0) {
69295
+ return;
69296
+ }
66608
69297
 
66609
- }
69298
+ this.hiddenHandles.forEach((handle) => {
69299
+ const objects = this.handleToOptimizedObjects.get(handle);
69300
+ if (objects) {
69301
+ objects.forEach((x) => this.oldOptimizeObjects.add(x));
69302
+ }
69303
+ });
66610
69304
 
66611
- if ( primitiveDef.indices !== undefined && ! geometry.index ) {
69305
+ this.oldOptimizeObjects.forEach((optimizedObject) => {
69306
+ optimizedObject.visible = false;
66612
69307
 
66613
- const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
69308
+ const originObjects = this.optimizedOriginalMap.get(optimizedObject);
69309
+ const updateListToOptimize = [];
69310
+ originObjects.forEach((obj) => {
69311
+ if (!this.hiddenHandles.has(obj.userData.handle)) {
69312
+ updateListToOptimize.push(obj);
69313
+ }
69314
+ });
66614
69315
 
66615
- geometry.setIndex( accessor );
69316
+ const firstObject = updateListToOptimize[0];
66616
69317
 
66617
- } );
69318
+ if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
69319
+ const geometries = updateListToOptimize.map((obj) => {
69320
+ const geometry = obj.geometry.clone();
69321
+ obj.updateWorldMatrix(true, false);
69322
+ geometry.applyMatrix4(obj.matrixWorld);
69323
+ return geometry;
69324
+ });
66618
69325
 
66619
- pending.push( accessor );
69326
+ const newMergedGeometry = mergeGeometries(geometries);
69327
+ const mergedObject =
69328
+ firstObject instanceof Mesh
69329
+ ? new Mesh(newMergedGeometry, optimizedObject.material)
69330
+ : new LineSegments(newMergedGeometry, optimizedObject.material);
66620
69331
 
66621
- }
69332
+ mergedObject.visible = true;
69333
+ optimizedObject.parent.add(mergedObject);
69334
+ this.newOptimizedObjects.add(mergedObject);
66622
69335
 
66623
- if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) {
69336
+ geometries.forEach((geometry) => {
69337
+ geometry.dispose();
69338
+ });
69339
+ } else if (firstObject instanceof Line$1) {
69340
+ let totalVertices = 0;
69341
+ updateListToOptimize.map((line) => {
69342
+ totalVertices += line.geometry.attributes.position.count;
69343
+ });
66624
69344
 
66625
- console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` );
69345
+ const positions = new Float32Array(totalVertices * 3);
69346
+ let posOffset = 0;
66626
69347
 
66627
- }
69348
+ const indices = [];
69349
+ let vertexOffset = 0;
66628
69350
 
66629
- assignExtrasToUserData( geometry, primitiveDef );
69351
+ updateListToOptimize.forEach((line) => {
69352
+ const geometry = line.geometry;
69353
+ const positionAttr = geometry.attributes.position;
69354
+ const vertexCount = positionAttr.count;
66630
69355
 
66631
- computeBounds( geometry, primitiveDef, parser );
69356
+ line.updateWorldMatrix(true, false);
69357
+ const matrix = line.matrixWorld;
69358
+ const vector = new Vector3();
66632
69359
 
66633
- return Promise.all( pending ).then( function () {
69360
+ for (let i = 0; i < vertexCount; i++) {
69361
+ vector.fromBufferAttribute(positionAttr, i);
69362
+ vector.applyMatrix4(matrix);
69363
+ positions[posOffset++] = vector.x;
69364
+ positions[posOffset++] = vector.y;
69365
+ positions[posOffset++] = vector.z;
69366
+ }
66634
69367
 
66635
- return primitiveDef.targets !== undefined
66636
- ? addMorphTargets( geometry, primitiveDef.targets, parser )
66637
- : geometry;
69368
+ for (let i = 0; i < vertexCount - 1; i++) {
69369
+ indices.push(vertexOffset + i, vertexOffset + i + 1);
69370
+ }
66638
69371
 
66639
- } );
69372
+ vertexOffset += vertexCount;
69373
+ });
66640
69374
 
66641
- }
69375
+ const geometry = new BufferGeometry();
69376
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
69377
+ geometry.setIndex(indices);
69378
+ geometry.computeBoundingSphere();
69379
+ geometry.computeBoundingBox();
66642
69380
 
66643
- ///////////////////////////////////////////////////////////////////////////////
66644
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
66645
- // All rights reserved.
66646
- //
66647
- // This software and its documentation and related materials are owned by
66648
- // the Alliance. The software may only be incorporated into application
66649
- // programs owned by members of the Alliance, subject to a signed
66650
- // Membership Agreement and Supplemental Software License Agreement with the
66651
- // Alliance. The structure and organization of this software are the valuable
66652
- // trade secrets of the Alliance and its suppliers. The software is also
66653
- // protected by copyright law and international treaty provisions. Application
66654
- // programs incorporating this software must include the following statement
66655
- // with their copyright notices:
66656
- //
66657
- // This application incorporates Open Design Alliance software pursuant to a
66658
- // license agreement with Open Design Alliance.
66659
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
66660
- // All rights reserved.
66661
- //
66662
- // By use of this software, its documentation or related materials, you
66663
- // acknowledge and accept the above terms.
66664
- ///////////////////////////////////////////////////////////////////////////////
66665
- class GLTFLoadingManager extends LoadingManager {
66666
- constructor(file, params = {}) {
66667
- super();
66668
- this.path = "";
66669
- this.resourcePath = "";
66670
- this.fileURL = "";
66671
- this.dataURLs = new Map();
66672
- this.path = params.path || "";
66673
- const externalFiles = params.externalFiles || new Map();
66674
- if (typeof file === "string") {
66675
- this.fileURL = file;
66676
- this.resourcePath = LoaderUtils.extractUrlBase(file);
66677
- }
66678
- else {
66679
- externalFiles.forEach((value, key) => (this.fileURL = value === file ? key : this.fileURL));
66680
- externalFiles.set(this.fileURL, file);
66681
- }
66682
- externalFiles.forEach((value, key) => {
66683
- let dataURL;
66684
- if (typeof value === "string")
66685
- dataURL = value;
66686
- else
66687
- dataURL = URL.createObjectURL(new Blob([value]));
66688
- this.dataURLs.set(key, dataURL);
66689
- });
66690
- this.setURLModifier((url) => {
66691
- const key = decodeURI(url)
66692
- .replace(this.path, "")
66693
- .replace(this.resourcePath, "")
66694
- .replace(/^(\.?\/)/, "");
66695
- const dataURL = this.dataURLs.get(key);
66696
- return dataURL !== null && dataURL !== undefined ? dataURL : url;
66697
- });
66698
- }
66699
- dispose() {
66700
- this.dataURLs.forEach(URL.revokeObjectURL);
66701
- }
69381
+ const mergedLine = new LineSegments(geometry, optimizedObject.material);
69382
+ mergedLine.visible = true;
69383
+ optimizedObject.parent.add(mergedLine);
69384
+ this.newOptimizedObjects.add(mergedLine);
69385
+ }
69386
+ });
69387
+ }
66702
69388
  }
66703
69389
 
66704
69390
  ///////////////////////////////////////////////////////////////////////////////
@@ -66723,93 +69409,82 @@ void main() {
66723
69409
  // By use of this software, its documentation or related materials, you
66724
69410
  // acknowledge and accept the above terms.
66725
69411
  ///////////////////////////////////////////////////////////////////////////////
66726
- class GLTFFileLoader extends Loader$1 {
69412
+ class GLTFCloudDynamicLoader {
66727
69413
  constructor(viewer) {
66728
- super();
69414
+ this.requestId = 0;
66729
69415
  this.viewer = viewer;
69416
+ this.scene = new Group$1();
66730
69417
  }
66731
- isSupport(file, format) {
66732
- return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
66733
- /(gltf|glb)$/i.test(format));
66734
- }
66735
- async load(file, format, params) {
66736
- const manager = new GLTFLoadingManager(file, params);
66737
- const loader = new GLTFLoader(manager);
66738
- loader.setPath(manager.path);
66739
- loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
66740
- loader.setWithCredentials(params.withCredentials || loader.withCredentials);
66741
- const progress = (event) => {
66742
- const { lengthComputable, loaded, total } = event;
66743
- const progress = lengthComputable ? loaded / total : 1;
66744
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
66745
- };
66746
- const gltf = await loader.loadAsync(manager.fileURL, progress);
66747
- if (!this.viewer.scene)
66748
- return this;
66749
- this.viewer.scene.add(gltf.scene);
66750
- this.viewer.models.push(gltf.scene);
66751
- this.viewer.syncOptions();
66752
- this.viewer.syncOverlay();
66753
- this.viewer.update();
66754
- this.viewer.emitEvent({ type: "databasechunk", data: gltf.scene, file });
66755
- return this;
66756
- }
66757
- }
66758
-
66759
- ///////////////////////////////////////////////////////////////////////////////
66760
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
66761
- // All rights reserved.
66762
- //
66763
- // This software and its documentation and related materials are owned by
66764
- // the Alliance. The software may only be incorporated into application
66765
- // programs owned by members of the Alliance, subject to a signed
66766
- // Membership Agreement and Supplemental Software License Agreement with the
66767
- // Alliance. The structure and organization of this software are the valuable
66768
- // trade secrets of the Alliance and its suppliers. The software is also
66769
- // protected by copyright law and international treaty provisions. Application
66770
- // programs incorporating this software must include the following statement
66771
- // with their copyright notices:
66772
- //
66773
- // This application incorporates Open Design Alliance software pursuant to a
66774
- // license agreement with Open Design Alliance.
66775
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
66776
- // All rights reserved.
66777
- //
66778
- // By use of this software, its documentation or related materials, you
66779
- // acknowledge and accept the above terms.
66780
- ///////////////////////////////////////////////////////////////////////////////
66781
- class GLTFCloudModelLoader extends Loader$1 {
66782
- constructor(viewer) {
66783
- super();
66784
- this.viewer = viewer;
69418
+ dispose() {
69419
+ if (this.gltfLoader)
69420
+ this.gltfLoader.clear();
66785
69421
  }
66786
- isSupport(model) {
66787
- return (typeof model === "object" &&
66788
- typeof model.database === "string" &&
66789
- typeof model.downloadResource === "function" &&
66790
- /.gltf$/i.test(model.database));
69422
+ isSupport(file) {
69423
+ return (typeof file === "object" &&
69424
+ typeof file.database === "string" &&
69425
+ typeof file.downloadResource === "function" &&
69426
+ typeof file.downloadResourceRange === "function" &&
69427
+ /.gltf$/i.test(file.database));
66791
69428
  }
66792
- async load(model) {
66793
- const url = `${model.httpClient.serverUrl}${model.path}/${model.database}`;
66794
- const manager = new GLTFLoadingManager(url);
66795
- const loader = new GLTFLoader(manager);
66796
- loader.setRequestHeader(model.httpClient.headers);
66797
- const progress = (event) => {
66798
- const { lengthComputable, loaded, total } = event;
66799
- const progress = lengthComputable ? loaded / total : 1;
69429
+ async load(model, format, params) {
69430
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, this.viewer.scene, this.viewer.renderer);
69431
+ this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
69432
+ this.gltfLoader.addEventListener("databasechunk", (data) => {
69433
+ const modelImpl = new DynamicModelImpl(this.scene);
69434
+ modelImpl.loader = this;
69435
+ modelImpl.gltfLoader = this.gltfLoader;
69436
+ modelImpl.viewer = this.viewer;
69437
+ this.viewer.scene.add(this.scene);
69438
+ this.viewer.models.push(modelImpl);
69439
+ this.viewer.syncOptions();
69440
+ this.viewer.syncOverlay();
69441
+ this.viewer.update();
69442
+ this.viewer.emitEvent({ type: "databasechunk", data, file: model.file, model });
69443
+ });
69444
+ this.gltfLoader.addEventListener("geometryprogress", (data) => {
69445
+ const progress = data.loaded / data.total;
66800
69446
  this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model.file, model });
69447
+ });
69448
+ this.gltfLoader.addEventListener("geometrymemory", (data) => {
69449
+ this.viewer.emit({ type: "geometryprogress", data });
69450
+ });
69451
+ this.gltfLoader.addEventListener("geometryerror", (data) => {
69452
+ this.viewer.emitEvent({ type: "geometryerror", data, file: model.file, model });
69453
+ });
69454
+ this.gltfLoader.addEventListener("update", (data) => {
69455
+ this.viewer.update();
69456
+ });
69457
+ const loadController = {
69458
+ loadJson: async () => {
69459
+ const progress = (progress) => {
69460
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file: model });
69461
+ };
69462
+ const arrayBuffer = await model.downloadResource(model.database, progress, this.gltfLoader.getAbortController().signal);
69463
+ const text = new TextDecoder().decode(arrayBuffer);
69464
+ const json = JSON.parse(text);
69465
+ return json;
69466
+ },
69467
+ loadBinaryData: (requests) => {
69468
+ const ranges = requests.map((request) => ({
69469
+ begin: request.offset,
69470
+ end: request.offset + request.length - 1,
69471
+ requestId: this.requestId++,
69472
+ }));
69473
+ return model.downloadResourceRange(model.geometry[0], undefined, ranges, undefined, this.gltfLoader.getAbortController().signal);
69474
+ },
69475
+ baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
66801
69476
  };
66802
- const gltf = await loader.loadAsync(url, progress);
66803
- if (!this.viewer.scene)
66804
- return this;
66805
- this.viewer.scene.add(gltf.scene);
66806
- this.viewer.models.push(gltf.scene);
66807
- this.viewer.syncOptions();
66808
- this.viewer.syncOverlay();
66809
- this.viewer.update();
66810
- this.viewer.emitEvent({ type: "databasechunk", data: gltf.scene, file: model.file, model });
69477
+ const structure = new GltfStructure(1);
69478
+ await structure.initialize(loadController);
69479
+ await this.gltfLoader.loadStructure(structure);
69480
+ await this.gltfLoader.loadNodes();
66811
69481
  return this;
66812
69482
  }
69483
+ cancel() {
69484
+ if (this.gltfLoader) {
69485
+ this.gltfLoader.abortLoading();
69486
+ }
69487
+ }
66813
69488
  }
66814
69489
 
66815
69490
  ///////////////////////////////////////////////////////////////////////////////
@@ -66902,7 +69577,7 @@ void main() {
66902
69577
  const loaders = loadersRegistry("threejs");
66903
69578
  // build-in loaders
66904
69579
  loaders.registerLoader("gltf-file", (viewer) => new GLTFFileLoader(viewer));
66905
- loaders.registerLoader("gltf-cloud-model", (viewer) => new GLTFCloudModelLoader(viewer));
69580
+ loaders.registerLoader("gltf-cloud", (viewer) => new GLTFCloudDynamicLoader(viewer));
66906
69581
 
66907
69582
  class EventEmitter2 {
66908
69583
  constructor() {
@@ -81357,8 +84032,8 @@ void main() {
81357
84032
  this.client = client;
81358
84033
  this.canvasEvents = CANVAS_EVENTS;
81359
84034
  this.canvaseventlistener = (event) => this.emit(event);
81360
- this.models = [];
81361
84035
  this.loaders = [];
84036
+ this.models = [];
81362
84037
  this.selected = [];
81363
84038
  this.extents = new Box3();
81364
84039
  this.target = new Vector3();
@@ -81390,14 +84065,22 @@ void main() {
81390
84065
  this.addEventListener("optionschange", (event) => this.syncOptions(event.data));
81391
84066
  this.scene = new Scene();
81392
84067
  this.helpers = new Scene();
84068
+ const pixelRatio = window.devicePixelRatio;
81393
84069
  const rect = canvas.parentElement.getBoundingClientRect();
81394
84070
  const width = rect.width || 1;
81395
84071
  const height = rect.height || 1;
81396
84072
  const aspect = width / height;
81397
84073
  this.camera = new PerspectiveCamera(45, aspect, 0.01, 1000);
81398
84074
  this.camera.up.set(0, 0, 1);
81399
- this.renderer = new WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true });
81400
- this.renderer.setPixelRatio(window.devicePixelRatio);
84075
+ this.renderer = new WebGLRenderer({
84076
+ canvas,
84077
+ antialias: true,
84078
+ alpha: true,
84079
+ preserveDrawingBuffer: true,
84080
+ powerPreference: "high-performance",
84081
+ logarithmicDepthBuffer: false,
84082
+ });
84083
+ this.renderer.setPixelRatio(pixelRatio);
81401
84084
  this.renderer.setSize(width, height);
81402
84085
  this.renderer.toneMapping = LinearToneMapping;
81403
84086
  this.canvas = canvas;
@@ -81440,6 +84123,12 @@ void main() {
81440
84123
  isInitialized() {
81441
84124
  return !!this.renderer;
81442
84125
  }
84126
+ update(force = false) {
84127
+ this.renderNeeded = true;
84128
+ if (force)
84129
+ this.render(performance.now());
84130
+ this.emitEvent({ type: "update", data: force });
84131
+ }
81443
84132
  render(time) {
81444
84133
  var _a, _b;
81445
84134
  if (!this.renderNeeded)
@@ -81460,15 +84149,6 @@ void main() {
81460
84149
  this.renderTime = time;
81461
84150
  this.emitEvent({ type: "render", time, deltaTime });
81462
84151
  }
81463
- update(force = false) {
81464
- this.renderNeeded = true;
81465
- if (force)
81466
- this.render(performance.now());
81467
- this.emitEvent({ type: "update", data: force });
81468
- }
81469
- syncOptions(options = this.options) {
81470
- // this.update();
81471
- }
81472
84152
  loadReferences(model) {
81473
84153
  // todo: load reference as text fonts
81474
84154
  return Promise.resolve(this);
@@ -81601,34 +84281,25 @@ void main() {
81601
84281
  clear() {
81602
84282
  if (!this.renderer)
81603
84283
  return this;
81604
- function disposeMaterial(material) {
81605
- const materials = Array.isArray(material) ? material : [material];
81606
- materials.forEach((material) => material.dispose());
81607
- }
81608
- function disposeObject(object) {
81609
- if (object.geometry)
81610
- object.geometry.dispose();
81611
- if (object.material)
81612
- disposeMaterial(object.material);
81613
- }
81614
84284
  this.setActiveDragger();
81615
84285
  this.clearSlices();
81616
84286
  this.clearOverlay();
81617
84287
  this.clearSelected();
81618
- this.helpers.traverse(disposeObject);
81619
- this.helpers.clear();
81620
- this.models.forEach((model) => model.traverse(disposeObject));
81621
- this.models.forEach((model) => model.removeFromParent());
81622
- this.models = [];
81623
- this.scene.clear();
81624
84288
  this.loaders.forEach((loader) => loader.dispose());
81625
84289
  this.loaders = [];
84290
+ this.models.forEach((model) => model.dispose());
84291
+ this.models = [];
84292
+ this.helpers.clear();
84293
+ this.scene.clear();
81626
84294
  this.syncOptions();
81627
84295
  this.syncOverlay();
81628
84296
  this.update(true);
81629
84297
  this.emitEvent({ type: "clear" });
81630
84298
  return this;
81631
84299
  }
84300
+ syncOptions(options = this.options) {
84301
+ // this.update();
84302
+ }
81632
84303
  syncOverlay() {
81633
84304
  if (!this.renderer)
81634
84305
  return;
@@ -81898,6 +84569,7 @@ void main() {
81898
84569
  exports.GLTFLoadingManager = GLTFLoadingManager;
81899
84570
  exports.Loader = Loader$1;
81900
84571
  exports.Markup = KonvaMarkup;
84572
+ exports.ModelImpl = ModelImpl;
81901
84573
  exports.Options = Options;
81902
84574
  exports.Viewer = Viewer;
81903
84575
  exports.commands = commands;