@inweb/viewer-three 25.9.7 → 25.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +26 -18
  2. package/dist/viewer-three.js +10315 -6179
  3. package/dist/viewer-three.js.map +1 -1
  4. package/dist/viewer-three.min.js +3 -2
  5. package/dist/viewer-three.module.js +1881 -322
  6. package/dist/viewer-three.module.js.map +1 -1
  7. package/lib/Viewer/IDisposable.d.ts +6 -0
  8. package/lib/Viewer/Viewer.d.ts +113 -14
  9. package/lib/Viewer/commands/ApplyModelTransform.d.ts +1 -0
  10. package/lib/Viewer/commands/ClearMarkup.d.ts +1 -0
  11. package/lib/Viewer/commands/ClearSlices.d.ts +1 -0
  12. package/lib/Viewer/commands/CreatePreview.d.ts +1 -0
  13. package/lib/Viewer/commands/Explode.d.ts +1 -0
  14. package/lib/Viewer/commands/GetDefaultViewPositions.d.ts +1 -0
  15. package/lib/Viewer/commands/GetModels.d.ts +1 -0
  16. package/lib/Viewer/commands/GetSelected.d.ts +1 -0
  17. package/lib/Viewer/commands/HideSelected.d.ts +1 -0
  18. package/lib/Viewer/commands/IsolateSelected.d.ts +1 -0
  19. package/lib/Viewer/commands/RegenerateAll.d.ts +1 -0
  20. package/lib/Viewer/commands/ResetView.d.ts +1 -0
  21. package/lib/Viewer/commands/SelectModel.d.ts +1 -0
  22. package/lib/Viewer/commands/SetActiveDragger.d.ts +1 -0
  23. package/lib/Viewer/commands/SetDefaultViewPosition.d.ts +13 -0
  24. package/lib/Viewer/commands/SetMarkupColor.d.ts +1 -0
  25. package/lib/Viewer/commands/SetSelected.d.ts +1 -0
  26. package/lib/Viewer/commands/ShowAll.d.ts +1 -0
  27. package/lib/Viewer/commands/Unselect.d.ts +1 -0
  28. package/lib/Viewer/commands/ZoomToExtents.d.ts +1 -0
  29. package/lib/Viewer/commands/ZoomToObjects.d.ts +1 -0
  30. package/lib/Viewer/commands/ZoomToSelected.d.ts +1 -0
  31. package/lib/Viewer/commands/index.d.ts +22 -0
  32. package/lib/Viewer/components/AxesHelperComponent.d.ts +10 -0
  33. package/lib/Viewer/components/BackgroundComponent.d.ts +4 -4
  34. package/lib/Viewer/components/{DefaultCameraPositionComponent.d.ts → DefaultPositionComponent.d.ts} +3 -2
  35. package/lib/Viewer/components/ExtentsComponent.d.ts +8 -0
  36. package/lib/Viewer/components/ExtentsHelperComponent.d.ts +9 -0
  37. package/lib/Viewer/components/LightComponent.d.ts +5 -5
  38. package/lib/Viewer/components/RenderLoopComponent.d.ts +3 -3
  39. package/lib/Viewer/components/ResizeCanvasComponent.d.ts +2 -2
  40. package/lib/Viewer/components/SelectionComponent.d.ts +19 -0
  41. package/lib/Viewer/components/ViewPositionComponent.d.ts +31 -0
  42. package/lib/Viewer/components/WCSHelperComponent.d.ts +9 -0
  43. package/lib/Viewer/controls/WalkControls.d.ts +26 -0
  44. package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +17 -0
  45. package/lib/Viewer/draggers/CuttingPlaneXAxis.d.ts +5 -0
  46. package/lib/Viewer/draggers/CuttingPlaneYAxis.d.ts +5 -0
  47. package/lib/Viewer/draggers/CuttingPlaneZAxis.d.ts +5 -0
  48. package/lib/Viewer/draggers/OrbitDragger.d.ts +9 -5
  49. package/lib/Viewer/draggers/WalkDragger.d.ts +7 -33
  50. package/lib/Viewer/helpers/PlaneHelper.d.ts +11 -0
  51. package/lib/Viewer/helpers/WCSHelper.d.ts +10 -0
  52. package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +3 -3
  53. package/lib/index.d.ts +1 -0
  54. package/package.json +7 -6
  55. package/src/Viewer/IDisposable.ts +29 -0
  56. package/src/Viewer/Viewer.ts +218 -49
  57. package/src/Viewer/commands/ApplyModelTransform.ts +33 -0
  58. package/src/Viewer/commands/ClearMarkup.ts +29 -0
  59. package/src/Viewer/commands/ClearSlices.ts +32 -0
  60. package/src/Viewer/commands/CreatePreview.ts +32 -0
  61. package/src/Viewer/commands/Explode.ts +83 -0
  62. package/src/Viewer/commands/GetDefaultViewPositions.ts +31 -0
  63. package/src/Viewer/commands/GetModels.ts +32 -0
  64. package/src/Viewer/commands/GetSelected.ts +31 -0
  65. package/src/Viewer/commands/HideSelected.ts +40 -0
  66. package/src/Viewer/commands/IsolateSelected.ts +50 -0
  67. package/src/Viewer/commands/RegenerateAll.ts +32 -0
  68. package/src/Viewer/commands/ResetView.ts +41 -0
  69. package/src/Viewer/commands/SelectModel.ts +32 -0
  70. package/src/Viewer/commands/SetActiveDragger.ts +29 -0
  71. package/src/Viewer/commands/SetDefaultViewPosition.ts +83 -0
  72. package/src/Viewer/commands/SetMarkupColor.ts +30 -0
  73. package/src/Viewer/commands/SetSelected.ts +44 -0
  74. package/src/Viewer/commands/ShowAll.ts +34 -0
  75. package/src/Viewer/commands/Unselect.ts +37 -0
  76. package/src/Viewer/commands/ZoomToExtents.ts +47 -0
  77. package/src/Viewer/commands/ZoomToObjects.ts +55 -0
  78. package/src/Viewer/commands/ZoomToSelected.ts +51 -0
  79. package/src/Viewer/commands/index.ts +45 -0
  80. package/src/Viewer/components/AxesHelperComponent.ts +70 -0
  81. package/src/Viewer/components/BackgroundComponent.ts +9 -7
  82. package/src/Viewer/components/{DefaultCameraPositionComponent.ts → DefaultPositionComponent.ts} +11 -22
  83. package/src/Viewer/components/ExtentsComponent.ts +54 -0
  84. package/src/Viewer/components/ExtentsHelperComponent.ts +58 -0
  85. package/src/Viewer/components/LightComponent.ts +14 -10
  86. package/src/Viewer/components/RenderLoopComponent.ts +6 -6
  87. package/src/Viewer/components/ResizeCanvasComponent.ts +2 -2
  88. package/src/Viewer/components/SelectionComponent.ts +132 -0
  89. package/src/Viewer/components/ViewPositionComponent.ts +165 -0
  90. package/src/Viewer/components/WCSHelperComponent.ts +46 -0
  91. package/src/Viewer/controls/WalkControls.ts +221 -0
  92. package/src/Viewer/draggers/CuttingPlaneDragger.ts +110 -0
  93. package/src/Viewer/draggers/CuttingPlaneXAxis.ts +33 -0
  94. package/src/Viewer/draggers/CuttingPlaneYAxis.ts +33 -0
  95. package/src/Viewer/draggers/CuttingPlaneZAxis.ts +33 -0
  96. package/src/Viewer/draggers/OrbitDragger.ts +47 -22
  97. package/src/Viewer/draggers/PanDragger.ts +4 -3
  98. package/src/Viewer/draggers/WalkDragger.ts +27 -216
  99. package/src/Viewer/draggers/ZoomDragger.ts +4 -3
  100. package/src/Viewer/helpers/PlaneHelper.ts +99 -0
  101. package/src/Viewer/helpers/WCSHelper.ts +119 -0
  102. package/src/Viewer/loaders/GLTFLoadingManager.ts +6 -6
  103. package/src/index.ts +2 -0
  104. package/lib/Viewer/IComponent.d.ts +0 -3
  105. package/lib/Viewer/components/ObjectSelectionComponent.d.ts +0 -16
  106. package/lib/Viewer/draggers/ClippingPlaneDragger.d.ts +0 -17
  107. package/src/Viewer/IComponent.ts +0 -3
  108. package/src/Viewer/components/ObjectSelectionComponent.ts +0 -105
  109. package/src/Viewer/draggers/ClippingPlaneDragger.ts +0 -120
