@inweb/viewer-three 27.4.7 → 27.6.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.
Files changed (94) hide show
  1. package/dist/extensions/components/AxesHelperComponent.js +3 -0
  2. package/dist/extensions/components/AxesHelperComponent.js.map +1 -1
  3. package/dist/extensions/components/AxesHelperComponent.min.js +1 -1
  4. package/dist/extensions/components/AxesHelperComponent.module.js +3 -0
  5. package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -1
  6. package/dist/extensions/components/ExtentsHelperComponent.js +6 -2
  7. package/dist/extensions/components/ExtentsHelperComponent.js.map +1 -1
  8. package/dist/extensions/components/ExtentsHelperComponent.min.js +1 -1
  9. package/dist/extensions/components/ExtentsHelperComponent.module.js +6 -2
  10. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +1 -1
  11. package/dist/extensions/components/GridHelperComponent.js +1 -0
  12. package/dist/extensions/components/GridHelperComponent.js.map +1 -1
  13. package/dist/extensions/components/GridHelperComponent.min.js +1 -1
  14. package/dist/extensions/components/GridHelperComponent.module.js +1 -0
  15. package/dist/extensions/components/GridHelperComponent.module.js.map +1 -1
  16. package/dist/extensions/components/LightHelperComponent.js +2 -1
  17. package/dist/extensions/components/LightHelperComponent.js.map +1 -1
  18. package/dist/extensions/components/LightHelperComponent.min.js +1 -1
  19. package/dist/extensions/components/LightHelperComponent.module.js +2 -1
  20. package/dist/extensions/components/LightHelperComponent.module.js.map +1 -1
  21. package/dist/extensions/components/StatsPanelComponent.js +0 -1
  22. package/dist/extensions/components/StatsPanelComponent.js.map +1 -1
  23. package/dist/extensions/components/StatsPanelComponent.min.js +1 -1
  24. package/dist/extensions/components/StatsPanelComponent.module.js +0 -1
  25. package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -1
  26. package/dist/extensions/loaders/GLTFCloudLoader.js +7 -2
  27. package/dist/extensions/loaders/GLTFCloudLoader.js.map +1 -1
  28. package/dist/extensions/loaders/GLTFCloudLoader.min.js +1 -1
  29. package/dist/extensions/loaders/GLTFCloudLoader.module.js +7 -2
  30. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +1 -1
  31. package/dist/extensions/loaders/GLTFFileLoader.js +2 -1
  32. package/dist/extensions/loaders/GLTFFileLoader.js.map +1 -1
  33. package/dist/extensions/loaders/GLTFFileLoader.min.js +1 -1
  34. package/dist/extensions/loaders/GLTFFileLoader.module.js +2 -1
  35. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +1 -1
  36. package/dist/extensions/loaders/IFCXLoader.js +10 -5
  37. package/dist/extensions/loaders/IFCXLoader.js.map +1 -1
  38. package/dist/extensions/loaders/IFCXLoader.min.js +1 -1
  39. package/dist/extensions/loaders/IFCXLoader.module.js +10 -5
  40. package/dist/extensions/loaders/IFCXLoader.module.js.map +1 -1
  41. package/dist/viewer-three.js +1901 -569
  42. package/dist/viewer-three.js.map +1 -1
  43. package/dist/viewer-three.min.js +4 -4
  44. package/dist/viewer-three.module.js +1366 -451
  45. package/dist/viewer-three.module.js.map +1 -1
  46. package/extensions/components/AxesHelperComponent.ts +3 -0
  47. package/extensions/components/ExtentsHelperComponent.ts +5 -2
  48. package/extensions/components/GridHelperComponent.ts +1 -0
  49. package/extensions/components/LightHelperComponent.ts +2 -1
  50. package/extensions/components/StatsPanelComponent.ts +0 -1
  51. package/extensions/loaders/GLTFCloudLoader.ts +8 -2
  52. package/extensions/loaders/GLTFFileLoader.ts +3 -2
  53. package/extensions/loaders/IFCX/IFCXFileLoader.ts +11 -5
  54. package/lib/Viewer/Viewer.d.ts +6 -8
  55. package/lib/Viewer/components/CameraComponent.d.ts +1 -1
  56. package/lib/Viewer/components/ClippingPlaneComponent.d.ts +8 -0
  57. package/lib/Viewer/components/HighlighterComponent.d.ts +2 -2
  58. package/lib/Viewer/components/InfoComponent.d.ts +1 -1
  59. package/lib/Viewer/components/SectionsComponent.d.ts +15 -0
  60. package/lib/Viewer/components/WCSHelperComponent.d.ts +2 -2
  61. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +6 -6
  62. package/lib/Viewer/draggers/OrbitDragger.d.ts +1 -1
  63. package/lib/Viewer/measurement/Snapper.d.ts +4 -4
  64. package/package.json +5 -5
  65. package/src/Viewer/Viewer.ts +59 -48
  66. package/src/Viewer/commands/GetSelected2.ts +1 -1
  67. package/src/Viewer/commands/SetSelected.ts +1 -1
  68. package/src/Viewer/commands/index.ts +1 -1
  69. package/src/Viewer/components/BackgroundComponent.ts +2 -1
  70. package/src/Viewer/components/CameraComponent.ts +6 -7
  71. package/src/Viewer/components/CanvasRemoveComponent.ts +0 -1
  72. package/src/Viewer/{scenes/Helpers.ts → components/ClippingPlaneComponent.ts} +22 -12
  73. package/src/Viewer/components/HighlighterComponent.ts +9 -5
  74. package/src/Viewer/components/HighlighterUtils.ts +2 -2
  75. package/src/Viewer/components/InfoComponent.ts +4 -4
  76. package/src/Viewer/components/SectionsComponent.ts +119 -0
  77. package/src/Viewer/components/SelectionComponent.ts +5 -3
  78. package/src/Viewer/components/WCSHelperComponent.ts +8 -6
  79. package/src/Viewer/components/index.ts +4 -0
  80. package/src/Viewer/draggers/CuttingPlaneDragger.ts +57 -34
  81. package/src/Viewer/draggers/MeasureLineDragger.ts +1 -1
  82. package/src/Viewer/draggers/OrbitDragger.ts +3 -3
  83. package/src/Viewer/helpers/SectionsHelper.js +1061 -0
  84. package/src/Viewer/helpers/WCSHelper.ts +31 -5
  85. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +417 -92
  86. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +19 -14
  87. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +76 -9
  88. package/src/Viewer/loaders/GLTFBinaryParser.ts +2 -2
  89. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +3 -2
  90. package/src/Viewer/loaders/GLTFFileDynamicLoader.ts +6 -4
  91. package/src/Viewer/measurement/Snapper.ts +6 -7
  92. package/src/Viewer/models/ModelImpl.ts +65 -28
  93. package/lib/Viewer/scenes/Helpers.d.ts +0 -7
  94. package/src/Viewer/postprocessing/SSAARenderPass.js +0 -245
