@inweb/viewer-three 27.2.4 → 27.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +1 -1
  3. package/dist/extensions/components/AxesHelperComponent.js +2 -2
  4. package/dist/extensions/components/AxesHelperComponent.js.map +1 -1
  5. package/dist/extensions/components/AxesHelperComponent.min.js +2 -2
  6. package/dist/extensions/components/AxesHelperComponent.module.js +2 -2
  7. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -1
  8. package/dist/extensions/components/ExtentsHelperComponent.js +2 -2
  9. package/dist/extensions/components/ExtentsHelperComponent.js.map +1 -1
  10. package/dist/extensions/components/ExtentsHelperComponent.min.js +2 -2
  11. package/dist/extensions/components/ExtentsHelperComponent.module.js +2 -2
  12. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +1 -1
  13. package/dist/extensions/components/GridHelperComponent.js +2 -2
  14. package/dist/extensions/components/GridHelperComponent.js.map +1 -1
  15. package/dist/extensions/components/GridHelperComponent.min.js +2 -2
  16. package/dist/extensions/components/GridHelperComponent.module.js +2 -2
  17. package/dist/extensions/components/GridHelperComponent.module.js.map +1 -1
  18. package/dist/extensions/components/InfoPanelComponent.js +2 -2
  19. package/dist/extensions/components/InfoPanelComponent.js.map +1 -1
  20. package/dist/extensions/components/InfoPanelComponent.min.js +2 -2
  21. package/dist/extensions/components/InfoPanelComponent.module.js +2 -2
  22. package/dist/extensions/components/InfoPanelComponent.module.js.map +1 -1
  23. package/dist/extensions/components/LightHelperComponent.js +2 -2
  24. package/dist/extensions/components/LightHelperComponent.js.map +1 -1
  25. package/dist/extensions/components/LightHelperComponent.min.js +2 -2
  26. package/dist/extensions/components/LightHelperComponent.module.js +2 -2
  27. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -1
  28. package/dist/extensions/components/RoomEnvironmentComponent.js +2 -2
  29. package/dist/extensions/components/RoomEnvironmentComponent.js.map +1 -1
  30. package/dist/extensions/components/RoomEnvironmentComponent.min.js +2 -2
  31. package/dist/extensions/components/RoomEnvironmentComponent.module.js +2 -2
  32. package/dist/extensions/components/RoomEnvironmentComponent.module.js.map +1 -1
  33. package/dist/extensions/components/StatsPanelComponent.js +2 -2
  34. package/dist/extensions/components/StatsPanelComponent.js.map +1 -1
  35. package/dist/extensions/components/StatsPanelComponent.min.js +2 -2
  36. package/dist/extensions/components/StatsPanelComponent.module.js +2 -2
  37. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -1
  38. package/dist/extensions/loaders/GLTFCloudLoader.js +2 -2
  39. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -1
  40. package/dist/extensions/loaders/GLTFCloudLoader.min.js +2 -2
  41. package/dist/extensions/loaders/GLTFCloudLoader.module.js +2 -2
  42. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -1
  43. package/dist/extensions/loaders/GLTFFileLoader.js +2 -2
  44. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -1
  45. package/dist/extensions/loaders/GLTFFileLoader.min.js +2 -2
  46. package/dist/extensions/loaders/GLTFFileLoader.module.js +2 -2
  47. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -1
  48. package/dist/extensions/loaders/IFCXLoader.js +2 -2
  49. package/dist/extensions/loaders/IFCXLoader.js.map +1 -1
  50. package/dist/extensions/loaders/IFCXLoader.min.js +2 -2
  51. package/dist/extensions/loaders/IFCXLoader.module.js +2 -2
  52. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -1
  53. package/dist/extensions/loaders/PotreeLoader.js +2 -2
  54. package/dist/extensions/loaders/PotreeLoader.js.map +1 -1
  55. package/dist/extensions/loaders/PotreeLoader.min.js +2 -2
  56. package/dist/extensions/loaders/PotreeLoader.module.js +2 -2
  57. package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -1
  58. package/dist/viewer-three.js +755 -362
  59. package/dist/viewer-three.js.map +1 -1
  60. package/dist/viewer-three.min.js +5 -5
  61. package/dist/viewer-three.module.js +756 -363
  62. package/dist/viewer-three.module.js.map +1 -1
  63. package/extensions/components/AxesHelperComponent.ts +2 -2
  64. package/extensions/components/ExtentsHelperComponent.ts +2 -2
  65. package/extensions/components/GridHelperComponent.ts +2 -2
  66. package/extensions/components/InfoPanelComponent.ts +2 -2
  67. package/extensions/components/LightHelperComponent.ts +2 -2
  68. package/extensions/components/RoomEnvironmentComponent.ts +2 -2
  69. package/extensions/components/StatsPanelComponent.ts +2 -2
  70. package/extensions/loaders/GLTFCloudLoader.ts +2 -2
  71. package/extensions/loaders/GLTFFileLoader.ts +2 -2
  72. package/extensions/loaders/IFCX/IFCXCloudLoader.ts +2 -2
  73. package/extensions/loaders/IFCX/IFCXFileLoader.ts +2 -2
  74. package/extensions/loaders/IFCX/IFCXLoader.ts +2 -2
  75. package/extensions/loaders/IFCX/index.ts +2 -2
  76. package/extensions/loaders/IFCX/render.js +2 -2
  77. package/extensions/loaders/Potree/PotreeFileLoader.ts +2 -2
  78. package/extensions/loaders/Potree/PotreeModelImpl.ts +2 -2
  79. package/extensions/loaders/Potree/index.ts +2 -2
  80. package/lib/Viewer/controls/WalkControls.d.ts +8 -0
  81. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +23 -5
  82. package/lib/Viewer/helpers/PlaneHelper2.d.ts +8 -4
  83. package/lib/Viewer/measurement/Snapper.d.ts +1 -1
  84. package/package.json +5 -5
  85. package/src/Viewer/Viewer.ts +2 -2
  86. package/src/Viewer/commands/ApplyModelTransform.ts +2 -2
  87. package/src/Viewer/commands/ClearMarkup.ts +2 -2
  88. package/src/Viewer/commands/ClearSelected.ts +2 -2
  89. package/src/Viewer/commands/ClearSlices.ts +2 -2
  90. package/src/Viewer/commands/Explode.ts +2 -2
  91. package/src/Viewer/commands/GetDefaultViewPositions.ts +2 -2
  92. package/src/Viewer/commands/GetModels.ts +2 -2
  93. package/src/Viewer/commands/GetSelected.ts +2 -2
  94. package/src/Viewer/commands/GetSelected2.ts +2 -2
  95. package/src/Viewer/commands/GetSnapshot.ts +2 -2
  96. package/src/Viewer/commands/HideSelected.ts +2 -2
  97. package/src/Viewer/commands/IsolateSelected.ts +2 -2
  98. package/src/Viewer/commands/RegenerateAll.ts +2 -2
  99. package/src/Viewer/commands/ResetView.ts +2 -2
  100. package/src/Viewer/commands/SelectModel.ts +2 -2
  101. package/src/Viewer/commands/SetActiveDragger.ts +2 -2
  102. package/src/Viewer/commands/SetDefaultViewPosition.ts +2 -2
  103. package/src/Viewer/commands/SetMarkupColor.ts +2 -2
  104. package/src/Viewer/commands/SetSelected.ts +2 -2
  105. package/src/Viewer/commands/SetSelected2.ts +2 -2
  106. package/src/Viewer/commands/ShowAll.ts +2 -2
  107. package/src/Viewer/commands/ZoomTo.ts +2 -2
  108. package/src/Viewer/commands/ZoomToExtents.ts +2 -2
  109. package/src/Viewer/commands/ZoomToObjects.ts +2 -2
  110. package/src/Viewer/commands/ZoomToSelected.ts +2 -2
  111. package/src/Viewer/commands/index.ts +2 -2
  112. package/src/Viewer/components/BackgroundComponent.ts +2 -2
  113. package/src/Viewer/components/CameraComponent.ts +2 -2
  114. package/src/Viewer/components/CanvasRemoveComponent.ts +2 -2
  115. package/src/Viewer/components/CanvasResizeComponent.ts +2 -2
  116. package/src/Viewer/components/ExtentsComponent.ts +2 -2
  117. package/src/Viewer/components/HighlighterComponent.ts +2 -2
  118. package/src/Viewer/components/HighlighterUtils.ts +2 -2
  119. package/src/Viewer/components/InfoComponent.ts +2 -2
  120. package/src/Viewer/components/LightComponent.ts +2 -2
  121. package/src/Viewer/components/RenderLoopComponent.ts +2 -2
  122. package/src/Viewer/components/ResetComponent.ts +2 -2
  123. package/src/Viewer/components/SelectionComponent.ts +3 -3
  124. package/src/Viewer/components/ViewPositionComponent.ts +2 -2
  125. package/src/Viewer/components/WCSHelperComponent.ts +2 -2
  126. package/src/Viewer/components/index.ts +2 -2
  127. package/src/Viewer/controls/FlyControls.ts +2 -2
  128. package/src/Viewer/controls/JoyStickControls.ts +2 -2
  129. package/src/Viewer/controls/WalkControls.ts +52 -6
  130. package/src/Viewer/draggers/CuttingPlaneDragger.ts +193 -33
  131. package/src/Viewer/draggers/CuttingPlaneXAxis.ts +4 -5
  132. package/src/Viewer/draggers/CuttingPlaneYAxis.ts +4 -5
  133. package/src/Viewer/draggers/CuttingPlaneZAxis.ts +4 -5
  134. package/src/Viewer/draggers/FlyDragger.ts +2 -2
  135. package/src/Viewer/draggers/MeasureLineDragger.ts +2 -2
  136. package/src/Viewer/draggers/OrbitDragger.ts +2 -2
  137. package/src/Viewer/draggers/PanDragger.ts +2 -2
  138. package/src/Viewer/draggers/WalkDragger.ts +2 -2
  139. package/src/Viewer/draggers/ZoomDragger.ts +2 -2
  140. package/src/Viewer/draggers/index.ts +4 -2
  141. package/src/Viewer/helpers/PlaneHelper2.ts +32 -19
  142. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +495 -182
  143. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +46 -35
  144. package/src/Viewer/loaders/GLTFBinaryParser.ts +2 -2
  145. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +2 -2
  146. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +2 -2
  147. package/src/Viewer/loaders/GLTFLoadingManager.ts +2 -2
  148. package/src/Viewer/loaders/JSONStreamParser.ts +2 -2
  149. package/src/Viewer/loaders/RangesLoader.ts +2 -2
  150. package/src/Viewer/loaders/index.ts +2 -2
  151. package/src/Viewer/measurement/Snapper.ts +15 -7
  152. package/src/Viewer/measurement/UnitConverter.ts +2 -2
  153. package/src/Viewer/measurement/UnitFormatter.ts +2 -2
  154. package/src/Viewer/models/IModelImpl.ts +2 -2
  155. package/src/Viewer/models/ModelImpl.ts +15 -12
  156. package/src/Viewer/postprocessing/SSAARenderPass.js +2 -2
  157. package/src/Viewer/scenes/Helpers.ts +2 -2
  158. package/src/index-umd.ts +2 -2
  159. package/src/index.ts +2 -2
