three-gamepad-controls 0.18.0 → 0.19.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 +4 -1
- package/dist/gamepad-arcball-controls.d.ts +10 -19
- package/dist/gamepad-arcball-controls.js +39 -20
- package/dist/gamepad-drag-controls.d.ts +7 -21
- package/dist/gamepad-drag-controls.js +19 -13
- package/dist/gamepad-first-person-controls.d.ts +8 -19
- package/dist/gamepad-first-person-controls.js +29 -24
- package/dist/gamepad-fly-controls.d.ts +7 -18
- package/dist/gamepad-fly-controls.js +24 -13
- package/dist/gamepad-input.d.ts +10 -69
- package/dist/gamepad-input.js +18 -78
- package/dist/gamepad-manager.js +1 -1
- package/dist/gamepad-map-controls.js +17 -5
- package/dist/gamepad-orbit-controls.d.ts +10 -19
- package/dist/gamepad-orbit-controls.js +23 -16
- package/dist/gamepad-pointer-lock-controls.d.ts +7 -21
- package/dist/gamepad-pointer-lock-controls.js +22 -16
- package/dist/gamepad-stick-processing.d.ts +138 -0
- package/dist/gamepad-stick-processing.js +160 -0
- package/dist/gamepad-trackball-controls.d.ts +10 -19
- package/dist/gamepad-trackball-controls.js +34 -33
- package/dist/gamepad-transform-controls.d.ts +4 -13
- package/dist/gamepad-transform-controls.js +12 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@ Gamepad support for [Three.js](https://threejs.org) controls, built on top of [W
|
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
7
|
-

|
|
8
|
+
|
|
9
|
+
The library is organized around a shared input foundation that handles gamepad selection, polling, button states, axes, sticks, haptics, and standard mappings. Applications can consume this input directly for gameplay, menus, and custom interactions, or use `GamepadControls` and its ready-made or custom wrappers to integrate gamepads with Three.js controls.
|
|
8
10
|
|
|
9
11
|
## 📦 Installation
|
|
10
12
|
|
|
@@ -55,6 +57,7 @@ bun add -d @types/three # optional: for TypeScript projects
|
|
|
55
57
|
|
|
56
58
|
- [Core](./docs/core.md) — The fundamental building blocks.
|
|
57
59
|
- [GamepadInput](./docs/gamepad-input.md) - Low-level reader for gamepad buttons, axes, sticks, and transitions.
|
|
60
|
+
- [Gamepad Stick Processing](./docs/gamepad-stick-processing.md) - Stateless processors, pipelines, and action bindings.
|
|
58
61
|
- [Haptic Feedback](./docs/haptic-feedback.md) - Optional gamepad vibration effects with graceful degradation.
|
|
59
62
|
- [Multiple Gamepads](./docs/multiple-gamepads.md) - Assign different gamepads to controls, players, and gameplay inputs.
|
|
60
63
|
- [GamepadControls](./docs/gamepad-controls.md) — Abstract base class for custom gamepad controls.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GamepadStickBindingOptions } from "./gamepad-stick-processing.js";
|
|
1
2
|
import { GamepadControls, GamepadControlsOptions } from "./gamepad-controls.js";
|
|
2
3
|
import { ArcballControls } from "three/addons/controls/ArcballControls.js";
|
|
3
4
|
//#region src/gamepad-arcball-controls.d.ts
|
|
@@ -28,30 +29,20 @@ type GamepadArcballControlsOptions = GamepadControlsOptions & {
|
|
|
28
29
|
*/
|
|
29
30
|
zRotateSpeed: number;
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @default
|
|
33
|
-
*/
|
|
34
|
-
deadzone: number;
|
|
35
|
-
/**
|
|
36
|
-
* Axis index for **horizontal** arcball rotation.
|
|
37
|
-
* @default 0 - Left stick X
|
|
32
|
+
* Stick binding used for arcball rotation.
|
|
33
|
+
* @default Left stick with the default stick pipeline
|
|
38
34
|
*/
|
|
39
|
-
|
|
35
|
+
rotateStick: GamepadStickBindingOptions;
|
|
40
36
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @default
|
|
37
|
+
* Stick binding used for panning.
|
|
38
|
+
* @default Right stick with the default stick pipeline
|
|
43
39
|
*/
|
|
44
|
-
|
|
40
|
+
panStick: GamepadStickBindingOptions;
|
|
45
41
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @default
|
|
48
|
-
*/
|
|
49
|
-
axisPanX: number;
|
|
50
|
-
/**
|
|
51
|
-
* Axis index for **vertical** panning.
|
|
52
|
-
* @default 3 - Right stick Y
|
|
42
|
+
* Dead zone threshold for analog button and trigger values.
|
|
43
|
+
* @default 0.1
|
|
53
44
|
*/
|
|
54
|
-
|
|
45
|
+
buttonDeadzone: number;
|
|
55
46
|
/**
|
|
56
47
|
* Button index for zooming **in** (analog trigger value used for proportional zoom).
|
|
57
48
|
* @default 7 - Right trigger
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
|
|
2
|
+
import { DEFAULT_GAMEPAD_STICK_PIPELINE, resolveGamepadStickBinding } from "./gamepad-stick-processing.js";
|
|
2
3
|
import { GamepadControls } from "./gamepad-controls.js";
|
|
3
4
|
import { Vector2, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-arcball-controls.ts
|
|
@@ -7,11 +8,17 @@ const DEFAULT_ARCBALL_OPTIONS = {
|
|
|
7
8
|
panSpeed: 1,
|
|
8
9
|
zoomSpeed: 1,
|
|
9
10
|
zRotateSpeed: 1,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
rotateStick: {
|
|
12
|
+
xAxis: GAMEPAD_AXIS.LeftX,
|
|
13
|
+
yAxis: GAMEPAD_AXIS.LeftY,
|
|
14
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
15
|
+
},
|
|
16
|
+
panStick: {
|
|
17
|
+
xAxis: GAMEPAD_AXIS.RightX,
|
|
18
|
+
yAxis: GAMEPAD_AXIS.RightY,
|
|
19
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
20
|
+
},
|
|
21
|
+
buttonDeadzone: .1,
|
|
15
22
|
buttonZoomIn: GAMEPAD_BUTTON.RightTrigger,
|
|
16
23
|
buttonZoomOut: GAMEPAD_BUTTON.LeftTrigger,
|
|
17
24
|
buttonZRotateLeft: GAMEPAD_BUTTON.LeftShoulder,
|
|
@@ -47,7 +54,9 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
47
54
|
this.#controls = controls;
|
|
48
55
|
this.#options = {
|
|
49
56
|
...DEFAULT_ARCBALL_OPTIONS,
|
|
50
|
-
...options
|
|
57
|
+
...options,
|
|
58
|
+
rotateStick: resolveGamepadStickBinding(DEFAULT_ARCBALL_OPTIONS.rotateStick, options?.rotateStick),
|
|
59
|
+
panStick: resolveGamepadStickBinding(DEFAULT_ARCBALL_OPTIONS.panStick, options?.panStick)
|
|
51
60
|
};
|
|
52
61
|
this.#centerNdc = new Vector2(0, 0);
|
|
53
62
|
this.#panStart = new Vector3();
|
|
@@ -65,20 +74,30 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
65
74
|
*/
|
|
66
75
|
onUpdate(deltaTime) {
|
|
67
76
|
const controls = this.#controls;
|
|
68
|
-
const { rotateSpeed, panSpeed, zoomSpeed, zRotateSpeed,
|
|
77
|
+
const { rotateSpeed, panSpeed, zoomSpeed, zRotateSpeed, rotateStick, panStick, buttonDeadzone, buttonZoomIn, buttonZoomOut, buttonZRotateLeft, buttonZRotateRight, buttonFocus } = this.#options;
|
|
69
78
|
const input = this.gamepadInput;
|
|
70
79
|
const focusPoint = this.#consumeFocusPoint(buttonFocus);
|
|
71
80
|
if (!controls.enabled) {
|
|
72
81
|
this.#endInteraction();
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
let rotateX = 0;
|
|
85
|
+
let rotateY = 0;
|
|
86
|
+
if (controls.enableRotate) {
|
|
87
|
+
const rotate = input.stick(rotateStick.xAxis, rotateStick.yAxis, rotateStick.pipeline);
|
|
88
|
+
rotateX = rotate.x;
|
|
89
|
+
rotateY = rotate.y;
|
|
90
|
+
}
|
|
91
|
+
let panX = 0;
|
|
92
|
+
let panY = 0;
|
|
93
|
+
if (controls.enablePan) {
|
|
94
|
+
const pan = input.stick(panStick.xAxis, panStick.yAxis, panStick.pipeline);
|
|
95
|
+
panX = pan.x;
|
|
96
|
+
panY = pan.y;
|
|
97
|
+
}
|
|
79
98
|
const zoom = controls.enableZoom ? input.buttonValue(buttonZoomIn) - input.buttonValue(buttonZoomOut) : 0;
|
|
80
99
|
const zRotation = controls.enableRotate ? input.buttonValue(buttonZRotateLeft) - input.buttonValue(buttonZRotateRight) : 0;
|
|
81
|
-
const activeInput = rotateX !== 0 || rotateY !== 0 || panX !== 0 || panY !== 0 || Math.abs(zoom) >
|
|
100
|
+
const activeInput = rotateX !== 0 || rotateY !== 0 || panX !== 0 || panY !== 0 || Math.abs(zoom) > buttonDeadzone || Math.abs(zRotation) > buttonDeadzone;
|
|
82
101
|
if (!activeInput && focusPoint === null) {
|
|
83
102
|
this.#endInteraction();
|
|
84
103
|
return;
|
|
@@ -87,8 +106,8 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
87
106
|
let changed = false;
|
|
88
107
|
changed = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed) || changed;
|
|
89
108
|
changed = this.#applyPan(deltaTime, panX, panY, panSpeed) || changed;
|
|
90
|
-
changed = this.#applyZoom(deltaTime, zoom, zoomSpeed,
|
|
91
|
-
changed = this.#applyZRotation(deltaTime, zRotation, zRotateSpeed,
|
|
109
|
+
changed = this.#applyZoom(deltaTime, zoom, zoomSpeed, buttonDeadzone) || changed;
|
|
110
|
+
changed = this.#applyZRotation(deltaTime, zRotation, zRotateSpeed, buttonDeadzone) || changed;
|
|
92
111
|
changed = this.#applyFocus(focusPoint) || changed;
|
|
93
112
|
if (changed) {
|
|
94
113
|
controls.update();
|
|
@@ -163,11 +182,11 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
163
182
|
* @param deltaTime - Seconds since the last frame.
|
|
164
183
|
* @param zoom - Signed zoom input from the configured trigger pair.
|
|
165
184
|
* @param zoomSpeed - User-configured zoom speed multiplier.
|
|
166
|
-
* @param
|
|
185
|
+
* @param buttonDeadzone - Trigger dead zone threshold.
|
|
167
186
|
* @returns `true` when a zoom transform was applied.
|
|
168
187
|
*/
|
|
169
|
-
#applyZoom(deltaTime, zoom, zoomSpeed,
|
|
170
|
-
if (Math.abs(zoom) <=
|
|
188
|
+
#applyZoom(deltaTime, zoom, zoomSpeed, buttonDeadzone) {
|
|
189
|
+
if (Math.abs(zoom) <= buttonDeadzone || this.#controls.scaleFactor <= 0) return false;
|
|
171
190
|
const controls = this.#controls;
|
|
172
191
|
const size = controls.scaleFactor ** (zoom * zoomSpeed * deltaTime * ZOOM_NOTCHES_PER_SECOND);
|
|
173
192
|
if (!Number.isFinite(size) || size <= 0 || size === 1) return false;
|
|
@@ -180,11 +199,11 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
180
199
|
* @param deltaTime - Seconds since the last frame.
|
|
181
200
|
* @param zRotation - Signed z-rotation input from the configured buttons.
|
|
182
201
|
* @param zRotateSpeed - User-configured z-rotation speed multiplier.
|
|
183
|
-
* @param
|
|
202
|
+
* @param buttonDeadzone - Button value dead zone threshold.
|
|
184
203
|
* @returns `true` when a z-rotation transform was applied.
|
|
185
204
|
*/
|
|
186
|
-
#applyZRotation(deltaTime, zRotation, zRotateSpeed,
|
|
187
|
-
if (Math.abs(zRotation) <=
|
|
205
|
+
#applyZRotation(deltaTime, zRotation, zRotateSpeed, buttonDeadzone) {
|
|
206
|
+
if (Math.abs(zRotation) <= buttonDeadzone) return false;
|
|
188
207
|
const controls = this.#controls;
|
|
189
208
|
const angle = zRotation * zRotateSpeed * deltaTime * Math.PI;
|
|
190
209
|
controls.updateMatrixState();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GamepadStickBindingOptions } from "./gamepad-stick-processing.js";
|
|
1
2
|
import { GamepadControls, GamepadControlsOptions } from "./gamepad-controls.js";
|
|
2
3
|
import { DragControls } from "three/addons/controls/DragControls.js";
|
|
3
4
|
//#region src/gamepad-drag-controls.d.ts
|
|
@@ -18,30 +19,15 @@ type GamepadDragControlsOptions = GamepadControlsOptions & {
|
|
|
18
19
|
*/
|
|
19
20
|
rotateSpeed: number;
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @default
|
|
22
|
+
* Stick binding used for screen-relative dragging.
|
|
23
|
+
* @default Left stick with the default stick pipeline
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
dragStick: GamepadStickBindingOptions;
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @default
|
|
27
|
+
* Stick binding used for object rotation.
|
|
28
|
+
* @default Right stick with the default stick pipeline
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Axis index for **vertical** dragging.
|
|
32
|
-
* @default 1 - Left stick Y
|
|
33
|
-
*/
|
|
34
|
-
axisDragY: number;
|
|
35
|
-
/**
|
|
36
|
-
* Axis index for **horizontal** object rotation.
|
|
37
|
-
* @default 2 - Right stick X
|
|
38
|
-
*/
|
|
39
|
-
axisRotateX: number;
|
|
40
|
-
/**
|
|
41
|
-
* Axis index for **vertical** object rotation.
|
|
42
|
-
* @default 3 - Right stick Y
|
|
43
|
-
*/
|
|
44
|
-
axisRotateY: number;
|
|
30
|
+
rotateStick: GamepadStickBindingOptions;
|
|
45
31
|
/**
|
|
46
32
|
* Button index for grabbing and dropping the object under the center reticle.
|
|
47
33
|
* @default 0 - South face button
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
|
|
2
|
+
import { DEFAULT_GAMEPAD_STICK_PIPELINE, resolveGamepadStickBinding } from "./gamepad-stick-processing.js";
|
|
2
3
|
import { GamepadControls } from "./gamepad-controls.js";
|
|
3
4
|
import { Matrix4, Vector2, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-drag-controls.ts
|
|
5
6
|
const DEFAULT_DRAG_OPTIONS = {
|
|
6
7
|
dragSpeed: 1,
|
|
7
8
|
rotateSpeed: 1,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
dragStick: {
|
|
10
|
+
xAxis: GAMEPAD_AXIS.LeftX,
|
|
11
|
+
yAxis: GAMEPAD_AXIS.LeftY,
|
|
12
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
13
|
+
},
|
|
14
|
+
rotateStick: {
|
|
15
|
+
xAxis: GAMEPAD_AXIS.RightX,
|
|
16
|
+
yAxis: GAMEPAD_AXIS.RightY,
|
|
17
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
18
|
+
},
|
|
13
19
|
buttonSelect: GAMEPAD_BUTTON.South
|
|
14
20
|
};
|
|
15
21
|
/**
|
|
@@ -44,7 +50,9 @@ var GamepadDragControls = class extends GamepadControls {
|
|
|
44
50
|
this.#controls = controls;
|
|
45
51
|
this.#options = {
|
|
46
52
|
...DEFAULT_DRAG_OPTIONS,
|
|
47
|
-
...options
|
|
53
|
+
...options,
|
|
54
|
+
dragStick: resolveGamepadStickBinding(DEFAULT_DRAG_OPTIONS.dragStick, options?.dragStick),
|
|
55
|
+
rotateStick: resolveGamepadStickBinding(DEFAULT_DRAG_OPTIONS.rotateStick, options?.rotateStick)
|
|
48
56
|
};
|
|
49
57
|
this.#centerNdc = new Vector2(0, 0);
|
|
50
58
|
this.#intersections = [];
|
|
@@ -111,14 +119,12 @@ var GamepadDragControls = class extends GamepadControls {
|
|
|
111
119
|
#updateSelected(deltaTime) {
|
|
112
120
|
const selected = this.#selected;
|
|
113
121
|
if (selected === null) return;
|
|
114
|
-
const { dragSpeed, rotateSpeed,
|
|
122
|
+
const { dragSpeed, rotateSpeed, dragStick, rotateStick } = this.#options;
|
|
115
123
|
const input = this.gamepadInput;
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const dragged = this.#applyDrag(deltaTime, dragX, dragY, dragSpeed);
|
|
121
|
-
const rotated = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed);
|
|
124
|
+
const drag = input.stick(dragStick.xAxis, dragStick.yAxis, dragStick.pipeline);
|
|
125
|
+
const rotate = input.stick(rotateStick.xAxis, rotateStick.yAxis, rotateStick.pipeline);
|
|
126
|
+
const dragged = this.#applyDrag(deltaTime, drag.x, drag.y, dragSpeed);
|
|
127
|
+
const rotated = this.#applyRotation(deltaTime, rotate.x, rotate.y, rotateSpeed);
|
|
122
128
|
if (dragged || rotated) this.#controls.dispatchEvent({
|
|
123
129
|
type: "drag",
|
|
124
130
|
object: selected
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GamepadStickBindingOptions } from "./gamepad-stick-processing.js";
|
|
1
2
|
import { GamepadControls, GamepadControlsOptions } from "./gamepad-controls.js";
|
|
2
3
|
import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
|
|
3
4
|
//#region src/gamepad-first-person-controls.d.ts
|
|
@@ -18,30 +19,18 @@ type GamepadFirstPersonControlsOptions = GamepadControlsOptions & {
|
|
|
18
19
|
*/
|
|
19
20
|
lookSpeed: number;
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @default 0.1
|
|
23
|
-
*/
|
|
24
|
-
deadzone: number;
|
|
25
|
-
/**
|
|
26
|
-
* Axis index for **forward / backward** movement.
|
|
27
|
-
* @default 1 - Left stick Y
|
|
22
|
+
* Movement stick axes and processing pipeline.
|
|
28
23
|
*/
|
|
29
|
-
|
|
24
|
+
moveStick: GamepadStickBindingOptions;
|
|
30
25
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @default 0 - Left stick X
|
|
26
|
+
* Camera-look stick axes and processing pipeline.
|
|
33
27
|
*/
|
|
34
|
-
|
|
28
|
+
lookStick: GamepadStickBindingOptions;
|
|
35
29
|
/**
|
|
36
|
-
*
|
|
37
|
-
* @default
|
|
38
|
-
*/
|
|
39
|
-
axisLookX: number;
|
|
40
|
-
/**
|
|
41
|
-
* Axis index for **vertical** camera look (pitch).
|
|
42
|
-
* @default 3 - Right stick Y
|
|
30
|
+
* Dead zone threshold for analog button values.
|
|
31
|
+
* @default 0.1
|
|
43
32
|
*/
|
|
44
|
-
|
|
33
|
+
buttonDeadzone: number;
|
|
45
34
|
/**
|
|
46
35
|
* Button index for **moving up** (analog trigger value used for proportional speed).
|
|
47
36
|
* @default 6 - Left trigger
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
|
|
2
|
+
import { DEFAULT_GAMEPAD_STICK_PIPELINE, resolveGamepadStickBinding } from "./gamepad-stick-processing.js";
|
|
2
3
|
import { GamepadControls } from "./gamepad-controls.js";
|
|
3
4
|
import { MathUtils, Spherical, Vector3 } from "three";
|
|
4
5
|
//#region src/gamepad-first-person-controls.ts
|
|
5
6
|
const DEFAULT_FIRST_PERSON_OPTIONS = {
|
|
6
7
|
moveSpeed: 1,
|
|
7
8
|
lookSpeed: 1,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
moveStick: {
|
|
10
|
+
xAxis: GAMEPAD_AXIS.LeftX,
|
|
11
|
+
yAxis: GAMEPAD_AXIS.LeftY,
|
|
12
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
13
|
+
},
|
|
14
|
+
lookStick: {
|
|
15
|
+
xAxis: GAMEPAD_AXIS.RightX,
|
|
16
|
+
yAxis: GAMEPAD_AXIS.RightY,
|
|
17
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
18
|
+
},
|
|
19
|
+
buttonDeadzone: .1,
|
|
13
20
|
buttonMoveUp: GAMEPAD_BUTTON.LeftTrigger,
|
|
14
21
|
buttonMoveDown: GAMEPAD_BUTTON.RightTrigger
|
|
15
22
|
};
|
|
@@ -37,7 +44,9 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
37
44
|
this.#controls = controls;
|
|
38
45
|
this.#options = {
|
|
39
46
|
...DEFAULT_FIRST_PERSON_OPTIONS,
|
|
40
|
-
...options
|
|
47
|
+
...options,
|
|
48
|
+
moveStick: resolveGamepadStickBinding(DEFAULT_FIRST_PERSON_OPTIONS.moveStick, options?.moveStick),
|
|
49
|
+
lookStick: resolveGamepadStickBinding(DEFAULT_FIRST_PERSON_OPTIONS.lookStick, options?.lookStick)
|
|
41
50
|
};
|
|
42
51
|
this.#lookDirection = new Vector3();
|
|
43
52
|
this.#spherical = new Spherical();
|
|
@@ -49,26 +58,26 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
49
58
|
* @param deltaTime - Seconds since the last frame.
|
|
50
59
|
*/
|
|
51
60
|
onUpdate(deltaTime) {
|
|
52
|
-
const { moveSpeed, lookSpeed,
|
|
53
|
-
this.#applyMovement(deltaTime, moveSpeed,
|
|
54
|
-
this.#applyLook(deltaTime, lookSpeed,
|
|
61
|
+
const { moveSpeed, lookSpeed, moveStick, lookStick, buttonDeadzone, buttonMoveUp, buttonMoveDown } = this.#options;
|
|
62
|
+
this.#applyMovement(deltaTime, moveSpeed, moveStick, buttonDeadzone, buttonMoveUp, buttonMoveDown);
|
|
63
|
+
this.#applyLook(deltaTime, lookSpeed, lookStick);
|
|
55
64
|
}
|
|
56
65
|
/**
|
|
57
66
|
* Applies local translation input to FirstPersonControls' object.
|
|
58
67
|
*
|
|
59
68
|
* @param deltaTime - Seconds since the last frame.
|
|
60
69
|
* @param moveSpeed - User-configured movement speed multiplier.
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
63
|
-
* @param axisMoveRight - Axis index for right and left strafe movement.
|
|
70
|
+
* @param moveStick - Resolved movement stick binding.
|
|
71
|
+
* @param buttonDeadzone - Analog button dead zone threshold.
|
|
64
72
|
* @param buttonMoveUp - Button index for upward movement.
|
|
65
73
|
* @param buttonMoveDown - Button index for downward movement.
|
|
66
74
|
*/
|
|
67
|
-
#applyMovement(deltaTime, moveSpeed,
|
|
75
|
+
#applyMovement(deltaTime, moveSpeed, moveStick, buttonDeadzone, buttonMoveUp, buttonMoveDown) {
|
|
68
76
|
const controls = this.#controls;
|
|
69
77
|
const input = this.gamepadInput;
|
|
70
78
|
const moveMult = deltaTime * controls.movementSpeed * moveSpeed;
|
|
71
|
-
const
|
|
79
|
+
const move = input.stick(moveStick.xAxis, moveStick.yAxis, moveStick.pipeline);
|
|
80
|
+
const forward = move.y;
|
|
72
81
|
if (forward !== 0) {
|
|
73
82
|
let distance = forward * moveMult;
|
|
74
83
|
if (forward < 0 && controls.heightSpeed) {
|
|
@@ -77,26 +86,22 @@ var GamepadFirstPersonControls = class extends GamepadControls {
|
|
|
77
86
|
}
|
|
78
87
|
controls.object.translateZ(distance);
|
|
79
88
|
}
|
|
80
|
-
const strafe =
|
|
89
|
+
const strafe = move.x;
|
|
81
90
|
if (strafe !== 0) controls.object.translateX(strafe * moveMult);
|
|
82
91
|
const up = input.buttonValue(buttonMoveUp);
|
|
83
92
|
const down = input.buttonValue(buttonMoveDown);
|
|
84
|
-
if (up >
|
|
85
|
-
if (down >
|
|
93
|
+
if (up > buttonDeadzone) controls.object.translateY(up * moveMult);
|
|
94
|
+
if (down > buttonDeadzone) controls.object.translateY(-down * moveMult);
|
|
86
95
|
}
|
|
87
96
|
/**
|
|
88
97
|
* Applies camera look input while keeping FirstPersonControls state in sync.
|
|
89
98
|
*
|
|
90
99
|
* @param deltaTime - Seconds since the last frame.
|
|
91
100
|
* @param lookSpeed - User-configured look speed multiplier.
|
|
92
|
-
* @param
|
|
93
|
-
* @param axisLookX - Axis index for yaw input.
|
|
94
|
-
* @param axisLookY - Axis index for pitch input.
|
|
101
|
+
* @param lookStick - Resolved camera-look stick binding.
|
|
95
102
|
*/
|
|
96
|
-
#applyLook(deltaTime, lookSpeed,
|
|
97
|
-
const
|
|
98
|
-
const lookX = input.axis(axisLookX, { deadzone });
|
|
99
|
-
const lookY = input.axis(axisLookY, { deadzone });
|
|
103
|
+
#applyLook(deltaTime, lookSpeed, lookStick) {
|
|
104
|
+
const { x: lookX, y: lookY } = this.gamepadInput.stick(lookStick.xAxis, lookStick.yAxis, lookStick.pipeline);
|
|
100
105
|
if (lookX === 0 && lookY === 0) return;
|
|
101
106
|
const controls = this.#controls;
|
|
102
107
|
const actualLookSpeed = controls.lookSpeed * lookSpeed * deltaTime * LOOK_SPEED_SCALE;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GamepadStickBindingOptions } from "./gamepad-stick-processing.js";
|
|
1
2
|
import { GamepadControls, GamepadControlsOptions } from "./gamepad-controls.js";
|
|
2
3
|
import { FlyControls } from "three/addons/controls/FlyControls.js";
|
|
3
4
|
//#region src/gamepad-fly-controls.d.ts
|
|
@@ -18,30 +19,18 @@ type GamepadFlyControlsOptions = GamepadControlsOptions & {
|
|
|
18
19
|
*/
|
|
19
20
|
rotateSpeed: number;
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
+
* Dead zone threshold for analog button values.
|
|
22
23
|
* @default 0.1
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
buttonDeadzone: number;
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @default 1 — Left stick Y
|
|
27
|
+
* Movement stick axes and processing pipeline.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
moveStick: GamepadStickBindingOptions;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @default 0 — Left stick X
|
|
31
|
+
* Camera-look stick axes and processing pipeline.
|
|
33
32
|
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Axis index for **horizontal** camera look (yaw).
|
|
37
|
-
* @default 2 — Right stick X
|
|
38
|
-
*/
|
|
39
|
-
axisLookX: number;
|
|
40
|
-
/**
|
|
41
|
-
* Axis index for **vertical** camera look (pitch).
|
|
42
|
-
* @default 3 — Right stick Y
|
|
43
|
-
*/
|
|
44
|
-
axisLookY: number;
|
|
33
|
+
lookStick: GamepadStickBindingOptions;
|
|
45
34
|
/**
|
|
46
35
|
* Button index for **rolling left**.
|
|
47
36
|
* @default 4 — Left shoulder
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
|
|
2
|
+
import { DEFAULT_GAMEPAD_STICK_PIPELINE, resolveGamepadStickBinding } from "./gamepad-stick-processing.js";
|
|
2
3
|
import { GamepadControls } from "./gamepad-controls.js";
|
|
3
4
|
import { Quaternion } from "three";
|
|
4
5
|
//#region src/gamepad-fly-controls.ts
|
|
5
6
|
const DEFAULT_FLY_OPTIONS = {
|
|
6
7
|
moveSpeed: 1,
|
|
7
8
|
rotateSpeed: 1,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
buttonDeadzone: .1,
|
|
10
|
+
moveStick: {
|
|
11
|
+
xAxis: GAMEPAD_AXIS.LeftX,
|
|
12
|
+
yAxis: GAMEPAD_AXIS.LeftY,
|
|
13
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
14
|
+
},
|
|
15
|
+
lookStick: {
|
|
16
|
+
xAxis: GAMEPAD_AXIS.RightX,
|
|
17
|
+
yAxis: GAMEPAD_AXIS.RightY,
|
|
18
|
+
pipeline: DEFAULT_GAMEPAD_STICK_PIPELINE
|
|
19
|
+
},
|
|
13
20
|
buttonRollLeft: GAMEPAD_BUTTON.LeftShoulder,
|
|
14
21
|
buttonRollRight: GAMEPAD_BUTTON.RightShoulder,
|
|
15
22
|
buttonMoveUp: GAMEPAD_BUTTON.LeftTrigger,
|
|
@@ -36,7 +43,9 @@ var GamepadFlyControls = class extends GamepadControls {
|
|
|
36
43
|
this.#controls = controls;
|
|
37
44
|
this.#options = {
|
|
38
45
|
...DEFAULT_FLY_OPTIONS,
|
|
39
|
-
...options
|
|
46
|
+
...options,
|
|
47
|
+
moveStick: resolveGamepadStickBinding(DEFAULT_FLY_OPTIONS.moveStick, options?.moveStick),
|
|
48
|
+
lookStick: resolveGamepadStickBinding(DEFAULT_FLY_OPTIONS.lookStick, options?.lookStick)
|
|
40
49
|
};
|
|
41
50
|
this.#tmpQuaternion = new Quaternion();
|
|
42
51
|
}
|
|
@@ -46,20 +55,22 @@ var GamepadFlyControls = class extends GamepadControls {
|
|
|
46
55
|
* @param deltaTime - Seconds since the last frame.
|
|
47
56
|
*/
|
|
48
57
|
onUpdate(deltaTime) {
|
|
49
|
-
const { moveSpeed, rotateSpeed,
|
|
58
|
+
const { moveSpeed, rotateSpeed, buttonDeadzone, moveStick, lookStick, buttonRollLeft, buttonRollRight, buttonMoveUp, buttonMoveDown } = this.#options;
|
|
50
59
|
const input = this.gamepadInput;
|
|
60
|
+
const move = input.stick(moveStick.xAxis, moveStick.yAxis, moveStick.pipeline);
|
|
61
|
+
const look = input.stick(lookStick.xAxis, lookStick.yAxis, lookStick.pipeline);
|
|
51
62
|
const moveMult = deltaTime * this.#controls.movementSpeed * moveSpeed;
|
|
52
|
-
const fwd =
|
|
63
|
+
const fwd = move.y;
|
|
53
64
|
if (fwd !== 0) this.#controls.object.translateZ(fwd * moveMult);
|
|
54
|
-
const strafe =
|
|
65
|
+
const strafe = move.x;
|
|
55
66
|
if (strafe !== 0) this.#controls.object.translateX(strafe * moveMult);
|
|
56
67
|
const up = input.buttonValue(buttonMoveUp);
|
|
57
68
|
const down = input.buttonValue(buttonMoveDown);
|
|
58
|
-
if (up >
|
|
59
|
-
if (down >
|
|
69
|
+
if (up > buttonDeadzone) this.#controls.object.translateY(up * moveMult);
|
|
70
|
+
if (down > buttonDeadzone) this.#controls.object.translateY(-down * moveMult);
|
|
60
71
|
const rotMult = deltaTime * this.#controls.rollSpeed * rotateSpeed;
|
|
61
|
-
const pitch = -
|
|
62
|
-
const yaw = -
|
|
72
|
+
const pitch = -look.y;
|
|
73
|
+
const yaw = -look.x;
|
|
63
74
|
const roll = (input.isPressed(buttonRollLeft) ? 1 : 0) - (input.isPressed(buttonRollRight) ? 1 : 0);
|
|
64
75
|
if (pitch !== 0 || yaw !== 0 || roll !== 0) {
|
|
65
76
|
this.#tmpQuaternion.set(pitch * rotMult, yaw * rotMult, roll * rotMult, 1).normalize();
|