@@ -23,19 +23,19 @@
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 { 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, LinearMipmapLinearFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, NearestMipmapNearestFilter, LinearFilter, NearestFilter, RepeatWrapping, MirroredRepeatWrapping, ClampToEdgeWrapping, PointsMaterial, MeshStandardMaterial, DataTexture, FloatType, 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, ShaderMaterial, PointsMaterial, Points, Sphere, ShapeUtils, CylinderGeometry, Sprite, CanvasTexture, SRGBColorSpace, SpriteMaterial, TextureLoader, BufferAttribute, LinearMipmapLinearFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, NearestMipmapNearestFilter, LinearFilter, NearestFilter, RepeatWrapping, MirroredRepeatWrapping, ClampToEdgeWrapping, MeshStandardMaterial, DataTexture, FloatType, TriangleStripDrawMode, TriangleFanDrawMode, LineSegments, LineLoop, Group, NormalBlending, LoadingManager, LoaderUtils, FileLoader, 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';
30
30
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
31
+ import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js';
31
32
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
32
33
  import { JSONParser } from '@streamparser/json';
33
34
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
34
35
  import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
35
36
  import { FXAAPass } from 'three/examples/jsm/postprocessing/FXAAPass.js';
36
37
  import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js';
37
- import { Pass, FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
38
- import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js';
38
+ import { SSAARenderPass } from 'three/examples/jsm/postprocessing/SSAARenderPass.js';
39
39
  import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
40
40
  import { EventEmitter2 } from '@inweb/eventemitter2';
41
41
  import { Markup } from '@inweb/markup';
@@ -751,9 +751,9 @@ const _line = new Line3();
751
751
  const _center = new Vector3();
752
752
  const _projection = new Vector3();
753
753
  class Snapper {
754
- constructor(camera, renderer, canvas) {
754
+ constructor(camera, clippingPlanes, canvas) {
755
755
  this.camera = camera;
756
- this.renderer = renderer;
756
+ this.clippingPlanes = clippingPlanes;
757
757
  this.canvas = canvas;
758
758
  this.threshold = 0.0001;
759
759
  this.raycaster = new Raycaster();
@@ -784,7 +784,7 @@ class Snapper {
784
784
  };
785
785
  let intersects = this.raycaster.intersectObjects(objects, recursive);
786
786
  if (clip) {
787
- const clippingPlanes = this.renderer.clippingPlanes || [];
787
+ const clippingPlanes = this.clippingPlanes;
788
788
  clippingPlanes.forEach((plane) => {
789
789
  intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
790
790
  });
@@ -815,7 +815,7 @@ class Snapper {
815
815
  const object = intersections[0].object;
816
816
  const intersectionPoint = intersections[0].point;
817
817
  const localPoint = object.worldToLocal(intersectionPoint.clone());
818
- let snapPoint;
818
+ let snapPoint = undefined;
819
819
  let snapDistance = this.getDetectRadius(intersectionPoint);
820
820
  const geometry = object.geometry;
821
821
  const positions = geometry.attributes.position.array;
@@ -871,7 +871,7 @@ class OrbitDragger {
871
871
  this.orbit.object = this.viewer.camera;
872
872
  this.orbit.update();
873
873
  };
874
- this.optionsChange = ({ data: options }) => {
874
+ this.updateZoomSpeed = ({ data: options }) => {
875
875
  this.orbit.zoomSpeed = Math.abs(this.orbit.zoomSpeed) * (options.reverseZoomWheel ? -1 : 1);
876
876
  };
877
877
  this.controlsStart = () => {
@@ -930,7 +930,7 @@ class OrbitDragger {
930
930
  this.viewer.addEventListener("zoom", this.updateControls);
931
931
  this.viewer.addEventListener("drawviewpoint", this.updateControls);
932
932
  this.viewer.addEventListener("changecameramode", this.updateControlsCamera);
933
- this.viewer.addEventListener("optionschange", this.optionsChange);
933
+ this.viewer.addEventListener("optionschange", this.updateZoomSpeed);
934
934
  this.viewer.addEventListener("contextmenu", this.stopContextMenu);
935
935
  this.updateControls();
936
936
  this.updateControlsCamera();
@@ -942,7 +942,7 @@ class OrbitDragger {
942
942
  this.viewer.removeEventListener("zoom", this.updateControls);
943
943
  this.viewer.removeEventListener("drawviewpoint", this.updateControls);
944
944
  this.viewer.removeEventListener("changecameramode", this.updateControlsCamera);
945
- this.viewer.removeEventListener("optionschange", this.optionsChange);
945
+ this.viewer.removeEventListener("optionschange", this.updateZoomSpeed);
946
946
  this.viewer.removeEventListener("contextmenu", this.stopContextMenu);
947
947
  this.orbit.removeEventListener("start", this.controlsStart);
948
948
  this.orbit.removeEventListener("change", this.controlsChange);
@@ -954,13 +954,17 @@ class CuttingPlaneDragger extends OrbitDragger {
954
954
  constructor(viewer) {
955
955
  super(viewer);
956
956
  this.helpers = [];
957
- this.activeHelper = null;
957
+ this.activeHelper = undefined;
958
+ this.transformUpdate = () => {
959
+ this.viewer.update();
960
+ };
958
961
  this.transformChange = () => {
959
962
  if (!this.activeHelper)
960
963
  return;
961
964
  const plane = this.activeHelper.plane;
962
965
  plane.normal.copy(new Vector3(0, 0, -1)).applyQuaternion(this.activeHelper.quaternion);
963
966
  plane.constant = -this.activeHelper.position.dot(plane.normal);
967
+ this.viewer.emitEvent({ type: "changecuttingplanes" });
964
968
  this.viewer.update();
965
969
  this.changed = true;
966
970
  };
@@ -972,25 +976,29 @@ class CuttingPlaneDragger extends OrbitDragger {
972
976
  this.orbit.enabled = !event.value;
973
977
  this.translate.enabled = !event.value;
974
978
  };
975
- this.updatePlaneSize = () => {
979
+ this.syncHelpers = () => {
976
980
  const extentsSize = this.viewer.extents.getSize(new Vector3()).length() || 1;
977
- this.helpers.forEach((planeHelper) => (planeHelper.size = extentsSize));
981
+ const extentsCenter = this.viewer.extents.getCenter(new Vector3());
982
+ this.helpers.forEach((planeHelper) => {
983
+ planeHelper.size = extentsSize;
984
+ planeHelper.position.copy(planeHelper.plane.projectPoint(extentsCenter, new Vector3()));
985
+ });
978
986
  this.viewer.update();
979
987
  };
980
- this.updateTransformCamera = () => {
981
- this.translate.camera = this.viewer.camera;
982
- this.rotate.camera = this.viewer.camera;
983
- this.snapper.camera = this.viewer.camera;
984
- };
985
988
  this.clearHelpers = () => {
986
989
  this.setActiveHelper();
987
990
  this.helpers.forEach((helper) => {
988
991
  helper.removeFromParent();
989
992
  helper.dispose();
990
993
  });
991
- this.helpers = [];
994
+ this.helpers.length = 0;
992
995
  this.viewer.update();
993
996
  };
997
+ this.updateTransformCamera = () => {
998
+ this.translate.camera = this.viewer.camera;
999
+ this.rotate.camera = this.viewer.camera;
1000
+ this.snapper.camera = this.viewer.camera;
1001
+ };
994
1002
  this.onKeyDown = (event) => {
995
1003
  if (event.key === "Shift")
996
1004
  this.rotate.setRotationSnap(Math.PI / 4);
@@ -1034,12 +1042,9 @@ class CuttingPlaneDragger extends OrbitDragger {
1034
1042
  this.transformChange();
1035
1043
  event.stopPropagation();
1036
1044
  };
1037
- if (!viewer.renderer.clippingPlanes)
1038
- viewer.renderer.clippingPlanes = [];
1039
- this.clippingPlanes = viewer.renderer.clippingPlanes;
1040
- this.clippingPlanes.forEach((plane) => this.addHelper(plane));
1045
+ viewer.clippingPlanes.forEach((plane) => this.addHelper(plane));
1041
1046
  const extentsSize = viewer.extents.getSize(new Vector3()).length() || 1;
1042
- this.snapper = new Snapper(viewer.camera, viewer.renderer, viewer.canvas);
1047
+ this.snapper = new Snapper(viewer.camera, viewer.clippingPlanes, viewer.canvas);
1043
1048
  this.snapper.threshold = extentsSize / 10000;
1044
1049
  this.downPosition = new Vector2();
1045
1050
  this.position0 = new Vector3();
@@ -1050,7 +1055,8 @@ class CuttingPlaneDragger extends OrbitDragger {
1050
1055
  this.translate.showX = false;
1051
1056
  this.translate.showY = false;
1052
1057
  this.translate.showZ = true;
1053
- this.translate.addEventListener("change", this.transformChange);
1058
+ this.translate.addEventListener("change", this.transformUpdate);
1059
+ this.translate.addEventListener("objectChange", this.transformChange);
1054
1060
  this.translate.addEventListener("dragging-changed", this.translateDrag);
1055
1061
  this.viewer.helpers.add(this.translate.getHelper());
1056
1062
  this.rotate = new TransformControls(viewer.camera, viewer.canvas);
@@ -1059,15 +1065,18 @@ class CuttingPlaneDragger extends OrbitDragger {
1059
1065
  this.rotate.showX = true;
1060
1066
  this.rotate.showY = true;
1061
1067
  this.rotate.showZ = false;
1062
- this.rotate.addEventListener("change", this.transformChange);
1068
+ this.rotate.addEventListener("change", this.transformUpdate);
1069
+ this.rotate.addEventListener("objectChange", this.transformChange);
1063
1070
  this.rotate.addEventListener("dragging-changed", this.rotateDrag);
1064
1071
  this.viewer.helpers.add(this.rotate.getHelper());
1065
1072
  this.setActiveHelper(this.helpers[this.helpers.length - 1]);
1066
- this.viewer.addEventListener("explode", this.updatePlaneSize);
1067
- this.viewer.addEventListener("show", this.updatePlaneSize);
1068
- this.viewer.addEventListener("showall", this.updatePlaneSize);
1069
- this.viewer.addEventListener("changecameramode", this.updateTransformCamera);
1073
+ this.viewer.addEventListener("explode", this.syncHelpers);
1074
+ this.viewer.addEventListener("hide", this.syncHelpers);
1075
+ this.viewer.addEventListener("isolate", this.syncHelpers);
1076
+ this.viewer.addEventListener("show", this.syncHelpers);
1077
+ this.viewer.addEventListener("showall", this.syncHelpers);
1070
1078
  this.viewer.addEventListener("clearslices", this.clearHelpers);
1079
+ this.viewer.addEventListener("changecameramode", this.updateTransformCamera);
1071
1080
  this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown, true);
1072
1081
  this.viewer.canvas.addEventListener("pointerup", this.onPointerUp, true);
1073
1082
  this.viewer.canvas.addEventListener("pointercancel", this.onPointerCancel, true);
@@ -1077,23 +1086,27 @@ class CuttingPlaneDragger extends OrbitDragger {
1077
1086
  this.viewer.update();
1078
1087
  }
1079
1088
  dispose() {
1080
- this.viewer.removeEventListener("explode", this.updatePlaneSize);
1081
- this.viewer.removeEventListener("show", this.updatePlaneSize);
1082
- this.viewer.removeEventListener("showall", this.updatePlaneSize);
1083
- this.viewer.removeEventListener("changecameramode", this.updateTransformCamera);
1089
+ this.viewer.removeEventListener("explode", this.syncHelpers);
1090
+ this.viewer.removeEventListener("hide", this.syncHelpers);
1091
+ this.viewer.removeEventListener("isolate", this.syncHelpers);
1092
+ this.viewer.removeEventListener("show", this.syncHelpers);
1093
+ this.viewer.removeEventListener("showall", this.syncHelpers);
1084
1094
  this.viewer.removeEventListener("clearslices", this.clearHelpers);
1095
+ this.viewer.removeEventListener("changecameramode", this.updateTransformCamera);
1085
1096
  this.viewer.canvas.removeEventListener("pointerdown", this.onPointerDown, true);
1086
1097
  this.viewer.canvas.removeEventListener("pointerup", this.onPointerUp, true);
1087
1098
  this.viewer.canvas.removeEventListener("pointercancel", this.onPointerCancel, true);
1088
1099
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
1089
1100
  window.removeEventListener("keydown", this.onKeyDown);
1090
1101
  window.removeEventListener("keyup", this.onKeyUp);
1091
- this.translate.removeEventListener("change", this.transformChange);
1102
+ this.translate.removeEventListener("change", this.transformUpdate);
1103
+ this.translate.removeEventListener("objectChange", this.transformChange);
1092
1104
  this.translate.removeEventListener("dragging-changed", this.translateDrag);
1093
1105
  this.translate.getHelper().removeFromParent();
1094
1106
  this.translate.detach();
1095
1107
  this.translate.dispose();
1096
- this.rotate.removeEventListener("change", this.transformChange);
1108
+ this.rotate.removeEventListener("change", this.transformUpdate);
1109
+ this.rotate.removeEventListener("objectChange", this.transformChange);
1097
1110
  this.rotate.removeEventListener("dragging-changed", this.rotateDrag);
1098
1111
  this.rotate.getHelper().removeFromParent();
1099
1112
  this.rotate.detach();
@@ -1102,8 +1115,8 @@ class CuttingPlaneDragger extends OrbitDragger {
1102
1115
  helper.removeFromParent();
1103
1116
  helper.dispose();
1104
1117
  });
1105
- this.helpers = [];
1106
- this.activeHelper = null;
1118
+ this.helpers.length = 0;
1119
+ this.activeHelper = undefined;
1107
1120
  super.dispose();
1108
1121
  }
1109
1122
  addHelper(plane) {
@@ -1155,9 +1168,10 @@ class CuttingPlaneDragger extends OrbitDragger {
1155
1168
  const extentsCenter = this.viewer.extents.getCenter(new Vector3());
1156
1169
  const constant = -extentsCenter.dot(normal);
1157
1170
  const plane = new Plane(normal, constant);
1158
- this.clippingPlanes.push(plane);
1171
+ this.viewer.clippingPlanes.push(plane);
1159
1172
  const helper = this.addHelper(plane);
1160
1173
  this.setActiveHelper(helper);
1174
+ this.viewer.emitEvent({ type: "changecuttingplanes" });
1161
1175
  }
1162
1176
  addPlaneX() {
1163
1177
  this.addPlane(new Vector3(-1, 0, 0));
@@ -1172,13 +1186,14 @@ class CuttingPlaneDragger extends OrbitDragger {
1172
1186
  if (!this.activeHelper)
1173
1187
  return;
1174
1188
  const helper = this.activeHelper;
1175
- const index = this.clippingPlanes.indexOf(helper.plane);
1189
+ const index = this.viewer.clippingPlanes.indexOf(helper.plane);
1176
1190
  if (index !== -1)
1177
- this.clippingPlanes.splice(index, 1);
1191
+ this.viewer.clippingPlanes.splice(index, 1);
1178
1192
  this.helpers = this.helpers.filter((x) => x !== helper);
1179
1193
  helper.removeFromParent();
1180
1194
  helper.dispose();
1181
1195
  this.setActiveHelper(this.helpers[this.helpers.length - 1]);
1196
+ this.viewer.emitEvent({ type: "changecuttingplanes" });
1182
1197
  }
1183
1198
  }
1184
1199
 
@@ -1372,7 +1387,7 @@ class MeasureLineDragger extends OrbitDragger {
1372
1387
  this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
1373
1388
  this.overlay.addLine(this.line);
1374
1389
  const extentsSize = this.viewer.extents.getSize(new Vector3()).length() || 1;
1375
- this.snapper = new Snapper(viewer.camera, viewer.renderer, viewer.canvas);
1390
+ this.snapper = new Snapper(viewer.camera, viewer.clippingPlanes, viewer.canvas);
1376
1391
  this.snapper.threshold = extentsSize / 10000;
1377
1392
  this.objects = [];
1378
1393
  this.updateObjects();
@@ -2651,8 +2666,8 @@ commands.registerCommand("left", (viewer) => setDefaultViewPosition(viewer, "lef
2651
2666
  commands.registerCommand("right", (viewer) => setDefaultViewPosition(viewer, "right"));
2652
2667
  commands.registerCommand("front", (viewer) => setDefaultViewPosition(viewer, "front"));
2653
2668
  commands.registerCommand("back", (viewer) => setDefaultViewPosition(viewer, "back"));
2654
- commands.registerCommand("sw", (viewer) => setDefaultViewPosition(viewer, "sw"));
2655
2669
  commands.registerCommand("se", (viewer) => setDefaultViewPosition(viewer, "se"));
2670
+ commands.registerCommand("sw", (viewer) => setDefaultViewPosition(viewer, "sw"));
2656
2671
  commands.registerCommand("ne", (viewer) => setDefaultViewPosition(viewer, "ne"));
2657
2672
  commands.registerCommand("nw", (viewer) => setDefaultViewPosition(viewer, "nw"));
2658
2673
  commands.registerCommandAlias("clearMarkup", "clearOverlay");
@@ -2675,6 +2690,7 @@ class BackgroundComponent {
2675
2690
  this.syncOptions = () => {
2676
2691
  this.backgroundColor.setHex(0xffffff);
2677
2692
  this.viewer.renderer.setClearColor(this.backgroundColor);
2693
+ this.viewer.update();
2678
2694
  };
2679
2695
  this.viewer = viewer;
2680
2696
  this.backgroundColor = new Color(0xffffff);
@@ -2684,13 +2700,13 @@ class BackgroundComponent {
2684
2700
  }
2685
2701
  dispose() {
2686
2702
  this.viewer.removeEventListener("optionschange", this.syncOptions);
2687
- this.viewer.scene.background = undefined;
2703
+ this.viewer.scene.background = null;
2688
2704
  }
2689
2705
  }
2690
2706
 
2691
2707
  class CameraComponent {
2692
2708
  constructor(viewer) {
2693
- this.optionsChange = () => {
2709
+ this.syncOptions = () => {
2694
2710
  this.switchCameraMode(this.viewer.options.cameraMode);
2695
2711
  };
2696
2712
  this.geometryEnd = () => {
@@ -2721,13 +2737,13 @@ class CameraComponent {
2721
2737
  };
2722
2738
  this.viewer = viewer;
2723
2739
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
2724
- this.viewer.addEventListener("optionschange", this.optionsChange);
2725
- this.viewer.addEventListener("initialize", this.optionsChange);
2740
+ this.viewer.addEventListener("optionschange", this.syncOptions);
2741
+ this.viewer.addEventListener("initialize", this.syncOptions);
2726
2742
  }
2727
2743
  dispose() {
2728
2744
  this.viewer.removeEventListener("databasechunk", this.geometryEnd);
2729
- this.viewer.removeEventListener("optionschange", this.optionsChange);
2730
- this.viewer.removeEventListener("initialize", this.optionsChange);
2745
+ this.viewer.removeEventListener("optionschange", this.syncOptions);
2746
+ this.viewer.removeEventListener("initialize", this.syncOptions);
2731
2747
  }
2732
2748
  getCameraMode(camera) {
2733
2749
  return camera.isOrthographicCamera ? "orthographic" : "perspective";
@@ -2750,7 +2766,6 @@ class CameraComponent {
2750
2766
  camera.updateProjectionMatrix();
2751
2767
  this.viewer.camera = camera;
2752
2768
  this.viewer.renderPass.camera = camera;
2753
- this.viewer.helpersPass.camera = camera;
2754
2769
  this.viewer.ssaaRenderPass.camera = camera;
2755
2770
  this.viewer.update();
2756
2771
  }
@@ -2761,7 +2776,7 @@ class CameraComponent {
2761
2776
  if (mode === this.getCameraMode(currentCamera))
2762
2777
  return;
2763
2778
  const target = this.viewer.target.clone();
2764
- let camera;
2779
+ let camera = null;
2765
2780
  if (currentCamera.isOrthographicCamera) {
2766
2781
  const fov = currentCamera.userData.fov || 45;
2767
2782
  const fieldHeight = (currentCamera.top - currentCamera.bottom) / currentCamera.zoom;
@@ -2894,7 +2909,7 @@ class InfoComponent {
2894
2909
  console.log("WebGL Renderer:", this.viewer.info.system.webglRenderer);
2895
2910
  console.log("WebGL Vendor:", this.viewer.info.system.webglVendor);
2896
2911
  this.resize();
2897
- this.optionsChange({ data: this.viewer.options });
2912
+ this.syncOptions({ data: this.viewer.options });
2898
2913
  };
2899
2914
  this.clear = () => {
2900
2915
  this.viewer.info.performance.timeToFirstRender = 0;
@@ -2918,7 +2933,7 @@ class InfoComponent {
2918
2933
  this.viewer.info.memory.totalEstimatedGpuBytes = 0;
2919
2934
  this.viewer.info.memory.usedJSHeapSize = 0;
2920
2935
  };
2921
- this.optionsChange = ({ data: options }) => {
2936
+ this.syncOptions = ({ data: options }) => {
2922
2937
  if (options.antialiasing === false)
2923
2938
  this.viewer.info.render.antialiasing = "";
2924
2939
  else if (options.antialiasing === true)
@@ -2993,7 +3008,7 @@ class InfoComponent {
2993
3008
  this.frames = 0;
2994
3009
  this.viewer.addEventListener("initialize", this.initialize);
2995
3010
  this.viewer.addEventListener("clear", this.clear);
2996
- this.viewer.addEventListener("optionschange", this.optionsChange);
3011
+ this.viewer.addEventListener("optionschange", this.syncOptions);
2997
3012
  this.viewer.addEventListener("geometrystart", this.geometryStart);
2998
3013
  this.viewer.addEventListener("databasechunk", this.databaseChunk);
2999
3014
  this.viewer.addEventListener("geometryend", this.geometryEnd);
@@ -3004,7 +3019,7 @@ class InfoComponent {
3004
3019
  dispose() {
3005
3020
  this.viewer.removeEventListener("initialize", this.initialize);
3006
3021
  this.viewer.removeEventListener("clear", this.clear);
3007
- this.viewer.removeEventListener("optionschange", this.optionsChange);
3022
+ this.viewer.removeEventListener("optionschange", this.syncOptions);
3008
3023
  this.viewer.removeEventListener("geometrystart", this.geometryStart);
3009
3024
  this.viewer.removeEventListener("databasechunk", this.databaseChunk);
3010
3025
  this.viewer.removeEventListener("geometryend", this.geometryEnd);
@@ -3052,7 +3067,6 @@ class CanvasRemoveComponent {
3052
3067
  }
3053
3068
  dispose() {
3054
3069
  this.mutationObserver.disconnect();
3055
- this.mutationObserver = undefined;
3056
3070
  }
3057
3071
  }
3058
3072
 
@@ -3142,28 +3156,38 @@ class HighlighterComponent {
3142
3156
  polygonOffset: true,
3143
3157
  polygonOffsetFactor: 1,
3144
3158
  polygonOffsetUnits: 1,
3159
+ clippingPlanes: this.viewer.clippingPlanes,
3145
3160
  });
3146
3161
  this.edgesMaterial = new LineMaterial({
3147
3162
  linewidth: 1.5,
3148
3163
  resolution: new Vector2(window.innerWidth, window.innerHeight),
3164
+ clippingPlanes: this.viewer.clippingPlanes,
3149
3165
  });
3150
3166
  this.lineMaterial = new LineBasicMaterial({
3151
3167
  transparent: true,
3152
3168
  depthTest: true,
3153
3169
  depthWrite: true,
3170
+ clippingPlanes: this.viewer.clippingPlanes,
3154
3171
  });
3155
3172
  this.lineGlowMaterial = new LineMaterial({
3156
3173
  linewidth: 1.5,
3157
3174
  transparent: true,
3158
3175
  opacity: 0.8,
3159
3176
  resolution: new Vector2(window.innerWidth, window.innerHeight),
3177
+ clippingPlanes: this.viewer.clippingPlanes,
3160
3178
  });
3161
3179
  this.syncHighlightColors();
3162
3180
  };
3163
- this.optionsChange = () => {
3181
+ this.syncOptions = () => {
3164
3182
  this.syncHighlightColors();
3165
3183
  this.viewer.update();
3166
3184
  };
3185
+ this.viewerResize = (event) => {
3186
+ var _a, _b, _c;
3187
+ (_a = this.renderTarget) === null || _a === void 0 ? void 0 : _a.setSize(event.width, event.height);
3188
+ (_b = this.edgesMaterial) === null || _b === void 0 ? void 0 : _b.resolution.set(event.width, event.height);
3189
+ (_c = this.lineGlowMaterial) === null || _c === void 0 ? void 0 : _c.resolution.set(event.width, event.height);
3190
+ };
3167
3191
  this.viewer = viewer;
3168
3192
  const gl2 = viewer.canvas.getContext("webgl2");
3169
3193
  if (gl2) {
@@ -3176,13 +3200,13 @@ class HighlighterComponent {
3176
3200
  });
3177
3201
  }
3178
3202
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
3179
- this.viewer.addEventListener("optionschange", this.optionsChange);
3203
+ this.viewer.addEventListener("optionschange", this.syncOptions);
3180
3204
  this.viewer.addEventListener("resize", this.viewerResize);
3181
3205
  this.geometryEnd();
3182
3206
  }
3183
3207
  dispose() {
3184
3208
  this.viewer.removeEventListener("databasechunk", this.geometryEnd);
3185
- this.viewer.removeEventListener("optionschange", this.optionsChange);
3209
+ this.viewer.removeEventListener("optionschange", this.syncOptions);
3186
3210
  this.viewer.removeEventListener("resize", this.viewerResize);
3187
3211
  }
3188
3212
  highlight(objects) {
@@ -3261,12 +3285,6 @@ class HighlighterComponent {
3261
3285
  wireframe.visible = edgesVisibility;
3262
3286
  });
3263
3287
  }
3264
- viewerResize(event) {
3265
- var _a, _b, _c;
3266
- (_a = this.renderTarget) === null || _a === void 0 ? void 0 : _a.setSize(event.width, event.height);
3267
- (_b = this.edgesMaterial) === null || _b === void 0 ? void 0 : _b.resolution.set(event.width, event.height);
3268
- (_c = this.lineGlowMaterial) === null || _c === void 0 ? void 0 : _c.resolution.set(event.width, event.height);
3269
- }
3270
3288
  }
3271
3289
 
3272
3290
  class SelectionComponent {
@@ -3283,7 +3301,7 @@ class SelectionComponent {
3283
3301
  if (upPosition.distanceTo(this.downPosition) !== 0)
3284
3302
  return;
3285
3303
  const extentsSize = this.viewer.extents.getSize(new Vector3()).length() || 1;
3286
- const snapper = new Snapper(this.viewer.camera, this.viewer.renderer, this.viewer.canvas);
3304
+ const snapper = new Snapper(this.viewer.camera, this.viewer.clippingPlanes, this.viewer.canvas);
3287
3305
  snapper.threshold = extentsSize / 10000;
3288
3306
  let intersections = [];
3289
3307
  this.viewer.models.forEach((model) => {
@@ -3380,9 +3398,824 @@ class SelectionComponent {
3380
3398
  }
3381
3399
  }
3382
3400
 
3401
+ class ClippingPlaneComponent {
3402
+ constructor(viewer) {
3403
+ this.applyClippingPlanes = () => {
3404
+ this.viewer.models.forEach((model) => {
3405
+ model.scene.traverse((object) => {
3406
+ if (object.material) {
3407
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
3408
+ materials.forEach((material) => (material.clippingPlanes = this.viewer.clippingPlanes));
3409
+ }
3410
+ });
3411
+ });
3412
+ };
3413
+ this.viewer = viewer;
3414
+ this.viewer.addEventListener("geometryend", this.applyClippingPlanes);
3415
+ this.viewer.addEventListener("changecuttingplanes", this.applyClippingPlanes);
3416
+ }
3417
+ dispose() {
3418
+ this.viewer.removeEventListener("geometryend", this.applyClippingPlanes);
3419
+ this.viewer.removeEventListener("changecuttingplanes", this.applyClippingPlanes);
3420
+ }
3421
+ }
3422
+
3423
+ class PointHashGrid {
3424
+ constructor(tolerance = 1e-5) {
3425
+ this.tolerance = tolerance;
3426
+ this.points = [];
3427
+ this.grid = new Map();
3428
+ }
3429
+ _hash(hx, hy, hz) {
3430
+ return `${hx},${hy},${hz}`;
3431
+ }
3432
+ add(v) {
3433
+ const hx = Math.round(v.x / this.tolerance);
3434
+ const hy = Math.round(v.y / this.tolerance);
3435
+ const hz = Math.round(v.z / this.tolerance);
3436
+ for (let i = -1; i <= 1; i++) {
3437
+ for (let j = -1; j <= 1; j++) {
3438
+ for (let k = -1; k <= 1; k++) {
3439
+ const hash = this._hash(hx + i, hy + j, hz + k);
3440
+ const cell = this.grid.get(hash);
3441
+ if (cell) {
3442
+ for (const id of cell) {
3443
+ if (this.points[id].distanceTo(v) <= this.tolerance) return id;
3444
+ }
3445
+ }
3446
+ }
3447
+ }
3448
+ }
3449
+ const id = this.points.length;
3450
+ this.points.push(v.clone());
3451
+ const centerHash = this._hash(hx, hy, hz);
3452
+ if (!this.grid.has(centerHash)) this.grid.set(centerHash, []);
3453
+ this.grid.get(centerHash).push(id);
3454
+ return id;
3455
+ }
3456
+ }
3457
+ class SectionsHelper extends Object3D {
3458
+ constructor() {
3459
+ super();
3460
+ this.type = "SectionsHelper";
3461
+ this.flags = {
3462
+ fillEnabled: true,
3463
+ fillColor: "#fffde7",
3464
+ hatchEnabled: true,
3465
+ hatchColor: "#000000",
3466
+ hatchScale: 8.0,
3467
+ outlineEnabled: true,
3468
+ outlineColor: "#000000",
3469
+ outlineWidth: 2,
3470
+ boundaryOnly: true,
3471
+ showDebugSeams: false,
3472
+ showDebugPoints: false,
3473
+ showDebugSegments: false,
3474
+ showDebugGaps: false,
3475
+ showDebugInfo: false,
3476
+ useObjFillColor: false,
3477
+ useObjOutlineColor: false,
3478
+ };
3479
+ this._caps = [];
3480
+ this._outlines = [];
3481
+ this._debugPoints = [];
3482
+ this._debugSegments = [];
3483
+ this._debugGaps = [];
3484
+ this._vA = new Vector3();
3485
+ this._vB = new Vector3();
3486
+ this._vC = new Vector3();
3487
+ this._worldBox = new Box3();
3488
+ }
3489
+ dispose() {
3490
+ const disposeMesh = (item) => {
3491
+ if (item.geometry) item.geometry.dispose();
3492
+ if (item.material) item.material.dispose();
3493
+ this.remove(item);
3494
+ };
3495
+ this._caps.forEach(disposeMesh);
3496
+ this._outlines.forEach(disposeMesh);
3497
+ this._debugPoints.forEach(disposeMesh);
3498
+ this._debugSegments.forEach(disposeMesh);
3499
+ this._debugGaps.forEach(disposeMesh);
3500
+ this._caps.length = 0;
3501
+ this._outlines.length = 0;
3502
+ this._debugPoints.length = 0;
3503
+ this._debugSegments.length = 0;
3504
+ this._debugGaps.length = 0;
3505
+ }
3506
+ setSize(width, height) {
3507
+ this._outlines.forEach((o) => {
3508
+ if (o.material.resolution) o.material.resolution.set(width, height);
3509
+ });
3510
+ this._debugSegments.forEach((s) => {
3511
+ if (s.material.resolution) s.material.resolution.set(width, height);
3512
+ });
3513
+ }
3514
+ _ensureHelpersCount(count) {
3515
+ const hatchVertexShader = `
3516
+ #include <common>
3517
+ #include <logdepthbuf_pars_vertex>
3518
+ #include <clipping_planes_pars_vertex>
3519
+
3520
+ attribute float aHatchDir;
3521
+ attribute vec3 aFillColor;
3522
+
3523
+ varying vec3 vWP;
3524
+ varying float vHatchDir;
3525
+ varying vec3 vFillColor;
3526
+
3527
+ void main() {
3528
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
3529
+ #include <clipping_planes_vertex>
3530
+
3531
+ vWP = (modelMatrix * vec4(position, 1.0)).xyz;
3532
+ vHatchDir = aHatchDir;
3533
+ vFillColor = aFillColor;
3534
+
3535
+ gl_Position = projectionMatrix * mvPosition;
3536
+ #include <logdepthbuf_vertex>
3537
+ }
3538
+ `;
3539
+ const hatchFragmentShader = `
3540
+ #include <common>
3541
+ #include <logdepthbuf_pars_fragment>
3542
+ #include <clipping_planes_pars_fragment>
3543
+
3544
+ uniform vec3 lineColor;
3545
+ uniform float uHatchScale;
3546
+ uniform float uHatchEnabled;
3547
+
3548
+ varying vec3 vWP;
3549
+ varying float vHatchDir;
3550
+ varying vec3 vFillColor;
3551
+
3552
+ void main() {
3553
+ #include <clipping_planes_fragment>
3554
+ #include <logdepthbuf_fragment>
3555
+
3556
+ float v1 = mod(gl_FragCoord.x + gl_FragCoord.y, uHatchScale);
3557
+ float v2 = mod(gl_FragCoord.x - gl_FragCoord.y, uHatchScale);
3558
+ float v = mix(v1, v2, vHatchDir);
3559
+
3560
+ float hatchMask = step(v, 1.5) * uHatchEnabled;
3561
+ gl_FragColor = vec4(mix(vFillColor, lineColor, hatchMask), 1.0);
3562
+ }
3563
+ `;
3564
+ while (this._caps.length < count) {
3565
+ const capMat = new ShaderMaterial({
3566
+ uniforms: {
3567
+ lineColor: { value: new Color() },
3568
+ uHatchScale: { value: 10.0 },
3569
+ uHatchEnabled: { value: 1.0 },
3570
+ },
3571
+ vertexShader: hatchVertexShader,
3572
+ fragmentShader: hatchFragmentShader,
3573
+ side: DoubleSide,
3574
+ clipping: true,
3575
+ depthTest: true,
3576
+ depthWrite: false,
3577
+ });
3578
+ const capMesh = new Mesh(new BufferGeometry(), capMat);
3579
+ capMesh.renderOrder = 5;
3580
+ this.add(capMesh);
3581
+ this._caps.push(capMesh);
3582
+ const lineMat = new LineMaterial({
3583
+ color: 0xffffff,
3584
+ linewidth: 2,
3585
+ resolution: new Vector2(window.innerWidth, window.innerHeight),
3586
+ depthTest: true,
3587
+ clipping: true,
3588
+ vertexColors: true,
3589
+ });
3590
+ const lineObj = new LineSegments2(new LineSegmentsGeometry(), lineMat);
3591
+ lineObj.renderOrder = 100;
3592
+ this.add(lineObj);
3593
+ this._outlines.push(lineObj);
3594
+ const ptsMat = new PointsMaterial({
3595
+ color: 0x00aaff,
3596
+ size: 6,
3597
+ sizeAttenuation: false,
3598
+ depthTest: false,
3599
+ transparent: true,
3600
+ depthWrite: false,
3601
+ });
3602
+ const pointsObj = new Points(new BufferGeometry(), ptsMat);
3603
+ pointsObj.renderOrder = 200;
3604
+ this.add(pointsObj);
3605
+ this._debugPoints.push(pointsObj);
3606
+ const debugSegMat = new LineMaterial({
3607
+ color: 0x00ff00,
3608
+ linewidth: 4,
3609
+ resolution: new Vector2(window.innerWidth, window.innerHeight),
3610
+ depthTest: false,
3611
+ transparent: true,
3612
+ depthWrite: false,
3613
+ clipping: true,
3614
+ });
3615
+ const debugSegObj = new LineSegments2(new LineSegmentsGeometry(), debugSegMat);
3616
+ debugSegObj.renderOrder = 150;
3617
+ this.add(debugSegObj);
3618
+ this._debugSegments.push(debugSegObj);
3619
+ const gapPtsMat = new PointsMaterial({
3620
+ size: 6,
3621
+ sizeAttenuation: false,
3622
+ depthTest: false,
3623
+ transparent: true,
3624
+ depthWrite: false,
3625
+ vertexColors: true,
3626
+ });
3627
+ const gapsObj = new Points(new BufferGeometry(), gapPtsMat);
3628
+ gapsObj.renderOrder = 250;
3629
+ this.add(gapsObj);
3630
+ this._debugGaps.push(gapsObj);
3631
+ }
3632
+ for (let i = count; i < this._caps.length; i++) {
3633
+ this._caps[i].visible = false;
3634
+ this._outlines[i].visible = false;
3635
+ this._debugPoints[i].visible = false;
3636
+ this._debugSegments[i].visible = false;
3637
+ this._debugGaps[i].visible = false;
3638
+ }
3639
+ }
3640
+ update(objects, extents, planes) {
3641
+ const t0 = performance.now();
3642
+ this._ensureHelpersCount(planes.length);
3643
+ if (planes.length === 0) return;
3644
+ const sphere = extents.getBoundingSphere(new Sphere());
3645
+ const globalRadius = Math.max(sphere.radius, 1e-3);
3646
+ const clippingBias = globalRadius * 1e-4;
3647
+ const biasedPlanes = planes.map((p) => {
3648
+ const bp = p.clone();
3649
+ bp.constant += clippingBias;
3650
+ return bp;
3651
+ });
3652
+ const targetMeshes = [];
3653
+ objects.forEach((obj) => {
3654
+ if (obj.isMesh && obj.material) {
3655
+ const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
3656
+ if (mats.some((m) => m.clippingPlanes)) targetMeshes.push(obj);
3657
+ }
3658
+ });
3659
+ planes.forEach((plane, pIdx) => {
3660
+ const capMesh = this._caps[pIdx];
3661
+ const outlineMesh = this._outlines[pIdx];
3662
+ const debugPtsMesh = this._debugPoints[pIdx];
3663
+ const debugSegsMesh = this._debugSegments[pIdx];
3664
+ const debugGapsMesh = this._debugGaps[pIdx];
3665
+ const hatchColor = new Color(this.flags.hatchColor);
3666
+ hatchColor.convertLinearToSRGB();
3667
+ capMesh.material.uniforms.lineColor.value.set(hatchColor);
3668
+ capMesh.material.uniforms.uHatchScale.value = this.flags.hatchScale;
3669
+ capMesh.material.uniforms.uHatchEnabled.value = this.flags.hatchEnabled ? 1.0 : 0.0;
3670
+ outlineMesh.material.linewidth = this.flags.outlineWidth;
3671
+ const otherBiasedPlanes = biasedPlanes.filter((_, i) => i !== pIdx);
3672
+ capMesh.material.clippingPlanes = otherBiasedPlanes;
3673
+ outlineMesh.material.clippingPlanes = otherBiasedPlanes;
3674
+ debugSegsMesh.material.clippingPlanes = otherBiasedPlanes;
3675
+ const n = plane.normal;
3676
+ const planeOrigin = n.clone().multiplyScalar(-plane.constant);
3677
+ const up = new Vector3(0, 1, 0);
3678
+ if (Math.abs(n.dot(up)) > 0.999) up.set(1, 0, 0);
3679
+ const uAxis = new Vector3().crossVectors(up, n).normalize();
3680
+ const vAxis = new Vector3().crossVectors(n, uAxis).normalize();
3681
+ const positions = [];
3682
+ const indices = [];
3683
+ const hatchDirs = [];
3684
+ const fillColors = [];
3685
+ const combinedOutlinePoints = [];
3686
+ const combinedOutlineColors = [];
3687
+ const rawPts = [];
3688
+ const rawSegs = [];
3689
+ const rawGaps = [];
3690
+ const rawGapColors = [];
3691
+ targetMeshes.forEach((mesh, meshIndex) => {
3692
+ if (!mesh.geometry.boundingBox) mesh.geometry.computeBoundingBox();
3693
+ if (!mesh.geometry.boundingSphere) mesh.geometry.computeBoundingSphere();
3694
+ this._worldBox.copy(mesh.geometry.boundingBox).applyMatrix4(mesh.matrixWorld);
3695
+ if (!plane.intersectsBox(this._worldBox)) return;
3696
+ const localScale = new Vector3().setFromMatrixScale(mesh.matrixWorld);
3697
+ const maxScale = Math.max(localScale.x, localScale.y, localScale.z);
3698
+ const localRadius = Math.max(mesh.geometry.boundingSphere.radius * maxScale, 1e-3);
3699
+ const localHashTolerance = Math.max(localRadius * 1e-4, 1e-6);
3700
+ const localEps = Math.max(localRadius * 1e-5, 1e-7);
3701
+ const baseColor = new Color(0xffffff);
3702
+ const om = mesh.userData.originalMaterial;
3703
+ const mm = om ?? (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material);
3704
+ if (mm.color) baseColor.copy(mm.color);
3705
+ const objFillColor = baseColor.clone().lerp(new Color(0x000000), 0.2);
3706
+ const objOutlineColor = baseColor.clone().lerp(new Color(0x000000), 0.85);
3707
+ const hue = ((meshIndex * 137.5) % 360) / 360;
3708
+ const meshGapColor = new Color().setHSL(hue, 1.0, 0.5);
3709
+ const currentHatchDir = meshIndex % 2 === 0 ? 0.0 : 1.0;
3710
+ const fillColor = this.flags.useObjFillColor ? objFillColor : new Color(this.flags.fillColor);
3711
+ const outlineColor = this.flags.useObjOutlineColor ? objOutlineColor : new Color(this.flags.outlineColor);
3712
+ meshGapColor.convertLinearToSRGB();
3713
+ fillColor.convertLinearToSRGB();
3714
+ outlineColor.convertLinearToSRGB();
3715
+ const localEdgeStats = new Map();
3716
+ const localPointGrid = new PointHashGrid(localHashTolerance);
3717
+ this._calculateMeshSegmentsUndirected(mesh, plane, localEdgeStats, localPointGrid, localEps);
3718
+ if (localEdgeStats.size > 0) {
3719
+ const boundaryEdges = [];
3720
+ for (const [key, stat] of localEdgeStats.entries()) {
3721
+ const isBoundary = stat.count % 2 !== 0;
3722
+ const ids = key.split("-");
3723
+ const id0 = Number(ids[0]);
3724
+ const id1 = Number(ids[1]);
3725
+ const p1 = localPointGrid.points[id0];
3726
+ const p2 = localPointGrid.points[id1];
3727
+ if (this.flags.showDebugSeams || (this.flags.boundaryOnly ? isBoundary : true)) {
3728
+ combinedOutlinePoints.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
3729
+ combinedOutlineColors.push(outlineColor.r, outlineColor.g, outlineColor.b);
3730
+ combinedOutlineColors.push(outlineColor.r, outlineColor.g, outlineColor.b);
3731
+ }
3732
+ if (isBoundary) boundaryEdges.push([id0, id1]);
3733
+ if (this.flags.showDebugSegments) rawSegs.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
3734
+ }
3735
+ if (this.flags.showDebugPoints) {
3736
+ for (const p of localPointGrid.points) rawPts.push(p.x, p.y, p.z);
3737
+ }
3738
+ if (this.flags.fillEnabled && boundaryEdges.length >= 3) {
3739
+ const currentAdj = new Map();
3740
+ for (const edge of boundaryEdges) {
3741
+ const a = edge[0];
3742
+ const b = edge[1];
3743
+ if (!currentAdj.has(a)) currentAdj.set(a, []);
3744
+ if (!currentAdj.has(b)) currentAdj.set(b, []);
3745
+ currentAdj.get(a).push(b);
3746
+ currentAdj.get(b).push(a);
3747
+ }
3748
+ const degree1 = [];
3749
+ for (const [node, neighbors] of currentAdj.entries()) {
3750
+ if (neighbors.length === 1) degree1.push(node);
3751
+ }
3752
+ const stitchTol = Math.max(localRadius * 0.05, 1e-4);
3753
+ for (let i = 0; i < degree1.length; i++) {
3754
+ const n1 = degree1[i];
3755
+ if (currentAdj.get(n1).length !== 1) continue;
3756
+ const p1 = localPointGrid.points[n1];
3757
+ let bestEdgeIdx = -1;
3758
+ let bestProj = null;
3759
+ let bestT = 0;
3760
+ let minDist = stitchTol;
3761
+ const edgeCount = boundaryEdges.length;
3762
+ for (let eIdx = 0; eIdx < edgeCount; eIdx++) {
3763
+ const edge = boundaryEdges[eIdx];
3764
+ const eA = edge[0];
3765
+ const eB = edge[1];
3766
+ if (eA === n1 || eB === n1) continue;
3767
+ const pA = localPointGrid.points[eA];
3768
+ const pB = localPointGrid.points[eB];
3769
+ const lineVec = new Vector3().subVectors(pB, pA);
3770
+ const lineLenSq = lineVec.lengthSq();
3771
+ let proj;
3772
+ let t;
3773
+ if (lineLenSq < 1e-12) {
3774
+ proj = pA.clone();
3775
+ t = 0;
3776
+ } else {
3777
+ const ptVec = new Vector3().subVectors(p1, pA);
3778
+ t = ptVec.dot(lineVec) / lineLenSq;
3779
+ t = Math.max(0, Math.min(1, t));
3780
+ proj = new Vector3().copy(pA).addScaledVector(lineVec, t);
3781
+ }
3782
+ const dist = p1.distanceTo(proj);
3783
+ if (dist < minDist) {
3784
+ minDist = dist;
3785
+ bestEdgeIdx = eIdx;
3786
+ bestProj = proj;
3787
+ bestT = t;
3788
+ }
3789
+ }
3790
+ if (bestEdgeIdx !== -1) {
3791
+ const edge = boundaryEdges[bestEdgeIdx];
3792
+ const eA = edge[0];
3793
+ const eB = edge[1];
3794
+ if (bestT < 0.001) {
3795
+ boundaryEdges.push([n1, eA]);
3796
+ currentAdj.get(n1).push(eA);
3797
+ currentAdj.get(eA).push(n1);
3798
+ p1.copy(localPointGrid.points[eA]);
3799
+ } else if (bestT > 0.999) {
3800
+ boundaryEdges.push([n1, eB]);
3801
+ currentAdj.get(n1).push(eB);
3802
+ currentAdj.get(eB).push(n1);
3803
+ p1.copy(localPointGrid.points[eB]);
3804
+ } else {
3805
+ const newNodeId = localPointGrid.add(bestProj);
3806
+ edge[1] = newNodeId;
3807
+ boundaryEdges.push([newNodeId, eB]);
3808
+ boundaryEdges.push([n1, newNodeId]);
3809
+ const neighborsA = currentAdj.get(eA);
3810
+ neighborsA[neighborsA.indexOf(eB)] = newNodeId;
3811
+ const neighborsB = currentAdj.get(eB);
3812
+ neighborsB[neighborsB.indexOf(eA)] = newNodeId;
3813
+ if (!currentAdj.has(newNodeId)) currentAdj.set(newNodeId, []);
3814
+ currentAdj.get(newNodeId).push(eA, eB, n1);
3815
+ currentAdj.get(n1).push(newNodeId);
3816
+ p1.copy(bestProj);
3817
+ }
3818
+ }
3819
+ }
3820
+ if (this.flags.showDebugGaps) {
3821
+ for (const [node, neighbors] of currentAdj.entries()) {
3822
+ if (neighbors.length !== 2) {
3823
+ const p = localPointGrid.points[node];
3824
+ rawGaps.push(p.x, p.y, p.z);
3825
+ rawGapColors.push(meshGapColor.r, meshGapColor.g, meshGapColor.b);
3826
+ }
3827
+ }
3828
+ }
3829
+ const loops = this._assembleLoopsUndirected(boundaryEdges, localPointGrid, uAxis, vAxis);
3830
+ if (loops.length > 0) {
3831
+ this._triangulateTreeOptimized(
3832
+ loops,
3833
+ planeOrigin,
3834
+ uAxis,
3835
+ vAxis,
3836
+ positions,
3837
+ indices,
3838
+ localRadius,
3839
+ fillColor,
3840
+ currentHatchDir,
3841
+ hatchDirs,
3842
+ fillColors
3843
+ );
3844
+ }
3845
+ }
3846
+ }
3847
+ });
3848
+ if (indices.length > 0) {
3849
+ capMesh.geometry.dispose();
3850
+ capMesh.geometry = new BufferGeometry();
3851
+ capMesh.geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
3852
+ capMesh.geometry.setAttribute("aHatchDir", new Float32BufferAttribute(hatchDirs, 1));
3853
+ capMesh.geometry.setAttribute("aFillColor", new Float32BufferAttribute(fillColors, 3));
3854
+ capMesh.geometry.setIndex(indices);
3855
+ capMesh.geometry.computeVertexNormals();
3856
+ capMesh.visible = this.flags.fillEnabled;
3857
+ } else {
3858
+ capMesh.visible = false;
3859
+ }
3860
+ if (outlineMesh.geometry) outlineMesh.geometry.dispose();
3861
+ outlineMesh.geometry = new LineSegmentsGeometry();
3862
+ if (this.flags.outlineEnabled && combinedOutlinePoints.length >= 6) {
3863
+ outlineMesh.geometry.setPositions(new Float32Array(combinedOutlinePoints));
3864
+ outlineMesh.geometry.setColors(new Float32Array(combinedOutlineColors));
3865
+ outlineMesh.visible = true;
3866
+ } else {
3867
+ outlineMesh.visible = false;
3868
+ }
3869
+ if (this.flags.showDebugPoints && rawPts.length > 0) {
3870
+ debugPtsMesh.geometry.setAttribute("position", new Float32BufferAttribute(rawPts, 3));
3871
+ debugPtsMesh.visible = true;
3872
+ } else {
3873
+ debugPtsMesh.visible = false;
3874
+ }
3875
+ if (debugSegsMesh.geometry) debugSegsMesh.geometry.dispose();
3876
+ debugSegsMesh.geometry = new LineSegmentsGeometry();
3877
+ if (this.flags.showDebugSegments && rawSegs.length >= 6) {
3878
+ debugSegsMesh.geometry.setPositions(new Float32Array(rawSegs));
3879
+ debugSegsMesh.visible = true;
3880
+ } else {
3881
+ debugSegsMesh.visible = false;
3882
+ }
3883
+ if (debugGapsMesh.geometry) debugGapsMesh.geometry.dispose();
3884
+ debugGapsMesh.geometry = new BufferGeometry();
3885
+ if (this.flags.showDebugGaps && rawGaps.length > 0) {
3886
+ debugGapsMesh.geometry.setAttribute("position", new Float32BufferAttribute(rawGaps, 3));
3887
+ debugGapsMesh.geometry.setAttribute("color", new Float32BufferAttribute(rawGapColors, 3));
3888
+ debugGapsMesh.visible = true;
3889
+ } else {
3890
+ debugGapsMesh.visible = false;
3891
+ }
3892
+ });
3893
+ if (this.flags.showDebugInfo) {
3894
+ console.log(`[SectionsHelper] v7.00 Updated in ${(performance.now() - t0).toFixed(2)} ms`);
3895
+ }
3896
+ }
3897
+ _assembleLoopsUndirected(edges, pointGrid, uAxis, vAxis) {
3898
+ const adj = new Map();
3899
+ for (const edge of edges) {
3900
+ const a = edge[0];
3901
+ const b = edge[1];
3902
+ if (!adj.has(a)) adj.set(a, []);
3903
+ if (!adj.has(b)) adj.set(b, []);
3904
+ adj.get(a).push(b);
3905
+ adj.get(b).push(a);
3906
+ }
3907
+ const loops = [];
3908
+ while (adj.size > 0) {
3909
+ let startNode = -1;
3910
+ for (const key of adj.keys()) {
3911
+ if (adj.get(key).length > 0) {
3912
+ startNode = key;
3913
+ break;
3914
+ }
3915
+ }
3916
+ if (startNode === -1) break;
3917
+ let current = startNode;
3918
+ let prev = -1;
3919
+ const path = [];
3920
+ const pathIndices = new Map();
3921
+ while (true) {
3922
+ path.push(current);
3923
+ pathIndices.set(current, path.length - 1);
3924
+ const neighbors = adj.get(current);
3925
+ if (!neighbors || neighbors.length === 0) break;
3926
+ let nextIdx = 0;
3927
+ if (neighbors.length > 1 && prev !== -1) {
3928
+ const pPrev = pointGrid.points[prev];
3929
+ const pCurr = pointGrid.points[current];
3930
+ const vIn = new Vector3().subVectors(pCurr, pPrev);
3931
+ const in2d = new Vector2(vIn.dot(uAxis), vIn.dot(vAxis));
3932
+ if (in2d.lengthSq() > 1e-10) {
3933
+ in2d.normalize();
3934
+ let minAngle = Infinity;
3935
+ for (let i = 0; i < neighbors.length; i++) {
3936
+ const pNext = pointGrid.points[neighbors[i]];
3937
+ const vOut = new Vector3().subVectors(pNext, pCurr);
3938
+ const out2d = new Vector2(vOut.dot(uAxis), vOut.dot(vAxis));
3939
+ if (out2d.lengthSq() > 1e-10) {
3940
+ out2d.normalize();
3941
+ const angle = Math.atan2(in2d.cross(out2d), in2d.dot(out2d));
3942
+ if (angle < minAngle) {
3943
+ minAngle = angle;
3944
+ nextIdx = i;
3945
+ }
3946
+ }
3947
+ }
3948
+ }
3949
+ }
3950
+ const next = neighbors[nextIdx];
3951
+ neighbors.splice(nextIdx, 1);
3952
+ const nextNeighbors = adj.get(next);
3953
+ if (nextNeighbors) {
3954
+ const revIdx = nextNeighbors.indexOf(current);
3955
+ if (revIdx !== -1) nextNeighbors.splice(revIdx, 1);
3956
+ }
3957
+ prev = current;
3958
+ current = next;
3959
+ if (pathIndices.has(current)) {
3960
+ const loopStartIdx = pathIndices.get(current);
3961
+ const loopNodes = path.slice(loopStartIdx);
3962
+ if (loopNodes.length >= 3) loops.push(loopNodes.map((id) => pointGrid.points[id]));
3963
+ for (let i = loopStartIdx; i < path.length; i++) pathIndices.delete(path[i]);
3964
+ path.length = loopStartIdx;
3965
+ prev = path.length > 1 ? path[path.length - 2] : -1;
3966
+ }
3967
+ }
3968
+ for (const key of adj.keys()) {
3969
+ if (adj.get(key).length === 0) adj.delete(key);
3970
+ }
3971
+ }
3972
+ return loops;
3973
+ }
3974
+ _triangulateTreeOptimized(
3975
+ loops,
3976
+ planeOrigin,
3977
+ uAxis,
3978
+ vAxis,
3979
+ positionsBuffer,
3980
+ indicesBuffer,
3981
+ localRadius,
3982
+ fillColor,
3983
+ hatchDir,
3984
+ hatchDirsBuffer,
3985
+ fillColorsBuffer
3986
+ ) {
3987
+ const shapesData = [];
3988
+ const minArea = localRadius * 1e-5 * (localRadius * 1e-5);
3989
+ loops.forEach((loop) => {
3990
+ const pts2d = loop.map((p) => {
3991
+ const pv = p.clone().sub(planeOrigin);
3992
+ return new Vector2(pv.dot(uAxis), pv.dot(vAxis));
3993
+ });
3994
+ const cleaned = [];
3995
+ for (let k = 0; k < pts2d.length; k++) {
3996
+ const prev = k === 0 ? pts2d[pts2d.length - 1] : pts2d[k - 1];
3997
+ if (pts2d[k].distanceTo(prev) > 1e-5) cleaned.push(pts2d[k]);
3998
+ }
3999
+ if (cleaned.length < 3) return;
4000
+ const area = ShapeUtils.area(cleaned);
4001
+ if (Math.abs(area) > minArea) {
4002
+ shapesData.push({ pts2d: cleaned, absArea: Math.abs(area), depth: 0, parent: -1, holes: [] });
4003
+ }
4004
+ });
4005
+ shapesData.sort((a, b) => b.absArea - a.absArea);
4006
+ for (let i = 0; i < shapesData.length; i++) {
4007
+ for (let j = i - 1; j >= 0; j--) {
4008
+ if (shapesData[i].absArea > shapesData[j].absArea * 0.98) continue;
4009
+ if (this._isLoopInside(shapesData[i].pts2d, shapesData[j].pts2d, localRadius)) {
4010
+ shapesData[i].parent = j;
4011
+ shapesData[i].depth = shapesData[j].depth + 1;
4012
+ break;
4013
+ }
4014
+ }
4015
+ }
4016
+ for (let i = 0; i < shapesData.length; i++) {
4017
+ const shape = shapesData[i];
4018
+ if (shape.depth % 2 === 1 && shape.parent !== -1) {
4019
+ shapesData[shape.parent].holes.push(shape.pts2d);
4020
+ }
4021
+ }
4022
+ for (let i = 0; i < shapesData.length; i++) {
4023
+ const shapeData = shapesData[i];
4024
+ if (shapeData.depth % 2 !== 0) continue;
4025
+ if (ShapeUtils.area(shapeData.pts2d) < 0) {
4026
+ shapeData.pts2d.reverse();
4027
+ }
4028
+ shapeData.holes.forEach((h) => {
4029
+ if (ShapeUtils.area(h) > 0) h.reverse();
4030
+ });
4031
+ const allPoints = [...shapeData.pts2d];
4032
+ shapeData.holes.forEach((h) => allPoints.push(...h));
4033
+ const faces = ShapeUtils.triangulateShape(shapeData.pts2d, shapeData.holes);
4034
+ const vertexOffset = positionsBuffer.length / 3;
4035
+ for (const pt of allPoints) {
4036
+ const p3d = planeOrigin.clone().addScaledVector(uAxis, pt.x).addScaledVector(vAxis, pt.y);
4037
+ positionsBuffer.push(p3d.x, p3d.y, p3d.z);
4038
+ hatchDirsBuffer.push(hatchDir);
4039
+ fillColorsBuffer.push(fillColor.r, fillColor.g, fillColor.b);
4040
+ }
4041
+ for (let f = 0; f < faces.length; f++) {
4042
+ indicesBuffer.push(vertexOffset + faces[f][0], vertexOffset + faces[f][1], vertexOffset + faces[f][2]);
4043
+ }
4044
+ }
4045
+ }
4046
+ _isPointInPoly(pt, poly) {
4047
+ let inside = false;
4048
+ const py = pt.y + 1.119e-7;
4049
+ const px = pt.x;
4050
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
4051
+ if (
4052
+ poly[i].y > py !== poly[j].y > py &&
4053
+ px < ((poly[j].x - poly[i].x) * (py - poly[i].y)) / (poly[j].y - poly[i].y) + poly[i].x
4054
+ ) {
4055
+ inside = !inside;
4056
+ }
4057
+ }
4058
+ return inside;
4059
+ }
4060
+ _isLoopInside(child, parent, localRadius) {
4061
+ let minX1 = Infinity;
4062
+ let maxX1 = -Infinity;
4063
+ let minY1 = Infinity;
4064
+ let maxY1 = -Infinity;
4065
+ for (const p of child) {
4066
+ if (p.x < minX1) minX1 = p.x;
4067
+ if (p.x > maxX1) maxX1 = p.x;
4068
+ if (p.y < minY1) minY1 = p.y;
4069
+ if (p.y > maxY1) maxY1 = p.y;
4070
+ }
4071
+ let minX2 = Infinity;
4072
+ let maxX2 = -Infinity;
4073
+ let minY2 = Infinity;
4074
+ let maxY2 = -Infinity;
4075
+ for (const p of parent) {
4076
+ if (p.x < minX2) minX2 = p.x;
4077
+ if (p.x > maxX2) maxX2 = p.x;
4078
+ if (p.y < minY2) minY2 = p.y;
4079
+ if (p.y > maxY2) maxY2 = p.y;
4080
+ }
4081
+ const margin = Math.max(localRadius * 1e-4, 1e-5);
4082
+ if (minX1 < minX2 - margin || maxX1 > maxX2 + margin || minY1 < minY2 - margin || maxY1 > maxY2 + margin) {
4083
+ return false;
4084
+ }
4085
+ let insideCount = 0;
4086
+ for (let i = 0; i < child.length; i++) {
4087
+ if (this._isPointInPoly(child[i], parent)) {
4088
+ insideCount++;
4089
+ }
4090
+ }
4091
+ return insideCount >= child.length * 0.85;
4092
+ }
4093
+ _calculateMeshSegmentsUndirected(mesh, plane, edgeStats, grid, eps) {
4094
+ const geom = mesh.geometry;
4095
+ const pos = geom.attributes.position;
4096
+ const index = geom.index;
4097
+ const world = mesh.matrixWorld;
4098
+ const count = index ? index.count : pos.count;
4099
+ for (let i = 0; i < count; i += 3) {
4100
+ const i1 = index ? index.getX(i) : i;
4101
+ const i2 = index ? index.getX(i + 1) : i + 1;
4102
+ const i3 = index ? index.getX(i + 2) : i + 2;
4103
+ const v1 = this._vA.fromBufferAttribute(pos, i1).applyMatrix4(world);
4104
+ const v2 = this._vB.fromBufferAttribute(pos, i2).applyMatrix4(world);
4105
+ const v3 = this._vC.fromBufferAttribute(pos, i3).applyMatrix4(world);
4106
+ let d1 = plane.distanceToPoint(v1);
4107
+ let d2 = plane.distanceToPoint(v2);
4108
+ let d3 = plane.distanceToPoint(v3);
4109
+ if (Math.abs(d1) <= eps) d1 = eps;
4110
+ if (Math.abs(d2) <= eps) d2 = eps;
4111
+ if (Math.abs(d3) <= eps) d3 = eps;
4112
+ const s1 = d1 > 0 ? 1 : -1;
4113
+ const s2 = d2 > 0 ? 1 : -1;
4114
+ const s3 = d3 > 0 ? 1 : -1;
4115
+ if (s1 === s2 && s2 === s3) continue;
4116
+ const intersections = [];
4117
+ if (s1 !== s2) {
4118
+ intersections.push(new Vector3().lerpVectors(v1, v2, Math.abs(d1) / (Math.abs(d1) + Math.abs(d2))));
4119
+ }
4120
+ if (s2 !== s3) {
4121
+ intersections.push(new Vector3().lerpVectors(v2, v3, Math.abs(d2) / (Math.abs(d2) + Math.abs(d3))));
4122
+ }
4123
+ if (s3 !== s1) {
4124
+ intersections.push(new Vector3().lerpVectors(v3, v1, Math.abs(d3) / (Math.abs(d3) + Math.abs(d1))));
4125
+ }
4126
+ if (intersections.length >= 2) {
4127
+ const id1 = grid.add(intersections[0]);
4128
+ const id2 = grid.add(intersections[1]);
4129
+ if (id1 !== id2) {
4130
+ const key = id1 < id2 ? `${id1}-${id2}` : `${id2}-${id1}`;
4131
+ const stat = edgeStats.get(key) || { count: 0 };
4132
+ stat.count++;
4133
+ edgeStats.set(key, stat);
4134
+ }
4135
+ }
4136
+ }
4137
+ }
4138
+ }
4139
+
4140
+ class SectionsComponent {
4141
+ constructor(viewer) {
4142
+ this.updateTimerId = 0;
4143
+ this.updateDelay = 250;
4144
+ this.syncOptions = () => {
4145
+ function rgbToHex(c) {
4146
+ const hex = (v = 0) => v.toString(16).padStart(2, "0");
4147
+ return "#" + hex(c.r) + hex(c.g) + hex(c.b);
4148
+ }
4149
+ const options = this.viewer.options;
4150
+ const flags = this.sectionsHelper.flags;
4151
+ flags.fillEnabled = options.enableSectionFill;
4152
+ flags.fillColor = rgbToHex(options.sectionFillColor);
4153
+ flags.useObjFillColor = options.sectionUseObjectColor;
4154
+ flags.hatchEnabled = options.enableSectionHatch;
4155
+ flags.hatchColor = rgbToHex(options.sectionHatchColor);
4156
+ flags.hatchScale = options.sectionHatchScale;
4157
+ flags.outlineEnabled = options.enableSectionOutline;
4158
+ flags.outlineColor = rgbToHex(options.sectionOutlineColor);
4159
+ flags.outlineWidth = options.sectionOutlineWidth;
4160
+ this.syncSections();
4161
+ };
4162
+ this.syncHelper = () => {
4163
+ this.sectionsHelper.removeFromParent();
4164
+ this.viewer.helpers.add(this.sectionsHelper);
4165
+ };
4166
+ this.syncSections = () => {
4167
+ this.sectionsHelper.visible = false;
4168
+ clearTimeout(this.updateTimerId);
4169
+ this.updateTimerId = window.setTimeout(this.updateSections, this.updateDelay);
4170
+ };
4171
+ this.viewerResize = (event) => {
4172
+ this.sectionsHelper.setSize(event.width, event.height);
4173
+ };
4174
+ this.updateSections = () => {
4175
+ const objects = [];
4176
+ this.viewer.models.forEach((model) => objects.push(model.getVisibleObjects()));
4177
+ const objects2 = objects.flat();
4178
+ this.sectionsHelper.update(objects2, this.viewer.extents, this.viewer.clippingPlanes);
4179
+ this.sectionsHelper.visible = true;
4180
+ this.viewer.update();
4181
+ };
4182
+ this.sectionsHelper = new SectionsHelper();
4183
+ this.viewer = viewer;
4184
+ this.viewer.addEventListener("initialize", this.syncHelper);
4185
+ this.viewer.addEventListener("databasechunk", this.syncHelper);
4186
+ this.viewer.addEventListener("drawviewpoint", this.syncHelper);
4187
+ this.viewer.addEventListener("changecuttingplanes", this.syncSections);
4188
+ this.viewer.addEventListener("explode", this.syncSections);
4189
+ this.viewer.addEventListener("hide", this.syncSections);
4190
+ this.viewer.addEventListener("isolate", this.syncSections);
4191
+ this.viewer.addEventListener("show", this.syncSections);
4192
+ this.viewer.addEventListener("showall", this.syncSections);
4193
+ this.viewer.addEventListener("resize", this.viewerResize);
4194
+ this.viewer.addEventListener("optionschange", this.syncOptions);
4195
+ this.syncOptions();
4196
+ }
4197
+ dispose() {
4198
+ clearTimeout(this.updateTimerId);
4199
+ this.sectionsHelper.removeFromParent();
4200
+ this.sectionsHelper.dispose();
4201
+ this.viewer.removeEventListener("initialize", this.syncHelper);
4202
+ this.viewer.removeEventListener("databasechunk", this.syncHelper);
4203
+ this.viewer.removeEventListener("drawviewpoint", this.syncHelper);
4204
+ this.viewer.removeEventListener("changecuttingplanes", this.syncSections);
4205
+ this.viewer.removeEventListener("explode", this.syncSections);
4206
+ this.viewer.removeEventListener("hide", this.syncSections);
4207
+ this.viewer.removeEventListener("isolate", this.syncSections);
4208
+ this.viewer.removeEventListener("show", this.syncSections);
4209
+ this.viewer.removeEventListener("showall", this.syncSections);
4210
+ this.viewer.removeEventListener("resize", this.viewerResize);
4211
+ this.viewer.removeEventListener("optionschange", this.syncOptions);
4212
+ }
4213
+ }
4214
+
3383
4215
  class WCSHelper extends Object3D {
3384
4216
  constructor(camera) {
3385
4217
  super();
4218
+ this.type = "WCSHelper";
3386
4219
  this.camera = camera;
3387
4220
  this.size = 160;
3388
4221
  this.orthoCamera = new OrthographicCamera(-2, 2, 2, -2, 0, 4);
@@ -3442,11 +4275,13 @@ class WCSHelper extends Object3D {
3442
4275
  canvas.width = 64;
3443
4276
  canvas.height = 64;
3444
4277
  const context = canvas.getContext("2d");
3445
- context.clearRect(0, 0, 64, 64);
3446
- context.font = "24px Arial";
3447
- context.textAlign = "center";
3448
- context.fillStyle = color.getStyle();
3449
- context.fillText(text, 32, 41);
4278
+ if (context) {
4279
+ context.clearRect(0, 0, 64, 64);
4280
+ context.font = "24px Arial";
4281
+ context.textAlign = "center";
4282
+ context.fillStyle = color.getStyle();
4283
+ context.fillText(text, 32, 41);
4284
+ }
3450
4285
  const texture = new CanvasTexture(canvas);
3451
4286
  texture.colorSpace = SRGBColorSpace;
3452
4287
  return new SpriteMaterial({ map: texture, toneMapped: false });
@@ -3470,7 +4305,7 @@ class WCSHelper extends Object3D {
3470
4305
 
3471
4306
  class WCSHelperComponent {
3472
4307
  constructor(viewer) {
3473
- this.geometryEnd = () => {
4308
+ this.syncHelper = () => {
3474
4309
  this.wcsHelper.dispose();
3475
4310
  this.wcsHelper = new WCSHelper(this.viewer.camera);
3476
4311
  };
@@ -3484,14 +4319,14 @@ class WCSHelperComponent {
3484
4319
  };
3485
4320
  this.wcsHelper = new WCSHelper(viewer.camera);
3486
4321
  this.viewer = viewer;
3487
- this.viewer.addEventListener("databasechunk", this.geometryEnd);
3488
- this.viewer.addEventListener("drawviewpoint", this.geometryEnd);
4322
+ this.viewer.addEventListener("databasechunk", this.syncHelper);
4323
+ this.viewer.addEventListener("drawviewpoint", this.syncHelper);
3489
4324
  this.viewer.addEventListener("render", this.viewerRender);
3490
4325
  this.viewer.addEventListener("changecameramode", this.updateHelperCamera);
3491
4326
  }
3492
4327
  dispose() {
3493
- this.viewer.removeEventListener("databasechunk", this.geometryEnd);
3494
- this.viewer.removeEventListener("drawviewpoint", this.geometryEnd);
4328
+ this.viewer.removeEventListener("databasechunk", this.syncHelper);
4329
+ this.viewer.removeEventListener("drawviewpoint", this.syncHelper);
3495
4330
  this.viewer.removeEventListener("render", this.viewerRender);
3496
4331
  this.viewer.removeEventListener("changecameramode", this.updateHelperCamera);
3497
4332
  this.wcsHelper.dispose();
@@ -3537,11 +4372,14 @@ components.registerComponent("CanvasRemoveComponent", (viewer) => new CanvasRemo
3537
4372
  components.registerComponent("RenderLoopComponent", (viewer) => new RenderLoopComponent(viewer));
3538
4373
  components.registerComponent("HighlighterComponent", (viewer) => new HighlighterComponent(viewer));
3539
4374
  components.registerComponent("SelectionComponent", (viewer) => new SelectionComponent(viewer));
4375
+ components.registerComponent("ClippingPlaneComponent", (viewer) => new ClippingPlaneComponent(viewer));
4376
+ components.registerComponent("SectionsComponent", (viewer) => new SectionsComponent(viewer));
3540
4377
  components.registerComponent("WCSHelperComponent", (viewer) => new WCSHelperComponent(viewer));
3541
4378
  components.registerComponent("ResetComponent", (viewer) => new ResetComponent(viewer));
3542
4379
 
3543
4380
  class ModelImpl {
3544
4381
  constructor(scene) {
4382
+ this.id = "";
3545
4383
  this.scene = scene;
3546
4384
  this.handleToObjects = new Map();
3547
4385
  this.originalObjects = new Set();
@@ -3572,8 +4410,8 @@ class ModelImpl {
3572
4410
  if (object.material)
3573
4411
  disposeMaterials(object.material);
3574
4412
  }
3575
- this.handleToObjects = undefined;
3576
- this.originalObjects = undefined;
4413
+ this.handleToObjects.clear();
4414
+ this.originalObjects.clear();
3577
4415
  this.scene.traverse(disposeObject);
3578
4416
  this.scene.clear();
3579
4417
  }
@@ -3717,7 +4555,25 @@ class ModelImpl {
3717
4555
  return info;
3718
4556
  }
3719
4557
  getExtents(target) {
3720
- this.scene.traverseVisible((object) => target.expandByObject(object));
4558
+ const _box = new Box3();
4559
+ function expandByObject(object, target) {
4560
+ if (!object.geometry)
4561
+ return;
4562
+ object.updateWorldMatrix(false, false);
4563
+ if (object.boundingBox !== undefined) {
4564
+ if (object.boundingBox === null)
4565
+ object.computeBoundingBox();
4566
+ _box.copy(object.boundingBox);
4567
+ }
4568
+ else {
4569
+ if (object.geometry.boundingBox === null)
4570
+ object.geometry.computeBoundingBox();
4571
+ _box.copy(object.geometry.boundingBox);
4572
+ }
4573
+ _box.applyMatrix4(object.matrixWorld);
4574
+ target.union(_box);
4575
+ }
4576
+ this.scene.traverseVisible((object) => expandByObject(object, target));
3721
4577
  return target;
3722
4578
  }
3723
4579
  getObjects() {
@@ -3725,8 +4581,8 @@ class ModelImpl {
3725
4581
  }
3726
4582
  getVisibleObjects() {
3727
4583
  const objects = [];
3728
- this.scene.traverseVisible((object) => objects.push(object));
3729
- return objects.filter((object) => object.userData.handle);
4584
+ this.scene.traverseVisible((object) => object.userData.handle && objects.push(object));
4585
+ return objects;
3730
4586
  }
3731
4587
  getObjectsByHandles(handles) {
3732
4588
  if (!Array.isArray(handles))
@@ -3821,44 +4677,54 @@ class ModelImpl {
3821
4677
  centersCache.set(handle, target.clone());
3822
4678
  return target;
3823
4679
  };
3824
- function calcExplodeDepth(object, depth) {
3825
- let result = depth;
3826
- object.children.forEach((x) => {
3827
- const objectDepth = calcExplodeDepth(x, depth + 1);
3828
- if (result < objectDepth)
3829
- result = objectDepth;
3830
- });
4680
+ const calcObjectOffset = (object, target) => {
4681
+ const parent = object.parent;
4682
+ if (!parent || parent.userData.originalCenter === undefined)
4683
+ return target;
4684
+ return target.subVectors(object.userData.originalCenter, parent.userData.originalCenter);
4685
+ };
4686
+ const calcObjectDepth = (object) => {
4687
+ if (object.userData.depth !== undefined)
4688
+ return object.userData.depth;
4689
+ const parent = object.parent;
4690
+ const depth = parent && object !== explodeRoot ? calcObjectDepth(parent) + 1 : 0;
4691
+ object.userData.depth = depth;
3831
4692
  object.userData.originalPosition = object.position.clone();
3832
4693
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
3833
- return result;
3834
- }
4694
+ object.userData.originalOffset = calcObjectOffset(object, new Vector3());
4695
+ return depth;
4696
+ };
3835
4697
  const explodeScale = scale / 100;
3836
4698
  const explodeRoot = this.scene;
3837
- if (!explodeRoot.userData.explodeDepth) {
3838
- explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1);
4699
+ const explodeObjects = this.getObjects();
4700
+ if (explodeRoot.userData.explodeDepth === undefined) {
4701
+ let maxDepth = 0;
4702
+ explodeObjects.forEach((object) => {
4703
+ const depth = calcObjectDepth(object);
4704
+ if (depth > maxDepth)
4705
+ maxDepth = depth;
4706
+ });
4707
+ explodeRoot.userData.explodeDepth = maxDepth;
3839
4708
  }
3840
4709
  const maxDepth = explodeRoot.userData.explodeDepth;
3841
4710
  const scaledExplodeDepth = explodeScale * maxDepth + 1;
3842
4711
  const explodeDepth = 0 | scaledExplodeDepth;
3843
4712
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
3844
- function explodeObject(object, depth) {
4713
+ const explodeObject = (object) => {
3845
4714
  if (object.isCamera)
3846
4715
  return;
3847
- if (object.userData.isHighlightWireframe)
3848
- return;
3849
4716
  object.position.copy(object.userData.originalPosition);
4717
+ const depth = object.userData.depth;
3850
4718
  if (depth > 0 && depth <= explodeDepth) {
3851
4719
  let objectScale = explodeScale * coeff;
3852
4720
  if (depth === explodeDepth)
3853
4721
  objectScale *= currentSegmentFraction;
3854
- const parentCenter = object.parent.userData.originalCenter;
3855
- const objectCenter = object.userData.originalCenter;
3856
- const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
3857
- object.position.add(localOffset);
4722
+ object.position.addScaledVector(object.userData.originalOffset, objectScale);
3858
4723
  }
3859
- object.children.forEach((x) => explodeObject(x, depth + 1));
3860
- }
3861
- explodeObject(explodeRoot, 0);
4724
+ };
4725
+ explodeObjects.forEach((object) => {
4726
+ explodeObject(object);
4727
+ });
3862
4728
  this.scene.updateMatrixWorld();
3863
4729
  return this;
3864
4730
  }
@@ -3961,6 +4827,12 @@ class DynamicModelImpl extends ModelImpl {
3961
4827
  centersCache.set(handle, target.clone());
3962
4828
  return target;
3963
4829
  };
4830
+ const calcObjectOffset = (object, target) => {
4831
+ const parent = object.parent;
4832
+ if (!parent || parent.userData.originalCenter === undefined)
4833
+ return target;
4834
+ return target.subVectors(object.userData.originalCenter, parent.userData.originalCenter);
4835
+ };
3964
4836
  const calcObjectDepth = (object) => {
3965
4837
  if (object.userData.depth !== undefined)
3966
4838
  return object.userData.depth;
@@ -3969,13 +4841,15 @@ class DynamicModelImpl extends ModelImpl {
3969
4841
  object.userData.depth = depth;
3970
4842
  object.userData.originalPosition = object.position.clone();
3971
4843
  object.userData.originalCenter = calcObjectCenter(object, new Vector3());
4844
+ object.userData.originalOffset = calcObjectOffset(object, new Vector3());
3972
4845
  return depth;
3973
4846
  };
3974
4847
  const explodeScale = scale / 100;
3975
4848
  const explodeRoot = this.scene.children[0];
3976
- if (!explodeRoot.userData.explodeDepth) {
4849
+ const explodeObjects = this.getObjects();
4850
+ if (explodeRoot.userData.explodeDepth === undefined) {
3977
4851
  let maxDepth = 0;
3978
- this.gltfLoader.originalObjects.forEach((object) => {
4852
+ explodeObjects.forEach((object) => {
3979
4853
  const depth = calcObjectDepth(object);
3980
4854
  if (depth > maxDepth)
3981
4855
  maxDepth = depth;
@@ -3987,29 +4861,26 @@ class DynamicModelImpl extends ModelImpl {
3987
4861
  const explodeDepth = 0 | scaledExplodeDepth;
3988
4862
  const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
3989
4863
  const offsetCache = new Map();
3990
- const calcObjectOffset = (object, target) => {
4864
+ const calcExplodeOffset = (object, target) => {
3991
4865
  if (offsetCache.has(object))
3992
4866
  return target.copy(offsetCache.get(object));
3993
4867
  const parent = object.parent;
3994
4868
  if (parent && object !== explodeRoot)
3995
- calcObjectOffset(parent, target);
4869
+ calcExplodeOffset(parent, target);
3996
4870
  const depth = object.userData.depth;
3997
4871
  if (depth > 0 && depth <= explodeDepth) {
3998
4872
  let objectScale = explodeScale * coeff;
3999
4873
  if (depth === explodeDepth)
4000
4874
  objectScale *= currentSegmentFraction;
4001
- const parentCenter = parent.userData.originalCenter;
4002
- const objectCenter = object.userData.originalCenter;
4003
- const localOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale);
4004
- target.add(localOffset);
4875
+ target.addScaledVector(object.userData.originalOffset, objectScale);
4005
4876
  }
4006
4877
  offsetCache.set(object, target.clone());
4007
4878
  return target;
4008
4879
  };
4009
4880
  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));
4881
+ explodeObjects.forEach((object) => {
4882
+ const offset = calcExplodeOffset(object, new Vector3());
4883
+ transformMap.set(object, new Matrix4().makeTranslation(offset));
4013
4884
  });
4014
4885
  this.gltfLoader.applyObjectTransforms(transformMap);
4015
4886
  this.scene.updateMatrixWorld();
@@ -4080,6 +4951,7 @@ class GltfStructure {
4080
4951
  this._nextObjectId = 1;
4081
4952
  this.loadingAborted = false;
4082
4953
  this.criticalError = null;
4954
+ this.embeddedBinaryChunk = null;
4083
4955
  }
4084
4956
  async initialize(loader) {
4085
4957
  const json = await this.loadController.loadJson();
@@ -4088,28 +4960,72 @@ class GltfStructure {
4088
4960
  }
4089
4961
  this.json = json;
4090
4962
  this.loader = loader;
4091
- this.uri = this.json.buffers[0].uri || "";
4963
+ const bufferUri = this.json.buffers?.[0]?.uri || "";
4964
+ if (bufferUri.startsWith("data:")) {
4965
+ this.embeddedBinaryChunk = await this._decodeDataUri(bufferUri);
4966
+ this.uri = "";
4967
+ } else {
4968
+ this.uri = bufferUri;
4969
+ }
4970
+ }
4971
+ async _decodeDataUri(dataUri) {
4972
+ try {
4973
+ const response = await fetch(dataUri);
4974
+ return await response.arrayBuffer();
4975
+ } catch (e) {
4976
+ throw new Error(`DynamicLoader: Failed to decode embedded data URI: ${e.message}`);
4977
+ }
4092
4978
  }
4093
4979
  clear() {
4094
- this.json = null;
4095
- this.loadController = null;
4096
- this.pendingRequests = [];
4097
4980
  if (this.batchTimeout) {
4098
4981
  clearTimeout(this.batchTimeout);
4099
4982
  this.batchTimeout = null;
4100
4983
  }
4101
- this.disposeMaterials();
4102
- this.textureCache.clear();
4103
- this.materials.clear();
4984
+ this._rejectPendingRequests();
4985
+ if (this.disposeMaterials) {
4986
+ try {
4987
+ this.disposeMaterials();
4988
+ } catch (e) {
4989
+ console.warn("DynamicLoader: error during disposeMaterials in clear():", e);
4990
+ }
4991
+ }
4992
+ if (this.textureCache) this.textureCache.clear();
4993
+ if (this.materials) this.materials.clear();
4994
+ this.embeddedBinaryChunk = null;
4995
+ this.json = null;
4996
+ this.loadController = null;
4997
+ this.uri = "";
4104
4998
  this.activeChunkLoads = 0;
4105
4999
  this.chunkQueue = [];
4106
5000
  this.loadingAborted = false;
4107
5001
  this.criticalError = null;
4108
5002
  }
5003
+ _rejectPendingRequests() {
5004
+ const pending = this.pendingRequests;
5005
+ this.pendingRequests = [];
5006
+ if (!pending || pending.length === 0) return;
5007
+ const cancelError = new Error("DynamicLoader: Structure cleared while requests pending");
5008
+ for (let i = 0; i < pending.length; i++) {
5009
+ const item = pending[i];
5010
+ if (item && typeof item._reject === "function") {
5011
+ try {
5012
+ item._reject(cancelError);
5013
+ } catch {
5014
+ }
5015
+ }
5016
+ }
5017
+ }
4109
5018
  getJson() {
4110
5019
  return this.json;
4111
5020
  }
4112
5021
  scheduleRequest(request) {
5022
+ if (this.embeddedBinaryChunk && !this.uri) {
5023
+ return Promise.resolve({
5024
+ buffer: this.embeddedBinaryChunk,
5025
+ relOffset: request.offset,
5026
+ length: request.length,
5027
+ });
5028
+ }
4113
5029
  return new Promise((resolve, reject) => {
4114
5030
  if (this.loadingAborted) {
4115
5031
  reject(
@@ -4386,8 +5302,13 @@ class GltfStructure {
4386
5302
  return await this.textureLoader.loadAsync(fullUrl);
4387
5303
  } else if (image.bufferView !== undefined) {
4388
5304
  const bufferView = this.json.bufferViews[image.bufferView];
4389
- const array = await this.getBufferView(bufferView.byteOffset || 0, bufferView.byteLength, 5121);
4390
- const blob = new Blob([array], { type: image.mimeType });
5305
+ const { buffer, relOffset } = await this.getBufferView(
5306
+ bufferView.byteOffset || 0,
5307
+ bufferView.byteLength,
5308
+ 5121
5309
+ );
5310
+ const imageBytes = new Uint8Array(buffer, relOffset, bufferView.byteLength);
5311
+ const blob = new Blob([imageBytes], { type: image.mimeType });
4391
5312
  const url = URL.createObjectURL(blob);
4392
5313
  const texture = await this.textureLoader.loadAsync(url);
4393
5314
  URL.revokeObjectURL(url);
@@ -4417,6 +5338,7 @@ class GltfStructure {
4417
5338
  })
4418
5339
  );
4419
5340
  }
5341
+ await this.flushBufferRequests();
4420
5342
  await Promise.all(texturePromises);
4421
5343
  }
4422
5344
  loadMaterials() {
@@ -4625,6 +5547,7 @@ class GltfStructure {
4625
5547
  }
4626
5548
  }
4627
5549
 
5550
+ const DRACO_EXTENSION_NAME = "KHR_draco_mesh_compression";
4628
5551
  const STRUCTURE_ID_SEPARATOR = ":";
4629
5552
  class DynamicGltfLoader {
4630
5553
  constructor(camera, scene, renderer) {
@@ -4699,6 +5622,189 @@ class DynamicGltfLoader {
4699
5622
  this.transformData = null;
4700
5623
  this.identityTransformData = null;
4701
5624
  this.visibilityMaterials = new Set();
5625
+ this._dracoLoader = null;
5626
+ }
5627
+ setDracoLoader(loader = null) {
5628
+ this._dracoLoader = loader || null;
5629
+ }
5630
+ async _decodeDracoPrimitive(structure, primitive, dracoBufferData) {
5631
+ const dracoExt = primitive.extensions[DRACO_EXTENSION_NAME];
5632
+ const loader = this._dracoLoader;
5633
+ const attributeIDs = {};
5634
+ const attributeTypes = {};
5635
+ const gltfNameToThreeName = new Map();
5636
+ const threeNameToAccessor = new Map();
5637
+ for (const [gltfAttrName, dracoUniqueId] of Object.entries(dracoExt.attributes)) {
5638
+ const threeName = this._gltfAttributeNameToThreeName(gltfAttrName);
5639
+ attributeIDs[threeName] = dracoUniqueId;
5640
+ gltfNameToThreeName.set(gltfAttrName, threeName);
5641
+ const accessorIdx = primitive.attributes[gltfAttrName];
5642
+ if (accessorIdx !== undefined) {
5643
+ const accessor = structure.json.accessors[accessorIdx];
5644
+ attributeTypes[threeName] = this._gltfComponentTypeToTypedArrayName(accessor.componentType);
5645
+ threeNameToAccessor.set(threeName, accessor);
5646
+ }
5647
+ }
5648
+ const geometry = await loader.decodeGeometry(dracoBufferData, {
5649
+ attributeIDs,
5650
+ attributeTypes,
5651
+ useUniqueIDs: true,
5652
+ });
5653
+ for (const [threeName, accessor] of threeNameToAccessor) {
5654
+ const attribute = geometry.getAttribute(threeName);
5655
+ if (!attribute) continue;
5656
+ if (accessor.normalized === true) {
5657
+ attribute.normalized = true;
5658
+ }
5659
+ if (accessor.min) attribute.min = accessor.min;
5660
+ if (accessor.max) attribute.max = accessor.max;
5661
+ }
5662
+ for (const [threeName, accessor] of threeNameToAccessor) {
5663
+ const attribute = geometry.getAttribute(threeName);
5664
+ if (!attribute || !attribute.normalized) continue;
5665
+ const denom = this._normalizedDenominator(accessor.componentType);
5666
+ if (denom <= 0) continue;
5667
+ const src = attribute.array;
5668
+ const inv = 1 / denom;
5669
+ const isSigned = accessor.componentType === 5120 || accessor.componentType === 5122;
5670
+ const out = new Float32Array(src.length);
5671
+ for (let i = 0; i < src.length; i++) {
5672
+ let v = src[i] * inv;
5673
+ if (isSigned && v < -1) v = -1;
5674
+ out[i] = v;
5675
+ }
5676
+ const newAttr = new BufferAttribute(out, attribute.itemSize, false);
5677
+ if (accessor.min) newAttr.min = accessor.min;
5678
+ if (accessor.max) newAttr.max = accessor.max;
5679
+ geometry.setAttribute(threeName, newAttr);
5680
+ }
5681
+ return geometry;
5682
+ }
5683
+ _gltfComponentTypeToTypedArrayName(componentType) {
5684
+ switch (componentType) {
5685
+ case 5120:
5686
+ return "Int8Array";
5687
+ case 5121:
5688
+ return "Uint8Array";
5689
+ case 5122:
5690
+ return "Int16Array";
5691
+ case 5123:
5692
+ return "Uint16Array";
5693
+ case 5125:
5694
+ return "Uint32Array";
5695
+ case 5126:
5696
+ return "Float32Array";
5697
+ default:
5698
+ return "Float32Array";
5699
+ }
5700
+ }
5701
+ _normalizedDenominator(componentType) {
5702
+ switch (componentType) {
5703
+ case 5120:
5704
+ return 127;
5705
+ case 5121:
5706
+ return 255;
5707
+ case 5122:
5708
+ return 32767;
5709
+ case 5123:
5710
+ return 65535;
5711
+ default:
5712
+ return 0;
5713
+ }
5714
+ }
5715
+ _gltfAttributeNameToThreeName(name) {
5716
+ switch (name) {
5717
+ case "POSITION":
5718
+ return "position";
5719
+ case "NORMAL":
5720
+ return "normal";
5721
+ case "TANGENT":
5722
+ return "tangent";
5723
+ case "TEXCOORD_0":
5724
+ return "uv";
5725
+ case "TEXCOORD_1":
5726
+ return "uv2";
5727
+ case "COLOR_0":
5728
+ return "color";
5729
+ case "JOINTS_0":
5730
+ return "skinIndex";
5731
+ case "WEIGHTS_0":
5732
+ return "skinWeight";
5733
+ default:
5734
+ return name.toLowerCase();
5735
+ }
5736
+ }
5737
+ _buildAccessorRequest(structure, accessorIndex, type, primIdx) {
5738
+ const accessor = structure.json.accessors[accessorIndex];
5739
+ const bufferView = structure.json.bufferViews[accessor.bufferView];
5740
+ const components = structure.getNumComponents(accessor.type);
5741
+ const componentSize = structure.getComponentSize(accessor.componentType);
5742
+ const itemBytes = components * componentSize;
5743
+ const accessorByteOffset = accessor.byteOffset || 0;
5744
+ const bvByteOffset = bufferView.byteOffset || 0;
5745
+ const byteStride = bufferView.byteStride || 0;
5746
+ const interleaved = byteStride !== 0 && byteStride !== itemBytes;
5747
+ const offset = bvByteOffset + accessorByteOffset;
5748
+ let length;
5749
+ if (interleaved) {
5750
+ length = (accessor.count - 1) * byteStride + itemBytes;
5751
+ } else {
5752
+ length = accessor.count * itemBytes;
5753
+ }
5754
+ return {
5755
+ offset,
5756
+ length,
5757
+ componentType: accessor.componentType,
5758
+ accessorIndex,
5759
+ type,
5760
+ primIdx,
5761
+ _accessor: accessor,
5762
+ _components: components,
5763
+ _componentSize: componentSize,
5764
+ _itemBytes: itemBytes,
5765
+ _byteStride: byteStride,
5766
+ _interleaved: interleaved,
5767
+ };
5768
+ }
5769
+ _createGeometryAttribute(req) {
5770
+ const accessor = req._accessor;
5771
+ const components = req._components;
5772
+ const count = accessor.count;
5773
+ const stride = req._interleaved ? req._byteStride / req._componentSize : components;
5774
+ const normalized = accessor.normalized === true;
5775
+ const componentType = req.componentType;
5776
+ const src = req.data;
5777
+ if (!req._interleaved && !normalized) {
5778
+ return new BufferAttribute(src, components, false);
5779
+ }
5780
+ if (normalized) {
5781
+ const denom = this._normalizedDenominator(componentType);
5782
+ if (denom > 0) {
5783
+ const out = new Float32Array(count * components);
5784
+ const inv = 1 / denom;
5785
+ const isSignedNormalized = componentType === 5120 || componentType === 5122;
5786
+ for (let i = 0; i < count; i++) {
5787
+ const srcBase = i * stride;
5788
+ const dstBase = i * components;
5789
+ for (let c = 0; c < components; c++) {
5790
+ let v = src[srcBase + c] * inv;
5791
+ if (isSignedNormalized && v < -1) v = -1;
5792
+ out[dstBase + c] = v;
5793
+ }
5794
+ }
5795
+ return new BufferAttribute(out, components, false);
5796
+ }
5797
+ }
5798
+ const TypedArrayCtor = src.constructor;
5799
+ const out = new TypedArrayCtor(count * components);
5800
+ for (let i = 0; i < count; i++) {
5801
+ const srcBase = i * stride;
5802
+ const dstBase = i * components;
5803
+ for (let c = 0; c < components; c++) {
5804
+ out[dstBase + c] = src[srcBase + c];
5805
+ }
5806
+ }
5807
+ return new BufferAttribute(out, components, false);
4702
5808
  }
4703
5809
  createDummyTexture() {
4704
5810
  const data = new Float32Array(16);
@@ -4770,7 +5876,7 @@ class DynamicGltfLoader {
4770
5876
  if (!this.transformTexture) return;
4771
5877
  this.transformTexture.needsUpdate = true;
4772
5878
  }
4773
- setVisibleEdges(visible) {
5879
+ setVisibleEdges(visible = true) {
4774
5880
  this.visibleEdges = visible;
4775
5881
  }
4776
5882
  getAvailableMemory() {
@@ -5024,78 +6130,51 @@ class DynamicGltfLoader {
5024
6130
  node.loading = true;
5025
6131
  const meshDef = node.structure.getJson().meshes[node.meshIndex];
5026
6132
  try {
6133
+ if (
6134
+ !this._dracoLoader &&
6135
+ meshDef.primitives &&
6136
+ meshDef.primitives.some((p) => p.extensions && p.extensions[DRACO_EXTENSION_NAME])
6137
+ ) {
6138
+ throw new Error(
6139
+ "primitive uses KHR_draco_mesh_compression but no DRACOLoader is configured. " +
6140
+ "Inject one via dynamicLoader.setDracoLoader(new DRACOLoader()) before opening the file."
6141
+ );
6142
+ }
5027
6143
  const bufferRequests = [];
5028
6144
  const primitiveReqMap = new Map();
6145
+ const dracoPrimitives = new Map();
5029
6146
  for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
5030
6147
  const primitive = meshDef.primitives[primIdx];
5031
6148
  const reqs = [];
5032
- if (primitive.attributes.POSITION !== undefined) {
5033
- const accessorIndex = primitive.attributes.POSITION;
5034
- const accessor = node.structure.json.accessors[accessorIndex];
5035
- const bufferView = node.structure.json.bufferViews[accessor.bufferView];
5036
- const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
5037
- const components = node.structure.getNumComponents(accessor.type);
5038
- const count = accessor.count;
5039
- const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
5040
- reqs.push({
6149
+ const dracoExt = primitive.extensions && primitive.extensions[DRACO_EXTENSION_NAME];
6150
+ if (dracoExt) {
6151
+ const bufferView = node.structure.json.bufferViews[dracoExt.bufferView];
6152
+ const byteOffset = bufferView.byteOffset || 0;
6153
+ const byteLength = bufferView.byteLength;
6154
+ const dracoReq = {
5041
6155
  offset: byteOffset,
5042
6156
  length: byteLength,
5043
- componentType: accessor.componentType,
5044
- accessorIndex,
5045
- type: "position",
6157
+ componentType: 5121,
6158
+ type: "draco",
5046
6159
  primIdx,
5047
- });
6160
+ };
6161
+ reqs.push(dracoReq);
6162
+ dracoPrimitives.set(primIdx, { req: dracoReq, primitive });
6163
+ primitiveReqMap.set(primIdx, reqs);
6164
+ bufferRequests.push(...reqs);
6165
+ continue;
6166
+ }
6167
+ if (primitive.attributes.POSITION !== undefined) {
6168
+ reqs.push(this._buildAccessorRequest(node.structure, primitive.attributes.POSITION, "position", primIdx));
5048
6169
  }
5049
6170
  if (primitive.attributes.NORMAL !== undefined) {
5050
- const accessorIndex = primitive.attributes.NORMAL;
5051
- const accessor = node.structure.json.accessors[accessorIndex];
5052
- const bufferView = node.structure.json.bufferViews[accessor.bufferView];
5053
- const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
5054
- const components = node.structure.getNumComponents(accessor.type);
5055
- const count = accessor.count;
5056
- const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
5057
- reqs.push({
5058
- offset: byteOffset,
5059
- length: byteLength,
5060
- componentType: accessor.componentType,
5061
- accessorIndex,
5062
- type: "normal",
5063
- primIdx,
5064
- });
6171
+ reqs.push(this._buildAccessorRequest(node.structure, primitive.attributes.NORMAL, "normal", primIdx));
5065
6172
  }
5066
6173
  if (primitive.attributes.TEXCOORD_0 !== undefined) {
5067
- const accessorIndex = primitive.attributes.TEXCOORD_0;
5068
- const accessor = node.structure.json.accessors[accessorIndex];
5069
- const bufferView = node.structure.json.bufferViews[accessor.bufferView];
5070
- const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
5071
- const components = node.structure.getNumComponents(accessor.type);
5072
- const count = accessor.count;
5073
- const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
5074
- reqs.push({
5075
- offset: byteOffset,
5076
- length: byteLength,
5077
- componentType: accessor.componentType,
5078
- accessorIndex,
5079
- type: "uv",
5080
- primIdx,
5081
- });
6174
+ reqs.push(this._buildAccessorRequest(node.structure, primitive.attributes.TEXCOORD_0, "uv", primIdx));
5082
6175
  }
5083
6176
  if (primitive.indices !== undefined) {
5084
- const accessorIndex = primitive.indices;
5085
- const accessor = node.structure.json.accessors[accessorIndex];
5086
- const bufferView = node.structure.json.bufferViews[accessor.bufferView];
5087
- const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
5088
- const components = node.structure.getNumComponents(accessor.type);
5089
- const count = accessor.count;
5090
- const byteLength = count * components * node.structure.getComponentSize(accessor.componentType);
5091
- reqs.push({
5092
- offset: byteOffset,
5093
- length: byteLength,
5094
- componentType: accessor.componentType,
5095
- accessorIndex,
5096
- type: "index",
5097
- primIdx,
5098
- });
6177
+ reqs.push(this._buildAccessorRequest(node.structure, primitive.indices, "index", primIdx));
5099
6178
  }
5100
6179
  primitiveReqMap.set(primIdx, reqs);
5101
6180
  bufferRequests.push(...reqs);
@@ -5121,29 +6200,31 @@ class DynamicGltfLoader {
5121
6200
  }
5122
6201
  for (let primIdx = 0; primIdx < meshDef.primitives.length; primIdx++) {
5123
6202
  const primitive = meshDef.primitives[primIdx];
5124
- const geometry = new BufferGeometry();
5125
6203
  const reqs = primitiveReqMap.get(primIdx);
5126
- if (primitive.attributes.POSITION !== undefined) {
5127
- const req = reqs.find((r) => r.type === "position" && r.accessorIndex === primitive.attributes.POSITION);
5128
- const accessor = node.structure.json.accessors[primitive.attributes.POSITION];
5129
- const components = node.structure.getNumComponents(accessor.type);
5130
- geometry.setAttribute("position", new BufferAttribute(req.data, components));
5131
- }
5132
- if (primitive.attributes.NORMAL !== undefined) {
5133
- const req = reqs.find((r) => r.type === "normal" && r.accessorIndex === primitive.attributes.NORMAL);
5134
- const accessor = node.structure.json.accessors[primitive.attributes.NORMAL];
5135
- const components = node.structure.getNumComponents(accessor.type);
5136
- geometry.setAttribute("normal", new BufferAttribute(req.data, components));
5137
- }
5138
- if (primitive.attributes.TEXCOORD_0 !== undefined) {
5139
- const req = reqs.find((r) => r.type === "uv" && r.accessorIndex === primitive.attributes.TEXCOORD_0);
5140
- const accessor = node.structure.json.accessors[primitive.attributes.TEXCOORD_0];
5141
- const components = node.structure.getNumComponents(accessor.type);
5142
- geometry.setAttribute("uv", new BufferAttribute(req.data, components));
5143
- }
5144
- if (primitive.indices !== undefined) {
5145
- const req = reqs.find((r) => r.type === "index" && r.accessorIndex === primitive.indices);
5146
- geometry.setIndex(new BufferAttribute(req.data, 1));
6204
+ let geometry;
6205
+ if (dracoPrimitives.has(primIdx)) {
6206
+ const dracoReq = reqs.find((r) => r.type === "draco");
6207
+ const dracoBytes = new Uint8Array(dracoReq.data.buffer, dracoReq.data.byteOffset, dracoReq.data.byteLength);
6208
+ const dracoBuffer = dracoBytes.slice().buffer;
6209
+ geometry = await this._decodeDracoPrimitive(node.structure, primitive, dracoBuffer);
6210
+ } else {
6211
+ geometry = new BufferGeometry();
6212
+ if (primitive.attributes.POSITION !== undefined) {
6213
+ const req = reqs.find((r) => r.type === "position" && r.accessorIndex === primitive.attributes.POSITION);
6214
+ geometry.setAttribute("position", this._createGeometryAttribute(req));
6215
+ }
6216
+ if (primitive.attributes.NORMAL !== undefined) {
6217
+ const req = reqs.find((r) => r.type === "normal" && r.accessorIndex === primitive.attributes.NORMAL);
6218
+ geometry.setAttribute("normal", this._createGeometryAttribute(req));
6219
+ }
6220
+ if (primitive.attributes.TEXCOORD_0 !== undefined) {
6221
+ const req = reqs.find((r) => r.type === "uv" && r.accessorIndex === primitive.attributes.TEXCOORD_0);
6222
+ geometry.setAttribute("uv", this._createGeometryAttribute(req));
6223
+ }
6224
+ if (primitive.indices !== undefined) {
6225
+ const req = reqs.find((r) => r.type === "index" && r.accessorIndex === primitive.indices);
6226
+ geometry.setIndex(this._createGeometryAttribute(req));
6227
+ }
5147
6228
  }
5148
6229
  let material;
5149
6230
  if (primitive.material !== undefined) {
@@ -5382,20 +6463,43 @@ class DynamicGltfLoader {
5382
6463
  const nodeMatrix = new Matrix4();
5383
6464
  const uniqueNodeId = `${structure.id}_${nodeId}`;
5384
6465
  const meshDef = structure.json.meshes[nodeDef.mesh];
6466
+ if (!meshDef || !meshDef.primitives || meshDef.primitives.length === 0) {
6467
+ if (nodeDef.children) {
6468
+ for (const childId of nodeDef.children) {
6469
+ await this.processNodeHierarchy(structure, childId, nodeGroup || parentGroup);
6470
+ }
6471
+ }
6472
+ return nodeGroup;
6473
+ }
5385
6474
  const geometryExtents = new Box3();
5386
6475
  for (const primitive of meshDef.primitives) {
6476
+ if (!primitive.attributes) continue;
5387
6477
  const positionAccessor = structure.json.accessors[primitive.attributes.POSITION];
5388
6478
  if (positionAccessor && positionAccessor.min && positionAccessor.max) {
5389
- const primitiveBox = new Box3(
5390
- new Vector3().fromArray(positionAccessor.min),
5391
- new Vector3().fromArray(positionAccessor.max)
5392
- );
5393
- geometryExtents.union(primitiveBox);
6479
+ const minVec = new Vector3().fromArray(positionAccessor.min);
6480
+ const maxVec = new Vector3().fromArray(positionAccessor.max);
6481
+ if (positionAccessor.normalized === true) {
6482
+ const denom = this._normalizedDenominator(positionAccessor.componentType);
6483
+ if (denom > 0) {
6484
+ minVec.divideScalar(denom);
6485
+ maxVec.divideScalar(denom);
6486
+ if (positionAccessor.componentType === 5120 || positionAccessor.componentType === 5122) {
6487
+ minVec.x = Math.max(minVec.x, -1);
6488
+ minVec.y = Math.max(minVec.y, -1);
6489
+ minVec.z = Math.max(minVec.z, -1);
6490
+ maxVec.x = Math.max(maxVec.x, -1);
6491
+ maxVec.y = Math.max(maxVec.y, -1);
6492
+ maxVec.z = Math.max(maxVec.z, -1);
6493
+ }
6494
+ }
6495
+ }
6496
+ geometryExtents.union(new Box3(minVec, maxVec));
5394
6497
  }
5395
6498
  }
5396
6499
  let isEdge = false;
5397
- if (meshDef.primitives[0].material !== undefined) {
5398
- const material = structure.json.materials[meshDef.primitives[0].material];
6500
+ const firstPrimitive = meshDef.primitives[0];
6501
+ if (firstPrimitive && firstPrimitive.material !== undefined) {
6502
+ const material = structure.json.materials[firstPrimitive.material];
5399
6503
  if (material?.name === "edges") {
5400
6504
  isEdge = true;
5401
6505
  }
@@ -5708,6 +6812,10 @@ class DynamicGltfLoader {
5708
6812
  vec3 objectNormal = vec3( normal );
5709
6813
  mat3 bm = mat3( batchingMatrix );
5710
6814
  objectNormal = bm * objectNormal;
6815
+ #ifdef USE_TANGENT
6816
+ vec3 objectTangent = vec3( tangent.xyz );
6817
+ objectTangent = bm * objectTangent;
6818
+ #endif
5711
6819
  `
5712
6820
  );
5713
6821
  }
@@ -6880,7 +7988,7 @@ class DynamicGltfLoader {
6880
7988
  }
6881
7989
  return extent;
6882
7990
  }
6883
- setMaxConcurrentChunks(maxChunks) {
7991
+ setMaxConcurrentChunks(maxChunks = 6) {
6884
7992
  if (maxChunks < 1) {
6885
7993
  console.warn("Max concurrent chunks must be at least 1");
6886
7994
  return;
@@ -6982,10 +8090,10 @@ class GLTFBinaryParser {
6982
8090
  }
6983
8091
  offset += chunkLength;
6984
8092
  }
6985
- if (typeof this.content === "undefined") {
8093
+ if (this.content === undefined) {
6986
8094
  throw new Error("GLTFBinaryParser: JSON content not found.");
6987
8095
  }
6988
- if (typeof this.body === "undefined") {
8096
+ if (this.body === undefined) {
6989
8097
  throw new Error("GLTFBinaryParser: Binary buffer chunk not found or type not supported.");
6990
8098
  }
6991
8099
  }
@@ -7068,16 +8176,18 @@ class GLTFFileDynamicLoader extends Loader {
7068
8176
  if (this.manager)
7069
8177
  this.manager.dispose();
7070
8178
  }
7071
- isSupport(file, format) {
8179
+ isSupport(file, format = "") {
7072
8180
  return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
7073
8181
  /(gltf|glb)$/i.test(format));
7074
8182
  }
7075
- async load(file, format, params) {
8183
+ async load(file, format, params = {}) {
7076
8184
  this.manager = new GLTFLoadingManager(file, params);
7077
8185
  const scene = new Group();
7078
8186
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
7079
- this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
7080
- this.gltfLoader.visibleEdges = this.viewer.options.edgeModel;
8187
+ this.gltfLoader.setMemoryLimit(this.viewer.options.memoryLimit);
8188
+ this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
8189
+ this.gltfLoader.setMaxConcurrentChunks(params.maxConcurrentChunks);
8190
+ this.gltfLoader.setDracoLoader(params.dracoLoader);
7081
8191
  const modelImpl = new DynamicModelImpl(scene);
7082
8192
  modelImpl.id = params.modelId || this.extractFileName(file);
7083
8193
  modelImpl.gltfLoader = this.gltfLoader;
@@ -7164,8 +8274,10 @@ class GLTFCloudDynamicLoader extends Loader {
7164
8274
  async load(model, format, params = {}) {
7165
8275
  const scene = new Group();
7166
8276
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
7167
- this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
8277
+ this.gltfLoader.setMemoryLimit(this.viewer.options.memoryLimit);
7168
8278
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
8279
+ this.gltfLoader.setMaxConcurrentChunks(params.maxConcurrentChunks);
8280
+ this.gltfLoader.setDracoLoader(params.dracoLoader);
7169
8281
  const modelImpl = new DynamicModelImpl(scene);
7170
8282
  modelImpl.id = model.file.id;
7171
8283
  modelImpl.gltfLoader = this.gltfLoader;
@@ -7225,207 +8337,6 @@ const loaders = loadersRegistry("threejs");
7225
8337
  loaders.registerLoader("gltf-file", (viewer) => new GLTFFileDynamicLoader(viewer));
7226
8338
  loaders.registerLoader("gltf-cloud", (viewer) => new GLTFCloudDynamicLoader(viewer));
7227
8339
 
7228
- class SSAARenderPass extends Pass {
7229
- constructor(scenes, camera, clearColor = 0x000000, clearAlpha = 0) {
7230
- super();
7231
- this.scenes = Array.isArray(scenes) ? scenes : [scenes];
7232
- this.camera = camera;
7233
- this.sampleLevel = 2;
7234
- this.unbiased = true;
7235
- this.stencilBuffer = false;
7236
- this.clearColor = clearColor;
7237
- this.clearAlpha = clearAlpha;
7238
- this._sampleRenderTarget = null;
7239
- this._oldClearColor = new Color();
7240
- this._copyUniforms = UniformsUtils.clone(CopyShader.uniforms);
7241
- this._copyMaterial = new ShaderMaterial({
7242
- uniforms: this._copyUniforms,
7243
- vertexShader: CopyShader.vertexShader,
7244
- fragmentShader: CopyShader.fragmentShader,
7245
- transparent: true,
7246
- depthTest: false,
7247
- depthWrite: false,
7248
- premultipliedAlpha: true,
7249
- blending: AdditiveBlending,
7250
- });
7251
- this._fsQuad = new FullScreenQuad(this._copyMaterial);
7252
- }
7253
- dispose() {
7254
- if (this._sampleRenderTarget) {
7255
- this._sampleRenderTarget.dispose();
7256
- this._sampleRenderTarget = null;
7257
- }
7258
- this._copyMaterial.dispose();
7259
- this._fsQuad.dispose();
7260
- }
7261
- setSize(width, height) {
7262
- if (this._sampleRenderTarget) this._sampleRenderTarget.setSize(width, height);
7263
- }
7264
- render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) {
7265
- if (!this._sampleRenderTarget) {
7266
- this._sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, {
7267
- type: HalfFloatType,
7268
- stencilBuffer: this.stencilBuffer,
7269
- });
7270
- this._sampleRenderTarget.texture.name = "SSAAMultiRenderPass.sample";
7271
- }
7272
- const jitterOffsets = _JitterVectors[Math.max(0, Math.min(this.sampleLevel, 5))];
7273
- const autoClear = renderer.autoClear;
7274
- renderer.autoClear = false;
7275
- renderer.getClearColor(this._oldClearColor);
7276
- const oldClearAlpha = renderer.getClearAlpha();
7277
- const baseSampleWeight = 1.0 / jitterOffsets.length;
7278
- const roundingRange = 1 / 32;
7279
- this._copyUniforms["tDiffuse"].value = this._sampleRenderTarget.texture;
7280
- const viewOffset = {
7281
- fullWidth: readBuffer.width,
7282
- fullHeight: readBuffer.height,
7283
- offsetX: 0,
7284
- offsetY: 0,
7285
- width: readBuffer.width,
7286
- height: readBuffer.height,
7287
- };
7288
- const originalViewOffset = Object.assign({}, this.camera.view);
7289
- if (originalViewOffset.enabled) Object.assign(viewOffset, originalViewOffset);
7290
- for (let i = 0; i < jitterOffsets.length; i++) {
7291
- const jitterOffset = jitterOffsets[i];
7292
- if (this.camera.setViewOffset) {
7293
- this.camera.setViewOffset(
7294
- viewOffset.fullWidth,
7295
- viewOffset.fullHeight,
7296
- viewOffset.offsetX + jitterOffset[0] * 0.0625,
7297
- viewOffset.offsetY + jitterOffset[1] * 0.0625,
7298
- viewOffset.width,
7299
- viewOffset.height
7300
- );
7301
- }
7302
- let sampleWeight = baseSampleWeight;
7303
- if (this.unbiased) {
7304
- const uniformCenteredDistribution = -0.5 + (i + 0.5) / jitterOffsets.length;
7305
- sampleWeight += roundingRange * uniformCenteredDistribution;
7306
- }
7307
- this._copyUniforms["opacity"].value = sampleWeight;
7308
- renderer.setClearColor(this.clearColor, this.clearAlpha);
7309
- renderer.setRenderTarget(this._sampleRenderTarget);
7310
- renderer.clear();
7311
- this.scenes.forEach((scene) => renderer.render(scene, this.camera));
7312
- renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
7313
- if (i === 0) {
7314
- renderer.setClearColor(0x000000, 0.0);
7315
- renderer.clear();
7316
- }
7317
- this._fsQuad.render(renderer);
7318
- }
7319
- if (this.camera.setViewOffset && originalViewOffset.enabled) {
7320
- this.camera.setViewOffset(
7321
- originalViewOffset.fullWidth,
7322
- originalViewOffset.fullHeight,
7323
- originalViewOffset.offsetX,
7324
- originalViewOffset.offsetY,
7325
- originalViewOffset.width,
7326
- originalViewOffset.height
7327
- );
7328
- } else if (this.camera.clearViewOffset) {
7329
- this.camera.clearViewOffset();
7330
- }
7331
- renderer.autoClear = autoClear;
7332
- renderer.setClearColor(this._oldClearColor, oldClearAlpha);
7333
- }
7334
- }
7335
- const _JitterVectors = [
7336
- [[0, 0]],
7337
- [
7338
- [4, 4],
7339
- [-4, -4],
7340
- ],
7341
- [
7342
- [-2, -6],
7343
- [6, -2],
7344
- [-6, 2],
7345
- [2, 6],
7346
- ],
7347
- [
7348
- [1, -3],
7349
- [-1, 3],
7350
- [5, 1],
7351
- [-3, -5],
7352
- [-5, 5],
7353
- [-7, -1],
7354
- [3, 7],
7355
- [7, -7],
7356
- ],
7357
- [
7358
- [1, 1],
7359
- [-1, -3],
7360
- [-3, 2],
7361
- [4, -1],
7362
- [-5, -2],
7363
- [2, 5],
7364
- [5, 3],
7365
- [3, -5],
7366
- [-2, 6],
7367
- [0, -7],
7368
- [-4, -6],
7369
- [-6, 4],
7370
- [-8, 0],
7371
- [7, -4],
7372
- [6, 7],
7373
- [-7, -8],
7374
- ],
7375
- [
7376
- [-4, -7],
7377
- [-7, -5],
7378
- [-3, -5],
7379
- [-5, -4],
7380
- [-1, -4],
7381
- [-2, -2],
7382
- [-6, -1],
7383
- [-4, 0],
7384
- [-7, 1],
7385
- [-1, 2],
7386
- [-6, 3],
7387
- [-3, 3],
7388
- [-7, 6],
7389
- [-3, 6],
7390
- [-5, 7],
7391
- [-1, 7],
7392
- [5, -7],
7393
- [1, -6],
7394
- [6, -5],
7395
- [4, -4],
7396
- [2, -3],
7397
- [7, -2],
7398
- [1, -1],
7399
- [4, -1],
7400
- [2, 1],
7401
- [6, 2],
7402
- [0, 4],
7403
- [4, 4],
7404
- [2, 5],
7405
- [7, 5],
7406
- [5, 6],
7407
- [3, 7],
7408
- ],
7409
- ];
7410
-
7411
- class Helpers extends Scene {
7412
- constructor() {
7413
- super(...arguments);
7414
- this.oldAutoClear = false;
7415
- this.oldClippingPlanes = [];
7416
- }
7417
- onBeforeRender(renderer) {
7418
- this.oldAutoClear = renderer.autoClear;
7419
- this.oldClippingPlanes = renderer.clippingPlanes;
7420
- renderer.autoClear = false;
7421
- renderer.clippingPlanes = [];
7422
- }
7423
- onAfterRender(renderer) {
7424
- renderer.clippingPlanes = this.oldClippingPlanes;
7425
- renderer.autoClear = this.oldAutoClear;
7426
- }
7427
- }
7428
-
7429
8340
  class Viewer extends EventEmitter2 {
7430
8341
  constructor(client) {
7431
8342
  super();
@@ -7439,9 +8350,9 @@ class Viewer extends EventEmitter2 {
7439
8350
  this.selected = [];
7440
8351
  this.extents = new Box3();
7441
8352
  this.target = new Vector3(0, 0, 0);
8353
+ this.clippingPlanes = [];
7442
8354
  this._activeDragger = null;
7443
8355
  this._components = [];
7444
- this._updateDelay = 1000;
7445
8356
  this._renderNeeded = false;
7446
8357
  this._renderTime = 0;
7447
8358
  this.render = this.render.bind(this);
@@ -7460,7 +8371,9 @@ class Viewer extends EventEmitter2 {
7460
8371
  initialize(canvas, onProgress) {
7461
8372
  this.addEventListener("optionschange", (event) => this.syncOptions(event.data));
7462
8373
  this.scene = new Scene();
7463
- this.helpers = new Helpers();
8374
+ this.helpers = new Group();
8375
+ this.helpers.name = "Helpers";
8376
+ this.scene.add(this.helpers);
7464
8377
  const pixelRatio = window.devicePixelRatio;
7465
8378
  const rect = canvas.parentElement.getBoundingClientRect();
7466
8379
  const width = rect.width || 1;
@@ -7482,17 +8395,17 @@ class Viewer extends EventEmitter2 {
7482
8395
  this.renderer.setPixelRatio(pixelRatio);
7483
8396
  this.renderer.setSize(width, height);
7484
8397
  this.renderer.outputColorSpace = LinearSRGBColorSpace;
8398
+ this.renderer.localClippingEnabled = true;
7485
8399
  this.renderPass = new RenderPass(this.scene, this.camera);
7486
- this.helpersPass = new RenderPass(this.helpers, this.camera);
7487
- this.helpersPass.clear = false;
7488
8400
  this.fxaaPass = new FXAAPass();
7489
8401
  this.smaaPass = new SMAAPass();
7490
- this.ssaaRenderPass = new SSAARenderPass([this.scene, this.helpers], this.camera);
8402
+ this.ssaaRenderPass = new SSAARenderPass(this.scene, this.camera);
7491
8403
  this.ssaaRenderPass.unbiased = true;
7492
8404
  this.outputPass = new OutputPass();
7493
- this.composer = new EffectComposer(this.renderer);
8405
+ const renderTarget = new WebGLRenderTarget(1, 1, { samples: 4 });
8406
+ renderTarget.texture.name = "EffectComposer.rt1";
8407
+ this.composer = new EffectComposer(this.renderer, renderTarget);
7494
8408
  this.composer.addPass(this.renderPass);
7495
- this.composer.addPass(this.helpersPass);
7496
8409
  this.composer.addPass(this.smaaPass);
7497
8410
  this.composer.addPass(this.fxaaPass);
7498
8411
  this.composer.addPass(this.ssaaRenderPass);
@@ -7502,7 +8415,9 @@ class Viewer extends EventEmitter2 {
7502
8415
  this.canvasEvents.forEach((x) => canvas.addEventListener(x, this.canvaseventlistener));
7503
8416
  this._markup.initialize(this.canvas, this.canvasEvents, this, this);
7504
8417
  for (const name of components.getComponents().keys()) {
7505
- this._components.push(components.createComponent(name, this));
8418
+ const component = components.createComponent(name, this);
8419
+ if (component)
8420
+ this._components.push(component);
7506
8421
  }
7507
8422
  this.syncOptions();
7508
8423
  this.syncOverlay();
@@ -7523,7 +8438,7 @@ class Viewer extends EventEmitter2 {
7523
8438
  this.removeAllListeners();
7524
8439
  this.setActiveDragger();
7525
8440
  this._components.forEach((component) => component.dispose());
7526
- this._components = [];
8441
+ this._components.length = 0;
7527
8442
  this._markup.dispose();
7528
8443
  if (this.canvas) {
7529
8444
  this.canvasEvents.forEach((x) => this.canvas.removeEventListener(x, this.canvaseventlistener));
@@ -7533,8 +8448,6 @@ class Viewer extends EventEmitter2 {
7533
8448
  this.composer.dispose();
7534
8449
  if (this.renderPass)
7535
8450
  this.renderPass.dispose();
7536
- if (this.helpersPass)
7537
- this.helpersPass.dispose();
7538
8451
  if (this.fxaaPass)
7539
8452
  this.fxaaPass.dispose();
7540
8453
  if (this.smaaPass)
@@ -7550,7 +8463,6 @@ class Viewer extends EventEmitter2 {
7550
8463
  this.camera = undefined;
7551
8464
  this.renderer = undefined;
7552
8465
  this.renderPass = undefined;
7553
- this.helpersPass = undefined;
7554
8466
  this.fxaaPass = undefined;
7555
8467
  this.smaaPass = undefined;
7556
8468
  this.ssaaRenderPass = undefined;
@@ -7582,11 +8494,12 @@ class Viewer extends EventEmitter2 {
7582
8494
  }
7583
8495
  update(force = false) {
7584
8496
  const time = performance.now();
7585
- force = force || time - this._renderTime >= this._updateDelay;
8497
+ if (typeof force === "number" && time - this._renderTime >= force)
8498
+ force = true;
7586
8499
  this._renderNeeded = true;
7587
8500
  if (force)
7588
8501
  this.render(time);
7589
- this.emitEvent({ type: "update", force });
8502
+ this.emitEvent({ type: "update", force: !!force });
7590
8503
  }
7591
8504
  render(time, force = false) {
7592
8505
  if (!this.renderer)
@@ -7600,13 +8513,7 @@ class Viewer extends EventEmitter2 {
7600
8513
  this._renderNeeded = false;
7601
8514
  this.renderer.info.autoReset = false;
7602
8515
  this.renderer.info.reset();
7603
- if (this.options.antialiasing === true || this.options.antialiasing === "msaa") {
7604
- this.renderer.render(this.scene, this.camera);
7605
- this.renderer.render(this.helpers, this.camera);
7606
- }
7607
- else {
7608
- this.composer.render(deltaTime);
7609
- }
8516
+ this.composer.render(deltaTime);
7610
8517
  this.emitEvent({ type: "render", time, deltaTime });
7611
8518
  }
7612
8519
  loadReferences(model) {
@@ -7666,11 +8573,12 @@ class Viewer extends EventEmitter2 {
7666
8573
  this.clearOverlay();
7667
8574
  this.clearSelected();
7668
8575
  this.loaders.forEach((loader) => loader.dispose());
7669
- this.loaders = [];
8576
+ this.loaders.length = 0;
7670
8577
  this.models.forEach((model) => model.dispose());
7671
- this.models = [];
7672
- this.scene.clear();
8578
+ this.models.length = 0;
7673
8579
  this.helpers.clear();
8580
+ this.scene.clear();
8581
+ this.scene.add(this.helpers);
7674
8582
  this.extents.makeEmpty();
7675
8583
  this.syncOptions();
7676
8584
  this.syncOverlay();
@@ -7687,8 +8595,15 @@ class Viewer extends EventEmitter2 {
7687
8595
  this.fxaaPass.enabled = options.antialiasing === "fxaa";
7688
8596
  this.smaaPass.enabled = options.antialiasing === "smaa";
7689
8597
  this.ssaaRenderPass.enabled = options.antialiasing === "ssaa";
7690
- this.renderPass.enabled = !this.ssaaRenderPass.enabled;
7691
- this.helpersPass.enabled = !this.ssaaRenderPass.enabled;
8598
+ this.renderPass.enabled = options.antialiasing !== "ssaa";
8599
+ const samples = options.antialiasing === true || options.antialiasing === "msaa" ? 4 : 0;
8600
+ if (this.composer.renderTarget1.samples !== samples) {
8601
+ const size = this.renderer.getSize(new Vector2());
8602
+ const newRenderTarget = new WebGLRenderTarget(1, 1, { samples });
8603
+ newRenderTarget.texture.name = "EffectComposer.rt1";
8604
+ this.composer.reset(newRenderTarget);
8605
+ this.composer.setSize(size.x, size.y);
8606
+ }
7692
8607
  this.update();
7693
8608
  }
7694
8609
  syncOverlay() {
@@ -7707,7 +8622,8 @@ class Viewer extends EventEmitter2 {
7707
8622
  clearSlices() {
7708
8623
  if (!this.renderer)
7709
8624
  return;
7710
- this.renderer.clippingPlanes = [];
8625
+ this.clippingPlanes.length = 0;
8626
+ this.emitEvent({ type: "changecuttingplanes" });
7711
8627
  this.emitEvent({ type: "clearslices" });
7712
8628
  this.update();
7713
8629
  }
@@ -7776,7 +8692,7 @@ class Viewer extends EventEmitter2 {
7776
8692
  }
7777
8693
  }
7778
8694
  getComponent(name) {
7779
- return this._components.find((component) => component.name === name);
8695
+ return this._components.find((component) => component.name === name) || null;
7780
8696
  }
7781
8697
  drawViewpoint(viewpoint) {
7782
8698
  var _a, _b, _c, _d;
@@ -7803,7 +8719,6 @@ class Viewer extends EventEmitter2 {
7803
8719
  camera.updateMatrixWorld();
7804
8720
  this.camera = camera;
7805
8721
  this.renderPass.camera = camera;
7806
- this.helpersPass.camera = camera;
7807
8722
  this.ssaaRenderPass.camera = camera;
7808
8723
  this.options.cameraMode = "orthographic";
7809
8724
  this.emitEvent({ type: "changecameramode", mode: "orthographic" });
@@ -7826,7 +8741,6 @@ class Viewer extends EventEmitter2 {
7826
8741
  camera.updateMatrixWorld();
7827
8742
  this.camera = camera;
7828
8743
  this.renderPass.camera = camera;
7829
- this.helpersPass.camera = camera;
7830
8744
  this.ssaaRenderPass.camera = camera;
7831
8745
  this.options.cameraMode = "perspective";
7832
8746
  this.emitEvent({ type: "changecameramode", mode: "perspective" });
@@ -7837,8 +8751,9 @@ class Viewer extends EventEmitter2 {
7837
8751
  clipping_planes.forEach((clipping_plane) => {
7838
8752
  const plane = new Plane();
7839
8753
  plane.setFromNormalAndCoplanarPoint(getVector3FromPoint3d(clipping_plane.direction), getVector3FromPoint3d(clipping_plane.location));
7840
- this.renderer.clippingPlanes.push(plane);
8754
+ this.clippingPlanes.push(plane);
7841
8755
  });
8756
+ this.emitEvent({ type: "changecuttingplanes" });
7842
8757
  }
7843
8758
  };
7844
8759
  const setSelection = (selection) => {
@@ -7893,7 +8808,7 @@ class Viewer extends EventEmitter2 {
7893
8808
  };
7894
8809
  const getClippingPlanes = () => {
7895
8810
  const clipping_planes = [];
7896
- this.renderer.clippingPlanes.forEach((plane) => {
8811
+ this.clippingPlanes.forEach((plane) => {
7897
8812
  const clipping_plane = {
7898
8813
  location: getPoint3dFromVector3(plane.coplanarPoint(new Vector3())),
7899
8814
  direction: getPoint3dFromVector3(plane.normal),