@@ -1,5 +1,5 @@
1
1
  ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
2
+ // Copyright (C) 2002-2026, Open Design Alliance (the "Alliance").
3
3
  // All rights reserved.
4
4
  //
5
5
  // This software and its documentation and related materials are owned by
@@ -14,7 +14,7 @@
14
14
  //
15
15
  // This application incorporates Open Design Alliance software pursuant to a
16
16
  // license agreement with Open Design Alliance.
17
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
17
+ // Open Design Alliance Copyright (C) 2002-2026 by Open Design Alliance.
18
18
  // All rights reserved.
19
19
  //
20
20
  // By use of this software, its documentation or related materials, you
@@ -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, BufferGeometry, Float32BufferAttribute, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, EventDispatcher, Vector3, 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 { EventDispatcher, Vector3, MOUSE, TOUCH, Spherical, Quaternion, Vector2, Object3D, BufferGeometry, Float32BufferAttribute, Line, LineBasicMaterial, Mesh, MeshBasicMaterial, DoubleSide, Line3, Raycaster, MathUtils, EdgesGeometry, Plane, Matrix4, Vector4, Controls, Box3, Clock, Color, PerspectiveCamera, OrthographicCamera, AmbientLight, DirectionalLight, HemisphereLight, REVISION, MeshPhongMaterial, WebGLRenderTarget, UnsignedByteType, RGBAFormat, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, PointsMaterial, DataTexture, FloatType, NearestFilter, 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,41 +41,6 @@ import { EventEmitter2 } from '@inweb/eventemitter2';
41
41
  import { Markup } from '@inweb/markup';
42
42
  export * from '@inweb/markup';
43
43
 
44
- class PlaneHelper2 extends Line {
45
- constructor(size = 1, color = 0xc0c0c0) {
46
- const positions = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0];
47
- const geometry = new BufferGeometry();
48
- geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
49
- geometry.computeBoundingSphere();
50
- super(geometry, new LineBasicMaterial({ color, toneMapped: false }));
51
- this.type = "PlaneHelper2";
52
- this.size = size;
53
- const positions2 = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0];
54
- const geometry2 = new BufferGeometry();
55
- geometry2.setAttribute("position", new Float32BufferAttribute(positions2, 3));
56
- geometry2.computeBoundingSphere();
57
- this.helper = new Mesh(geometry2, new MeshBasicMaterial({
58
- color,
59
- opacity: 0.2,
60
- transparent: true,
61
- depthWrite: false,
62
- toneMapped: false,
63
- side: DoubleSide,
64
- }));
65
- this.add(this.helper);
66
- }
67
- dispose() {
68
- this.geometry.dispose();
69
- this.material.dispose();
70
- this.helper.geometry.dispose();
71
- this.helper.material.dispose();
72
- }
73
- updateMatrixWorld(force) {
74
- this.scale.set(0.5 * this.size, 0.5 * this.size, 1);
75
- super.updateMatrixWorld(force);
76
- }
77
- }
78
-
79
44
  const _changeEvent = { type: "change" };
80
45
  const _startEvent = { type: "start" };
81
46
  const _endEvent = { type: "end" };
@@ -734,6 +699,166 @@ class OrbitControls extends EventDispatcher {
734
699
  }
735
700
  }
736
701
 
