three-gamepad-controls 0.11.0 → 0.12.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.
package/README.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  Gamepad support for [Three.js](https://threejs.org) controls, built on top of [Web Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API).
4
4
 
5
+ ## Architecture
6
+
7
+ ```mermaid
8
+ flowchart TD
9
+ GamepadManager["GamepadManager<br/>(internal polling and active device lifecycle)"]
10
+ GamepadInput["GamepadInput<br/>(public low-level input state)"]
11
+ GamepadControls["GamepadControls<br/>(abstract Three.js wrapper base)"]
12
+ Wrappers["Specific wrappers<br/>(Orbit, Map, Fly, Transform, etc.)"]
13
+
14
+ GamepadManager --> GamepadInput
15
+ GamepadInput --> GamepadControls
16
+ GamepadControls --> Wrappers
17
+ ```
18
+
5
19
  ## 📦 Installation
6
20
 
7
21
  npm:
@@ -37,7 +51,7 @@ bun add three-gamepad-controls
37
51
  ## 📖 Documentation
38
52
 
39
53
  - [Core](./docs/core.md) — The fundamental building blocks.
40
- - [GamepadInput](./docs/gamepad-input.md) - Gamepad input state reader for gameplay and menus.
54
+ - [GamepadInput](./docs/gamepad-input.md) - Low-level reader for gamepad buttons, axes, sticks, and transitions.
41
55
  - [GamepadControls](./docs/gamepad-controls.md) — Abstract base class for custom gamepad controls.
42
56
  - [GamepadArcballControls](./docs/gamepad-arcball-controls.md) - Gamepad support for `ArcballControls`.
43
57
  - [GamepadDragControls](./docs/gamepad-drag-controls.md) - Gamepad support for `DragControls`.
@@ -99,9 +99,8 @@ declare class GamepadArcballControls extends GamepadControls {
99
99
  * z-rotation, and center focus.
100
100
  *
101
101
  * @param deltaTime - Seconds since the last frame.
102
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
103
102
  */
104
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
103
+ protected onUpdate(deltaTime: number): void;
105
104
  }
106
105
  //#endregion
107
106
  export { GamepadArcballControls, GamepadArcballControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonPressed, getGamepadButtonValue } from "./utils.js";
4
3
  import { Vector2, Vector3 } from "three";
5
4
  //#region src/gamepad-arcball-controls.ts
6
5
  /**
@@ -40,7 +39,6 @@ var GamepadArcballControls = class extends GamepadControls {
40
39
  #cameraForward;
41
40
  #cameraRight;
42
41
  #previousUp;
43
- #focusButtonPressed = false;
44
42
  #wasInteracting = false;
45
43
  /**
46
44
  * @param controls - A Three.js `ArcballControls` instance.
@@ -67,22 +65,22 @@ var GamepadArcballControls = class extends GamepadControls {
67
65
  * z-rotation, and center focus.
68
66
  *
69
67
  * @param deltaTime - Seconds since the last frame.
70
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
71
68
  */
72
- onUpdate(deltaTime, gamepad) {
69
+ onUpdate(deltaTime) {
73
70
  const controls = this.#controls;
74
71
  const { rotateSpeed, panSpeed, zoomSpeed, zRotateSpeed, deadzone, axisRotateX, axisRotateY, axisPanX, axisPanY, buttonZoomIn, buttonZoomOut, buttonZRotateLeft, buttonZRotateRight, buttonFocus } = this.#options;
75
- const focusPoint = this.#consumeFocusPoint(gamepad, buttonFocus);
72
+ const input = this.gamepadInput;
73
+ const focusPoint = this.#consumeFocusPoint(buttonFocus);
76
74
  if (!controls.enabled) {
77
75
  this.#endInteraction();
78
76
  return;
79
77
  }
80
- const rotateX = controls.enableRotate ? applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone) : 0;
81
- const rotateY = controls.enableRotate ? applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone) : 0;
82
- const panX = controls.enablePan ? applyGamepadDeadzone(gamepad.axes[axisPanX] ?? 0, deadzone) : 0;
83
- const panY = controls.enablePan ? applyGamepadDeadzone(gamepad.axes[axisPanY] ?? 0, deadzone) : 0;
84
- const zoom = controls.enableZoom ? getGamepadButtonValue(gamepad, buttonZoomIn) - getGamepadButtonValue(gamepad, buttonZoomOut) : 0;
85
- const zRotation = controls.enableRotate ? getGamepadButtonValue(gamepad, buttonZRotateLeft) - getGamepadButtonValue(gamepad, buttonZRotateRight) : 0;
78
+ const rotateX = controls.enableRotate ? input.axis(axisRotateX, { deadzone }) : 0;
79
+ const rotateY = controls.enableRotate ? input.axis(axisRotateY, { deadzone }) : 0;
80
+ const panX = controls.enablePan ? input.axis(axisPanX, { deadzone }) : 0;
81
+ const panY = controls.enablePan ? input.axis(axisPanY, { deadzone }) : 0;
82
+ const zoom = controls.enableZoom ? input.buttonValue(buttonZoomIn) - input.buttonValue(buttonZoomOut) : 0;
83
+ const zRotation = controls.enableRotate ? input.buttonValue(buttonZRotateLeft) - input.buttonValue(buttonZRotateRight) : 0;
86
84
  const activeInput = rotateX !== 0 || rotateY !== 0 || panX !== 0 || panY !== 0 || Math.abs(zoom) > deadzone || Math.abs(zRotation) > deadzone;
87
85
  if (!activeInput && focusPoint === null) {
88
86
  this.#endInteraction();
@@ -228,16 +226,12 @@ var GamepadArcballControls = class extends GamepadControls {
228
226
  /**
229
227
  * Consumes a focus-button press and resolves the viewport center hit point.
230
228
  *
231
- * @param gamepad - Fresh gamepad snapshot to read from.
232
229
  * @param buttonFocus - Button index configured for focus.
233
230
  * @returns The center hit point, or `null` when focus should not run.
234
231
  */
235
- #consumeFocusPoint(gamepad, buttonFocus) {
232
+ #consumeFocusPoint(buttonFocus) {
236
233
  const controls = this.#controls;
237
- const focusPressed = getGamepadButtonPressed(gamepad, buttonFocus);
238
- const shouldFocus = focusPressed && !this.#focusButtonPressed;
239
- this.#focusButtonPressed = focusPressed;
240
- if (!shouldFocus || !controls.enabled || !controls.enablePan || !controls.enableFocus || controls.scene === null) return null;
234
+ if (!this.gamepadInput.wasPressed(buttonFocus) || !controls.enabled || !controls.enablePan || !controls.enableFocus || controls.scene === null) return null;
241
235
  return controls.unprojectOnObj(this.#centerNdc, controls.object);
242
236
  }
243
237
  /**
@@ -1,3 +1,4 @@
1
+ import { GamepadInput } from "./gamepad-input.js";
1
2
  import { EventDispatcher } from "three";
2
3
 
3
4
  //#region src/gamepad-controls.d.ts
@@ -30,8 +31,8 @@ type GamepadControlsEventMap = {
30
31
  /**
31
32
  * Abstract base class for Three.js gamepad controls.
32
33
  *
33
- * Handles the gamepad connection lifecycle and input polling so subclasses
34
- * only need to implement {@link onUpdate}.
34
+ * Delegates gamepad connection lifecycle and input polling to {@link GamepadInput}
35
+ * so subclasses can focus on mapping input to the wrapped Three.js control.
35
36
  */
36
37
  declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEventMap> {
37
38
  #private;
@@ -45,9 +46,15 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
45
46
  */
46
47
  gamepad: Gamepad | null;
47
48
  /**
48
- * Creates the base gamepad lifecycle manager and attaches browser listeners.
49
+ * Creates the base input reader and attaches lifecycle listeners.
49
50
  */
50
51
  constructor();
52
+ /**
53
+ * Low-level gamepad input reader used by subclasses.
54
+ *
55
+ * @returns The shared input reader for the active gamepad.
56
+ */
57
+ protected get gamepadInput(): GamepadInput;
51
58
  /**
52
59
  * Advances the controller by one frame. Call this inside your render loop.
53
60
  *
@@ -62,25 +69,22 @@ declare abstract class GamepadControls extends EventDispatcher<GamepadControlsEv
62
69
  * Called every frame when a gamepad is available and `enabled` is `true`.
63
70
  *
64
71
  * @param deltaTime - Seconds since the last frame.
65
- * @param gamepad - A fresh snapshot of the currently active gamepad.
66
72
  */
67
- protected abstract onUpdate(deltaTime: number, gamepad: Gamepad): void;
73
+ protected abstract onUpdate(deltaTime: number): void;
68
74
  /**
69
- * Called when any gamepad fires a `gamepadconnected` event.
75
+ * Called when a gamepad becomes active through the shared input reader.
70
76
  *
71
- * The default accepts the first gamepad that connects and dispatches `connected`.
72
- * Override to customize selection behavior.
77
+ * The default dispatches a `connected` event.
73
78
  *
74
- * @param gamepad - The gamepad that just connected.
79
+ * @param gamepad - The gamepad that became active.
75
80
  */
76
81
  protected onGamepadConnected(gamepad: Gamepad): void;
77
82
  /**
78
- * Called when any gamepad fires a `gamepaddisconnected` event.
83
+ * Called when the active gamepad disconnects through the shared input reader.
79
84
  *
80
- * The default clears `this.gamepad` and dispatches `disconnected` if the
81
- * disconnecting gamepad was the active one. Override to add custom cleanup.
85
+ * The default dispatches a `disconnected` event.
82
86
  *
83
- * @param gamepad - The gamepad that just disconnected.
87
+ * @param gamepad - The gamepad that was active before disconnection.
84
88
  */
85
89
  protected onGamepadDisconnected(gamepad: Gamepad): void;
86
90
  }
@@ -1,11 +1,11 @@
1
- import { GamepadManager } from "./gamepad-manager.js";
1
+ import { GamepadInput } from "./gamepad-input.js";
2
2
  import { EventDispatcher } from "three";
3
3
  //#region src/gamepad-controls.ts
4
4
  /**
5
5
  * Abstract base class for Three.js gamepad controls.
6
6
  *
7
- * Handles the gamepad connection lifecycle and input polling so subclasses
8
- * only need to implement {@link onUpdate}.
7
+ * Delegates gamepad connection lifecycle and input polling to {@link GamepadInput}
8
+ * so subclasses can focus on mapping input to the wrapped Three.js control.
9
9
  */
10
10
  var GamepadControls = class extends EventDispatcher {
11
11
  /**
@@ -17,40 +17,50 @@ var GamepadControls = class extends EventDispatcher {
17
17
  * The currently active gamepad, or `null` if no gamepad is connected.
18
18
  */
19
19
  gamepad = null;
20
- #manager;
20
+ #gamepadInput;
21
21
  /**
22
- * Bound browser connection listener kept so it can be removed in {@link dispose}.
22
+ * Bound input connection listener kept so it can be removed in {@link dispose}.
23
23
  */
24
24
  #onGamepadConnected;
25
25
  /**
26
- * Bound browser disconnection listener kept so it can be removed in {@link dispose}.
26
+ * Bound input disconnection listener kept so it can be removed in {@link dispose}.
27
27
  */
28
28
  #onGamepadDisconnected;
29
29
  /**
30
- * Creates the base gamepad lifecycle manager and attaches browser listeners.
30
+ * Creates the base input reader and attaches lifecycle listeners.
31
31
  */
32
32
  constructor() {
33
33
  super();
34
- this.#manager = new GamepadManager();
35
- this.#onGamepadConnected = this.#handleGamepadConnectedEvent.bind(this);
36
- this.#onGamepadDisconnected = this.#handleGamepadDisconnectedEvent.bind(this);
37
- window.addEventListener("gamepadconnected", this.#onGamepadConnected);
38
- window.addEventListener("gamepaddisconnected", this.#onGamepadDisconnected);
34
+ this.#gamepadInput = new GamepadInput();
35
+ this.#onGamepadConnected = this.#handleGamepadConnected.bind(this);
36
+ this.#onGamepadDisconnected = this.#handleGamepadDisconnected.bind(this);
37
+ this.#gamepadInput.addEventListener("connected", this.#onGamepadConnected);
38
+ this.#gamepadInput.addEventListener("disconnected", this.#onGamepadDisconnected);
39
39
  }
40
40
  /**
41
- * Forwards a browser connection event to the overridable lifecycle hook.
41
+ * Low-level gamepad input reader used by subclasses.
42
42
  *
43
- * @param event - Browser event containing the connected gamepad snapshot.
43
+ * @returns The shared input reader for the active gamepad.
44
44
  */
45
- #handleGamepadConnectedEvent(event) {
45
+ get gamepadInput() {
46
+ return this.#gamepadInput;
47
+ }
48
+ /**
49
+ * Forwards an input connection event to the overridable lifecycle hook.
50
+ *
51
+ * @param event - Input event containing the connected gamepad snapshot.
52
+ */
53
+ #handleGamepadConnected(event) {
54
+ this.gamepad = this.#gamepadInput.gamepad;
46
55
  this.onGamepadConnected(event.gamepad);
47
56
  }
48
57
  /**
49
- * Forwards a browser disconnection event to the overridable lifecycle hook.
58
+ * Forwards an input disconnection event to the overridable lifecycle hook.
50
59
  *
51
- * @param event - Browser event containing the disconnected gamepad snapshot.
60
+ * @param event - Input event containing the disconnected gamepad snapshot.
52
61
  */
53
- #handleGamepadDisconnectedEvent(event) {
62
+ #handleGamepadDisconnected(event) {
63
+ this.gamepad = this.#gamepadInput.gamepad;
54
64
  this.onGamepadDisconnected(event.gamepad);
55
65
  }
56
66
  /**
@@ -60,69 +70,45 @@ var GamepadControls = class extends EventDispatcher {
60
70
  */
61
71
  update(deltaTime) {
62
72
  if (!this.enabled) return;
63
- this.#manager.activeGamepad = this.gamepad;
64
- const { gamepad, connected, disconnected } = this.#manager.update();
65
- if (connected !== null) {
66
- this.#manager.activeGamepad = this.gamepad;
67
- this.onGamepadConnected(connected);
68
- this.#manager.activeGamepad = this.gamepad;
69
- } else if (disconnected !== null) {
70
- this.#manager.activeGamepad = this.gamepad;
71
- this.onGamepadDisconnected(disconnected);
72
- this.#manager.activeGamepad = this.gamepad;
73
- return;
74
- } else {
75
- this.gamepad = gamepad;
76
- this.#manager.activeGamepad = this.gamepad;
77
- }
73
+ this.#gamepadInput.update();
74
+ this.gamepad = this.#gamepadInput.gamepad;
78
75
  if (this.gamepad === null) return;
79
- this.onUpdate(deltaTime, this.gamepad);
76
+ this.onUpdate(deltaTime);
80
77
  }
81
78
  /**
82
79
  * Removes all event listeners attached by this controller. Call when no longer needed.
83
80
  */
84
81
  dispose() {
85
- window.removeEventListener("gamepadconnected", this.#onGamepadConnected);
86
- window.removeEventListener("gamepaddisconnected", this.#onGamepadDisconnected);
82
+ this.#gamepadInput.removeEventListener("connected", this.#onGamepadConnected);
83
+ this.#gamepadInput.removeEventListener("disconnected", this.#onGamepadDisconnected);
84
+ this.#gamepadInput.dispose();
87
85
  this.gamepad = null;
88
- this.#manager.activeGamepad = null;
89
86
  this.enabled = false;
90
87
  }
91
88
  /**
92
- * Called when any gamepad fires a `gamepadconnected` event.
89
+ * Called when a gamepad becomes active through the shared input reader.
93
90
  *
94
- * The default accepts the first gamepad that connects and dispatches `connected`.
95
- * Override to customize selection behavior.
91
+ * The default dispatches a `connected` event.
96
92
  *
97
- * @param gamepad - The gamepad that just connected.
93
+ * @param gamepad - The gamepad that became active.
98
94
  */
99
95
  onGamepadConnected(gamepad) {
100
- this.#manager.activeGamepad = this.gamepad;
101
- if (!this.#manager.connect(gamepad)) return;
102
- const connectedGamepad = this.#manager.activeGamepad;
103
- if (connectedGamepad === null) return;
104
- this.gamepad = connectedGamepad;
105
96
  this.dispatchEvent({
106
97
  type: "connected",
107
- gamepad: connectedGamepad
98
+ gamepad
108
99
  });
109
100
  }
110
101
  /**
111
- * Called when any gamepad fires a `gamepaddisconnected` event.
102
+ * Called when the active gamepad disconnects through the shared input reader.
112
103
  *
113
- * The default clears `this.gamepad` and dispatches `disconnected` if the
114
- * disconnecting gamepad was the active one. Override to add custom cleanup.
104
+ * The default dispatches a `disconnected` event.
115
105
  *
116
- * @param gamepad - The gamepad that just disconnected.
106
+ * @param gamepad - The gamepad that was active before disconnection.
117
107
  */
118
108
  onGamepadDisconnected(gamepad) {
119
- this.#manager.activeGamepad = this.gamepad;
120
- const disconnectedGamepad = this.#manager.disconnect(gamepad);
121
- if (disconnectedGamepad === null) return;
122
- this.gamepad = this.#manager.activeGamepad;
123
109
  this.dispatchEvent({
124
110
  type: "disconnected",
125
- gamepad: disconnectedGamepad
111
+ gamepad
126
112
  });
127
113
  }
128
114
  };
@@ -68,9 +68,8 @@ declare class GamepadDragControls extends GamepadControls {
68
68
  * and rotate behavior.
69
69
  *
70
70
  * @param deltaTime - Seconds since the last frame.
71
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
72
71
  */
73
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
72
+ protected onUpdate(deltaTime: number): void;
74
73
  /**
75
74
  * Releases any selected object and removes hover state before disposing the
76
75
  * gamepad lifecycle listeners.
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonPressed } from "./utils.js";
4
3
  import { Matrix4, Vector2, Vector3 } from "three";
5
4
  //#region src/gamepad-drag-controls.ts
6
5
  /**
@@ -38,7 +37,6 @@ var GamepadDragControls = class extends GamepadControls {
38
37
  #viewSize;
39
38
  #hovered = null;
40
39
  #selected = null;
41
- #selectButtonPressed = false;
42
40
  /**
43
41
  * @param controls - A Three.js `DragControls` instance.
44
42
  * @param options - Optional overrides for the default behavior.
@@ -68,13 +66,10 @@ var GamepadDragControls = class extends GamepadControls {
68
66
  * and rotate behavior.
69
67
  *
70
68
  * @param deltaTime - Seconds since the last frame.
71
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
72
69
  */
73
- onUpdate(deltaTime, gamepad) {
70
+ onUpdate(deltaTime) {
74
71
  const controls = this.#controls;
75
- const selectPressed = getGamepadButtonPressed(gamepad, this.#options.buttonSelect);
76
- const selectStarted = selectPressed && !this.#selectButtonPressed;
77
- this.#selectButtonPressed = selectPressed;
72
+ const selectStarted = this.gamepadInput.wasPressed(this.#options.buttonSelect);
78
73
  if (!controls.enabled) {
79
74
  this.#releaseSelected();
80
75
  this.#clearHover();
@@ -85,7 +80,7 @@ var GamepadDragControls = class extends GamepadControls {
85
80
  this.#releaseSelected();
86
81
  return;
87
82
  }
88
- this.#updateSelected(deltaTime, gamepad);
83
+ this.#updateSelected(deltaTime);
89
84
  return;
90
85
  }
91
86
  const hit = this.#intersectCenter();
@@ -115,16 +110,16 @@ var GamepadDragControls = class extends GamepadControls {
115
110
  * Updates the selected object from gamepad drag and rotation input.
116
111
  *
117
112
  * @param deltaTime - Seconds since the last frame.
118
- * @param gamepad - Fresh gamepad snapshot to read from.
119
113
  */
120
- #updateSelected(deltaTime, gamepad) {
114
+ #updateSelected(deltaTime) {
121
115
  const selected = this.#selected;
122
116
  if (selected === null) return;
123
117
  const { dragSpeed, rotateSpeed, deadzone, axisDragX, axisDragY, axisRotateX, axisRotateY } = this.#options;
124
- const dragX = applyGamepadDeadzone(gamepad.axes[axisDragX] ?? 0, deadzone);
125
- const dragY = applyGamepadDeadzone(gamepad.axes[axisDragY] ?? 0, deadzone);
126
- const rotateX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
127
- const rotateY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
118
+ const input = this.gamepadInput;
119
+ const dragX = input.axis(axisDragX, { deadzone });
120
+ const dragY = input.axis(axisDragY, { deadzone });
121
+ const rotateX = input.axis(axisRotateX, { deadzone });
122
+ const rotateY = input.axis(axisRotateY, { deadzone });
128
123
  const dragged = this.#applyDrag(deltaTime, dragX, dragY, dragSpeed);
129
124
  const rotated = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed);
130
125
  if (dragged || rotated) this.#controls.dispatchEvent({
@@ -73,9 +73,8 @@ declare class GamepadFirstPersonControls extends GamepadControls {
73
73
  * Maps the current gamepad state to `FirstPersonControls` translation and look.
74
74
  *
75
75
  * @param deltaTime - Seconds since the last frame.
76
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
77
76
  */
78
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
77
+ protected onUpdate(deltaTime: number): void;
79
78
  }
80
79
  //#endregion
81
80
  export { GamepadFirstPersonControls, GamepadFirstPersonControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
4
3
  import { MathUtils, Spherical, Vector3 } from "three";
5
4
  //#region src/gamepad-first-person-controls.ts
6
5
  /**
@@ -51,18 +50,16 @@ var GamepadFirstPersonControls = class extends GamepadControls {
51
50
  * Maps the current gamepad state to `FirstPersonControls` translation and look.
52
51
  *
53
52
  * @param deltaTime - Seconds since the last frame.
54
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
55
53
  */
56
- onUpdate(deltaTime, gamepad) {
54
+ onUpdate(deltaTime) {
57
55
  const { moveSpeed, lookSpeed, deadzone, axisMoveForward, axisMoveRight, axisLookX, axisLookY, buttonMoveUp, buttonMoveDown } = this.#options;
58
- this.#applyMovement(deltaTime, gamepad, moveSpeed, deadzone, axisMoveForward, axisMoveRight, buttonMoveUp, buttonMoveDown);
59
- this.#applyLook(deltaTime, gamepad, lookSpeed, deadzone, axisLookX, axisLookY);
56
+ this.#applyMovement(deltaTime, moveSpeed, deadzone, axisMoveForward, axisMoveRight, buttonMoveUp, buttonMoveDown);
57
+ this.#applyLook(deltaTime, lookSpeed, deadzone, axisLookX, axisLookY);
60
58
  }
61
59
  /**
62
60
  * Applies local translation input to FirstPersonControls' object.
63
61
  *
64
62
  * @param deltaTime - Seconds since the last frame.
65
- * @param gamepad - Fresh gamepad snapshot to read from.
66
63
  * @param moveSpeed - User-configured movement speed multiplier.
67
64
  * @param deadzone - Axis and trigger dead zone threshold.
68
65
  * @param axisMoveForward - Axis index for forward and backward movement.
@@ -70,10 +67,11 @@ var GamepadFirstPersonControls = class extends GamepadControls {
70
67
  * @param buttonMoveUp - Button index for upward movement.
71
68
  * @param buttonMoveDown - Button index for downward movement.
72
69
  */
73
- #applyMovement(deltaTime, gamepad, moveSpeed, deadzone, axisMoveForward, axisMoveRight, buttonMoveUp, buttonMoveDown) {
70
+ #applyMovement(deltaTime, moveSpeed, deadzone, axisMoveForward, axisMoveRight, buttonMoveUp, buttonMoveDown) {
74
71
  const controls = this.#controls;
72
+ const input = this.gamepadInput;
75
73
  const moveMult = deltaTime * controls.movementSpeed * moveSpeed;
76
- const forward = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
74
+ const forward = input.axis(axisMoveForward, { deadzone });
77
75
  if (forward !== 0) {
78
76
  let distance = forward * moveMult;
79
77
  if (forward < 0 && controls.heightSpeed) {
@@ -82,10 +80,10 @@ var GamepadFirstPersonControls = class extends GamepadControls {
82
80
  }
83
81
  controls.object.translateZ(distance);
84
82
  }
85
- const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
83
+ const strafe = input.axis(axisMoveRight, { deadzone });
86
84
  if (strafe !== 0) controls.object.translateX(strafe * moveMult);
87
- const up = getGamepadButtonValue(gamepad, buttonMoveUp);
88
- const down = getGamepadButtonValue(gamepad, buttonMoveDown);
85
+ const up = input.buttonValue(buttonMoveUp);
86
+ const down = input.buttonValue(buttonMoveDown);
89
87
  if (up > deadzone) controls.object.translateY(up * moveMult);
90
88
  if (down > deadzone) controls.object.translateY(-down * moveMult);
91
89
  }
@@ -93,15 +91,15 @@ var GamepadFirstPersonControls = class extends GamepadControls {
93
91
  * Applies camera look input while keeping FirstPersonControls state in sync.
94
92
  *
95
93
  * @param deltaTime - Seconds since the last frame.
96
- * @param gamepad - Fresh gamepad snapshot to read from.
97
94
  * @param lookSpeed - User-configured look speed multiplier.
98
95
  * @param deadzone - Axis dead zone threshold.
99
96
  * @param axisLookX - Axis index for yaw input.
100
97
  * @param axisLookY - Axis index for pitch input.
101
98
  */
102
- #applyLook(deltaTime, gamepad, lookSpeed, deadzone, axisLookX, axisLookY) {
103
- const lookX = applyGamepadDeadzone(gamepad.axes[axisLookX] ?? 0, deadzone);
104
- const lookY = applyGamepadDeadzone(gamepad.axes[axisLookY] ?? 0, deadzone);
99
+ #applyLook(deltaTime, lookSpeed, deadzone, axisLookX, axisLookY) {
100
+ const input = this.gamepadInput;
101
+ const lookX = input.axis(axisLookX, { deadzone });
102
+ const lookY = input.axis(axisLookY, { deadzone });
105
103
  if (lookX === 0 && lookY === 0) return;
106
104
  const controls = this.#controls;
107
105
  const actualLookSpeed = controls.lookSpeed * lookSpeed * deltaTime * LOOK_SPEED_SCALE;
@@ -83,9 +83,8 @@ declare class GamepadFlyControls extends GamepadControls {
83
83
  * Maps the current gamepad state to `FlyControls` translation and rotation.
84
84
  *
85
85
  * @param deltaTime - Seconds since the last frame.
86
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
87
86
  */
88
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
87
+ protected onUpdate(deltaTime: number): void;
89
88
  }
90
89
  //#endregion
91
90
  export { GamepadFlyControls, GamepadFlyControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonPressed, getGamepadButtonValue } from "./utils.js";
4
3
  import { Quaternion } from "three";
5
4
  //#region src/gamepad-fly-controls.ts
6
5
  /**
@@ -48,23 +47,23 @@ var GamepadFlyControls = class extends GamepadControls {
48
47
  * Maps the current gamepad state to `FlyControls` translation and rotation.
49
48
  *
50
49
  * @param deltaTime - Seconds since the last frame.
51
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
52
50
  */
53
- onUpdate(deltaTime, gamepad) {
51
+ onUpdate(deltaTime) {
54
52
  const { moveSpeed, rotateSpeed, deadzone, axisMoveForward, axisMoveRight, axisLookX, axisLookY, buttonRollLeft, buttonRollRight, buttonMoveUp, buttonMoveDown } = this.#options;
53
+ const input = this.gamepadInput;
55
54
  const moveMult = deltaTime * this.#controls.movementSpeed * moveSpeed;
56
- const fwd = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
55
+ const fwd = input.axis(axisMoveForward, { deadzone });
57
56
  if (fwd !== 0) this.#controls.object.translateZ(fwd * moveMult);
58
- const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
57
+ const strafe = input.axis(axisMoveRight, { deadzone });
59
58
  if (strafe !== 0) this.#controls.object.translateX(strafe * moveMult);
60
- const up = getGamepadButtonValue(gamepad, buttonMoveUp);
61
- const down = getGamepadButtonValue(gamepad, buttonMoveDown);
59
+ const up = input.buttonValue(buttonMoveUp);
60
+ const down = input.buttonValue(buttonMoveDown);
62
61
  if (up > deadzone) this.#controls.object.translateY(up * moveMult);
63
62
  if (down > deadzone) this.#controls.object.translateY(-down * moveMult);
64
63
  const rotMult = deltaTime * this.#controls.rollSpeed * rotateSpeed;
65
- const pitch = -applyGamepadDeadzone(gamepad.axes[axisLookY] ?? 0, deadzone);
66
- const yaw = -applyGamepadDeadzone(gamepad.axes[axisLookX] ?? 0, deadzone);
67
- const roll = (getGamepadButtonPressed(gamepad, buttonRollLeft) ? 1 : 0) - (getGamepadButtonPressed(gamepad, buttonRollRight) ? 1 : 0);
64
+ const pitch = -input.axis(axisLookY, { deadzone });
65
+ const yaw = -input.axis(axisLookX, { deadzone });
66
+ const roll = (input.isPressed(buttonRollLeft) ? 1 : 0) - (input.isPressed(buttonRollRight) ? 1 : 0);
68
67
  if (pitch !== 0 || yaw !== 0 || roll !== 0) {
69
68
  this.#tmpQuaternion.set(pitch * rotMult, yaw * rotMult, roll * rotMult, 1).normalize();
70
69
  this.#controls.object.quaternion.multiply(this.#tmpQuaternion);
@@ -1,9 +1,49 @@
1
1
  import { GamepadManager } from "./gamepad-manager.js";
2
- import { applyGamepadDeadzone, getGamepadButtonPressed, getGamepadButtonValue } from "./utils.js";
3
2
  import { EventDispatcher } from "three";
4
3
  //#region src/gamepad-input.ts
5
4
  const DEFAULT_GAMEPAD_INPUT_OPTIONS = { deadzone: .1 };
6
5
  /**
6
+ * Returns `value` unchanged, or `0` if below the dead zone `threshold`.
7
+ *
8
+ * Kept private to this module because `GamepadInput` is the only public API
9
+ * that currently exposes processed axis values.
10
+ *
11
+ * @param value - Raw axis or trigger value, typically in `[-1, 1]`.
12
+ * @param threshold - Dead zone size; values below this magnitude are zeroed.
13
+ * @returns The original value when outside the dead zone, otherwise `0`.
14
+ */
15
+ const applyGamepadDeadzone = (value, threshold) => {
16
+ return Math.abs(value) < threshold ? 0 : value;
17
+ };
18
+ /**
19
+ * Returns whether a gamepad button is currently pressed.
20
+ *
21
+ * Missing buttons are treated as not pressed.
22
+ *
23
+ * @param gamepad - Gamepad snapshot to read from.
24
+ * @param button - Button index to inspect.
25
+ * @returns `true` when the button exists and is pressed, otherwise `false`.
26
+ */
27
+ const getGamepadButtonPressed = (gamepad, button) => {
28
+ return gamepad.buttons[button]?.pressed ?? false;
29
+ };
30
+ /**
31
+ * Returns the analog value for a gamepad button.
32
+ *
33
+ * Some digital buttons may report `pressed` without a meaningful non-zero
34
+ * `value`, so pressed buttons fall back to `1`.
35
+ *
36
+ * @param gamepad - Gamepad snapshot to read from.
37
+ * @param button - Button index to inspect.
38
+ * @returns The button value, `1` for pressed digital buttons, or `0` when unavailable.
39
+ */
40
+ const getGamepadButtonValue = (gamepad, button) => {
41
+ const gamepadButton = gamepad.buttons[button];
42
+ if (gamepadButton === void 0) return 0;
43
+ if (gamepadButton.value !== 0) return gamepadButton.value;
44
+ return gamepadButton.pressed ? 1 : 0;
45
+ };
46
+ /**
7
47
  * Gamepad input state reader for gameplay, menus, and custom actions.
8
48
  *
9
49
  * Call {@link update} once per frame before reading button transitions or axes.
@@ -77,9 +77,8 @@ declare class GamepadOrbitControls extends GamepadControls {
77
77
  * Maps the current gamepad state to `OrbitControls` rotation, pan, and dolly.
78
78
  *
79
79
  * @param deltaTime - Seconds since the last frame.
80
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
81
80
  */
82
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
81
+ protected onUpdate(deltaTime: number): void;
83
82
  }
84
83
  //#endregion
85
84
  export { GamepadOrbitControls, GamepadOrbitControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
4
3
  //#region src/gamepad-orbit-controls.ts
5
4
  /**
6
5
  * Default options merged in the constructor when no explicit configuration is provided.
@@ -43,19 +42,19 @@ var GamepadOrbitControls = class extends GamepadControls {
43
42
  * Maps the current gamepad state to `OrbitControls` rotation, pan, and dolly.
44
43
  *
45
44
  * @param deltaTime - Seconds since the last frame.
46
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
47
45
  */
48
- onUpdate(deltaTime, gamepad) {
46
+ onUpdate(deltaTime) {
49
47
  const { rotateSpeed, panSpeed, zoomSpeed, deadzone, axisRotateX, axisRotateY, axisPanX, axisPanY, buttonDollyIn, buttonDollyOut } = this.#options;
50
- const rotX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
51
- const rotY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
48
+ const input = this.gamepadInput;
49
+ const rotX = input.axis(axisRotateX, { deadzone });
50
+ const rotY = input.axis(axisRotateY, { deadzone });
52
51
  if (rotX !== 0) this.#controls.rotateLeft(rotX * rotateSpeed * deltaTime * Math.PI);
53
52
  if (rotY !== 0) this.#controls.rotateUp(rotY * rotateSpeed * deltaTime * Math.PI);
54
- const panX = applyGamepadDeadzone(gamepad.axes[axisPanX] ?? 0, deadzone);
55
- const panY = applyGamepadDeadzone(gamepad.axes[axisPanY] ?? 0, deadzone);
53
+ const panX = input.axis(axisPanX, { deadzone });
54
+ const panY = input.axis(axisPanY, { deadzone });
56
55
  if (panX !== 0 || panY !== 0) this.#controls.pan(panX * panSpeed * deltaTime * 500, panY * panSpeed * deltaTime * 500);
57
- const triggerIn = getGamepadButtonValue(gamepad, buttonDollyIn);
58
- const triggerOut = getGamepadButtonValue(gamepad, buttonDollyOut);
56
+ const triggerIn = input.buttonValue(buttonDollyIn);
57
+ const triggerOut = input.buttonValue(buttonDollyOut);
59
58
  if (triggerIn > deadzone) this.#controls.dollyIn(1 / (1 + zoomSpeed * triggerIn * deltaTime));
60
59
  if (triggerOut > deadzone) this.#controls.dollyOut(1 / (1 + zoomSpeed * triggerOut * deltaTime));
61
60
  }
@@ -63,9 +63,8 @@ declare class GamepadPointerLockControls extends GamepadControls {
63
63
  * Maps the current gamepad state to `PointerLockControls` movement and look.
64
64
  *
65
65
  * @param deltaTime - Seconds since the last frame.
66
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
67
66
  */
68
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
67
+ protected onUpdate(deltaTime: number): void;
69
68
  }
70
69
  //#endregion
71
70
  export { GamepadPointerLockControls, GamepadPointerLockControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone } from "./utils.js";
4
3
  import { Euler } from "three";
5
4
  //#region src/gamepad-pointer-lock-controls.ts
6
5
  /**
@@ -44,16 +43,16 @@ var GamepadPointerLockControls = class extends GamepadControls {
44
43
  * Maps the current gamepad state to `PointerLockControls` movement and look.
45
44
  *
46
45
  * @param deltaTime - Seconds since the last frame.
47
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
48
46
  */
49
- onUpdate(deltaTime, gamepad) {
47
+ onUpdate(deltaTime) {
50
48
  const { moveSpeed, lookSpeed, deadzone, axisMoveForward, axisMoveRight, axisLookX, axisLookY } = this.#options;
51
- const fwd = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
52
- const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
49
+ const input = this.gamepadInput;
50
+ const fwd = input.axis(axisMoveForward, { deadzone });
51
+ const strafe = input.axis(axisMoveRight, { deadzone });
53
52
  if (fwd !== 0) this.#controls.moveForward(-fwd * moveSpeed * deltaTime);
54
53
  if (strafe !== 0) this.#controls.moveRight(strafe * moveSpeed * deltaTime);
55
- const lookX = applyGamepadDeadzone(gamepad.axes[axisLookX] ?? 0, deadzone);
56
- const lookY = applyGamepadDeadzone(gamepad.axes[axisLookY] ?? 0, deadzone);
54
+ const lookX = input.axis(axisLookX, { deadzone });
55
+ const lookY = input.axis(axisLookY, { deadzone });
57
56
  if (lookX !== 0 || lookY !== 0) {
58
57
  const camera = this.#controls.object;
59
58
  const scale = lookSpeed * this.#controls.pointerSpeed * deltaTime * Math.PI;
@@ -77,9 +77,8 @@ declare class GamepadTrackballControls extends GamepadControls {
77
77
  * Maps the current gamepad state to `TrackballControls` rotation, pan, and zoom.
78
78
  *
79
79
  * @param deltaTime - Seconds since the last frame.
80
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
81
80
  */
82
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
81
+ protected onUpdate(deltaTime: number): void;
83
82
  }
84
83
  //#endregion
85
84
  export { GamepadTrackballControls, GamepadTrackballControlsOptions };
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonValue } from "./utils.js";
4
3
  //#region src/gamepad-trackball-controls.ts
5
4
  /**
6
5
  * Default options merged in the constructor when no explicit configuration is provided.
@@ -43,33 +42,32 @@ var GamepadTrackballControls = class extends GamepadControls {
43
42
  * Maps the current gamepad state to `TrackballControls` rotation, pan, and zoom.
44
43
  *
45
44
  * @param deltaTime - Seconds since the last frame.
46
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
47
45
  */
48
- onUpdate(deltaTime, gamepad) {
46
+ onUpdate(deltaTime) {
49
47
  const { rotateSpeed, panSpeed, zoomSpeed, deadzone, axisRotateX, axisRotateY, axisPanX, axisPanY, buttonZoomIn, buttonZoomOut } = this.#options;
50
- this.#queueRotation(deltaTime, gamepad, rotateSpeed, deadzone, axisRotateX, axisRotateY);
51
- this.#queuePan(deltaTime, gamepad, panSpeed, deadzone, axisPanX, axisPanY);
52
- this.#queueZoom(deltaTime, gamepad, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut);
48
+ this.#queueRotation(deltaTime, rotateSpeed, deadzone, axisRotateX, axisRotateY);
49
+ this.#queuePan(deltaTime, panSpeed, deadzone, axisPanX, axisPanY);
50
+ this.#queueZoom(deltaTime, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut);
53
51
  }
54
52
  /**
55
53
  * Queues rotation input into TrackballControls' normalized move state.
56
54
  *
57
55
  * @param deltaTime - Seconds since the last frame.
58
- * @param gamepad - Fresh gamepad snapshot to read from.
59
56
  * @param rotateSpeed - User-configured rotation speed multiplier.
60
57
  * @param deadzone - Axis dead zone threshold.
61
58
  * @param axisRotateX - Axis index for horizontal rotation.
62
59
  * @param axisRotateY - Axis index for vertical rotation.
63
60
  */
64
- #queueRotation(deltaTime, gamepad, rotateSpeed, deadzone, axisRotateX, axisRotateY) {
61
+ #queueRotation(deltaTime, rotateSpeed, deadzone, axisRotateX, axisRotateY) {
65
62
  const controls = this.#controls;
66
63
  if (controls.noRotate) {
67
64
  controls._movePrev.copy(controls._moveCurr);
68
65
  controls._lastAngle = 0;
69
66
  return;
70
67
  }
71
- const rotX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
72
- const rotY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
68
+ const input = this.gamepadInput;
69
+ const rotX = input.axis(axisRotateX, { deadzone });
70
+ const rotY = input.axis(axisRotateY, { deadzone });
73
71
  if (rotX === 0 && rotY === 0) return;
74
72
  const scale = rotateSpeed * deltaTime * Math.PI;
75
73
  controls._moveCurr.x += rotX * scale;
@@ -79,20 +77,20 @@ var GamepadTrackballControls = class extends GamepadControls {
79
77
  * Queues pan input into TrackballControls' normalized pan state.
80
78
  *
81
79
  * @param deltaTime - Seconds since the last frame.
82
- * @param gamepad - Fresh gamepad snapshot to read from.
83
80
  * @param panSpeed - User-configured pan speed multiplier.
84
81
  * @param deadzone - Axis dead zone threshold.
85
82
  * @param axisPanX - Axis index for horizontal panning.
86
83
  * @param axisPanY - Axis index for vertical panning.
87
84
  */
88
- #queuePan(deltaTime, gamepad, panSpeed, deadzone, axisPanX, axisPanY) {
85
+ #queuePan(deltaTime, panSpeed, deadzone, axisPanX, axisPanY) {
89
86
  const controls = this.#controls;
90
87
  if (controls.noPan) {
91
88
  controls._panStart.copy(controls._panEnd);
92
89
  return;
93
90
  }
94
- const panX = applyGamepadDeadzone(gamepad.axes[axisPanX] ?? 0, deadzone);
95
- const panY = applyGamepadDeadzone(gamepad.axes[axisPanY] ?? 0, deadzone);
91
+ const input = this.gamepadInput;
92
+ const panX = input.axis(axisPanX, { deadzone });
93
+ const panY = input.axis(axisPanY, { deadzone });
96
94
  if (panX === 0 && panY === 0) return;
97
95
  const scale = panSpeed * deltaTime * this.#getInputDampingFactor();
98
96
  controls._panEnd.x += panX * scale;
@@ -102,20 +100,20 @@ var GamepadTrackballControls = class extends GamepadControls {
102
100
  * Queues trigger zoom input into TrackballControls' normalized zoom state.
103
101
  *
104
102
  * @param deltaTime - Seconds since the last frame.
105
- * @param gamepad - Fresh gamepad snapshot to read from.
106
103
  * @param zoomSpeed - User-configured zoom speed multiplier.
107
104
  * @param deadzone - Trigger dead zone threshold.
108
105
  * @param buttonZoomIn - Button index for zooming in.
109
106
  * @param buttonZoomOut - Button index for zooming out.
110
107
  */
111
- #queueZoom(deltaTime, gamepad, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut) {
108
+ #queueZoom(deltaTime, zoomSpeed, deadzone, buttonZoomIn, buttonZoomOut) {
112
109
  const controls = this.#controls;
113
110
  if (controls.noZoom) {
114
111
  controls._zoomStart.copy(controls._zoomEnd);
115
112
  return;
116
113
  }
117
- const triggerIn = getGamepadButtonValue(gamepad, buttonZoomIn);
118
- const triggerOut = getGamepadButtonValue(gamepad, buttonZoomOut);
114
+ const input = this.gamepadInput;
115
+ const triggerIn = input.buttonValue(buttonZoomIn);
116
+ const triggerOut = input.buttonValue(buttonZoomOut);
119
117
  if (triggerIn <= deadzone && triggerOut <= deadzone) return;
120
118
  controls._zoomEnd.y += (triggerOut - triggerIn) * zoomSpeed * deltaTime * this.#getInputDampingFactor();
121
119
  }
@@ -114,9 +114,8 @@ declare class GamepadTransformControls extends GamepadControls {
114
114
  * translate, rotate, scale, and reset behavior.
115
115
  *
116
116
  * @param deltaTime - Seconds since the last frame.
117
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
118
117
  */
119
- protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
118
+ protected onUpdate(deltaTime: number): void;
120
119
  /**
121
120
  * Ends any active transform before disposing the gamepad lifecycle listeners.
122
121
  */
@@ -1,6 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
- import { applyGamepadDeadzone, getGamepadButtonPressed } from "./utils.js";
4
3
  import { Matrix4, Quaternion, Vector2, Vector3 } from "three";
5
4
  //#region src/gamepad-transform-controls.ts
6
5
  /**
@@ -70,7 +69,6 @@ const PROJECTED_AXIS_EPSILON = .001;
70
69
  var GamepadTransformControls = class extends GamepadControls {
71
70
  #controls;
72
71
  #options;
73
- #pressedButtons;
74
72
  #activeAxisByMode;
75
73
  #viewSize;
76
74
  #parentInverse;
@@ -117,7 +115,6 @@ var GamepadTransformControls = class extends GamepadControls {
117
115
  ...DEFAULT_TRANSFORM_OPTIONS,
118
116
  ...options
119
117
  };
120
- this.#pressedButtons = /* @__PURE__ */ new Set();
121
118
  this.#activeAxisByMode = {
122
119
  translate: "X",
123
120
  rotate: "X",
@@ -158,10 +155,9 @@ var GamepadTransformControls = class extends GamepadControls {
158
155
  * translate, rotate, scale, and reset behavior.
159
156
  *
160
157
  * @param deltaTime - Seconds since the last frame.
161
- * @param gamepad - Fresh gamepad snapshot provided by the base class.
162
158
  */
163
- onUpdate(deltaTime, gamepad) {
164
- const startedButtons = this.#getStartedButtons(gamepad);
159
+ onUpdate(deltaTime) {
160
+ const startedButtons = this.#getStartedButtons();
165
161
  this.#handleModeAndAxisButtons(startedButtons);
166
162
  if (startedButtons.has(this.#options.buttonReset)) this.#resetActiveTransform();
167
163
  const controls = this.#controls;
@@ -173,8 +169,8 @@ var GamepadTransformControls = class extends GamepadControls {
173
169
  this.#endTransform(true);
174
170
  return;
175
171
  }
176
- const transformX = applyGamepadDeadzone(gamepad.axes[this.#options.axisTransformX] ?? 0, this.#options.deadzone);
177
- const transformY = applyGamepadDeadzone(gamepad.axes[this.#options.axisTransformY] ?? 0, this.#options.deadzone);
172
+ const transformX = this.gamepadInput.axis(this.#options.axisTransformX, { deadzone: this.#options.deadzone });
173
+ const transformY = this.gamepadInput.axis(this.#options.axisTransformY, { deadzone: this.#options.deadzone });
178
174
  if (transformX === 0 && transformY === 0) {
179
175
  this.#endTransform(false);
180
176
  return;
@@ -190,7 +186,6 @@ var GamepadTransformControls = class extends GamepadControls {
190
186
  */
191
187
  dispose() {
192
188
  this.#endTransform(true);
193
- this.#pressedButtons.clear();
194
189
  super.dispose();
195
190
  }
196
191
  /**
@@ -200,7 +195,6 @@ var GamepadTransformControls = class extends GamepadControls {
200
195
  */
201
196
  onGamepadDisconnected(gamepad) {
202
197
  this.#endTransform(true);
203
- this.#pressedButtons.clear();
204
198
  super.onGamepadDisconnected(gamepad);
205
199
  }
206
200
  /**
@@ -882,21 +876,27 @@ var GamepadTransformControls = class extends GamepadControls {
882
876
  return Math.abs(inputX) >= Math.abs(inputY) ? inputX : inputY;
883
877
  }
884
878
  /**
885
- * Tracks pressed buttons and returns those newly pressed this frame.
879
+ * Returns configured button indices that were newly pressed this frame.
886
880
  *
887
- * @param gamepad - Fresh gamepad snapshot to read from.
888
881
  * @returns Button indices that transitioned to pressed.
889
882
  */
890
- #getStartedButtons(gamepad) {
883
+ #getStartedButtons() {
891
884
  const startedButtons = /* @__PURE__ */ new Set();
892
- for (let index = 0; index < gamepad.buttons.length; index += 1) {
893
- const pressed = getGamepadButtonPressed(gamepad, index);
894
- const wasPressed = this.#pressedButtons.has(index);
895
- if (pressed) {
896
- this.#pressedButtons.add(index);
897
- if (!wasPressed) startedButtons.add(index);
898
- } else this.#pressedButtons.delete(index);
899
- }
885
+ const input = this.gamepadInput;
886
+ const buttons = [
887
+ this.#options.buttonTranslate,
888
+ this.#options.buttonRotate,
889
+ this.#options.buttonScale,
890
+ this.#options.buttonToggleSpace,
891
+ this.#options.buttonAxisX,
892
+ this.#options.buttonAxisY,
893
+ this.#options.buttonAxisZ,
894
+ this.#options.buttonAxisComposite,
895
+ this.#options.buttonAxisPrevious,
896
+ this.#options.buttonAxisNext,
897
+ this.#options.buttonReset
898
+ ];
899
+ for (const button of buttons) if (input.wasPressed(button)) startedButtons.add(button);
900
900
  return startedButtons;
901
901
  }
902
902
  /**
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue } from "./core.js";
2
+ import { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick } from "./gamepad-input.js";
2
3
  import { GamepadControls, GamepadControlsEventMap } from "./gamepad-controls.js";
3
4
  import { GamepadArcballControls, GamepadArcballControlsOptions } from "./gamepad-arcball-controls.js";
4
5
  import { GamepadDragControls, GamepadDragControlsOptions } from "./gamepad-drag-controls.js";
5
6
  import { GamepadFirstPersonControls, GamepadFirstPersonControlsOptions } from "./gamepad-first-person-controls.js";
6
7
  import { GamepadFlyControls, GamepadFlyControlsOptions } from "./gamepad-fly-controls.js";
7
- import { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick } from "./gamepad-input.js";
8
8
  import { GamepadOrbitControls, GamepadOrbitControlsOptions } from "./gamepad-orbit-controls.js";
9
9
  import { GamepadMapControls } from "./gamepad-map-controls.js";
10
10
  import { GamepadPointerLockControls, GamepadPointerLockControlsOptions } from "./gamepad-pointer-lock-controls.js";
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
+ import { GamepadInput } from "./gamepad-input.js";
2
3
  import { GamepadControls } from "./gamepad-controls.js";
3
4
  import { GamepadArcballControls } from "./gamepad-arcball-controls.js";
4
5
  import { GamepadDragControls } from "./gamepad-drag-controls.js";
5
6
  import { GamepadFirstPersonControls } from "./gamepad-first-person-controls.js";
6
7
  import { GamepadFlyControls } from "./gamepad-fly-controls.js";
7
- import { GamepadInput } from "./gamepad-input.js";
8
8
  import { GamepadOrbitControls } from "./gamepad-orbit-controls.js";
9
9
  import { GamepadMapControls } from "./gamepad-map-controls.js";
10
10
  import { GamepadPointerLockControls } from "./gamepad-pointer-lock-controls.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "three-gamepad-controls",
3
3
  "description": "Gamepad support for Three.js controls.",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "homepage": "https://github.com/luckasnix/three-gamepad-controls#readme",
6
6
  "author": {
7
7
  "name": "Kasnix",
@@ -51,14 +51,14 @@
51
51
  "@commitlint/config-conventional": "21.0.2",
52
52
  "@commitlint/types": "21.0.1",
53
53
  "@types/node": "24.13.2",
54
- "@types/three": "0.184.1",
54
+ "@types/three": "0.184.0",
55
55
  "husky": "9.1.7",
56
56
  "three": "0.184.0",
57
57
  "tsdown": "0.22.2",
58
58
  "typescript": "6.0.3"
59
59
  },
60
60
  "peerDependencies": {
61
- "@types/three": ">=0.184.1",
61
+ "@types/three": ">=0.184.0",
62
62
  "three": ">=0.184.0"
63
63
  },
64
64
  "scripts": {
package/dist/utils.js DELETED
@@ -1,41 +0,0 @@
1
- //#region src/utils.ts
2
- /**
3
- * Returns `value` unchanged, or `0` if below the dead zone `threshold`.
4
- *
5
- * @param value - Raw axis or trigger value, typically in `[-1, 1]`.
6
- * @param threshold - Dead zone size; values below this magnitude are zeroed.
7
- * @returns The original value when outside the dead zone, otherwise `0`.
8
- */
9
- const applyGamepadDeadzone = (value, threshold) => {
10
- return Math.abs(value) < threshold ? 0 : value;
11
- };
12
- /**
13
- * Returns whether a gamepad button is currently pressed.
14
- *
15
- * Missing buttons are treated as not pressed.
16
- *
17
- * @param gamepad - Gamepad snapshot to read from.
18
- * @param button - Button index to inspect.
19
- * @returns `true` when the button exists and is pressed, otherwise `false`.
20
- */
21
- const getGamepadButtonPressed = (gamepad, button) => {
22
- return gamepad.buttons[button]?.pressed ?? false;
23
- };
24
- /**
25
- * Returns the analog value for a gamepad button.
26
- *
27
- * Some digital buttons may report `pressed` without a meaningful non-zero
28
- * `value`, so pressed buttons fall back to `1`.
29
- *
30
- * @param gamepad - Gamepad snapshot to read from.
31
- * @param button - Button index to inspect.
32
- * @returns The button value, `1` for pressed digital buttons, or `0` when unavailable.
33
- */
34
- const getGamepadButtonValue = (gamepad, button) => {
35
- const gamepadButton = gamepad.buttons[button];
36
- if (gamepadButton === void 0) return 0;
37
- if (gamepadButton.value !== 0) return gamepadButton.value;
38
- return gamepadButton.pressed ? 1 : 0;
39
- };
40
- //#endregion
41
- export { applyGamepadDeadzone, getGamepadButtonPressed, getGamepadButtonValue };