@inweb/viewer-three 27.2.2 → 27.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, 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';
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) {
@@ -1616,14 +1759,20 @@ class WalkControls extends Controls {
1616
1759
  }
1617
1760
  };
1618
1761
  this.onKeyUp = (event) => {
1619
- 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
+ }
1620
1766
  this.update();
1767
+ }
1621
1768
  };
1622
1769
  this.camera = camera;
1623
1770
  this.groundObjects = groundObjects;
1624
1771
  for (const obj of groundObjects) {
1625
1772
  this._groundObjectBoxes.set(obj, new Box3().setFromObject(obj));
1626
1773
  }
1774
+ const pos = this.object.position;
1775
+ this._rebuildGroundBox(pos);
1627
1776
  this.raycaster = new Raycaster();
1628
1777
  this.raycaster.near = 0;
1629
1778
  this.raycaster.far = this.EYE_HEIGHT + this.FAILING_DISTANCE;
@@ -2232,6 +2381,7 @@ draggers.registerDragger("Pan", (viewer) => new PanDragger(viewer));
2232
2381
  draggers.registerDragger("Orbit", (viewer) => new OrbitDragger(viewer));
2233
2382
  draggers.registerDragger("Zoom", (viewer) => new ZoomDragger(viewer));
2234
2383
  draggers.registerDragger("MeasureLine", (viewer) => new MeasureLineDragger(viewer));
2384
+ draggers.registerDragger("CuttingPlane", (viewer) => new CuttingPlaneDragger(viewer));
2235
2385
  draggers.registerDragger("CuttingPlaneXAxis", (viewer) => new CuttingPlaneXAxisDragger(viewer));
2236
2386
  draggers.registerDragger("CuttingPlaneYAxis", (viewer) => new CuttingPlaneYAxisDragger(viewer));
2237
2387
  draggers.registerDragger("CuttingPlaneZAxis", (viewer) => new CuttingPlaneZAxisDragger(viewer));
@@ -3127,7 +3277,7 @@ class SelectionComponent {
3127
3277
  this.getMousePosition(event, this.downPosition);
3128
3278
  };
