@inweb/viewer-three 27.2.2 → 27.2.3

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)
@@ -4441,6 +4591,81 @@ class DynamicGltfLoader {
4441
4591
  this.mergedObjectMap = new Map();
4442
4592
  this.mergedGeometryVisibility = new Map();
4443
4593
  this._webglInfoCache = null;
4594
+ this.transformTextureSize = 1024;
4595
+ this.transformTexture = this.createDummyTexture();
4596
+ this.transformData = null;
4597
+ this.identityTransformData = null;
4598
+ this.visibilityMaterials = new Set();
4599
+ }
4600
+ createDummyTexture() {
4601
+ const data = new Float32Array(16);
4602
+ const identity = new Matrix4();
4603
+ identity.toArray(data);
4604
+ const dummyData = new Float32Array(16);
4605
+ identity.toArray(dummyData);
4606
+ const dummyTexture = new DataTexture(dummyData, 4, 1, RGBAFormat, FloatType);
4607
+ dummyTexture.minFilter = NearestFilter;
4608
+ dummyTexture.magFilter = NearestFilter;
4609
+ dummyTexture.needsUpdate = true;
4610
+ return dummyTexture;
4611
+ }
4612
+ initTransformTexture() {
4613
+ if (this.transformTexture) {
4614
+ this.transformTexture.dispose();
4615
+ }
4616
+ const maxInstanceCount = this.maxObjectId + 1;
4617
+ let size = Math.sqrt(maxInstanceCount * 4);
4618
+ size = Math.ceil(size / 4) * 4;
4619
+ size = Math.max(size, 4);
4620
+ this.transformTextureSize = size;
4621
+ const arraySize = size * size * 4;
4622
+ this.transformData = new Float32Array(arraySize);
4623
+ this.identityTransformData = new Float32Array(arraySize);
4624
+ for (let i = 0; i <= this.maxObjectId; i++) {
4625
+ const base = i * 16;
4626
+ if (base + 15 < arraySize) {
4627
+ this.identityTransformData[base + 0] = 1;
4628
+ this.identityTransformData[base + 5] = 1;
4629
+ this.identityTransformData[base + 10] = 1;
4630
+ this.identityTransformData[base + 15] = 1;
4631
+ }
4632
+ }
4633
+ this._resetTransformData(false);
4634
+ this.transformTexture = new DataTexture(this.transformData, size, size, RGBAFormat, FloatType);
4635
+ this.transformTexture.needsUpdate = true;
4636
+ this.transformTexture.generateMipmaps = false;
4637
+ console.log(`Initialized transform texture: ${size}x${size} for ${maxInstanceCount} objects`);
4638
+ this.updateMaterialUniforms();
4639
+ this.visibilityMaterials.forEach((material) => {
4640
+ material.needsUpdate = true;
4641
+ });
4642
+ }
4643
+ _resetTransformData(updateTexture = true) {
4644
+ if (!this.transformData || !this.identityTransformData) return;
4645
+ this.transformData.set(this.identityTransformData);
4646
+ if (updateTexture) {
4647
+ this.updateTransformTexture();
4648
+ }
4649
+ }
4650
+ updateMaterialUniforms() {
4651
+ if (
4652
+ this._lastTransformTexture === this.transformTexture &&
4653
+ this._lastTransformTextureSize === this.transformTextureSize
4654
+ ) {
4655
+ return;
4656
+ }
4657
+ this._lastTransformTexture = this.transformTexture;
4658
+ this._lastTransformTextureSize = this.transformTextureSize;
4659
+ this.visibilityMaterials.forEach((material) => {
4660
+ if (material.userData && material.userData.visibilityUniforms) {
4661
+ material.userData.visibilityUniforms.transformTexture.value = this.transformTexture;
4662
+ material.userData.visibilityUniforms.transformTextureSize.value = this.transformTextureSize;
4663
+ }
4664
+ });
4665
+ }
4666
+ updateTransformTexture() {
4667
+ if (!this.transformTexture) return;
4668
+ this.transformTexture.needsUpdate = true;
4444
4669
  }
4445
4670
  setVisibleEdges(visible) {
4446
4671
  this.visibleEdges = visible;
@@ -5330,36 +5555,82 @@ class DynamicGltfLoader {
5330
5555
  }
5331
5556
  }