702
+ class PlaneHelper2 extends Object3D {
703
+ constructor(size = 1, color = 0xf0f0f0, opacity = 0.15) {
704
+ super();
705
+ this.type = "PlaneHelper2";
706
+ this.size = size;
707
+ const positions = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0];
708
+ const geometry = new BufferGeometry();
709
+ geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
710
+ geometry.computeBoundingSphere();
711
+ this.outline = new Line(geometry, new LineBasicMaterial({ color, toneMapped: false }));
712
+ const positions2 = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0];
713
+ const geometry2 = new BufferGeometry();
714
+ geometry2.setAttribute("position", new Float32BufferAttribute(positions2, 3));
715
+ geometry2.computeBoundingSphere();
716
+ this.mesh = new Mesh(geometry2, new MeshBasicMaterial({
717
+ color,
718
+ opacity,
719
+ transparent: true,
720
+ depthWrite: false,
721
+ toneMapped: false,
722
+ side: DoubleSide,
723
+ }));
724
+ this.add(this.outline);
725
+ this.add(this.mesh);
726
+ }
727
+ dispose() {
728
+ this.outline.geometry.dispose();
729
+ this.outline.material.dispose();
730
+ this.mesh.geometry.dispose();
731
+ this.mesh.material.dispose();
732
+ }
733
+ updateMatrixWorld(force) {
734
+ this.scale.set(0.5 * this.size, 0.5 * this.size, 1);
735
+ super.updateMatrixWorld(force);
736
+ }
737
+ getLineMaterial() {
738
+ return this.outline.material;
739
+ }
740
+ getMeshMaterial() {
741
+ return this.mesh.material;
742
+ }
743
+ }
744
+
745
+ const DESKTOP_SNAP_DISTANCE = 10;
746
+ const MOBILE_SNAP_DISTANCE = 50;
747
+ const _vertex = new Vector3();
748
+ const _start = new Vector3();
749
+ const _end = new Vector3();
750
+ const _line = new Line3();
751
+ const _center = new Vector3();
752
+ const _projection = new Vector3();
753
+ class Snapper {
754
+ constructor(camera, renderer, canvas) {
755
+ this.camera = camera;
756
+ this.renderer = renderer;
757
+ this.canvas = canvas;
758
+ this.threshold = 0.0001;
759
+ this.raycaster = new Raycaster();
760
+ this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
761
+ this.edgesCache = new WeakMap();
762
+ }
763
+ isMobile() {
764
+ if (typeof navigator === "undefined")
765
+ return false;
766
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
767
+ }
768
+ getMousePosition(event, target) {
769
+ return target.set(event.clientX, event.clientY);
770
+ }
771
+ getPointerIntersects(mouse, objects, recursive = false, clip = true) {
772
+ const rect = this.canvas.getBoundingClientRect();
773
+ const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
774
+ const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
775
+ const coords = new Vector2(x, y);
776
+ this.raycaster.setFromCamera(coords, this.camera);
777
+ this.raycaster.params = {
778
+ Mesh: {},
779
+ Line: { threshold: this.threshold },
780
+ Line2: { threshold: this.threshold },
781
+ LOD: {},
782
+ Points: { threshold: this.threshold },
783
+ Sprite: {},
784
+ };
785
+ let intersects = this.raycaster.intersectObjects(objects, recursive);
786
+ if (clip) {
787
+ const clippingPlanes = this.renderer.clippingPlanes || [];
788
+ clippingPlanes.forEach((plane) => {
789
+ intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
790
+ });
791
+ }
792
+ return intersects;
793
+ }
794
+ getDetectRadius(point) {
795
+ const camera = this.camera;
796
+ if (camera.isOrthographicCamera) {
797
+ const fieldHeight = (camera.top - camera.bottom) / camera.zoom;
798
+ const canvasHeight = this.canvas.height;
799
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
800
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
801
+ }
802
+ if (camera.isPerspectiveCamera) {
803
+ const distance = camera.position.distanceTo(point);
804
+ const fieldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
805
+ const canvasHeight = this.canvas.height;
806
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
807
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
808
+ }
809
+ return 0.1;
810
+ }
811
+ getSnapPoint(mouse, objects) {
812
+ const intersections = this.getPointerIntersects(mouse, objects);
813
+ if (intersections.length === 0)
814
+ return undefined;
815
+ const object = intersections[0].object;
816
+ const intersectionPoint = intersections[0].point;
817
+ const localPoint = object.worldToLocal(intersectionPoint.clone());
818
+ let snapPoint;
819
+ let snapDistance = this.getDetectRadius(intersectionPoint);
820
+ const geometry = object.geometry;
821
+ const positions = geometry.attributes.position.array;
822
+ for (let i = 0; i < positions.length; i += 3) {
823
+ _vertex.set(positions[i], positions[i + 1], positions[i + 2]);
824
+ const distance = _vertex.distanceTo(localPoint);
825
+ if (distance < snapDistance) {
826
+ snapDistance = distance;
827
+ snapPoint = _vertex.clone();
828
+ }
829
+ }
830
+ if (snapPoint)
831
+ return object.localToWorld(snapPoint);
832
+ let edges = this.edgesCache.get(geometry);
833
+ if (!edges) {
834
+ edges = new EdgesGeometry(geometry);
835
+ this.edgesCache.set(geometry, edges);
836
+ }
837
+ const edgePositions = edges.attributes.position.array;
838
+ for (let i = 0; i < edgePositions.length; i += 6) {
839
+ _start.set(edgePositions[i], edgePositions[i + 1], edgePositions[i + 2]);
840
+ _end.set(edgePositions[i + 3], edgePositions[i + 4], edgePositions[i + 5]);
841
+ _line.set(_start, _end);
842
+ _line.getCenter(_center);
843
+ const centerDistance = _center.distanceTo(localPoint);
844
+ if (centerDistance < snapDistance) {
845
+ snapDistance = centerDistance;
846
+ snapPoint = _center.clone();
847
+ continue;
848
+ }
849
+ _line.closestPointToPoint(localPoint, true, _projection);
850
+ const lineDistance = _projection.distanceTo(localPoint);
851
+ if (lineDistance < snapDistance) {
852
+ snapDistance = lineDistance;
853
+ snapPoint = _projection.clone();
854
+ }
855
+ }
856
+ if (snapPoint)
857
+ return object.localToWorld(snapPoint);
858
+ return intersectionPoint.clone();
859
+ }
860
+ }
861
+
737
862
  class OrbitDragger {
738
863
  constructor(viewer) {
739
864
  this.updateControls = () => {
@@ -826,12 +951,18 @@ class OrbitDragger {
826
951
  }
827
952
 
828
953
  class CuttingPlaneDragger extends OrbitDragger {
829
- constructor(viewer, normal) {
954
+ constructor(viewer) {
830
955
  super(viewer);
956
+ this.helpers = [];
957
+ this.activeHelper = null;
831
958
  this.transformChange = () => {
832
- this.plane.normal.copy(new Vector3(0, 0, -1)).applyQuaternion(this.planeCenter.quaternion);
833
- this.plane.constant = -this.planeCenter.position.dot(this.plane.normal);
959
+ if (!this.activeHelper)
960
+ return;
961
+ const plane = this.activeHelper.plane;
962
+ plane.normal.copy(new Vector3(0, 0, -1)).applyQuaternion(this.activeHelper.quaternion);
963
+ plane.constant = -this.activeHelper.position.dot(plane.normal);
834
964
  this.viewer.update();
965
+ this.changed = true;
835
966
  };
836
967
  this.translateDrag = (event) => {
837
968
  this.orbit.enabled = !event.value;
@@ -842,45 +973,83 @@ class CuttingPlaneDragger extends OrbitDragger {
842
973
  this.translate.enabled = !event.value;
843
974
  };
844
975
  this.updatePlaneSize = () => {
845
- this.planeHelper.size = this.viewer.extents.getSize(new Vector3()).length() || 1;
976
+ const extentsSize = this.viewer.extents.getSize(new Vector3()).length() || 1;
977
+ this.helpers.forEach((planeHelper) => (planeHelper.size = extentsSize));
846
978
  this.viewer.update();
847
979
  };
848
980
  this.updateTransformCamera = () => {
849
981
  this.translate.camera = this.viewer.camera;
850
982
  this.rotate.camera = this.viewer.camera;
983
+ this.snapper.camera = this.viewer.camera;
984
+ };
985
+ this.clearHelpers = () => {
986
+ this.setActiveHelper();
987
+ this.helpers.forEach((helper) => {
988
+ helper.removeFromParent();
989
+ helper.dispose();
990
+ });
991
+ this.helpers = [];
992
+ this.viewer.update();
851
993
  };
852
994
  this.onKeyDown = (event) => {
853
995
  if (event.key === "Shift")
854
996
  this.rotate.setRotationSnap(Math.PI / 4);
997
+ if (event.key === "Delete" || event.key === "Backspace")
998
+ this.deleteActivePlane();
999
+ if (event.key === "Escape" && (this.translate.dragging || this.rotate.dragging))
1000
+ this.reset();
855
1001
  };
856
1002
  this.onKeyUp = (event) => {
857
1003
  if (event.key === "Shift")
858
1004
  this.rotate.setRotationSnap(null);
859
1005
  };
1006
+ this.onPointerDown = (event) => {
1007
+ if (event.button !== 0 || !event.isPrimary)
1008
+ return;
1009
+ this.snapper.getMousePosition(event, this.downPosition);
1010
+ this.saveState();
1011
+ };
1012
+ this.onPointerUp = (event) => {
1013
+ if (event.button !== 0)
1014
+ return;
1015
+ const upPosition = this.snapper.getMousePosition(event, new Vector2());
1016
+ if (upPosition.distanceTo(this.downPosition) !== 0)
1017
+ return;
1018
+ const intersects = this.snapper.getPointerIntersects(upPosition, this.helpers, true, false);
1019
+ if (intersects.length === 0)
1020
+ return;
1021
+ this.setActiveHelper(intersects[0].object.parent);
1022
+ };
1023
+ this.onPointerCancel = (event) => {
1024
+ this.viewer.canvas.dispatchEvent(new PointerEvent("pointerup", event));
1025
+ };
860
1026
  this.onDoubleClick = (event) => {
861
- event.stopPropagation();
862
- this.planeCenter.rotateOnAxis(new Vector3(0, 1, 0), Math.PI);
1027
+ if (!this.activeHelper)
1028
+ return;
1029
+ const mousePosition = this.snapper.getMousePosition(event, new Vector2());
1030
+ const intersects = this.snapper.getPointerIntersects(mousePosition, [this.activeHelper], true, false);
1031
+ if (intersects.length === 0)
1032
+ return;
1033
+ this.activeHelper.rotateOnAxis(new Vector3(0, 1, 0), Math.PI);
863
1034
  this.transformChange();
1035
+ event.stopPropagation();
864
1036
  };
865
- const extentsSize = viewer.extents.getSize(new Vector3()).length() || 1;
866
- const extentsCenter = viewer.extents.getCenter(new Vector3());
867
- const constant = -extentsCenter.dot(normal);
868
- this.plane = new Plane(normal, constant);
869
1037
  if (!viewer.renderer.clippingPlanes)
870
1038
  viewer.renderer.clippingPlanes = [];
871
- viewer.renderer.clippingPlanes.push(this.plane);
872
- this.planeCenter = new Object3D();
873
- this.planeCenter.position.copy(extentsCenter);
874
- this.planeCenter.quaternion.setFromUnitVectors(new Vector3(0, 0, -1), normal);
875
- this.viewer.helpers.add(this.planeCenter);
876
- this.planeHelper = new PlaneHelper2(extentsSize);
877
- this.planeCenter.add(this.planeHelper);
1039
+ this.clippingPlanes = viewer.renderer.clippingPlanes;
1040
+ this.clippingPlanes.forEach((plane) => this.addHelper(plane));
1041
+ const extentsSize = viewer.extents.getSize(new Vector3()).length() || 1;
1042
+ this.snapper = new Snapper(viewer.camera, viewer.renderer, viewer.canvas);
1043
+ this.snapper.threshold = extentsSize / 10000;
1044
+ this.downPosition = new Vector2();
1045
+ this.position0 = new Vector3();
1046
+ this.quaternion0 = new Quaternion();
878
1047
  this.translate = new TransformControls(viewer.camera, viewer.canvas);
1048
+ this.translate.setMode("translate");
879
1049
  this.translate.setSpace("local");
880
1050
  this.translate.showX = false;
881
1051
  this.translate.showY = false;
882
1052
  this.translate.showZ = true;
883
- this.translate.attach(this.planeCenter);
884
1053
  this.translate.addEventListener("change", this.transformChange);
885
1054
  this.translate.addEventListener("dragging-changed", this.translateDrag);
886
1055
  this.viewer.helpers.add(this.translate.getHelper());
@@ -890,14 +1059,18 @@ class CuttingPlaneDragger extends OrbitDragger {
890
1059
  this.rotate.showX = true;
891
1060
  this.rotate.showY = true;
892
1061
  this.rotate.showZ = false;
893
- this.rotate.attach(this.planeCenter);
894
1062
  this.rotate.addEventListener("change", this.transformChange);
895
1063
  this.rotate.addEventListener("dragging-changed", this.rotateDrag);
896
1064
  this.viewer.helpers.add(this.rotate.getHelper());
1065
+ this.setActiveHelper(this.helpers[this.helpers.length - 1]);
897
1066
  this.viewer.addEventListener("explode", this.updatePlaneSize);
898
1067
  this.viewer.addEventListener("show", this.updatePlaneSize);
899
1068
  this.viewer.addEventListener("showall", this.updatePlaneSize);
900
1069
  this.viewer.addEventListener("changecameramode", this.updateTransformCamera);
1070
+ this.viewer.addEventListener("clearslices", this.clearHelpers);
1071
+ this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown, true);
1072
+ this.viewer.canvas.addEventListener("pointerup", this.onPointerUp, true);
1073
+ this.viewer.canvas.addEventListener("pointercancel", this.onPointerCancel, true);
901
1074
  this.viewer.canvas.addEventListener("dblclick", this.onDoubleClick, true);
902
1075
  window.addEventListener("keydown", this.onKeyDown);
903
1076
  window.addEventListener("keyup", this.onKeyUp);
@@ -908,6 +1081,10 @@ class CuttingPlaneDragger extends OrbitDragger {
908
1081
  this.viewer.removeEventListener("show", this.updatePlaneSize);
909
1082
  this.viewer.removeEventListener("showall", this.updatePlaneSize);
910
1083
  this.viewer.removeEventListener("changecameramode", this.updateTransformCamera);
1084
+ this.viewer.removeEventListener("clearslices", this.clearHelpers);
1085
+ this.viewer.canvas.removeEventListener("pointerdown", this.onPointerDown, true);
1086
+ this.viewer.canvas.removeEventListener("pointerup", this.onPointerUp, true);
1087
+ this.viewer.canvas.removeEventListener("pointercancel", this.onPointerCancel, true);
911
1088
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
912
1089
  window.removeEventListener("keydown", this.onKeyDown);
913
1090
  window.removeEventListener("keyup", this.onKeyUp);
@@ -921,28 +1098,108 @@ class CuttingPlaneDragger extends OrbitDragger {
921
1098
  this.rotate.getHelper().removeFromParent();
922
1099
  this.rotate.detach();
923
1100
  this.rotate.dispose();
924
- this.planeHelper.removeFromParent();
925
- this.planeHelper.dispose();
926
- this.planeCenter.removeFromParent();
1101
+ this.helpers.forEach((helper) => {
1102
+ helper.removeFromParent();
1103
+ helper.dispose();
1104
+ });
1105
+ this.helpers = [];
1106
+ this.activeHelper = null;
927
1107
  super.dispose();
928
1108
  }
1109
+ addHelper(plane) {
1110
+ const extentsSize = this.viewer.extents.getSize(new Vector3()).length() || 1;
1111
+ const extentsCenter = this.viewer.extents.getCenter(new Vector3());
1112
+ const helper = new PlaneHelper2(extentsSize);
1113
+ helper.plane = plane;
1114
+ helper.position.copy(plane.projectPoint(extentsCenter, new Vector3()));
1115
+ helper.quaternion.setFromUnitVectors(new Vector3(0, 0, -1), plane.normal);
1116
+ this.helpers.push(helper);
1117
+ this.viewer.helpers.add(helper);
1118
+ return helper;
1119
+ }
1120
+ setActiveHelper(helper) {
1121
+ if (helper === this.activeHelper)
1122
+ return;
1123
+ if (this.activeHelper) {
1124
+ this.activeHelper.getLineMaterial().color.setHex(0xf0f0f0);
1125
+ this.activeHelper.getMeshMaterial().opacity = 0.15;
1126
+ this.translate.detach();
1127
+ this.rotate.detach();
1128
+ }
1129
+ this.activeHelper = helper;
1130
+ if (this.activeHelper) {
1131
+ this.activeHelper.getLineMaterial().color.setHex(0xd0d0d0);
1132
+ this.activeHelper.getMeshMaterial().opacity = 0.3;
1133
+ this.translate.attach(this.activeHelper);
1134
+ this.rotate.attach(this.activeHelper);
1135
+ }
1136
+ this.viewer.update();
1137
+ }
1138
+ saveState() {
1139
+ if (!this.activeHelper)
1140
+ return;
1141
+ this.position0.copy(this.activeHelper.position);
1142
+ this.quaternion0.copy(this.activeHelper.quaternion);
1143
+ }
1144
+ reset() {
1145
+ if (!this.activeHelper)
1146
+ return;
1147
+ this.translate.dragging = false;
1148
+ this.rotate.dragging = false;
1149
+ this.orbit.state = STATE.NONE;
1150
+ this.activeHelper.position.copy(this.position0);
1151
+ this.activeHelper.quaternion.copy(this.quaternion0);
1152
+ this.transformChange();
1153
+ }
1154
+ addPlane(normal) {
1155
+ const extentsCenter = this.viewer.extents.getCenter(new Vector3());
1156
+ const constant = -extentsCenter.dot(normal);
1157
+ const plane = new Plane(normal, constant);
1158
+ this.clippingPlanes.push(plane);
1159
+ const helper = this.addHelper(plane);
1160
+ this.setActiveHelper(helper);
1161
+ }
1162
+ addPlaneX() {
1163
+ this.addPlane(new Vector3(-1, 0, 0));
1164
+ }
1165
+ addPlaneY() {
1166
+ this.addPlane(new Vector3(0, -1, 0));
1167
+ }
1168
+ addPlaneZ() {
1169
+ this.addPlane(new Vector3(0, 0, -1));
1170
+ }
1171
+ deleteActivePlane() {
1172
+ if (!this.activeHelper)
1173
+ return;
1174
+ const helper = this.activeHelper;
1175
+ const index = this.clippingPlanes.indexOf(helper.plane);
1176
+ if (index !== -1)
1177
+ this.clippingPlanes.splice(index, 1);
1178
+ this.helpers = this.helpers.filter((x) => x !== helper);
1179
+ helper.removeFromParent();
1180
+ helper.dispose();
1181
+ this.setActiveHelper(this.helpers[this.helpers.length - 1]);
1182
+ }
929
1183
  }
930
1184
 
931
1185
  class CuttingPlaneXAxisDragger extends CuttingPlaneDragger {
932
1186
  constructor(viewer) {
933
- super(viewer, new Vector3(-1, 0, 0));
1187
+ super(viewer);
1188
+ this.addPlaneX();
934
1189
  }
935
1190
  }
936
1191
 
937
1192
  class CuttingPlaneYAxisDragger extends CuttingPlaneDragger {
938
1193
  constructor(viewer) {
939
- super(viewer, new Vector3(0, -1, 0));
1194
+ super(viewer);
1195
+ this.addPlaneY();
940
1196
  }
941
1197
  }
942
1198
 
943
1199
  class CuttingPlaneZAxisDragger extends CuttingPlaneDragger {
944
1200
  constructor(viewer) {
945
- super(viewer, new Vector3(0, 0, -1));
1201
+ super(viewer);
1202
+ this.addPlaneZ();
946
1203
  }
947
1204
  }
948
1205
 
@@ -1019,120 +1276,6 @@ function formatDistance(distance, units, precision = 2) {
1019
1276
  }
1020
1277
  }
1021
1278
 
1022
- const DESKTOP_SNAP_DISTANCE = 10;
1023
- const MOBILE_SNAP_DISTANCE = 50;
1024
- const _vertex = new Vector3();
1025
- const _start = new Vector3();
1026
- const _end = new Vector3();
1027
- const _line = new Line3();
1028
- const _center = new Vector3();
1029
- const _projection = new Vector3();
1030
- class Snapper {
1031
- constructor(camera, renderer, canvas) {
1032
- this.camera = camera;
1033
- this.renderer = renderer;
1034
- this.canvas = canvas;
1035
- this.threshold = 0.0001;
1036
- this.raycaster = new Raycaster();
1037
- this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
1038
- this.edgesCache = new WeakMap();
1039
- }
1040
- isMobile() {
1041
- if (typeof navigator === "undefined")
1042
- return false;
1043
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
1044
- }
1045
- getMousePosition(event, target) {
1046
- return target.set(event.clientX, event.clientY);
1047
- }
1048
- getPointerIntersects(mouse, objects) {
1049
- const rect = this.canvas.getBoundingClientRect();
1050
- const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
1051
- const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
1052
- const coords = new Vector2(x, y);
1053
- this.raycaster.setFromCamera(coords, this.camera);
1054
- this.raycaster.params = {
1055
- Mesh: {},
1056
- Line: { threshold: this.threshold },
1057
- Line2: { threshold: this.threshold },
1058
- LOD: {},
1059
- Points: { threshold: this.threshold },
1060
- Sprite: {},
1061
- };
1062
- let intersects = this.raycaster.intersectObjects(objects, false);
1063
- (this.renderer.clippingPlanes || []).forEach((plane) => {
1064
- intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
1065
- });
1066
- return intersects;
1067
- }
1068
- getDetectRadius(point) {
1069
- const camera = this.camera;
1070
- if (camera.isOrthographicCamera) {
1071
- const fieldHeight = (camera.top - camera.bottom) / camera.zoom;
1072
- const canvasHeight = this.canvas.height;
1073
- const worldUnitsPerPixel = fieldHeight / canvasHeight;
1074
- return this.detectRadiusInPixels * worldUnitsPerPixel;
1075
- }
1076
- if (camera.isPerspectiveCamera) {
1077
- const distance = camera.position.distanceTo(point);
1078
- const fieldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
1079
- const canvasHeight = this.canvas.height;
1080
- const worldUnitsPerPixel = fieldHeight / canvasHeight;
1081
- return this.detectRadiusInPixels * worldUnitsPerPixel;
1082
- }
1083
- return 0.1;
1084
- }
1085
- getSnapPoint(mouse, objects) {
1086
- const intersections = this.getPointerIntersects(mouse, objects);
1087
- if (intersections.length === 0)
1088
- return undefined;
1089
- const object = intersections[0].object;
1090
- const intersectionPoint = intersections[0].point;
1091
- const localPoint = object.worldToLocal(intersectionPoint.clone());
1092
- let snapPoint;
1093
- let snapDistance = this.getDetectRadius(intersectionPoint);
1094
- const geometry = object.geometry;
1095
- const positions = geometry.attributes.position.array;
1096
- for (let i = 0; i < positions.length; i += 3) {
1097
- _vertex.set(positions[i], positions[i + 1], positions[i + 2]);
1098
- const distance = _vertex.distanceTo(localPoint);
1099
- if (distance < snapDistance) {
1100
- snapDistance = distance;
1101
- snapPoint = _vertex.clone();
1102
- }
1103
- }
1104
- if (snapPoint)
1105
- return object.localToWorld(snapPoint);
1106
- let edges = this.edgesCache.get(geometry);
1107
- if (!edges) {
1108
- edges = new EdgesGeometry(geometry);
1109
- this.edgesCache.set(geometry, edges);
1110
- }
1111
- const edgePositions = edges.attributes.position.array;
1112
- for (let i = 0; i < edgePositions.length; i += 6) {
1113
- _start.set(edgePositions[i], edgePositions[i + 1], edgePositions[i + 2]);
1114
- _end.set(edgePositions[i + 3], edgePositions[i + 4], edgePositions[i + 5]);
1115
- _line.set(_start, _end);
1116
- _line.getCenter(_center);
1117
- const centerDistance = _center.distanceTo(localPoint);
1118
- if (centerDistance < snapDistance) {
1119
- snapDistance = centerDistance;
1120
- snapPoint = _center.clone();
1121
- continue;
1122
- }
1123
- _line.closestPointToPoint(localPoint, true, _projection);
1124
- const lineDistance = _projection.distanceTo(localPoint);
1125
- if (lineDistance < snapDistance) {
1126
- snapDistance = lineDistance;
1127
- snapPoint = _projection.clone();
1128
- }
1129
- }
1130
- if (snapPoint)
1131
- return object.localToWorld(snapPoint);
1132
- return intersectionPoint.clone();
1133
- }
1134
- }
1135
-
1136
1279
  const _downPoint = new Vector2();
1137
1280
  class MeasureLineDragger extends OrbitDragger {
1138
1281
  constructor(viewer) {
@@ -1544,6 +1687,12 @@ class WalkControls extends Controls {
1544
1687
  this.movementSpeed = 0.1;
1545
1688
  this.multiplier = 3;
1546
1689
  this.groundFollowingSkippedFrames = 0;
1690
+ this.GROUND_BOX_HALF_SIZE = 20;
1691
+ this.GROUND_BOX_REFRESH_THRESHOLD = 0.3;
1692
+ this._groundObjectBoxes = new Map();
1693
+ this._activeGroundObjects = [];
1694
+ this._groundBox = new Box3();
1695
+ this._groundBoxCenter = new Vector3();
1547
1696
  this.moveWheel = 0;
1548
1697
  this.mouseDragOn = false;
1549
1698
  this._up = new Vector3();
@@ -1610,11 +1759,20 @@ class WalkControls extends Controls {
1610
1759
  }
1611
1760
  };
1612
1761
  this.onKeyUp = (event) => {
1613
- if (this.moveKeys.delete(event.code))
1762
+ if (this.moveKeys.delete(event.code)) {
1763
+ if (this.moveKeys.size === 0) {
1764
+ this._rebuildGroundBox(this.object.position);
1765
+ }
1614
1766
  this.update();
1767
+ }
1615
1768
  };
1616
1769
  this.camera = camera;
1617
1770
  this.groundObjects = groundObjects;
1771
+ for (const obj of groundObjects) {
1772
+ this._groundObjectBoxes.set(obj, new Box3().setFromObject(obj));
1773
+ }
1774
+ const pos = this.object.position;
1775
+ this._rebuildGroundBox(pos);
1618
1776
  this.raycaster = new Raycaster();
1619
1777
  this.raycaster.near = 0;
1620
1778
  this.raycaster.far = this.EYE_HEIGHT + this.FAILING_DISTANCE;
@@ -1649,10 +1807,29 @@ class WalkControls extends Controls {
1649
1807
  window.removeEventListener("keyup", this.onKeyUp);
1650
1808
  super.dispose();
1651
1809
  }
1810
+ _rebuildGroundBox(center) {
1811
+ const h = this.GROUND_BOX_HALF_SIZE;
1812
+ this._groundBoxCenter.copy(center);
1813
+ 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));
1814
+ this._activeGroundObjects = this.groundObjects.filter((obj) => {
1815
+ const objectBox = this._groundObjectBoxes.get(obj);
1816
+ return objectBox !== undefined && this._groundBox.intersectsBox(objectBox);
1817
+ });
1818
+ }
1819
+ _needsGroundBoxRebuild(pos) {
1820
+ if (this._activeGroundObjects.length === 0 && this.groundObjects.length > 0)
1821
+ return true;
1822
+ const threshold = this.GROUND_BOX_HALF_SIZE * this.GROUND_BOX_REFRESH_THRESHOLD;
1823
+ return (Math.abs(pos.x - this._groundBoxCenter.x) > threshold || Math.abs(pos.z - this._groundBoxCenter.z) > threshold);
1824
+ }
1652
1825
  updateGroundFollowing() {
1826
+ const pos = this.object.position;
1827
+ if (this._needsGroundBoxRebuild(pos)) {
1828
+ this._rebuildGroundBox(pos);
1829
+ }
1653
1830
  this._up.copy(this.camera.up).negate();
1654
- this.raycaster.set(this.object.position, this._up);
1655
- const intersects = this.raycaster.intersectObjects(this.groundObjects, false);
1831
+ this.raycaster.set(pos, this._up);
1832
+ const intersects = this.raycaster.intersectObjects(this._activeGroundObjects, false);
1656
1833
  if (intersects.length > 0) {
1657
1834
  const groundY = intersects[0].point.y;
1658
1835
  const targetY = groundY + this.EYE_HEIGHT;
@@ -2204,6 +2381,7 @@ draggers.registerDragger("Pan", (viewer) => new PanDragger(viewer));
2204
2381
  draggers.registerDragger("Orbit", (viewer) => new OrbitDragger(viewer));
2205
2382
  draggers.registerDragger("Zoom", (viewer) => new ZoomDragger(viewer));
2206
2383
  draggers.registerDragger("MeasureLine", (viewer) => new MeasureLineDragger(viewer));
2384
+ draggers.registerDragger("CuttingPlane", (viewer) => new CuttingPlaneDragger(viewer));
2207
2385
  draggers.registerDragger("CuttingPlaneXAxis", (viewer) => new CuttingPlaneXAxisDragger(viewer));
2208
2386
  draggers.registerDragger("CuttingPlaneYAxis", (viewer) => new CuttingPlaneYAxisDragger(viewer));
2209
2387
  draggers.registerDragger("CuttingPlaneZAxis", (viewer) => new CuttingPlaneZAxisDragger(viewer));
@@ -3099,7 +3277,7 @@ class SelectionComponent {
3099
3277
  this.getMousePosition(event, this.downPosition);
3100
3278
  };
3101
3279
  this.onPointerUp = (event) => {
3102
- if (!event.isPrimary)
3280
+ if (!event.isPrimary || event.button !== 0)
3103
3281
  return;
3104
3282
  const upPosition = this.getMousePosition(event, new Vector2());
3105
3283
  if (upPosition.distanceTo(this.downPosition) !== 0)
@@ -3628,19 +3806,19 @@ class ModelImpl {
3628
3806
  return this;
3629
3807
  }
3630
3808
  explode(scale = 0, coeff = 4) {
3631
- const centers = new Map();
3632
- const getObjectCenter = (object, target) => {
3809
+ const centersCache = new Map();
3810
+ const calcObjectCenter = (object, target) => {
3633
3811
  const extents = new Box3().setFromObject(object);
3634
3812
  const handle = object.userData.handle;
3635
3813
  if (!handle)
3636
3814
  return extents.getCenter(target);
3637
- const center = centers.get(handle);
3815
+ const center = centersCache.get(handle);
3638
3816
  if (center)
3639
3817
  return target.copy(center);
3640
3818
  const objects = this.getObjectsByHandles(handle);
3641
3819
  objects.forEach((x) => extents.expandByObject(x));
3642
3820
  extents.getCenter(target);
3643
- centers.set(handle, target.clone());
3821
+ centersCache.set(handle, target.clone());
3644
3822
  return target;
3645
3823
  };
3646
3824
  function calcExplodeDepth(object, depth) {
@@ -3651,13 +3829,14 @@ class ModelImpl {
3651
3829
  result = objectDepth;
3652
3830
  });
3653
3831
  object.userData.originalPosition = object.position.clone();
3654
- object.userData.originalCenter = getObjectCenter(object, new Vector3());
3832
+ object.userData.originalCenter = calcObjectCenter(object, new Vector3());
3655
3833
  return result;
3656
3834
  }
3657
3835
  const explodeScale = scale / 100;
3658
3836
  const explodeRoot = this.scene;
3659
- if (!explodeRoot.userData.explodeDepth)
3837
+ if (!explodeRoot.userData.explodeDepth) {
3660
3838
  explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
3839
+ }
3661
3840
  const maxDepth = explodeRoot.userData.explodeDepth;
3662
3841
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
3663
3842
  const explodeDepth = 0 | scaledExplodeDepth;
@@ -3674,8 +3853,8 @@ class ModelImpl {
3674
3853
  objectScale *= currentSegmentFraction;
3675
3854
  const parentCenter = object.parent.userData.originalCenter;
3676
3855
  const objectCenter = object.userData.originalCenter;
3677
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3678
- object.position.add(objectOffset);
3856
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3857
+ object.position.add(localOffset);
3679
3858
  }
3680
3859
  object.children.forEach((x) => explodeObject(x, depth + 1));
3681
3860
  }
@@ -3767,67 +3946,73 @@ class DynamicModelImpl extends ModelImpl {
3767
3946
  return this;
3768
3947
  }
3769
3948
  explode(scale = 0, coeff = 4) {
3770
- const centers = new Map();
3949
+ const centersCache = new Map();
3771
3950
  const calcObjectCenter = (object, target) => {
3772
3951
  const extents = new Box3().setFromObject(object);
3773
3952
  const handle = object.userData.handle;
3774
3953
  if (!handle)
3775
3954
  return extents.getCenter(target);
3776
- const center = centers.get(handle);
3955
+ const center = centersCache.get(handle);
3777
3956
  if (center)
3778
3957
  return target.copy(center);
3779
3958
  const objects = this.getObjectsByHandles(handle);
3780
3959
  objects.forEach((x) => extents.expandByObject(x));
3781
3960
  extents.getCenter(target);
3782
- centers.set(handle, target.clone());
3961
+ centersCache.set(handle, target.clone());
3783
3962
  return target;
3784
3963
  };
3785
- function calcExplodeDepth(object, depth) {
3786
- let result = depth;
3787
- object.children
3788
- .filter((x) => !x.userData.isOptimized)
3789
- .forEach((x) => {
3790
- const objectDepth = calcExplodeDepth(x, depth + 1);
3791
- if (result < objectDepth)
3792
- result = objectDepth;
3793
- });
3964
+ const calcObjectDepth = (object) => {
3965
+ if (object.userData.depth !== undefined)
3966
+ return object.userData.depth;
3967
+ const parent = object.parent;
3968
+ const depth = parent && object !== explodeRoot ? calcObjectDepth(parent) + 1 : 0;
3969
+ object.userData.depth = depth;
3794
3970
  object.userData.originalPosition = object.position.clone();
3795
3971
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
3796
- return result;
3797
- }
3972
+ return depth;
3973
+ };
3798
3974
  const explodeScale = scale / 100;
3799
3975
  const explodeRoot = this.scene.children[0];
3800
- if (!explodeRoot.userData.explodeDepth)
3801
- explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
3976
+ if (!explodeRoot.userData.explodeDepth) {
3977
+ let maxDepth = 0;
3978
+ this.gltfLoader.originalObjects.forEach((object) => {
3979
+ const depth = calcObjectDepth(object);
3980
+ if (depth > maxDepth)
3981
+ maxDepth = depth;
3982
+ });
3983
+ explodeRoot.userData.explodeDepth = maxDepth;
3984
+ }
3802
3985
  const maxDepth = explodeRoot.userData.explodeDepth;
3803
3986
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
3804
3987
  const explodeDepth = 0 | scaledExplodeDepth;
3805
3988
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
3806
- const transformMap = new Map();
3807
- function explodeObject(object, depth) {
3808
- if (object.isCamera)
3809
- return;
3810
- if (object.userData.isHighlightWireframe)
3811
- return;
3812
- object.position.copy(object.userData.originalPosition);
3813
- if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
3989
+ const offsetCache = new Map();
3990
+ const calcObjectOffset = (object, target) => {
3991
+ if (offsetCache.has(object))
3992
+ return target.copy(offsetCache.get(object));
3993
+ const parent = object.parent;
3994
+ if (parent && object !== explodeRoot)
3995
+ calcObjectOffset(parent, target);
3996
+ const depth = object.userData.depth;
3997
+ if (depth > 0 && depth <= explodeDepth) {
3814
3998
  let objectScale = explodeScale * coeff;
3815
3999
  if (depth === explodeDepth)
3816
4000
  objectScale *= currentSegmentFraction;
3817
- const parentCenter = object.parent.userData.originalCenter;
4001
+ const parentCenter = parent.userData.originalCenter;
3818
4002
  const objectCenter = object.userData.originalCenter;
3819
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3820
- object.position.add(objectOffset);
3821
- const matrix = new Matrix4().makeTranslation(objectOffset.x, objectOffset.y, objectOffset.z);
3822
- transformMap.set(object, matrix);
4003
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
4004
+ target.add(localOffset);
3823
4005
  }
3824
- object.children
3825
- .filter((x) => !x.userData.isOptimized)
3826
- .forEach((x) => explodeObject(x, depth + 1));
3827
- }
3828
- explodeObject(explodeRoot, 0);
3829
- this.scene.updateMatrixWorld();
4006
+ offsetCache.set(object, target.clone());
4007
+ return target;
4008
+ };
4009
+ const transformMap = new Map();
4010
+ this.gltfLoader.originalObjects.forEach((object) => {
4011
+ const globalOffset = calcObjectOffset(object, new Vector3());
4012
+ transformMap.set(object, new Matrix4().makeTranslation(globalOffset));
4013
+ });
3830
4014
  this.gltfLoader.applyObjectTransforms(transformMap);
4015
+ this.scene.updateMatrixWorld();
3831
4016
  return this;
3832
4017
  }
3833
4018
  }
@@ -4404,6 +4589,8 @@ class DynamicGltfLoader {
4404
4589
  this.oldOptimizeObjects = new Set();
4405
4590
  this.objectTransforms = new Map();
4406
4591
  this.transformedGeometries = new Map();
4592
+ this.syncTransformsToOriginalObjects = true;
4593
+ this._originalObjectMatrices = new Map();
4407
4594
  this.activeChunkLoads = 0;
4408
4595
  this.chunkQueue = [];
4409
4596
  this.objectIdToIndex = new Map();
@@ -4413,6 +4600,81 @@ class DynamicGltfLoader {
4413
4600
  this.mergedObjectMap = new Map();
4414
4601
  this.mergedGeometryVisibility = new Map();
4415
4602
  this._webglInfoCache = null;
4603
+ this.transformTextureSize = 1024;
4604
+ this.transformTexture = this.createDummyTexture();
4605
+ this.transformData = null;
4606
+ this.identityTransformData = null;
4607
+ this.visibilityMaterials = new Set();
4608
+ }
4609
+ createDummyTexture() {
4610
+ const data = new Float32Array(16);
4611
+ const identity = new Matrix4();
4612
+ identity.toArray(data);
4613
+ const dummyData = new Float32Array(16);
4614
+ identity.toArray(dummyData);
4615
+ const dummyTexture = new DataTexture(dummyData, 4, 1, RGBAFormat, FloatType);
4616
+ dummyTexture.minFilter = NearestFilter;
4617
+ dummyTexture.magFilter = NearestFilter;
4618
+ dummyTexture.needsUpdate = true;
4619
+ return dummyTexture;
4620
+ }
4621
+ initTransformTexture() {
4622
+ if (this.transformTexture) {
4623
+ this.transformTexture.dispose();
4624
+ }
4625
+ const maxInstanceCount = this.maxObjectId + 1;
4626
+ let size = Math.sqrt(maxInstanceCount * 4);
4627
+ size = Math.ceil(size / 4) * 4;
4628
+ size = Math.max(size, 4);
4629
+ this.transformTextureSize = size;
4630
+ const arraySize = size * size * 4;
4631
+ this.transformData = new Float32Array(arraySize);
4632
+ this.identityTransformData = new Float32Array(arraySize);
4633
+ for (let i = 0; i <= this.maxObjectId; i++) {
4634
+ const base = i * 16;
4635
+ if (base + 15 < arraySize) {
4636
+ this.identityTransformData[base + 0] = 1;
4637
+ this.identityTransformData[base + 5] = 1;
4638
+ this.identityTransformData[base + 10] = 1;
4639
+ this.identityTransformData[base + 15] = 1;
4640
+ }
4641
+ }
4642
+ this._resetTransformData(false);
4643
+ this.transformTexture = new DataTexture(this.transformData, size, size, RGBAFormat, FloatType);
4644
+ this.transformTexture.needsUpdate = true;
4645
+ this.transformTexture.generateMipmaps = false;
4646
+ console.log(`Initialized transform texture: ${size}x${size} for ${maxInstanceCount} objects`);
4647
+ this.updateMaterialUniforms();
4648
+ this.visibilityMaterials.forEach((material) => {
4649
+ material.needsUpdate = true;
4650
+ });
4651
+ }
4652
+ _resetTransformData(updateTexture = true) {
4653
+ if (!this.transformData || !this.identityTransformData) return;
4654
+ this.transformData.set(this.identityTransformData);
4655
+ if (updateTexture) {
4656
+ this.updateTransformTexture();
4657
+ }
4658
+ }
4659
+ updateMaterialUniforms() {
4660
+ if (
4661
+ this._lastTransformTexture === this.transformTexture &&
4662
+ this._lastTransformTextureSize === this.transformTextureSize
4663
+ ) {
4664
+ return;
4665
+ }
4666
+ this._lastTransformTexture = this.transformTexture;
4667
+ this._lastTransformTextureSize = this.transformTextureSize;
4668
+ this.visibilityMaterials.forEach((material) => {
4669
+ if (material.userData && material.userData.visibilityUniforms) {
4670
+ material.userData.visibilityUniforms.transformTexture.value = this.transformTexture;
4671
+ material.userData.visibilityUniforms.transformTextureSize.value = this.transformTextureSize;
4672
+ }
4673
+ });
4674
+ }
4675
+ updateTransformTexture() {
4676
+ if (!this.transformTexture) return;
4677
+ this.transformTexture.needsUpdate = true;
4416
4678
  }
4417
4679
  setVisibleEdges(visible) {
4418
4680
  this.visibleEdges = visible;
@@ -5302,36 +5564,82 @@ class DynamicGltfLoader {
5302
5564
  }
5303
5565
  }
5304
5566
  createVisibilityMaterial(material) {
5567
+ this.visibilityMaterials.add(material);
5568
+ const uniforms = {
5569
+ transformTexture: { value: this.transformTexture },
5570
+ transformTextureSize: { value: this.transformTextureSize },
5571
+ };
5572
+ material.userData.visibilityUniforms = uniforms;
5305
5573
  material.onBeforeCompile = (shader) => {
5574
+ shader.uniforms.transformTexture = uniforms.transformTexture;
5575
+ shader.uniforms.transformTextureSize = uniforms.transformTextureSize;
5306
5576
  shader.vertexShader = shader.vertexShader.replace(
5307
5577
  "#include <common>",
5308
5578
  `
5309
5579
  #include <common>
5580
+
5310
5581
  attribute float visibility;
5582
+ attribute float objectId;
5311
5583
  varying float vVisibility;
5584
+ uniform highp sampler2D transformTexture;
5585
+ uniform float transformTextureSize;
5586
+
5587
+ mat4 getTransformMatrix(float instanceId) {
5588
+ int size = int(transformTextureSize);
5589
+ int index = int(instanceId) * 4;
5590
+
5591
+ int x0 = index % size;
5592
+ int y0 = index / size;
5593
+
5594
+ vec4 row0 = texelFetch(transformTexture, ivec2(x0, y0), 0);
5595
+ vec4 row1 = texelFetch(transformTexture, ivec2(x0 + 1, y0), 0);
5596
+ vec4 row2 = texelFetch(transformTexture, ivec2(x0 + 2, y0), 0);
5597
+ vec4 row3 = texelFetch(transformTexture, ivec2(x0 + 3, y0), 0);
5598
+
5599
+ return mat4(row0, row1, row2, row3);
5600
+ }
5312
5601
  `
5313
5602
  );
5314
- shader.fragmentShader = shader.fragmentShader.replace(
5315
- "#include <common>",
5603
+ shader.vertexShader = shader.vertexShader.replace(
5604
+ "void main() {",
5316
5605
  `
5317
- #include <common>
5318
- varying float vVisibility;
5606
+ void main() {
5607
+ mat4 batchingMatrix = getTransformMatrix(objectId);
5608
+ vVisibility = visibility;
5319
5609
  `
5320
5610
  );
5611
+ if (shader.vertexShader.includes("#include <beginnormal_vertex>")) {
5612
+ shader.vertexShader = shader.vertexShader.replace(
5613
+ "#include <beginnormal_vertex>",
5614
+ `
5615
+ vec3 objectNormal = vec3( normal );
5616
+ mat3 bm = mat3( batchingMatrix );
5617
+ objectNormal = bm * objectNormal;
5618
+ `
5619
+ );
5620
+ }
5321
5621
  shader.vertexShader = shader.vertexShader.replace(
5322
- "void main() {",
5622
+ "#include <begin_vertex>",
5323
5623
  `
5324
- void main() {
5325
- vVisibility = visibility;
5624
+ vec3 transformed = vec3( position );
5625
+ transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
5326
5626
  `
5327
5627
  );
5328
- shader.fragmentShader = shader.fragmentShader.replace(
5329
- "void main() {",
5628
+ shader.fragmentShader = shader.fragmentShader
5629
+ .replace(
5630
+ "#include <common>",
5631
+ `
5632
+ #include <common>
5633
+ varying float vVisibility;
5330
5634
  `
5635
+ )
5636
+ .replace(
5637
+ "void main() {",
5638
+ `
5331
5639
  void main() {
5332
5640
  if (vVisibility < 0.5) discard;
5333
5641
  `
5334
- );
5642
+ );
5335
5643
  };
5336
5644
  material.needsUpdate = true;
5337
5645
  return material;
@@ -5437,6 +5745,7 @@ class DynamicGltfLoader {
5437
5745
  this.isolatedObjects = [];
5438
5746
  this.objectTransforms.clear();
5439
5747
  this.transformedGeometries.clear();
5748
+ this._originalObjectMatrices.clear();
5440
5749
  this.totalLoadedObjects = 0;
5441
5750
  this.currentMemoryUsage = 0;
5442
5751
  this.pendingMemoryUsage = 0;
@@ -5446,6 +5755,8 @@ class DynamicGltfLoader {
5446
5755
  this.objectIdToIndex.clear();
5447
5756
  this.maxObjectId = 0;
5448
5757
  this.objectVisibility = new Float32Array();
5758
+ this.meshToNodeMap = null;
5759
+ this.visibilityMaterials.clear();
5449
5760
  }
5450
5761
  setStructureTransform(structureId, matrix) {
5451
5762
  const rootGroup = this.structureRoots.get(structureId);
@@ -5561,12 +5872,15 @@ class DynamicGltfLoader {
5561
5872
  });
5562
5873
  this.originalObjects.clear();
5563
5874
  this.originalObjectsToSelection.clear();
5875
+ this.objectIdToIndex.clear();
5876
+ this.maxObjectId = 0;
5564
5877
  const structureGroups = new Map();
5565
5878
  this.dispatchEvent("optimizationprogress", {
5566
5879
  phase: "collecting",
5567
5880
  progress: 5,
5568
5881
  message: "Collecting scene objects...",
5569
5882
  });
5883
+ let totalObjectsToMerge = 0;
5570
5884
  this.scene.traverse((object) => {
5571
5885
  if (object.userData.structureId) {
5572
5886
  const structureId = object.userData.structureId;
@@ -5584,17 +5898,32 @@ class DynamicGltfLoader {
5584
5898
  });
5585
5899
  }
5586
5900
  const group = structureGroups.get(structureId);
5901
+ let added = false;
5587
5902
  if (object instanceof Mesh) {
5588
5903
  this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
5904
+ added = true;
5589
5905
  } else if (object instanceof LineSegments) {
5590
5906
  this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
5907
+ added = true;
5591
5908
  } else if (object instanceof Line) {
5592
5909
  this.addToMaterialGroup(object, group.mapLines, group.lines);
5910
+ added = true;
5593
5911
  } else if (object instanceof Points) {
5594
5912
  this.addToMaterialGroup(object, group.mapPoints, group.points);
5913
+ added = true;
5914
+ }
5915
+ if (added) {
5916
+ totalObjectsToMerge++;
5595
5917
  }
5596
5918
  }
5597
5919
  });
5920
+ if (totalObjectsToMerge > 0) {
5921
+ console.log(`Pre-allocating transform texture for ${totalObjectsToMerge} objects`);
5922
+ this.maxObjectId = totalObjectsToMerge;
5923
+ this.initTransformTexture();
5924
+ this.initializeObjectVisibility();
5925
+ this.maxObjectId = 0;
5926
+ }
5598
5927
  let processedGroups = 0;
5599
5928
  const totalGroups = structureGroups.size;
5600
5929
  this.dispatchEvent("optimizationprogress", {
@@ -5639,7 +5968,6 @@ class DynamicGltfLoader {
5639
5968
  this.originalObjectsToSelection.add(obj);
5640
5969
  }
5641
5970
  });
5642
- this.initializeObjectVisibility();
5643
5971
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5644
5972
  this.dispatchEvent("optimizationprogress", {
5645
5973
  phase: "complete",
@@ -5708,6 +6036,7 @@ class DynamicGltfLoader {
5708
6036
  }
5709
6037
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5710
6038
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
6039
+ mergedMesh.frustumCulled = false;
5711
6040
  mergedMesh.userData.isOptimized = true;
5712
6041
  rootGroup.add(mergedMesh);
5713
6042
  this.mergedMesh.add(mergedMesh);
@@ -5824,6 +6153,7 @@ class DynamicGltfLoader {
5824
6153
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5825
6154
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5826
6155
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
6156
+ mergedLine.frustumCulled = false;
5827
6157
  mergedLine.userData.isEdge = isEdge;
5828
6158
  mergedLine.userData.isOptimized = true;
5829
6159
  const mergedObjects = [mergedLine];
@@ -5912,6 +6242,7 @@ class DynamicGltfLoader {
5912
6242
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5913
6243
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5914
6244
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
6245
+ mergedLine.frustumCulled = false;
5915
6246
  mergedLine.userData.isEdge = isEdge;
5916
6247
  mergedLine.userData.isOptimized = true;
5917
6248
  if (this.useVAO) {
@@ -5981,7 +6312,27 @@ class DynamicGltfLoader {
5981
6312
  const mergedObjects = [];
5982
6313
  if (geometries.length > 0) {
5983
6314
  const mergedGeometry = mergeGeometries(geometries, false);
5984
- const mergedPoints = new Points(mergedGeometry, group.material);
6315
+ const totalVertices = mergedGeometry.attributes.position.count;
6316
+ const objectIds = new Float32Array(totalVertices);
6317
+ let vertexOffset = 0;
6318
+ group.objects.forEach((points) => {
6319
+ const handle = points.userData.handle;
6320
+ if (!this.objectIdToIndex.has(handle)) {
6321
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
6322
+ }
6323
+ const objectId = this.objectIdToIndex.get(handle);
6324
+ const count = points.geometry.attributes.position.count;
6325
+ for (let i = 0; i < count; i++) {
6326
+ objectIds[vertexOffset++] = objectId;
6327
+ }
6328
+ });
6329
+ mergedGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
6330
+ const visibilityArray = new Float32Array(totalVertices);
6331
+ visibilityArray.fill(1.0);
6332
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
6333
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
6334
+ const mergedPoints = new Points(mergedGeometry, visibilityMaterial);
6335
+ mergedPoints.frustumCulled = false;
5985
6336
  mergedPoints.userData.isOptimized = true;
5986
6337
  if (this.useVAO) {
5987
6338
  this.createVAO(mergedPoints);
@@ -6050,13 +6401,33 @@ class DynamicGltfLoader {
6050
6401
  geometriesWithIndex.push(clonedGeometry);
6051
6402
  });
6052
6403
  const finalGeometry = mergeGeometries(geometriesWithIndex, false);
6404
+ const totalVertices = finalGeometry.attributes.position.count;
6405
+ const objectIds = new Float32Array(totalVertices);
6406
+ let vertexOffset = 0;
6407
+ lineSegmentsArray.forEach((segment) => {
6408
+ const handle = segment.userData.handle;
6409
+ if (!this.objectIdToIndex.has(handle)) {
6410
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
6411
+ }
6412
+ const objectId = this.objectIdToIndex.get(handle);
6413
+ const count = segment.geometry.attributes.position.count;
6414
+ for (let i = 0; i < count; i++) {
6415
+ objectIds[vertexOffset++] = objectId;
6416
+ }
6417
+ });
6418
+ finalGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
6419
+ const visibilityArray = new Float32Array(totalVertices);
6420
+ visibilityArray.fill(1.0);
6421
+ finalGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
6053
6422
  const material = new LineBasicMaterial({
6054
6423
  vertexColors: true,
6055
6424
  });
6425
+ const visibilityMaterial = this.createVisibilityMaterial(material);
6056
6426
  if (this.useVAO) {
6057
6427
  this.createVAO(finalGeometry);
6058
6428
  }
6059
- const mergedLine = new LineSegments(finalGeometry, material);
6429
+ const mergedLine = new LineSegments(finalGeometry, visibilityMaterial);
6430
+ mergedLine.frustumCulled = false;
6060
6431
  mergedLine.userData.structureId = structureId;
6061
6432
  mergedLine.userData.isOptimized = true;
6062
6433
  rootGroup.add(mergedLine);
@@ -6175,18 +6546,91 @@ class DynamicGltfLoader {
6175
6546
  console.warn("No merged objects to transform");
6176
6547
  return;
6177
6548
  }
6178
- this.objectTransforms = new Map(objectTransformMap);
6179
- for (const mesh of this.mergedMesh) {
6180
- this._applyTransformToMergedObject(mesh);
6549
+ if (!this.transformData) {
6550
+ console.warn("Transform texture not initialized");
6551
+ return;
6181
6552
  }
6182
- for (const line of this.mergedLines) {
6183
- this._applyTransformToMergedObject(line);
6553
+ this.objectTransforms = objectTransformMap;
6554
+ this._resetTransformData(false);
6555
+ const transformData = this.transformData;
6556
+ const objectIdToIndex = this.objectIdToIndex;
6557
+ let textureNeedsUpdate = false;
6558
+ if (objectTransformMap instanceof Map) {
6559
+ for (const [object, matrix] of objectTransformMap.entries()) {
6560
+ const userData = object.userData;
6561
+ if (!userData) continue;
6562
+ const handle = userData.handle;
6563
+ if (handle === undefined) continue;
6564
+ const objectId = objectIdToIndex.get(handle);
6565
+ if (objectId !== undefined) {
6566
+ transformData.set(matrix.elements, objectId * 16);
6567
+ textureNeedsUpdate = true;
6568
+ }
6569
+ }
6570
+ } else {
6571
+ const len = objectTransformMap.length;
6572
+ for (let i = 0; i < len; i++) {
6573
+ const pair = objectTransformMap[i];
6574
+ const userData = pair[0].userData;
6575
+ if (!userData) continue;
6576
+ const handle = userData.handle;
6577
+ if (handle === undefined) continue;
6578
+ const objectId = objectIdToIndex.get(handle);
6579
+ if (objectId !== undefined) {
6580
+ transformData.set(pair[1].elements, objectId * 16);
6581
+ textureNeedsUpdate = true;
6582
+ }
6583
+ }
6584
+ }
6585
+ if (textureNeedsUpdate) {
6586
+ this.updateTransformTexture();
6587
+ if (
6588
+ this._lastTransformTexture !== this.transformTexture ||
6589
+ this._lastTransformTextureSize !== this.transformTextureSize
6590
+ ) {
6591
+ this.updateMaterialUniforms();
6592
+ }
6184
6593
  }
6185
- for (const lineSegment of this.mergedLineSegments) {
6186
- this._applyTransformToMergedObject(lineSegment);
6594
+ if (this.syncTransformsToOriginalObjects) {
6595
+ this._syncOriginalObjectTransforms(objectTransformMap);
6187
6596
  }
6188
- for (const point of this.mergedPoints) {
6189
- this._applyTransformToMergedObject(point);
6597
+ }
6598
+ _syncOriginalObjectTransforms(objectTransformMap) {
6599
+ for (const [obj, savedPos] of this._originalObjectMatrices) {
6600
+ obj.position.copy(savedPos);
6601
+ if (obj.userData.highlight) {
6602
+ obj.userData.highlight.position.copy(savedPos);
6603
+ }
6604
+ }
6605
+ this._originalObjectMatrices.clear();
6606
+ const _offset = new Vector3();
6607
+ const _parentInverse = new Matrix4();
6608
+ if (objectTransformMap instanceof Map) {
6609
+ for (const [object, matrix] of objectTransformMap.entries()) {
6610
+ if (!object.userData?.handle) continue;
6611
+ if (!this._originalObjectMatrices.has(object)) {
6612
+ this._originalObjectMatrices.set(object, object.position.clone());
6613
+ }
6614
+ _offset.setFromMatrixPosition(matrix);
6615
+ if (object.userData.structureId) {
6616
+ const rootGroup = this.structureRoots.get(object.userData.structureId);
6617
+ if (rootGroup && object.parent && object.parent !== rootGroup) {
6618
+ const origin = new Vector3(0, 0, 0);
6619
+ origin.applyMatrix4(rootGroup.matrixWorld);
6620
+ _offset.applyMatrix4(rootGroup.matrixWorld);
6621
+ _offset.sub(origin);
6622
+ const parentOrigin = new Vector3(0, 0, 0);
6623
+ _parentInverse.copy(object.parent.matrixWorld).invert();
6624
+ parentOrigin.applyMatrix4(_parentInverse);
6625
+ _offset.applyMatrix4(_parentInverse);
6626
+ _offset.sub(parentOrigin);
6627
+ }
6628
+ }
6629
+ object.position.add(_offset);
6630
+ if (object.userData.highlight) {
6631
+ object.userData.highlight.position.copy(object.position);
6632
+ }
6633
+ }
6190
6634
  }
6191
6635
  }
6192
6636
  createExplodeTransforms(objects = null, explodeCenter = null, explodeFactor = 1.5) {
@@ -6203,21 +6647,66 @@ class DynamicGltfLoader {
6203
6647
  ? objects
6204
6648
  : Array.from(objects)
6205
6649
  : Array.from(this.originalObjects);
6650
+ const structureInverseMatrices = new Map();
6651
+ if (!this.meshToNodeMap) {
6652
+ this.meshToNodeMap = new Map();
6653
+ for (const node of this.nodes.values()) {
6654
+ if (node.object) {
6655
+ this.meshToNodeMap.set(node.object, node);
6656
+ }
6657
+ }
6658
+ }
6206
6659
  for (const obj of objectsArray) {
6207
6660
  if (!obj.geometry || !obj.geometry.attributes.position) continue;
6208
- const boundingBox = new Box3().setFromBufferAttribute(obj.geometry.attributes.position);
6209
- if (obj.matrixWorld) {
6210
- boundingBox.applyMatrix4(obj.matrixWorld);
6211
- }
6212
- if (boundingBox.isEmpty()) continue;
6213
- const objectCenter = new Vector3();
6214
- boundingBox.getCenter(objectCenter);
6215
- const direction = objectCenter.clone().sub(explodeCenter);
6216
- const distance = direction.length();
6661
+ if (!obj.userData.explodeVector) {
6662
+ let center = null;
6663
+ const node = this.meshToNodeMap.get(obj);
6664
+ if (node && node.geometryExtents) {
6665
+ const box = node.geometryExtents.clone();
6666
+ box.applyMatrix4(obj.matrixWorld);
6667
+ center = new Vector3();
6668
+ box.getCenter(center);
6669
+ }
6670
+ if (!center) {
6671
+ if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
6672
+ const box = obj.geometry.boundingBox.clone();
6673
+ box.applyMatrix4(obj.matrixWorld);
6674
+ center = new Vector3();
6675
+ box.getCenter(center);
6676
+ }
6677
+ const explodeVector = center.sub(explodeCenter);
6678
+ obj.userData.explodeVector = explodeVector;
6679
+ }
6680
+ const explodeVector = obj.userData.explodeVector;
6681
+ const distance = explodeVector.length();
6217
6682
  if (distance > 0) {
6218
- direction.normalize();
6219
- const offset = direction.multiplyScalar(distance * (explodeFactor - 1.0));
6220
- const matrix = new Matrix4().makeTranslation(offset.x, offset.y, offset.z);
6683
+ const offset = explodeVector.clone().multiplyScalar(explodeFactor - 1.0);
6684
+ const localOffset = offset.clone();
6685
+ if (obj.userData.structureId) {
6686
+ const structureId = obj.userData.structureId;
6687
+ let inverseMatrix = structureInverseMatrices.get(structureId);
6688
+ if (!inverseMatrix) {
6689
+ const rootGroup = this.structureRoots.get(structureId);
6690
+ if (rootGroup) {
6691
+ if (!rootGroup.userData.inverseWorldMatrix) {
6692
+ rootGroup.userData.inverseWorldMatrix = new Matrix4().copy(rootGroup.matrixWorld).invert();
6693
+ }
6694
+ inverseMatrix = rootGroup.userData.inverseWorldMatrix;
6695
+ structureInverseMatrices.set(structureId, inverseMatrix);
6696
+ }
6697
+ }
6698
+ if (inverseMatrix) {
6699
+ const zero = new Vector3(0, 0, 0).applyMatrix4(inverseMatrix);
6700
+ const vec = offset.clone().applyMatrix4(inverseMatrix).sub(zero);
6701
+ localOffset.copy(vec);
6702
+ }
6703
+ }
6704
+ let matrix = obj.userData.explodeMatrix;
6705
+ if (!matrix) {
6706
+ matrix = new Matrix4();
6707
+ obj.userData.explodeMatrix = matrix;
6708
+ }
6709
+ matrix.makeTranslation(localOffset.x, localOffset.y, localOffset.z);
6221
6710
  transformMap.set(obj, matrix);
6222
6711
  }
6223
6712
  }
@@ -6225,115 +6714,19 @@ class DynamicGltfLoader {
6225
6714
  }
6226
6715
  clearTransforms() {
6227
6716
  this.objectTransforms.clear();
6228
- for (const mesh of this.mergedMesh) {
6229
- this._restoreOriginalGeometry(mesh);
6230
- }
6231
- for (const line of this.mergedLines) {
6232
- this._restoreOriginalGeometry(line);
6233
- }
6234
- for (const lineSegment of this.mergedLineSegments) {
6235
- this._restoreOriginalGeometry(lineSegment);
6236
- }
6237
- for (const point of this.mergedPoints) {
6238
- this._restoreOriginalGeometry(point);
6239
- }
6240
- }
6241
- clearHandleTransforms() {
6242
- this.clearTransforms();
6243
- }
6244
- _applyTransformToMergedObject(mergedObject) {
6245
- const objectData = this.mergedObjectMap.get(mergedObject.uuid);
6246
- if (!objectData || !objectData.objectMapping) return;
6247
- const geometry = mergedObject.geometry;
6248
- if (!geometry || !geometry.attributes.position) return;
6249
- const positionAttr = geometry.attributes.position;
6250
- const positions = positionAttr.array;
6251
- if (!this.transformedGeometries.has(mergedObject.uuid)) {
6252
- this.transformedGeometries.set(mergedObject.uuid, new Float32Array(positions));
6253
- }
6254
- const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
6255
- const tempVector = new Vector3();
6256
- for (const [originalMesh, mappingData] of objectData.objectMapping) {
6257
- const transform = this.objectTransforms.get(originalMesh);
6258
- if (!transform) {
6259
- const startIdx = mappingData.startVertexIndex * 3;
6260
- const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
6261
- for (let i = startIdx; i < endIdx; i++) {
6262
- positions[i] = originalPositions[i];
6263
- }
6264
- continue;
6265
- }
6266
- const startVertex = mappingData.startVertexIndex;
6267
- const vertexCount = mappingData.vertexCount;
6268
- for (let i = 0; i < vertexCount; i++) {
6269
- const idx = (startVertex + i) * 3;
6270
- tempVector.set(originalPositions[idx], originalPositions[idx + 1], originalPositions[idx + 2]);
6271
- tempVector.applyMatrix4(transform);
6272
- positions[idx] = tempVector.x;
6273
- positions[idx + 1] = tempVector.y;
6274
- positions[idx + 2] = tempVector.z;
6275
- }
6276
- }
6277
- if (geometry.attributes.normal) {
6278
- this._updateNormalsForTransform(geometry, objectData, originalPositions);
6279
- }
6280
- positionAttr.needsUpdate = true;
6281
- geometry.computeBoundingSphere();
6282
- geometry.computeBoundingBox();
6283
- }
6284
- _updateNormalsForTransform(geometry, objectData, originalPositions) {
6285
- const normalAttr = geometry.attributes.normal;
6286
- if (!normalAttr) return;
6287
- const normals = normalAttr.array;
6288
- const tempVector = new Vector3();
6289
- const normalMatrix = new Matrix4();
6290
- const normalsKey = `${geometry.uuid}_normals`;
6291
- if (!this.transformedGeometries.has(normalsKey)) {
6292
- this.transformedGeometries.set(normalsKey, new Float32Array(normals));
6293
- }
6294
- const originalNormals = this.transformedGeometries.get(normalsKey);
6295
- for (const [originalMesh, mappingData] of objectData.objectMapping) {
6296
- const transform = this.objectTransforms.get(originalMesh);
6297
- if (!transform) {
6298
- const startIdx = mappingData.startVertexIndex * 3;
6299
- const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
6300
- for (let i = startIdx; i < endIdx; i++) {
6301
- normals[i] = originalNormals[i];
6717
+ this._resetTransformData(true);
6718
+ if (this.syncTransformsToOriginalObjects) {
6719
+ for (const [obj, savedPos] of this._originalObjectMatrices) {
6720
+ obj.position.copy(savedPos);
6721
+ if (obj.userData.highlight) {
6722
+ obj.userData.highlight.position.copy(savedPos);
6302
6723
  }
6303
- continue;
6304
- }
6305
- normalMatrix.copy(transform).invert().transpose();
6306
- const startVertex = mappingData.startVertexIndex;
6307
- const vertexCount = mappingData.vertexCount;
6308
- for (let i = 0; i < vertexCount; i++) {
6309
- const idx = (startVertex + i) * 3;
6310
- tempVector.set(originalNormals[idx], originalNormals[idx + 1], originalNormals[idx + 2]);
6311
- tempVector.applyMatrix4(normalMatrix).normalize();
6312
- normals[idx] = tempVector.x;
6313
- normals[idx + 1] = tempVector.y;
6314
- normals[idx + 2] = tempVector.z;
6315
6724
  }
6725
+ this._originalObjectMatrices.clear();
6316
6726
  }
6317
- normalAttr.needsUpdate = true;
6318
6727
  }
6319
- _restoreOriginalGeometry(mergedObject) {
6320
- const geometry = mergedObject.geometry;
6321
- if (!geometry || !geometry.attributes.position) return;
6322
- const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
6323
- if (originalPositions) {
6324
- const positions = geometry.attributes.position.array;
6325
- positions.set(originalPositions);
6326
- geometry.attributes.position.needsUpdate = true;
6327
- }
6328
- const normalsKey = `${geometry.uuid}_normals`;
6329
- const originalNormals = this.transformedGeometries.get(normalsKey);
6330
- if (originalNormals && geometry.attributes.normal) {
6331
- const normals = geometry.attributes.normal.array;
6332
- normals.set(originalNormals);
6333
- geometry.attributes.normal.needsUpdate = true;
6334
- }
6335
- geometry.computeBoundingSphere();
6336
- geometry.computeBoundingBox();
6728
+ clearHandleTransforms() {
6729
+ this.clearTransforms();
6337
6730
  }
6338
6731
  syncHiddenObjects() {
6339
6732
  if (this.mergedObjectMap.size === 0) {