3129
3279
  this.onPointerUp = (event) => {
3130
- if (!event.isPrimary)
3280
+ if (!event.isPrimary || event.button !== 0)
3131
3281
  return;
3132
3282
  const upPosition = this.getMousePosition(event, new Vector2());
3133
3283
  if (upPosition.distanceTo(this.downPosition) !== 0)
@@ -3656,19 +3806,19 @@ class ModelImpl {
3656
3806
  return this;
3657
3807
  }
3658
3808
  explode(scale = 0, coeff = 4) {
3659
- const centers = new Map();
3660
- const getObjectCenter = (object, target) => {
3809
+ const centersCache = new Map();
3810
+ const calcObjectCenter = (object, target) => {
3661
3811
  const extents = new Box3().setFromObject(object);
3662
3812
  const handle = object.userData.handle;
3663
3813
  if (!handle)
3664
3814
  return extents.getCenter(target);
3665
- const center = centers.get(handle);
3815
+ const center = centersCache.get(handle);
3666
3816
  if (center)
3667
3817
  return target.copy(center);
3668
3818
  const objects = this.getObjectsByHandles(handle);
3669
3819
  objects.forEach((x) => extents.expandByObject(x));
3670
3820
  extents.getCenter(target);
3671
- centers.set(handle, target.clone());
3821
+ centersCache.set(handle, target.clone());
3672
3822
  return target;
3673
3823
  };
3674
3824
  function calcExplodeDepth(object, depth) {
@@ -3679,13 +3829,14 @@ class ModelImpl {
3679
3829
  result = objectDepth;
3680
3830
  });
3681
3831
  object.userData.originalPosition = object.position.clone();
3682
- object.userData.originalCenter = getObjectCenter(object, new Vector3());
3832
+ object.userData.originalCenter = calcObjectCenter(object, new Vector3());
3683
3833
  return result;
3684
3834
  }
3685
3835
  const explodeScale = scale / 100;
3686
3836
  const explodeRoot = this.scene;
3687
- if (!explodeRoot.userData.explodeDepth)
3837
+ if (!explodeRoot.userData.explodeDepth) {
3688
3838
  explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
3839
+ }
3689
3840
  const maxDepth = explodeRoot.userData.explodeDepth;
3690
3841
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
3691
3842
  const explodeDepth = 0 | scaledExplodeDepth;
@@ -3702,8 +3853,8 @@ class ModelImpl {
3702
3853
  objectScale *= currentSegmentFraction;
3703
3854
  const parentCenter = object.parent.userData.originalCenter;
3704
3855
  const objectCenter = object.userData.originalCenter;
3705
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3706
- object.position.add(objectOffset);
3856
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3857
+ object.position.add(localOffset);
3707
3858
  }
3708
3859
  object.children.forEach((x) => explodeObject(x, depth + 1));
3709
3860
  }
@@ -3795,67 +3946,73 @@ class DynamicModelImpl extends ModelImpl {
3795
3946
  return this;
3796
3947
  }
3797
3948
  explode(scale = 0, coeff = 4) {
3798
- const centers = new Map();
3949
+ const centersCache = new Map();
3799
3950
  const calcObjectCenter = (object, target) => {
3800
3951
  const extents = new Box3().setFromObject(object);
3801
3952
  const handle = object.userData.handle;
3802
3953
  if (!handle)
3803
3954
  return extents.getCenter(target);
3804
- const center = centers.get(handle);
3955
+ const center = centersCache.get(handle);
3805
3956
  if (center)
3806
3957
  return target.copy(center);
3807
3958
  const objects = this.getObjectsByHandles(handle);
3808
3959
  objects.forEach((x) => extents.expandByObject(x));
3809
3960
  extents.getCenter(target);
3810
- centers.set(handle, target.clone());
3961
+ centersCache.set(handle, target.clone());
3811
3962
  return target;
3812
3963
  };
3813
- function calcExplodeDepth(object, depth) {
3814
- let result = depth;
3815
- object.children
3816
- .filter((x) => !x.userData.isOptimized)
3817
- .forEach((x) => {
3818
- const objectDepth = calcExplodeDepth(x, depth + 1);
3819
- if (result < objectDepth)
3820
- result = objectDepth;
3821
- });
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;
3822
3970
  object.userData.originalPosition = object.position.clone();
3823
3971
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
3824
- return result;
3825
- }
3972
+ return depth;
3973
+ };
3826
3974
  const explodeScale = scale / 100;
3827
3975
  const explodeRoot = this.scene.children[0];
3828
- if (!explodeRoot.userData.explodeDepth)
3829
- 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
+ }
3830
3985
  const maxDepth = explodeRoot.userData.explodeDepth;
3831
3986
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
3832
3987
  const explodeDepth = 0 | scaledExplodeDepth;
3833
3988
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
3834
- const transformMap = new Map();
3835
- function explodeObject(object, depth) {
3836
- if (object.isCamera)
3837
- return;
3838
- if (object.userData.isHighlightWireframe)
3839
- return;
3840
- object.position.copy(object.userData.originalPosition);
3841
- 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) {
3842
3998
  let objectScale = explodeScale * coeff;
3843
3999
  if (depth === explodeDepth)
3844
4000
  objectScale *= currentSegmentFraction;
3845
- const parentCenter = object.parent.userData.originalCenter;
4001
+ const parentCenter = parent.userData.originalCenter;
3846
4002
  const objectCenter = object.userData.originalCenter;
3847
- const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3848
- object.position.add(objectOffset);
3849
- const matrix = new Matrix4().makeTranslation(objectOffset.x, objectOffset.y, objectOffset.z);
3850
- transformMap.set(object, matrix);
4003
+ const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
4004
+ target.add(localOffset);
3851
4005
  }
3852
- object.children
3853
- .filter((x) => !x.userData.isOptimized)
3854
- .forEach((x) => explodeObject(x, depth + 1));
3855
- }
3856
- explodeObject(explodeRoot, 0);
3857
- 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
+ });
3858
4014
  this.gltfLoader.applyObjectTransforms(transformMap);
4015
+ this.scene.updateMatrixWorld();
3859
4016
  return this;
3860
4017
  }
3861
4018
  }
