three-gamepad-controls 0.15.0 → 0.16.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.
@@ -44,7 +44,7 @@ type GamepadInputOptions = {
44
44
  gamepadIndex?: number;
45
45
  };
46
46
  /**
47
- * Options for axis and stick reads.
47
+ * Options for axis reads.
48
48
  */
49
49
  type GamepadAxisOptions = {
50
50
  /**
@@ -52,6 +52,24 @@ type GamepadAxisOptions = {
52
52
  */
53
53
  deadzone?: number;
54
54
  };
55
+ /**
56
+ * Options for two-dimensional stick reads.
57
+ */
58
+ type GamepadStickOptions = GamepadAxisOptions & {
59
+ /**
60
+ * Dead zone shape for this read.
61
+ *
62
+ * `"axial"` processes each axis independently, while `"radial"` processes
63
+ * the stick magnitude.
64
+ * @default "axial"
65
+ */
66
+ deadzoneMode?: "axial" | "radial";
67
+ /**
68
+ * Whether to remap values outside the dead zone to the full output range.
69
+ * @default false
70
+ */
71
+ rescale?: boolean;
72
+ };
55
73
  /**
56
74
  * Two-dimensional stick input after dead zone processing.
57
75
  */
@@ -166,10 +184,10 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
166
184
  *
167
185
  * @param xAxis - Horizontal axis index.
168
186
  * @param yAxis - Vertical axis index.
169
- * @param options - Optional per-read axis options.
187
+ * @param options - Optional per-read stick options.
170
188
  * @returns Object containing processed `x` and `y` values.
171
189
  */
172
- stick(xAxis: number, yAxis: number, options?: GamepadAxisOptions): GamepadStick;
190
+ stick(xAxis: number, yAxis: number, options?: GamepadStickOptions): GamepadStick;
173
191
  /**
174
192
  * Plays an effect through the active gamepad's primary vibration actuator.
175
193
  *
@@ -193,4 +211,4 @@ declare class GamepadInput extends EventDispatcher<GamepadInputEventMap> {
193
211
  resetVibration(): Promise<GamepadHapticsResult | null>;
194
212
  }
195
213
  //#endregion
196
- export { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick };
214
+ export { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick, GamepadStickOptions };
@@ -4,17 +4,62 @@ import { EventDispatcher } from "three";
4
4
  //#region src/gamepad-input.ts
5
5
  const DEFAULT_GAMEPAD_INPUT_OPTIONS = { deadzone: .1 };
6
6
  /**
7
- * Returns `value` unchanged, or `0` if below the dead zone `threshold`.
7
+ * Applies a dead zone and remaps the remaining magnitude to `[0, 1]`.
8
8
  *
9
- * Kept private to this module because `GamepadInput` is the only public API
10
- * that currently exposes processed axis values.
9
+ * @param magnitude - Non-negative input magnitude.
10
+ * @param threshold - Dead zone size.
11
+ * @returns Rescaled magnitude, or `0` when inside a fully closed dead zone.
12
+ */
13
+ const rescaleGamepadDeadzoneMagnitude = (magnitude, threshold) => {
14
+ if (magnitude <= threshold || threshold >= 1) return 0;
15
+ return Math.min((magnitude - threshold) / (1 - threshold), 1);
16
+ };
17
+ /**
18
+ * Applies an axial dead zone, optionally rescaling the remaining range.
19
+ *
20
+ * @param value - Raw axis value.
21
+ * @param threshold - Dead zone size.
22
+ * @param rescale - Whether to remap the remaining magnitude to `[0, 1]`.
23
+ * @returns Processed signed axis value.
24
+ */
25
+ const applyGamepadAxialDeadzone = (value, threshold, rescale) => {
26
+ const magnitude = Math.abs(value);
27
+ if (magnitude < threshold) return 0;
28
+ if (!rescale) return value;
29
+ return Math.sign(value) * rescaleGamepadDeadzoneMagnitude(magnitude, threshold);
30
+ };
31
+ /**
32
+ * Returns the stick unchanged, or centered if its magnitude is below the dead
33
+ * zone `threshold`.
11
34
  *
12
- * @param value - Raw axis or trigger value, typically in `[-1, 1]`.
13
- * @param threshold - Dead zone size; values below this magnitude are zeroed.
14
- * @returns The original value when outside the dead zone, otherwise `0`.
35
+ * @param x - Raw horizontal stick value.
36
+ * @param y - Raw vertical stick value.
37
+ * @param threshold - Radial dead zone size.
38
+ * @param rescale - Whether to remap the remaining magnitude to `[0, 1]`.
39
+ * @returns The processed stick, or `{ x: 0, y: 0 }` inside the dead zone.
15
40
  */
