@inweb/viewer-three 27.2.0 → 27.2.2

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.
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { draggersRegistry, commandsRegistry, Options, componentsRegistry, Info, Loader, loadersRegistry, CANVAS_EVENTS } from '@inweb/viewer-core';
25
25
  export * from '@inweb/viewer-core';
26
- import { Line, Vector3, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Clock, Box3, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, REVISION, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
26
+ import { Line, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, Vector3, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Plane, Object3D, Line3, Raycaster, MathUtils, EdgesGeometry, Matrix4, Vector4, Controls, Box3, Clock, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, REVISION, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, Points, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, UniformsUtils, ShaderMaterial, AdditiveBlending, HalfFloatType, Scene, WebGLRenderer, LinearSRGBColorSpace } from 'three';
27
27
  import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
28
28
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
29
29
  import { Wireframe } from 'three/examples/jsm/lines/Wireframe.js';
@@ -41,17 +41,15 @@ import { EventEmitter2 } from '@inweb/eventemitter2';
41
41
  import { Markup } from '@inweb/markup';
42
42
  export * from '@inweb/markup';
43
43
 
44
- class PlaneHelper extends Line {
45
- constructor(plane, size = 1, color = 0xffff00, offset = new Vector3()) {
44
+ class PlaneHelper2 extends Line {
45
+ constructor(size = 1, color = 0xc0c0c0) {
46
46
  const positions = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0];
47
47
  const geometry = new BufferGeometry();
48
48
  geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
49
49
  geometry.computeBoundingSphere();
50
50
  super(geometry, new LineBasicMaterial({ color, toneMapped: false }));
51
- this.type = "PlaneHelper";
52
- this.plane = plane;
51
+ this.type = "PlaneHelper2";
53
52
  this.size = size;
54
- this.offset = offset;
55
53
  const positions2 = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0];
56
54
  const geometry2 = new BufferGeometry();
57
55
  geometry2.setAttribute("position", new Float32BufferAttribute(positions2, 3));
@@ -69,14 +67,10 @@ class PlaneHelper extends Line {
69
67
  dispose() {
70
68
  this.geometry.dispose();
71
69
  this.material.dispose();
72
- this.children[0].geometry.dispose();
73
- this.children[0].material.dispose();
70
+ this.helper.geometry.dispose();
71
+ this.helper.material.dispose();
74
72
  }
75
73
  updateMatrixWorld(force) {
76
- this.position.set(0, 0, 0);
77
- this.lookAt(this.plane.normal);
78
- this.position.copy(this.offset);
79
- this.translateZ(-(this.offset.dot(this.plane.normal) + this.plane.constant));
80
74
  this.scale.set(0.5 * this.size, 0.5 * this.size, 1);
81
75
  super.updateMatrixWorld(force);
82
76
  }
@@ -832,26 +826,41 @@ class OrbitDragger {
832
826
  }
833
827
 
834
828
  class CuttingPlaneDragger extends OrbitDragger {
835
- constructor(viewer, normal, color) {
829
+ constructor(viewer, normal) {
836
830
  super(viewer);
837
831
  this.transformChange = () => {
832
+ this.plane.normal.copy(new Vector3(0, 0, -1)).applyQuaternion(this.planeCenter.quaternion);
838
833
  this.plane.constant = -this.planeCenter.position.dot(this.plane.normal);
839
834
  this.viewer.update();
840
835
  };
841
- this.transformDrag = (event) => {
836
+ this.translateDrag = (event) => {
842
837
  this.orbit.enabled = !event.value;
838
+ this.rotate.enabled = !event.value;
839
+ };
840
+ this.rotateDrag = (event) => {
841
+ this.orbit.enabled = !event.value;
842
+ this.translate.enabled = !event.value;
843
843
  };
844
844
  this.updatePlaneSize = () => {
845
845
  this.planeHelper.size = this.viewer.extents.getSize(new Vector3()).length() || 1;
846
846
  this.viewer.update();
847
847
  };
848
848
  this.updateTransformCamera = () => {
849
- this.transform.camera = this.viewer.camera;
849
+ this.translate.camera = this.viewer.camera;
850
+ this.rotate.camera = this.viewer.camera;
851
+ };
852
+ this.onKeyDown = (event) => {
853
+ if (event.key === "Shift")
854
+ this.rotate.setRotationSnap(Math.PI / 4);
855
+ };
856
+ this.onKeyUp = (event) => {
857
+ if (event.key === "Shift")
858
+ this.rotate.setRotationSnap(null);
850
859
  };
851
860
  this.onDoubleClick = (event) => {
852
861
  event.stopPropagation();
853
- this.plane.negate();
854
- this.viewer.update();
862
+ this.planeCenter.rotateOnAxis(new Vector3(0, 1, 0), Math.PI);
863
+ this.transformChange();
855
864
  };
856
865
  const extentsSize = viewer.extents.getSize(new Vector3()).length() || 1;
857
866
  const extentsCenter = viewer.extents.getCenter(new Vector3());
@@ -860,24 +869,38 @@ class CuttingPlaneDragger extends OrbitDragger {
860
869
  if (!viewer.renderer.clippingPlanes)
861
870
  viewer.renderer.clippingPlanes = [];
862
871
  viewer.renderer.clippingPlanes.push(this.plane);
863
- this.planeHelper = new PlaneHelper(this.plane, extentsSize, color, extentsCenter);
864
- this.viewer.helpers.add(this.planeHelper);
865
872
  this.planeCenter = new Object3D();
866
873
  this.planeCenter.position.copy(extentsCenter);
874
+ this.planeCenter.quaternion.setFromUnitVectors(new Vector3(0, 0, -1), normal);
867
875
  this.viewer.helpers.add(this.planeCenter);
868
- this.transform = new TransformControls(viewer.camera, viewer.canvas);
869
- this.transform.showX = !!normal.x;
870
- this.transform.showY = !!normal.y;
871
- this.transform.showZ = !!normal.z;
872
- this.transform.attach(this.planeCenter);
873
- this.transform.addEventListener("change", this.transformChange);
874
- this.transform.addEventListener("dragging-changed", this.transformDrag);
875
- this.viewer.helpers.add(this.transform.getHelper());
876
+ this.planeHelper = new PlaneHelper2(extentsSize);
877
+ this.planeCenter.add(this.planeHelper);
878
+ this.translate = new TransformControls(viewer.camera, viewer.canvas);
879
+ this.translate.setSpace("local");
880
+ this.translate.showX = false;
881
+ this.translate.showY = false;
882
+ this.translate.showZ = true;
883
+ this.translate.attach(this.planeCenter);
884
+ this.translate.addEventListener("change", this.transformChange);
885
+ this.translate.addEventListener("dragging-changed", this.translateDrag);
886
+ this.viewer.helpers.add(this.translate.getHelper());
887
+ this.rotate = new TransformControls(viewer.camera, viewer.canvas);
888
+ this.rotate.setMode("rotate");
889
+ this.rotate.setSpace("local");
890
+ this.rotate.showX = true;
891
+ this.rotate.showY = true;
892
+ this.rotate.showZ = false;
893
+ this.rotate.attach(this.planeCenter);
894
+ this.rotate.addEventListener("change", this.transformChange);
895
+ this.rotate.addEventListener("dragging-changed", this.rotateDrag);
896
+ this.viewer.helpers.add(this.rotate.getHelper());
876
897
  this.viewer.addEventListener("explode", this.updatePlaneSize);
877
898
  this.viewer.addEventListener("show", this.updatePlaneSize);
878
899
  this.viewer.addEventListener("showall", this.updatePlaneSize);
879
900
  this.viewer.addEventListener("changecameramode", this.updateTransformCamera);
880
901
  this.viewer.canvas.addEventListener("dblclick", this.onDoubleClick, true);
902
+ window.addEventListener("keydown", this.onKeyDown);
903
+ window.addEventListener("keyup", this.onKeyUp);
881
904
  this.viewer.update();
882
905
  }
883
906
  dispose() {
@@ -886,11 +909,18 @@ class CuttingPlaneDragger extends OrbitDragger {
886
909
  this.viewer.removeEventListener("showall", this.updatePlaneSize);
887
910
  this.viewer.removeEventListener("changecameramode", this.updateTransformCamera);
888
911
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
889
- this.transform.removeEventListener("change", this.transformChange);
890
- this.transform.removeEventListener("dragging-changed", this.transformDrag);
891
- this.transform.getHelper().removeFromParent();
892
- this.transform.detach();
893
- this.transform.dispose();
912
+ window.removeEventListener("keydown", this.onKeyDown);
913
+ window.removeEventListener("keyup", this.onKeyUp);
914
+ this.translate.removeEventListener("change", this.transformChange);
915
+ this.translate.removeEventListener("dragging-changed", this.translateDrag);
916
+ this.translate.getHelper().removeFromParent();
917
+ this.translate.detach();
918
+ this.translate.dispose();
919
+ this.rotate.removeEventListener("change", this.transformChange);
920
+ this.rotate.removeEventListener("dragging-changed", this.rotateDrag);
921
+ this.rotate.getHelper().removeFromParent();
922
+ this.rotate.detach();
923
+ this.rotate.dispose();
894
924
  this.planeHelper.removeFromParent();
895
925
  this.planeHelper.dispose();
896
926
  this.planeCenter.removeFromParent();
@@ -900,19 +930,19 @@ class CuttingPlaneDragger extends OrbitDragger {
900
930
 
901
931
  class CuttingPlaneXAxisDragger extends CuttingPlaneDragger {
902
932
  constructor(viewer) {
903
- super(viewer, new Vector3(1, 0, 0), 0xff0000);
933
+ super(viewer, new Vector3(-1, 0, 0));
904
934
  }
905
935
  }
906
936
 
907
937
  class CuttingPlaneYAxisDragger extends CuttingPlaneDragger {
908
938
  constructor(viewer) {
909
- super(viewer, new Vector3(0, 1, 0), 0x00ff00);
939
+ super(viewer, new Vector3(0, -1, 0));
910
940
  }
911
941
  }
912
942
 
913
943
  class CuttingPlaneZAxisDragger extends CuttingPlaneDragger {
914
944
  constructor(viewer) {
915
- super(viewer, new Vector3(0, 0, 1), 0x0000ff);
945
+ super(viewer, new Vector3(0, 0, -1));
916
946
  }
917
947
  }
918
948
 
@@ -1514,6 +1544,12 @@ class WalkControls extends Controls {
1514
1544
  this.movementSpeed = 0.1;
1515
1545
  this.multiplier = 3;
1516
1546
  this.groundFollowingSkippedFrames = 0;
1547
+ this.GROUND_BOX_HALF_SIZE = 20;
1548
+ this.GROUND_BOX_REFRESH_THRESHOLD = 0.3;
1549
+ this._groundObjectBoxes = new Map();
1550
+ this._activeGroundObjects = [];
1551
+ this._groundBox = new Box3();
1552
+ this._groundBoxCenter = new Vector3();
1517
1553
  this.moveWheel = 0;
1518
1554
  this.mouseDragOn = false;
1519
1555
  this._up = new Vector3();
@@ -1585,6 +1621,9 @@ class WalkControls extends Controls {
1585
1621
  };
1586
1622
  this.camera = camera;
1587
1623
  this.groundObjects = groundObjects;
1624
+ for (const obj of groundObjects) {
1625
+ this._groundObjectBoxes.set(obj, new Box3().setFromObject(obj));
1626
+ }
1588
1627
  this.raycaster = new Raycaster();
1589
1628
  this.raycaster.near = 0;
1590
1629
  this.raycaster.far = this.EYE_HEIGHT + this.FAILING_DISTANCE;
@@ -1619,10 +1658,29 @@ class WalkControls extends Controls {
1619
1658
  window.removeEventListener("keyup", this.onKeyUp);
1620
1659
  super.dispose();
1621
1660
  }
1661
+ _rebuildGroundBox(center) {
1662
+ const h = this.GROUND_BOX_HALF_SIZE;
1663
+ this._groundBoxCenter.copy(center);
1664
+ this._groundBox.set(new Vector3(center.x - h, center.y - h * 4, center.z - h), new Vector3(center.x + h, center.y + h * 4, center.z + h));
1665
+ this._activeGroundObjects = this.groundObjects.filter((obj) => {
1666
+ const objectBox = this._groundObjectBoxes.get(obj);
1667
+ return objectBox !== undefined && this._groundBox.intersectsBox(objectBox);
1668
+ });
1669
+ }
1670
+ _needsGroundBoxRebuild(pos) {
1671
+ if (this._activeGroundObjects.length === 0 && this.groundObjects.length > 0)
1672
+ return true;
1673
+ const threshold = this.GROUND_BOX_HALF_SIZE * this.GROUND_BOX_REFRESH_THRESHOLD;
1674
+ return (Math.abs(pos.x - this._groundBoxCenter.x) > threshold || Math.abs(pos.z - this._groundBoxCenter.z) > threshold);
1675
+ }
1622
1676
  updateGroundFollowing() {
1677
+ const pos = this.object.position;
1678
+ if (this._needsGroundBoxRebuild(pos)) {
1679
+ this._rebuildGroundBox(pos);
1680
+ }
1623
1681
  this._up.copy(this.camera.up).negate();
1624
- this.raycaster.set(this.object.position, this._up);
1625
- const intersects = this.raycaster.intersectObjects(this.groundObjects, false);
1682
+ this.raycaster.set(pos, this._up);
1683
+ const intersects = this.raycaster.intersectObjects(this._activeGroundObjects, false);
1626
1684
  if (intersects.length > 0) {
1627
1685
  const groundY = intersects[0].point.y;
1628
1686
  const targetY = groundY + this.EYE_HEIGHT;
@@ -2499,7 +2557,7 @@ class CameraComponent {
2499
2557
  camera = object;
2500
2558
  });
2501
2559
  if (camera) {
2502
- camera.isDefaultCamera = true;
2560
+ camera.userData.isDefaultCamera = true;
2503
2561
  camera.scale.set(1, 1, 1);
2504
2562
  this.switchCamera(camera);
2505
2563
  const mode = this.getCameraMode(camera);
@@ -2703,6 +2761,7 @@ class InfoComponent {
2703
2761
  this.viewer.info.optimizedScene.edges = 0;
2704
2762
  this.viewer.info.memory.geometries = 0;
2705
2763
  this.viewer.info.memory.geometryBytes = 0;
2764
+ this.viewer.info.memory.optimizedGeometryBytes = 0;
2706
2765
  this.viewer.info.memory.textures = 0;
2707
2766
  this.viewer.info.memory.textureBytes = 0;
2708
2767
  this.viewer.info.memory.materials = 0;
@@ -2739,6 +2798,7 @@ class InfoComponent {
2739
2798
  this.viewer.info.optimizedScene.edges += info.optimizedScene.edges;
2740
2799
  this.viewer.info.memory.geometries += info.memory.geometries;
2741
2800
  this.viewer.info.memory.geometryBytes += info.memory.geometryBytes;
2801
+ this.viewer.info.memory.optimizedGeometryBytes += info.memory.optimizedGeometryBytes;
2742
2802
  this.viewer.info.memory.textures += info.memory.textures;
2743
2803
  this.viewer.info.memory.textureBytes += info.memory.textureBytes;
2744
2804
  this.viewer.info.memory.materials += info.memory.materials;
@@ -2749,7 +2809,9 @@ class InfoComponent {
2749
2809
  this.viewer.info.performance.loadTime += performance.now() - this.startTime;
2750
2810
  console.log("Number of objects:", info.scene.objects);
2751
2811
  console.log("Number of objects after optimization:", info.optimizedScene.objects);
2752
- console.log("Total geometry size:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
2812
+ console.log("Geometry size:", info.memory.geometryBytes / (1024 * 1024), "MB");
2813
+ console.log("Optimized geometry size:", info.memory.optimizedGeometryBytes / (1024 * 1024), "MB");
2814
+ console.log("Estimated GPU used:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
2753
2815
  console.log("File load time:", this.viewer.info.performance.loadTime, "ms");
2754
2816
  };
2755
2817
  this.resize = () => {
@@ -3492,6 +3554,7 @@ class ModelImpl {
3492
3554
  info.scene.edges = Math.floor(totalEdges);
3493
3555
  info.memory.geometries = geometries.size;
3494
3556
  info.memory.geometryBytes = geometryBytes;
3557
+ info.memory.optimizedGeometryBytes = 0;
3495
3558
  info.memory.textures = textures.size;
3496
3559
  info.memory.textureBytes = Math.floor(textureBytes);
3497
3560
  info.memory.materials = materials.size;
@@ -3664,6 +3727,7 @@ class DynamicModelImpl extends ModelImpl {
3664
3727
  info.optimizedScene.edges = stats.scene.afterOptimization.edges;
3665
3728
  info.memory.geometries = stats.memory.geometries.count;
3666
3729
  info.memory.geometryBytes = stats.memory.geometries.bytes;
3730
+ info.memory.optimizedGeometryBytes = stats.memory.geometries.optimizedBytes;
3667
3731
  info.memory.textures = stats.memory.textures.count;
3668
3732
  info.memory.materials = stats.memory.materials.count;
3669
3733
  info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;
@@ -4335,11 +4399,14 @@ class DynamicGltfLoader {
4335
4399
  this.structures = [];
4336
4400
  this.structureRoots = new Map();
4337
4401
  this.memoryLimit = this.getAvailableMemory();
4402
+ this.optimizationMemoryMultiplier = 5;
4403
+ this.memoryEstimationFactor = 1.7;
4338
4404
  this.loadedGeometrySize = 0;
4339
4405
  this.geometryCache = new Map();
4340
4406
  this.materialCache = new Map();
4341
4407
  this.textureCache = new Map();
4342
4408
  this.currentMemoryUsage = 0;
4409
+ this.pendingMemoryUsage = 0;
4343
4410
  this.updateMemoryIndicator();
4344
4411
  this.loadedMaterials = new Map();
4345
4412
  this.abortController = new AbortController();
@@ -4395,7 +4462,7 @@ class DynamicGltfLoader {
4395
4462
  } catch (error) {
4396
4463
  console.warn("Error detecting available memory:", error);
4397
4464
  }
4398
- return memoryLimit / 3;
4465
+ return memoryLimit;
4399
4466
  }
4400
4467
  getAbortController() {
4401
4468
  return this.abortController;
@@ -4403,9 +4470,26 @@ class DynamicGltfLoader {
4403
4470
  abortLoading() {
4404
4471
  this.abortController.abort();
4405
4472
  }
4473
+ getOptimizedGeometrySize() {
4474
+ let total = 0;
4475
+ const addSize = (obj) => {
4476
+ if (obj && obj.geometry) total += this.estimateGeometrySize(obj);
4477
+ };
4478
+ this.mergedMesh?.forEach(addSize);
4479
+ this.mergedLines?.forEach(addSize);
4480
+ this.mergedLineSegments?.forEach(addSize);
4481
+ this.mergedPoints?.forEach(addSize);
4482
+ return total;
4483
+ }
4406
4484
  updateMemoryIndicator() {
4485
+ const optimizedUsage = this.getOptimizedGeometrySize();
4486
+ const totalUsage = this.currentMemoryUsage + optimizedUsage;
4487
+ const totalUsageEstimate = Math.round(totalUsage * this.memoryEstimationFactor);
4407
4488
  this.dispatchEvent("geometrymemory", {
4408
4489
  currentUsage: this.currentMemoryUsage,
4490
+ optimizedUsage,
4491
+ totalUsage,
4492
+ totalUsageEstimate,
4409
4493
  limit: this.memoryLimit,
4410
4494
  });
4411
4495
  }
@@ -4457,10 +4541,13 @@ class DynamicGltfLoader {
4457
4541
  for (const geo of geometries) {
4458
4542
  currentMemoryUsage += geo.size;
4459
4543
  }
4460
- if (currentMemoryUsage > this.memoryLimit) {
4461
- console.log(`Memory usage (${Math.round(currentMemoryUsage / (1024 * 1024))}MB) exceeds limit`);
4544
+ const effectiveLimitForEviction = this.memoryLimit / this.memoryEstimationFactor;
4545
+ if (currentMemoryUsage > effectiveLimitForEviction) {
4546
+ console.log(
4547
+ `Memory usage (${Math.round((currentMemoryUsage * this.memoryEstimationFactor) / (1024 * 1024))}MB est.) exceeds limit`
4548
+ );
4462
4549
  for (const geo of geometries) {
4463
- if (currentMemoryUsage <= this.memoryLimit) break;
4550
+ if (currentMemoryUsage <= effectiveLimitForEviction) break;
4464
4551
  if (this.abortController.signal.aborted) {
4465
4552
  throw new DOMException("Loading aborted", "AbortError");
4466
4553
  }
@@ -4544,7 +4631,9 @@ class DynamicGltfLoader {
4544
4631
  }
4545
4632
  const materialCount = uniqueMaterialIds.size;
4546
4633
  const textureCount = uniqueTextureIds.size;
4547
- const estimatedGpuMemoryBytes = geometryMemoryBytes;
4634
+ const optimizedUsageBytes = this.getOptimizedGeometrySize();
4635
+ const totalUsageBytes = geometryMemoryBytes + optimizedUsageBytes;
4636
+ const estimatedGpuMemoryBytes = Math.round(totalUsageBytes * this.memoryEstimationFactor);
4548
4637
  if (!this._webglInfoCache) {
4549
4638
  try {
4550
4639
  const gl = this.renderer.getContext();
@@ -4581,7 +4670,12 @@ class DynamicGltfLoader {
4581
4670
  },
4582
4671
  },
4583
4672
  memory: {
4584
- geometries: { count: geometryCount, bytes: geometryMemoryBytes },
4673
+ geometries: {
4674
+ count: geometryCount,
4675
+ bytes: geometryMemoryBytes,
4676
+ optimizedBytes: optimizedUsageBytes,
4677
+ totalRawBytes: totalUsageBytes,
4678
+ },
4585
4679
  textures: { count: textureCount },
4586
4680
  materials: { count: materialCount },
4587
4681
  totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
@@ -4593,9 +4687,12 @@ class DynamicGltfLoader {
4593
4687
  },
4594
4688
  };
4595
4689
  }
4596
- async loadNode(nodeId, onLoadFinishCb) {
4690
+ async loadNode(nodeId, onLoadFinishCb, reservedEstimatedSize = 0) {
4597
4691
  const node = this.nodes.get(nodeId);
4598
- if (!node || node.loaded || node.loading) return;
4692
+ if (!node || node.loaded || node.loading) {
4693
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
4694
+ return;
4695
+ }
4599
4696
  node.loading = true;
4600
4697
  const meshDef = node.structure.getJson().meshes[node.meshIndex];
4601
4698
  try {
@@ -4678,6 +4775,7 @@ class DynamicGltfLoader {
4678
4775
  if (bufferRequests.length === 0) {
4679
4776
  node.loaded = true;
4680
4777
  node.loading = false;
4778
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
4681
4779
  return;
4682
4780
  }
4683
4781
  bufferRequests.sort((a, b) => a.offset - b.offset);
@@ -4789,6 +4887,7 @@ class DynamicGltfLoader {
4789
4887
  }
4790
4888
  node.loaded = true;
4791
4889
  node.loading = false;
4890
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
4792
4891
  const geometrySize = this.estimateGeometrySize(node.object);
4793
4892
  this.geometryCache.set(node.object.uuid, geometrySize);
4794
4893
  this.currentMemoryUsage += geometrySize;
@@ -4797,6 +4896,7 @@ class DynamicGltfLoader {
4797
4896
  }
4798
4897
  } catch (error) {
4799
4898
  node.loading = false;
4899
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
4800
4900
  if (error.name === "AbortError") {
4801
4901
  return;
4802
4902
  }
@@ -5012,15 +5112,18 @@ class DynamicGltfLoader {
5012
5112
  let loadedCount = 0;
5013
5113
  let lastLoadedCount = 0;
5014
5114
  const totalNodes = nodesToLoad.length;
5115
+ const progressTotal = { value: totalNodes };
5015
5116
  const loadProgress = async () => {
5016
5117
  loadedCount++;
5118
+ const total = progressTotal.value;
5119
+ const percentage = total > 0 ? Math.min(100, Math.round((loadedCount / total) * 100)) : 0;
5017
5120
  if (loadedCount - lastLoadedCount > 1000) {
5018
5121
  lastLoadedCount = loadedCount;
5019
5122
  this.updateMemoryIndicator();
5020
5123
  this.dispatchEvent("geometryprogress", {
5021
- percentage: Math.round((loadedCount / totalNodes) * 100),
5124
+ percentage,
5022
5125
  loaded: loadedCount,
5023
- total: totalNodes,
5126
+ total,
5024
5127
  });
5025
5128
  this.dispatchEvent("update");
5026
5129
  await new Promise((resolve) => {
@@ -5030,20 +5133,22 @@ class DynamicGltfLoader {
5030
5133
  };
5031
5134
  try {
5032
5135
  const loadOperations = [];
5136
+ let memoryLimitReached = false;
5033
5137
  for (const nodeId of nodesToLoad) {
5034
5138
  if (this.abortController.signal.aborted) {
5035
5139
  throw new DOMException("Loading aborted", "AbortError");
5036
5140
  }
5037
5141
  const estimatedSize = await this.estimateNodeSize(nodeId);
5038
- if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
5039
- console.log(`Memory limit reached after loading ${loadedCount} nodes`);
5040
- this.dispatchEvent("geometryerror", {
5041
- message: "Memory limit reached",
5042
- });
5043
- this.dispatchEvent("update");
5044
- return loadedCount;
5142
+ const estimated = Number(estimatedSize) || 0;
5143
+ const effectiveLimit = this.memoryLimit / this.optimizationMemoryMultiplier / this.memoryEstimationFactor;
5144
+ if (this.currentMemoryUsage + this.pendingMemoryUsage + estimated > effectiveLimit) {
5145
+ memoryLimitReached = true;
5146
+ progressTotal.value = loadOperations.length;
5147
+ console.log(`Memory limit reached after scheduling ${loadOperations.length} nodes`);
5148
+ break;
5045
5149
  }
5046
- loadOperations.push(this.loadNode(nodeId, loadProgress));
5150
+ this.pendingMemoryUsage += estimated;
5151
+ loadOperations.push(this.loadNode(nodeId, loadProgress, estimated));
5047
5152
  }
5048
5153
  for (const structure of this.structures) {
5049
5154
  loadOperations.push(structure.flushBufferRequests());
@@ -5051,7 +5156,8 @@ class DynamicGltfLoader {
5051
5156
  await Promise.all(loadOperations);
5052
5157
  this.dispatchEvent("geometryend", {
5053
5158
  totalLoaded: loadedCount,
5054
- totalNodes,
5159
+ totalNodes: progressTotal.value,
5160
+ memoryLimitReached,
5055
5161
  });
5056
5162
  return loadedCount;
5057
5163
  } catch (error) {
@@ -5361,6 +5467,7 @@ class DynamicGltfLoader {
5361
5467
  this.transformedGeometries.clear();
5362
5468
  this.totalLoadedObjects = 0;
5363
5469
  this.currentMemoryUsage = 0;
5470
+ this.pendingMemoryUsage = 0;
5364
5471
  this.loadedGeometrySize = 0;
5365
5472
  this.abortController = new AbortController();
5366
5473
  this.updateMemoryIndicator();
@@ -5567,6 +5674,7 @@ class DynamicGltfLoader {
5567
5674
  progress: 100,
5568
5675
  message: `Optimization complete! ${this.maxObjectId} objects processed.`,
5569
5676
  });
5677
+ this.updateMemoryIndicator();
5570
5678
  this.dispatchEvent("update");
5571
5679
  }
5572
5680
  async mergeMeshGroups(materialGroups, rootGroup) {
@@ -7136,12 +7244,14 @@ class Viewer extends EventEmitter2 {
7136
7244
  if (!this.renderer)
7137
7245
  return;
7138
7246
  this._markup.clearOverlay();
7247
+ this.emitEvent({ type: "clearoverlay" });
7139
7248
  this.update();
7140
7249
  }
7141
7250
  clearSlices() {
7142
7251
  if (!this.renderer)
7143
7252
  return;
7144
7253
  this.renderer.clippingPlanes = [];
7254
+ this.emitEvent({ type: "clearslices" });
7145
7255
  this.update();
7146
7256
  }
7147
7257
  getSelected() {