bilca commited on
Commit
66bfa9c
·
verified ·
1 Parent(s): 4c78aab

Upload 85 files

Browse files
Files changed (3) hide show
  1. dist/index.js +0 -0
  2. dist/index.js.map +0 -0
  3. src/controls/OrbitControls.ts +326 -307
dist/index.js CHANGED
The diff for this file is too large to render. See raw diff
 
dist/index.js.map CHANGED
The diff for this file is too large to render. See raw diff
 
src/controls/OrbitControls.ts CHANGED
@@ -4,314 +4,333 @@ import { Quaternion } from "../math/Quaternion";
4
  import { Vector3 } from "../math/Vector3";
5
 
6
  class OrbitControls {
7
- // Vertical (polar) limits: by default, free rotation.
8
- minAngle: number = -Infinity;
9
- maxAngle: number = Infinity;
10
- // Horizontal (azimuth) limits: by default, free rotation.
11
- minAzimuth: number = -Infinity;
12
- maxAzimuth: number = Infinity;
13
- minZoom: number = 0.1;
14
- maxZoom: number = 30;
15
- orbitSpeed: number = 1;
16
- panSpeed: number = 1;
17
- zoomSpeed: number = 1;
18
- dampening: number = 0.12;
19
- setCameraTarget: (newTarget: Vector3) => void = () => {};
20
- update: () => void;
21
- dispose: () => void;
22
-
23
- constructor(
24
- camera: Camera,
25
- canvas: HTMLElement,
26
- alpha: number = 0.5,
27
- beta: number = 0.5,
28
- radius: number = 5,
29
- enableKeyboardControls: boolean = true,
30
- inputTarget: Vector3 = new Vector3(),
31
- ) {
32
- let target = inputTarget.clone();
33
-
34
- let desiredTarget = target.clone();
35
- let desiredAlpha = alpha;
36
- let desiredBeta = beta;
37
- let desiredRadius = radius;
38
-
39
- let dragging = false;
40
- let panning = false;
41
- let lastDist = 0;
42
- let lastX = 0;
43
- let lastY = 0;
44
-
45
- const keys: { [key: string]: boolean } = {};
46
-
47
- let isUpdatingCamera = false;
48
-
49
- const onCameraChange = () => {
50
- if (isUpdatingCamera) return;
51
-
52
- const eulerRotation = camera.rotation.toEuler();
53
- desiredAlpha = -eulerRotation.y;
54
- desiredBeta = -eulerRotation.x;
55
-
56
- const x = camera.position.x - desiredRadius * Math.sin(desiredAlpha) * Math.cos(desiredBeta);
57
- const y = camera.position.y + desiredRadius * Math.sin(desiredBeta);
58
- const z = camera.position.z + desiredRadius * Math.cos(desiredAlpha) * Math.cos(desiredBeta);
59
-
60
- desiredTarget = new Vector3(x, y, z);
61
- };
62
-
63
- camera.addEventListener("objectChanged", onCameraChange);
64
-
65
- this.setCameraTarget = (newTarget: Vector3) => {
66
- const dx = newTarget.x - camera.position.x;
67
- const dy = newTarget.y - camera.position.y;
68
- const dz = newTarget.z - camera.position.z;
69
- desiredRadius = Math.sqrt(dx * dx + dy * dy + dz * dz);
70
- desiredBeta = Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
71
- desiredAlpha = -Math.atan2(dx, dz);
72
- desiredTarget = new Vector3(newTarget.x, newTarget.y, newTarget.z);
73
- };
74
-
75
- const computeZoomNorm = () => {
76
- return 0.1 + (0.9 * (desiredRadius - this.minZoom)) / (this.maxZoom - this.minZoom);
77
- };
78
-
79
- const onKeyDown = (e: KeyboardEvent) => {
80
- keys[e.code] = true;
81
- // Map arrow keys to WASD keys
82
- if (e.code === "ArrowUp") keys["KeyW"] = true;
83
- if (e.code === "ArrowDown") keys["KeyS"] = true;
84
- if (e.code === "ArrowLeft") keys["KeyA"] = true;
85
- if (e.code === "ArrowRight") keys["KeyD"] = true;
86
- };
87
-
88
- const onKeyUp = (e: KeyboardEvent) => {
89
- keys[e.code] = false;
90
- if (e.code === "ArrowUp") keys["KeyW"] = false;
91
- if (e.code === "ArrowDown") keys["KeyS"] = false;
92
- if (e.code === "ArrowLeft") keys["KeyA"] = false;
93
- if (e.code === "ArrowRight") keys["KeyD"] = false;
94
- };
95
-
96
- const onMouseDown = (e: MouseEvent) => {
97
- preventDefault(e);
98
- dragging = true;
99
- panning = e.button === 2;
100
- lastX = e.clientX;
101
- lastY = e.clientY;
102
- window.addEventListener("mouseup", onMouseUp);
103
- };
104
-
105
- const onMouseUp = (e: MouseEvent) => {
106
- preventDefault(e);
107
- dragging = false;
108
- panning = false;
109
- window.removeEventListener("mouseup", onMouseUp);
110
- };
111
-
112
- const onMouseMove = (e: MouseEvent) => {
113
- preventDefault(e);
114
- if (!dragging || !camera) return;
115
-
116
- const dx = e.clientX - lastX;
117
- const dy = e.clientY - lastY;
118
-
119
- if (panning) {
120
- const zoomNorm = computeZoomNorm();
121
- const panX = -dx * this.panSpeed * 0.01 * zoomNorm;
122
- const panY = -dy * this.panSpeed * 0.01 * zoomNorm;
123
- const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
124
- const right = new Vector3(R[0], R[3], R[6]);
125
- const up = new Vector3(R[1], R[4], R[7]);
126
- desiredTarget = desiredTarget.add(right.multiply(panX));
127
- desiredTarget = desiredTarget.add(up.multiply(panY));
128
- } else {
129
- desiredAlpha -= dx * this.orbitSpeed * 0.003;
130
- desiredBeta += dy * this.orbitSpeed * 0.003;
131
- // Clamp vertical angle (beta) only if limits are finite
132
- desiredBeta = Math.min(
133
- Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
134
- (this.maxAngle * Math.PI) / 180,
135
- );
136
- }
137
-
138
- lastX = e.clientX;
139
- lastY = e.clientY;
140
- };
141
-
142
- const onWheel = (e: WheelEvent) => {
143
- preventDefault(e);
144
- const zoomNorm = computeZoomNorm();
145
- desiredRadius += e.deltaY * this.zoomSpeed * 0.025 * zoomNorm;
146
- desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom);
147
- };
148
-
149
- const onTouchStart = (e: TouchEvent) => {
150
- preventDefault(e);
151
- if (e.touches.length === 1) {
152
- dragging = true;
153
- panning = false;
154
- lastX = e.touches[0].clientX;
155
- lastY = e.touches[0].clientY;
156
- lastDist = 0;
157
- } else if (e.touches.length === 2) {
158
- dragging = true;
159
- panning = true;
160
- lastX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
161
- lastY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
162
- const distX = e.touches[0].clientX - e.touches[1].clientX;
163
- const distY = e.touches[0].clientY - e.touches[1].clientY;
164
- lastDist = Math.sqrt(distX * distX + distY * distY);
165
- }
166
- };
167
-
168
- const onTouchEnd = (e: TouchEvent) => {
169
- preventDefault(e);
170
- dragging = false;
171
- panning = false;
172
- };
173
-
174
- const onTouchMove = (e: TouchEvent) => {
175
- preventDefault(e);
176
- if (!dragging || !camera) return;
177
-
178
- if (panning) {
179
- const zoomNorm = computeZoomNorm();
180
- const distX = e.touches[0].clientX - e.touches[1].clientX;
181
- const distY = e.touches[0].clientY - e.touches[1].clientY;
182
- const dist = Math.sqrt(distX * distX + distY * distY);
183
- const delta = lastDist - dist;
184
- desiredRadius += delta * this.zoomSpeed * 0.1 * zoomNorm;
185
- desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom);
186
- lastDist = dist;
187
-
188
- const touchX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
189
- const touchY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
190
- const dx = touchX - lastX;
191
- const dy = touchY - lastY;
192
- const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
193
- const right = new Vector3(R[0], R[3], R[6]);
194
- const up = new Vector3(R[1], R[4], R[7]);
195
- desiredTarget = desiredTarget.add(right.multiply(-dx * this.panSpeed * 0.025 * zoomNorm));
196
- desiredTarget = desiredTarget.add(up.multiply(-dy * this.panSpeed * 0.025 * zoomNorm));
197
- lastX = touchX;
198
- lastY = touchY;
199
- } else {
200
- const dx = e.touches[0].clientX - lastX;
201
- const dy = e.touches[0].clientY - lastY;
202
-
203
- desiredAlpha -= dx * this.orbitSpeed * 0.003;
204
- desiredBeta += dy * this.orbitSpeed * 0.003;
205
- // Clamp vertical (polar) angle (beta) if limits are finite
206
- desiredBeta = Math.min(
207
- Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
208
- (this.maxAngle * Math.PI) / 180,
209
- );
210
-
211
- lastX = e.touches[0].clientX;
212
- lastY = e.touches[0].clientY;
213
- }
214
- };
215
-
216
- const lerp = (a: number, b: number, t: number) => {
217
- return (1 - t) * a + t * b;
218
- };
219
-
220
- this.update = () => {
221
- isUpdatingCamera = true;
222
-
223
- alpha = lerp(alpha, desiredAlpha, this.dampening);
224
- beta = lerp(beta, desiredBeta, this.dampening);
225
- radius = lerp(radius, desiredRadius, this.dampening);
226
- target = target.lerp(desiredTarget, this.dampening);
227
-
228
- const x = target.x + radius * Math.sin(alpha) * Math.cos(beta);
229
- const y = target.y - radius * Math.sin(beta);
230
- const z = target.z - radius * Math.cos(alpha) * Math.cos(beta);
231
- camera.position = new Vector3(x, y, z);
232
-
233
- const direction = target.subtract(camera.position).normalize();
234
- const rx = Math.asin(-direction.y);
235
- const ry = Math.atan2(direction.x, direction.z);
236
- camera.rotation = Quaternion.FromEuler(new Vector3(rx, ry, 0));
237
-
238
- const moveSpeed = 0.025;
239
- const rotateSpeed = 0.01;
240
-
241
- const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
242
- const forward = new Vector3(-R[2], -R[5], -R[8]);
243
- const right = new Vector3(R[0], R[3], R[6]);
244
-
245
- if (keys["KeyS"]) desiredTarget = desiredTarget.add(forward.multiply(moveSpeed));
246
- if (keys["KeyW"]) desiredTarget = desiredTarget.subtract(forward.multiply(moveSpeed));
247
- if (keys["KeyA"]) desiredTarget = desiredTarget.subtract(right.multiply(moveSpeed));
248
- if (keys["KeyD"]) desiredTarget = desiredTarget.add(right.multiply(moveSpeed));
249
-
250
- // Horizontal (azimuth) rotation with 'e' and 'q'
251
- if (keys["KeyE"]) desiredAlpha += rotateSpeed;
252
- if (keys["KeyQ"]) desiredAlpha -= rotateSpeed;
253
- // Clamp horizontal angle (azimuth) only if limits are finite
254
- desiredAlpha = Math.min(
255
- Math.max(desiredAlpha, (this.minAzimuth * Math.PI) / 180),
256
- (this.maxAzimuth * Math.PI) / 180
257
- );
258
-
259
- // Vertical (polar) rotation with 'r' and 'f'
260
- if (keys["KeyR"]) desiredBeta += rotateSpeed;
261
- if (keys["KeyF"]) desiredBeta -= rotateSpeed;
262
- desiredBeta = Math.min(
263
- Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
264
- (this.maxAngle * Math.PI) / 180,
265
- );
266
-
267
- isUpdatingCamera = false;
268
- };
269
-
270
- const preventDefault = (e: Event) => {
271
- e.preventDefault();
272
- e.stopPropagation();
273
- };
274
-
275
- this.dispose = () => {
276
- canvas.removeEventListener("dragenter", preventDefault);
277
- canvas.removeEventListener("dragover", preventDefault);
278
- canvas.removeEventListener("dragleave", preventDefault);
279
- canvas.removeEventListener("contextmenu", preventDefault);
280
-
281
- canvas.removeEventListener("mousedown", onMouseDown);
282
- canvas.removeEventListener("mousemove", onMouseMove);
283
- canvas.removeEventListener("wheel", onWheel);
284
-
285
- canvas.removeEventListener("touchstart", onTouchStart);
286
- canvas.removeEventListener("touchend", onTouchEnd);
287
- canvas.removeEventListener("touchmove", onTouchMove);
288
-
289
- if (enableKeyboardControls) {
290
- window.removeEventListener("keydown", onKeyDown);
291
- window.removeEventListener("keyup", onKeyUp);
292
- }
293
- };
294
-
295
- if (enableKeyboardControls) {
296
- window.addEventListener("keydown", onKeyDown);
297
- window.addEventListener("keyup", onKeyUp);
298
- }
299
-
300
- canvas.addEventListener("dragenter", preventDefault);
301
- canvas.addEventListener("dragover", preventDefault);
302
- canvas.addEventListener("dragleave", preventDefault);
303
- canvas.addEventListener("contextmenu", preventDefault);
304
-
305
- canvas.addEventListener("mousedown", onMouseDown);
306
- canvas.addEventListener("mousemove", onMouseMove);
307
- canvas.addEventListener("wheel", onWheel);
308
-
309
- canvas.addEventListener("touchstart", onTouchStart);
310
- canvas.addEventListener("touchend", onTouchEnd);
311
- canvas.addEventListener("touchmove", onTouchMove);
312
-
313
- this.update();
 
 
 
