three-gamepad-controls 0.25.5 → 0.25.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.
@@ -51,6 +51,8 @@ export type GamepadFirstPersonControlsOptions = GamepadControlsOptions & {
51
51
  * and climbing along Y, independent of camera pitch and roll.
52
52
  * Combined gamepad movement is limited to unit magnitude before speed and
53
53
  * forward height gains, preserving smaller analog intentions unchanged.
54
+ * Applied backward movement suppresses only the automatic target for the next
55
+ * native update; native inertia and manual input remain intact.
54
56
  * Bindings and speeds are configurable via {@link GamepadFirstPersonControlsOptions}.
55
57
  */
56
58
  export declare class GamepadFirstPersonControls extends GamepadControls {
@@ -61,6 +63,20 @@ export declare class GamepadFirstPersonControls extends GamepadControls {
61
63
  * Any property not provided falls back to its default value.
62
64
  */
63
65
  constructor(controls: FirstPersonControls, options?: Partial<GamepadFirstPersonControlsOptions>);
66
+ /**
67
+ * Starts a fresh input frame, dropping unconsumed auto-forward suppression.
68
+ * Recursive updates are ignored; failed input frames retain no suppression.
69
+ *
70
+ * @param deltaTime - Seconds since the last frame.
71
+ */
72
+ update(deltaTime: number): void;
73
+ /**
74
+ * Releases this instance's update adapter without disposing the native control.
75
+ * Later application replacements are preserved. Disposal is terminal.
76
+ */
77
+ dispose(): void;
78
+ /** Releases pending suppression before notifying disconnection listeners. */
79
+ protected onGamepadDisconnected(gamepad: Gamepad): void;
64
80
  /**
65
81
  * Maps the current gamepad state to `FirstPersonControls` translation and look.
66
82
  *
@@ -30,11 +30,21 @@ const LOOK_SPEED_SCALE = 36e3;
30
30
  * and climbing along Y, independent of camera pitch and roll.
31
31
  * Combined gamepad movement is limited to unit magnitude before speed and
32
32
  * forward height gains, preserving smaller analog intentions unchanged.
33
+ * Applied backward movement suppresses only the automatic target for the next
34
+ * native update; native inertia and manual input remain intact.
33
35
  * Bindings and speeds are configurable via {@link GamepadFirstPersonControlsOptions}.
34
36
  */
35
37
  var GamepadFirstPersonControls = class extends GamepadControls {
36
38
  #controls;
37
39
  #options;
40
+ #originalUpdate;
41
+ #originalUpdateDescriptor;
42
+ #nativeUpdate;
43
+ #backwardObject = null;
44
+ #backwardGamepad = null;
45
+ #updating = false;
46
+ #nativeUpdating = false;
47
+ #disposed = false;
38
48
  #lookDirection;
39
49
  #spherical;
40
50
  #targetPosition;
@@ -55,6 +65,111 @@ var GamepadFirstPersonControls = class extends GamepadControls {
55
65
  this.#lookDirection = new Vector3();
56
66
  this.#spherical = new Spherical();
57
67
  this.#targetPosition = new Vector3();
68
+ this.#originalUpdate = controls.update;
69
+ this.#originalUpdateDescriptor = Object.getOwnPropertyDescriptor(controls, "update");
70
+ this.#nativeUpdate = (deltaTime) => this.#updateNative(deltaTime);
71
+ controls.update = this.#nativeUpdate;
72
+ }
73
+ /**
74
+ * Starts a fresh input frame, dropping unconsumed auto-forward suppression.
75
+ * Recursive updates are ignored; failed input frames retain no suppression.
76
+ *
77
+ * @param deltaTime - Seconds since the last frame.
78
+ */
79
+ update(deltaTime) {
80
+ if (this.#updating || this.#nativeUpdating || this.#disposed) return;
81
+ this.#clearBackwardInput();
82
+ this.#updating = true;
83
+ try {
84
+ super.update(deltaTime);
85
+ } catch (error) {
86
+ this.#clearBackwardInput();
87
+ throw error;
88
+ } finally {
89
+ this.#updating = false;
90
+ }
91
+ }
92
+ /**
93
+ * Releases this instance's update adapter without disposing the native control.
94
+ * Later application replacements are preserved. Disposal is terminal.
95
+ */
96
+ dispose() {
97
+ if (this.#disposed) return;
98
+ this.#disposed = true;
99
+ this.#clearBackwardInput();
100
+ super.dispose();
101
+ if (this.#controls.update === this.#nativeUpdate) {
102
+ if (this.#originalUpdateDescriptor === void 0) Reflect.deleteProperty(this.#controls, "update");
103
+ else Object.defineProperty(this.#controls, "update", this.#originalUpdateDescriptor);
104
+ }
105
+ }
106
+ /** Releases pending suppression before notifying disconnection listeners. */
107
+ onGamepadDisconnected(gamepad) {
108
+ this.#clearBackwardInput();
109
+ super.onGamepadDisconnected(gamepad);
110
+ }
111
+ /** Clears the destination and snapshot that received backward movement. */
112
+ #clearBackwardInput() {
113
+ this.#backwardObject = null;
114
+ this.#backwardGamepad = null;
115
+ }
116
+ /** Revalidates ownership and permissions after synchronous callbacks. */
117
+ #canApplyInput(gamepad, object) {
118
+ return !this.#disposed && this.enabled && this.#controls.enabled && this.gamepadInput.gamepad === gamepad && this.#controls.object === object;
119
+ }
120
+ /**
121
+ * Consumes one backward frame. Masking only autoForward lets the original
122
+ * update compute manual targets and damp its existing velocity unchanged.
123
+ * A temporary setter preserves assignments, including explicit false writes;
124
+ * conditional restoration also preserves application property replacements.
125
+ */
126
+ #updateNative(deltaTime) {
127
+ if (this.#nativeUpdating || this.#updating) return;
128
+ const controls = this.#controls;
129
+ const gamepad = this.#backwardGamepad;
130
+ const object = this.#backwardObject;
131
+ this.#clearBackwardInput();
132
+ this.#nativeUpdating = true;
133
+ try {
134
+ if (gamepad === null || deltaTime === 0 || !this.#canApplyInput(gamepad, object)) {
135
+ this.#originalUpdate.call(controls, deltaTime);
136
+ return;
137
+ }
138
+ const descriptor = Object.getOwnPropertyDescriptor(controls, "autoForward");
139
+ let value = controls.autoForward;
140
+ let assigned = false;
141
+ let suppress = true;
142
+ const enumerable = descriptor?.enumerable ?? true;
143
+ const get = () => suppress && this.#canApplyInput(gamepad, object) ? false : value;
144
+ const set = (next) => {
145
+ assigned = true;
146
+ value = next;
147
+ descriptor?.set?.call(controls, next);
148
+ };
149
+ Object.defineProperty(controls, "autoForward", {
150
+ configurable: true,
151
+ enumerable,
152
+ get,
153
+ set
154
+ });
155
+ try {
156
+ this.#originalUpdate.call(controls, deltaTime);
157
+ } finally {
158
+ suppress = false;
159
+ const current = Object.getOwnPropertyDescriptor(controls, "autoForward");
160
+ if (current?.get === get && current.set === set && current.configurable === true && current.enumerable === enumerable) {
161
+ if (descriptor === void 0) {
162
+ Reflect.deleteProperty(controls, "autoForward");
163
+ if (assigned) controls.autoForward = value;
164
+ } else Object.defineProperty(controls, "autoForward", "value" in descriptor ? {
165
+ ...descriptor,
166
+ value
167
+ } : descriptor);
168
+ }
169
+ }
170
+ } finally {
171
+ this.#nativeUpdating = false;
172
+ }
58
173
  }
59
174
  /**
60
175
  * Maps the current gamepad state to `FirstPersonControls` translation and look.
@@ -64,8 +179,15 @@ var GamepadFirstPersonControls = class extends GamepadControls {
64
179
  onUpdate(deltaTime) {
65
180
  if (!this.#controls.enabled) return;
66
181
  const { moveSpeed, lookSpeed, moveStick, lookStick, buttonDeadzone, buttonMoveUp, buttonMoveDown } = this.#options;
67
- this.#applyMovement(deltaTime, moveSpeed, moveStick, buttonDeadzone, buttonMoveUp, buttonMoveDown);
68
- this.#applyLook(deltaTime, lookSpeed, lookStick);
182
+ const input = this.gamepadInput;
183
+ const gamepad = input.gamepad;
184
+ const object = this.#controls.object;
185
+ const move = input.stick(moveStick.xAxis, moveStick.yAxis, moveStick.pipeline);
186
+ if (!this.#canApplyInput(gamepad, object)) return;
187
+ const look = input.stick(lookStick.xAxis, lookStick.yAxis, lookStick.pipeline);
188
+ if (!this.#canApplyInput(gamepad, object)) return;
189
+ this.#applyMovement(deltaTime, moveSpeed, move, buttonDeadzone, buttonMoveUp, buttonMoveDown);
190
+ this.#applyLook(deltaTime, lookSpeed, look);
69
191
  }
70
192
  /**
71
193
  * Applies translation in the native keyboard frame, using yaw before look.
@@ -75,15 +197,14 @@ var GamepadFirstPersonControls = class extends GamepadControls {
75
197
  *
76
198
  * @param deltaTime - Seconds since the last frame.
77
199
  * @param moveSpeed - User-configured movement speed multiplier.
78
- * @param moveStick - Resolved movement stick binding.
200
+ * @param move - Processed movement stick input.
79
201
  * @param buttonDeadzone - Analog button dead zone threshold.
80
202
  * @param buttonMoveUp - Button index for upward movement.
81
203
  * @param buttonMoveDown - Button index for downward movement.
82
204
  */
83
- #applyMovement(deltaTime, moveSpeed, moveStick, buttonDeadzone, buttonMoveUp, buttonMoveDown) {
205
+ #applyMovement(deltaTime, moveSpeed, move, buttonDeadzone, buttonMoveUp, buttonMoveDown) {
84
206
  const controls = this.#controls;
85
207
  const input = this.gamepadInput;
86
- const move = input.stick(moveStick.xAxis, moveStick.yAxis, moveStick.pipeline);
87
208
  const up = input.buttonValue(buttonMoveUp);
88
209
  const down = input.buttonValue(buttonMoveDown);
89
210
  let strafe = move.x;
@@ -108,6 +229,10 @@ var GamepadFirstPersonControls = class extends GamepadControls {
108
229
  const strafeDistance = strafe * moveMult;
109
230
  controls.object.position.x += sinYaw * forwardDistance - cosYaw * strafeDistance;
110
231
  controls.object.position.z += cosYaw * forwardDistance + sinYaw * strafeDistance;
232
+ if (forwardDistance < 0) {
233
+ this.#backwardObject = controls.object;
234
+ this.#backwardGamepad = input.gamepad;
235
+ }
111
236
  }
112
237
  if (climb !== 0) controls.object.position.y += climb * moveMult;
113
238
  }
@@ -116,10 +241,10 @@ var GamepadFirstPersonControls = class extends GamepadControls {
116
241
  *
117
242
  * @param deltaTime - Seconds since the last frame.
118
243
  * @param lookSpeed - User-configured look speed multiplier.
119
- * @param lookStick - Resolved camera-look stick binding.
244
+ * @param look - Processed camera-look stick input.
120
245
  */
121
- #applyLook(deltaTime, lookSpeed, lookStick) {
122
- const { x: lookX, y: lookY } = this.gamepadInput.stick(lookStick.xAxis, lookStick.yAxis, lookStick.pipeline);
246
+ #applyLook(deltaTime, lookSpeed, look) {
247
+ const { x: lookX, y: lookY } = look;
123
248
  if (lookX === 0 && lookY === 0) return;
124
249
  const controls = this.#controls;
125
250
  const actualLookSpeed = controls.lookSpeed * lookSpeed * deltaTime * LOOK_SPEED_SCALE;
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.25.5",
4
+ "version": "0.25.6",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/luckasnix/three-gamepad-controls#readme",
7
7
  "author": {