16
- const applyGamepadDeadzone = (value, threshold) => {
17
- return Math.abs(value) < threshold ? 0 : value;
41
+ const applyGamepadRadialDeadzone = (x, y, threshold, rescale) => {
42
+ const magnitude = Math.hypot(x, y);
43
+ if (magnitude < threshold) return {
44
+ x: 0,
45
+ y: 0
46
+ };
47
+ if (rescale) {
48
+ const rescaledMagnitude = rescaleGamepadDeadzoneMagnitude(magnitude, threshold);
49
+ if (rescaledMagnitude === 0 || magnitude === 0) return {
50
+ x: 0,
51
+ y: 0
52
+ };
53
+ const scale = rescaledMagnitude / magnitude;
54
+ return {
55
+ x: x * scale,
56
+ y: y * scale
57
+ };
58
+ }
59
+ return {
60
+ x,
61
+ y
62
+ };
18
63
  };
19
64
  /**
20
65
  * Returns whether a gamepad button is currently pressed.
@@ -225,20 +270,25 @@ var GamepadInput = class extends EventDispatcher {
225
270
  */
226
271
  axis(axis, options) {
227
272
  const value = this.#gamepad?.axes[axis] ?? 0;
228
- return applyGamepadDeadzone(value, this.#getDeadzone(options));
273
+ return applyGamepadAxialDeadzone(value, this.#getDeadzone(options), false);
229
274
  }
230
275
  /**
231
276
  * Returns a two-axis stick after dead zone processing.
232
277
  *
233
278
  * @param xAxis - Horizontal axis index.
234
279
  * @param yAxis - Vertical axis index.
235
- * @param options - Optional per-read axis options.
280
+ * @param options - Optional per-read stick options.
236
281
  * @returns Object containing processed `x` and `y` values.
237
282
  */
238
283
  stick(xAxis, yAxis, options) {
284
+ const rescale = options?.rescale ?? false;
285
+ const threshold = this.#getDeadzone(options);
286
+ const x = this.#gamepad?.axes[xAxis] ?? 0;
287
+ const y = this.#gamepad?.axes[yAxis] ?? 0;
288
+ if (options?.deadzoneMode === "radial") return applyGamepadRadialDeadzone(x, y, threshold, rescale);
239
289
  return {
240
- x: this.axis(xAxis, options),
241
- y: this.axis(yAxis, options)
290
+ x: applyGamepadAxialDeadzone(x, threshold, rescale),
291
+ y: applyGamepadAxialDeadzone(y, threshold, rescale)
242
292
  };
243
293
  }
244
294
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, MAX_GAMEPAD_INDEX, MIN_GAMEPAD_INDEX } from "./core.js";
2
- import { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick } from "./gamepad-input.js";
2
+ import { GamepadAxisOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadStick, GamepadStickOptions } from "./gamepad-input.js";
3
3
  import { GamepadControls, GamepadControlsEventMap, GamepadControlsOptions } from "./gamepad-controls.js";
4
4
  import { GamepadArcballControls, GamepadArcballControlsOptions } from "./gamepad-arcball-controls.js";
5
5
  import { GamepadDragControls, GamepadDragControlsOptions } from "./gamepad-drag-controls.js";
@@ -10,4 +10,4 @@ import { GamepadMapControls } from "./gamepad-map-controls.js";
10
10
  import { GamepadPointerLockControls, GamepadPointerLockControlsOptions } from "./gamepad-pointer-lock-controls.js";
11
11
  import { GamepadTrackballControls, GamepadTrackballControlsOptions } from "./gamepad-trackball-controls.js";
12
12
  import { GamepadTransformControls, GamepadTransformControlsOptions } from "./gamepad-transform-controls.js";
13
- export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadArcballControlsOptions, GamepadAxisKey, GamepadAxisOptions, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, GamepadControls, GamepadControlsEventMap, GamepadControlsOptions, GamepadDragControls, GamepadDragControlsOptions, GamepadFirstPersonControls, GamepadFirstPersonControlsOptions, GamepadFlyControls, GamepadFlyControlsOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadMapControls, GamepadOrbitControls, GamepadOrbitControlsOptions, GamepadPointerLockControls, GamepadPointerLockControlsOptions, GamepadStick, GamepadTrackballControls, GamepadTrackballControlsOptions, GamepadTransformControls, GamepadTransformControlsOptions, MAX_GAMEPAD_INDEX, MIN_GAMEPAD_INDEX };
13
+ export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadArcballControlsOptions, GamepadAxisKey, GamepadAxisOptions, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, GamepadControls, GamepadControlsEventMap, GamepadControlsOptions, GamepadDragControls, GamepadDragControlsOptions, GamepadFirstPersonControls, GamepadFirstPersonControlsOptions, GamepadFlyControls, GamepadFlyControlsOptions, GamepadInput, GamepadInputEventMap, GamepadInputOptions, GamepadMapControls, GamepadOrbitControls, GamepadOrbitControlsOptions, GamepadPointerLockControls, GamepadPointerLockControlsOptions, GamepadStick, GamepadStickOptions, GamepadTrackballControls, GamepadTrackballControlsOptions, GamepadTransformControls, GamepadTransformControlsOptions, MAX_GAMEPAD_INDEX, MIN_GAMEPAD_INDEX };
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.15.0",
4
+ "version": "0.16.0",
5
5
  "homepage": "https://github.com/luckasnix/three-gamepad-controls#readme",
6
6
  "author": {
7
7
  "name": "Kasnix",