three-gamepad-controls 0.10.4 → 0.10.6
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/dist/gamepad-arcball-controls.js +8 -25
- package/dist/gamepad-controls.js +29 -10
- package/dist/gamepad-drag-controls.js +6 -17
- package/dist/gamepad-first-person-controls.js +7 -15
- package/dist/gamepad-fly-controls.js +8 -16
- package/dist/gamepad-manager.js +104 -0
- package/dist/gamepad-orbit-controls.js +7 -15
- package/dist/gamepad-pointer-lock-controls.js +5 -13
- package/dist/gamepad-trackball-controls.js +7 -15
- package/dist/gamepad-transform-controls.js +4 -12
- package/dist/utils.js +41 -0
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
import { Vector2, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-arcball-controls.ts
|
|
5
6
|
/**
|
|
@@ -76,12 +77,12 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
76
77
|
this.#endInteraction();
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
|
-
const rotateX = controls.enableRotate ?
|
|
80
|
-
const rotateY = controls.enableRotate ?
|
|
81
|
-
const panX = controls.enablePan ?
|
|
82
|
-
const panY = controls.enablePan ?
|
|
83
|
-
const zoom = controls.enableZoom ?
|
|
84
|
-
const zRotation = controls.enableRotate ?
|
|
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;
|
|
85
86
|
const activeInput = rotateX !== 0 || rotateY !== 0 || panX !== 0 || panY !== 0 || Math.abs(zoom) > deadzone || Math.abs(zRotation) > deadzone;
|
|
86
87
|
if (!activeInput && focusPoint === null) {
|
|
87
88
|
this.#endInteraction();
|
|
@@ -171,7 +172,7 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
171
172
|
}
|
|
172
173
|
#consumeFocusPoint(gamepad, buttonFocus) {
|
|
173
174
|
const controls = this.#controls;
|
|
174
|
-
const focusPressed =
|
|
175
|
+
const focusPressed = getGamepadButtonPressed(gamepad, buttonFocus);
|
|
175
176
|
const shouldFocus = focusPressed && !this.#focusButtonPressed;
|
|
176
177
|
this.#focusButtonPressed = focusPressed;
|
|
177
178
|
if (!shouldFocus || !controls.enabled || !controls.enablePan || !controls.enableFocus || controls.scene === null) return null;
|
|
@@ -182,24 +183,6 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
182
183
|
this.#controls.dispatchEvent({ type: "end" });
|
|
183
184
|
this.#wasInteracting = false;
|
|
184
185
|
}
|
|
185
|
-
#getButtonValue(gamepad, button) {
|
|
186
|
-
const gamepadButton = gamepad.buttons[button];
|
|
187
|
-
if (gamepadButton === void 0) return 0;
|
|
188
|
-
if (gamepadButton.value !== 0) return gamepadButton.value;
|
|
189
|
-
return gamepadButton.pressed ? 1 : 0;
|
|
190
|
-
}
|
|
191
|
-
#getButtonPressed(gamepad, button) {
|
|
192
|
-
return gamepad.buttons[button]?.pressed ?? false;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
196
|
-
*
|
|
197
|
-
* @param value - Raw axis or trigger value, typically in `[-1, 1]`.
|
|
198
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
199
|
-
*/
|
|
200
|
-
#applyDeadzone(value, threshold) {
|
|
201
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
202
|
-
}
|
|
203
186
|
};
|
|
204
187
|
//#endregion
|
|
205
188
|
export { GamepadArcballControls };
|
package/dist/gamepad-controls.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GamepadManager } from "./gamepad-manager.js";
|
|
1
2
|
import { EventDispatcher } from "three";
|
|
2
3
|
//#region src/gamepad-controls.ts
|
|
3
4
|
/**
|
|
@@ -16,10 +17,12 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
16
17
|
* The currently active gamepad, or `null` if no gamepad is connected.
|
|
17
18
|
*/
|
|
18
19
|
gamepad = null;
|
|
20
|
+
#manager;
|
|
19
21
|
#onGamepadConnected;
|
|
20
22
|
#onGamepadDisconnected;
|
|
21
23
|
constructor() {
|
|
22
24
|
super();
|
|
25
|
+
this.#manager = new GamepadManager();
|
|
23
26
|
this.#onGamepadConnected = (event) => {
|
|
24
27
|
this.onGamepadConnected(event.gamepad);
|
|
25
28
|
};
|
|
@@ -36,9 +39,20 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
36
39
|
*/
|
|
37
40
|
update(deltaTime) {
|
|
38
41
|
if (!this.enabled) return;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
43
|
+
const { gamepad, connected, disconnected } = this.#manager.update();
|
|
44
|
+
if (connected !== null) {
|
|
45
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
46
|
+
this.onGamepadConnected(connected);
|
|
47
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
48
|
+
} else if (disconnected !== null) {
|
|
49
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
50
|
+
this.onGamepadDisconnected(disconnected);
|
|
51
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
52
|
+
return;
|
|
53
|
+
} else {
|
|
54
|
+
this.gamepad = gamepad;
|
|
55
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
42
56
|
}
|
|
43
57
|
if (this.gamepad === null) return;
|
|
44
58
|
this.onUpdate(deltaTime, this.gamepad);
|
|
@@ -50,6 +64,7 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
50
64
|
window.removeEventListener("gamepadconnected", this.#onGamepadConnected);
|
|
51
65
|
window.removeEventListener("gamepaddisconnected", this.#onGamepadDisconnected);
|
|
52
66
|
this.gamepad = null;
|
|
67
|
+
this.#manager.activeGamepad = null;
|
|
53
68
|
this.enabled = false;
|
|
54
69
|
}
|
|
55
70
|
/**
|
|
@@ -61,11 +76,14 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
61
76
|
* @param gamepad - The gamepad that just connected.
|
|
62
77
|
*/
|
|
63
78
|
onGamepadConnected(gamepad) {
|
|
64
|
-
|
|
65
|
-
this.gamepad
|
|
79
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
80
|
+
if (!this.#manager.connect(gamepad)) return;
|
|
81
|
+
const connectedGamepad = this.#manager.activeGamepad;
|
|
82
|
+
if (connectedGamepad === null) return;
|
|
83
|
+
this.gamepad = connectedGamepad;
|
|
66
84
|
this.dispatchEvent({
|
|
67
85
|
type: "connected",
|
|
68
|
-
gamepad
|
|
86
|
+
gamepad: connectedGamepad
|
|
69
87
|
});
|
|
70
88
|
}
|
|
71
89
|
/**
|
|
@@ -77,12 +95,13 @@ var GamepadControls = class extends EventDispatcher {
|
|
|
77
95
|
* @param gamepad - The gamepad that just disconnected.
|
|
78
96
|
*/
|
|
79
97
|
onGamepadDisconnected(gamepad) {
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
98
|
+
this.#manager.activeGamepad = this.gamepad;
|
|
99
|
+
const disconnectedGamepad = this.#manager.disconnect(gamepad);
|
|
100
|
+
if (disconnectedGamepad === null) return;
|
|
101
|
+
this.gamepad = this.#manager.activeGamepad;
|
|
83
102
|
this.dispatchEvent({
|
|
84
103
|
type: "disconnected",
|
|
85
|
-
gamepad:
|
|
104
|
+
gamepad: disconnectedGamepad
|
|
86
105
|
});
|
|
87
106
|
}
|
|
88
107
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
import { Matrix4, Vector2, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-drag-controls.ts
|
|
5
6
|
/**
|
|
@@ -71,7 +72,7 @@ var GamepadDragControls = class extends GamepadControls {
|
|
|
71
72
|
*/
|
|
72
73
|
onUpdate(deltaTime, gamepad) {
|
|
73
74
|
const controls = this.#controls;
|
|
74
|
-
const selectPressed =
|
|
75
|
+
const selectPressed = getGamepadButtonPressed(gamepad, this.#options.buttonSelect);
|
|
75
76
|
const selectStarted = selectPressed && !this.#selectButtonPressed;
|
|
76
77
|
this.#selectButtonPressed = selectPressed;
|
|
77
78
|
if (!controls.enabled) {
|
|
@@ -114,10 +115,10 @@ var GamepadDragControls = class extends GamepadControls {
|
|
|
114
115
|
const selected = this.#selected;
|
|
115
116
|
if (selected === null) return;
|
|
116
117
|
const { dragSpeed, rotateSpeed, deadzone, axisDragX, axisDragY, axisRotateX, axisRotateY } = this.#options;
|
|
117
|
-
const dragX =
|
|
118
|
-
const dragY =
|
|
119
|
-
const rotateX =
|
|
120
|
-
const rotateY =
|
|
118
|
+
const dragX = applyGamepadDeadzone(gamepad.axes[axisDragX] ?? 0, deadzone);
|
|
119
|
+
const dragY = applyGamepadDeadzone(gamepad.axes[axisDragY] ?? 0, deadzone);
|
|
120
|
+
const rotateX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
|
|
121
|
+
const rotateY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
|
|
121
122
|
const dragged = this.#applyDrag(deltaTime, dragX, dragY, dragSpeed);
|
|
122
123
|
const rotated = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed);
|
|
123
124
|
if (dragged || rotated) this.#controls.dispatchEvent({
|
|
@@ -243,18 +244,6 @@ var GamepadDragControls = class extends GamepadControls {
|
|
|
243
244
|
#isOrthographicCamera(camera) {
|
|
244
245
|
return camera.isOrthographicCamera === true;
|
|
245
246
|
}
|
|
246
|
-
#getButtonPressed(gamepad, button) {
|
|
247
|
-
return gamepad.buttons[button]?.pressed ?? false;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
251
|
-
*
|
|
252
|
-
* @param value - Raw axis value, typically in `[-1, 1]`.
|
|
253
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
254
|
-
*/
|
|
255
|
-
#applyDeadzone(value, threshold) {
|
|
256
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
257
|
-
}
|
|
258
247
|
};
|
|
259
248
|
//#endregion
|
|
260
249
|
export { GamepadDragControls };
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
import { MathUtils, Spherical, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-first-person-controls.ts
|
|
5
6
|
/**
|
|
@@ -60,7 +61,7 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
60
61
|
#applyMovement(deltaTime, gamepad, moveSpeed, deadzone, axisMoveForward, axisMoveRight, buttonMoveUp, buttonMoveDown) {
|
|
61
62
|
const controls = this.#controls;
|
|
62
63
|
const moveMult = deltaTime * controls.movementSpeed * moveSpeed;
|
|
63
|
-
const forward =
|
|
64
|
+
const forward = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
|
|
64
65
|
if (forward !== 0) {
|
|
65
66
|
let distance = forward * moveMult;
|
|
66
67
|
if (forward < 0 && controls.heightSpeed) {
|
|
@@ -69,16 +70,16 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
69
70
|
}
|
|
70
71
|
controls.object.translateZ(distance);
|
|
71
72
|
}
|
|
72
|
-
const strafe =
|
|
73
|
+
const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
|
|
73
74
|
if (strafe !== 0) controls.object.translateX(strafe * moveMult);
|
|
74
|
-
const up = gamepad
|
|
75
|
-
const down = gamepad
|
|
75
|
+
const up = getGamepadButtonValue(gamepad, buttonMoveUp);
|
|
76
|
+
const down = getGamepadButtonValue(gamepad, buttonMoveDown);
|
|
76
77
|
if (up > deadzone) controls.object.translateY(up * moveMult);
|
|
77
78
|
if (down > deadzone) controls.object.translateY(-down * moveMult);
|
|
78
79
|
}
|
|
79
80
|
#applyLook(deltaTime, gamepad, lookSpeed, deadzone, axisLookX, axisLookY) {
|
|
80
|
-
const lookX =
|
|
81
|
-
const lookY =
|
|
81
|
+
const lookX = applyGamepadDeadzone(gamepad.axes[axisLookX] ?? 0, deadzone);
|
|
82
|
+
const lookY = applyGamepadDeadzone(gamepad.axes[axisLookY] ?? 0, deadzone);
|
|
82
83
|
if (lookX === 0 && lookY === 0) return;
|
|
83
84
|
const controls = this.#controls;
|
|
84
85
|
const actualLookSpeed = controls.lookSpeed * lookSpeed * deltaTime * LOOK_SPEED_SCALE;
|
|
@@ -110,15 +111,6 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
110
111
|
lon: MathUtils.radToDeg(this.#spherical.theta)
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
115
|
-
*
|
|
116
|
-
* @param value - Raw axis or trigger value, typically in `[-1, 1]`.
|
|
117
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
118
|
-
*/
|
|
119
|
-
#applyDeadzone(value, threshold) {
|
|
120
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
121
|
-
}
|
|
122
114
|
};
|
|
123
115
|
//#endregion
|
|
124
116
|
export { GamepadFirstPersonControls };
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
import { Quaternion } from "three";
|
|
4
5
|
//#region src/gamepad-fly-controls.ts
|
|
5
6
|
/**
|
|
@@ -52,32 +53,23 @@ var GamepadFlyControls = class extends GamepadControls {
|
|
|
52
53
|
onUpdate(deltaTime, gamepad) {
|
|
53
54
|
const { moveSpeed, rotateSpeed, deadzone, axisMoveForward, axisMoveRight, axisLookX, axisLookY, buttonRollLeft, buttonRollRight, buttonMoveUp, buttonMoveDown } = this.#options;
|
|
54
55
|
const moveMult = deltaTime * this.#controls.movementSpeed * moveSpeed;
|
|
55
|
-
const fwd =
|
|
56
|
+
const fwd = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
|
|
56
57
|
if (fwd !== 0) this.#controls.object.translateZ(fwd * moveMult);
|
|
57
|
-
const strafe =
|
|
58
|
+
const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
|
|
58
59
|
if (strafe !== 0) this.#controls.object.translateX(strafe * moveMult);
|
|
59
|
-
const up = gamepad
|
|
60
|
-
const down = gamepad
|
|
60
|
+
const up = getGamepadButtonValue(gamepad, buttonMoveUp);
|
|
61
|
+
const down = getGamepadButtonValue(gamepad, buttonMoveDown);
|
|
61
62
|
if (up > deadzone) this.#controls.object.translateY(up * moveMult);
|
|
62
63
|
if (down > deadzone) this.#controls.object.translateY(-down * moveMult);
|
|
63
64
|
const rotMult = deltaTime * this.#controls.rollSpeed * rotateSpeed;
|
|
64
|
-
const pitch = -
|
|
65
|
-
const yaw = -
|
|
66
|
-
const roll = (gamepad
|
|
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);
|
|
67
68
|
if (pitch !== 0 || yaw !== 0 || roll !== 0) {
|
|
68
69
|
this.#tmpQuaternion.set(pitch * rotMult, yaw * rotMult, roll * rotMult, 1).normalize();
|
|
69
70
|
this.#controls.object.quaternion.multiply(this.#tmpQuaternion);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
74
|
-
*
|
|
75
|
-
* @param value - Raw axis or trigger value, typically in `[-1, 1]`.
|
|
76
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
77
|
-
*/
|
|
78
|
-
#applyDeadzone(value, threshold) {
|
|
79
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
80
|
-
}
|
|
81
73
|
};
|
|
82
74
|
//#endregion
|
|
83
75
|
export { GamepadFlyControls };
|
|
@@ -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,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
//#region src/gamepad-orbit-controls.ts
|
|
4
5
|
/**
|
|
5
6
|
* Default options merged in the constructor when no explicit configuration is provided.
|
|
@@ -46,27 +47,18 @@ var GamepadOrbitControls = class extends GamepadControls {
|
|
|
46
47
|
*/
|
|
47
48
|
onUpdate(deltaTime, gamepad) {
|
|
48
49
|
const { rotateSpeed, panSpeed, zoomSpeed, deadzone, axisRotateX, axisRotateY, axisPanX, axisPanY, buttonDollyIn, buttonDollyOut } = this.#options;
|
|
49
|
-
const rotX =
|
|
50
|
-
const rotY =
|
|
50
|
+
const rotX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
|
|
51
|
+
const rotY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
|
|
51
52
|
if (rotX !== 0) this.#controls.rotateLeft(rotX * rotateSpeed * deltaTime * Math.PI);
|
|
52
53
|
if (rotY !== 0) this.#controls.rotateUp(rotY * rotateSpeed * deltaTime * Math.PI);
|
|
53
|
-
const panX =
|
|
54
|
-
const panY =
|
|
54
|
+
const panX = applyGamepadDeadzone(gamepad.axes[axisPanX] ?? 0, deadzone);
|
|
55
|
+
const panY = applyGamepadDeadzone(gamepad.axes[axisPanY] ?? 0, deadzone);
|
|
55
56
|
if (panX !== 0 || panY !== 0) this.#controls.pan(panX * panSpeed * deltaTime * 500, panY * panSpeed * deltaTime * 500);
|
|
56
|
-
const triggerIn = gamepad
|
|
57
|
-
const triggerOut = gamepad
|
|
57
|
+
const triggerIn = getGamepadButtonValue(gamepad, buttonDollyIn);
|
|
58
|
+
const triggerOut = getGamepadButtonValue(gamepad, buttonDollyOut);
|
|
58
59
|
if (triggerIn > deadzone) this.#controls.dollyIn(1 / (1 + zoomSpeed * triggerIn * deltaTime));
|
|
59
60
|
if (triggerOut > deadzone) this.#controls.dollyOut(1 / (1 + zoomSpeed * triggerOut * deltaTime));
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
63
|
-
*
|
|
64
|
-
* @param value - Raw axis or trigger value, typically in `[-1, 1]`.
|
|
65
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
66
|
-
*/
|
|
67
|
-
#applyDeadzone(value, threshold) {
|
|
68
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
69
|
-
}
|
|
70
62
|
};
|
|
71
63
|
//#endregion
|
|
72
64
|
export { GamepadOrbitControls };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GAMEPAD_AXIS } from "./core.js";
|
|
2
2
|
import { GamepadControls } from "./gamepad-controls.js";
|
|
3
|
+
import { applyGamepadDeadzone } from "./utils.js";
|
|
3
4
|
import { Euler } from "three";
|
|
4
5
|
//#region src/gamepad-pointer-lock-controls.ts
|
|
5
6
|
/**
|
|
@@ -47,12 +48,12 @@ var GamepadPointerLockControls = class extends GamepadControls {
|
|
|
47
48
|
*/
|
|
48
49
|
onUpdate(deltaTime, gamepad) {
|
|
49
50
|
const { moveSpeed, lookSpeed, deadzone, axisMoveForward, axisMoveRight, axisLookX, axisLookY } = this.#options;
|
|
50
|
-
const fwd =
|
|
51
|
-
const strafe =
|
|
51
|
+
const fwd = applyGamepadDeadzone(gamepad.axes[axisMoveForward] ?? 0, deadzone);
|
|
52
|
+
const strafe = applyGamepadDeadzone(gamepad.axes[axisMoveRight] ?? 0, deadzone);
|
|
52
53
|
if (fwd !== 0) this.#controls.moveForward(-fwd * moveSpeed * deltaTime);
|
|
53
54
|
if (strafe !== 0) this.#controls.moveRight(strafe * moveSpeed * deltaTime);
|
|
54
|
-
const lookX =
|
|
55
|
-
const lookY =
|
|
55
|
+
const lookX = applyGamepadDeadzone(gamepad.axes[axisLookX] ?? 0, deadzone);
|
|
56
|
+
const lookY = applyGamepadDeadzone(gamepad.axes[axisLookY] ?? 0, deadzone);
|
|
56
57
|
if (lookX !== 0 || lookY !== 0) {
|
|
57
58
|
const camera = this.#controls.object;
|
|
58
59
|
const scale = lookSpeed * this.#controls.pointerSpeed * deltaTime * Math.PI;
|
|
@@ -63,15 +64,6 @@ var GamepadPointerLockControls = class extends GamepadControls {
|
|
|
63
64
|
camera.quaternion.setFromEuler(this.#euler);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
68
|
-
*
|
|
69
|
-
* @param value - Raw axis value, typically in `[-1, 1]`.
|
|
70
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
71
|
-
*/
|
|
72
|
-
#applyDeadzone(value, threshold) {
|
|
73
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
74
|
-
}
|
|
75
67
|
};
|
|
76
68
|
//#endregion
|
|
77
69
|
export { GamepadPointerLockControls };
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
//#region src/gamepad-trackball-controls.ts
|
|
4
5
|
/**
|
|
5
6
|
* Default options merged in the constructor when no explicit configuration is provided.
|
|
@@ -57,8 +58,8 @@ var GamepadTrackballControls = class extends GamepadControls {
|
|
|
57
58
|
controls._lastAngle = 0;
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
|
-
const rotX =
|
|
61
|
-
const rotY =
|
|
61
|
+
const rotX = applyGamepadDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
|
|
62
|
+
const rotY = applyGamepadDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
|
|
62
63
|
if (rotX === 0 && rotY === 0) return;
|
|
63
64
|
const scale = rotateSpeed * deltaTime * Math.PI;
|
|
64
65
|
controls._moveCurr.x += rotX * scale;
|
|
@@ -70,8 +71,8 @@ var GamepadTrackballControls = class extends GamepadControls {
|
|
|
70
71
|
controls._panStart.copy(controls._panEnd);
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
73
|
-
const panX =
|
|
74
|
-
const panY =
|
|
74
|
+
const panX = applyGamepadDeadzone(gamepad.axes[axisPanX] ?? 0, deadzone);
|
|
75
|
+
const panY = applyGamepadDeadzone(gamepad.axes[axisPanY] ?? 0, deadzone);
|
|
75
76
|
if (panX === 0 && panY === 0) return;
|
|
76
77
|
const scale = panSpeed * deltaTime * this.#getInputDampingFactor();
|
|
77
78
|
controls._panEnd.x += panX * scale;
|
|
@@ -83,8 +84,8 @@ var GamepadTrackballControls = class extends GamepadControls {
|
|
|
83
84
|
controls._zoomStart.copy(controls._zoomEnd);
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
|
-
const triggerIn = gamepad
|
|
87
|
-
const triggerOut = gamepad
|
|
87
|
+
const triggerIn = getGamepadButtonValue(gamepad, buttonZoomIn);
|
|
88
|
+
const triggerOut = getGamepadButtonValue(gamepad, buttonZoomOut);
|
|
88
89
|
if (triggerIn <= deadzone && triggerOut <= deadzone) return;
|
|
89
90
|
controls._zoomEnd.y += (triggerOut - triggerIn) * zoomSpeed * deltaTime * this.#getInputDampingFactor();
|
|
90
91
|
}
|
|
@@ -96,15 +97,6 @@ var GamepadTrackballControls = class extends GamepadControls {
|
|
|
96
97
|
const controls = this.#controls;
|
|
97
98
|
return controls.staticMoving ? 1 : controls.dynamicDampingFactor;
|
|
98
99
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
101
|
-
*
|
|
102
|
-
* @param value - Raw axis or trigger value, typically in `[-1, 1]`.
|
|
103
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
104
|
-
*/
|
|
105
|
-
#applyDeadzone(value, threshold) {
|
|
106
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
107
|
-
}
|
|
108
100
|
};
|
|
109
101
|
//#endregion
|
|
110
102
|
export { GamepadTrackballControls };
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
import { Matrix4, Quaternion, Vector2, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-transform-controls.ts
|
|
5
6
|
/**
|
|
@@ -172,8 +173,8 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
172
173
|
this.#endTransform(true);
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
175
|
-
const transformX =
|
|
176
|
-
const transformY =
|
|
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);
|
|
177
178
|
if (transformX === 0 && transformY === 0) {
|
|
178
179
|
this.#endTransform(false);
|
|
179
180
|
return;
|
|
@@ -605,7 +606,7 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
605
606
|
#getStartedButtons(gamepad) {
|
|
606
607
|
const startedButtons = /* @__PURE__ */ new Set();
|
|
607
608
|
for (let index = 0; index < gamepad.buttons.length; index += 1) {
|
|
608
|
-
const pressed = gamepad
|
|
609
|
+
const pressed = getGamepadButtonPressed(gamepad, index);
|
|
609
610
|
const wasPressed = this.#pressedButtons.has(index);
|
|
610
611
|
if (pressed) {
|
|
611
612
|
this.#pressedButtons.add(index);
|
|
@@ -620,15 +621,6 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
620
621
|
#isOrthographicCamera(camera) {
|
|
621
622
|
return camera.isOrthographicCamera === true;
|
|
622
623
|
}
|
|
623
|
-
/**
|
|
624
|
-
* Returns `value` unchanged, or `0` if below the dead zone `threshold`.
|
|
625
|
-
*
|
|
626
|
-
* @param value - Raw axis value, typically in `[-1, 1]`.
|
|
627
|
-
* @param threshold - Dead zone size; values below this magnitude are zeroed.
|
|
628
|
-
*/
|
|
629
|
-
#applyDeadzone(value, threshold) {
|
|
630
|
-
return Math.abs(value) < threshold ? 0 : value;
|
|
631
|
-
}
|
|
632
624
|
};
|
|
633
625
|
//#endregion
|
|
634
626
|
export { GamepadTransformControls };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 };
|
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.10.
|
|
4
|
+
"version": "0.10.6",
|
|
5
5
|
"homepage": "https://github.com/luckasnix/three-gamepad-controls#readme",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Kasnix",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@biomejs/biome": "2.
|
|
49
|
+
"@biomejs/biome": "2.5.0",
|
|
50
50
|
"@commitlint/cli": "21.0.2",
|
|
51
51
|
"@commitlint/config-conventional": "21.0.2",
|
|
52
52
|
"@commitlint/types": "21.0.1",
|
|
53
|
-
"@types/node": "24.
|
|
53
|
+
"@types/node": "24.13.2",
|
|
54
54
|
"@types/three": "0.184.1",
|
|
55
55
|
"husky": "9.1.7",
|
|
56
56
|
"three": "0.184.0",
|