@@ -21,10 +21,10 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { IComponent } from "../IComponent";
24
+ import { IDisposable } from "../IDisposable";
25
25
  import type { Viewer } from "../Viewer";
26
26
 
27
- export class ResizeCanvasComponent implements IComponent {
27
+ export class ResizeCanvasComponent implements IDisposable {
28
28
  protected viewer: Viewer;
29
29
  protected resizeObserver: ResizeObserver;
30
30
 
@@ -0,0 +1,132 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2024, 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-2024 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 { Intersection, MeshBasicMaterial, Object3D, Raycaster, Vector2 } from "three";
25
+
26
+ import { IDisposable } from "../IDisposable";
27
+ import type { Viewer } from "../Viewer";
28
+
29
+ export class SelectionComponent implements IDisposable {
30
+ protected viewer: Viewer;
31
+ protected raycaster: Raycaster;
32
+ protected downPosition: Vector2;
33
+ protected facesMaterial: MeshBasicMaterial;
34
+
35
+ constructor(viewer: Viewer) {
36
+ this.viewer = viewer;
37
+ this.raycaster = new Raycaster();
38
+ this.downPosition = new Vector2();
39
+
40
+ const { facesColor, facesTransparancy } = this.viewer.options;
41
+ this.facesMaterial = new MeshBasicMaterial();
42
+ this.facesMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
43
+ this.facesMaterial.opacity = (255 - facesTransparancy) / 255;
44
+ this.facesMaterial.transparent = true;
45
+
46
+ this.viewer.addEventListener("pointerdown", this.onPointerDown);
47
+ this.viewer.addEventListener("pointerup", this.onPointerUp);
48
+ this.viewer.addEventListener("dblclick", this.onDoubleClick);
49
+ this.viewer.addEventListener("optionschange", this.optionsChange);
50
+ }
51
+
52
+ dispose() {
53
+ this.facesMaterial.dispose();
54
+
55
+ this.viewer.removeEventListener("pointerdown", this.onPointerDown);
56
+ this.viewer.removeEventListener("pointerup", this.onPointerUp);
57
+ this.viewer.removeEventListener("dblclick", this.onDoubleClick);
58
+ this.viewer.removeEventListener("optionschange", this.optionsChange);
59
+ }
60
+
61
+ onPointerDown = (event: PointerEvent) => {
62
+ if (!event.isPrimary || event.button !== 0) return;
63
+
64
+ this.getMousePosition(event, this.downPosition);
65
+ };
66
+
67
+ onPointerUp = (event: PointerEvent) => {
68
+ if (!event.isPrimary) return;
69
+
70
+ const upPosition = this.getMousePosition(event, new Vector2());
71
+ if (this.downPosition.distanceTo(upPosition) !== 0) return;
72
+
73
+ const intersects = this.getPointerIntersects(upPosition);
74
+
75
+ this.clearSelection();
76
+ if (intersects.length > 0) this.select(intersects[0].object);
77
+
78
+ this.viewer.update();
79
+ this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() });
80
+ };
81
+
82
+ onDoubleClick = (event: MouseEvent) => {
83
+ if (event.button !== 0) return;
84
+
85
+ this.viewer.executeCommand("zoomToSelected");
86
+ };
87
+
88
+ getMousePosition(event: MouseEvent, position: Vector2): Vector2 {
89
+ const rect = this.viewer.canvas.getBoundingClientRect();
90
+
91
+ position.setX((event.clientX - rect.left) / rect.width);
92
+ position.setY((event.clientY - rect.top) / rect.height);
93
+
94
+ return position;
95
+ }
96
+
97
+ getPointerIntersects(position: Vector2): Array<Intersection<Object3D>> {
98
+ const mouse = new Vector2(position.x * 2 - 1, -(position.y * 2) + 1);
99
+ this.raycaster.setFromCamera(mouse, this.viewer.camera);
100
+
101
+ const objects = [];
102
+ this.viewer.scene.traverseVisible((child) => objects.push(child));
103
+
104
+ return this.raycaster.intersectObjects(objects, false);
105
+ }
106
+
107
+ select(object: any) {
108
+ if (object.isSelected) return;
109
+
110
+ object.isSelected = true;
111
+ object.originalMaterial = object.material;
112
+ object.material = this.facesMaterial;
113
+
114
+ this.viewer.selected.push(object);
115
+ }
116
+
117
+ clearSelection() {
118
+ this.viewer.selected.forEach((object: any) => {
119
+ object.isSelected = false;
120
+ object.material = object.originalMaterial;
121
+ });
122
+ this.viewer.selected.length = 0;
123
+ }
124
+
125
+ optionsChange = () => {
126
+ const { facesColor, facesTransparancy } = this.viewer.options;
127
+ this.facesMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
128
+ this.facesMaterial.opacity = (255 - facesTransparancy) / 255;
129
+
130
+ this.viewer.update();
131
+ };
132
+ }
@@ -0,0 +1,165 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2024, 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-2024 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 { Euler, Quaternion, Object3D, Vector3 } from "three";
25
+
26
+ import { CommandEvent, RenderEvent } from "@inweb/viewer-core";
27
+ import { IDisposable } from "../IDisposable";
28
+ import type { Viewer } from "../Viewer";
29
+
30
+ export const defaultViewPositions = {
31
+ top: new Vector3(0, 0, 1),
32
+ bottom: new Vector3(0, 0, -1),
33
+ left: new Vector3(-1, 0, 0),
34
+ right: new Vector3(1, 0, 0),
35
+ front: new Vector3(0, 1, 0),
36
+ back: new Vector3(0, -1, 0),
37
+ sw: new Vector3(-0.5, -0.5, 1.0).normalize(),
38
+ se: new Vector3(0.5, -0.5, 1.0).normalize(),
39
+ ne: new Vector3(0.5, 0.5, 1.0).normalize(),
40
+ nw: new Vector3(-0.5, 0.5, 1.0).normalize(),
41
+ };
42
+
43
+ export class ViewPositionComponent implements IDisposable {
44
+ private position: string;
45
+ private center: Vector3;
46
+ private targetPosition: Vector3;
47
+ private targetQuaternion: Quaternion;
48
+ private radius: number;
49
+ private q1: Quaternion;
50
+ private q2: Quaternion;
51
+ private animating: boolean;
52
+ private viewer: Viewer;
53
+
54
+ constructor(viewer: Viewer) {
55
+ this.animating = false;
56
+ // this.center = new THREE.Vector3();
57
+ this.targetPosition = new Vector3();
58
+ this.targetQuaternion = new Quaternion();
59
+ this.q1 = new Quaternion();
60
+ this.q2 = new Quaternion();
61
+ this.viewer = viewer;
62
+ this.viewer.addEventListener("render", this.onRender);
63
+ this.viewer.addEventListener("command", this.onCommand);
64
+
65
+ console.log("--- ViewPositionComponent.constructor");
66
+ }
67
+
68
+ dispose() {
69
+ this.viewer.off("render", this.onRender);
70
+ this.viewer.off("viewposition", this.onCommand);
71
+
72
+ console.log("--- ViewPositionComponent.dispose");
73
+ }
74
+
75
+ onCommand = (event: CommandEvent) => {
76
+ if (this.animating) return;
77
+
78
+ this.position = event.data === "setDefaultViewPosition" ? event.args[0] : event.data;
79
+
80
+ console.log("--- ViewPositionComponent:onCommand", this.position);
81
+
82
+ switch (this.position) {
83
+ case "left":
84
+ this.targetPosition.set(-1, 0, 0);
85
+ this.targetQuaternion.setFromEuler(new Euler(0, -Math.PI * 0.5, 0));
86
+ break;
87
+
88
+ case "right":
89
+ this.targetPosition.set(1, 0, 0);
90
+ this.targetQuaternion.setFromEuler(new Euler(0, Math.PI * 0.5, 0));
91
+ break;
92
+
93
+ case "front":
94
+ this.targetPosition.set(0, 1, 0);
95
+ this.targetQuaternion.setFromEuler(new Euler(-Math.PI * 0.5, 0, 0));
96
+ // this.targetQuaternion.setFromEuler(new Euler(Math.PI * 0.5, 0, 0));
97
+ break;
98
+
99
+ case "back":
100
+ this.targetPosition.set(0, -1, 0);
101
+ this.targetQuaternion.setFromEuler(new Euler(Math.PI * 0.5, 0, 0));
102
+ break;
103
+
104
+ case "top":
105
+ default:
106
+ this.targetPosition.set(0, 0, 1);
107
+ this.targetQuaternion.setFromEuler(new Euler());
108
+ break;
109
+
110
+ case "bottom":
111
+ this.targetPosition.set(0, 0, -1);
112
+ this.targetQuaternion.setFromEuler(new Euler(0, Math.PI, 0));
113
+ break;
114
+ }
115
+
116
+ this.center = this.viewer.extents.getCenter(new Vector3());
117
+
118
+ this.radius = this.viewer.camera.position.distanceTo(this.center);
119
+ this.targetPosition.multiplyScalar(this.radius).add(this.center);
120
+
121
+ const dummy = new Object3D();
122
+ dummy.position.copy(this.center);
123
+
124
+ dummy.lookAt(this.viewer.camera.position);
125
+ this.q1.copy(dummy.quaternion);
126
+
127
+ dummy.lookAt(this.targetPosition);
128
+ this.q2.copy(dummy.quaternion);
129
+
130
+ this.animating = true;
131
+
132
+ // this.viewer.camera.position.set(0, 0, 1).applyQuaternion(this.q2).multiplyScalar(this.radius).add(this.center);
133
+ // this.viewer.camera.quaternion.copy(this.targetQuaternion);
134
+ // this.viewer.camera.updateProjectionMatrix();
135
+
136
+ this.viewer.update();
137
+ };
138
+
139
+ onRender = (event: RenderEvent) => {
140
+ if (!this.animating) return;
141
+
142
+ const turnRate = 2 * Math.PI; // turn rate in angles per second
143
+ const step = (event.deltaTime * turnRate) / 10;
144
+
145
+ // animate position by doing a slerp and then scaling the position on the unit sphere
146
+ this.q1.rotateTowards(this.q2, step);
147
+ this.viewer.camera.position.set(0, 0, 1).applyQuaternion(this.q1).multiplyScalar(this.radius).add(this.center);
148
+
149
+ // animate orientation
150
+ this.viewer.camera.quaternion.rotateTowards(this.targetQuaternion, step);
151
+
152
+ this.viewer.update();
153
+
154
+ if (this.q1.angleTo(this.q2) === 0) {
155
+ this.animating = false;
156
+
157
+ // console.log("--- ViewPositionComponent.onRender", event.deltaTime, step, this.animating);
158
+
159
+ this.viewer.emit({ type: "viewposition", data: this.position });
160
+ return;
161
+ }
162
+
163
+ // console.log("--- ViewPositionComponent.onRender", event.deltaTime, step, this.animating);
164
+ };
165
+ }
@@ -0,0 +1,46 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2024, 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-2024 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 { IDisposable } from "../IDisposable";
25
+ import type { Viewer } from "../Viewer";
26
+ import { WCSHelper } from "../helpers/WCSHelper";
27
+
28
+ export class WCSHelperComponent implements IDisposable {
29
+ private wcsHelper: WCSHelper;
30
+ private viewer: Viewer;
31
+
32
+ constructor(viewer: Viewer) {
33
+ this.wcsHelper = new WCSHelper(viewer.camera);
34
+ this.viewer = viewer;
35
+ this.viewer.addEventListener("render", this.viewerRender);
36
+ }
37
+
38
+ dispose() {
39
+ this.viewer.removeEventListener("render", this.viewerRender);
40
+ this.wcsHelper.dispose();
41
+ }
42
+
43
+ viewerRender = () => {
44
+ if (!this.viewer.extents.isEmpty()) this.wcsHelper.render(this.viewer.renderer);
45
+ };
46
+ }
@@ -0,0 +1,221 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2002-2024, 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-2024 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 { Clock, Camera, EventDispatcher, Quaternion, Vector2, Vector3 } from "three";
25
+
26
+ const _changeEvent = { type: "change" };
27
+
28
+ export class WalkControls extends EventDispatcher {
29
+ public camera: Camera;
30
+ public canvas: HTMLElement;
31
+
32
+ public movementSpeed = 0.2;
33
+ public lookSpeed = 5;
34
+ public multiplier = 5;
35
+
36
+ private moveKeys: Set<string>;
37
+ private moveWheel = 0;
38
+ private moveClock: Clock;
39
+
40
+ private quaternion: Quaternion;
41
+ private downPosition: Vector2;
42
+ private mouseDragOn = false;
43
+ public rotateDelta: Vector2;
44
+
45
+ constructor(camera: Camera, canvas: HTMLElement) {
46
+ super();
47
+
48
+ this.camera = camera;
49
+ this.canvas = canvas;
50
+
51
+ this.moveKeys = new Set();
52
+ this.moveClock = new Clock();
53
+
54
+ this.quaternion = camera.quaternion.clone();
55
+ this.downPosition = new Vector2(0, 0);
56
+ this.rotateDelta = new Vector2(0, 0);
57
+
58
+ this.canvas.addEventListener("pointerdown", this.onPointerDown);
59
+ this.canvas.addEventListener("pointermove", this.onPointerMove);
60
+ this.canvas.addEventListener("pointerup", this.onPointerUp);
61
+ this.canvas.addEventListener("pointercancel", this.onPointerCancel);
62
+ this.canvas.addEventListener("wheel", this.onWheel);
63
+
64
+ window.addEventListener("keydown", this.onKeyDown);
65
+ window.addEventListener("keyup", this.onKeyUp);
66
+ }
67
+
68
+ dispose() {
69
+ this.canvas.removeEventListener("pointerdown", this.onPointerDown);
70
+ this.canvas.removeEventListener("pointermove", this.onPointerMove);
71
+ this.canvas.removeEventListener("pointerup", this.onPointerUp);
72
+ this.canvas.removeEventListener("pointercancel", this.onPointerCancel);
73
+ this.canvas.removeEventListener("wheel", this.onWheel);
74
+
75
+ window.removeEventListener("keydown", this.onKeyDown);
76
+ window.removeEventListener("keyup", this.onKeyUp);
77
+ }
78
+
79
+ onPointerDown = (event: PointerEvent) => {
80
+ if (!event.isPrimary || event.button !== 0) return;
81
+
82
+ this.canvas.setPointerCapture(event.pointerId);
83
+ this.downPosition.set(event.clientX, event.clientY);
84
+ this.quaternion.copy(this.camera.quaternion);
85
+ this.mouseDragOn = true;
86
+ };
87
+
88
+ onPointerMove = (event: PointerEvent) => {
89
+ if (!event.isPrimary || !this.mouseDragOn) return;
90
+
91
+ const movePosition = new Vector2(event.clientX, event.clientY);
92
+ if (this.downPosition.distanceTo(movePosition) === 0) return;
93
+
94
+ this.rotateDelta.copy(this.downPosition).sub(movePosition);
95
+ this.rotateCamera(this.rotateDelta);
96
+ this.dispatchEvent(_changeEvent);
97
+ };
98
+
99
+ onPointerUp = (event: PointerEvent) => {
100
+ if (!event.isPrimary || event.button !== 0) return;
101
+
102
+ this.canvas.releasePointerCapture(event.pointerId);
103
+ this.mouseDragOn = false;
104
+ };
105
+
106
+ onPointerCancel = (event: PointerEvent) => {
107
+ this.canvas.dispatchEvent(new PointerEvent("pointerup", event));
108
+ };
109
+
110
+ onWheel = (event: WheelEvent) => {
111
+ this.moveWheel = event.deltaY;
112
+ this.update();
113
+ };
114
+
115
+ onKeyDown = (event: KeyboardEvent) => {
116
+ switch (event.code) {
117
+ case "NumpadSubtract":
118
+ case "Minus":
119
+ if (this.multiplier > 1) {
120
+ this.multiplier = this.multiplier - 1;
121
+ this.dispatchEvent({ type: "walkspeedchange", data: this.multiplier });
122
+ }
123
+ break;
124
+
125
+ case "NumpadAdd":
126
+ case "Equal":
127
+ if (this.multiplier < 10) {
128
+ this.multiplier = this.multiplier + 1;
129
+ this.dispatchEvent({ type: "walkspeedchange", data: this.multiplier });
130
+ }
131
+ break;
132
+
133
+ case "ArrowLeft":
134
+ case "ArrowRight":
135
+ case "ArrowUp":
136
+ case "ArrowDown":
137
+ case "KeyW":
138
+ case "KeyS":
139
+ case "KeyA":
140
+ case "KeyD":
141
+ case "KeyQ":
142
+ case "KeyE":
143
+ this.moveKeys.add(event.code);
144
+ this.update();
145
+ break;
146
+ }
147
+ };
148
+
149
+ onKeyUp = (event: KeyboardEvent) => {
150
+ switch (event.code) {
151
+ case "ArrowLeft":
152
+ case "ArrowRight":
153
+ case "ArrowUp":
154
+ case "ArrowDown":
155
+ case "KeyW":
156
+ case "KeyS":
157
+ case "KeyA":
158
+ case "KeyD":
159
+ case "KeyQ":
160
+ case "KeyE":
161
+ this.moveKeys.delete(event.code);
162
+ this.update();
163
+ break;
164
+ }
165
+ };
166
+
167
+ update() {
168
+ if (this.moveKeys.size > 0) {
169
+ const timeDelta = this.moveClock.getDelta();
170
+ const moveDelta = timeDelta * this.movementSpeed * this.multiplier;
171
+
172
+ if (this.moveKeys.has("KeyW")) this.camera.translateZ(-moveDelta);
173
+ if (this.moveKeys.has("KeyS")) this.camera.translateZ(moveDelta);
174
+
175
+ if (this.moveKeys.has("KeyA")) this.camera.translateX(-moveDelta);
176
+ if (this.moveKeys.has("KeyD")) this.camera.translateX(moveDelta);
177
+
178
+ if (this.moveKeys.has("KeyQ")) this.camera.translateY(moveDelta);
179
+ if (this.moveKeys.has("KeyE")) this.camera.translateY(-moveDelta);
180
+
181
+ const lookDelta = this.lookSpeed + (this.multiplier - 1);
182
+
183
+ if (this.moveKeys.has("ArrowUp")) this.rotateCamera(this.rotateDelta.add(new Vector2(0, -lookDelta / 2)));
184
+ if (this.moveKeys.has("ArrowDown")) this.rotateCamera(this.rotateDelta.add(new Vector2(0, lookDelta / 2)));
185
+
186
+ if (this.moveKeys.has("ArrowLeft")) this.rotateCamera(this.rotateDelta.add(new Vector2(lookDelta, 0)));
187
+ if (this.moveKeys.has("ArrowRight")) this.rotateCamera(this.rotateDelta.add(new Vector2(-lookDelta, 0)));
188
+
189
+ this.moveWheel = 0;
190
+ this.dispatchEvent(_changeEvent);
191
+ }
192
+
193
+ if (this.moveWheel !== 0) {
194
+ const moveDelta = this.moveWheel * 0.0001 * this.movementSpeed * this.multiplier;
195
+ this.camera.translateZ(-moveDelta);
196
+ this.moveWheel += -1 * Math.sign(this.moveWheel);
197
+ this.dispatchEvent(_changeEvent);
198
+ }
199
+
200
+ if (this.moveKeys.size === 0 && this.moveWheel === 0) {
201
+ this.moveClock.stop();
202
+ this.moveClock.autoStart = true;
203
+ }
204
+ }
205
+
206
+ rotateCamera(delta: Vector2) {
207
+ const rotateX = (Math.PI * delta.x) / this.canvas.clientWidth;
208
+ const rotateY = (Math.PI * delta.y) / this.canvas.clientHeight;
209
+
210
+ const xRotation = new Quaternion();
211
+ xRotation.setFromAxisAngle(this.camera.up, rotateX);
212
+
213
+ const yRotation = new Quaternion();
214
+ yRotation.setFromAxisAngle(new Vector3(1, 0, 0), rotateY);
215
+
216
+ const quaternion = this.quaternion.clone();
217
+ quaternion.premultiply(xRotation).multiply(yRotation).normalize();
218
+
219
+ this.camera.setRotationFromQuaternion(quaternion);
220
+ }
221
+ }