@inweb/viewer-three 26.10.1 → 26.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/plugins/components/AxesHelperComponent.js +5 -9
  2. package/dist/plugins/components/AxesHelperComponent.js.map +1 -1
  3. package/dist/plugins/components/AxesHelperComponent.min.js +1 -1
  4. package/dist/plugins/components/AxesHelperComponent.module.js +5 -9
  5. package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -1
  6. package/dist/plugins/components/GridHelperComponent.js +62 -0
  7. package/dist/plugins/components/GridHelperComponent.js.map +1 -0
  8. package/dist/plugins/components/GridHelperComponent.min.js +24 -0
  9. package/dist/plugins/components/GridHelperComponent.module.js +57 -0
  10. package/dist/plugins/components/GridHelperComponent.module.js.map +1 -0
  11. package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
  12. package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
  13. package/dist/viewer-three.js +163 -59
  14. package/dist/viewer-three.js.map +1 -1
  15. package/dist/viewer-three.min.js +3 -3
  16. package/dist/viewer-three.module.js +151 -56
  17. package/dist/viewer-three.module.js.map +1 -1
  18. package/lib/Viewer/components/CameraComponent.d.ts +5 -1
  19. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +1 -0
  20. package/lib/Viewer/draggers/FlyDragger.d.ts +1 -0
  21. package/lib/Viewer/draggers/MeasureLineDragger.d.ts +1 -0
  22. package/lib/Viewer/draggers/OrbitDragger.d.ts +1 -0
  23. package/lib/Viewer/draggers/WalkDragger.d.ts +1 -0
  24. package/package.json +5 -5
  25. package/plugins/components/AxesHelperComponent.ts +6 -11
  26. package/plugins/components/GridHelperComponent.ts +67 -0
  27. package/plugins/loaders/IFCX/render.js +2 -2
  28. package/src/Viewer/Viewer.ts +4 -0
  29. package/src/Viewer/components/CameraComponent.ts +86 -25
  30. package/src/Viewer/components/SelectionComponent.ts +7 -1
  31. package/src/Viewer/controls/WalkControls.ts +1 -1
  32. package/src/Viewer/draggers/CuttingPlaneDragger.ts +12 -6
  33. package/src/Viewer/draggers/FlyDragger.ts +10 -4
  34. package/src/Viewer/draggers/MeasureLineDragger.ts +50 -17
  35. package/src/Viewer/draggers/OrbitDragger.ts +7 -1
  36. package/src/Viewer/draggers/WalkDragger.ts +10 -4
@@ -1,8 +1,12 @@
1
- import { IComponent } from "@inweb/viewer-core";
1
+ import { CameraMode, IComponent } from "@inweb/viewer-core";
2
2
  import type { Viewer } from "../Viewer";
3
3
  export declare class CameraComponent implements IComponent {
4
4
  protected viewer: Viewer;
5
5
  constructor(viewer: Viewer);
6
6
  dispose(): void;
7
+ getCameraMode(camera: any): CameraMode;
8
+ switchCamera(camera: any): void;
9
+ switchCameraMode(mode: CameraMode): void;
10
+ optionsChange: () => void;
7
11
  geometryEnd: () => void;
8
12
  }
@@ -13,5 +13,6 @@ export declare class CuttingPlaneDragger extends OrbitDragger {
13
13
  transformChange: () => void;
14
14
  transformDrag: (event: any) => void;
15
15
  updatePlaneSize: () => void;
16
+ updateTransformCamera: () => void;
16
17
  onDoubleClick: (event: PointerEvent) => void;
17
18
  }