314
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  }
316
 
317
  export { OrbitControls };
 
4
  import { Vector3 } from "../math/Vector3";
5
 
6
  class OrbitControls {
7
+ // Vertical (polar) limits: by default, free rotation.
8
+ minAngle: number = -Infinity;
9
+ maxAngle: number = Infinity;
10
+ // Horizontal (azimuth) limits: by default, free rotation.
11
+ minAzimuth: number = -Infinity;
12
+ maxAzimuth: number = Infinity;
13
+ minZoom: number = 0.1;
14
+ maxZoom: number = 30;
15
+ orbitSpeed: number = 1;
16
+ panSpeed: number = 1;
17
+ zoomSpeed: number = 1;
18
+ dampening: number = 0.12;
19
+ setCameraTarget: (newTarget: Vector3) => void = () => {};
20
+ update: () => void;
21
+ dispose: () => void;
22
+
23
+ constructor(
24
+ camera: Camera,
25
+ canvas: HTMLElement,
26
+ // The following defaults will be overridden by the computed values.
27
+ alpha: number = 0.5,
28
+ beta: number = 0.5,
29
+ radius: number = 5,
30
+ enableKeyboardControls: boolean = true,
31
+ inputTarget: Vector3 = new Vector3(),
32
+ ) {
33
+ // 'target' is the point the camera orbits around.
34
+ let target = inputTarget.clone();
35
+
36
+ // Compute the initial values from the camera’s position relative to the target.
37
+ // Let v = camera.position - target.
38
+ const v = camera.position.subtract(target);
39
+ const computedRadius = v.magnitude();
40
+ // From the update formulas, camera.position is computed as:
41
+ // x = target.x + radius * sin(alpha)*cos(beta)
42
+ // y = target.y - radius * sin(beta)
43
+ // z = target.z - radius * cos(alpha)*cos(beta)
44
+ // Thus, to recover alpha and beta:
45
+ const computedBeta = Math.asin(-v.y / computedRadius);
46
+ const computedAlpha = Math.atan2(v.x, -v.z);
47
+
48
+ // Initialize our internal state using the computed values.
49
+ let alphaVal = computedAlpha;
50
+ let betaVal = computedBeta;
51
+ let radiusVal = computedRadius;
52
+ let desiredAlpha = computedAlpha;
53
+ let desiredBeta = computedBeta;
54
+ let desiredRadius = computedRadius;
55
+ let desiredTarget = target.clone();
56
+
57
+ const keys: { [key: string]: boolean } = {};
58
+
59
+ let dragging = false;
60
+ let panning = false;
61
+ let lastDist = 0;
62
+ let lastX = 0;
63
+ let lastY = 0;
64
+ let isUpdatingCamera = false;
65
+
66
+ const onCameraChange = () => {
67
+ if (isUpdatingCamera) return;
68
+
69
+ // When the camera itself changes (e.g. via a reset),
70
+ // update the desired angles from its rotation.
71
+ const eulerRotation = camera.rotation.toEuler();
72
+ // (Note: depending on your math conventions you might need to adjust signs.)
73
+ desiredAlpha = -eulerRotation.y;
74
+ desiredBeta = -eulerRotation.x;
75
+
76
+ const x = camera.position.x - desiredRadius * Math.sin(desiredAlpha) * Math.cos(desiredBeta);
77
+ const y = camera.position.y + desiredRadius * Math.sin(desiredBeta);
78
+ const z = camera.position.z + desiredRadius * Math.cos(desiredAlpha) * Math.cos(desiredBeta);
79
+ desiredTarget = new Vector3(x, y, z);
80
+ };
81
+
82
+ camera.addEventListener("objectChanged", onCameraChange);
83
+
84
+ this.setCameraTarget = (newTarget: Vector3) => {
85
+ const dx = newTarget.x - camera.position.x;
86
+ const dy = newTarget.y - camera.position.y;
87
+ const dz = newTarget.z - camera.position.z;
88
+ desiredRadius = Math.sqrt(dx * dx + dy * dy + dz * dz);
89
+ desiredBeta = Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
90
+ desiredAlpha = -Math.atan2(dx, dz);
91
+ desiredTarget = new Vector3(newTarget.x, newTarget.y, newTarget.z);
92
+ };
93
+
94
+ const computeZoomNorm = () => {
95
+ return 0.1 + (0.9 * (desiredRadius - this.minZoom)) / (this.maxZoom - this.minZoom);
96
+ };
97
+
98
+ const onKeyDown = (e: KeyboardEvent) => {
99
+ keys[e.code] = true;
100
+ // Map arrow keys to WASD keys
101
+ if (e.code === "ArrowUp") keys["KeyW"] = true;
102
+ if (e.code === "ArrowDown") keys["KeyS"] = true;
103
+ if (e.code === "ArrowLeft") keys["KeyA"] = true;
104
+ if (e.code === "ArrowRight") keys["KeyD"] = true;
105
+ };
106
+
107
+ const onKeyUp = (e: KeyboardEvent) => {
108
+ keys[e.code] = false;
109
+ if (e.code === "ArrowUp") keys["KeyW"] = false;
110
+ if (e.code === "ArrowDown") keys["KeyS"] = false;
111
+ if (e.code === "ArrowLeft") keys["KeyA"] = false;
112
+ if (e.code === "ArrowRight") keys["KeyD"] = false;
113
+ };
114
+
115
+ const onMouseDown = (e: MouseEvent) => {
116
+ preventDefault(e);
117
+ dragging = true;
118
+ panning = e.button === 2;
119
+ lastX = e.clientX;
120
+ lastY = e.clientY;
121
+ window.addEventListener("mouseup", onMouseUp);
122
+ };
123
+
124
+ const onMouseUp = (e: MouseEvent) => {
125
+ preventDefault(e);
126
+ dragging = false;
127
+ panning = false;
128
+ window.removeEventListener("mouseup", onMouseUp);
129
+ };
130
+
131
+ const onMouseMove = (e: MouseEvent) => {
132
+ preventDefault(e);
133
+ if (!dragging || !camera) return;
134
+
135
+ const dx = e.clientX - lastX;
136
+ const dy = e.clientY - lastY;
137
+
138
+ if (panning) {
139
+ const zoomNorm = computeZoomNorm();
140
+ const panX = -dx * this.panSpeed * 0.01 * zoomNorm;
141
+ const panY = -dy * this.panSpeed * 0.01 * zoomNorm;
142
+ const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
143
+ const right = new Vector3(R[0], R[3], R[6]);
144
+ const up = new Vector3(R[1], R[4], R[7]);
145
+ desiredTarget = desiredTarget.add(right.multiply(panX));
146
+ desiredTarget = desiredTarget.add(up.multiply(panY));
147
+ } else {
148
+ desiredAlpha -= dx * this.orbitSpeed * 0.003;
149
+ desiredBeta += dy * this.orbitSpeed * 0.003;
150
+ desiredBeta = Math.min(
151
+ Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
152
+ (this.maxAngle * Math.PI) / 180,
153
+ );
154
+ }
155
+
156
+ lastX = e.clientX;
157
+ lastY = e.clientY;
158
+ };
159
+
160
+ const onWheel = (e: WheelEvent) => {
161
+ preventDefault(e);
162
+ const zoomNorm = computeZoomNorm();
163
+ desiredRadius += e.deltaY * this.zoomSpeed * 0.025 * zoomNorm;
164
+ desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom);
165
+ };
166
+
167
+ const onTouchStart = (e: TouchEvent) => {
168
+ preventDefault(e);
169
+ if (e.touches.length === 1) {
170
+ dragging = true;
171
+ panning = false;
172
+ lastX = e.touches[0].clientX;
173
+ lastY = e.touches[0].clientY;
174
+ lastDist = 0;
175
+ } else if (e.touches.length === 2) {
176
+ dragging = true;
177
+ panning = true;
178
+ lastX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
179
+ lastY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
180
+ const distX = e.touches[0].clientX - e.touches[1].clientX;
181
+ const distY = e.touches[0].clientY - e.touches[1].clientY;
182
+ lastDist = Math.sqrt(distX * distX + distY * distY);
183
+ }
184
+ };
185
+
186
+ const onTouchEnd = (e: TouchEvent) => {
187
+ preventDefault(e);
188
+ dragging = false;
189
+ panning = false;
190
+ };
191
+
192
+ const onTouchMove = (e: TouchEvent) => {
193
+ preventDefault(e);
194
+ if (!dragging || !camera) return;
195
+
196
+ if (panning) {
197
+ const zoomNorm = computeZoomNorm();
198
+ const distX = e.touches[0].clientX - e.touches[1].clientX;
199
+ const distY = e.touches[0].clientY - e.touches[1].clientY;
200
+ const dist = Math.sqrt(distX * distX + distY * distY);
201
+ const delta = lastDist - dist;
202
+ desiredRadius += delta * this.zoomSpeed * 0.1 * zoomNorm;
203
+ desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom);
204
+ lastDist = dist;
205
+
206
+ const touchX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
207
+ const touchY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
208
+ const dx = touchX - lastX;
209
+ const dy = touchY - lastY;
210
+ const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
211
+ const right = new Vector3(R[0], R[3], R[6]);
212
+ const up = new Vector3(R[1], R[4], R[7]);
213
+ desiredTarget = desiredTarget.add(right.multiply(-dx * this.panSpeed * 0.025 * zoomNorm));
214
+ desiredTarget = desiredTarget.add(up.multiply(-dy * this.panSpeed * 0.025 * zoomNorm));
215
+ lastX = touchX;
216
+ lastY = touchY;
217
+ } else {
218
+ const dx = e.touches[0].clientX - lastX;
219
+ const dy = e.touches[0].clientY - lastY;
220
+
221
+ desiredAlpha -= dx * this.orbitSpeed * 0.003;
222
+ desiredBeta += dy * this.orbitSpeed * 0.003;
223
+ desiredBeta = Math.min(
224
+ Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
225
+ (this.maxAngle * Math.PI) / 180,
226
+ );
227
+
228
+ lastX = e.touches[0].clientX;
229
+ lastY = e.touches[0].clientY;
230
+ }
231
+ };
232
+
233
+ const lerp = (a: number, b: number, t: number) => {
234
+ return (1 - t) * a + t * b;
235
+ };
236
+
237
+ this.update = () => {
238
+ isUpdatingCamera = true;
239
+
240
+ // Update the current values toward the desired values.
241
+ alphaVal = lerp(alphaVal, desiredAlpha, this.dampening);
242
+ betaVal = lerp(betaVal, desiredBeta, this.dampening);
243
+ radiusVal = lerp(radiusVal, desiredRadius, this.dampening);
244
+ target = target.lerp(desiredTarget, this.dampening);
245
+
246
+ // Recompute camera.position from the current angles.
247
+ const x = target.x + radiusVal * Math.sin(alphaVal) * Math.cos(betaVal);
248
+ const y = target.y - radiusVal * Math.sin(betaVal);
249
+ const z = target.z - radiusVal * Math.cos(alphaVal) * Math.cos(betaVal);
250
+ camera.position = new Vector3(x, y, z);
251
+
252
+ // Compute new rotation so that camera looks at the target.
253
+ const direction = target.subtract(camera.position).normalize();
254
+ const rx = Math.asin(-direction.y);
255
+ const ry = Math.atan2(direction.x, direction.z);
256
+ camera.rotation = Quaternion.FromEuler(new Vector3(rx, ry, 0));
257
+
258
+ // Process keyboard input for panning/orbiting.
259
+ const moveSpeed = 0.025;
260
+ const rotateSpeed = 0.01;
261
+ const R = Matrix3.RotationFromQuaternion(camera.rotation).buffer;
262
+ const forward = new Vector3(-R[2], -R[5], -R[8]);
263
+ const right = new Vector3(R[0], R[3], R[6]);
264
+
265
+ if (keys["KeyS"]) desiredTarget = desiredTarget.add(forward.multiply(moveSpeed));
266
+ if (keys["KeyW"]) desiredTarget = desiredTarget.subtract(forward.multiply(moveSpeed));
267
+ if (keys["KeyA"]) desiredTarget = desiredTarget.subtract(right.multiply(moveSpeed));
268
+ if (keys["KeyD"]) desiredTarget = desiredTarget.add(right.multiply(moveSpeed));
269
+
270
+ // Horizontal (azimuth) rotation with 'e' and 'q'
271
+ if (keys["KeyE"]) desiredAlpha += rotateSpeed;
272
+ if (keys["KeyQ"]) desiredAlpha -= rotateSpeed;
273
+ desiredAlpha = Math.min(
274
+ Math.max(desiredAlpha, (this.minAzimuth * Math.PI) / 180),
275
+ (this.maxAzimuth * Math.PI) / 180,
276
+ );
277
+
278
+ // Vertical (polar) rotation with 'r' and 'f'
279
+ if (keys["KeyR"]) desiredBeta += rotateSpeed;
280
+ if (keys["KeyF"]) desiredBeta -= rotateSpeed;
281
+ desiredBeta = Math.min(
282
+ Math.max(desiredBeta, (this.minAngle * Math.PI) / 180),
283
+ (this.maxAngle * Math.PI) / 180,
284
+ );
285
+
286
+ isUpdatingCamera = false;
287
+ };
288
+
289
+ const preventDefault = (e: Event) => {
290
+ e.preventDefault();
291
+ e.stopPropagation();
292
+ };
293
+
294
+ this.dispose = () => {
295
+ canvas.removeEventListener("dragenter", preventDefault);
296
+ canvas.removeEventListener("dragover", preventDefault);
297
+ canvas.removeEventListener("dragleave", preventDefault);
298
+ canvas.removeEventListener("contextmenu", preventDefault);
299
+
300
+ canvas.removeEventListener("mousedown", onMouseDown);
301
+ canvas.removeEventListener("mousemove", onMouseMove);
302
+ canvas.removeEventListener("wheel", onWheel);
303
+
304
+ canvas.removeEventListener("touchstart", onTouchStart);
305
+ canvas.removeEventListener("touchend", onTouchEnd);
306
+ canvas.removeEventListener("touchmove", onTouchMove);
307
+
308
+ if (enableKeyboardControls) {
309
+ window.removeEventListener("keydown", onKeyDown);
310
+ window.removeEventListener("keyup", onKeyUp);
311
+ }
312
+ };
313
+
314
+ if (enableKeyboardControls) {
315
+ window.addEventListener("keydown", onKeyDown);
316
+ window.addEventListener("keyup", onKeyUp);
317
  }
318
+
319
+ canvas.addEventListener("dragenter", preventDefault);
320
+ canvas.addEventListener("dragover", preventDefault);
321
+ canvas.addEventListener("dragleave", preventDefault);
322
+ canvas.addEventListener("contextmenu", preventDefault);
323
+
324
+ canvas.addEventListener("mousedown", onMouseDown);
325
+ canvas.addEventListener("mousemove", onMouseMove);
326
+ canvas.addEventListener("wheel", onWheel);
327
+
328
+ canvas.addEventListener("touchstart", onTouchStart);
329
+ canvas.addEventListener("touchend", onTouchEnd);
330
+ canvas.addEventListener("touchmove", onTouchMove);
331
+
332
+ this.update();
333
+ }
334
  }
335
 
336
  export { OrbitControls };