@inweb/viewer-three 26.10.0 → 26.10.2

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.
@@ -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.0",
3
+ "version": "26.10.2",
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.0",
39
- "@inweb/eventemitter2": "~26.10.0",
40
- "@inweb/markup": "~26.10.0",
41
- "@inweb/viewer-core": "~26.10.0"
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"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/three": "^0.179.0",
@@ -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
  };
@@ -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" });
@@ -70,6 +70,7 @@ export class MeasureLineDragger extends OrbitDragger {
70
70
  this.viewer.addEventListener("isolate", this.updateSnapper);
71
71
  this.viewer.addEventListener("show", this.updateSnapper);
72
72
  this.viewer.addEventListener("showall", this.updateSnapper);
73
+ this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
73
74
  }
74
75
 
75
76
  override dispose() {
@@ -84,6 +85,9 @@ export class MeasureLineDragger extends OrbitDragger {
84
85
  this.viewer.removeEventListener("isolate", this.updateSnapper);
85
86
  this.viewer.removeEventListener("show", this.updateSnapper);
86
87
  this.viewer.removeEventListener("showall", this.updateSnapper);
88
+ this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
89
+
90
+ this.snapper.dispose();
87
91
 
88
92
  this.snapper.dispose();
89
93
 
@@ -145,6 +149,11 @@ export class MeasureLineDragger extends OrbitDragger {
145
149
  updateSnapper = () => {
146
150
  this.snapper.update(this.viewer);
147
151
  };
152
+
153
+ updateSnapperCamera = () => {
154
+ this.snapper.camera = this.viewer.camera;
155
+ this.overlay.camera = this.viewer.camera;
156
+ };
148
157
  }
149
158
 
150
159
  const _vertex = new Vector3();
@@ -155,7 +164,7 @@ const _center = new Vector3();
155
164
  const _projection = new Vector3();
156
165
 
157
166
  class MeasureSnapper {
158
- private camera: Camera;
167
+ public camera: Camera;
159
168
  private canvas: HTMLCanvasElement;
160
169
  private objects: Object3D[];
161
170
  private raycaster: Raycaster;
@@ -224,20 +233,20 @@ class MeasureSnapper {
224
233
  // Notes: Originally AI-generated, modified manually
225
234
 
226
235
  if (camera.isOrthographicCamera) {
227
- const worldHeight = camera.top - camera.bottom;
236
+ const fieldHeight = (camera.top - camera.bottom) / camera.zoom;
228
237
 
229
238
  const canvasHeight = this.canvas.height;
230
- const worldUnitsPerPixel = worldHeight / canvasHeight;
239
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
231
240
 
232
241
  return this.detectRadiusInPixels * worldUnitsPerPixel;
233
242
  }
234
243
 
235
244
  if (camera.isPerspectiveCamera) {
236
245
  const distance = camera.position.distanceTo(point);
237
- const worldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
246
+ const fieldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
238
247
 
239
248
  const canvasHeight = this.canvas.height;
240
- const worldUnitsPerPixel = worldHeight / canvasHeight;
249
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
241
250
 
242
251
  return this.detectRadiusInPixels * worldUnitsPerPixel;
243
252
  }
@@ -368,7 +377,7 @@ class MeasureOverlay {
368
377
  }
369
378
 
370
379
  render() {
371
- this.projector.updateProjectionMatrix();
380
+ this.projector.setFromCamera(this.camera);
372
381
  this.lines.forEach((line) => line.render());
373
382
  }
374
383
 
@@ -550,7 +559,7 @@ const point1 = new Vector2();
550
559
  const point2 = new Vector2();
551
560
 
552
561
  class MeasureProjector {
553
- private camera: Camera;
562
+ public camera: Camera;
554
563
  private canvas: HTMLElement;
555
564
 
556
565
  constructor(camera: Camera, canvas: HTMLCanvasElement) {
@@ -558,6 +567,11 @@ class MeasureProjector {
558
567
  this.canvas = canvas;
559
568
  }
560
569
 
570
+ setFromCamera(camera: Camera) {
571
+ this.camera = camera;
572
+ this.updateProjectionMatrix();
573
+ }
574
+
561
575
  updateProjectionMatrix() {
562
576
  const rect = this.canvas.getBoundingClientRect();
563
577
  _widthHalf = rect.width / 2;
@@ -46,6 +46,7 @@ export class OrbitDragger implements IDragger {
46
46
  this.viewer.on("viewposition", this.updateControls);
47
47
  this.viewer.addEventListener("zoom", this.updateControls);
48
48
  this.viewer.addEventListener("drawviewpoint", this.updateControls);
49
+ this.viewer.addEventListener("changecameramode", this.updateControlsCamera);
49
50
  this.viewer.addEventListener("contextmenu", this.stopContextMenu);
50
51
  this.updateControls();
51
52
  }
@@ -57,6 +58,7 @@ export class OrbitDragger implements IDragger {
57
58
  this.viewer.off("viewposition", this.updateControls);
58
59
  this.viewer.removeEventListener("zoom", this.updateControls);
59
60
  this.viewer.removeEventListener("drawviewpoint", this.updateControls);
61
+ this.viewer.removeEventListener("changecameramode", this.updateControlsCamera);
60
62
  this.viewer.removeEventListener("contextmenu", this.stopContextMenu);
61
63
 
62
64
  this.orbit.removeEventListener("start", this.controlsStart);
@@ -65,10 +67,14 @@ export class OrbitDragger implements IDragger {
65
67
  }
66
68
 
67
69
  updateControls = () => {
70
+ this.orbit.target.copy(this.viewer.target);
71
+ this.orbit.update();
72
+ };
73
+
74
+ updateControlsCamera = () => {
68
75
  this.orbit.maxDistance = this.viewer.camera.far;
69
76
  this.orbit.minDistance = this.viewer.camera.near;
70
77
  this.orbit.object = this.viewer.camera;
71
- this.orbit.target.copy(this.viewer.target);
72
78
  this.orbit.update();
73
79
  };
74
80
 
@@ -44,14 +44,16 @@ export class WalkDragger implements IDragger {
44
44
  this.controls.addEventListener("change", this.controlsChange);
45
45
  this.controls.addEventListener("walkspeedchange", this.walkspeedChange);
46
46
  this.viewer = viewer;
47
- this.viewer.on("render", this.viewerRender);
48
- this.viewer.on("zoom", this.viewerZoom);
47
+ this.viewer.addEventListener("render", this.viewerRender);
48
+ this.viewer.addEventListener("zoom", this.viewerZoom);
49
+ this.viewer.addEventListener("changecameramode", this.updateControlsCamera);
49
50
  this.updateControls();
50
51
  }
51
52
 
52
53
  dispose() {
53
- this.viewer.off("render", this.viewerRender);
54
- this.viewer.off("zoom", this.viewerZoom);
54
+ this.viewer.removeEventListener("render", this.viewerRender);
55
+ this.viewer.removeEventListener("zoom", this.viewerZoom);
56
+ this.viewer.removeEventListener("changecameramode", this.updateControlsCamera);
55
57
  this.controls.removeEventListener("walkspeedchange", this.walkspeedChange);
56
58
  this.controls.removeEventListener("change", this.controlsChange);
57
59
  this.controls.dispose();
@@ -62,6 +64,10 @@ export class WalkDragger implements IDragger {
62
64
  this.controls.movementSpeed = Math.min(size.x, size.y, size.z) / 2;
63
65
  };
64
66
 
67
+ updateControlsCamera = () => {
68
+ this.controls.object = this.viewer.camera;
69
+ };
70
+
65
71
  controlsChange = () => {
66
72
  this.viewer.update();
67
73
  this.viewer.emitEvent({ type: "changecamera" });