@inweb/viewer-three 26.6.6 → 26.6.7

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
@@ -2,7 +2,7 @@ import { draggersRegistry, commandsRegistry, componentsRegistry, Loader, loaders
2
2
 
3
3
  export * from "@inweb/viewer-core";
4
4
 
5
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Matrix4, Vector4, Raycaster, Controls, Clock, Box3, Sphere, MathUtils, Color, AmbientLight, DirectionalLight, HemisphereLight, EdgesGeometry, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, Scene, PerspectiveCamera, WebGLRenderer, LinearToneMapping } from "three";
5
+ import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Matrix4, Vector4, Raycaster, Controls, Clock, Sphere, MathUtils, Box3, Color, AmbientLight, DirectionalLight, HemisphereLight, WebGLRenderTarget, UnsignedByteType, RGBAFormat, EdgesGeometry, OrthographicCamera, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, LoadingManager, LoaderUtils, TextureLoader, BufferAttribute, MeshStandardMaterial, FrontSide, PointsMaterial, Material, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, PerspectiveCamera, Scene, WebGLRenderer, LinearToneMapping } from "three";
6
6
 
7
7
  import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
8
8
 
@@ -843,7 +843,7 @@ class CuttingPlaneDragger extends OrbitDragger {
843
843
  this.transformDrag = event => {
844
844
  this.orbit.enabled = !event.value;
845
845
  };
846
- this.viewerExplode = () => {
846
+ this.updatePlaneSize = () => {
847
847
  this.planeHelper.size = this.viewer.extents.getSize(new Vector3).length();
848
848
  this.viewer.update();
849
849
  };
@@ -871,12 +871,16 @@ class CuttingPlaneDragger extends OrbitDragger {
871
871
  this.transform.addEventListener("change", this.transformChange);
872
872
  this.transform.addEventListener("dragging-changed", this.transformDrag);
873
873
  this.viewer.helpers.add(this.transform.getHelper());
874
- this.viewer.on("explode", this.viewerExplode);
874
+ this.viewer.on("explode", this.updatePlaneSize);
875
+ this.viewer.on("show", this.updatePlaneSize);
876
+ this.viewer.on("showall", this.updatePlaneSize);
875
877
  this.viewer.canvas.addEventListener("dblclick", this.onDoubleClick, true);
876
878
  this.viewer.update();
877
879
  }
878
880
  dispose() {
879
- this.viewer.off("explode", this.viewerExplode);
881
+ this.viewer.off("explode", this.updatePlaneSize);
882
+ this.viewer.off("show", this.updatePlaneSize);
883
+ this.viewer.off("showAll", this.updatePlaneSize);
880
884
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
881
885
  this.transform.removeEventListener("change", this.transformChange);
882
886
  this.transform.removeEventListener("dragging-changed", this.transformDrag);
@@ -965,6 +969,7 @@ class MeasureLineDragger extends OrbitDragger {
965
969
  this.viewer.addEventListener("render", this.renderOverlay);
966
970
  this.viewer.addEventListener("hide", this.updateSnapper);
967
971
  this.viewer.addEventListener("isolate", this.updateSnapper);
972
+ this.viewer.addEventListener("show", this.updateSnapper);
968
973
  this.viewer.addEventListener("showall", this.updateSnapper);
969
974
  }
970
975
  dispose() {
@@ -976,6 +981,7 @@ class MeasureLineDragger extends OrbitDragger {
976
981
  this.viewer.removeEventListener("render", this.renderOverlay);
977
982
  this.viewer.removeEventListener("hide", this.updateSnapper);
978
983
  this.viewer.removeEventListener("isolate", this.updateSnapper);
984
+ this.viewer.removeEventListener("show", this.updateSnapper);
979
985
  this.viewer.removeEventListener("showall", this.updateSnapper);
980
986
  this.overlay.detach();
981
987
  this.overlay.dispose();
@@ -1492,7 +1498,6 @@ function clearMarkup(viewer) {
1492
1498
 
1493
1499
  function clearSelected(viewer) {
1494
1500
  const selection = viewer.getComponent("SelectionComponent");
1495
- if (!selection) return;
1496
1501
  selection.clearSelection();
1497
1502
  viewer.update();
1498
1503
  viewer.emitEvent({
@@ -1511,43 +1516,8 @@ function createPreview(viewer, type = "image/jpeg", encoderOptions = .25) {
1511
1516
  return viewer.canvas.toDataURL(type, encoderOptions);
1512
1517
  }
1513
1518
 
1514
- function calcExplodeDepth(object, depth) {
1515
- let res = depth;
1516
- object.children.forEach((x => {
1517
- const objectDepth = calcExplodeDepth(x, depth + 1);
1518
- if (res < objectDepth) res = objectDepth;
1519
- }));
1520
- object.originalPosition = object.position.clone();
1521
- object.originalCenter = (new Box3).setFromObject(object).getCenter(new Vector3);
1522
- object.isExplodeLocked = depth > 2 && object.children.length === 0;
1523
- return res;
1524
- }
1525
-
1526
- function explodeModel(scene, scale = 0, coeff = 4) {
1527
- scale /= 100;
1528
- if (!scene.explodeDepth) scene.explodeDepth = calcExplodeDepth(scene, 1);
1529
- const maxDepth = scene.explodeDepth;
1530
- const scaledExplodeDepth = scale * maxDepth + 1;
1531
- const explodeDepth = 0 | scaledExplodeDepth;
1532
- const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
1533
- function explodeObject(object, depth) {
1534
- object.position.copy(object.originalPosition);
1535
- if (depth > 0 && depth <= explodeDepth && !object.isExplodeLocked) {
1536
- let objectScale = scale * coeff;
1537
- if (depth === explodeDepth) objectScale *= currentSegmentFraction;
1538
- const parentCenter = object.parent.originalCenter;
1539
- const objectCenter = object.originalCenter;
1540
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
1541
- object.position.add(objectOffset);
1542
- }
1543
- object.children.forEach((x => explodeObject(x, depth + 1)));
1544
- }
1545
- explodeObject(scene, 0);
1546
- }
1547
-
1548
1519
  function explode(viewer, index = 0) {
1549
- viewer.models.forEach((model => explodeModel(model, index)));
1550
- viewer.scene.updateMatrixWorld();
1520
+ viewer.models.forEach((model => model.explode(index)));
1551
1521
  viewer.update();
1552
1522
  viewer.emitEvent({
1553
1523
  type: "explode",
@@ -1629,107 +1599,19 @@ function getDefaultViewPositions() {
1629
1599
  }
1630
1600
 
1631
1601
  function getModels(viewer) {
1632
- return viewer.models.map((model => {
1633
- var _a;
1634
- return ((_a = model.userData) === null || _a === undefined ? undefined : _a.handle) || "";
1635
- })).filter((handle => handle));
1602
+ return viewer.models.map((model => model.handle));
1636
1603
  }
1637
1604
 
1638
1605
  function getSelected(viewer) {
1639
- return viewer.selected.map((object => {
1640
- var _a;
1641
- return (_a = object.userData) === null || _a === undefined ? undefined : _a.handle;
1642
- })).filter((handle => handle));
1643
- }
1644
-
1645
- class SelectionComponent {
1646
- constructor(viewer) {
1647
- this.onPointerDown = event => {
1648
- if (!event.isPrimary || event.button !== 0) return;
1649
- this.getMousePosition(event, this.downPosition);
1650
- };
1651
- this.onPointerUp = event => {
1652
- if (!event.isPrimary) return;
1653
- const upPosition = this.getMousePosition(event, new Vector2);
1654
- if (this.downPosition.distanceTo(upPosition) !== 0) return;
1655
- const intersects = this.getPointerIntersects(upPosition);
1656
- this.clearSelection();
1657
- if (intersects.length > 0) this.select(intersects[0].object);
1658
- this.viewer.update();
1659
- this.viewer.emitEvent({
1660
- type: "select",
1661
- data: undefined,
1662
- handles: this.viewer.getSelected()
1663
- });
1664
- };
1665
- this.onDoubleClick = event => {
1666
- if (event.button !== 0) return;
1667
- this.viewer.executeCommand("zoomToSelected");
1668
- };
1669
- this.initHighlighter = () => {
1670
- this.highlighter = this.viewer.getComponent("HighlighterComponent");
1671
- };
1672
- this.viewer = viewer;
1673
- this.raycaster = new Raycaster;
1674
- this.downPosition = new Vector2;
1675
- this.viewer.addEventListener("pointerdown", this.onPointerDown);
1676
- this.viewer.addEventListener("pointerup", this.onPointerUp);
1677
- this.viewer.addEventListener("dblclick", this.onDoubleClick);
1678
- this.viewer.addEventListener("initialize", this.initHighlighter);
1679
- }
1680
- dispose() {
1681
- this.viewer.removeEventListener("pointerdown", this.onPointerDown);
1682
- this.viewer.removeEventListener("pointerup", this.onPointerUp);
1683
- this.viewer.removeEventListener("dblclick", this.onDoubleClick);
1684
- this.viewer.removeEventListener("initialize", this.initHighlighter);
1685
- }
1686
- getMousePosition(event, target) {
1687
- return target.set(event.clientX, event.clientY);
1688
- }
1689
- getPointerIntersects(mouse) {
1690
- const rect = this.viewer.canvas.getBoundingClientRect();
1691
- const x = (mouse.x - rect.left) / rect.width * 2 - 1;
1692
- const y = -(mouse.y - rect.top) / rect.height * 2 + 1;
1693
- const coords = new Vector2(x, y);
1694
- this.raycaster.setFromCamera(coords, this.viewer.camera);
1695
- const objects = [];
1696
- this.viewer.scene.traverseVisible((child => objects.push(child)));
1697
- this.raycaster.params = this.raycaster.params = {
1698
- Mesh: {},
1699
- Line: {
1700
- threshold: .25
1701
- },
1702
- Line2: {
1703
- threshold: .25
1704
- },
1705
- LOD: {},
1706
- Points: {
1707
- threshold: .1
1708
- },
1709
- Sprite: {}
1710
- };
1711
- return this.raycaster.intersectObjects(objects, false);
1712
- }
1713
- select(object) {
1714
- if (object.isSelected) return;
1715
- object.isSelected = true;
1716
- this.highlighter.highlight(object);
1717
- this.viewer.selected.push(object);
1718
- }
1719
- clearSelection() {
1720
- this.viewer.selected.forEach((object => {
1721
- object.isSelected = false;
1722
- this.highlighter.unhighlight(object);
1723
- }));
1724
- this.viewer.selected.length = 0;
1725
- }
1606
+ const handles = [];
1607
+ viewer.models.forEach((model => handles.push(...model.getHandlesByObjects(viewer.selected))));
1608
+ return handles;
1726
1609
  }
1727
1610
 
1728
1611
  function hideSelected(viewer) {
1729
- viewer.selected.forEach((object => object.visible = false));
1730
- const selection = new SelectionComponent(viewer);
1612
+ viewer.models.forEach((model => model.hideObjects(viewer.selected)));
1613
+ const selection = viewer.getComponent("SelectionComponent");
1731
1614
  selection.clearSelection();
1732
- selection.dispose();
1733
1615
  viewer.update();
1734
1616
  viewer.emitEvent({
1735
1617
  type: "hide"
@@ -1742,12 +1624,7 @@ function hideSelected(viewer) {
1742
1624
  }
1743
1625
 
1744
1626
  function isolateSelected(viewer) {
1745
- const visibleSet = new Set;
1746
- viewer.selected.forEach((object => {
1747
- visibleSet.add(object);
1748
- object.traverseAncestors((object2 => visibleSet.add(object2)));
1749
- }));
1750
- viewer.scene.traverse((object => object.visible = visibleSet.has(object)));
1627
+ viewer.models.forEach((model => model.isolateObjects(viewer.selected)));
1751
1628
  viewer.update();
1752
1629
  viewer.emitEvent({
1753
1630
  type: "isolate"
@@ -1776,7 +1653,10 @@ function resetView(viewer) {
1776
1653
  }
1777
1654
 
1778
1655
  function selectModel(viewer, handle) {
1779
- console.warn("selectModel not implemented");
1656
+ const selection = viewer.getComponent("SelectionComponent");
1657
+ selection.clearSelection();
1658
+ viewer.models.filter((model => model.handle === handle)).forEach((model => selection.select(model.getObjects(), model)));
1659
+ viewer.update();
1780
1660
  viewer.emit({
1781
1661
  type: "select",
1782
1662
  data: []
@@ -1792,17 +1672,16 @@ function setMarkupColor(viewer, r = 255, g = 0, b = 0) {
1792
1672
  }
1793
1673
 
1794
1674
  function setSelected(viewer, handles = []) {
1795
- const handleSet = new Set(handles);
1796
- const objects = [];
1797
- viewer.scene.traverseVisible((child => {
1798
- var _a;
1799
- if (handleSet.has((_a = child.userData) === null || _a === undefined ? undefined : _a.handle)) objects.push(child);
1800
- }));
1801
1675
  const selection = viewer.getComponent("SelectionComponent");
1802
- if (!selection) return;
1803
1676
  selection.clearSelection();
1804
- objects.forEach((object => selection.select(object)));
1677
+ viewer.models.forEach((model => {
1678
+ const objects = model.getObjectsByHandles(handles);
1679
+ selection.select(objects, model);
1680
+ }));
1805
1681
  viewer.update();
1682
+ viewer.emitEvent({
1683
+ type: "show"
1684
+ });
1806
1685
  viewer.emitEvent({
1807
1686
  type: "select",
1808
1687
  data: undefined,
@@ -1811,7 +1690,7 @@ function setSelected(viewer, handles = []) {
1811
1690
  }
1812
1691
 
1813
1692
  function showAll(viewer) {
1814
- viewer.scene.traverse((object => object.visible = true));
1693
+ viewer.models.forEach((model => model.showAllObjects()));
1815
1694
  viewer.update();
1816
1695
  viewer.emitEvent({
1817
1696
  type: "showall"
@@ -1938,6 +1817,7 @@ class BackgroundComponent {
1938
1817
  constructor(viewer) {
1939
1818
  this.syncOptions = () => {
1940
1819
  this.backgroundColor.setHex(16777215);
1820
+ this.viewer.renderer.setClearColor(this.backgroundColor);
1941
1821
  };
1942
1822
  this.viewer = viewer;
1943
1823
  this.backgroundColor = new Color(16777215);
@@ -1998,23 +1878,25 @@ class ExtentsComponent {
1998
1878
  constructor(viewer) {
1999
1879
  this.syncExtents = () => {
2000
1880
  const extents = new Box3;
2001
- this.viewer.scene.traverseVisible((object => !object.children.length && extents.expandByObject(object)));
1881
+ this.viewer.models.forEach((model => model.getExtents(extents)));
2002
1882
  this.viewer.extents.copy(extents);
2003
1883
  };
2004
1884
  this.viewer = viewer;
2005
1885
  this.viewer.addEventListener("databasechunk", this.syncExtents);
2006
1886
  this.viewer.addEventListener("clear", this.syncExtents);
2007
1887
  this.viewer.on("explode", this.syncExtents);
2008
- this.viewer.on("isolate", this.syncExtents);
2009
1888
  this.viewer.on("hide", this.syncExtents);
1889
+ this.viewer.on("isolate", this.syncExtents);
1890
+ this.viewer.on("show", this.syncExtents);
2010
1891
  this.viewer.on("showall", this.syncExtents);
2011
1892
  }
2012
1893
  dispose() {
2013
1894
  this.viewer.removeEventListener("databasechunk", this.syncExtents);
2014
1895
  this.viewer.removeEventListener("clear", this.syncExtents);
2015
1896
  this.viewer.off("explode", this.syncExtents);
2016
- this.viewer.off("isolate", this.syncExtents);
2017
1897
  this.viewer.off("hide", this.syncExtents);
1898
+ this.viewer.off("isolate", this.syncExtents);
1899
+ this.viewer.off("show", this.syncExtents);
2018
1900
  this.viewer.off("showall", this.syncExtents);
2019
1901
  }
2020
1902
  }
@@ -2209,6 +2091,16 @@ class HighlighterComponent {
2209
2091
  this.viewer.update();
2210
2092
  };
2211
2093
  this.viewer = viewer;
2094
+ const gl2 = viewer.canvas.getContext("webgl2");
2095
+ if (gl2) {
2096
+ const size = viewer.renderer.getSize(new Vector2);
2097
+ this.renderTarget = new WebGLRenderTarget(size.x, size.y, {
2098
+ format: RGBAFormat,
2099
+ stencilBuffer: false,
2100
+ samples: 4,
2101
+ type: UnsignedByteType
2102
+ });
2103
+ }
2212
2104
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
2213
2105
  this.viewer.addEventListener("optionschange", this.optionsChange);
2214
2106
  this.viewer.addEventListener("resize", this.viewerResize);
@@ -2219,46 +2111,174 @@ class HighlighterComponent {
2219
2111
  this.viewer.removeEventListener("optionschange", this.optionsChange);
2220
2112
  this.viewer.removeEventListener("resize", this.viewerResize);
2221
2113
  }
2222
- highlight(object) {
2223
- if (object.isHighlighted) return;
2224
- if (object.isLine || object.isLineSegments) {
2225
- const positions = object.geometry.attributes.position.array;
2226
- const indices = object.geometry.index ? object.geometry.index.array : null;
2227
- const lineGeometry = indices ? HighlighterUtils.fromIndexedLine(positions, indices) : HighlighterUtils.fromNonIndexedLine(positions, object.isLineSegments);
2228
- const wireframe = new Wireframe(lineGeometry, this.highlightLineGlowMaterial);
2229
- wireframe.position.copy(object.position);
2230
- wireframe.rotation.copy(object.rotation);
2231
- wireframe.scale.copy(object.scale);
2232
- object.parent.add(wireframe);
2233
- object.userData.highlightwireframe = wireframe;
2234
- object.userData.originalMaterial = object.material;
2235
- object.material = this.highlightLineMaterial;
2236
- object.isHighlighted = true;
2237
- } else if (object.isMesh) {
2238
- const edgesGeometry = new EdgesGeometry(object.geometry, 30);
2239
- const lineGeometry = (new LineSegmentsGeometry).fromEdgesGeometry(edgesGeometry);
2240
- const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
2241
- wireframe.position.copy(object.position);
2242
- wireframe.rotation.copy(object.rotation);
2243
- wireframe.scale.copy(object.scale);
2244
- object.parent.add(wireframe);
2245
- object.userData.highlightwireframe = wireframe;
2246
- object.userData.originalMaterial = object.material;
2247
- object.material = this.highlightMaterial;
2248
- object.isHighlighted = true;
2249
- }
2250
- }
2251
- unhighlight(object) {
2252
- if (!object.isHighlighted) return;
2253
- object.isHighlighted = false;
2254
- object.material = object.userData.originalMaterial;
2255
- object.userData.highlightwireframe.removeFromParent();
2256
- delete object.userData.originalMaterial;
2257
- delete object.userData.highlightwireframe;
2114
+ highlight(objects) {
2115
+ if (!Array.isArray(objects)) objects = [ objects ];
2116
+ if (!objects.length) return;
2117
+ objects.forEach((object => {
2118
+ if (object.isHighlighted) return;
2119
+ if (object.isLine || object.isLineSegments) {
2120
+ const positions = object.geometry.attributes.position.array;
2121
+ const indices = object.geometry.index ? object.geometry.index.array : null;
2122
+ const lineGeometry = indices ? HighlighterUtils.fromIndexedLine(positions, indices) : HighlighterUtils.fromNonIndexedLine(positions, object.isLineSegments);
2123
+ const wireframe = new Wireframe(lineGeometry, this.highlightLineGlowMaterial);
2124
+ wireframe.position.copy(object.position);
2125
+ wireframe.rotation.copy(object.rotation);
2126
+ wireframe.scale.copy(object.scale);
2127
+ object.parent.add(wireframe);
2128
+ object.userData.highlightwireframe = wireframe;
2129
+ object.userData.originalMaterial = object.material;
2130
+ object.material = this.highlightLineMaterial;
2131
+ object.isHighlighted = true;
2132
+ } else if (object.isMesh) {
2133
+ const edgesGeometry = new EdgesGeometry(object.geometry, 30);
2134
+ const lineGeometry = (new LineSegmentsGeometry).fromEdgesGeometry(edgesGeometry);
2135
+ const wireframe = new Wireframe(lineGeometry, this.outlineMaterial);
2136
+ wireframe.position.copy(object.position);
2137
+ wireframe.rotation.copy(object.rotation);
2138
+ wireframe.scale.copy(object.scale);
2139
+ object.parent.add(wireframe);
2140
+ object.userData.highlightwireframe = wireframe;
2141
+ object.userData.originalMaterial = object.material;
2142
+ object.material = this.highlightMaterial;
2143
+ object.isHighlighted = true;
2144
+ }
2145
+ }));
2146
+ }
2147
+ unhighlight(objects) {
2148
+ if (!Array.isArray(objects)) objects = [ objects ];
2149
+ if (!objects.length) return;
2150
+ objects.forEach((object => {
2151
+ if (!object.isHighlighted) return;
2152
+ object.isHighlighted = false;
2153
+ object.material = object.userData.originalMaterial;
2154
+ object.userData.highlightwireframe.removeFromParent();
2155
+ delete object.userData.originalMaterial;
2156
+ delete object.userData.highlightwireframe;
2157
+ }));
2258
2158
  }
2259
2159
  viewerResize(event) {
2260
- if (!this.outlineMaterial) return;
2261
- this.outlineMaterial.resolution.set(event.width, event.height);
2160
+ var _a, _b;
2161
+ (_a = this.renderTarget) === null || _a === undefined ? undefined : _a.setSize(event.width, event.height);
2162
+ (_b = this.outlineMaterial) === null || _b === undefined ? undefined : _b.resolution.set(event.width, event.height);
2163
+ }
2164
+ }
2165
+
2166
+ class SelectionComponent {
2167
+ constructor(viewer) {
2168
+ this.onPointerDown = event => {
2169
+ if (!event.isPrimary || event.button !== 0) return;
2170
+ this.getMousePosition(event, this.downPosition);
2171
+ };
2172
+ this.onPointerUp = event => {
2173
+ if (!event.isPrimary) return;
2174
+ const upPosition = this.getMousePosition(event, new Vector2);
2175
+ if (upPosition.distanceTo(this.downPosition) !== 0) return;
2176
+ let intersections = [];
2177
+ this.viewer.models.forEach((model => {
2178
+ const objects = model.getVisibleObjects();
2179
+ const intersects = this.getPointerIntersects(upPosition, objects);
2180
+ intersections.push(...intersects.map((x => ({
2181
+ ...x,
2182
+ model: model
2183
+ }))));
2184
+ }));
2185
+ intersections = intersections.sort(((a, b) => a.distance - b.distance));
2186
+ if (!event.shiftKey) this.clearSelection();
2187
+ if (intersections.length > 0) {
2188
+ const model = intersections[0].model;
2189
+ const handles = model.getHandlesByObjects(intersections[0].object);
2190
+ const objects = model.getObjectsByHandles(handles);
2191
+ if (!event.shiftKey) this.select(objects, model); else this.toggleSelection(objects, model);
2192
+ }
2193
+ this.viewer.update();
2194
+ this.viewer.emitEvent({
2195
+ type: "select",
2196
+ data: undefined,
2197
+ handles: this.viewer.getSelected()
2198
+ });
2199
+ };
2200
+ this.onDoubleClick = event => {
2201
+ if (event.button !== 0) return;
2202
+ this.viewer.executeCommand("zoomToSelected");
2203
+ };
2204
+ this.initHighlighter = () => {
2205
+ this.highlighter = this.viewer.getComponent("HighlighterComponent");
2206
+ };
2207
+ this.viewer = viewer;
2208
+ this.raycaster = new Raycaster;
2209
+ this.downPosition = new Vector2;
2210
+ this.viewer.addEventListener("pointerdown", this.onPointerDown);
2211
+ this.viewer.addEventListener("pointerup", this.onPointerUp);
2212
+ this.viewer.addEventListener("dblclick", this.onDoubleClick);
2213
+ this.viewer.addEventListener("initialize", this.initHighlighter);
2214
+ }
2215
+ dispose() {
2216
+ this.viewer.removeEventListener("pointerdown", this.onPointerDown);
2217
+ this.viewer.removeEventListener("pointerup", this.onPointerUp);
2218
+ this.viewer.removeEventListener("dblclick", this.onDoubleClick);
2219
+ this.viewer.removeEventListener("initialize", this.initHighlighter);
2220
+ }
2221
+ getMousePosition(event, target) {
2222
+ return target.set(event.clientX, event.clientY);
2223
+ }
2224
+ getPointerIntersects(mouse, objects) {
2225
+ const rect = this.viewer.canvas.getBoundingClientRect();
2226
+ const x = (mouse.x - rect.left) / rect.width * 2 - 1;
2227
+ const y = -(mouse.y - rect.top) / rect.height * 2 + 1;
2228
+ const coords = new Vector2(x, y);
2229
+ this.raycaster.setFromCamera(coords, this.viewer.camera);
2230
+ this.raycaster.params = this.raycaster.params = {
2231
+ Mesh: {},
2232
+ Line: {
2233
+ threshold: .25
2234
+ },
2235
+ Line2: {
2236
+ threshold: .25
2237
+ },
2238
+ LOD: {},
2239
+ Points: {
2240
+ threshold: .1
2241
+ },
2242
+ Sprite: {}
2243
+ };
2244
+ return this.raycaster.intersectObjects(objects, false);
2245
+ }
2246
+ select(objects, model) {
2247
+ if (!model) {
2248
+ this.viewer.models.forEach((model => this.select(objects, model)));
2249
+ return;
2250
+ }
2251
+ if (!Array.isArray(objects)) objects = [ objects ];
2252
+ if (!objects.length) return;
2253
+ model.showObjects(objects);
2254
+ model.showOriginalObjects(objects);
2255
+ this.highlighter.highlight(objects);
2256
+ objects.forEach((object => this.viewer.selected.push(object)));
2257
+ objects.forEach((object => object.isSelected = true));
2258
+ }
2259
+ deselect(objects, model) {
2260
+ if (!model) {
2261
+ this.viewer.models.forEach((model => this.select(objects, model)));
2262
+ return;
2263
+ }
2264
+ if (!Array.isArray(objects)) objects = [ objects ];
2265
+ if (!objects.length) return;
2266
+ this.highlighter.unhighlight(objects);
2267
+ model.hideOriginalObjects(objects);
2268
+ this.viewer.selected = this.viewer.selected.filter((x => !objects.includes(x)));
2269
+ objects.forEach((object => object.isSelected = false));
2270
+ }
2271
+ toggleSelection(objects, model) {
2272
+ if (!Array.isArray(objects)) objects = [ objects ];
2273
+ if (!objects.length) return;
2274
+ if (objects[0].isSelected) this.deselect(objects, model); else this.select(objects, model);
2275
+ }
2276
+ clearSelection() {
2277
+ if (!this.viewer.selected.length) return;
2278
+ this.highlighter.unhighlight(this.viewer.selected);
2279
+ this.viewer.models.forEach((model => model.hideOriginalObjects(this.viewer.selected)));
2280
+ this.viewer.selected.forEach((object => object.isSelected = false));
2281
+ this.viewer.selected.length = 0;
2262
2282
  }
2263
2283
  }
2264
2284
 
@@ -2419,6 +2439,132 @@ class GLTFLoadingManager extends LoadingManager {
2419
2439
  }
2420
2440
  }
2421
2441
 
2442
+ class ModelImpl {
2443
+ constructor(scene) {
2444
+ this.handle = "1";
2445
+ this.scene = scene;
2446
+ }
2447
+ dispose() {
2448
+ function disposeMaterial(material) {
2449
+ material.dispose();
2450
+ }
2451
+ function disposeMaterials(material) {
2452
+ const materials = Array.isArray(material) ? material : [ material ];
2453
+ materials.forEach((material => disposeMaterial(material)));
2454
+ }
2455
+ function disposeObject(object) {
2456
+ if (object.geometry) object.geometry.dispose();
2457
+ if (object.material) disposeMaterials(object.material);
2458
+ }
2459
+ this.scene.traverse(disposeObject);
2460
+ this.scene.clear();
2461
+ }
2462
+ getExtents(target) {
2463
+ this.scene.traverseVisible((object => !object.children.length && target.expandByObject(object)));
2464
+ return target;
2465
+ }
2466
+ getObjects() {
2467
+ const objects = [];
2468
+ this.scene.traverse((object => objects.push(object)));
2469
+ return objects;
2470
+ }
2471
+ getVisibleObjects() {
2472
+ const objects = [];
2473
+ this.scene.traverseVisible((object => objects.push(object)));
2474
+ return objects;
2475
+ }
2476
+ hasObject(object) {
2477
+ while (object) {
2478
+ if (object === this.scene) return true;
2479
+ object = object.parent;
2480
+ }
2481
+ return false;
2482
+ }
2483
+ getOwnObjects(objects) {
2484
+ if (!Array.isArray(objects)) objects = [ objects ];
2485
+ return objects.filter((object => this.hasObject(object)));
2486
+ }
2487
+ getObjectsByHandles(handles) {
2488
+ const handleSet = new Set(handles);
2489
+ const objects = [];
2490
+ this.scene.traverse((object => handleSet.has(object.userData.handle) && objects.push(object)));
2491
+ return objects;
2492
+ }
2493
+ getHandlesByObjects(objects) {
2494
+ if (!Array.isArray(objects)) objects = [ objects ];
2495
+ const handlesSet = new Set;
2496
+ this.getOwnObjects(objects).forEach((object => handlesSet.add(object.userData.handle)));
2497
+ return Array.from(handlesSet);
2498
+ }
2499
+ hideObjects(objects) {
2500
+ if (!Array.isArray(objects)) objects = [ objects ];
2501
+ this.getOwnObjects(objects).forEach((object => object.visible = false));
2502
+ return this;
2503
+ }
2504
+ hideAllObjects() {
2505
+ return this.isolateObjects([]);
2506
+ }
2507
+ isolateObjects(objects) {
2508
+ if (!Array.isArray(objects)) objects = [ objects ];
2509
+ const visibleSet = new Set(objects);
2510
+ this.getOwnObjects(objects).forEach((object => object.traverseAncestors((parent => visibleSet.add(parent)))));
2511
+ this.scene.traverse((object => object.visible = visibleSet.has(object)));
2512
+ return this;
2513
+ }
2514
+ showObjects(objects) {
2515
+ if (!Array.isArray(objects)) objects = [ objects ];
2516
+ this.getOwnObjects(objects).forEach((object => {
2517
+ object.visible = true;
2518
+ object.traverseAncestors((parent => parent.visible = true));
2519
+ }));
2520
+ return this;
2521
+ }
2522
+ showAllObjects() {
2523
+ this.scene.traverse((object => object.visible = true));
2524
+ return this;
2525
+ }
2526
+ showOriginalObjects(objects) {
2527
+ return this;
2528
+ }
2529
+ hideOriginalObjects(objects) {
2530
+ return this;
2531
+ }
2532
+ explode(scale = 0, coeff = 4) {
2533
+ function calcExplodeDepth(object, depth) {
2534
+ let res = depth;
2535
+ object.children.forEach((x => {
2536
+ const objectDepth = calcExplodeDepth(x, depth + 1);
2537
+ if (res < objectDepth) res = objectDepth;
2538
+ }));
2539
+ object.userData.originalPosition = object.position.clone();
2540
+ object.userData.originalCenter = (new Box3).setFromObject(object).getCenter(new Vector3);
2541
+ object.userData.isExplodeLocked = depth > 2 && object.children.length === 0;
2542
+ return res;
2543
+ }
2544
+ scale /= 100;
2545
+ if (!this.scene.userData.explodeDepth) this.scene.userData.explodeDepth = calcExplodeDepth(this.scene, 1);
2546
+ const maxDepth = this.scene.userData.explodeDepth;
2547
+ const scaledExplodeDepth = scale * maxDepth + 1;
2548
+ const explodeDepth = 0 | scaledExplodeDepth;
2549
+ const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
2550
+ function explodeObject(object, depth) {
2551
+ object.position.copy(object.userData.originalPosition);
2552
+ if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
2553
+ let objectScale = scale * coeff;
2554
+ if (depth === explodeDepth) objectScale *= currentSegmentFraction;
2555
+ const parentCenter = object.parent.userData.originalCenter;
2556
+ const objectCenter = object.userData.originalCenter;
2557
+ const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
2558
+ object.position.add(objectOffset);
2559
+ }
2560
+ object.children.forEach((x => explodeObject(x, depth + 1)));
2561
+ }
2562
+ explodeObject(this.scene, 0);
2563
+ this.scene.updateMatrixWorld();
2564
+ return this;
2565
+ }
2566
+ }
2567
+
2422
2568
  class GLTFFileLoader extends Loader {
2423
2569
  constructor(viewer) {
2424
2570
  super();
@@ -2444,8 +2590,11 @@ class GLTFFileLoader extends Loader {
2444
2590
  };
2445
2591
  const gltf = await loader.loadAsync(manager.fileURL, progress);
2446
2592
  if (!this.viewer.scene) return this;
2447
- this.viewer.scene.add(gltf.scene);
2448
- this.viewer.models.push(gltf.scene);
2593
+ const modelImpl = new ModelImpl(gltf.scene);
2594
+ modelImpl.loader = this;
2595
+ modelImpl.viewer = this.viewer;
2596
+ this.viewer.scene.add(modelImpl.scene);
2597
+ this.viewer.models.push(modelImpl);
2449
2598
  this.viewer.syncOptions();
2450
2599
  this.viewer.syncOverlay();
2451
2600
  this.viewer.update();
@@ -2458,61 +2607,1980 @@ class GLTFFileLoader extends Loader {
2458
2607
  }
2459
2608
  }
2460
2609
 
2461
- class GLTFCloudModelLoader extends Loader {
2462
- constructor(viewer) {
2463
- super();
2464
- this.viewer = viewer;
2610
+ class DynamicModelImpl extends ModelImpl {
2611
+ getExtents(target) {
2612
+ return target.union(this.gltfLoader.getTotalGeometryExtent());
2465
2613
  }
2466
- isSupport(model) {
2467
- return typeof model === "object" && typeof model.database === "string" && typeof model.downloadResource === "function" && /.gltf$/i.test(model.database);
2614
+ getObjects() {
2615
+ const objects = [];
2616
+ this.gltfLoader.originalObjects.forEach((object => {
2617
+ objects.push(object);
2618
+ }));
2619
+ return objects;
2468
2620
  }
2469
- async load(model) {
2470
- const url = `${model.httpClient.serverUrl}${model.path}/${model.database}`;
2471
- const manager = new GLTFLoadingManager(url);
2472
- const loader = new GLTFLoader(manager);
2473
- loader.setRequestHeader(model.httpClient.headers);
2474
- const progress = event => {
2475
- const {lengthComputable: lengthComputable, loaded: loaded, total: total} = event;
2476
- const progress = lengthComputable ? loaded / total : 1;
2477
- this.viewer.emitEvent({
2478
- type: "geometryprogress",
2479
- data: progress,
2480
- file: model.file,
2481
- model: model
2482
- });
2483
- };
2484
- const gltf = await loader.loadAsync(url, progress);
2485
- if (!this.viewer.scene) return this;
2486
- this.viewer.scene.add(gltf.scene);
2487
- this.viewer.models.push(gltf.scene);
2488
- this.viewer.syncOptions();
2489
- this.viewer.syncOverlay();
2490
- this.viewer.update();
2491
- this.viewer.emitEvent({
2492
- type: "databasechunk",
2493
- data: gltf.scene,
2494
- file: model.file,
2495
- model: model
2496
- });
2621
+ getVisibleObjects() {
2622
+ return this.gltfLoader.getOriginalObjectForSelect();
2623
+ }
2624
+ hasObject(object) {
2625
+ return this.gltfLoader.originalObjects.has(object);
2626
+ }
2627
+ getObjectsByHandles(handles) {
2628
+ const handlesSet = new Set(handles);
2629
+ const objects = [];
2630
+ handlesSet.forEach((handle => {
2631
+ const handles = this.gltfLoader.handleToObjects.get(handle) || [];
2632
+ objects.push(...Array.from(handles));
2633
+ }));
2634
+ return objects;
2635
+ }
2636
+ hideObjects(objects) {
2637
+ this.getOwnObjects(objects).map((object => object.userData.handle)).forEach((handle => this.gltfLoader.hiddenHandles.add(handle)));
2638
+ this.gltfLoader.syncHiddenObjects();
2497
2639
  return this;
2498
2640
  }
2499
- }
2500
-
2501
- const loaders = loadersRegistry("threejs");
2502
-
2503
- loaders.registerLoader("gltf-file", (viewer => new GLTFFileLoader(viewer)));
2504
-
2505
- loaders.registerLoader("gltf-cloud-model", (viewer => new GLTFCloudModelLoader(viewer)));
2506
-
2507
- class Viewer extends EventEmitter2 {
2641
+ isolateObjects(objects) {
2642
+ const handles = this.getHandlesByObjects(objects);
2643
+ this.gltfLoader.isolateObjects(new Set(handles));
2644
+ return this;
2645
+ }
2646
+ showObjects(objects) {
2647
+ this.getOwnObjects(objects).map((object => object.userData.handle)).forEach((handle => this.gltfLoader.hiddenHandles.delete(handle)));
2648
+ this.gltfLoader.syncHiddenObjects();
2649
+ return this;
2650
+ }
2651
+ showAllObjects() {
2652
+ this.gltfLoader.hiddenHandles.clear();
2653
+ this.gltfLoader.syncHiddenObjects();
2654
+ return this;
2655
+ }
2656
+ showOriginalObjects(objects) {
2657
+ this.getOwnObjects(objects).forEach((object => object.visible = true));
2658
+ return this;
2659
+ }
2660
+ hideOriginalObjects(objects) {
2661
+ this.getOwnObjects(objects).forEach((object => object.visible = false));
2662
+ return this;
2663
+ }
2664
+ }
2665
+
2666
+ const GL_COMPONENT_TYPES = {
2667
+ 5120: Int8Array,
2668
+ 5121: Uint8Array,
2669
+ 5122: Int16Array,
2670
+ 5123: Uint16Array,
2671
+ 5125: Uint32Array,
2672
+ 5126: Float32Array
2673
+ };
2674
+
2675
+ const GL_CONSTANTS = {
2676
+ POINTS: 0,
2677
+ LINES: 1,
2678
+ LINE_LOOP: 2,
2679
+ LINE_STRIP: 3,
2680
+ TRIANGLES: 4,
2681
+ TRIANGLE_STRIP: 5,
2682
+ TRIANGLE_FAN: 6
2683
+ };
2684
+
2685
+ class GltfStructure {
2686
+ constructor(id) {
2687
+ this.id = `${id}`;
2688
+ this.json = null;
2689
+ this.baseUrl = "";
2690
+ this.loadController = null;
2691
+ this.batchDelay = 10;
2692
+ this.maxBatchSize = 5 * 1024 * 1024;
2693
+ this.maxRangesPerRequest = 512;
2694
+ this.pendingRequests = [];
2695
+ this.batchTimeout = null;
2696
+ this.textureLoader = new TextureLoader;
2697
+ this.materials = new Map;
2698
+ this.textureCache = new Map;
2699
+ }
2700
+ async initialize(loadController) {
2701
+ this.json = await loadController.loadJson();
2702
+ this.baseUrl = await loadController.baseUrl();
2703
+ this.loadController = loadController;
2704
+ }
2705
+ clear() {
2706
+ this.json = null;
2707
+ this.baseUrl = "";
2708
+ this.loadController = null;
2709
+ this.pendingRequests = [];
2710
+ if (this.batchTimeout) {
2711
+ clearTimeout(this.batchTimeout);
2712
+ this.batchTimeout = null;
2713
+ }
2714
+ this.disposeMaterials();
2715
+ this.textureCache.clear();
2716
+ this.materials.clear();
2717
+ }
2718
+ getJson() {
2719
+ return this.json;
2720
+ }
2721
+ scheduleRequest(request) {
2722
+ this.pendingRequests.push(request);
2723
+ if (this.batchTimeout) {
2724
+ clearTimeout(this.batchTimeout);
2725
+ }
2726
+ this.batchTimeout = setTimeout((() => this.processBatch()), this.batchDelay);
2727
+ return new Promise(((resolve, reject) => {
2728
+ request.resolve = resolve;
2729
+ request.reject = reject;
2730
+ }));
2731
+ }
2732
+ async processBatch() {
2733
+ if (this.pendingRequests.length === 0) return;
2734
+ const currentBatch = [ ...this.pendingRequests ];
2735
+ this.pendingRequests = [];
2736
+ if (this.batchTimeout) {
2737
+ clearTimeout(this.batchTimeout);
2738
+ this.batchTimeout = null;
2739
+ }
2740
+ try {
2741
+ for (let i = 0; i < currentBatch.length; i += this.maxRangesPerRequest) {
2742
+ const batchRequests = currentBatch.slice(i, i + this.maxRangesPerRequest);
2743
+ const buffer = await this.loadController.loadBinaryData(batchRequests);
2744
+ let currentOffset = 0;
2745
+ batchRequests.forEach((request => {
2746
+ const view = this.createTypedArray(buffer, currentOffset, request.length, request.componentType);
2747
+ request.resolve(view);
2748
+ currentOffset += request.length;
2749
+ }));
2750
+ }
2751
+ } catch (error) {
2752
+ console.error("Error processing batch:", error);
2753
+ currentBatch.forEach((request => request.reject(error)));
2754
+ }
2755
+ if (this.pendingRequests.length > 0) {
2756
+ this.batchTimeout = setTimeout((() => this.processBatch()), this.batchDelay);
2757
+ }
2758
+ }
2759
+ getBufferView(byteOffset, byteLength, componentType) {
2760
+ return this.scheduleRequest({
2761
+ offset: byteOffset,
2762
+ length: byteLength,
2763
+ componentType: componentType
2764
+ });
2765
+ }
2766
+ createTypedArray(buffer, offset, length, componentType) {
2767
+ try {
2768
+ if (!buffer || !(buffer instanceof ArrayBuffer)) {
2769
+ throw new Error("Invalid buffer");
2770
+ }
2771
+ let elementSize;
2772
+ switch (componentType) {
2773
+ case 5120:
2774
+ case 5121:
2775
+ elementSize = 1;
2776
+ break;
2777
+
2778
+ case 5122:
2779
+ case 5123:
2780
+ elementSize = 2;
2781
+ break;
2782
+
2783
+ case 5125:
2784
+ case 5126:
2785
+ elementSize = 4;
2786
+ break;
2787
+
2788
+ default:
2789
+ throw new Error(`Unsupported component type: ${componentType}`);
2790
+ }
2791
+ const numElements = length / elementSize;
2792
+ if (!Number.isInteger(numElements)) {
2793
+ throw new Error(`Invalid length ${length} for component type ${componentType}`);
2794
+ }
2795
+ if (length > buffer.byteLength) {
2796
+ throw new Error(`Buffer too small: need ${length} bytes, but buffer is ${buffer.byteLength} bytes`);
2797
+ }
2798
+ const ArrayType = GL_COMPONENT_TYPES[componentType];
2799
+ return new ArrayType(buffer, offset, numElements);
2800
+ } catch (error) {
2801
+ if (error.name !== "AbortError") {
2802
+ console.error("Error creating typed array:", {
2803
+ bufferSize: buffer?.byteLength,
2804
+ offset: offset,
2805
+ length: length,
2806
+ componentType: componentType,
2807
+ error: error
2808
+ });
2809
+ }
2810
+ throw error;
2811
+ }
2812
+ }
2813
+ async createBufferAttribute(accessorIndex) {
2814
+ if (!this.json) {
2815
+ throw new Error("No GLTF structure loaded");
2816
+ }
2817
+ const gltf = this.json;
2818
+ const accessor = gltf.accessors[accessorIndex];
2819
+ const bufferView = gltf.bufferViews[accessor.bufferView];
2820
+ try {
2821
+ const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
2822
+ const components = this.getNumComponents(accessor.type);
2823
+ const count = accessor.count;
2824
+ const byteLength = count * components * this.getComponentSize(accessor.componentType);
2825
+ const array = await this.getBufferView(byteOffset, byteLength, accessor.componentType);
2826
+ const attribute = new BufferAttribute(array, components);
2827
+ if (accessor.min !== undefined) attribute.min = accessor.min;
2828
+ if (accessor.max !== undefined) attribute.max = accessor.max;
2829
+ return attribute;
2830
+ } catch (error) {
2831
+ if (error.name !== "AbortError") {
2832
+ console.error("Error creating buffer attribute:", {
2833
+ error: error,
2834
+ accessor: accessor,
2835
+ bufferView: bufferView
2836
+ });
2837
+ }
2838
+ throw error;
2839
+ }
2840
+ }
2841
+ getComponentSize(componentType) {
2842
+ switch (componentType) {
2843
+ case 5120:
2844
+ case 5121:
2845
+ return 1;
2846
+
2847
+ case 5122:
2848
+ case 5123:
2849
+ return 2;
2850
+
2851
+ case 5125:
2852
+ case 5126:
2853
+ return 4;
2854
+
2855
+ default:
2856
+ throw new Error(`Unknown component type: ${componentType}`);
2857
+ }
2858
+ }
2859
+ getNumComponents(type) {
2860
+ switch (type) {
2861
+ case "SCALAR":
2862
+ return 1;
2863
+
2864
+ case "VEC2":
2865
+ return 2;
2866
+
2867
+ case "VEC3":
2868
+ return 3;
2869
+
2870
+ case "VEC4":
2871
+ return 4;
2872
+
2873
+ case "MAT2":
2874
+ return 4;
2875
+
2876
+ case "MAT3":
2877
+ return 9;
2878
+
2879
+ case "MAT4":
2880
+ return 16;
2881
+
2882
+ default:
2883
+ throw new Error(`Unknown type: ${type}`);
2884
+ }
2885
+ }
2886
+ async loadTextures() {
2887
+ if (!this.json.textures) return;
2888
+ const loadTexture = async imageIndex => {
2889
+ const image = this.json.images[imageIndex];
2890
+ if (image.uri) {
2891
+ if (image.uri.startsWith("data:")) {
2892
+ return await this.textureLoader.loadAsync(image.uri);
2893
+ } else {
2894
+ const fullUrl = this.baseUrl + image.uri;
2895
+ return await this.textureLoader.loadAsync(fullUrl);
2896
+ }
2897
+ } else if (image.bufferView !== undefined) {
2898
+ const bufferView = this.json.bufferViews[image.bufferView];
2899
+ const array = await this.getBufferView(bufferView.byteOffset || 0, bufferView.byteLength, 5121);
2900
+ const blob = new Blob([ array ], {
2901
+ type: image.mimeType
2902
+ });
2903
+ const url = URL.createObjectURL(blob);
2904
+ const texture = await this.textureLoader.loadAsync(url);
2905
+ URL.revokeObjectURL(url);
2906
+ texture.flipY = false;
2907
+ return texture;
2908
+ }
2909
+ };
2910
+ const texturePromises = [];
2911
+ for (let i = 0; i < this.json.textures.length; i++) {
2912
+ texturePromises.push(loadTexture(this.json.textures[i].source).then((texture => this.textureCache.set(i, texture))));
2913
+ }
2914
+ await Promise.all(texturePromises);
2915
+ }
2916
+ loadMaterials() {
2917
+ if (!this.json.materials) return this.materials;
2918
+ for (let i = 0; i < this.json.materials.length; i++) {
2919
+ const materialDef = this.json.materials[i];
2920
+ const material = this.createMaterial(materialDef);
2921
+ this.materials.set(i, material);
2922
+ }
2923
+ return this.materials;
2924
+ }
2925
+ createMaterial(materialDef) {
2926
+ const material = new MeshStandardMaterial;
2927
+ if (materialDef.pbrMetallicRoughness) {
2928
+ const pbr = materialDef.pbrMetallicRoughness;
2929
+ if (pbr.baseColorFactor) {
2930
+ material.color.fromArray(pbr.baseColorFactor);
2931
+ material.opacity = pbr.baseColorFactor[3];
2932
+ }
2933
+ if (pbr.baseColorTexture) {
2934
+ material.map = this.textureCache.get(pbr.baseColorTexture.index);
2935
+ }
2936
+ if (pbr.metallicFactor !== undefined) {
2937
+ material.metalness = pbr.metallicFactor;
2938
+ }
2939
+ if (pbr.roughnessFactor !== undefined) {
2940
+ material.roughness = pbr.roughnessFactor;
2941
+ }
2942
+ if (pbr.metallicRoughnessTexture) {
2943
+ material.metalnessMap = this.textureCache.get(pbr.metallicRoughnessTexture.index);
2944
+ material.roughnessMap = material.metalnessMap;
2945
+ }
2946
+ }
2947
+ if (materialDef.normalTexture) {
2948
+ material.normalMap = this.textureCache.get(materialDef.normalTexture.index);
2949
+ if (materialDef.normalTexture.scale !== undefined) {
2950
+ material.normalScale.set(materialDef.normalTexture.scale, materialDef.normalTexture.scale);
2951
+ }
2952
+ }
2953
+ if (materialDef.emissiveFactor) {
2954
+ material.emissive.fromArray(materialDef.emissiveFactor);
2955
+ }
2956
+ if (materialDef.emissiveTexture) {
2957
+ material.emissiveMap = this.textureCache.get(materialDef.emissiveTexture.index);
2958
+ }
2959
+ if (materialDef.occlusionTexture) {
2960
+ material.aoMap = this.textureCache.get(materialDef.occlusionTexture.index);
2961
+ if (materialDef.occlusionTexture.strength !== undefined) {
2962
+ material.aoMapIntensity = materialDef.occlusionTexture.strength;
2963
+ }
2964
+ }
2965
+ if (materialDef.alphaMode === "BLEND") {
2966
+ material.transparent = true;
2967
+ } else if (materialDef.alphaMode === "MASK") {
2968
+ material.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : .5;
2969
+ }
2970
+ material.side = materialDef.doubleSided ? DoubleSide : FrontSide;
2971
+ material.name = materialDef.name;
2972
+ return material;
2973
+ }
2974
+ disposeMaterials() {
2975
+ this.textureCache.forEach((texture => texture.dispose()));
2976
+ this.textureCache.clear();
2977
+ this.materials.forEach((material => {
2978
+ if (material.map) material.map.dispose();
2979
+ if (material.lightMap) material.lightMap.dispose();
2980
+ if (material.bumpMap) material.bumpMap.dispose();
2981
+ if (material.normalMap) material.normalMap.dispose();
2982
+ if (material.specularMap) material.specularMap.dispose();
2983
+ if (material.envMap) material.envMap.dispose();
2984
+ if (material.aoMap) material.aoMap.dispose();
2985
+ if (material.metalnessMap) material.metalnessMap.dispose();
2986
+ if (material.roughnessMap) material.roughnessMap.dispose();
2987
+ if (material.emissiveMap) material.emissiveMap.dispose();
2988
+ material.dispose();
2989
+ }));
2990
+ this.materials.clear();
2991
+ }
2992
+ estimateNodeSize(meshIndex) {
2993
+ if (!this.json.meshes) return 0;
2994
+ const meshDef = this.json.meshes[meshIndex];
2995
+ if (!meshDef || !meshDef.primitives) return 0;
2996
+ let totalSize = 0;
2997
+ for (const primitive of meshDef.primitives) {
2998
+ if (primitive.attributes) {
2999
+ for (const [, accessorIndex] of Object.entries(primitive.attributes)) {
3000
+ if (accessorIndex === undefined) continue;
3001
+ const accessor = this.json.accessors[accessorIndex];
3002
+ if (!accessor) continue;
3003
+ const numComponents = this.getNumComponents(accessor.type);
3004
+ const bytesPerComponent = this.getComponentSize(accessor.componentType);
3005
+ totalSize += accessor.count * numComponents * bytesPerComponent;
3006
+ }
3007
+ }
3008
+ if (primitive.indices !== undefined) {
3009
+ const accessor = this.json.accessors[primitive.indices];
3010
+ if (accessor) {
3011
+ const bytesPerComponent = this.getComponentSize(accessor.componentType);
3012
+ totalSize += accessor.count * bytesPerComponent;
3013
+ }
3014
+ }
3015
+ }
3016
+ return totalSize;
3017
+ }
3018
+ }
3019
+
3020
+ function mergeGeometries(geometries, useGroups = false) {
3021
+ const isIndexed = geometries[0].index !== null;
3022
+ const attributesUsed = new Set(Object.keys(geometries[0].attributes));
3023
+ const morphAttributesUsed = new Set(Object.keys(geometries[0].morphAttributes));
3024
+ const attributes = {};
3025
+ const morphAttributes = {};
3026
+ const morphTargetsRelative = geometries[0].morphTargetsRelative;
3027
+ const mergedGeometry = new BufferGeometry;
3028
+ let offset = 0;
3029
+ for (let i = 0; i < geometries.length; ++i) {
3030
+ const geometry = geometries[i];
3031
+ let attributesCount = 0;
3032
+ if (isIndexed !== (geometry.index !== null)) {
3033
+ 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.");
3034
+ return null;
3035
+ }
3036
+ for (const name in geometry.attributes) {
3037
+ if (!attributesUsed.has(name)) {
3038
+ 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.');
3039
+ return null;
3040
+ }
3041
+ if (attributes[name] === undefined) attributes[name] = [];
3042
+ attributes[name].push(geometry.attributes[name]);
3043
+ attributesCount++;
3044
+ }
3045
+ if (attributesCount !== attributesUsed.size) {
3046
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index " + i + ". Make sure all geometries have the same number of attributes.");
3047
+ return null;
3048
+ }
3049
+ if (morphTargetsRelative !== geometry.morphTargetsRelative) {
3050
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index " + i + ". .morphTargetsRelative must be consistent throughout all geometries.");
3051
+ return null;
3052
+ }
3053
+ for (const name in geometry.morphAttributes) {
3054
+ if (!morphAttributesUsed.has(name)) {
3055
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index " + i + ". .morphAttributes must be consistent throughout all geometries.");
3056
+ return null;
3057
+ }
3058
+ if (morphAttributes[name] === undefined) morphAttributes[name] = [];
3059
+ morphAttributes[name].push(geometry.morphAttributes[name]);
3060
+ }
3061
+ if (useGroups) {
3062
+ let count;
3063
+ if (isIndexed) {
3064
+ count = geometry.index.count;
3065
+ } else if (geometry.attributes.position !== undefined) {
3066
+ count = geometry.attributes.position.count;
3067
+ } else {
3068
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index " + i + ". The geometry must have either an index or a position attribute");
3069
+ return null;
3070
+ }
3071
+ mergedGeometry.addGroup(offset, count, i);
3072
+ offset += count;
3073
+ }
3074
+ }
3075
+ if (isIndexed) {
3076
+ let indexOffset = 0;
3077
+ const mergedIndex = [];
3078
+ for (let i = 0; i < geometries.length; ++i) {
3079
+ const index = geometries[i].index;
3080
+ for (let j = 0; j < index.count; ++j) {
3081
+ mergedIndex.push(index.getX(j) + indexOffset);
3082
+ }
3083
+ indexOffset += geometries[i].attributes.position.count;
3084
+ }
3085
+ mergedGeometry.setIndex(mergedIndex);
3086
+ }
3087
+ for (const name in attributes) {
3088
+ const mergedAttribute = mergeAttributes(attributes[name]);
3089
+ if (!mergedAttribute) {
3090
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the " + name + " attribute.");
3091
+ return null;
3092
+ }
3093
+ mergedGeometry.setAttribute(name, mergedAttribute);
3094
+ }
3095
+ for (const name in morphAttributes) {
3096
+ const numMorphTargets = morphAttributes[name][0].length;
3097
+ if (numMorphTargets === 0) break;
3098
+ mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
3099
+ mergedGeometry.morphAttributes[name] = [];
3100
+ for (let i = 0; i < numMorphTargets; ++i) {
3101
+ const morphAttributesToMerge = [];
3102
+ for (let j = 0; j < morphAttributes[name].length; ++j) {
3103
+ morphAttributesToMerge.push(morphAttributes[name][j][i]);
3104
+ }
3105
+ const mergedMorphAttribute = mergeAttributes(morphAttributesToMerge);
3106
+ if (!mergedMorphAttribute) {
3107
+ console.error("THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the " + name + " morphAttribute.");
3108
+ return null;
3109
+ }
3110
+ mergedGeometry.morphAttributes[name].push(mergedMorphAttribute);
3111
+ }
3112
+ }
3113
+ return mergedGeometry;
3114
+ }
3115
+
3116
+ function mergeAttributes(attributes) {
3117
+ let TypedArray;
3118
+ let itemSize;
3119
+ let normalized;
3120
+ let gpuType = -1;
3121
+ let arrayLength = 0;
3122
+ for (let i = 0; i < attributes.length; ++i) {
3123
+ const attribute = attributes[i];
3124
+ if (TypedArray === undefined) TypedArray = attribute.array.constructor;
3125
+ if (TypedArray !== attribute.array.constructor) {
3126
+ console.error("THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.");
3127
+ return null;
3128
+ }
3129
+ if (itemSize === undefined) itemSize = attribute.itemSize;
3130
+ if (itemSize !== attribute.itemSize) {
3131
+ console.error("THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.");
3132
+ return null;
3133
+ }
3134
+ if (normalized === undefined) normalized = attribute.normalized;
3135
+ if (normalized !== attribute.normalized) {
3136
+ console.error("THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.");
3137
+ return null;
3138
+ }
3139
+ if (gpuType === -1) gpuType = attribute.gpuType;
3140
+ if (gpuType !== attribute.gpuType) {
3141
+ console.error("THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.");
3142
+ return null;
3143
+ }
3144
+ arrayLength += attribute.count * itemSize;
3145
+ }
3146
+ const array = new TypedArray(arrayLength);
3147
+ const result = new BufferAttribute(array, itemSize, normalized);
3148
+ let offset = 0;
3149
+ for (let i = 0; i < attributes.length; ++i) {
3150
+ const attribute = attributes[i];
3151
+ if (attribute.isInterleavedBufferAttribute) {
3152
+ const tupleOffset = offset / itemSize;
3153
+ for (let j = 0, l = attribute.count; j < l; j++) {
3154
+ for (let c = 0; c < itemSize; c++) {
3155
+ const value = attribute.getComponent(j, c);
3156
+ result.setComponent(j + tupleOffset, c, value);
3157
+ }
3158
+ }
3159
+ } else {
3160
+ array.set(attribute.array, offset);
3161
+ }
3162
+ offset += attribute.count * itemSize;
3163
+ }
3164
+ if (gpuType !== undefined) {
3165
+ result.gpuType = gpuType;
3166
+ }
3167
+ return result;
3168
+ }
3169
+
3170
+ class DynamicGltfLoader {
3171
+ constructor(camera, scene, renderer) {
3172
+ this.camera = camera;
3173
+ this.scene = scene;
3174
+ this.renderer = renderer;
3175
+ this.eventHandlers = {
3176
+ geometryprogress: [],
3177
+ databasechunk: [],
3178
+ geometryend: [],
3179
+ geometryerror: [],
3180
+ update: [],
3181
+ geometrymemory: []
3182
+ };
3183
+ this.loadDistance = 100;
3184
+ this.unloadDistance = 150;
3185
+ this.checkInterval = 1e3;
3186
+ this.nodes = new Map;
3187
+ this.loadedMeshes = new Map;
3188
+ this.nodesToLoad = [];
3189
+ this.edgeNodes = [];
3190
+ this.structures = [];
3191
+ this.structureRoots = new Map;
3192
+ this.memoryLimit = this.getAvailableMemory();
3193
+ this.loadedGeometrySize = 0;
3194
+ this.geometryCache = new Map;
3195
+ this.materialCache = new Map;
3196
+ this.textureCache = new Map;
3197
+ this.currentMemoryUsage = 0;
3198
+ this.updateMemoryIndicator();
3199
+ this.loadedMaterials = new Map;
3200
+ this.abortController = new AbortController;
3201
+ this.batchSize = 1e4;
3202
+ this.frameDelay = 0;
3203
+ this.graphicsObjectLimit = 1e4;
3204
+ this.totalLoadedObjects = 0;
3205
+ this.lastUpdateTime = 0;
3206
+ this.updateInterval = 1e3;
3207
+ this.handleToObjects = new Map;
3208
+ this.originalObjects = new Set;
3209
+ this.originalObjectsToSelection = new Set;
3210
+ this.optimizedOriginalMap = new Map;
3211
+ this.mergedMesh = new Set;
3212
+ this.mergedLines = new Set;
3213
+ this.mergedLineSegments = new Set;
3214
+ this.mergedPoints = new Set;
3215
+ this.isolatedObjects = [];
3216
+ this.useVAO = !!window.WebGL2RenderingContext && this.renderer.getContext() instanceof WebGL2RenderingContext;
3217
+ this.handleToOptimizedObjects = new Map;
3218
+ this.hiddenHandles = new Set;
3219
+ this.newOptimizedObjects = new Set;
3220
+ this.oldOptimizeObjects = new Set;
3221
+ }
3222
+ getAvailableMemory() {
3223
+ let memoryLimit = 6 * 1024 * 1024 * 1024;
3224
+ try {
3225
+ if (navigator.deviceMemory) {
3226
+ memoryLimit = navigator.deviceMemory * 1024 * 1024 * 1024;
3227
+ } else if (performance.memory) {
3228
+ const jsHeapSizeLimit = performance.memory.jsHeapSizeLimit;
3229
+ if (jsHeapSizeLimit) {
3230
+ memoryLimit = Math.min(memoryLimit, jsHeapSizeLimit);
3231
+ }
3232
+ }
3233
+ memoryLimit = Math.min(memoryLimit, 16 * 1024 * 1024 * 1024);
3234
+ memoryLimit = Math.max(memoryLimit, 2 * 1024 * 1024 * 1024);
3235
+ console.log(`Available memory set to ${Math.round(memoryLimit / (1024 * 1024 * 1024))}GB`);
3236
+ } catch (error) {
3237
+ console.warn("Error detecting available memory:", error);
3238
+ }
3239
+ return memoryLimit;
3240
+ }
3241
+ getAbortController() {
3242
+ return this.abortController;
3243
+ }
3244
+ abortLoading() {
3245
+ this.abortController.abort();
3246
+ }
3247
+ updateMemoryIndicator() {
3248
+ this.dispatchEvent("geometrymemory", {
3249
+ currentUsage: this.currentMemoryUsage,
3250
+ limit: this.memoryLimit
3251
+ });
3252
+ }
3253
+ setMemoryLimit(bytesLimit) {}
3254
+ estimateGeometrySize(nodeGroup) {
3255
+ let totalSize = 0;
3256
+ nodeGroup.traverse((child => {
3257
+ if (child.geometry) {
3258
+ if (this.abortController.signal.aborted) {
3259
+ throw new DOMException("Loading aborted", "AbortError");
3260
+ }
3261
+ const geometry = child.geometry;
3262
+ if (geometry.attributes) {
3263
+ Object.values(geometry.attributes).forEach((attribute => {
3264
+ if (attribute && attribute.array) {
3265
+ totalSize += attribute.array.byteLength;
3266
+ }
3267
+ }));
3268
+ }
3269
+ if (geometry.index && geometry.index.array) {
3270
+ totalSize += geometry.index.array.byteLength;
3271
+ }
3272
+ }
3273
+ }));
3274
+ return totalSize;
3275
+ }
3276
+ recalculateScene() {
3277
+ const geometries = [];
3278
+ this.scene.traverse((object => {
3279
+ if (this.abortController.signal.aborted) {
3280
+ throw new DOMException("Loading aborted", "AbortError");
3281
+ }
3282
+ if (object.geometry && !this.geometryCache.has(object.geometry.uuid)) {
3283
+ const size = this.estimateGeometrySize(object);
3284
+ this.geometryCache.set(object.geometry.uuid, size);
3285
+ geometries.push({
3286
+ object: object,
3287
+ size: size,
3288
+ distance: object.position.distanceTo(this.camera.position)
3289
+ });
3290
+ }
3291
+ }));
3292
+ if (this.abortController.signal.aborted) {
3293
+ throw new DOMException("Loading aborted", "AbortError");
3294
+ }
3295
+ geometries.sort(((a, b) => b.distance - a.distance));
3296
+ let currentMemoryUsage = 0;
3297
+ for (const geo of geometries) {
3298
+ currentMemoryUsage += geo.size;
3299
+ }
3300
+ if (currentMemoryUsage > this.memoryLimit) {
3301
+ console.log(`Memory usage (${Math.round(currentMemoryUsage / (1024 * 1024))}MB) exceeds limit`);
3302
+ for (const geo of geometries) {
3303
+ if (currentMemoryUsage <= this.memoryLimit) break;
3304
+ if (this.abortController.signal.aborted) {
3305
+ throw new DOMException("Loading aborted", "AbortError");
3306
+ }
3307
+ const object = geo.object;
3308
+ if (object.geometry) {
3309
+ currentMemoryUsage -= geo.size;
3310
+ this.geometryCache.delete(object.geometry.uuid);
3311
+ object.geometry.dispose();
3312
+ object.visible = false;
3313
+ }
3314
+ }
3315
+ }
3316
+ this.currentMemoryUsage = currentMemoryUsage;
3317
+ this.updateMemoryIndicator();
3318
+ console.log(`Final memory usage: ${Math.round(currentMemoryUsage / (1024 * 1024))}MB`);
3319
+ }
3320
+ async loadNode(nodeId) {
3321
+ const node = this.nodes.get(nodeId);
3322
+ if (!node || node.loaded || node.loading) return;
3323
+ node.loading = true;
3324
+ const meshDef = node.structure.getJson().meshes[node.meshIndex];
3325
+ try {
3326
+ for (const primitive of meshDef.primitives) {
3327
+ const positionAccessor = primitive.attributes.POSITION;
3328
+ const geometry = new BufferGeometry;
3329
+ const attributes = new Map;
3330
+ attributes.set("position", node.structure.createBufferAttribute(positionAccessor));
3331
+ if (primitive.attributes.NORMAL !== undefined) {
3332
+ attributes.set("normal", node.structure.createBufferAttribute(primitive.attributes.NORMAL));
3333
+ }
3334
+ if (primitive.attributes.TEXCOORD_0 !== undefined) {
3335
+ attributes.set("uv", node.structure.createBufferAttribute(primitive.attributes.TEXCOORD_0));
3336
+ }
3337
+ const loadedAttributes = await Promise.all([ ...attributes.entries() ].map((async ([name, promise]) => {
3338
+ const attribute = await promise;
3339
+ return [ name, attribute ];
3340
+ })));
3341
+ loadedAttributes.forEach((([name, attribute]) => {
3342
+ geometry.setAttribute(name, attribute);
3343
+ }));
3344
+ if (primitive.indices !== undefined) {
3345
+ const indexAttribute = await node.structure.createBufferAttribute(primitive.indices);
3346
+ geometry.setIndex(indexAttribute);
3347
+ }
3348
+ this.currentPrimitiveMode = primitive.mode;
3349
+ let material;
3350
+ if (primitive.material !== undefined) {
3351
+ material = node.structure.materials.get(primitive.material) || this.createDefaultMaterial();
3352
+ } else {
3353
+ material = this.createDefaultMaterial();
3354
+ }
3355
+ let mesh;
3356
+ if (primitive.mode === GL_CONSTANTS.POINTS) {
3357
+ const pointsMaterial = new PointsMaterial;
3358
+ Material.prototype.copy.call(pointsMaterial, material);
3359
+ pointsMaterial.color.copy(material.color);
3360
+ pointsMaterial.map = material.map;
3361
+ pointsMaterial.sizeAttenuation = false;
3362
+ mesh = new Points(geometry, pointsMaterial);
3363
+ } else if (primitive.mode === GL_CONSTANTS.TRIANGLES || primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP || primitive.mode === GL_CONSTANTS.TRIANGLE_FAN || primitive.mode === undefined) {
3364
+ mesh = new Mesh(geometry, material);
3365
+ if (primitive.mode === GL_CONSTANTS.TRIANGLE_STRIP) {
3366
+ mesh.drawMode = TriangleStripDrawMode;
3367
+ } else if (primitive.mode === GL_CONSTANTS.TRIANGLE_FAN) {
3368
+ mesh.drawMode = TriangleFanDrawMode;
3369
+ }
3370
+ } else if (primitive.mode === GL_CONSTANTS.LINES) {
3371
+ mesh = new LineSegments(geometry, material);
3372
+ } else if (primitive.mode === GL_CONSTANTS.LINE_STRIP) {
3373
+ mesh = new Line(geometry, material);
3374
+ } else if (primitive.mode === GL_CONSTANTS.LINE_LOOP) {
3375
+ mesh = new LineLoop(geometry, material);
3376
+ }
3377
+ if (node.extras) {
3378
+ mesh.userData = {
3379
+ ...mesh.userData,
3380
+ ...node.extras
3381
+ };
3382
+ }
3383
+ if (meshDef.extras) {
3384
+ mesh.userData = {
3385
+ ...mesh.userData,
3386
+ ...meshDef.extras
3387
+ };
3388
+ }
3389
+ if (primitive.extras) {
3390
+ mesh.userData = {
3391
+ ...mesh.userData,
3392
+ ...primitive.extras
3393
+ };
3394
+ }
3395
+ if (node.handle) {
3396
+ mesh.userData.handle = node.handle;
3397
+ } else {
3398
+ mesh.userData.handle = `${node.structure.id}_${mesh.userData.handle}`;
3399
+ }
3400
+ if (mesh.material.name === "edges") {
3401
+ mesh.userData.isEdge = true;
3402
+ } else {
3403
+ mesh.userData.isEdge = false;
3404
+ }
3405
+ this.registerObjectWithHandle(mesh, mesh.userData.handle);
3406
+ mesh.position.copy(node.position);
3407
+ if (!geometry.attributes.normal) {
3408
+ geometry.computeVertexNormals();
3409
+ }
3410
+ if (material.aoMap && geometry.attributes.uv) {
3411
+ geometry.setAttribute("uv2", geometry.attributes.uv);
3412
+ }
3413
+ if (node.group) {
3414
+ node.group.add(mesh);
3415
+ } else {
3416
+ this.scene.add(mesh);
3417
+ }
3418
+ node.object = mesh;
3419
+ this.totalLoadedObjects++;
3420
+ mesh.visible = this.totalLoadedObjects < this.graphicsObjectLimit;
3421
+ }
3422
+ node.loaded = true;
3423
+ node.loading = false;
3424
+ const geometrySize = this.estimateGeometrySize(node.object);
3425
+ this.geometryCache.set(node.object.uuid, geometrySize);
3426
+ this.currentMemoryUsage += geometrySize;
3427
+ } catch (error) {
3428
+ if (error.name !== "AbortError") {
3429
+ console.error(`Error loading node ${nodeId}:`, error);
3430
+ }
3431
+ node.loading = false;
3432
+ }
3433
+ }
3434
+ unloadNode(nodeId) {
3435
+ const node = this.nodes.get(nodeId);
3436
+ if (!node || !node.loaded) return;
3437
+ if (node.object) {
3438
+ if (node.object.parent) {
3439
+ node.object.parent.remove(node.object);
3440
+ } else {
3441
+ this.scene.remove(node.object);
3442
+ }
3443
+ node.object.traverse((child => {
3444
+ if (child.geometry) {
3445
+ const geometrySize = this.geometryCache.get(child.geometry.uuid) || 0;
3446
+ this.currentMemoryUsage -= geometrySize;
3447
+ this.geometryCache.delete(child.geometry.uuid);
3448
+ child.geometry.dispose();
3449
+ }
3450
+ }));
3451
+ node.object = null;
3452
+ node.loaded = false;
3453
+ this.updateMemoryIndicator();
3454
+ console.log(`Unloaded node: ${nodeId}`);
3455
+ }
3456
+ }
3457
+ checkDistances() {
3458
+ const cameraPosition = this.camera.position;
3459
+ this.nodes.forEach(((node, nodeId) => {
3460
+ const distance = cameraPosition.distanceTo(node.position);
3461
+ if (node.loaded) {
3462
+ if (distance > this.unloadDistance) {
3463
+ this.unloadNode(nodeId);
3464
+ }
3465
+ } else if (!node.loading) {
3466
+ if (distance < this.loadDistance) {
3467
+ this.loadNode(nodeId);
3468
+ }
3469
+ }
3470
+ }));
3471
+ }
3472
+ async loadStructure(structures) {
3473
+ this.clear();
3474
+ const structureArray = Array.isArray(structures) ? structures : [ structures ];
3475
+ for (const structure of structureArray) {
3476
+ this.structures.push(structure);
3477
+ }
3478
+ for (const structure of this.structures) {
3479
+ try {
3480
+ await structure.loadTextures();
3481
+ await structure.loadMaterials();
3482
+ } catch (error) {
3483
+ console.error("Error loading materials:", error);
3484
+ throw error;
3485
+ }
3486
+ }
3487
+ await this.processSceneHierarchy();
3488
+ }
3489
+ async processSceneHierarchy() {
3490
+ if (this.structures.length === 0) {
3491
+ throw new Error("No GLTF structures loaded");
3492
+ }
3493
+ this.nodesToLoad = [];
3494
+ let estimatedSize = 0;
3495
+ for (const structure of this.structures) {
3496
+ const gltf = structure.getJson();
3497
+ if (!gltf.scenes || !gltf.scenes.length) {
3498
+ console.warn("No scenes found in GLTF structure");
3499
+ continue;
3500
+ }
3501
+ estimatedSize += gltf.buffers[0].byteLength;
3502
+ const rootGroup = new Group;
3503
+ rootGroup.name = `structure_${structure.id}_root`;
3504
+ this.scene.add(rootGroup);
3505
+ this.structureRoots.set(structure.id, rootGroup);
3506
+ const scene = gltf.scenes[gltf.scene || 0];
3507
+ for (const nodeIndex of scene.nodes) {
3508
+ await this.processNodeHierarchy(structure, nodeIndex, rootGroup);
3509
+ }
3510
+ }
3511
+ const ignoreEdges = estimatedSize * 2 > this.memoryLimit;
3512
+ this.nodesToLoad.sort(((a, b) => {
3513
+ const nodeA = this.nodes.get(a);
3514
+ const nodeB = this.nodes.get(b);
3515
+ if (!nodeA?.geometryExtents || !nodeB?.geometryExtents) {
3516
+ return 0;
3517
+ }
3518
+ const sizeA = nodeA.geometryExtents.getSize(new Vector3);
3519
+ const sizeB = nodeB.geometryExtents.getSize(new Vector3);
3520
+ const volumeA = sizeA.x * sizeA.y * sizeA.z;
3521
+ const volumeB = sizeB.x * sizeB.y * sizeB.z;
3522
+ return volumeB - volumeA;
3523
+ }));
3524
+ if (!ignoreEdges) {
3525
+ this.nodesToLoad.push(...this.edgeNodes);
3526
+ }
3527
+ this.dispatchEvent("databasechunk", {
3528
+ totalNodes: this.nodesToLoad.length,
3529
+ structures: this.structures.map((s => ({
3530
+ id: s.id,
3531
+ nodeCount: this.nodesToLoad.filter((nodeId => nodeId.startsWith(s.id))).length
3532
+ })))
3533
+ });
3534
+ }
3535
+ async processNodeHierarchy(structure, nodeId, parentGroup) {
3536
+ const nodeDef = structure.json.nodes[nodeId];
3537
+ let nodeGroup = null;
3538
+ let handle = null;
3539
+ if (nodeDef.extras?.handle) {
3540
+ handle = `${structure.id}_${nodeDef.extras.handle}`;
3541
+ }
3542
+ if (nodeDef.camera !== undefined) {
3543
+ const camera = this.loadCamera(structure, nodeDef.camera, nodeDef);
3544
+ if (nodeDef.extras) {
3545
+ camera.userData = {
3546
+ ...camera.userData,
3547
+ ...nodeDef.extras
3548
+ };
3549
+ }
3550
+ this.scene.add(camera);
3551
+ return;
3552
+ }
3553
+ const needsGroup = this.needsGroupForNode(structure, nodeDef);
3554
+ if (needsGroup) {
3555
+ nodeGroup = new Group;
3556
+ nodeGroup.name = nodeDef.name || `node_${nodeId}`;
3557
+ if (nodeDef.extras) {
3558
+ nodeGroup.userData = {
3559
+ ...nodeDef.extras
3560
+ };
3561
+ if (nodeGroup.userData.handle) {
3562
+ nodeGroup.userData.handle = `${structure.id}_${nodeGroup.userData.handle}`;
3563
+ }
3564
+ }
3565
+ if (nodeDef.matrix) {
3566
+ nodeGroup.matrix.fromArray(nodeDef.matrix);
3567
+ nodeGroup.matrixAutoUpdate = false;
3568
+ } else if (nodeDef.translation || nodeDef.rotation || nodeDef.scale) {
3569
+ const position = nodeDef.translation ? (new Vector3).fromArray(nodeDef.translation) : new Vector3;
3570
+ const quaternion = nodeDef.rotation ? (new Quaternion).fromArray(nodeDef.rotation) : new Quaternion;
3571
+ const scale = nodeDef.scale ? (new Vector3).fromArray(nodeDef.scale) : new Vector3(1, 1, 1);
3572
+ nodeGroup.matrix.compose(position, quaternion, scale);
3573
+ nodeGroup.matrixAutoUpdate = false;
3574
+ }
3575
+ if (parentGroup) {
3576
+ parentGroup.add(nodeGroup);
3577
+ }
3578
+ }
3579
+ if (nodeDef.mesh !== undefined) {
3580
+ const nodeMatrix = new Matrix4;
3581
+ const uniqueNodeId = `${structure.id}_${nodeId}`;
3582
+ const meshDef = structure.json.meshes[nodeDef.mesh];
3583
+ const geometryExtents = new Box3;
3584
+ for (const primitive of meshDef.primitives) {
3585
+ const positionAccessor = structure.json.accessors[primitive.attributes.POSITION];
3586
+ if (positionAccessor && positionAccessor.min && positionAccessor.max) {
3587
+ const primitiveBox = new Box3((new Vector3).fromArray(positionAccessor.min), (new Vector3).fromArray(positionAccessor.max));
3588
+ geometryExtents.union(primitiveBox);
3589
+ }
3590
+ }
3591
+ let isEdge = false;
3592
+ if (meshDef.primitives[0].material !== undefined) {
3593
+ const material = structure.json.materials[meshDef.primitives[0].material];
3594
+ if (material?.name === "edges") {
3595
+ isEdge = true;
3596
+ }
3597
+ }
3598
+ if (!isEdge) {
3599
+ this.nodesToLoad.push(uniqueNodeId);
3600
+ } else {
3601
+ this.edgeNodes.push(uniqueNodeId);
3602
+ }
3603
+ this.nodes.set(uniqueNodeId, {
3604
+ position: nodeGroup ? nodeGroup.position.clone() : (new Vector3).setFromMatrixPosition(nodeMatrix),
3605
+ nodeIndex: nodeId,
3606
+ meshIndex: nodeDef.mesh,
3607
+ loaded: false,
3608
+ loading: false,
3609
+ object: null,
3610
+ group: nodeGroup || parentGroup,
3611
+ structure: structure,
3612
+ extras: nodeDef.extras,
3613
+ geometryExtents: geometryExtents,
3614
+ handle: handle
3615
+ });
3616
+ }
3617
+ if (nodeDef.children) {
3618
+ for (const childId of nodeDef.children) {
3619
+ await this.processNodeHierarchy(structure, childId, nodeGroup || parentGroup);
3620
+ }
3621
+ }
3622
+ return nodeGroup;
3623
+ }
3624
+ needsGroupForNode(structure, nodeDef) {
3625
+ const hasTransforms = nodeDef.matrix || nodeDef.translation || nodeDef.rotation || nodeDef.scale;
3626
+ const hasMultiplePrimitives = nodeDef.mesh !== undefined && structure.json.meshes[nodeDef.mesh].primitives.length > 1;
3627
+ return hasTransforms !== undefined || hasMultiplePrimitives;
3628
+ }
3629
+ async processNodes() {
3630
+ const nodesToLoad = this.nodesToLoad;
3631
+ let loadedCount = 0;
3632
+ const totalNodes = nodesToLoad.length;
3633
+ try {
3634
+ while (loadedCount < totalNodes) {
3635
+ const batch = nodesToLoad.slice(loadedCount, loadedCount + this.batchSize);
3636
+ const batchPromises = [];
3637
+ for (const nodeId of batch) {
3638
+ if (this.abortController.signal.aborted) {
3639
+ throw new DOMException("Loading aborted", "AbortError");
3640
+ }
3641
+ const estimatedSize = await this.estimateNodeSize(nodeId);
3642
+ if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
3643
+ console.log(`Memory limit reached after loading ${loadedCount} nodes`);
3644
+ this.dispatchEvent("geometryerror", {
3645
+ message: "Memory limit reached"
3646
+ });
3647
+ this.dispatchEvent("update");
3648
+ return loadedCount;
3649
+ }
3650
+ batchPromises.push(this.loadNode(nodeId));
3651
+ }
3652
+ await Promise.all(batchPromises);
3653
+ loadedCount += batch.length;
3654
+ this.updateMemoryIndicator();
3655
+ this.dispatchEvent("geometryprogress", {
3656
+ percentage: Math.round(loadedCount / totalNodes * 100),
3657
+ loaded: loadedCount,
3658
+ total: totalNodes
3659
+ });
3660
+ const currentTime = Date.now();
3661
+ if (currentTime - this.lastUpdateTime >= this.updateInterval) {
3662
+ this.dispatchEvent("update");
3663
+ this.lastUpdateTime = currentTime;
3664
+ }
3665
+ await new Promise((resolve => {
3666
+ setTimeout(resolve, 0);
3667
+ }));
3668
+ }
3669
+ this.dispatchEvent("geometryend", {
3670
+ totalLoaded: loadedCount,
3671
+ totalNodes: totalNodes
3672
+ });
3673
+ return loadedCount;
3674
+ } catch (error) {
3675
+ this.dispatchEvent("geometryerror", {
3676
+ error: error
3677
+ });
3678
+ throw error;
3679
+ }
3680
+ }
3681
+ async loadNodes() {
3682
+ console.time("process nodes");
3683
+ await this.processNodes();
3684
+ console.timeEnd("process nodes");
3685
+ console.time("optimize scene");
3686
+ await this.optimizeScene();
3687
+ console.timeEnd("optimize scene");
3688
+ }
3689
+ cleanupPartialLoad() {
3690
+ this.nodesToLoad.forEach((nodeId => {
3691
+ const node = this.nodes.get(nodeId);
3692
+ if (node && node.loading) {
3693
+ this.unloadNode(nodeId);
3694
+ }
3695
+ }));
3696
+ }
3697
+ createDefaultMaterial() {
3698
+ if (this.currentPrimitiveMode === GL_CONSTANTS.POINTS) {
3699
+ return new PointsMaterial({
3700
+ color: new Color(8421504),
3701
+ size: .05,
3702
+ sizeAttenuation: true,
3703
+ alphaTest: .5,
3704
+ transparent: true,
3705
+ vertexColors: false,
3706
+ blending: NormalBlending,
3707
+ depthWrite: false,
3708
+ depthTest: true
3709
+ });
3710
+ } else {
3711
+ return new MeshStandardMaterial({
3712
+ color: 8421504,
3713
+ metalness: 0,
3714
+ roughness: 1,
3715
+ side: DoubleSide
3716
+ });
3717
+ }
3718
+ }
3719
+ async estimateNodeSize(nodeId) {
3720
+ const node = this.nodes.get(nodeId);
3721
+ if (!node) return 0;
3722
+ return await node.structure.estimateNodeSize(node.meshIndex);
3723
+ }
3724
+ getTotalGeometryExtent() {
3725
+ const totalExtent = new Box3;
3726
+ for (const node of this.nodes.values()) {
3727
+ if (!node.geometryExtents) continue;
3728
+ if (node.object && this.hiddenHandles.has(node.object.userData.handle)) continue;
3729
+ const transformedBox = node.geometryExtents.clone();
3730
+ if (node.group && node.group.matrix) {
3731
+ transformedBox.applyMatrix4(node.group.matrix);
3732
+ if (node.group.parent && node.group.parent.matrix) {
3733
+ transformedBox.applyMatrix4(node.group.parent.matrix);
3734
+ }
3735
+ }
3736
+ totalExtent.union(transformedBox);
3737
+ }
3738
+ return totalExtent;
3739
+ }
3740
+ loadCamera(structure, cameraIndex, nodeDef) {
3741
+ const cameraDef = structure.getJson().cameras[cameraIndex];
3742
+ const params = cameraDef[cameraDef.type];
3743
+ let camera;
3744
+ if (cameraDef.type === "perspective") {
3745
+ camera = new PerspectiveCamera(MathUtils.radToDeg(params.yfov), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6);
3746
+ } else if (cameraDef.type === "orthographic") {
3747
+ camera = new OrthographicCamera(params.xmag / -2, params.xmag / 2, params.ymag / 2, params.ymag / -2, params.znear, params.zfar);
3748
+ }
3749
+ if (nodeDef.matrix) {
3750
+ camera.matrix.fromArray(nodeDef.matrix);
3751
+ camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
3752
+ } else {
3753
+ if (nodeDef.translation) {
3754
+ camera.position.fromArray(nodeDef.translation);
3755
+ }
3756
+ if (nodeDef.rotation) {
3757
+ camera.quaternion.fromArray(nodeDef.rotation);
3758
+ }
3759
+ if (nodeDef.scale) {
3760
+ camera.scale.fromArray(nodeDef.scale);
3761
+ }
3762
+ }
3763
+ return camera;
3764
+ }
3765
+ clearNodesToLoad() {
3766
+ this.nodesToLoad = [];
3767
+ }
3768
+ async addStructure(loadController) {
3769
+ const structure = new GltfStructure;
3770
+ await structure.initialize(loadController);
3771
+ this.structures.push(structure);
3772
+ return structure;
3773
+ }
3774
+ removeOptimization() {
3775
+ this.originalObjects.forEach((obj => obj.visible = true));
3776
+ const disposeMerged = obj => {
3777
+ if (obj.parent) {
3778
+ obj.parent.remove(obj);
3779
+ }
3780
+ if (obj.geometry) {
3781
+ obj.geometry.dispose();
3782
+ }
3783
+ };
3784
+ if (this.structureGroups) {
3785
+ for (const group of this.structureGroups.values()) {
3786
+ group.meshes.forEach(disposeMerged);
3787
+ group.lines.forEach(disposeMerged);
3788
+ group.lineSegments.forEach(disposeMerged);
3789
+ group.meshes.clear();
3790
+ group.lines.clear();
3791
+ group.lineSegments.clear();
3792
+ }
3793
+ }
3794
+ this.optimizedOriginalMap.clear();
3795
+ this.mergedMesh.clear();
3796
+ this.mergedLines.clear();
3797
+ this.mergedLineSegments.clear();
3798
+ this.originalObjects.clear();
3799
+ this.originalObjectsToSelection.clear();
3800
+ }
3801
+ clear() {
3802
+ this.structures.forEach((structure => {
3803
+ if (structure) {
3804
+ structure.clear();
3805
+ }
3806
+ }));
3807
+ this.structures = [];
3808
+ this.nodes.forEach((node => {
3809
+ if (node.object) {
3810
+ if (node.object.parent) {
3811
+ node.object.parent.remove(node.object);
3812
+ }
3813
+ if (node.object.geometry) {
3814
+ node.object.geometry.dispose();
3815
+ }
3816
+ if (node.object.material) {
3817
+ if (Array.isArray(node.object.material)) {
3818
+ node.object.material.forEach((material => material.dispose()));
3819
+ } else {
3820
+ node.object.material.dispose();
3821
+ }
3822
+ }
3823
+ }
3824
+ }));
3825
+ this.nodes.clear();
3826
+ this.loadedMeshes.forEach((mesh => {
3827
+ if (mesh.geometry) mesh.geometry.dispose();
3828
+ if (mesh.material) {
3829
+ if (Array.isArray(mesh.material)) {
3830
+ mesh.material.forEach((material => material.dispose()));
3831
+ } else {
3832
+ mesh.material.dispose();
3833
+ }
3834
+ }
3835
+ }));
3836
+ this.loadedMeshes.clear();
3837
+ this.structureRoots.forEach((rootGroup => {
3838
+ if (rootGroup) {
3839
+ rootGroup.traverse((child => {
3840
+ if (child.geometry) child.geometry.dispose();
3841
+ if (child.material) {
3842
+ if (Array.isArray(child.material)) {
3843
+ child.material.forEach((material => material.dispose()));
3844
+ } else {
3845
+ child.material.dispose();
3846
+ }
3847
+ }
3848
+ }));
3849
+ if (rootGroup.parent) {
3850
+ rootGroup.parent.remove(rootGroup);
3851
+ }
3852
+ }
3853
+ }));
3854
+ this.structureRoots.clear();
3855
+ this.mergedMesh.forEach((mesh => {
3856
+ if (mesh.geometry) mesh.geometry.dispose();
3857
+ if (mesh.material) {
3858
+ if (Array.isArray(mesh.material)) {
3859
+ mesh.material.forEach((material => material.dispose()));
3860
+ } else {
3861
+ mesh.material.dispose();
3862
+ }
3863
+ }
3864
+ if (mesh.parent) mesh.parent.remove(mesh);
3865
+ }));
3866
+ this.mergedMesh.clear();
3867
+ this.mergedLines.forEach((line => {
3868
+ if (line.geometry) line.geometry.dispose();
3869
+ if (line.material) line.material.dispose();
3870
+ if (line.parent) line.parent.remove(line);
3871
+ }));
3872
+ this.mergedLines.clear();
3873
+ this.mergedLineSegments.forEach((lineSegment => {
3874
+ if (lineSegment.geometry) lineSegment.geometry.dispose();
3875
+ if (lineSegment.material) lineSegment.material.dispose();
3876
+ if (lineSegment.parent) lineSegment.parent.remove(lineSegment);
3877
+ }));
3878
+ this.mergedLineSegments.clear();
3879
+ this.mergedPoints.forEach((points => {
3880
+ if (points.geometry) points.geometry.dispose();
3881
+ if (points.material) points.material.dispose();
3882
+ if (points.parent) points.parent.remove(points);
3883
+ }));
3884
+ this.mergedPoints.clear();
3885
+ this.geometryCache.clear();
3886
+ this.materialCache.clear();
3887
+ this.textureCache.clear();
3888
+ this.loadedMaterials.clear();
3889
+ this.nodesToLoad = [];
3890
+ this.handleToObjects.clear();
3891
+ this.originalObjects.clear();
3892
+ this.originalObjectsToSelection.clear();
3893
+ this.optimizedOriginalMap.clear();
3894
+ this.handleToOptimizedObjects.clear();
3895
+ this.hiddenHandles.clear();
3896
+ this.newOptimizedObjects.clear();
3897
+ this.oldOptimizeObjects.clear();
3898
+ this.isolatedObjects = [];
3899
+ this.totalLoadedObjects = 0;
3900
+ this.lastUpdateTime = 0;
3901
+ this.currentMemoryUsage = 0;
3902
+ this.loadedGeometrySize = 0;
3903
+ this.abortController = new AbortController;
3904
+ this.updateMemoryIndicator();
3905
+ }
3906
+ setStructureTransform(structureId, matrix) {
3907
+ const rootGroup = this.structureRoots.get(structureId);
3908
+ if (rootGroup) {
3909
+ rootGroup.matrix.copy(matrix);
3910
+ rootGroup.matrix.decompose(rootGroup.position, rootGroup.quaternion, rootGroup.scale);
3911
+ return true;
3912
+ }
3913
+ return false;
3914
+ }
3915
+ getStructureRootGroup(structureId) {
3916
+ return this.structureRoots.get(structureId);
3917
+ }
3918
+ addEventListener(event, handler) {
3919
+ if (this.eventHandlers[event]) {
3920
+ this.eventHandlers[event].push(handler);
3921
+ }
3922
+ }
3923
+ removeEventListener(event, handler) {
3924
+ if (this.eventHandlers[event]) {
3925
+ this.eventHandlers[event] = this.eventHandlers[event].filter((h => h !== handler));
3926
+ }
3927
+ }
3928
+ dispatchEvent(event, data) {
3929
+ if (this.eventHandlers[event]) {
3930
+ this.eventHandlers[event].forEach((handler => handler(data)));
3931
+ }
3932
+ }
3933
+ registerObjectWithHandle(object, handle) {
3934
+ if (!handle) return;
3935
+ const fullHandle = object.userData.handle;
3936
+ if (!this.handleToObjects.has(fullHandle)) {
3937
+ this.handleToObjects.set(fullHandle, new Set);
3938
+ }
3939
+ this.handleToObjects.get(fullHandle).add(object);
3940
+ object.userData.structureId = object.userData.handle.split("_")[0];
3941
+ }
3942
+ getObjectsByHandle(handle) {
3943
+ if (!handle) return [];
3944
+ return Array.from(this.handleToObjects.get(handle) || []);
3945
+ }
3946
+ getHandlesByObjects(objects) {
3947
+ if (!objects.length) return [];
3948
+ const handles = new Set;
3949
+ objects.forEach((obj => {
3950
+ if (this.originalObjects.has(obj)) handles.add(obj.userData.handle);
3951
+ }));
3952
+ return Array.from(handles);
3953
+ }
3954
+ getMaterialId(material, index) {
3955
+ const props = {
3956
+ type: material.type,
3957
+ color: material.color?.getHex(),
3958
+ map: material.map?.uuid,
3959
+ transparent: material.transparent,
3960
+ opacity: material.opacity,
3961
+ side: material.side,
3962
+ index: index ? 1 : 0
3963
+ };
3964
+ return JSON.stringify(props);
3965
+ }
3966
+ addToMaterialGroup(object, groupsMap, optimizeGroupList) {
3967
+ const VERTEX_LIMIT = 1e5;
3968
+ const INDEX_LIMIT = 1e5;
3969
+ const objectGeometryVertexCount = object.geometry.attributes.position.count;
3970
+ const objectGeometryIndexCount = object.geometry.index ? object.geometry.index.count : 0;
3971
+ const material = object.material;
3972
+ let materialId = this.getMaterialId(material, object.geometry.index !== null);
3973
+ let group;
3974
+ if (!groupsMap.has(materialId)) {
3975
+ group = {
3976
+ material: material,
3977
+ objects: [ object ],
3978
+ totalVertices: objectGeometryVertexCount,
3979
+ totalIndices: objectGeometryIndexCount
3980
+ };
3981
+ groupsMap.set(materialId, group);
3982
+ optimizeGroupList.push(group);
3983
+ } else {
3984
+ group = groupsMap.get(materialId);
3985
+ if (group.totalVertices + objectGeometryVertexCount > VERTEX_LIMIT || group.totalIndices + objectGeometryIndexCount > INDEX_LIMIT) {
3986
+ const newGroup = {
3987
+ material: material,
3988
+ objects: [ object ],
3989
+ totalVertices: objectGeometryVertexCount,
3990
+ totalIndices: objectGeometryIndexCount
3991
+ };
3992
+ materialId = this.getMaterialId(material, object.geometry.index !== null);
3993
+ groupsMap.set(materialId, newGroup);
3994
+ optimizeGroupList.push(newGroup);
3995
+ } else {
3996
+ group.objects.push(object);
3997
+ group.totalVertices += objectGeometryVertexCount;
3998
+ group.totalIndices += objectGeometryIndexCount;
3999
+ }
4000
+ }
4001
+ this.originalObjects.add(object);
4002
+ }
4003
+ optimizeScene() {
4004
+ this.originalObjects.clear();
4005
+ this.originalObjectsToSelection.clear();
4006
+ const structureGroups = new Map;
4007
+ this.scene.traverse((object => {
4008
+ if (object.userData.structureId) {
4009
+ const structureId = object.userData.structureId;
4010
+ if (!structureGroups.has(structureId)) {
4011
+ structureGroups.set(structureId, {
4012
+ mapMeshes: new Map,
4013
+ mapLines: new Map,
4014
+ mapLineSegments: new Map,
4015
+ mapPoints: new Map,
4016
+ meshes: [],
4017
+ lines: [],
4018
+ lineSegments: [],
4019
+ points: [],
4020
+ rootGroup: this.structureRoots.get(structureId)
4021
+ });
4022
+ }
4023
+ const group = structureGroups.get(structureId);
4024
+ if (object instanceof Mesh) {
4025
+ this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
4026
+ } else if (object instanceof LineSegments) {
4027
+ this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
4028
+ } else if (object instanceof Line) {
4029
+ this.addToMaterialGroup(object, group.mapLines, group.lines);
4030
+ } else if (object instanceof Points) {
4031
+ this.addToMaterialGroup(object, group.mapPoints, group.points);
4032
+ }
4033
+ }
4034
+ }));
4035
+ for (const group of structureGroups.values()) {
4036
+ group.mapMeshes.clear();
4037
+ group.mapLines.clear();
4038
+ group.mapLineSegments.clear();
4039
+ group.mapPoints.clear();
4040
+ this.mergeMeshGroups(group.meshes, group.rootGroup);
4041
+ this.mergeLineGroups(group.lines, group.rootGroup);
4042
+ this.mergeLineSegmentGroups(group.lineSegments, group.rootGroup);
4043
+ this.mergePointsGroups(group.points, group.rootGroup);
4044
+ }
4045
+ this.originalObjects.forEach((obj => {
4046
+ obj.visible = false;
4047
+ if (!(obj instanceof Points) && !obj.userData.isEdge) {
4048
+ this.originalObjectsToSelection.add(obj);
4049
+ }
4050
+ }));
4051
+ this.dispatchEvent("update");
4052
+ }
4053
+ mergeMeshGroups(materialGroups, rootGroup) {
4054
+ for (const group of materialGroups) {
4055
+ try {
4056
+ const geometries = [];
4057
+ const handles = new Set;
4058
+ const optimizedObjects = [];
4059
+ for (const mesh of group.objects) {
4060
+ const geometry = mesh.geometry.clone();
4061
+ mesh.updateWorldMatrix(true, false);
4062
+ geometry.applyMatrix4(mesh.matrixWorld);
4063
+ geometries.push(geometry);
4064
+ optimizedObjects.push(mesh);
4065
+ handles.add(mesh.userData.handle);
4066
+ }
4067
+ const mergedObjects = [];
4068
+ if (geometries.length > 0) {
4069
+ const mergedGeometry = mergeGeometries(geometries);
4070
+ if (this.useVAO) {
4071
+ this.createVAO(mergedGeometry);
4072
+ }
4073
+ const mergedMesh = new Mesh(mergedGeometry, group.material);
4074
+ rootGroup.add(mergedMesh);
4075
+ this.mergedMesh.add(mergedMesh);
4076
+ this.optimizedOriginalMap.set(mergedMesh, optimizedObjects);
4077
+ mergedObjects.push(mergedMesh);
4078
+ geometries.forEach((geometry => {
4079
+ geometry.dispose();
4080
+ }));
4081
+ }
4082
+ handles.forEach((handle => {
4083
+ if (this.handleToOptimizedObjects.has(handle)) {
4084
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
4085
+ existingObjects.push(...mergedObjects);
4086
+ this.handleToOptimizedObjects.set(handle, existingObjects);
4087
+ } else {
4088
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
4089
+ }
4090
+ }));
4091
+ } catch (error) {
4092
+ console.error("Failed to merge meshes for material:", error);
4093
+ group.objects.forEach((mesh => {
4094
+ mesh.visible = true;
4095
+ }));
4096
+ }
4097
+ }
4098
+ }
4099
+ mergeLineGroups(materialGroups, rootGroup) {
4100
+ for (const group of materialGroups) {
4101
+ if (group.objects.length === 0) continue;
4102
+ const handles = new Set;
4103
+ let totalVertices = 0;
4104
+ group.objects.map((line => {
4105
+ handles.add(line.userData.handle);
4106
+ totalVertices += line.geometry.attributes.position.count;
4107
+ }));
4108
+ const positions = new Float32Array(totalVertices * 3);
4109
+ let posOffset = 0;
4110
+ const indices = [];
4111
+ let vertexOffset = 0;
4112
+ group.objects.forEach((line => {
4113
+ const geometry = line.geometry;
4114
+ const positionAttr = geometry.attributes.position;
4115
+ const vertexCount = positionAttr.count;
4116
+ line.updateWorldMatrix(true, false);
4117
+ const matrix = line.matrixWorld;
4118
+ const vector = new Vector3;
4119
+ for (let i = 0; i < vertexCount; i++) {
4120
+ vector.fromBufferAttribute(positionAttr, i);
4121
+ vector.applyMatrix4(matrix);
4122
+ positions[posOffset++] = vector.x;
4123
+ positions[posOffset++] = vector.y;
4124
+ positions[posOffset++] = vector.z;
4125
+ }
4126
+ for (let i = 0; i < vertexCount - 1; i++) {
4127
+ indices.push(vertexOffset + i, vertexOffset + i + 1);
4128
+ }
4129
+ vertexOffset += vertexCount;
4130
+ }));
4131
+ const geometry = new BufferGeometry;
4132
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
4133
+ geometry.setIndex(indices);
4134
+ geometry.computeBoundingSphere();
4135
+ geometry.computeBoundingBox();
4136
+ const mergedLine = new LineSegments(geometry, group.material);
4137
+ const mergedObjects = [ mergedLine ];
4138
+ if (this.useVAO) {
4139
+ this.createVAO(mergedLine);
4140
+ }
4141
+ rootGroup.add(mergedLine);
4142
+ this.mergedLines.add(mergedLine);
4143
+ this.optimizedOriginalMap.set(mergedLine, group.objects);
4144
+ handles.forEach((handle => {
4145
+ if (this.handleToOptimizedObjects.has(handle)) {
4146
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
4147
+ existingObjects.push(...mergedObjects);
4148
+ this.handleToOptimizedObjects.set(handle, existingObjects);
4149
+ } else {
4150
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
4151
+ }
4152
+ }));
4153
+ }
4154
+ }
4155
+ mergeLineSegmentGroups(materialGroups, rootGroup) {
4156
+ for (const group of materialGroups) {
4157
+ try {
4158
+ const geometries = [];
4159
+ const optimizedObjects = [];
4160
+ const handles = new Set;
4161
+ for (const line of group.objects) {
4162
+ const geometry = line.geometry.clone();
4163
+ line.updateWorldMatrix(true, false);
4164
+ geometry.applyMatrix4(line.matrixWorld);
4165
+ geometries.push(geometry);
4166
+ optimizedObjects.push(line);
4167
+ handles.add(line.userData.handle);
4168
+ }
4169
+ const mergedObjects = [];
4170
+ if (geometries.length > 0) {
4171
+ const mergedGeometry = mergeGeometries(geometries, false);
4172
+ const mergedLine = new LineSegments(mergedGeometry, group.material);
4173
+ if (this.useVAO) {
4174
+ this.createVAO(mergedLine);
4175
+ }
4176
+ rootGroup.add(mergedLine);
4177
+ this.mergedLineSegments.add(mergedLine);
4178
+ this.optimizedOriginalMap.set(mergedLine, optimizedObjects);
4179
+ mergedObjects.push(mergedLine);
4180
+ geometries.forEach((geometry => {
4181
+ geometry.dispose();
4182
+ }));
4183
+ }
4184
+ handles.forEach((handle => {
4185
+ if (this.handleToOptimizedObjects.has(handle)) {
4186
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
4187
+ existingObjects.push(...mergedObjects);
4188
+ this.handleToOptimizedObjects.set(handle, existingObjects);
4189
+ } else {
4190
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
4191
+ }
4192
+ }));
4193
+ } catch (error) {
4194
+ console.warn("Failed to merge line segments for material:", error);
4195
+ group.objects.forEach((line => {
4196
+ line.visible = true;
4197
+ }));
4198
+ }
4199
+ }
4200
+ }
4201
+ mergePointsGroups(materialGroups, rootGroup) {
4202
+ for (const group of materialGroups) {
4203
+ try {
4204
+ const geometries = [];
4205
+ const optimizedObjects = [];
4206
+ const handles = new Set;
4207
+ for (const points of group.objects) {
4208
+ const geometry = points.geometry.clone();
4209
+ points.updateWorldMatrix(true, false);
4210
+ geometry.applyMatrix4(points.matrixWorld);
4211
+ geometries.push(geometry);
4212
+ optimizedObjects.push(points);
4213
+ handles.add(points.userData.handle);
4214
+ }
4215
+ const mergedObjects = [];
4216
+ if (geometries.length > 0) {
4217
+ const mergedGeometry = mergeGeometries(geometries, false);
4218
+ const mergedPoints = new Points(mergedGeometry, group.material);
4219
+ if (this.useVAO) {
4220
+ this.createVAO(mergedPoints);
4221
+ }
4222
+ rootGroup.add(mergedPoints);
4223
+ this.mergedPoints.add(mergedPoints);
4224
+ this.optimizedOriginalMap.set(mergedPoints, optimizedObjects);
4225
+ mergedObjects.push(mergedPoints);
4226
+ geometries.forEach((geometry => {
4227
+ geometry.dispose();
4228
+ }));
4229
+ }
4230
+ handles.forEach((handle => {
4231
+ if (this.handleToOptimizedObjects.has(handle)) {
4232
+ const existingObjects = this.handleToOptimizedObjects.get(handle);
4233
+ existingObjects.push(...mergedObjects);
4234
+ this.handleToOptimizedObjects.set(handle, existingObjects);
4235
+ } else {
4236
+ this.handleToOptimizedObjects.set(handle, mergedObjects);
4237
+ }
4238
+ }));
4239
+ } catch (error) {
4240
+ console.warn("Failed to merge points for material:", error);
4241
+ group.objects.forEach((points => {
4242
+ points.visible = true;
4243
+ }));
4244
+ }
4245
+ }
4246
+ }
4247
+ mergeInSingleSegment(structureId, rootGroup) {
4248
+ const lineSegmentsArray = [ ...this.mergedLineSegments, ...this.mergedLines ].filter((obj => obj.userData.structureId === structureId));
4249
+ if (lineSegmentsArray.length === 0) return;
4250
+ try {
4251
+ const geometriesWithIndex = [];
4252
+ const hasNormals = lineSegmentsArray.some((segment => segment.geometry.attributes.normal !== undefined));
4253
+ lineSegmentsArray.forEach((segment => {
4254
+ const clonedGeometry = segment.geometry.clone();
4255
+ segment.updateWorldMatrix(true, false);
4256
+ clonedGeometry.applyMatrix4(segment.matrixWorld);
4257
+ if (hasNormals && !clonedGeometry.attributes.normal) {
4258
+ clonedGeometry.computeVertexNormals();
4259
+ }
4260
+ if (!hasNormals && clonedGeometry.attributes.normal) {
4261
+ clonedGeometry.deleteAttribute("normal");
4262
+ }
4263
+ const colorArray = new Float32Array(clonedGeometry.attributes.position.count * 3);
4264
+ for (let i = 0; i < colorArray.length; i += 3) {
4265
+ colorArray[i] = segment.material.color.r;
4266
+ colorArray[i + 1] = segment.material.color.g;
4267
+ colorArray[i + 2] = segment.material.color.b;
4268
+ }
4269
+ clonedGeometry.setAttribute("color", new BufferAttribute(colorArray, 3));
4270
+ if (!clonedGeometry.index) {
4271
+ const indices = [];
4272
+ const posCount = clonedGeometry.attributes.position.count;
4273
+ for (let i = 0; i < posCount - 1; i += 2) {
4274
+ indices.push(i, i + 1);
4275
+ }
4276
+ clonedGeometry.setIndex(indices);
4277
+ }
4278
+ geometriesWithIndex.push(clonedGeometry);
4279
+ }));
4280
+ const finalGeometry = mergeGeometries(geometriesWithIndex, false);
4281
+ const material = new LineBasicMaterial({
4282
+ vertexColors: true
4283
+ });
4284
+ if (this.useVAO) {
4285
+ this.createVAO(finalGeometry);
4286
+ }
4287
+ const mergedLine = new LineSegments(finalGeometry, material);
4288
+ mergedLine.userData.structureId = structureId;
4289
+ rootGroup.add(mergedLine);
4290
+ this.mergedLineSegments.add(mergedLine);
4291
+ lineSegmentsArray.forEach((obj => {
4292
+ if (obj.parent) {
4293
+ obj.parent.remove(obj);
4294
+ }
4295
+ obj.geometry.dispose();
4296
+ }));
4297
+ } catch (error) {
4298
+ console.error("Failed to merge geometries:", error);
4299
+ lineSegmentsArray.forEach((obj => {
4300
+ obj.visible = true;
4301
+ rootGroup.add(obj);
4302
+ }));
4303
+ }
4304
+ }
4305
+ showOriginalObjects(objects) {
4306
+ objects.forEach((obj => {
4307
+ if (this.originalObjects.has(obj)) {
4308
+ obj.visible = true;
4309
+ }
4310
+ }));
4311
+ }
4312
+ hideOriginalObjects(objects) {
4313
+ objects.forEach((obj => {
4314
+ if (this.originalObjects.has(obj)) {
4315
+ obj.visible = false;
4316
+ }
4317
+ }));
4318
+ }
4319
+ createVAO(geometry) {
4320
+ if (!this.useVAO) {
4321
+ return;
4322
+ }
4323
+ if (geometry.attributes?.position?.count < 1e3) {
4324
+ return;
4325
+ }
4326
+ const gl = this.renderer.getContext();
4327
+ const vao = gl.createVertexArray();
4328
+ gl.bindVertexArray(vao);
4329
+ for (const name in geometry.attributes) {
4330
+ const attribute = geometry.attributes[name];
4331
+ const buffer = this.renderer.properties.get(attribute).buffer;
4332
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
4333
+ gl.enableVertexAttribArray(attribute.itemSize);
4334
+ gl.vertexAttribPointer(attribute.itemSize, attribute.itemSize, gl.FLOAT, false, 0, 0);
4335
+ }
4336
+ if (geometry.index) {
4337
+ const indexBuffer = this.renderer.properties.get(geometry.index).buffer;
4338
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
4339
+ }
4340
+ gl.bindVertexArray(null);
4341
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
4342
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
4343
+ geometry.vao = vao;
4344
+ }
4345
+ getOriginalObjectForSelect() {
4346
+ const optimizedOriginals = [];
4347
+ for (const obj of this.originalObjectsToSelection) {
4348
+ if (this.hiddenHandles.has(obj.userData.handle)) {
4349
+ continue;
4350
+ }
4351
+ optimizedOriginals.push(obj);
4352
+ }
4353
+ return optimizedOriginals;
4354
+ }
4355
+ isolateObjects(handles) {
4356
+ if (this.hiddenHandles.size !== 0) {
4357
+ this.hiddenHandles.clear();
4358
+ this.syncHiddenObjects();
4359
+ }
4360
+ for (const handle of this.handleToOptimizedObjects.keys()) {
4361
+ if (!handles.has(handle)) {
4362
+ this.hiddenHandles.add(handle);
4363
+ }
4364
+ }
4365
+ this.syncHiddenObjects();
4366
+ }
4367
+ showAllHiddenObjects() {
4368
+ this.hiddenHandles.clear();
4369
+ this.syncHiddenObjects();
4370
+ }
4371
+ hideObjects(handles) {
4372
+ handles.forEach((handle => {
4373
+ this.hiddenHandles.add(handle);
4374
+ }));
4375
+ this.syncHiddenObjects();
4376
+ }
4377
+ showObjects(handles) {
4378
+ handles.forEach((handle => {
4379
+ this.hiddenHandles.delete(handle);
4380
+ }));
4381
+ this.syncHiddenObjects();
4382
+ }
4383
+ syncHiddenObjects() {
4384
+ if (this.oldOptimizeObjects.size !== 0) {
4385
+ for (const obj of this.oldOptimizeObjects) {
4386
+ obj.visible = true;
4387
+ }
4388
+ this.oldOptimizeObjects.clear();
4389
+ }
4390
+ if (this.newOptimizedObjects.size !== 0) {
4391
+ for (const obj of this.newOptimizedObjects) {
4392
+ obj.visible = false;
4393
+ obj.geometry.dispose();
4394
+ obj.parent.remove(obj);
4395
+ }
4396
+ this.newOptimizedObjects.clear();
4397
+ }
4398
+ if (this.hiddenHandles.size === 0) {
4399
+ return;
4400
+ }
4401
+ this.hiddenHandles.forEach((handle => {
4402
+ const objects = this.handleToOptimizedObjects.get(handle);
4403
+ if (objects) {
4404
+ objects.forEach((x => this.oldOptimizeObjects.add(x)));
4405
+ }
4406
+ }));
4407
+ this.oldOptimizeObjects.forEach((optimizedObject => {
4408
+ optimizedObject.visible = false;
4409
+ const originObjects = this.optimizedOriginalMap.get(optimizedObject);
4410
+ const updateListToOptimize = [];
4411
+ originObjects.forEach((obj => {
4412
+ if (!this.hiddenHandles.has(obj.userData.handle)) {
4413
+ updateListToOptimize.push(obj);
4414
+ }
4415
+ }));
4416
+ const firstObject = updateListToOptimize[0];
4417
+ if (firstObject instanceof Mesh || firstObject instanceof LineSegments) {
4418
+ const geometries = updateListToOptimize.map((obj => {
4419
+ const geometry = obj.geometry.clone();
4420
+ obj.updateWorldMatrix(true, false);
4421
+ geometry.applyMatrix4(obj.matrixWorld);
4422
+ return geometry;
4423
+ }));
4424
+ const newMergedGeometry = mergeGeometries(geometries);
4425
+ const mergedObject = firstObject instanceof Mesh ? new Mesh(newMergedGeometry, optimizedObject.material) : new LineSegments(newMergedGeometry, optimizedObject.material);
4426
+ mergedObject.visible = true;
4427
+ optimizedObject.parent.add(mergedObject);
4428
+ this.newOptimizedObjects.add(mergedObject);
4429
+ geometries.forEach((geometry => {
4430
+ geometry.dispose();
4431
+ }));
4432
+ } else if (firstObject instanceof Line) {
4433
+ let totalVertices = 0;
4434
+ updateListToOptimize.map((line => {
4435
+ totalVertices += line.geometry.attributes.position.count;
4436
+ }));
4437
+ const positions = new Float32Array(totalVertices * 3);
4438
+ let posOffset = 0;
4439
+ const indices = [];
4440
+ let vertexOffset = 0;
4441
+ updateListToOptimize.forEach((line => {
4442
+ const geometry = line.geometry;
4443
+ const positionAttr = geometry.attributes.position;
4444
+ const vertexCount = positionAttr.count;
4445
+ line.updateWorldMatrix(true, false);
4446
+ const matrix = line.matrixWorld;
4447
+ const vector = new Vector3;
4448
+ for (let i = 0; i < vertexCount; i++) {
4449
+ vector.fromBufferAttribute(positionAttr, i);
4450
+ vector.applyMatrix4(matrix);
4451
+ positions[posOffset++] = vector.x;
4452
+ positions[posOffset++] = vector.y;
4453
+ positions[posOffset++] = vector.z;
4454
+ }
4455
+ for (let i = 0; i < vertexCount - 1; i++) {
4456
+ indices.push(vertexOffset + i, vertexOffset + i + 1);
4457
+ }
4458
+ vertexOffset += vertexCount;
4459
+ }));
4460
+ const geometry = new BufferGeometry;
4461
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
4462
+ geometry.setIndex(indices);
4463
+ geometry.computeBoundingSphere();
4464
+ geometry.computeBoundingBox();
4465
+ const mergedLine = new LineSegments(geometry, optimizedObject.material);
4466
+ mergedLine.visible = true;
4467
+ optimizedObject.parent.add(mergedLine);
4468
+ this.newOptimizedObjects.add(mergedLine);
4469
+ }
4470
+ }));
4471
+ }
4472
+ }
4473
+
4474
+ class GLTFCloudDynamicLoader {
4475
+ constructor(viewer) {
4476
+ this.requestId = 0;
4477
+ this.viewer = viewer;
4478
+ this.scene = new Group;
4479
+ }
4480
+ dispose() {
4481
+ if (this.gltfLoader) this.gltfLoader.clear();
4482
+ }
4483
+ isSupport(file) {
4484
+ return typeof file === "object" && typeof file.database === "string" && typeof file.downloadResource === "function" && typeof file.downloadResourceRange === "function" && /.gltf$/i.test(file.database);
4485
+ }
4486
+ async load(model, format, params) {
4487
+ this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, this.viewer.scene, this.viewer.renderer);
4488
+ this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
4489
+ this.gltfLoader.addEventListener("databasechunk", (data => {
4490
+ const modelImpl = new DynamicModelImpl(this.scene);
4491
+ modelImpl.loader = this;
4492
+ modelImpl.gltfLoader = this.gltfLoader;
4493
+ modelImpl.viewer = this.viewer;
4494
+ this.viewer.scene.add(this.scene);
4495
+ this.viewer.models.push(modelImpl);
4496
+ this.viewer.syncOptions();
4497
+ this.viewer.syncOverlay();
4498
+ this.viewer.update();
4499
+ this.viewer.emitEvent({
4500
+ type: "databasechunk",
4501
+ data: data,
4502
+ file: model.file,
4503
+ model: model
4504
+ });
4505
+ }));
4506
+ this.gltfLoader.addEventListener("geometryprogress", (data => {
4507
+ const progress = data.loaded / data.total;
4508
+ this.viewer.emitEvent({
4509
+ type: "geometryprogress",
4510
+ data: progress,
4511
+ file: model.file,
4512
+ model: model
4513
+ });
4514
+ }));
4515
+ this.gltfLoader.addEventListener("geometrymemory", (data => {
4516
+ this.viewer.emit({
4517
+ type: "geometryprogress",
4518
+ data: data
4519
+ });
4520
+ }));
4521
+ this.gltfLoader.addEventListener("geometryerror", (data => {
4522
+ this.viewer.emitEvent({
4523
+ type: "geometryerror",
4524
+ data: data,
4525
+ file: model.file,
4526
+ model: model
4527
+ });
4528
+ }));
4529
+ this.gltfLoader.addEventListener("update", (data => {
4530
+ this.viewer.update();
4531
+ }));
4532
+ const loadController = {
4533
+ loadJson: async () => {
4534
+ const progress = progress => {
4535
+ this.viewer.emitEvent({
4536
+ type: "geometryprogress",
4537
+ data: progress,
4538
+ file: model
4539
+ });
4540
+ };
4541
+ const arrayBuffer = await model.downloadResource(model.database, progress, this.gltfLoader.getAbortController().signal);
4542
+ const text = (new TextDecoder).decode(arrayBuffer);
4543
+ const json = JSON.parse(text);
4544
+ return json;
4545
+ },
4546
+ loadBinaryData: requests => {
4547
+ const ranges = requests.map((request => ({
4548
+ begin: request.offset,
4549
+ end: request.offset + request.length - 1,
4550
+ requestId: this.requestId++
4551
+ })));
4552
+ return model.downloadResourceRange(model.geometry[0], undefined, ranges, undefined, this.gltfLoader.getAbortController().signal);
4553
+ },
4554
+ baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`)
4555
+ };
4556
+ const structure = new GltfStructure(1);
4557
+ await structure.initialize(loadController);
4558
+ await this.gltfLoader.loadStructure(structure);
4559
+ await this.gltfLoader.loadNodes();
4560
+ return this;
4561
+ }
4562
+ cancel() {
4563
+ if (this.gltfLoader) {
4564
+ this.gltfLoader.abortLoading();
4565
+ }
4566
+ }
4567
+ }
4568
+
4569
+ const loaders = loadersRegistry("threejs");
4570
+
4571
+ loaders.registerLoader("gltf-file", (viewer => new GLTFFileLoader(viewer)));
4572
+
4573
+ loaders.registerLoader("gltf-cloud", (viewer => new GLTFCloudDynamicLoader(viewer)));
4574
+
4575
+ class Viewer extends EventEmitter2 {
2508
4576
  constructor(client) {
2509
4577
  super();
2510
4578
  this._options = new Options(this);
2511
4579
  this.client = client;
2512
4580
  this.canvasEvents = CANVAS_EVENTS;
2513
4581
  this.canvaseventlistener = event => this.emit(event);
2514
- this.models = [];
2515
4582
  this.loaders = [];
4583
+ this.models = [];
2516
4584
  this.selected = [];
2517
4585
  this.extents = new Box3;
2518
4586
  this.target = new Vector3;
@@ -2539,6 +4607,7 @@ class Viewer extends EventEmitter2 {
2539
4607
  this.addEventListener("optionschange", (event => this.syncOptions(event.data)));
2540
4608
  this.scene = new Scene;
2541
4609
  this.helpers = new Scene;
4610
+ const pixelRatio = window.devicePixelRatio;
2542
4611
  const rect = canvas.parentElement.getBoundingClientRect();
2543
4612
  const width = rect.width || 1;
2544
4613
  const height = rect.height || 1;
@@ -2548,9 +4617,12 @@ class Viewer extends EventEmitter2 {
2548
4617
  this.renderer = new WebGLRenderer({
2549
4618
  canvas: canvas,
2550
4619
  antialias: true,
2551
- preserveDrawingBuffer: true
4620
+ alpha: true,
4621
+ preserveDrawingBuffer: true,
4622
+ powerPreference: "high-performance",
4623
+ logarithmicDepthBuffer: false
2552
4624
  });
2553
- this.renderer.setPixelRatio(window.devicePixelRatio);
4625
+ this.renderer.setPixelRatio(pixelRatio);
2554
4626
  this.renderer.setSize(width, height);
2555
4627
  this.renderer.toneMapping = LinearToneMapping;
2556
4628
  this.canvas = canvas;
@@ -2604,6 +4676,14 @@ class Viewer extends EventEmitter2 {
2604
4676
  isInitialized() {
2605
4677
  return !!this.renderer;
2606
4678
  }
4679
+ update(force = false) {
4680
+ this.renderNeeded = true;
4681
+ if (force) this.render(performance.now());
4682
+ this.emitEvent({
4683
+ type: "update",
4684
+ data: force
4685
+ });
4686
+ }
2607
4687
  render(time) {
2608
4688
  var _a, _b;
2609
4689
  if (!this.renderNeeded) return;
@@ -2626,15 +4706,6 @@ class Viewer extends EventEmitter2 {
2626
4706
  deltaTime: deltaTime
2627
4707
  });
2628
4708
  }
2629
- update(force = false) {
2630
- this.renderNeeded = true;
2631
- if (force) this.render(performance.now());
2632
- this.emitEvent({
2633
- type: "update",
2634
- data: force
2635
- });
2636
- }
2637
- syncOptions(options = this.options) {}
2638
4709
  loadReferences(model) {
2639
4710
  return Promise.resolve(this);
2640
4711
  }
@@ -2709,26 +4780,16 @@ class Viewer extends EventEmitter2 {
2709
4780
  }
2710
4781
  clear() {
2711
4782
  if (!this.renderer) return this;
2712
- function disposeMaterial(material) {
2713
- const materials = Array.isArray(material) ? material : [ material ];
2714
- materials.forEach((material => material.dispose()));
2715
- }
2716
- function disposeObject(object) {
2717
- if (object.geometry) object.geometry.dispose();
2718
- if (object.material) disposeMaterial(object.material);
2719
- }
2720
4783
  this.setActiveDragger();
2721
4784
  this.clearSlices();
2722
4785
  this.clearOverlay();
2723
4786
  this.clearSelected();
2724
- this.helpers.traverse(disposeObject);
2725
- this.helpers.clear();
2726
- this.models.forEach((model => model.traverse(disposeObject)));
2727
- this.models.forEach((model => model.removeFromParent()));
2728
- this.models = [];
2729
- this.scene.clear();
2730
4787
  this.loaders.forEach((loader => loader.dispose()));
2731
4788
  this.loaders = [];
4789
+ this.models.forEach((model => model.dispose()));
4790
+ this.models = [];
4791
+ this.helpers.clear();
4792
+ this.scene.clear();
2732
4793
  this.syncOptions();
2733
4794
  this.syncOverlay();
2734
4795
  this.update(true);
@@ -2737,6 +4798,7 @@ class Viewer extends EventEmitter2 {
2737
4798
  });
2738
4799
  return this;
2739
4800
  }
4801
+ syncOptions(options = this.options) {}
2740
4802
  syncOverlay() {
2741
4803
  if (!this.renderer) return;
2742
4804
  this._markup.syncOverlay();
@@ -2991,5 +5053,5 @@ class Viewer extends EventEmitter2 {
2991
5053
  }
2992
5054
  }
2993
5055
 
2994
- export { GLTFLoadingManager, Viewer, commands, components, draggers, loaders };
5056
+ export { GLTFLoadingManager, ModelImpl, Viewer, commands, components, draggers, loaders };
2995
5057
  //# sourceMappingURL=viewer-three.module.js.map