@@ -7,6 +7,7 @@ export declare class FlyDragger implements IDragger {
7
7
  constructor(viewer: Viewer);
8
8
  dispose(): void;
9
9
  updateControls: () => void;
10
+ updateControlsCamera: () => void;
10
11
  controlsChange: () => void;
11
12
  flyspeedChange: (event: any) => void;
12
13
  viewerRender: () => void;
@@ -13,4 +13,5 @@ export declare class MeasureLineDragger extends OrbitDragger {
13
13
  onPointerLeave: () => void;
14
14
  renderOverlay: () => void;
15
15
  updateSnapper: () => void;
16
+ updateSnapperCamera: () => void;
16
17
  }
@@ -9,6 +9,7 @@ export declare class OrbitDragger implements IDragger {
9
9
  initialize(): void;
10
10
  dispose(): void;
11
11
  updateControls: () => void;
12
+ updateControlsCamera: () => void;
12
13
  controlsStart: () => void;
13
14
  controlsChange: () => void;
14
15
  stopContextMenu: (event: PointerEvent) => void;
@@ -7,6 +7,7 @@ export declare class WalkDragger implements IDragger {
7
7
  constructor(viewer: Viewer);
8
8
  dispose(): void;
9
9
  updateControls: () => void;
10
+ updateControlsCamera: () => void;
10
11
  controlsChange: () => void;
11
12
  walkspeedChange: (event: any) => void;
12
13
  viewerRender: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "26.10.1",
3
+ "version": "26.10.3",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,10 +35,10 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~26.10.1",
39
- "@inweb/eventemitter2": "~26.10.1",
40
- "@inweb/markup": "~26.10.1",
41
- "@inweb/viewer-core": "~26.10.1"
38
+ "@inweb/client": "~26.10.3",
39
+ "@inweb/eventemitter2": "~26.10.3",
40
+ "@inweb/markup": "~26.10.3",
41
+ "@inweb/viewer-core": "~26.10.3"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/three": "^0.179.0",
@@ -30,8 +30,8 @@ class AxesHelperComponent implements IComponent {
30
30
  private axesHelper2: AxesHelper;
31
31
 
32
32
  constructor(viewer: Viewer) {
33
- this.axesHelper1 = new AxesHelper();
34
- this.axesHelper2 = new AxesHelper();
33
+ this.axesHelper1 = new AxesHelper(1);
34
+ this.axesHelper2 = new AxesHelper(1);
35
35
 
36
36
  this.viewer = viewer;
37
37
  this.viewer.addEventListener("initialize", this.syncHelper);
@@ -53,23 +53,18 @@ class AxesHelperComponent implements IComponent {
53
53
 
54
54
  syncHelper = () => {
55
55
  this.axesHelper1.removeFromParent();
56
- this.axesHelper1.dispose();
57
-
58
56
  this.axesHelper2.removeFromParent();
59
- this.axesHelper2.dispose();
60
57
 
61
58
  const size = this.viewer.extents.getSize(new Vector3()).length();
62
59
  const center = this.viewer.extents.getCenter(new Vector3());
63
60
 
64
- this.axesHelper1 = new AxesHelper(size || 1);
65
- this.axesHelper2 = new AxesHelper(size);
66
-
67
61
  this.axesHelper1.position.set(0, 0, 0);
68
- this.viewer.helpers.add(this.axesHelper1);
69
-
70
- if (this.viewer.extents.isEmpty()) return;
62
+ this.axesHelper1.scale.setScalar(size);
71
63
 
72
64
  this.axesHelper2.position.copy(center);
65
+ this.axesHelper2.scale.setScalar(size);
66
+
67
+ this.viewer.helpers.add(this.axesHelper1);
73
68
  this.viewer.helpers.add(this.axesHelper2);
74
69
  };
75
70
  }
@@ -0,0 +1,67 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
3
+ // All rights reserved.
4
+ //
5
+ // This software and its documentation and related materials are owned by
6
+ // the Alliance. The software may only be incorporated into application
7
+ // programs owned by members of the Alliance, subject to a signed
8
+ // Membership Agreement and Supplemental Software License Agreement with the
9
+ // Alliance. The structure and organization of this software are the valuable
10
+ // trade secrets of the Alliance and its suppliers. The software is also
11
+ // protected by copyright law and international treaty provisions. Application
12
+ // programs incorporating this software must include the following statement
13
+ // with their copyright notices:
14
+ //
15
+ // This application incorporates Open Design Alliance software pursuant to a
16
+ // license agreement with Open Design Alliance.
17
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
18
+ // All rights reserved.
19
+ //
20
+ // By use of this software, its documentation or related materials, you
21
+ // acknowledge and accept the above terms.
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+
24
+ import { GridHelper, Vector3 } from "three";
25
+ import { IComponent, components, Viewer } from "@inweb/viewer-three";
26
+
27
+ class GridHelperComponent implements IComponent {
28
+ private viewer: Viewer;
29
+ private gridHelper: GridHelper;
30
+
31
+ constructor(viewer: Viewer) {
32
+ this.gridHelper = new GridHelper(10, 20, 0x444444, 0xaaaaaa);
33
+ this.viewer = viewer;
34
+ this.viewer.on("initialize", this.syncHelper);
35
+ this.viewer.on("geometryend", this.syncHelper);
36
+ this.viewer.on("clear", this.syncHelper);
37
+ }
38
+
39
+ dispose() {
40
+ this.gridHelper.removeFromParent();
41
+ this.gridHelper.dispose();
42
+
43
+ this.viewer.off("initialize", this.syncHelper);
44
+ this.viewer.off("geometryend", this.syncHelper);
45
+ this.viewer.off("clear", this.syncHelper);
46
+ }
47
+
48
+ syncHelper = () => {
49
+ this.gridHelper.removeFromParent();
50
+
51
+ if (this.viewer.extents.isEmpty()) return;
52
+
53
+ const size = this.viewer.extents.getSize(new Vector3()).multiply(this.viewer.camera.up).length();
54
+ const center = this.viewer.extents.getCenter(new Vector3());
55
+
56
+ const upY = new Vector3(0, 1, 0);
57
+ const up = new Vector3().copy(this.viewer.camera.up);
58
+
59
+ this.gridHelper.scale.setScalar(size);
60
+ this.gridHelper.position.copy(center);
61
+ this.gridHelper.quaternion.setFromUnitVectors(upY, up);
62
+
63
+ this.viewer.helpers.add(this.gridHelper);
64
+ };
65
+ }
66
+
67
+ components.registerComponent("GridHelperComponent", (viewer) => new GridHelperComponent(viewer));
@@ -23,8 +23,8 @@
23
23
 
24
24
  // Repository: https://github.com/buildingSMART/IFC5-development
25
25
  // Original File: docs/viewer/render.mjs
26
- // Commit SHA-1: 83a7ae862232c90065d1bd03fcd538315e7d2563
27
- // Commit Date: 20.05.2025 21:00:52
26
+ // Commit SHA-1: 7a2b39e56324d892b78b476944ec0e557d50b236
27
+ // Commit Date: 30.07.2025 08:20:14
28
28
 
29
29
  // (C) buildingSMART International
30
30
  // published under MIT license
@@ -698,6 +698,8 @@ export class Viewer
698
698
  this.renderPass.camera = camera;
699
699
  this.helpersPass.camera = camera;
700
700
  this.ssaaRenderPass.camera = camera;
701
+
702
+ this.emitEvent({ type: "changecameramode", mode: "orthographic" });
701
703
  }
702
704
  };
703
705
 
@@ -723,6 +725,8 @@ export class Viewer
723
725
  this.renderPass.camera = camera;
724
726
  this.helpersPass.camera = camera;
725
727
  this.ssaaRenderPass.camera = camera;
728
+
729
+ this.emitEvent({ type: "changecameramode", mode: "perspective" });
726
730
  }
727
731
  };
728
732
 
@@ -21,9 +21,9 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { Sphere, Vector2 } from "three";
24
+ import { MathUtils, OrthographicCamera, PerspectiveCamera, Sphere, Vector2, Vector3 } from "three";
25
25
 
26
- import { IComponent } from "@inweb/viewer-core";
26
+ import { CameraMode, IComponent } from "@inweb/viewer-core";
27
27
  import type { Viewer } from "../Viewer";
28
28
 
29
29
  export class CameraComponent implements IComponent {
@@ -32,17 +32,93 @@ export class CameraComponent implements IComponent {
32
32
  constructor(viewer: Viewer) {
33
33
  this.viewer = viewer;
34
34
  this.viewer.addEventListener("databasechunk", this.geometryEnd);
35
+ this.viewer.addEventListener("optionschange", this.optionsChange);
36
+ this.viewer.addEventListener("initialize", this.optionsChange);
35
37
  }
36
38
 
37
39
  dispose() {
38
40
  this.viewer.removeEventListener("databasechunk", this.geometryEnd);
41
+ this.viewer.removeEventListener("optionschange", this.optionsChange);
42
+ this.viewer.removeEventListener("initialize", this.optionsChange);
39
43
  }
40
44
 
41
- geometryEnd = () => {
42
- const extentsSize = this.viewer.extents.getBoundingSphere(new Sphere()).radius * 2;
45
+ getCameraMode(camera: any): CameraMode {
46
+ return camera.isOrthographicCamera ? "orthographic" : "perspective";
47
+ }
48
+
49
+ switchCamera(camera: any) {
50
+ const extentsSize = this.viewer.extents.getBoundingSphere(new Sphere()).radius * 2 || 1;
43
51
  const rendererSize = this.viewer.renderer.getSize(new Vector2());
44
52
  const aspect = rendererSize.x / rendererSize.y;
45
53
 
54
+ if (camera.isPerspectiveCamera) {
55
+ camera.aspect = aspect;
56
+ camera.near = extentsSize / 1000;
57
+ camera.far = extentsSize * 1000;
58
+ }
59
+ if (camera.isOrthographicCamera) {
60
+ camera.left = camera.bottom * aspect;
61
+ camera.right = camera.top * aspect;
62
+ camera.near = 0;
63
+ camera.far = extentsSize * 1000;
64
+ }
65
+
66
+ camera.updateProjectionMatrix();
67
+
68
+ this.viewer.camera = camera;
69
+ this.viewer.renderPass.camera = camera;
70
+ this.viewer.helpersPass.camera = camera;
71
+ this.viewer.ssaaRenderPass.camera = camera;
72
+
73
+ this.viewer.update();
74
+ }
75
+
76
+ switchCameraMode(mode: CameraMode) {
77
+ if (!mode) return;
78
+
79
+ const currentCamera: any = this.viewer.camera;
80
+ if (mode === this.getCameraMode(currentCamera)) return;
81
+
82
+ const target = this.viewer.target.clone();
83
+
84
+ let camera: PerspectiveCamera | OrthographicCamera;
85
+
86
+ if (currentCamera.isOrthographicCamera) {
87
+ const fov = currentCamera.userData.fov || 45;
88
+ const fieldHeight = (currentCamera.top - currentCamera.bottom) / currentCamera.zoom;
89
+ const distance = fieldHeight / (2 * Math.tan(MathUtils.degToRad(fov) / 2));
90
+ const direction = new Vector3().subVectors(currentCamera.position, target).normalize();
91
+
92
+ camera = new PerspectiveCamera(fov);
93
+ camera.position.copy(direction).multiplyScalar(distance).add(target);
94
+ }
95
+ if (currentCamera.isPerspectiveCamera) {
96
+ const fov = currentCamera.fov;
97
+ const distance = currentCamera.position.distanceTo(target);
98
+ const fieldHeight = 2 * Math.tan(MathUtils.degToRad(fov) / 2) * distance;
99
+
100
+ camera = new OrthographicCamera();
101
+ camera.top = fieldHeight / 2;
102
+ camera.bottom = -fieldHeight / 2;
103
+ camera.position.copy(currentCamera.position);
104
+
105
+ camera.userData.fov = fov;
106
+ }
107
+ if (!camera) return;
108
+
109
+ camera.up.copy(currentCamera.up);
110
+ camera.quaternion.copy(currentCamera.quaternion);
111
+
112
+ this.switchCamera(camera);
113
+
114
+ this.viewer.emitEvent({ type: "changecameramode", mode });
115
+ }
116
+
117
+ optionsChange = () => {
118
+ this.switchCameraMode(this.viewer.options.cameraMode);
119
+ };
120
+
121
+ geometryEnd = () => {
46
122
  let camera: any;
47
123
 
48
124
  // TODO: do not change the camera and target after opening the second model in "append" mode
@@ -56,29 +132,14 @@ export class CameraComponent implements IComponent {
56
132
  camera.isDefaultCamera = true;
57
133
  camera.scale.set(1, 1, 1); // <- Visualize fix
58
134
 
59
- this.viewer.camera = camera;
60
- this.viewer.renderPass.camera = camera;
61
- this.viewer.helpersPass.camera = camera;
62
- this.viewer.ssaaRenderPass.camera = camera;
63
- } else {
64
- camera = this.viewer.camera;
65
- }
135
+ this.switchCamera(camera);
66
136
 
67
- if (camera.isPerspectiveCamera) {
68
- camera.aspect = aspect;
69
- camera.near = extentsSize / 1000;
70
- camera.far = extentsSize * 1000;
71
- camera.updateProjectionMatrix();
72
- }
73
- if (camera.isOrthographicCamera) {
74
- camera.left = camera.bottom * aspect;
75
- camera.right = camera.top * aspect;
76
- camera.near = 0;
77
- camera.far = extentsSize * 1000;
78
- camera.updateProjectionMatrix();
79
- }
137
+ const mode = this.getCameraMode(camera);
80
138
 
81
- if (!camera.isDefaultCamera) {
139
+ this.viewer.options.cameraMode = mode;
140
+ this.viewer.emitEvent({ type: "changecameramode", mode });
141
+ } else {
142
+ this.switchCamera(this.viewer.camera);
82
143
  this.viewer.executeCommand("setDefaultViewPosition");
83
144
  }
84
145
  };
@@ -114,7 +114,13 @@ export class SelectionComponent implements IComponent {
114
114
  Sprite: {},
115
115
  };
116
116
 
117
- return this.raycaster.intersectObjects(objects, false);
117
+ let intersects = this.raycaster.intersectObjects(objects, false);
118
+
119
+ (this.viewer.renderer.clippingPlanes || []).forEach((plane) => {
120
+ intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
121
+ });
122
+
123
+ return intersects;
118
124
  }
119
125
 
120
126
  select(objects: Object3D | Object3D[], model?: IModelImpl) {
@@ -177,7 +177,7 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
177
177
  Sprite: { threshold: 0 },
178
178
  };
179
179
 
180
- let intersects = this.raycaster.intersectObjects(this.groundObjects, false);
180
+ const intersects = this.raycaster.intersectObjects(this.groundObjects, false);
181
181
  if (intersects.length > 0) {
182
182
  const groundY = intersects[0].point.y;
183
183
  const targetY = groundY + this.EYE_HEIGHT;
@@ -62,17 +62,19 @@ export class CuttingPlaneDragger extends OrbitDragger {
62
62
  this.transform.addEventListener("dragging-changed", this.transformDrag);
63
63
  this.viewer.helpers.add(this.transform.getHelper());
64
64
 
65
- this.viewer.on("explode", this.updatePlaneSize);
66
- this.viewer.on("show", this.updatePlaneSize);
67
- this.viewer.on("showall", this.updatePlaneSize);
65
+ this.viewer.addEventListener("explode", this.updatePlaneSize);
66
+ this.viewer.addEventListener("show", this.updatePlaneSize);
67
+ this.viewer.addEventListener("showall", this.updatePlaneSize);
68
+ this.viewer.addEventListener("changecameramode", this.updateTransformCamera);
68
69
  this.viewer.canvas.addEventListener("dblclick", this.onDoubleClick, true);
69
70
  this.viewer.update();
70
71
  }
71
72
 
72
73
  override dispose() {
73
- this.viewer.off("explode", this.updatePlaneSize);
74
- this.viewer.off("show", this.updatePlaneSize);
75
- this.viewer.off("showAll", this.updatePlaneSize);
74
+ this.viewer.removeEventListener("explode", this.updatePlaneSize);
75
+ this.viewer.removeEventListener("show", this.updatePlaneSize);
76
+ this.viewer.removeEventListener("showall", this.updatePlaneSize);
77
+ this.viewer.removeEventListener("changecameramode", this.updateTransformCamera);
76
78
  this.viewer.canvas.removeEventListener("dblclick", this.onDoubleClick, true);
77
79
 
78
80
  this.transform.removeEventListener("change", this.transformChange);
@@ -106,6 +108,10 @@ export class CuttingPlaneDragger extends OrbitDragger {
106
108
  this.viewer.update();
107
109
  };
108
110
 
111
+ updateTransformCamera = () => {
112
+ this.transform.camera = this.viewer.camera;
113
+ };
114
+
109
115
  onDoubleClick = (event: PointerEvent) => {
110
116
  event.stopPropagation();
111
117
 
@@ -36,14 +36,16 @@ export class FlyDragger implements IDragger {
36
36
  this.controls.addEventListener("change", this.controlsChange);
37
37
  this.controls.addEventListener("flyspeedchange", this.flyspeedChange);
38
38
  this.viewer = viewer;
39
- this.viewer.on("render", this.viewerRender);
40
- this.viewer.on("zoom", this.viewerZoom);
39
+ this.viewer.addEventListener("render", this.viewerRender);
40
+ this.viewer.addEventListener("zoom", this.viewerZoom);
41
+ this.viewer.addEventListener("changecameramode", this.updateControlsCamera);
41
42
  this.updateControls();
42
43
  }
43
44
 
44
45
  dispose() {
45
- this.viewer.off("render", this.viewerRender);
46
- this.viewer.off("zoom", this.viewerZoom);
46
+ this.viewer.removeEventListener("render", this.viewerRender);
47
+ this.viewer.removeEventListener("zoom", this.viewerZoom);
48
+ this.viewer.removeEventListener("changecameramode", this.updateControlsCamera);
47
49
  this.controls.removeEventListener("flyspeedchange", this.flyspeedChange);
48
50
  this.controls.removeEventListener("change", this.controlsChange);
49
51
  this.controls.dispose();
@@ -54,6 +56,10 @@ export class FlyDragger implements IDragger {
54
56
  this.controls.movementSpeed = Math.min(size.x, size.y, size.z) / 2;
55
57
  };
56
58
 
59
+ updateControlsCamera = () => {
60
+ this.controls.object = this.viewer.camera;
61
+ };
62
+
57
63
  controlsChange = () => {
58
64
  this.viewer.update();
59
65
  this.viewer.emitEvent({ type: "changecamera" });
@@ -29,6 +29,7 @@ import {
29
29
  MathUtils,
30
30
  Matrix4,
31
31
  Object3D,
32
+ Plane,
32
33
  Raycaster,
33
34
  Vector2,
34
35
  Vector3,
@@ -70,6 +71,7 @@ export class MeasureLineDragger extends OrbitDragger {
70
71
  this.viewer.addEventListener("isolate", this.updateSnapper);
71
72
  this.viewer.addEventListener("show", this.updateSnapper);
72
73
  this.viewer.addEventListener("showall", this.updateSnapper);
74
+ this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
73
75
  }
74
76
 
75
77
  override dispose() {
@@ -84,6 +86,7 @@ export class MeasureLineDragger extends OrbitDragger {
84
86
  this.viewer.removeEventListener("isolate", this.updateSnapper);
85
87
  this.viewer.removeEventListener("show", this.updateSnapper);
86
88
  this.viewer.removeEventListener("showall", this.updateSnapper);
89
+ this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
87
90
 
88
91
  this.snapper.dispose();
89
92
 
@@ -143,7 +146,12 @@ export class MeasureLineDragger extends OrbitDragger {
143
146
  };
144
147
 
145
148
  updateSnapper = () => {
146
- this.snapper.update(this.viewer);
149
+ this.snapper.setFromViewer(this.viewer);
150
+ };
151
+
152
+ updateSnapperCamera = () => {
153
+ this.snapper.camera = this.viewer.camera;
154
+ this.overlay.camera = this.viewer.camera;
147
155
  };
148
156
  }
149
157
 
@@ -155,9 +163,10 @@ const _center = new Vector3();
155
163
  const _projection = new Vector3();
156
164
 
157
165
  class MeasureSnapper {
158
- private camera: Camera;
166
+ public camera: Camera;
159
167
  private canvas: HTMLCanvasElement;
160
168
  private objects: Object3D[];
169
+ private clippingPlanes: Plane[];
161
170
  private raycaster: Raycaster;
162
171
  private detectRadiusInPixels: number;
163
172
  private edgesCache: WeakMap<any, EdgesGeometry>;
@@ -166,6 +175,7 @@ class MeasureSnapper {
166
175
  this.camera = camera;
167
176
  this.canvas = canvas;
168
177
  this.objects = [];
178
+ this.clippingPlanes = [];
169
179
  this.raycaster = new Raycaster();
170
180
  this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
171
181
  this.edgesCache = new WeakMap();
@@ -193,7 +203,7 @@ class MeasureSnapper {
193
203
  return target.set(event.clientX, event.clientY);
194
204
  }
195
205
 
196
- getPointerIntersects(mouse: Vector2, objects: Object3D[]): Array<Intersection<Object3D>> {
206
+ getPointerIntersects(mouse: Vector2): Array<Intersection<Object3D>> {
197
207
  const rect = this.canvas.getBoundingClientRect();
198
208
  const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
199
209
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
@@ -210,7 +220,13 @@ class MeasureSnapper {
210
220
  Sprite: {},
211
221
  };
212
222
 
213
- return this.raycaster.intersectObjects(objects, false);
223
+ let intersects = this.raycaster.intersectObjects(this.objects, false);
224
+
225
+ this.clippingPlanes.forEach((plane) => {
226
+ intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
227
+ });
228
+
229
+ return intersects;
214
230
  }
215
231
 
216
232
  getDetectRadius(point: Vector3): number {
@@ -224,20 +240,20 @@ class MeasureSnapper {
224
240
  // Notes: Originally AI-generated, modified manually
225
241
 
226
242
  if (camera.isOrthographicCamera) {
227
- const worldHeight = camera.top - camera.bottom;
243
+ const fieldHeight = (camera.top - camera.bottom) / camera.zoom;
228
244
 
229
245
  const canvasHeight = this.canvas.height;
230
- const worldUnitsPerPixel = worldHeight / canvasHeight;
246
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
231
247
 
232
248
  return this.detectRadiusInPixels * worldUnitsPerPixel;
233
249
  }
234
250
 
235
251
  if (camera.isPerspectiveCamera) {
236
252
  const distance = camera.position.distanceTo(point);
237
- const worldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
253
+ const fieldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
238
254
 
239
255
  const canvasHeight = this.canvas.height;
240
- const worldUnitsPerPixel = worldHeight / canvasHeight;
256
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
241
257
 
242
258
  return this.detectRadiusInPixels * worldUnitsPerPixel;
243
259
  }
@@ -250,7 +266,7 @@ class MeasureSnapper {
250
266
  getSnapPoint(event: PointerEvent): Vector3 {
251
267
  const mouse = this.getMousePosition(event, new Vector2());
252
268
 
253
- const intersections = this.getPointerIntersects(mouse, this.objects);
269
+ const intersections = this.getPointerIntersects(mouse);
254
270
  if (intersections.length === 0) return undefined;
255
271
 
256
272
  // ===================== AI-CODE-START ======================
@@ -314,11 +330,14 @@ class MeasureSnapper {
314
330
  return intersectionPoint.clone();
315
331
  }
316
332
 
317
- update(viewer: Viewer) {
333
+ setFromViewer(viewer: Viewer) {
318
334
  this.objects.length = 0;
319
335
  viewer.models.forEach((model) => {
320
336
  model.getVisibleObjects().forEach((object) => this.objects.push(object));
321
337
  });
338
+
339
+ this.camera = viewer.camera;
340
+ this.clippingPlanes = viewer.renderer.clippingPlanes || [];
322
341
  }
323
342
  }
324
343
 
@@ -328,22 +347,19 @@ class MeasureOverlay {
328
347
  public container: HTMLElement;
329
348
  public lines: MeasureLine[] = [];
330
349
  public projector: MeasureProjector;
350
+ private resizeObserver: ResizeObserver;
331
351
 
332
352
  constructor(camera: Camera, canvas: HTMLCanvasElement) {
333
353
  this.camera = camera;
334
354
  this.canvas = canvas;
335
355
  this.projector = new MeasureProjector(camera, canvas);
356
+ this.resizeObserver = new ResizeObserver(this.resizeContainer);
336
357
  }
337
358
 
338
359
  attach() {
339
360
  this.container = document.createElement("div");
340
361
  this.container.id = "measure-container";
341
- this.container.style.background = "rgba(0,0,0,0)";
342
362
  this.container.style.position = "absolute";
343
- this.container.style.top = "0px";
344
- this.container.style.left = "0px";
345
- this.container.style.width = "100%";
346
- this.container.style.height = "100%";
347
363
  this.container.style.outline = "none";
348
364
  this.container.style.pointerEvents = "none";
349
365
  this.container.style.overflow = "hidden";
@@ -351,6 +367,7 @@ class MeasureOverlay {
351
367
  if (!this.canvas.parentElement) return;
352
368
 
353
369
  this.canvas.parentElement.appendChild(this.container);
370
+ this.resizeObserver.observe(this.canvas.parentElement);
354
371
  }
355
372
 
356
373
  dispose() {
@@ -358,6 +375,8 @@ class MeasureOverlay {
358
375
  }
359
376
 
360
377
  detach() {
378
+ this.resizeObserver.disconnect();
379
+
361
380
  this.container.remove();
362
381
  this.container = undefined;
363
382
  }
@@ -368,7 +387,7 @@ class MeasureOverlay {
368
387
  }
369
388
 
370
389
  render() {
371
- this.projector.updateProjectionMatrix();
390
+ this.projector.setFromCamera(this.camera);
372
391
  this.lines.forEach((line) => line.render());
373
392
  }
374
393
 
@@ -383,6 +402,15 @@ class MeasureOverlay {
383
402
  removeLine(line: MeasureLine) {
384
403
  this.lines = this.lines.filter((x) => x !== line);
385
404
  }
405
+
406
+ resizeContainer = (entries: ResizeObserverEntry[]) => {
407
+ const { width, height } = entries[0].contentRect;
408
+
409
+ if (!width || !height) return; // <- invisible canvas, or canvas with parent removed
410
+
411
+ this.container.style.width = `${width}px`;
412
+ this.container.style.height = `${height}px`;
413
+ };
386
414
  }
387
415
 
388
416
  const _middlePoint = new Vector3();
@@ -550,7 +578,7 @@ const point1 = new Vector2();
550
578
  const point2 = new Vector2();
551
579
 
552
580
  class MeasureProjector {
553
- private camera: Camera;
581
+ public camera: Camera;
554
582
  private canvas: HTMLElement;
555
583
 
556
584
  constructor(camera: Camera, canvas: HTMLCanvasElement) {
@@ -558,6 +586,11 @@ class MeasureProjector {
558
586
  this.canvas = canvas;
559
587
  }
560
588
 
589
+ setFromCamera(camera: Camera) {
590
+ this.camera = camera;
591
+ this.updateProjectionMatrix();
592
+ }
593
+
561
594
  updateProjectionMatrix() {
562
595
  const rect = this.canvas.getBoundingClientRect();
563
596
  _widthHalf = rect.width / 2;