@@ -4432,6 +4589,8 @@ class DynamicGltfLoader {
4432
4589
  this.oldOptimizeObjects = new Set();
4433
4590
  this.objectTransforms = new Map();
4434
4591
  this.transformedGeometries = new Map();
4592
+ this.syncTransformsToOriginalObjects = true;
4593
+ this._originalObjectMatrices = new Map();
4435
4594
  this.activeChunkLoads = 0;
4436
4595
  this.chunkQueue = [];
4437
4596
  this.objectIdToIndex = new Map();
@@ -4441,6 +4600,81 @@ class DynamicGltfLoader {
4441
4600
  this.mergedObjectMap = new Map();
4442
4601
  this.mergedGeometryVisibility = new Map();
4443
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;
4444
4678
  }
4445
4679
  setVisibleEdges(visible) {
4446
4680
  this.visibleEdges = visible;
@@ -5330,36 +5564,82 @@ class DynamicGltfLoader {
5330
5564
  }
5331
5565
  }
5332
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;
5333
5573
  material.onBeforeCompile = (shader) => {
5574
+ shader.uniforms.transformTexture = uniforms.transformTexture;
5575
+ shader.uniforms.transformTextureSize = uniforms.transformTextureSize;
5334
5576
  shader.vertexShader = shader.vertexShader.replace(
5335
5577
  "#include <common>",
5336
5578
  `
5337
5579
  #include <common>
5580
+
5338
5581
  attribute float visibility;
5582
+ attribute float objectId;
5339
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
+ }
5340
5601
  `
5341
5602
  );
5342
- shader.fragmentShader = shader.fragmentShader.replace(
5343
- "#include <common>",
5603
+ shader.vertexShader = shader.vertexShader.replace(
5604
+ "void main() {",
5344
5605
  `
5345
- #include <common>
5346
- varying float vVisibility;
5606
+ void main() {
5607
+ mat4 batchingMatrix = getTransformMatrix(objectId);
5608
+ vVisibility = visibility;
5347
5609
  `
5348
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
+ }
5349
5621
  shader.vertexShader = shader.vertexShader.replace(
5350
- "void main() {",
5622
+ "#include <begin_vertex>",
5351
5623
  `
5352
- void main() {
5353
- vVisibility = visibility;
5624
+ vec3 transformed = vec3( position );
5625
+ transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
5354
5626
  `
5355
5627
  );
5356
- shader.fragmentShader = shader.fragmentShader.replace(
5357
- "void main() {",
5628
+ shader.fragmentShader = shader.fragmentShader
5629
+ .replace(
5630
+ "#include <common>",
5631
+ `
5632
+ #include <common>
5633
+ varying float vVisibility;
5358
5634
  `
5635
+ )
5636
+ .replace(
5637
+ "void main() {",
5638
+ `
5359
5639
  void main() {
5360
5640
  if (vVisibility < 0.5) discard;
5361
5641
  `
5362
- );
5642
+ );
5363
5643
  };
5364
5644
  material.needsUpdate = true;
5365
5645
  return material;
@@ -5465,6 +5745,7 @@ class DynamicGltfLoader {
5465
5745
  this.isolatedObjects = [];
5466
5746
  this.objectTransforms.clear();
5467
5747
  this.transformedGeometries.clear();
5748
+ this._originalObjectMatrices.clear();
5468
5749
  this.totalLoadedObjects = 0;
5469
5750
  this.currentMemoryUsage = 0;
5470
5751
  this.pendingMemoryUsage = 0;
@@ -5474,6 +5755,8 @@ class DynamicGltfLoader {
5474
5755
  this.objectIdToIndex.clear();
5475
5756
  this.maxObjectId = 0;
5476
5757
  this.objectVisibility = new Float32Array();
5758
+ this.meshToNodeMap = null;
5759
+ this.visibilityMaterials.clear();
5477
5760
  }
5478
5761
  setStructureTransform(structureId, matrix) {
5479
5762
  const rootGroup = this.structureRoots.get(structureId);
@@ -5589,12 +5872,15 @@ class DynamicGltfLoader {
5589
5872
  });
5590
5873
  this.originalObjects.clear();
5591
5874
  this.originalObjectsToSelection.clear();
5875
+ this.objectIdToIndex.clear();
5876
+ this.maxObjectId = 0;
5592
5877
  const structureGroups = new Map();
5593
5878
  this.dispatchEvent("optimizationprogress", {
5594
5879
  phase: "collecting",
5595
5880
  progress: 5,
5596
5881
  message: "Collecting scene objects...",
5597
5882
  });
5883
+ let totalObjectsToMerge = 0;
5598
5884
  this.scene.traverse((object) => {
5599
5885
  if (object.userData.structureId) {
5600
5886
  const structureId = object.userData.structureId;
@@ -5612,17 +5898,32 @@ class DynamicGltfLoader {
5612
5898
  });
5613
5899
  }
5614
5900
  const group = structureGroups.get(structureId);
5901
+ let added = false;
5615
5902
  if (object instanceof Mesh) {
5616
5903
  this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
5904
+ added = true;
5617
5905
  } else if (object instanceof LineSegments) {
5618
5906
  this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
5907
+ added = true;
5619
5908
  } else if (object instanceof Line) {
5620
5909
  this.addToMaterialGroup(object, group.mapLines, group.lines);
5910
+ added = true;
5621
5911
  } else if (object instanceof Points) {
5622
5912
  this.addToMaterialGroup(object, group.mapPoints, group.points);
5913
+ added = true;
5914
+ }
5915
+ if (added) {
5916
+ totalObjectsToMerge++;
5623
5917
  }
5624
5918
  }
5625
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
+ }
5626
5927
  let processedGroups = 0;
5627
5928
  const totalGroups = structureGroups.size;
5628
5929
  this.dispatchEvent("optimizationprogress", {
@@ -5667,7 +5968,6 @@ class DynamicGltfLoader {
5667
5968
  this.originalObjectsToSelection.add(obj);
5668
5969
  }
5669
5970
  });
5670
- this.initializeObjectVisibility();
5671
5971
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5672
5972
  this.dispatchEvent("optimizationprogress", {
5673
5973
  phase: "complete",
@@ -5736,6 +6036,7 @@ class DynamicGltfLoader {
5736
6036
  }
5737
6037
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5738
6038
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
6039
+ mergedMesh.frustumCulled = false;
5739
6040
  mergedMesh.userData.isOptimized = true;
5740
6041
  rootGroup.add(mergedMesh);
5741
6042
  this.mergedMesh.add(mergedMesh);
@@ -5852,6 +6153,7 @@ class DynamicGltfLoader {
5852
6153
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5853
6154
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5854
6155
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
6156
+ mergedLine.frustumCulled = false;
5855
6157
  mergedLine.userData.isEdge = isEdge;
5856
6158
  mergedLine.userData.isOptimized = true;
5857
6159
  const mergedObjects = [mergedLine];
@@ -5940,6 +6242,7 @@ class DynamicGltfLoader {
5940
6242
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5941
6243
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5942
6244
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
6245
+ mergedLine.frustumCulled = false;
5943
6246
  mergedLine.userData.isEdge = isEdge;
5944
6247
  mergedLine.userData.isOptimized = true;
5945
6248
  if (this.useVAO) {
@@ -6009,7 +6312,27 @@ class DynamicGltfLoader {
6009
6312
  const mergedObjects = [];
6010
6313
  if (geometries.length > 0) {
6011
6314
  const mergedGeometry = mergeGeometries(geometries, false);
6012
- 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;
6013
6336
  mergedPoints.userData.isOptimized = true;
6014
6337
  if (this.useVAO) {
6015
6338
  this.createVAO(mergedPoints);
@@ -6078,13 +6401,33 @@ class DynamicGltfLoader {
6078
6401
  geometriesWithIndex.push(clonedGeometry);
6079
6402
  });
6080
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));
6081
6422
  const material = new LineBasicMaterial({
6082
6423
  vertexColors: true,
6083
6424
  });
6425
+ const visibilityMaterial = this.createVisibilityMaterial(material);
6084
6426
  if (this.useVAO) {
6085
6427
  this.createVAO(finalGeometry);
6086
6428
  }
6087
- const mergedLine = new LineSegments(finalGeometry, material);
6429
+ const mergedLine = new LineSegments(finalGeometry, visibilityMaterial);
6430
+ mergedLine.frustumCulled = false;
6088
6431
  mergedLine.userData.structureId = structureId;
6089
6432
  mergedLine.userData.isOptimized = true;
6090
6433
  rootGroup.add(mergedLine);
@@ -6203,18 +6546,91 @@ class DynamicGltfLoader {
6203
6546
  console.warn("No merged objects to transform");
6204
6547
  return;
6205
6548
  }
6206
- this.objectTransforms = new Map(objectTransformMap);
6207
- for (const mesh of this.mergedMesh) {
6208
- this._applyTransformToMergedObject(mesh);
6549
+ if (!this.transformData) {
6550
+ console.warn("Transform texture not initialized");
6551
+ return;
6209
6552
  }
6210
- for (const line of this.mergedLines) {
6211
- 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
+ }
6212
6593
  }
6213
- for (const lineSegment of this.mergedLineSegments) {
6214
- this._applyTransformToMergedObject(lineSegment);
6594
+ if (this.syncTransformsToOriginalObjects) {
6595
+ this._syncOriginalObjectTransforms(objectTransformMap);
6215
6596
  }
6216
- for (const point of this.mergedPoints) {
6217
- 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
+ }
6218
6634
  }
6219
6635
  }
6220
6636
  createExplodeTransforms(objects = null, explodeCenter = null, explodeFactor = 1.5) {
@@ -6231,21 +6647,66 @@ class DynamicGltfLoader {
6231
6647
  ? objects
6232
6648
  : Array.from(objects)
6233
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
+ }
6234
6659
  for (const obj of objectsArray) {
6235
6660
  if (!obj.geometry || !obj.geometry.attributes.position) continue;
6236
- const boundingBox = new Box3().setFromBufferAttribute(obj.geometry.attributes.position);
6237
- if (obj.matrixWorld) {
6238
- boundingBox.applyMatrix4(obj.matrixWorld);
6239
- }
6240
- if (boundingBox.isEmpty()) continue;
6241
- const objectCenter = new Vector3();
6242
- boundingBox.getCenter(objectCenter);
6243
- const direction = objectCenter.clone().sub(explodeCenter);
6244
- 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();
6245
6682
  if (distance > 0) {
6246
- direction.normalize();
6247
- const offset = direction.multiplyScalar(distance * (explodeFactor - 1.0));
6248
- 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);
6249
6710
  transformMap.set(obj, matrix);
6250
6711
  }
6251
6712
  }
@@ -6253,115 +6714,19 @@ class DynamicGltfLoader {
6253
6714
  }
6254
6715
  clearTransforms() {
6255
6716
  this.objectTransforms.clear();
6256
- for (const mesh of this.mergedMesh) {
6257
- this._restoreOriginalGeometry(mesh);
6258
- }
6259
- for (const line of this.mergedLines) {
6260
- this._restoreOriginalGeometry(line);
6261
- }
6262
- for (const lineSegment of this.mergedLineSegments) {
6263
- this._restoreOriginalGeometry(lineSegment);
6264
- }
6265
- for (const point of this.mergedPoints) {
6266
- this._restoreOriginalGeometry(point);
6267
- }
6268
- }
6269
- clearHandleTransforms() {
6270
- this.clearTransforms();
6271
- }
6272
- _applyTransformToMergedObject(mergedObject) {
6273
- const objectData = this.mergedObjectMap.get(mergedObject.uuid);
6274
- if (!objectData || !objectData.objectMapping) return;
6275
- const geometry = mergedObject.geometry;
6276
- if (!geometry || !geometry.attributes.position) return;
6277
- const positionAttr = geometry.attributes.position;
6278
- const positions = positionAttr.array;
6279
- if (!this.transformedGeometries.has(mergedObject.uuid)) {
6280
- this.transformedGeometries.set(mergedObject.uuid, new Float32Array(positions));
6281
- }
6282
- const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
6283
- const tempVector = new Vector3();
6284
- for (const [originalMesh, mappingData] of objectData.objectMapping) {
6285
- const transform = this.objectTransforms.get(originalMesh);
6286
- if (!transform) {
6287
- const startIdx = mappingData.startVertexIndex * 3;
6288
- const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
6289
- for (let i = startIdx; i < endIdx; i++) {
6290
- positions[i] = originalPositions[i];
6291
- }
6292
- continue;
6293
- }
6294
- const startVertex = mappingData.startVertexIndex;
6295
- const vertexCount = mappingData.vertexCount;
6296
- for (let i = 0; i < vertexCount; i++) {
6297
- const idx = (startVertex + i) * 3;
6298
- tempVector.set(originalPositions[idx], originalPositions[idx + 1], originalPositions[idx + 2]);
6299
- tempVector.applyMatrix4(transform);
6300
- positions[idx] = tempVector.x;
6301
- positions[idx + 1] = tempVector.y;
6302
- positions[idx + 2] = tempVector.z;
6303
- }
6304
- }
6305
- if (geometry.attributes.normal) {
6306
- this._updateNormalsForTransform(geometry, objectData, originalPositions);
6307
- }
6308
- positionAttr.needsUpdate = true;
6309
- geometry.computeBoundingSphere();
6310
- geometry.computeBoundingBox();
6311
- }
6312
- _updateNormalsForTransform(geometry, objectData, originalPositions) {
6313
- const normalAttr = geometry.attributes.normal;
6314
- if (!normalAttr) return;
6315
- const normals = normalAttr.array;
6316
- const tempVector = new Vector3();
6317
- const normalMatrix = new Matrix4();
6318
- const normalsKey = `${geometry.uuid}_normals`;
6319
- if (!this.transformedGeometries.has(normalsKey)) {
6320
- this.transformedGeometries.set(normalsKey, new Float32Array(normals));
6321
- }
6322
- const originalNormals = this.transformedGeometries.get(normalsKey);
6323
- for (const [originalMesh, mappingData] of objectData.objectMapping) {
6324
- const transform = this.objectTransforms.get(originalMesh);
6325
- if (!transform) {
6326
- const startIdx = mappingData.startVertexIndex * 3;
6327
- const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
6328
- for (let i = startIdx; i < endIdx; i++) {
6329
- 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);
6330
6723
  }
6331
- continue;
6332
- }
6333
- normalMatrix.copy(transform).invert().transpose();
6334
- const startVertex = mappingData.startVertexIndex;
6335
- const vertexCount = mappingData.vertexCount;
6336
- for (let i = 0; i < vertexCount; i++) {
6337
- const idx = (startVertex + i) * 3;
6338
- tempVector.set(originalNormals[idx], originalNormals[idx + 1], originalNormals[idx + 2]);
6339
- tempVector.applyMatrix4(normalMatrix).normalize();
6340
- normals[idx] = tempVector.x;
6341
- normals[idx + 1] = tempVector.y;
6342
- normals[idx + 2] = tempVector.z;
6343
6724
  }
6725
+ this._originalObjectMatrices.clear();
6344
6726
  }
6345
- normalAttr.needsUpdate = true;
6346
6727
  }
6347
- _restoreOriginalGeometry(mergedObject) {
6348
- const geometry = mergedObject.geometry;
6349
- if (!geometry || !geometry.attributes.position) return;
6350
- const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
6351
- if (originalPositions) {
6352
- const positions = geometry.attributes.position.array;
6353
- positions.set(originalPositions);
6354
- geometry.attributes.position.needsUpdate = true;
6355
- }
6356
- const normalsKey = `${geometry.uuid}_normals`;
6357
- const originalNormals = this.transformedGeometries.get(normalsKey);
6358
- if (originalNormals && geometry.attributes.normal) {
6359
- const normals = geometry.attributes.normal.array;
6360
- normals.set(originalNormals);
6361
- geometry.attributes.normal.needsUpdate = true;
6362
- }
6363
- geometry.computeBoundingSphere();
6364
- geometry.computeBoundingBox();
6728
+ clearHandleTransforms() {
6729
+ this.clearTransforms();
6365
6730
  }
6366
6731
  syncHiddenObjects() {
6367
6732
  if (this.mergedObjectMap.size === 0) {