@inweb/viewer-three 26.6.6 → 26.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/components/AxesHelperComponent.js +5 -5
- package/dist/plugins/components/AxesHelperComponent.js.map +1 -1
- package/dist/plugins/components/AxesHelperComponent.min.js +1 -1
- package/dist/plugins/components/AxesHelperComponent.module.js +5 -5
- package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.js +15 -7
- package/dist/plugins/components/ExtentsHelperComponent.js.map +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.min.js +1 -1
- package/dist/plugins/components/ExtentsHelperComponent.module.js +15 -7
- package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -1
- package/dist/plugins/components/LightHelperComponent.js +5 -5
- package/dist/plugins/components/LightHelperComponent.js.map +1 -1
- package/dist/plugins/components/LightHelperComponent.min.js +1 -1
- package/dist/plugins/components/LightHelperComponent.module.js +5 -5
- package/dist/plugins/components/LightHelperComponent.module.js.map +1 -1
- package/dist/plugins/loaders/GLTFCloudLoader.js +4840 -0
- package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -0
- package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -0
- package/dist/plugins/loaders/GLTFCloudLoader.module.js +49 -0
- package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +1 -0
- package/dist/plugins/loaders/IFCXLoader.js +12 -6
- package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
- package/dist/plugins/loaders/IFCXLoader.min.js +1 -1
- package/dist/plugins/loaders/IFCXLoader.module.js +13 -7
- package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
- package/dist/viewer-three.js +3131 -459
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +2 -2
- package/dist/viewer-three.module.js +2326 -264
- package/dist/viewer-three.module.js.map +1 -1
- package/lib/Viewer/Viewer.d.ts +6 -5
- package/lib/Viewer/components/HighlighterComponent.d.ts +4 -3
- package/lib/Viewer/components/SelectionComponent.d.ts +8 -5
- package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +1 -1
- package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +20 -0
- package/lib/Viewer/loaders/GLTFCloudDynamicLoader.d.ts +15 -0
- package/lib/Viewer/model/IModelImpl.d.ts +27 -0
- package/lib/Viewer/model/ModelImpl.d.ts +30 -0
- package/lib/Viewer/model/index.d.ts +2 -0
- package/lib/index.d.ts +1 -0
- package/package.json +11 -7
- package/plugins/components/AxesHelperComponent.ts +5 -5
- package/plugins/components/ExtentsHelperComponent.ts +15 -7
- package/plugins/components/LightHelperComponent.ts +5 -5
- package/{src/Viewer/loaders/GLTFCloudModelLoader.ts → plugins/loaders/GLTFCloudLoader.ts} +15 -12
- package/plugins/loaders/{IFCXCloudFileLoader.ts → IFCXCloudLoader.ts} +8 -4
- package/plugins/loaders/IFCXFileLoader.ts +7 -3
- package/plugins/loaders/IFCXLoader.ts +2 -2
- package/src/Viewer/Viewer.ts +32 -36
- package/src/Viewer/commands/ClearSelected.ts +2 -3
- package/src/Viewer/commands/Explode.ts +1 -47
- package/src/Viewer/commands/GetModels.ts +1 -1
- package/src/Viewer/commands/GetSelected.ts +3 -1
- package/src/Viewer/commands/HideSelected.ts +3 -4
- package/src/Viewer/commands/IsolateSelected.ts +1 -7
- package/src/Viewer/commands/SelectModel.ts +9 -1
- package/src/Viewer/commands/SetSelected.ts +8 -10
- package/src/Viewer/commands/ShowAll.ts +1 -1
- package/src/Viewer/components/BackgroundComponent.ts +1 -0
- package/src/Viewer/components/ExtentsComponent.ts +5 -3
- package/src/Viewer/components/HighlighterComponent.ts +79 -48
- package/src/Viewer/components/SelectionComponent.ts +67 -21
- package/src/Viewer/draggers/CuttingPlaneDragger.ts +7 -3
- package/src/Viewer/draggers/MeasureLineDragger.ts +2 -0
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +1628 -0
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +102 -0
- package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +450 -0
- package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +145 -0
- package/src/Viewer/loaders/GLTFFileLoader.ts +7 -2
- package/src/Viewer/loaders/index.ts +2 -2
- package/src/Viewer/model/IModelImpl.ts +67 -0
- package/src/Viewer/model/ModelImpl.ts +215 -0
- package/src/Viewer/model/index.ts +25 -0
- package/src/index.ts +1 -0
- package/lib/Viewer/loaders/GLTFCloudModelLoader.d.ts +0 -8
|
@@ -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,
|
|
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.
|
|
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.
|
|
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.
|
|
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 =>
|
|
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
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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.
|
|
1730
|
-
const selection =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
2223
|
-
if (
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
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
|
-
|
|
2261
|
-
this.
|
|
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
|
-
|
|
2448
|
-
this
|
|
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
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
this.viewer = viewer;
|
|
2610
|
+
class DynamicModelImpl extends ModelImpl {
|
|
2611
|
+
getExtents(target) {
|
|
2612
|
+
return target.union(this.gltfLoader.getTotalGeometryExtent());
|
|
2465
2613
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2614
|
+
getObjects() {
|
|
2615
|
+
const objects = [];
|
|
2616
|
+
this.gltfLoader.originalObjects.forEach((object => {
|
|
2617
|
+
objects.push(object);
|
|
2618
|
+
}));
|
|
2619
|
+
return objects;
|
|
2468
2620
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
this.
|
|
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
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
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
|
-
|
|
4620
|
+
alpha: true,
|
|
4621
|
+
preserveDrawingBuffer: true,
|
|
4622
|
+
powerPreference: "high-performance",
|
|
4623
|
+
logarithmicDepthBuffer: false
|
|
2552
4624
|
});
|
|
2553
|
-
this.renderer.setPixelRatio(
|
|
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
|