@inweb/viewer-three 26.10.3 → 26.10.5

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.3",
3
+ "version": "26.10.5",
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,18 +35,18 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
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"
38
+ "@inweb/client": "~26.10.5",
39
+ "@inweb/eventemitter2": "~26.10.5",
40
+ "@inweb/markup": "~26.10.5",
41
+ "@inweb/viewer-core": "~26.10.5"
42
42
  },
43
43
  "devDependencies": {
44
- "@types/three": "^0.179.0",
44
+ "@types/three": "^0.180.0",
45
45
  "potree-core": "^2.0.11",
46
- "three": "^0.179.1"
46
+ "three": "^0.180.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@types/three": "^0.179.0",
50
- "three": "^0.179.1"
49
+ "@types/three": "^0.180.0",
50
+ "three": "^0.180.0"
51
51
  }
52
52
  }
@@ -30,7 +30,7 @@
30
30
  // published under MIT license
31
31
 
32
32
  /* eslint-disable lines-between-class-members */
33
- /* eslint-disable @typescript-eslint/no-unused-vars */
33
+ /* eslint-disable no-unused-vars */
34
34
  /* eslint-disable prefer-const */
35
35
  /* eslint-disable no-useless-catch */
36
36
 
@@ -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
+ }
@@ -285,7 +285,6 @@ class OrbitControls extends EventDispatcher {
285
285
  // internals
286
286
  //
287
287
 
288
- // eslint-disable-next-line @typescript-eslint/no-this-alias
289
288
  const scope = this;
290
289
 
291
290
  scope.state = STATE.NONE;
@@ -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
 
@@ -367,7 +367,7 @@ class MeasureOverlay {
367
367
  if (!this.canvas.parentElement) return;
368
368
 
369
369
  this.canvas.parentElement.appendChild(this.container);
370
- this.resizeObserver.observe(this.canvas.parentElement);
370
+ this.resizeObserver.observe(this.canvas);
371
371
  }
372
372
 
373
373
  dispose() {
@@ -403,13 +403,16 @@ class MeasureOverlay {
403
403
  this.lines = this.lines.filter((x) => x !== line);
404
404
  }
405
405
 
406
- resizeContainer = (entries: ResizeObserverEntry[]) => {
407
- const { width, height } = entries[0].contentRect;
406
+ resizeContainer = () => {
407
+ const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.canvas;
408
408
 
409
- if (!width || !height) return; // <- invisible canvas, or canvas with parent removed
409
+ if (!offsetWidth || !offsetHeight) return; // <- invisible canvas, or canvas with parent removed
410
410
 
411
- this.container.style.width = `${width}px`;
412
- this.container.style.height = `${height}px`;
411
+ this.container.style.left = `${offsetLeft}px`;
412
+ this.container.style.top = `${offsetTop}px`;
413
+
414
+ this.container.style.width = `${offsetWidth}px`;
415
+ this.container.style.height = `${offsetHeight}px`;
413
416
  };
414
417
  }
415
418
 
@@ -26,10 +26,12 @@ import { type IDragger } from "@inweb/viewer-core";
26
26
 
27
27
  import type { Viewer } from "../Viewer";
28
28
  import { WalkControls } from "../controls/WalkControls";
29
+ import { JoyStickControls } from "../controls/JoyStickControls";
29
30
 
30
31
  export class WalkDragger implements IDragger {
31
32
  protected viewer: Viewer;
32
33
  public controls: WalkControls;
34
+ private joyStickControls: JoyStickControls;
33
35
 
34
36
  constructor(viewer: Viewer) {
35
37
  const meshOnlyGround: Mesh[] = [];
@@ -43,6 +45,10 @@ export class WalkDragger implements IDragger {
43
45
  this.controls = new WalkControls(viewer.camera, viewer.canvas, meshOnlyGround);
44
46
  this.controls.addEventListener("change", this.controlsChange);
45
47
  this.controls.addEventListener("walkspeedchange", this.walkspeedChange);
48
+
49
+ this.joyStickControls = new JoyStickControls(viewer.camera, viewer.canvas, viewer.canvas, meshOnlyGround);
50
+ this.joyStickControls.addEventListener("change", this.controlsChange);
51
+
46
52
  this.viewer = viewer;
47
53
  this.viewer.addEventListener("render", this.viewerRender);
48
54
  this.viewer.addEventListener("zoom", this.viewerZoom);
@@ -57,11 +63,16 @@ export class WalkDragger implements IDragger {
57
63
  this.controls.removeEventListener("walkspeedchange", this.walkspeedChange);
58
64
  this.controls.removeEventListener("change", this.controlsChange);
59
65
  this.controls.dispose();
66
+
67
+ this.joyStickControls.removeEventListener("change", this.controlsChange);
68
+ this.joyStickControls.dispose();
60
69
  }
61
70
 
62
71
  updateControls = () => {
63
72
  const size = this.viewer.extents.getSize(new Vector3());
64
73
  this.controls.movementSpeed = Math.min(size.x, size.y, size.z) / 2;
74
+ this.joyStickControls.movementSpeed = this.controls.movementSpeed;
75
+ this.joyStickControls.multiplier = this.controls.multiplier;
65
76
  };
66
77
 
67
78
  updateControlsCamera = () => {
@@ -75,9 +86,11 @@ export class WalkDragger implements IDragger {
75
86
 
76
87
  walkspeedChange = (event: any) => {
77
88
  this.viewer.emitEvent(event);
89
+ this.joyStickControls.multiplier = this.controls.multiplier;
78
90
  };
79
91
 
80
92
  viewerRender = () => {
93
+ this.joyStickControls.update();
81
94
  this.controls.update();
82
95
  };
83
96