5332
5557
  createVisibilityMaterial(material) {
5558
+ this.visibilityMaterials.add(material);
5559
+ const uniforms = {
5560
+ transformTexture: { value: this.transformTexture },
5561
+ transformTextureSize: { value: this.transformTextureSize },
5562
+ };
5563
+ material.userData.visibilityUniforms = uniforms;
5333
5564
  material.onBeforeCompile = (shader) => {
5565
+ shader.uniforms.transformTexture = uniforms.transformTexture;
5566
+ shader.uniforms.transformTextureSize = uniforms.transformTextureSize;
5334
5567
  shader.vertexShader = shader.vertexShader.replace(
5335
5568
  "#include <common>",
5336
5569
  `
5337
5570
  #include <common>
5571
+
5338
5572
  attribute float visibility;
5573
+ attribute float objectId;
5339
5574
  varying float vVisibility;
5575
+ uniform highp sampler2D transformTexture;
5576
+ uniform float transformTextureSize;
5577
+
5578
+ mat4 getTransformMatrix(float instanceId) {
5579
+ int size = int(transformTextureSize);
5580
+ int index = int(instanceId) * 4;
5581
+
5582
+ int x0 = index % size;
5583
+ int y0 = index / size;
5584
+
5585
+ vec4 row0 = texelFetch(transformTexture, ivec2(x0, y0), 0);
5586
+ vec4 row1 = texelFetch(transformTexture, ivec2(x0 + 1, y0), 0);
5587
+ vec4 row2 = texelFetch(transformTexture, ivec2(x0 + 2, y0), 0);
5588
+ vec4 row3 = texelFetch(transformTexture, ivec2(x0 + 3, y0), 0);
5589
+
5590
+ return mat4(row0, row1, row2, row3);
5591
+ }
5340
5592
  `
5341
5593
  );
5342
- shader.fragmentShader = shader.fragmentShader.replace(
5343
- "#include <common>",
5594
+ shader.vertexShader = shader.vertexShader.replace(
5595
+ "void main() {",
5344
5596
  `
5345
- #include <common>
5346
- varying float vVisibility;
5597
+ void main() {
5598
+ mat4 batchingMatrix = getTransformMatrix(objectId);
5599
+ vVisibility = visibility;
5347
5600
  `
5348
5601
  );
5602
+ if (shader.vertexShader.includes("#include <beginnormal_vertex>")) {
5603
+ shader.vertexShader = shader.vertexShader.replace(
5604
+ "#include <beginnormal_vertex>",
5605
+ `
5606
+ vec3 objectNormal = vec3( normal );
5607
+ mat3 bm = mat3( batchingMatrix );
5608
+ objectNormal = bm * objectNormal;
5609
+ `
5610
+ );
5611
+ }
5349
5612
  shader.vertexShader = shader.vertexShader.replace(
5350
- "void main() {",
5613
+ "#include <begin_vertex>",
5351
5614
  `
5352
- void main() {
5353
- vVisibility = visibility;
5615
+ vec3 transformed = vec3( position );
5616
+ transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
5354
5617
  `
5355
5618
  );
5356
- shader.fragmentShader = shader.fragmentShader.replace(
5357
- "void main() {",
5619
+ shader.fragmentShader = shader.fragmentShader
5620
+ .replace(
5621
+ "#include <common>",
5622
+ `
5623
+ #include <common>
5624
+ varying float vVisibility;
5358
5625
  `
5626
+ )
5627
+ .replace(
5628
+ "void main() {",
5629
+ `
5359
5630
  void main() {
5360
5631
  if (vVisibility < 0.5) discard;
5361
5632
  `
5362
- );
5633
+ );
5363
5634
  };
5364
5635
  material.needsUpdate = true;
5365
5636
  return material;
@@ -5474,6 +5745,8 @@ class DynamicGltfLoader {
5474
5745
  this.objectIdToIndex.clear();
5475
5746
  this.maxObjectId = 0;
5476
5747
  this.objectVisibility = new Float32Array();
5748
+ this.meshToNodeMap = null;
5749
+ this.visibilityMaterials.clear();
5477
5750
  }
5478
5751
  setStructureTransform(structureId, matrix) {
5479
5752
  const rootGroup = this.structureRoots.get(structureId);
@@ -5589,12 +5862,15 @@ class DynamicGltfLoader {
5589
5862
  });
5590
5863
  this.originalObjects.clear();
5591
5864
  this.originalObjectsToSelection.clear();
5865
+ this.objectIdToIndex.clear();
5866
+ this.maxObjectId = 0;
5592
5867
  const structureGroups = new Map();
5593
5868
  this.dispatchEvent("optimizationprogress", {
5594
5869
  phase: "collecting",
5595
5870
  progress: 5,
5596
5871
  message: "Collecting scene objects...",
5597
5872
  });
5873
+ let totalObjectsToMerge = 0;
5598
5874
  this.scene.traverse((object) => {
5599
5875
  if (object.userData.structureId) {
5600
5876
  const structureId = object.userData.structureId;
@@ -5612,17 +5888,32 @@ class DynamicGltfLoader {
5612
5888
  });
5613
5889
  }
5614
5890
  const group = structureGroups.get(structureId);
5891
+ let added = false;
5615
5892
  if (object instanceof Mesh) {
5616
5893
  this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
5894
+ added = true;
5617
5895
  } else if (object instanceof LineSegments) {
5618
5896
  this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
5897
+ added = true;
5619
5898
  } else if (object instanceof Line) {
5620
5899
  this.addToMaterialGroup(object, group.mapLines, group.lines);
5900
+ added = true;
5621
5901
  } else if (object instanceof Points) {
5622
5902
  this.addToMaterialGroup(object, group.mapPoints, group.points);
5903
+ added = true;
5904
+ }
5905
+ if (added) {
5906
+ totalObjectsToMerge++;
5623
5907
  }
5624
5908
  }
5625
5909
  });
