three-gamepad-controls 0.10.5 → 0.11.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.
@@ -0,0 +1,156 @@
1
+ import { EventDispatcher } from "three";
2
+
3
+ //#region src/gamepad-input.d.ts
4
+ /**
5
+ * Event map for {@link GamepadInput}.
6
+ */
7
+ type GamepadInputEventMap = {
8
+ /**
9
+ * Fired when a gamepad is connected and becomes active.
10
+ */
11
+ connected: {
12
+ /**
13
+ * Gamepad snapshot that became active.
14
+ */
15
+ gamepad: Gamepad;
16
+ };
17
+ /**
18
+ * Fired when the active gamepad is disconnected.
19
+ */
20
+ disconnected: {
21
+ /**
22
+ * Gamepad snapshot that was active before disconnection.
23
+ */
24
+ gamepad: Gamepad;
25
+ };
26
+ };
27
+ /**
28
+ * Configuration for {@link GamepadInput}.
29
+ */
30
+ type GamepadInputOptions = {
31
+ /**
32
+ * Default axis dead zone threshold in the range `[0, 1]`.
33
+ * @default 0.1
34
+ */
35
+ deadzone: number;
36
+ };
37
+ /**
38
+ * Options for axis and stick reads.
39
+ */
40
+ type GamepadAxisOptions = {
41
+ /**
42
+ * Axis dead zone threshold for this read.
43
+ */
44
+ deadzone?: number;
45
+ };
46
+ /**
47
+ * Two-dimensional stick input after dead zone processing.
48
+ */
49
+ type GamepadStick = {
50
+ /**
51
+ * Horizontal stick value.
52
+ */
53
+ x: number;
54
+ /**
55
+ * Vertical stick value.
56
+ */
57
+ y: number;
58
+ };
59
+ /**
60
+ * Gamepad input state reader for gameplay, menus, and custom actions.
61
+ *
62
+ * Call {@link update} once per frame before reading button transitions or axes.
63
+ */
64
+ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
65
+ #private;
66
+ /**
67
+ * When `false`, input polling is paused.
68
+ * @default true
69
+ */
70
+ enabled: boolean;
71
+ /**
72
+ * Creates a gamepad input reader.
73
+ *
74
+ * @param options - Optional overrides for the default input behavior.
75
+ */
76
+ constructor(options?: Partial<GamepadInputOptions>);
77
+ /**
78
+ * The currently active gamepad, or `null` if no gamepad is connected.
79
+ *
80
+ * @returns The active gamepad snapshot, or `null`.
81
+ */
82
+ get gamepad(): Gamepad | null;
83
+ /**
84
+ * Whether a gamepad is currently active.
85
+ *
86
+ * @returns `true` when a gamepad is active, otherwise `false`.
87
+ */
88
+ get connected(): boolean;
89
+ /**
90
+ * Mapping reported by the active gamepad, or `null` when none is active.
91
+ *
92
+ * @returns The active gamepad mapping, or `null`.
93
+ */
94
+ get mapping(): GamepadMappingType | null;
95
+ /**
96
+ * Raw active gamepad snapshot, or `null` if no gamepad is connected.
97
+ *
98
+ * @returns The raw active gamepad snapshot, or `null`.
99
+ */
100
+ get rawGamepad(): Gamepad | null;
101
+ /**
102
+ * Polls the gamepad and refreshes current and previous button state.
103
+ */
104
+ update(): void;
105
+ /**
106
+ * Removes all window-level event listeners attached by this input reader.
107
+ */
108
+ dispose(): void;
109
+ /**
110
+ * Returns whether a button is currently pressed.
111
+ *
112
+ * @param button - Button index to inspect.
113
+ * @returns `true` when the button is currently pressed, otherwise `false`.
114
+ */
115
+ isPressed(button: number): boolean;
116
+ /**
117
+ * Returns whether a button was pressed during the latest update.
118
+ *
119
+ * @param button - Button index to inspect.
120
+ * @returns `true` only on the frame where the button transitions to pressed.
121
+ */
122
+ wasPressed(button: number): boolean;
123
+ /**
124
+ * Returns whether a button was released during the latest update.
125
+ *
126
+ * @param button - Button index to inspect.
127
+ * @returns `true` only on the frame where the button transitions to released.
128
+ */
129
+ wasReleased(button: number): boolean;
130
+ /**
131
+ * Returns the current analog value for a button.
132
+ *
133
+ * @param button - Button index to inspect.
134
+ * @returns The button value, `1` for pressed digital buttons, or `0`.
135
+ */
136
+ buttonValue(button: number): number;
137
+ /**
138
+ * Returns the current value of an axis after dead zone processing.
139
+ *
140
+ * @param axis - Axis index to inspect.
141
+ * @param options - Optional per-read axis options.
142
+ * @returns Axis value after dead zone processing, or `0` when unavailable.
143
+ */
144
+ axis(axis: number, options?: GamepadAxisOptions): number;
145
+ /**
146
+ * Returns a two-axis stick after dead zone processing.
147
+ *
148
+ * @param xAxis - Horizontal axis index.
149
+ * @param yAxis - Vertical axis index.
150
+ * @param options - Optional per-read axis options.
151
+ * @returns Object containing processed `x` and `y` values.
152
+ */
153
+ stick(xAxis: number, yAxis: number, options?: GamepadAxisOptions): GamepadStick;
154
+ }
155
+ //#endregion
156
+ export { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick };
@@ -0,0 +1,270 @@
1
+ import { GamepadManager } from "./gamepad-manager.js";
2
+ import { applyGamepadDeadzone, getGamepadButtonPressed, getGamepadButtonValue } from "./utils.js";
3
+ import { EventDispatcher } from "three";
4
+ //#region src/gamepad-input.ts
5
+ const DEFAULT_GAMEPAD_INPUT_OPTIONS = { deadzone: .1 };
6
+ /**
7
+ * Gamepad input state reader for gameplay, menus, and custom actions.
8
+ *
9
+ * Call {@link update} once per frame before reading button transitions or axes.
10
+ */
11
+ var GamepadInput = class extends EventDispatcher {
12
+ /**
13
+ * When `false`, input polling is paused.
14
+ * @default true
15
+ */
16
+ enabled = true;
17
+ #manager;
18
+ #options;
19
+ #pressedButtons;
20
+ #previousPressedButtons;
21
+ /**
22
+ * Bound browser connection listener kept so it can be removed in {@link dispose}.
23
+ */
24
+ #onGamepadConnected;
25
+ /**
26
+ * Bound browser disconnection listener kept so it can be removed in {@link dispose}.
27
+ */
28
+ #onGamepadDisconnected;
29
+ #gamepad = null;
30
+ /**
31
+ * Creates a gamepad input reader.
32
+ *
33
+ * @param options - Optional overrides for the default input behavior.
34
+ */
35
+ constructor(options) {
36
+ super();
37
+ this.#manager = new GamepadManager();
38
+ this.#options = {
39
+ ...DEFAULT_GAMEPAD_INPUT_OPTIONS,
40
+ ...options
41
+ };
42
+ this.#pressedButtons = /* @__PURE__ */ new Set();
43
+ this.#previousPressedButtons = /* @__PURE__ */ new Set();
44
+ this.#onGamepadConnected = this.#handleGamepadConnectedEvent.bind(this);
45
+ this.#onGamepadDisconnected = this.#handleGamepadDisconnectedEvent.bind(this);
46
+ window.addEventListener("gamepadconnected", this.#onGamepadConnected);
47
+ window.addEventListener("gamepaddisconnected", this.#onGamepadDisconnected);
48
+ }
49
+ /**
50
+ * Forwards a browser connection event to the active-gamepad adoption logic.
51
+ *
52
+ * @param event - Browser event containing the connected gamepad snapshot.
53
+ */
54
+ #handleGamepadConnectedEvent(event) {
55
+ this.#handleGamepadConnected(event.gamepad);
56
+ }
57
+ /**
58
+ * Forwards a browser disconnection event to active-gamepad cleanup.
59
+ *
60
+ * @param event - Browser event containing the disconnected gamepad snapshot.
61
+ */
62
+ #handleGamepadDisconnectedEvent(event) {
63
+ this.#handleGamepadDisconnected(event.gamepad);
64
+ }
65
+ /**
66
+ * The currently active gamepad, or `null` if no gamepad is connected.
67
+ *
68
+ * @returns The active gamepad snapshot, or `null`.
69
+ */
70
+ get gamepad() {
71
+ return this.#gamepad;
72
+ }
73
+ /**
74
+ * Whether a gamepad is currently active.
75
+ *
76
+ * @returns `true` when a gamepad is active, otherwise `false`.
77
+ */
78
+ get connected() {
79
+ return this.#gamepad !== null;
80
+ }
81
+ /**
82
+ * Mapping reported by the active gamepad, or `null` when none is active.
83
+ *
84
+ * @returns The active gamepad mapping, or `null`.
85
+ */
86
+ get mapping() {
87
+ return this.#gamepad?.mapping ?? null;
88
+ }
89
+ /**
90
+ * Raw active gamepad snapshot, or `null` if no gamepad is connected.
91
+ *
92
+ * @returns The raw active gamepad snapshot, or `null`.
93
+ */
94
+ get rawGamepad() {
95
+ return this.#gamepad;
96
+ }
97
+ /**
98
+ * Polls the gamepad and refreshes current and previous button state.
99
+ */
100
+ update() {
101
+ if (!this.enabled) return;
102
+ const { gamepad, connected, disconnected } = this.#manager.update();
103
+ if (connected !== null) {
104
+ this.#gamepad = gamepad;
105
+ this.#syncButtonState({ seedPrevious: true });
106
+ this.dispatchEvent({
107
+ type: "connected",
108
+ gamepad: connected
109
+ });
110
+ return;
111
+ }
112
+ if (disconnected !== null) {
113
+ this.#gamepad = null;
114
+ this.#clearButtonState();
115
+ this.dispatchEvent({
116
+ type: "disconnected",
117
+ gamepad: disconnected
118
+ });
119
+ return;
120
+ }
121
+ this.#gamepad = gamepad;
122
+ this.#syncButtonState({ seedPrevious: false });
123
+ }
124
+ /**
125
+ * Removes all window-level event listeners attached by this input reader.
126
+ */
127
+ dispose() {
128
+ window.removeEventListener("gamepadconnected", this.#onGamepadConnected);
129
+ window.removeEventListener("gamepaddisconnected", this.#onGamepadDisconnected);
130
+ this.#manager.activeGamepad = null;
131
+ this.#gamepad = null;
132
+ this.#clearButtonState();
133
+ this.enabled = false;
134
+ }
135
+ /**
136
+ * Returns whether a button is currently pressed.
137
+ *
138
+ * @param button - Button index to inspect.
139
+ * @returns `true` when the button is currently pressed, otherwise `false`.
140
+ */
141
+ isPressed(button) {
142
+ return this.#pressedButtons.has(button);
143
+ }
144
+ /**
145
+ * Returns whether a button was pressed during the latest update.
146
+ *
147
+ * @param button - Button index to inspect.
148
+ * @returns `true` only on the frame where the button transitions to pressed.
149
+ */
150
+ wasPressed(button) {
151
+ return this.#pressedButtons.has(button) && !this.#previousPressedButtons.has(button);
152
+ }
153
+ /**
154
+ * Returns whether a button was released during the latest update.
155
+ *
156
+ * @param button - Button index to inspect.
157
+ * @returns `true` only on the frame where the button transitions to released.
158
+ */
159
+ wasReleased(button) {
160
+ return !this.#pressedButtons.has(button) && this.#previousPressedButtons.has(button);
161
+ }
162
+ /**
163
+ * Returns the current analog value for a button.
164
+ *
165
+ * @param button - Button index to inspect.
166
+ * @returns The button value, `1` for pressed digital buttons, or `0`.
167
+ */
168
+ buttonValue(button) {
169
+ if (this.#gamepad === null) return 0;
170
+ return getGamepadButtonValue(this.#gamepad, button);
171
+ }
172
+ /**
173
+ * Returns the current value of an axis after dead zone processing.
174
+ *
175
+ * @param axis - Axis index to inspect.
176
+ * @param options - Optional per-read axis options.
177
+ * @returns Axis value after dead zone processing, or `0` when unavailable.
178
+ */
179
+ axis(axis, options) {
180
+ return applyGamepadDeadzone(this.#gamepad?.axes[axis] ?? 0, this.#getDeadzone(options));
181
+ }
182
+ /**
183
+ * Returns a two-axis stick after dead zone processing.
184
+ *
185
+ * @param xAxis - Horizontal axis index.
186
+ * @param yAxis - Vertical axis index.
187
+ * @param options - Optional per-read axis options.
188
+ * @returns Object containing processed `x` and `y` values.
189
+ */
190
+ stick(xAxis, yAxis, options) {
191
+ return {
192
+ x: this.axis(xAxis, options),
193
+ y: this.axis(yAxis, options)
194
+ };
195
+ }
196
+ /**
197
+ * Handles a browser connection event and adopts the gamepad when possible.
198
+ *
199
+ * Button state is seeded as both current and previous so an already-held
200
+ * button does not produce a synthetic `wasPressed` transition on connect.
201
+ *
202
+ * @param gamepad - Browser-provided connected gamepad snapshot.
203
+ */
204
+ #handleGamepadConnected(gamepad) {
205
+ if (!this.#manager.connect(gamepad)) return;
206
+ this.#gamepad = this.#manager.activeGamepad;
207
+ this.#syncButtonState({ seedPrevious: true });
208
+ this.dispatchEvent({
209
+ type: "connected",
210
+ gamepad
211
+ });
212
+ }
213
+ /**
214
+ * Handles a browser disconnection event for the active gamepad.
215
+ *
216
+ * Button state is cleared instead of diffed so disconnecting a controller
217
+ * does not produce synthetic `wasReleased` transitions.
218
+ *
219
+ * @param gamepad - Browser-provided disconnected gamepad snapshot.
220
+ */
221
+ #handleGamepadDisconnected(gamepad) {
222
+ const disconnectedGamepad = this.#manager.disconnect(gamepad);
223
+ if (disconnectedGamepad === null) return;
224
+ this.#gamepad = null;
225
+ this.#clearButtonState();
226
+ this.dispatchEvent({
227
+ type: "disconnected",
228
+ gamepad: disconnectedGamepad
229
+ });
230
+ }
231
+ /**
232
+ * Refreshes current and previous pressed-button sets from the active snapshot.
233
+ *
234
+ * When `seedPrevious` is `true`, the refreshed current state is copied into
235
+ * the previous state. This intentionally suppresses transition events on the
236
+ * first frame after adopting a gamepad.
237
+ *
238
+ * @param options - Button state synchronization options.
239
+ * @param options.seedPrevious - Whether to seed previous state from current state.
240
+ */
241
+ #syncButtonState({ seedPrevious }) {
242
+ this.#previousPressedButtons.clear();
243
+ for (const button of this.#pressedButtons) this.#previousPressedButtons.add(button);
244
+ this.#pressedButtons.clear();
245
+ if (this.#gamepad !== null) {
246
+ for (let index = 0; index < this.#gamepad.buttons.length; index += 1) if (getGamepadButtonPressed(this.#gamepad, index)) this.#pressedButtons.add(index);
247
+ }
248
+ if (!seedPrevious) return;
249
+ this.#previousPressedButtons.clear();
250
+ for (const button of this.#pressedButtons) this.#previousPressedButtons.add(button);
251
+ }
252
+ /**
253
+ * Clears all stored button state.
254
+ */
255
+ #clearButtonState() {
256
+ this.#pressedButtons.clear();
257
+ this.#previousPressedButtons.clear();
258
+ }
259
+ /**
260
+ * Resolves the dead zone for a single axis or stick read.
261
+ *
262
+ * @param options - Optional per-read axis options.
263
+ * @returns The per-read dead zone when provided, otherwise the instance default.
264
+ */
265
+ #getDeadzone(options) {
266
+ return options?.deadzone ?? this.#options.deadzone;
267
+ }
268
+ };
269
+ //#endregion
270
+ export { GamepadInput };
@@ -0,0 +1,104 @@
1
+ //#region src/gamepad-manager.ts
2
+ const EMPTY_UPDATE_RESULT = {
3
+ gamepad: null,
4
+ connected: null,
5
+ disconnected: null
6
+ };
7
+ /**
8
+ * Internal input core that owns active gamepad polling and snapshot refresh.
9
+ *
10
+ * This class intentionally tracks only one active gamepad. Higher-level
11
+ * multi-gamepad selection should be built on top of this lifecycle in a
12
+ * later phase.
13
+ *
14
+ * @internal
15
+ */
16
+ var GamepadManager = class {
17
+ /**
18
+ * The active gamepad snapshot, or `null` when none is active.
19
+ */
20
+ activeGamepad = null;
21
+ /**
22
+ * Accepts a gamepad as active when no active gamepad exists.
23
+ *
24
+ * Additional connected gamepads are ignored so the current controls keep
25
+ * using the first active device by default.
26
+ *
27
+ * @param gamepad - Gamepad snapshot to activate.
28
+ * @returns `true` when the gamepad became active, otherwise `false`.
29
+ */
30
+ connect(gamepad) {
31
+ if (this.activeGamepad !== null || !gamepad.connected) return false;
32
+ this.activeGamepad = gamepad;
33
+ return true;
34
+ }
35
+ /**
36
+ * Clears the active gamepad when it matches the disconnecting gamepad index.
37
+ *
38
+ * @param gamepad - Gamepad snapshot that disconnected.
39
+ * @returns The previously active gamepad when it was cleared, otherwise `null`.
40
+ */
41
+ disconnect(gamepad) {
42
+ if (this.activeGamepad?.index !== gamepad.index) return null;
43
+ const disconnectedGamepad = this.activeGamepad;
44
+ this.activeGamepad = null;
45
+ return disconnectedGamepad;
46
+ }
47
+ /**
48
+ * Polls the Gamepad API and refreshes the active gamepad snapshot.
49
+ *
50
+ * The browser exposes gamepad state as snapshots, so polling must replace
51
+ * the stored reference before controls read axes or buttons.
52
+ *
53
+ * @returns The active gamepad and any connect/disconnect transition found.
54
+ */
55
+ update() {
56
+ if (this.activeGamepad === null) {
57
+ const connectedGamepad = this.#getFirstConnectedGamepad();
58
+ if (connectedGamepad === null) return EMPTY_UPDATE_RESULT;
59
+ this.activeGamepad = connectedGamepad;
60
+ return {
61
+ gamepad: connectedGamepad,
62
+ connected: connectedGamepad,
63
+ disconnected: null
64
+ };
65
+ }
66
+ const previousGamepad = this.activeGamepad;
67
+ const nextGamepad = this.#getGamepadByIndex(previousGamepad.index);
68
+ if (nextGamepad === null) {
69
+ this.activeGamepad = null;
70
+ return {
71
+ gamepad: null,
72
+ connected: null,
73
+ disconnected: previousGamepad
74
+ };
75
+ }
76
+ this.activeGamepad = nextGamepad;
77
+ return {
78
+ gamepad: nextGamepad,
79
+ connected: null,
80
+ disconnected: null
81
+ };
82
+ }
83
+ /**
84
+ * Reads the latest connected gamepad snapshot at a known index.
85
+ *
86
+ * @param index - Browser-assigned gamepad index to refresh.
87
+ * @returns A connected gamepad snapshot, or `null` if it is gone.
88
+ */
89
+ #getGamepadByIndex(index) {
90
+ const gamepad = navigator.getGamepads()[index] ?? null;
91
+ return gamepad?.connected === true ? gamepad : null;
92
+ }
93
+ /**
94
+ * Finds the first currently connected gamepad snapshot.
95
+ *
96
+ * @returns The first connected gamepad snapshot, or `null` if none exist.
97
+ */
98
+ #getFirstConnectedGamepad() {
99
+ for (const gamepad of navigator.getGamepads()) if (gamepad?.connected === true) return gamepad;
100
+ return null;
101
+ }
102
+ };
103
+ //#endregion
104
+ export { GamepadManager };
@@ -1,6 +1,6 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
- import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
3
2
  import { GamepadControls } from "./gamepad-controls.js";
3
+ import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
4
4
  //#region src/gamepad-orbit-controls.ts
5
5
  /**
6
6
  * Default options merged in the constructor when no explicit configuration is provided.
@@ -1,6 +1,6 @@
1
1
  import { GAMEPAD_AXIS } from "./core.js";
2
- import { applyGamepadDeadzone } from "./utils.js";
3
2
  import { GamepadControls } from "./gamepad-controls.js";
3
+ import { applyGamepadDeadzone } from "./utils.js";
4
4
  import { Euler } from "three";
5
5
  //#region src/gamepad-pointer-lock-controls.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
- import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
3
2
  import { GamepadControls } from "./gamepad-controls.js";
3
+ import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
4
4
  //#region src/gamepad-trackball-controls.ts
5
5
  /**
6
6
  * Default options merged in the constructor when no explicit configuration is provided.
@@ -51,6 +51,16 @@ var GamepadTrackballControls = class extends GamepadControls {
51
51
  this.#queuePan(deltaTime, gamepad, panSpeed, deadzone, axisPanX, axisPanY);
52
52
  this.#queueZoom(deltaTime, gamepad, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut);
53
53
  }
54
+ /**
55
+ * Queues rotation input into TrackballControls' normalized move state.
56
+ *
57
+ * @param deltaTime - Seconds since the last frame.
58
+ * @param gamepad - Fresh gamepad snapshot to read from.
59
+ * @param rotateSpeed - User-configured rotation speed multiplier.
60
+ * @param deadzone - Axis dead zone threshold.
61
+ * @param axisRotateX - Axis index for horizontal rotation.
62
+ * @param axisRotateY - Axis index for vertical rotation.
63
+ */
54
64
  #queueRotation(deltaTime, gamepad, rotateSpeed, deadzone, axisRotateX, axisRotateY) {
55
65
  const controls = this.#controls;
56
66
  if (controls.noRotate) {
@@ -65,6 +75,16 @@ var GamepadTrackballControls = class extends GamepadControls {
65
75
  controls._moveCurr.x += rotX * scale;
66
76
  controls._moveCurr.y += -rotY * scale;
67
77
  }
78
+ /**
79
+ * Queues pan input into TrackballControls' normalized pan state.
80
+ *
81
+ * @param deltaTime - Seconds since the last frame.
82
+ * @param gamepad - Fresh gamepad snapshot to read from.
83
+ * @param panSpeed - User-configured pan speed multiplier.
84
+ * @param deadzone - Axis dead zone threshold.
85
+ * @param axisPanX - Axis index for horizontal panning.
86
+ * @param axisPanY - Axis index for vertical panning.
87
+ */
68
88
  #queuePan(deltaTime, gamepad, panSpeed, deadzone, axisPanX, axisPanY) {
69
89
  const controls = this.#controls;
70
90
  if (controls.noPan) {
@@ -78,6 +98,16 @@ var GamepadTrackballControls = class extends GamepadControls {
78
98
  controls._panEnd.x += panX * scale;
79
99
  controls._panEnd.y += panY * scale;
80
100
  }
101
+ /**
102
+ * Queues trigger zoom input into TrackballControls' normalized zoom state.
103
+ *
104
+ * @param deltaTime - Seconds since the last frame.
105
+ * @param gamepad - Fresh gamepad snapshot to read from.
106
+ * @param zoomSpeed - User-configured zoom speed multiplier.
107
+ * @param deadzone - Trigger dead zone threshold.
108
+ * @param buttonZoomIn - Button index for zooming in.
109
+ * @param buttonZoomOut - Button index for zooming out.
110
+ */
81
111
  #queueZoom(deltaTime, gamepad, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut) {
82
112
  const controls = this.#controls;
83
113
  if (controls.noZoom) {
@@ -92,6 +122,8 @@ var GamepadTrackballControls = class extends GamepadControls {
92
122
  /**
93
123
  * Compensates for TrackballControls reapplying queued pan and zoom deltas
94
124
  * while their input state catches up through damping.
125
+ *
126
+ * @returns Multiplier that matches TrackballControls' damping mode.
95
127
  */
96
128
  #getInputDampingFactor() {
97
129
  const controls = this.#controls;