@inweb/viewer-three 26.10.2 → 26.10.4

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.
@@ -0,0 +1,44 @@
1
+ import { Camera, Controls, Object3D } from "three";
2
+ interface JoyStickControlsEventMap {
3
+ change: {
4
+ type: "change";
5
+ };
6
+ }
7
+ export declare class JoyStickControls extends Controls<JoyStickControlsEventMap> {
8
+ readonly EYE_HEIGHT = 1.7;
9
+ readonly FAILING_DISTANCE = 2;
10
+ readonly GROUND_FOLLOWING_SPEED = 0.05;
11
+ readonly WALK_SPEED_DELIMITER = 4;
12
+ movementSpeed: number;
13
+ multiplier: number;
14
+ private raycaster;
15
+ private groundObjects;
16
+ private canvas;
17
+ private overlayElement;
18
+ private joyStickCanvas;
19
+ private context;
20
+ private moveClock;
21
+ private camera;
22
+ private joyStickPosition;
23
+ private isActive;
24
+ private readonly MAX_JOYSTICK_DISTANCE;
25
+ private readonly INTERNAL_RADIUS;
26
+ private readonly MAX_MOVE_STICK;
27
+ private readonly EXTERNAL_RADIUS;
28
+ private readonly CANVAS_SIZE;
29
+ private centerX;
30
+ private centerY;
31
+ private pressed;
32
+ constructor(camera: Camera, domElement: HTMLElement, canvasElement: HTMLCanvasElement, groundObjects: Object3D[]);
33
+ dispose(): void;
34
+ private onPointerDown;
35
+ private onPointerMove;
36
+ private onPointerUp;
37
+ private onResize;
38
+ private updateVisibility;
39
+ private updatePosition;
40
+ private draw;
41
+ private updateGroundFollowing;
42
+ update(): void;
43
+ }
44
+ export {};
@@ -4,6 +4,7 @@ import { FlyControls } from "../controls/FlyControls";
4
4
  export declare class FlyDragger implements IDragger {
5
5
  protected viewer: Viewer;
6
6
  controls: FlyControls;
7
+ private joyStickControls;
7
8
  constructor(viewer: Viewer);
8
9
  dispose(): void;
9
10
  updateControls: () => void;
@@ -4,6 +4,7 @@ import { WalkControls } from "../controls/WalkControls";
4
4
  export declare class WalkDragger implements IDragger {
5
5
  protected viewer: Viewer;
6
6
  controls: WalkControls;
7
+ private joyStickControls;
7
8
  constructor(viewer: Viewer);
8
9
  dispose(): void;
9
10
  updateControls: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "26.10.2",
3
+ "version": "26.10.4",
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.2",
39
- "@inweb/eventemitter2": "~26.10.2",
40
- "@inweb/markup": "~26.10.2",
41
- "@inweb/viewer-core": "~26.10.2"
38
+ "@inweb/client": "~26.10.4",
39
+ "@inweb/eventemitter2": "~26.10.4",
40
+ "@inweb/markup": "~26.10.4",
41
+ "@inweb/viewer-core": "~26.10.4"
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));
@@ -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) {
@@ -0,0 +1,285 @@
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
+ // ===================== AI-CODE-FILE ======================
25
+ // Source: Claude Sonnet 4.5
26
+ // Date: 2025-10-07
27
+ // Reviewer: vitaly.ivanov@opendesign.com
28
+ // Issue: CLOUD-5851
29
+ // Notes: Originally AI-generated, modified manually
30
+ // =========================================================
31
+
32
+ import { Camera, Clock, Controls, Vector2, Vector3, Raycaster, Object3D, MathUtils } from "three";
33
+
34
+ interface JoyStickControlsEventMap {
35
+ change: { type: "change" };
36
+ }
37
+
38
+ export class JoyStickControls extends Controls<JoyStickControlsEventMap> {
39
+ readonly EYE_HEIGHT = 1.7;
40
+ readonly FAILING_DISTANCE = 2;
41
+ readonly GROUND_FOLLOWING_SPEED = 0.05;
42
+ readonly WALK_SPEED_DELIMITER = 4;
43
+
44
+ public movementSpeed = 0.1;
45
+ public multiplier = 3;
46
+
47
+ private raycaster: Raycaster;
48
+ private groundObjects: Object3D[];
49
+
50
+ private canvas: HTMLCanvasElement;
51
+ private overlayElement: HTMLDivElement;
52
+ private joyStickCanvas: HTMLCanvasElement;
53
+ private context: CanvasRenderingContext2D;
54
+
55
+ private moveClock: Clock;
56
+ private camera: Camera;
57
+
58
+ private joyStickPosition: Vector2;
59
+ private isActive: boolean = false;
60
+
61
+ private readonly MAX_JOYSTICK_DISTANCE = 100;
62
+ private readonly INTERNAL_RADIUS = 35;
63
+ private readonly MAX_MOVE_STICK = 40;
64
+ private readonly EXTERNAL_RADIUS = 65;
65
+ private readonly CANVAS_SIZE = 200;
66
+
67
+ private centerX: number;
68
+ private centerY: number;
69
+ private pressed: boolean = false;
70
+
71
+ constructor(camera: Camera, domElement: HTMLElement, canvasElement: HTMLCanvasElement, groundObjects: Object3D[]) {
72
+ super(camera, domElement);
73
+ this.camera = camera;
74
+ this.canvas = canvasElement;
75
+ this.moveClock = new Clock(false);
76
+ this.joyStickPosition = new Vector2(0, 0);
77
+
78
+ this.groundObjects = groundObjects;
79
+ this.raycaster = new Raycaster();
80
+ this.raycaster.near = 0;
81
+ this.raycaster.far = this.EYE_HEIGHT + this.FAILING_DISTANCE;
82
+
83
+ this.centerX = this.CANVAS_SIZE / 2;
84
+ this.centerY = this.CANVAS_SIZE / 2;
85
+
86
+ this.overlayElement = document.createElement("div");
87
+ this.overlayElement.id = "joyStickDiv";
88
+ this.overlayElement.style.background = "rgba(0,0,0,0)";
89
+ this.overlayElement.style.position = "fixed";
90
+ this.overlayElement.style.zIndex = "0";
91
+ this.overlayElement.style.touchAction = "none";
92
+ document.body.appendChild(this.overlayElement);
93
+
94
+ this.joyStickCanvas = document.createElement("canvas");
95
+ this.joyStickCanvas.id = "joyStickCanvas";
96
+ this.joyStickCanvas.width = this.CANVAS_SIZE;
97
+ this.joyStickCanvas.height = this.CANVAS_SIZE;
98
+ this.overlayElement.appendChild(this.joyStickCanvas);
99
+ this.context = this.joyStickCanvas.getContext("2d")!;
100
+
101
+ this.joyStickCanvas.addEventListener("pointerdown", this.onPointerDown);
102
+ document.addEventListener("pointermove", this.onPointerMove);
103
+ document.addEventListener("pointerup", this.onPointerUp);
104
+
105
+ window.addEventListener("resize", this.onResize);
106
+ document.addEventListener("fullscreenchange", this.onResize);
107
+
108
+ this.updateVisibility();
109
+ this.updatePosition();
110
+ this.draw();
111
+ }
112
+
113
+ override dispose() {
114
+ this.joyStickCanvas.removeEventListener("pointerdown", this.onPointerDown);
115
+ document.removeEventListener("pointermove", this.onPointerMove);
116
+ document.removeEventListener("pointerup", this.onPointerUp);
117
+
118
+ window.removeEventListener("resize", this.onResize);
119
+ document.removeEventListener("fullscreenchange", this.onResize);
120
+
121
+ this.overlayElement.remove();
122
+ super.dispose();
123
+ }
124
+
125
+ private onPointerDown = (event: PointerEvent) => {
126
+ event.preventDefault();
127
+ this.pressed = true;
128
+ };
129
+
130
+ private onPointerMove = (event: PointerEvent) => {
131
+ event.preventDefault();
132
+ if (!this.pressed) return;
133
+
134
+ let movedX = event.pageX;
135
+ let movedY = event.pageY;
136
+
137
+ if (
138
+ this.joyStickCanvas.offsetParent &&
139
+ (this.joyStickCanvas.offsetParent as HTMLElement).tagName.toUpperCase() === "BODY"
140
+ ) {
141
+ movedX -= this.joyStickCanvas.offsetLeft;
142
+ movedY -= this.joyStickCanvas.offsetTop;
143
+ } else if (this.joyStickCanvas.offsetParent) {
144
+ movedX -= (this.joyStickCanvas.offsetParent as HTMLElement).offsetLeft;
145
+ movedY -= (this.joyStickCanvas.offsetParent as HTMLElement).offsetTop;
146
+ }
147
+
148
+ const x = 100 * ((movedX - this.centerX) / this.MAX_MOVE_STICK);
149
+ const y = 100 * ((movedY - this.centerY) / this.MAX_MOVE_STICK) * -1;
150
+
151
+ const distance = Math.sqrt(x * x + y * y);
152
+ if (distance > 20) {
153
+ this.joyStickPosition.set(x, y);
154
+ this.isActive = true;
155
+ } else {
156
+ this.joyStickPosition.set(0, 0);
157
+ this.isActive = false;
158
+ }
159
+
160
+ this.draw();
161
+ this.moveClock.start();
162
+ this.update();
163
+ };
164
+
165
+ private onPointerUp = (event: PointerEvent) => {
166
+ event.preventDefault();
167
+ this.pressed = false;
168
+ this.joyStickPosition.set(0, 0);
169
+ this.isActive = false;
170
+ this.moveClock.stop();
171
+ this.draw();
172
+ };
173
+
174
+ private onResize = () => {
175
+ this.updateVisibility();
176
+ this.updatePosition();
177
+ };
178
+
179
+ private updateVisibility() {
180
+ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
181
+ const isNarrowScreen = window.innerWidth < 1024;
182
+
183
+ if (isMobile || isNarrowScreen) {
184
+ this.overlayElement.style.display = "block";
185
+ } else {
186
+ this.overlayElement.style.display = "none";
187
+ }
188
+ }
189
+
190
+ private updatePosition() {
191
+ const rect = this.canvas.getBoundingClientRect();
192
+ this.overlayElement.style.top = `${rect.height - this.CANVAS_SIZE}px`;
193
+ this.overlayElement.style.left = `${rect.left}px`;
194
+ this.overlayElement.style.width = `${this.CANVAS_SIZE}px`;
195
+ this.overlayElement.style.height = `${this.CANVAS_SIZE}px`;
196
+ }
197
+
198
+ private draw() {
199
+ this.context.clearRect(0, 0, this.CANVAS_SIZE, this.CANVAS_SIZE);
200
+
201
+ // Draw external circle
202
+ this.context.beginPath();
203
+ this.context.arc(this.centerX, this.centerY, this.EXTERNAL_RADIUS, 0, 2 * Math.PI, false);
204
+ this.context.lineWidth = 2;
205
+ this.context.strokeStyle = "#35436E";
206
+ this.context.globalAlpha = 0.5;
207
+ this.context.stroke();
208
+
209
+ // Draw internal circle
210
+ let movedX = this.centerX + (this.joyStickPosition.x * this.MAX_MOVE_STICK) / 100;
211
+ let movedY = this.centerY - (this.joyStickPosition.y * this.MAX_MOVE_STICK) / 100;
212
+
213
+ movedX = Math.max(this.MAX_MOVE_STICK, Math.min(this.CANVAS_SIZE - this.MAX_MOVE_STICK, movedX));
214
+ movedY = Math.max(this.MAX_MOVE_STICK, Math.min(this.CANVAS_SIZE - this.MAX_MOVE_STICK, movedY));
215
+
216
+ this.context.beginPath();
217
+ this.context.arc(movedX, movedY, this.INTERNAL_RADIUS, 0, 2 * Math.PI, false);
218
+ this.context.fillStyle = "#35436E";
219
+ this.context.lineWidth = 2;
220
+ this.context.strokeStyle = "#35436E";
221
+ this.context.globalAlpha = 0.5;
222
+ this.context.fill();
223
+ this.context.stroke();
224
+ }
225
+
226
+ private updateGroundFollowing() {
227
+ const up = new Vector3().copy(this.camera.up);
228
+ this.raycaster.set(this.camera.position, up.negate());
229
+
230
+ this.raycaster.params = this.raycaster.params = {
231
+ Mesh: {},
232
+ Line: { threshold: 0 },
233
+ Line2: { threshold: 0 },
234
+ LOD: { threshold: 0 },
235
+ Points: { threshold: 0 },
236
+ Sprite: { threshold: 0 },
237
+ };
238
+
239
+ const intersects = this.raycaster.intersectObjects(this.groundObjects, false);
240
+ if (intersects.length > 0) {
241
+ const groundY = intersects[0].point.y;
242
+ const targetY = groundY + this.EYE_HEIGHT;
243
+
244
+ // Smoothly interpolate the camera's y position to the target height
245
+ this.camera.position.y = MathUtils.lerp(this.camera.position.y, targetY, this.GROUND_FOLLOWING_SPEED);
246
+ }
247
+ }
248
+
249
+ override update() {
250
+ if (!this.isActive) return;
251
+
252
+ const forwardInput = this.joyStickPosition.y / this.MAX_JOYSTICK_DISTANCE;
253
+ const rightInput = this.joyStickPosition.x / this.MAX_JOYSTICK_DISTANCE;
254
+
255
+ const timeDelta = this.moveClock.getDelta();
256
+ const moveDelta = (timeDelta * this.multiplier * this.movementSpeed) / this.WALK_SPEED_DELIMITER;
257
+
258
+ const forward = new Vector3();
259
+ const sideways = new Vector3();
260
+
261
+ this.camera.getWorldDirection(forward);
262
+ if (this.groundObjects.length > 0) {
263
+ forward.y = 0;
264
+ }
265
+ forward.normalize();
266
+
267
+ sideways.setFromMatrixColumn(this.camera.matrix, 0);
268
+ if (this.groundObjects.length > 0) {
269
+ sideways.y = 0;
270
+ }
271
+ sideways.normalize();
272
+
273
+ if (forwardInput !== 0) {
274
+ this.camera.position.addScaledVector(forward, moveDelta * forwardInput);
275
+ }
276
+ if (rightInput !== 0) {
277
+ this.camera.position.addScaledVector(sideways, moveDelta * rightInput);
278
+ }
279
+
280
+ if (forwardInput !== 0 || rightInput !== 0) {
281
+ if (this.groundObjects.length > 0) this.updateGroundFollowing();
282
+ this.dispatchEvent({ type: "change" });
283
+ }
284
+ }
285
+ }
@@ -26,15 +26,21 @@ import { type IDragger } from "@inweb/viewer-core";
26
26
 
27
27
  import type { Viewer } from "../Viewer";
28
28
  import { FlyControls } from "../controls/FlyControls";
29
+ import { JoyStickControls } from "../controls/JoyStickControls";
29
30
 
30
31
  export class FlyDragger implements IDragger {
31
32
  protected viewer: Viewer;
32
33
  public controls: FlyControls;
34
+ private joyStickControls: JoyStickControls;
33
35
 
34
36
  constructor(viewer: Viewer) {
35
37
  this.controls = new FlyControls(viewer.camera, viewer.canvas);
36
38
  this.controls.addEventListener("change", this.controlsChange);
37
39
  this.controls.addEventListener("flyspeedchange", this.flyspeedChange);
40
+
41
+ this.joyStickControls = new JoyStickControls(viewer.camera, viewer.canvas, viewer.canvas, []);
42
+ this.joyStickControls.addEventListener("change", this.controlsChange);
43
+
38
44
  this.viewer = viewer;
39
45
  this.viewer.addEventListener("render", this.viewerRender);
40
46
  this.viewer.addEventListener("zoom", this.viewerZoom);
@@ -49,11 +55,16 @@ export class FlyDragger implements IDragger {
49
55
  this.controls.removeEventListener("flyspeedchange", this.flyspeedChange);
50
56
  this.controls.removeEventListener("change", this.controlsChange);
51
57
  this.controls.dispose();
58
+
59
+ this.joyStickControls.removeEventListener("change", this.controlsChange);
60
+ this.joyStickControls.dispose();
52
61
  }
53
62
 
54
63
  updateControls = () => {
55
64
  const size = this.viewer.extents.getSize(new Vector3());
56
65
  this.controls.movementSpeed = Math.min(size.x, size.y, size.z) / 2;
66
+ this.joyStickControls.movementSpeed = this.controls.movementSpeed;
67
+ this.joyStickControls.multiplier = this.controls.multiplier;
57
68
  };
58
69
 
59
70
  updateControlsCamera = () => {
@@ -67,9 +78,11 @@ export class FlyDragger implements IDragger {
67
78
 
68
79
  flyspeedChange = (event: any) => {
69
80
  this.viewer.emitEvent(event);
81
+ this.joyStickControls.multiplier = this.controls.multiplier;
70
82
  };
71
83
 
72
84
  viewerRender = () => {
85
+ this.joyStickControls.update();
73
86
  this.controls.update();
74
87
  };
75
88
 
@@ -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,
@@ -89,8 +90,6 @@ export class MeasureLineDragger extends OrbitDragger {
89
90
 
90
91
  this.snapper.dispose();
91
92
 
92
- this.snapper.dispose();
93
-
94
93
  this.overlay.detach();
95
94
  this.overlay.dispose();
96
95
 
@@ -147,7 +146,7 @@ export class MeasureLineDragger extends OrbitDragger {
147
146
  };
148
147
 
149
148
  updateSnapper = () => {
150
- this.snapper.update(this.viewer);
149
+ this.snapper.setFromViewer(this.viewer);
151
150
  };
152
151
 
153
152
  updateSnapperCamera = () => {
@@ -167,6 +166,7 @@ class MeasureSnapper {
167
166
  public camera: Camera;
168
167
  private canvas: HTMLCanvasElement;
169
168
  private objects: Object3D[];
169
+ private clippingPlanes: Plane[];
170
170
  private raycaster: Raycaster;
171
171
  private detectRadiusInPixels: number;
172
172
  private edgesCache: WeakMap<any, EdgesGeometry>;
@@ -175,6 +175,7 @@ class MeasureSnapper {
175
175
  this.camera = camera;
176
176
  this.canvas = canvas;
177
177
  this.objects = [];
178
+ this.clippingPlanes = [];
178
179
  this.raycaster = new Raycaster();
179
180
  this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
180
181
  this.edgesCache = new WeakMap();
@@ -202,7 +203,7 @@ class MeasureSnapper {
202
203
  return target.set(event.clientX, event.clientY);
203
204
  }
204
205
 
205
- getPointerIntersects(mouse: Vector2, objects: Object3D[]): Array<Intersection<Object3D>> {
206
+ getPointerIntersects(mouse: Vector2): Array<Intersection<Object3D>> {
206
207
  const rect = this.canvas.getBoundingClientRect();
207
208
  const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
208
209
  const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
@@ -219,7 +220,13 @@ class MeasureSnapper {
219
220
  Sprite: {},
220
221
  };
221
222
 
222
- 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;
223
230
  }
224
231
 
225
232
  getDetectRadius(point: Vector3): number {
@@ -259,7 +266,7 @@ class MeasureSnapper {
259
266
  getSnapPoint(event: PointerEvent): Vector3 {
260
267
  const mouse = this.getMousePosition(event, new Vector2());
261
268
 
262
- const intersections = this.getPointerIntersects(mouse, this.objects);
269
+ const intersections = this.getPointerIntersects(mouse);
263
270
  if (intersections.length === 0) return undefined;
264
271
 
265
272
  // ===================== AI-CODE-START ======================
@@ -323,11 +330,14 @@ class MeasureSnapper {
323
330
  return intersectionPoint.clone();
324
331
  }
325
332
 
326
- update(viewer: Viewer) {
333
+ setFromViewer(viewer: Viewer) {
327
334
  this.objects.length = 0;
328
335
  viewer.models.forEach((model) => {
329
336
  model.getVisibleObjects().forEach((object) => this.objects.push(object));
330
337
  });
338
+
339
+ this.camera = viewer.camera;
340
+ this.clippingPlanes = viewer.renderer.clippingPlanes || [];
331
341
  }
332
342
  }
333
343
 
@@ -337,22 +347,19 @@ class MeasureOverlay {
337
347
  public container: HTMLElement;
338
348
  public lines: MeasureLine[] = [];
339
349
  public projector: MeasureProjector;
350
+ private resizeObserver: ResizeObserver;
340
351
 
341
352
  constructor(camera: Camera, canvas: HTMLCanvasElement) {
342
353
  this.camera = camera;
343
354
  this.canvas = canvas;
344
355
  this.projector = new MeasureProjector(camera, canvas);
356
+ this.resizeObserver = new ResizeObserver(this.resizeContainer);
345
357
  }
346
358
 
347
359
  attach() {
348
360
  this.container = document.createElement("div");
349
361
  this.container.id = "measure-container";
350
- this.container.style.background = "rgba(0,0,0,0)";
351
362
  this.container.style.position = "absolute";
352
- this.container.style.top = "0px";
353
- this.container.style.left = "0px";
354
- this.container.style.width = "100%";
355
- this.container.style.height = "100%";
356
363
  this.container.style.outline = "none";
357
364
  this.container.style.pointerEvents = "none";
358
365
  this.container.style.overflow = "hidden";
@@ -360,6 +367,7 @@ class MeasureOverlay {
360
367
  if (!this.canvas.parentElement) return;
361
368
 
362
369
  this.canvas.parentElement.appendChild(this.container);
370
+ this.resizeObserver.observe(this.canvas.parentElement);
363
371
  }
364
372
 
365
373
  dispose() {
@@ -367,6 +375,8 @@ class MeasureOverlay {
367
375
  }
368
376
 
369
377
  detach() {
378
+ this.resizeObserver.disconnect();
379
+
370
380
  this.container.remove();
371
381
  this.container = undefined;
372
382
  }
@@ -392,6 +402,15 @@ class MeasureOverlay {
392
402
  removeLine(line: MeasureLine) {
393
403
  this.lines = this.lines.filter((x) => x !== line);
394
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
+ };
395
414
  }
396
415
 
397
416
  const _middlePoint = new Vector3();