5910
+ if (totalObjectsToMerge > 0) {
5911
+ console.log(`Pre-allocating transform texture for ${totalObjectsToMerge} objects`);
5912
+ this.maxObjectId = totalObjectsToMerge;
5913
+ this.initTransformTexture();
5914
+ this.initializeObjectVisibility();
5915
+ this.maxObjectId = 0;
5916
+ }
5626
5917
  let processedGroups = 0;
5627
5918
  const totalGroups = structureGroups.size;
5628
5919
  this.dispatchEvent("optimizationprogress", {
@@ -5667,7 +5958,6 @@ class DynamicGltfLoader {
5667
5958
  this.originalObjectsToSelection.add(obj);
5668
5959
  }
5669
5960
  });
5670
- this.initializeObjectVisibility();
5671
5961
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
5672
5962
  this.dispatchEvent("optimizationprogress", {
5673
5963
  phase: "complete",
@@ -5736,6 +6026,7 @@ class DynamicGltfLoader {
5736
6026
  }
5737
6027
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5738
6028
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
6029
+ mergedMesh.frustumCulled = false;
5739
6030
  mergedMesh.userData.isOptimized = true;
5740
6031
  rootGroup.add(mergedMesh);
5741
6032
  this.mergedMesh.add(mergedMesh);
@@ -5852,6 +6143,7 @@ class DynamicGltfLoader {
5852
6143
  geometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5853
6144
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5854
6145
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
6146
+ mergedLine.frustumCulled = false;
5855
6147
  mergedLine.userData.isEdge = isEdge;
5856
6148
  mergedLine.userData.isOptimized = true;
5857
6149
  const mergedObjects = [mergedLine];
@@ -5940,6 +6232,7 @@ class DynamicGltfLoader {
5940
6232
  mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
5941
6233
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
5942
6234
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
6235
+ mergedLine.frustumCulled = false;
5943
6236
  mergedLine.userData.isEdge = isEdge;
5944
6237
  mergedLine.userData.isOptimized = true;
5945
6238
  if (this.useVAO) {
@@ -6009,7 +6302,27 @@ class DynamicGltfLoader {
6009
6302
  const mergedObjects = [];
6010
6303
  if (geometries.length > 0) {
6011
6304
  const mergedGeometry = mergeGeometries(geometries, false);
6012
- const mergedPoints = new Points(mergedGeometry, group.material);
6305
+ const totalVertices = mergedGeometry.attributes.position.count;
6306
+ const objectIds = new Float32Array(totalVertices);
6307
+ let vertexOffset = 0;
6308
+ group.objects.forEach((points) => {
6309
+ const handle = points.userData.handle;
6310
+ if (!this.objectIdToIndex.has(handle)) {
6311
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
6312
+ }
6313
+ const objectId = this.objectIdToIndex.get(handle);
6314
+ const count = points.geometry.attributes.position.count;
6315
+ for (let i = 0; i < count; i++) {
6316
+ objectIds[vertexOffset++] = objectId;
6317
+ }
6318
+ });
6319
+ mergedGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
6320
+ const visibilityArray = new Float32Array(totalVertices);
6321
+ visibilityArray.fill(1.0);
6322
+ mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
6323
+ const visibilityMaterial = this.createVisibilityMaterial(group.material);
6324
+ const mergedPoints = new Points(mergedGeometry, visibilityMaterial);
6325
+ mergedPoints.frustumCulled = false;
6013
6326
  mergedPoints.userData.isOptimized = true;
6014
6327
  if (this.useVAO) {
6015
6328
  this.createVAO(mergedPoints);
@@ -6078,13 +6391,33 @@ class DynamicGltfLoader {
6078
6391
  geometriesWithIndex.push(clonedGeometry);
6079
6392
  });
6080
6393
  const finalGeometry = mergeGeometries(geometriesWithIndex, false);
6394
+ const totalVertices = finalGeometry.attributes.position.count;
6395
+ const objectIds = new Float32Array(totalVertices);
6396
+ let vertexOffset = 0;
6397
+ lineSegmentsArray.forEach((segment) => {
6398
+ const handle = segment.userData.handle;
6399
+ if (!this.objectIdToIndex.has(handle)) {
6400
+ this.objectIdToIndex.set(handle, this.maxObjectId++);
6401
+ }
6402
+ const objectId = this.objectIdToIndex.get(handle);
6403
+ const count = segment.geometry.attributes.position.count;
6404
+ for (let i = 0; i < count; i++) {
6405
+ objectIds[vertexOffset++] = objectId;
6406
+ }
6407
+ });
6408
+ finalGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
6409
+ const visibilityArray = new Float32Array(totalVertices);
6410
+ visibilityArray.fill(1.0);
6411
+ finalGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
6081
6412
  const material = new LineBasicMaterial({
6082
6413
  vertexColors: true,
6083
6414
  });
6415
+ const visibilityMaterial = this.createVisibilityMaterial(material);
6084
6416
  if (this.useVAO) {
6085
6417
  this.createVAO(finalGeometry);
6086
6418
  }
6087
- const mergedLine = new LineSegments(finalGeometry, material);
6419
+ const mergedLine = new LineSegments(finalGeometry, visibilityMaterial);
6420
+ mergedLine.frustumCulled = false;
6088
6421
  mergedLine.userData.structureId = structureId;
6089
6422
  mergedLine.userData.isOptimized = true;
6090
6423
  rootGroup.add(mergedLine);
@@ -6203,18 +6536,50 @@ class DynamicGltfLoader {
6203
6536
  console.warn("No merged objects to transform");
6204
6537
  return;
6205
6538
  }
6206
- this.objectTransforms = new Map(objectTransformMap);
6207
- for (const mesh of this.mergedMesh) {
6208
- this._applyTransformToMergedObject(mesh);
6209
- }
6210
- for (const line of this.mergedLines) {
6211
- this._applyTransformToMergedObject(line);
6212
- }
6213
- for (const lineSegment of this.mergedLineSegments) {
6214
- this._applyTransformToMergedObject(lineSegment);
6539
+ if (!this.transformData) {
6540
+ console.warn("Transform texture not initialized");
6541
+ return;
6215
6542
  }
6216
- for (const point of this.mergedPoints) {
6217
- this._applyTransformToMergedObject(point);
6543
+ this.objectTransforms = objectTransformMap;
6544
+ this._resetTransformData(false);
6545
+ const transformData = this.transformData;
6546
+ const objectIdToIndex = this.objectIdToIndex;
6547
+ let textureNeedsUpdate = false;
6548
+ if (objectTransformMap instanceof Map) {
6549
+ for (const [object, matrix] of objectTransformMap.entries()) {
6550
+ const userData = object.userData;
6551
+ if (!userData) continue;
6552
+ const handle = userData.handle;
6553
+ if (handle === undefined) continue;
6554
+ const objectId = objectIdToIndex.get(handle);
6555
+ if (objectId !== undefined) {
6556
+ transformData.set(matrix.elements, objectId * 16);
6557
+ textureNeedsUpdate = true;
6558
+ }
6559
+ }
6560
+ } else {
6561
+ const len = objectTransformMap.length;
6562
+ for (let i = 0; i < len; i++) {
6563
+ const pair = objectTransformMap[i];
6564
+ const userData = pair[0].userData;
6565
+ if (!userData) continue;
6566
+ const handle = userData.handle;
6567
+ if (handle === undefined) continue;
6568
+ const objectId = objectIdToIndex.get(handle);
6569
+ if (objectId !== undefined) {
6570
+ transformData.set(pair[1].elements, objectId * 16);
6571
+ textureNeedsUpdate = true;
6572
+ }
6573
+ }
6574
+ }
6575
+ if (textureNeedsUpdate) {
6576
+ this.updateTransformTexture();
6577
+ if (
6578
+ this._lastTransformTexture !== this.transformTexture ||
6579
+ this._lastTransformTextureSize !== this.transformTextureSize
6580
+ ) {
6581
+ this.updateMaterialUniforms();
6582
+ }
6218
6583
  }
6219
6584
  }
6220
6585
  createExplodeTransforms(objects = null, explodeCenter = null, explodeFactor = 1.5) {
@@ -6231,21 +6596,66 @@ class DynamicGltfLoader {
6231
6596
  ? objects
6232
6597
  : Array.from(objects)
6233
6598
  : Array.from(this.originalObjects);
6599
+ const structureInverseMatrices = new Map();
6600
+ if (!this.meshToNodeMap) {
6601
+ this.meshToNodeMap = new Map();
6602
+ for (const node of this.nodes.values()) {
6603
+ if (node.object) {
6604
+ this.meshToNodeMap.set(node.object, node);
6605
+ }
6606
+ }
6607
+ }
6234
6608
  for (const obj of objectsArray) {
6235
6609
  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();
6610
+ if (!obj.userData.explodeVector) {
6611
+ let center = null;
6612
+ const node = this.meshToNodeMap.get(obj);
6613
+ if (node && node.geometryExtents) {
6614
+ const box = node.geometryExtents.clone();
6615
+ box.applyMatrix4(obj.matrixWorld);
6616
+ center = new Vector3();
6617
+ box.getCenter(center);
6618
+ }
6619
+ if (!center) {
6620
+ if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
6621
+ const box = obj.geometry.boundingBox.clone();
6622
+ box.applyMatrix4(obj.matrixWorld);
6623
+ center = new Vector3();
6624
+ box.getCenter(center);
6625
+ }
6626
+ const explodeVector = center.sub(explodeCenter);
6627
+ obj.userData.explodeVector = explodeVector;
6628
+ }
6629
+ const explodeVector = obj.userData.explodeVector;
6630
+ const distance = explodeVector.length();
6245
6631
  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);
6632
+ const offset = explodeVector.clone().multiplyScalar(explodeFactor - 1.0);
6633
+ const localOffset = offset.clone();
6634
+ if (obj.userData.structureId) {
6635
+ const structureId = obj.userData.structureId;
6636
+ let inverseMatrix = structureInverseMatrices.get(structureId);
6637
+ if (!inverseMatrix) {
6638
+ const rootGroup = this.structureRoots.get(structureId);
6639
+ if (rootGroup) {
6640
+ if (!rootGroup.userData.inverseWorldMatrix) {
6641
+ rootGroup.userData.inverseWorldMatrix = new Matrix4().copy(rootGroup.matrixWorld).invert();
6642
+ }
6643
+ inverseMatrix = rootGroup.userData.inverseWorldMatrix;
6644
+ structureInverseMatrices.set(structureId, inverseMatrix);
6645
+ }
6646
+ }
6647
+ if (inverseMatrix) {
6648
+ const zero = new Vector3(0, 0, 0).applyMatrix4(inverseMatrix);
6649
+ const vec = offset.clone().applyMatrix4(inverseMatrix).sub(zero);
6650
+ localOffset.copy(vec);
6651
+ }
6652
+ }
6653
+ let matrix = obj.userData.explodeMatrix;
6654
+ if (!matrix) {
6655
+ matrix = new Matrix4();
6656
+ obj.userData.explodeMatrix = matrix;
6657
+ }
6658
+ matrix.makeTranslation(localOffset.x, localOffset.y, localOffset.z);
6249
6659
  transformMap.set(obj, matrix);
6250
6660
  }
6251
6661
  }
@@ -6253,116 +6663,11 @@ class DynamicGltfLoader {
6253
6663
  }
6254
6664
  clearTransforms() {
6255
6665
  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
- }
6666
+ this._resetTransformData(true);
6268
6667
  }
6269
6668
  clearHandleTransforms() {
6270
6669
  this.clearTransforms();
6271
6670
  }
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];
6330
- }
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
- }
6344
- }
6345
- normalAttr.needsUpdate = true;
6346
- }
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();
6365
- }
6366
6671
  syncHiddenObjects() {
6367
6672
  if (this.mergedObjectMap.size === 0) {
6368
6673
  console.log("No merged objects to sync");