three-gamepad-controls 0.24.1 → 0.24.3
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.d.ts +22 -2
- package/dist/gamepad-arcball-controls.js +145 -75
- package/dist/gamepad-map-controls.d.ts +2 -0
- package/dist/gamepad-map-controls.js +2 -0
- package/dist/gamepad-orbit-controls.d.ts +27 -5
- package/dist/gamepad-orbit-controls.js +134 -22
- package/dist/gamepad-pointer-lock-controls.d.ts +13 -0
- package/dist/gamepad-pointer-lock-controls.js +30 -3
- package/dist/gamepad-trackball-controls.d.ts +24 -2
- package/dist/gamepad-trackball-controls.js +107 -50
- package/dist/gamepad-transform-controls.js +227 -59
- package/package.json +1 -1
|
@@ -75,6 +75,8 @@ export type GamepadArcballControlsOptions = GamepadControlsOptions & {
|
|
|
75
75
|
* Call `update()` inside the render loop to poll gamepad input and apply
|
|
76
76
|
* Arcball transformations. The wrapped `ArcballControls.update()` is only
|
|
77
77
|
* needed after manual camera or target changes, matching Arcball's native API.
|
|
78
|
+
* Gamepad `start`, `change`, and `end` events are dispatched on the native controls.
|
|
79
|
+
* The balanced session belongs to this wrapper, including isolated focus commands.
|
|
78
80
|
*/
|
|
79
81
|
export declare class GamepadArcballControls extends GamepadControls {
|
|
80
82
|
#private;
|
|
@@ -85,13 +87,31 @@ export declare class GamepadArcballControls extends GamepadControls {
|
|
|
85
87
|
*/
|
|
86
88
|
constructor(controls: ArcballControls, options?: Partial<GamepadArcballControlsOptions>);
|
|
87
89
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
+
* Polls and applies gamepad input, ignoring updates from synchronous native listeners.
|
|
91
|
+
* Pausing through `enabled` retains the owned interaction until input resumes,
|
|
92
|
+
* the gamepad disconnects, or the wrapper is disposed.
|
|
90
93
|
*
|
|
91
94
|
* @param deltaTime - Seconds since the last frame.
|
|
92
95
|
*/
|
|
96
|
+
update(deltaTime: number): void;
|
|
97
|
+
/**
|
|
98
|
+
* Reads each binding once and applies accepted Arcball transforms in one session.
|
|
99
|
+
* Revalidates the cached frame after `start` and `change`; an isolated focus
|
|
100
|
+
* command ends immediately, while continuous input keeps the session open.
|
|
101
|
+
*
|
|
102
|
+
* @param deltaTime - Seconds elapsed for this input frame.
|
|
103
|
+
*/
|
|
93
104
|
protected onUpdate(deltaTime: number): void;
|
|
105
|
+
/**
|
|
106
|
+
* Removes gamepad listeners and ends only this wrapper's active interaction.
|
|
107
|
+
* Repeated calls are safe; the native Arcball instance is not disposed.
|
|
108
|
+
*/
|
|
94
109
|
dispose(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Ends the owned interaction before forwarding the active gamepad's disconnection.
|
|
112
|
+
*
|
|
113
|
+
* @param gamepad - The gamepad that just disconnected.
|
|
114
|
+
*/
|
|
95
115
|
protected onGamepadDisconnected(gamepad: Gamepad): void;
|
|
96
116
|
}
|
|
97
117
|
//#endregion
|
|
@@ -32,6 +32,8 @@ const ZOOM_NOTCHES_PER_SECOND = 8;
|
|
|
32
32
|
* Call `update()` inside the render loop to poll gamepad input and apply
|
|
33
33
|
* Arcball transformations. The wrapped `ArcballControls.update()` is only
|
|
34
34
|
* needed after manual camera or target changes, matching Arcball's native API.
|
|
35
|
+
* Gamepad `start`, `change`, and `end` events are dispatched on the native controls.
|
|
36
|
+
* The balanced session belongs to this wrapper, including isolated focus commands.
|
|
35
37
|
*/
|
|
36
38
|
var GamepadArcballControls = class extends GamepadControls {
|
|
37
39
|
#controls;
|
|
@@ -43,7 +45,12 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
43
45
|
#cameraForward;
|
|
44
46
|
#cameraRight;
|
|
45
47
|
#previousUp;
|
|
48
|
+
/** Whether this wrapper owns an active gamepad interaction. */
|
|
46
49
|
#wasInteracting = false;
|
|
50
|
+
/** Blocks recursive updates while a frame is being processed. */
|
|
51
|
+
#updating = false;
|
|
52
|
+
/** Blocks recursive updates while the owned interaction is ending. */
|
|
53
|
+
#ending = false;
|
|
47
54
|
/**
|
|
48
55
|
* @param controls - A Three.js `ArcballControls` instance.
|
|
49
56
|
* @param options - Optional overrides for the default behavior.
|
|
@@ -67,64 +74,134 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
67
74
|
this.#previousUp = new Vector3();
|
|
68
75
|
}
|
|
69
76
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
77
|
+
* Polls and applies gamepad input, ignoring updates from synchronous native listeners.
|
|
78
|
+
* Pausing through `enabled` retains the owned interaction until input resumes,
|
|
79
|
+
* the gamepad disconnects, or the wrapper is disposed.
|
|
72
80
|
*
|
|
73
81
|
* @param deltaTime - Seconds since the last frame.
|
|
74
82
|
*/
|
|
83
|
+
update(deltaTime) {
|
|
84
|
+
if (this.#updating || this.#ending) return;
|
|
85
|
+
this.#updating = true;
|
|
86
|
+
try {
|
|
87
|
+
super.update(deltaTime);
|
|
88
|
+
} finally {
|
|
89
|
+
this.#updating = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reads each binding once and applies accepted Arcball transforms in one session.
|
|
94
|
+
* Revalidates the cached frame after `start` and `change`; an isolated focus
|
|
95
|
+
* command ends immediately, while continuous input keeps the session open.
|
|
96
|
+
*
|
|
97
|
+
* @param deltaTime - Seconds elapsed for this input frame.
|
|
98
|
+
*/
|
|
75
99
|
onUpdate(deltaTime) {
|
|
76
|
-
const controls = this.#controls;
|
|
77
|
-
const { rotateSpeed, panSpeed, zoomSpeed, zRotateSpeed, rotateStick, panStick, buttonDeadzone, buttonZoomIn, buttonZoomOut, buttonZRotateLeft, buttonZRotateRight, buttonFocus } = this.#options;
|
|
78
|
-
const input = this.gamepadInput;
|
|
79
|
-
const focusPoint = this.#consumeFocusPoint(buttonFocus);
|
|
80
100
|
if (!this.#canApplyInput()) return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const activeInput = rotateX !== 0 || rotateY !== 0 || panX !== 0 || panY !== 0 || Math.abs(zoom) > buttonDeadzone || Math.abs(zRotation) > buttonDeadzone;
|
|
98
|
-
if (!activeInput && focusPoint === null) {
|
|
99
|
-
this.#endInteraction();
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
101
|
+
const { rotateStick, panStick, buttonZoomIn, buttonZoomOut, buttonZRotateLeft, buttonZRotateRight, buttonFocus } = this.#options;
|
|
102
|
+
const input = this.gamepadInput;
|
|
103
|
+
const rotate = input.stick(rotateStick.xAxis, rotateStick.yAxis, rotateStick.pipeline);
|
|
104
|
+
const pan = input.stick(panStick.xAxis, panStick.yAxis, panStick.pipeline);
|
|
105
|
+
const frame = {
|
|
106
|
+
rotateX: rotate.x,
|
|
107
|
+
rotateY: rotate.y,
|
|
108
|
+
panX: pan.x,
|
|
109
|
+
panY: pan.y,
|
|
110
|
+
zoom: input.buttonValue(buttonZoomIn) - input.buttonValue(buttonZoomOut),
|
|
111
|
+
zRotation: input.buttonValue(buttonZRotateLeft) - input.buttonValue(buttonZRotateRight),
|
|
112
|
+
focus: this.#consumeFocusPoint(buttonFocus)
|
|
113
|
+
};
|
|
114
|
+
let actions = this.#acceptActions(frame, deltaTime);
|
|
115
|
+
if (actions === null) return;
|
|
116
|
+
const controls = this.#controls;
|
|
102
117
|
if (!this.#wasInteracting) {
|
|
103
118
|
this.#wasInteracting = true;
|
|
104
119
|
controls.dispatchEvent({ type: "start" });
|
|
120
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
121
|
+
if (actions === null) return;
|
|
105
122
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (controls.enableRotate) changed = this.#applyZRotation(deltaTime, zRotation, zRotateSpeed, buttonDeadzone) || changed;
|
|
112
|
-
if (controls.enablePan && controls.enableFocus) changed = this.#applyFocus(focusPoint) || changed;
|
|
123
|
+
let changed = this.#applyRotation(actions.rotateX, actions.rotateY);
|
|
124
|
+
changed = this.#applyPan(actions.panX, actions.panY) || changed;
|
|
125
|
+
changed = this.#applyZoom(actions.zoomSize) || changed;
|
|
126
|
+
changed = this.#applyZRotation(actions.zRotation) || changed;
|
|
127
|
+
changed = this.#applyFocus(actions.focus) || changed;
|
|
113
128
|
if (changed) {
|
|
114
129
|
controls.update();
|
|
115
130
|
controls.updateMatrixState();
|
|
116
131
|
controls.dispatchEvent({ type: "change" });
|
|
117
132
|
}
|
|
118
|
-
|
|
133
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
134
|
+
if (actions !== null && !this.#hasContinuousInput(actions)) this.#endInteraction();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Resolves a cached frame against current permissions, gains, and zoom validity.
|
|
138
|
+
* Zero deltas and invalid or neutral zoom factors do not sustain an interaction.
|
|
139
|
+
* Ends the owned session when neither continuous input nor focus remains accepted.
|
|
140
|
+
*
|
|
141
|
+
* @param frame - Already processed input and the focus point resolved for this frame.
|
|
142
|
+
* @param delta - Frame duration in seconds.
|
|
143
|
+
* @returns Accepted deltas and focus, or `null` when input application must stop.
|
|
144
|
+
*/
|
|
145
|
+
#acceptActions(frame, delta) {
|
|
146
|
+
if (!this.#canApplyInput()) return null;
|
|
147
|
+
const controls = this.#controls;
|
|
148
|
+
const options = this.#options;
|
|
149
|
+
const rotate = controls.enableRotate ? controls.rotateSpeed * options.rotateSpeed * delta * Math.PI : 0;
|
|
150
|
+
const pan = controls.enablePan ? controls._tbRadius * options.panSpeed * delta : 0;
|
|
151
|
+
const zRotation = controls.enableRotate && Math.abs(frame.zRotation) > options.buttonDeadzone ? frame.zRotation * options.zRotateSpeed * delta * Math.PI : 0;
|
|
152
|
+
let zoomSize = 1;
|
|
153
|
+
if (controls.enableZoom && Math.abs(frame.zoom) > options.buttonDeadzone && controls.scaleFactor > 0) {
|
|
154
|
+
const size = controls.scaleFactor ** (frame.zoom * options.zoomSpeed * delta * ZOOM_NOTCHES_PER_SECOND);
|
|
155
|
+
if (Number.isFinite(size) && size > 0) zoomSize = size;
|
|
156
|
+
}
|
|
157
|
+
const actions = {
|
|
158
|
+
rotateX: frame.rotateX * rotate,
|
|
159
|
+
rotateY: frame.rotateY * rotate,
|
|
160
|
+
panX: frame.panX * pan,
|
|
161
|
+
panY: frame.panY * pan,
|
|
162
|
+
zoomSize,
|
|
163
|
+
zRotation,
|
|
164
|
+
focus: controls.enablePan && controls.enableFocus ? frame.focus : null
|
|
165
|
+
};
|
|
166
|
+
if (!this.#hasContinuousInput(actions) && actions.focus === null) {
|
|
167
|
+
this.#endInteraction();
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return actions;
|
|
119
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Checks whether accepted movement can sustain a session, excluding one-shot focus.
|
|
174
|
+
*
|
|
175
|
+
* @param actions - Frame deltas already resolved against current permissions and gains.
|
|
176
|
+
* @returns `true` when any rotation, pan, or zoom delta is non-neutral.
|
|
177
|
+
*/
|
|
178
|
+
#hasContinuousInput(actions) {
|
|
179
|
+
return actions.rotateX !== 0 || actions.rotateY !== 0 || actions.panX !== 0 || actions.panY !== 0 || actions.zoomSize !== 1 || actions.zRotation !== 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Removes gamepad listeners and ends only this wrapper's active interaction.
|
|
183
|
+
* Repeated calls are safe; the native Arcball instance is not disposed.
|
|
184
|
+
*/
|
|
120
185
|
dispose() {
|
|
121
186
|
super.dispose();
|
|
122
187
|
this.#endInteraction();
|
|
123
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Ends the owned interaction before forwarding the active gamepad's disconnection.
|
|
191
|
+
*
|
|
192
|
+
* @param gamepad - The gamepad that just disconnected.
|
|
193
|
+
*/
|
|
124
194
|
onGamepadDisconnected(gamepad) {
|
|
125
195
|
this.#endInteraction();
|
|
126
196
|
super.onGamepadDisconnected(gamepad);
|
|
127
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Checks whether input remains applicable after a synchronous native callback.
|
|
200
|
+
* Native disable ends the owned session; a wrapper pause retains it until resume,
|
|
201
|
+
* disconnection, or disposal.
|
|
202
|
+
*
|
|
203
|
+
* @returns `true` when the wrapper and native controls are enabled and a gamepad is available.
|
|
204
|
+
*/
|
|
128
205
|
#canApplyInput() {
|
|
129
206
|
if (!this.#controls.enabled) {
|
|
130
207
|
this.#endInteraction();
|
|
@@ -133,27 +210,23 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
133
210
|
return this.enabled && this.gamepad !== null;
|
|
134
211
|
}
|
|
135
212
|
/**
|
|
136
|
-
* Applies
|
|
213
|
+
* Applies accepted angular deltas through native rotation helpers.
|
|
137
214
|
*
|
|
138
|
-
* @param
|
|
139
|
-
* @param
|
|
140
|
-
* @
|
|
141
|
-
* @param rotateSpeed - User-configured rotation speed multiplier.
|
|
142
|
-
* @returns `true` when a rotation was applied.
|
|
215
|
+
* @param rotateX - Horizontal rotation delta in radians.
|
|
216
|
+
* @param rotateY - Vertical rotation delta in radians.
|
|
217
|
+
* @returns `true` when at least one rotation transform was applied.
|
|
143
218
|
*/
|
|
144
|
-
#applyRotation(
|
|
145
|
-
if (rotateX === 0 && rotateY === 0) return false;
|
|
219
|
+
#applyRotation(rotateX, rotateY) {
|
|
146
220
|
const controls = this.#controls;
|
|
147
|
-
const amount = controls.rotateSpeed * rotateSpeed * deltaTime * Math.PI;
|
|
148
221
|
let changed = false;
|
|
149
222
|
if (rotateX !== 0) {
|
|
150
223
|
this.#rotationAxis.copy(controls.object.up).normalize();
|
|
151
|
-
changed = this.#applyRotationAroundAxis(this.#rotationAxis, rotateX
|
|
224
|
+
changed = this.#applyRotationAroundAxis(this.#rotationAxis, rotateX);
|
|
152
225
|
}
|
|
153
226
|
if (rotateY !== 0) {
|
|
154
227
|
controls.object.getWorldDirection(this.#cameraForward);
|
|
155
228
|
this.#cameraRight.crossVectors(this.#cameraForward, controls.object.up).normalize();
|
|
156
|
-
changed = this.#applyRotationAroundAxis(this.#cameraRight, -rotateY
|
|
229
|
+
changed = this.#applyRotationAroundAxis(this.#cameraRight, -rotateY) || changed;
|
|
157
230
|
}
|
|
158
231
|
return changed;
|
|
159
232
|
}
|
|
@@ -174,38 +247,30 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
174
247
|
return true;
|
|
175
248
|
}
|
|
176
249
|
/**
|
|
177
|
-
* Applies
|
|
250
|
+
* Applies accepted pan deltas between two virtual trackball points.
|
|
178
251
|
*
|
|
179
|
-
* @param
|
|
180
|
-
* @param
|
|
181
|
-
* @
|
|
182
|
-
* @param panSpeed - User-configured pan speed multiplier.
|
|
183
|
-
* @returns `true` when a pan transform was applied.
|
|
252
|
+
* @param panX - Horizontal displacement in Arcball's virtual trackball space.
|
|
253
|
+
* @param panY - Vertical displacement in Arcball's virtual trackball space.
|
|
254
|
+
* @returns `true` when a nonzero pan transform was applied.
|
|
184
255
|
*/
|
|
185
|
-
#applyPan(
|
|
256
|
+
#applyPan(panX, panY) {
|
|
186
257
|
if (panX === 0 && panY === 0) return false;
|
|
187
258
|
const controls = this.#controls;
|
|
188
|
-
const distance = controls._tbRadius * panSpeed * deltaTime;
|
|
189
259
|
controls.updateMatrixState();
|
|
190
260
|
this.#panStart.set(0, 0, 0);
|
|
191
|
-
this.#panEnd.set(panX
|
|
261
|
+
this.#panEnd.set(panX, panY, 0);
|
|
192
262
|
this.#applyTransform(controls.pan(this.#panStart, this.#panEnd));
|
|
193
263
|
return true;
|
|
194
264
|
}
|
|
195
265
|
/**
|
|
196
|
-
* Applies
|
|
266
|
+
* Applies an accepted zoom factor around the native gizmo center.
|
|
197
267
|
*
|
|
198
|
-
* @param
|
|
199
|
-
* @
|
|
200
|
-
* @param zoomSpeed - User-configured zoom speed multiplier.
|
|
201
|
-
* @param buttonDeadzone - Trigger dead zone threshold.
|
|
202
|
-
* @returns `true` when a zoom transform was applied.
|
|
268
|
+
* @param size - Positive finite scale factor; `1` leaves zoom unchanged.
|
|
269
|
+
* @returns `true` when the factor is non-neutral and the native helper returns a transform.
|
|
203
270
|
*/
|
|
204
|
-
#applyZoom(
|
|
205
|
-
if (
|
|
271
|
+
#applyZoom(size) {
|
|
272
|
+
if (size === 1) return false;
|
|
206
273
|
const controls = this.#controls;
|
|
207
|
-
const size = controls.scaleFactor ** (zoom * zoomSpeed * deltaTime * ZOOM_NOTCHES_PER_SECOND);
|
|
208
|
-
if (!Number.isFinite(size) || size <= 0 || size === 1) return false;
|
|
209
274
|
controls.updateMatrixState();
|
|
210
275
|
const transformation = controls.scale(size, controls._gizmos.position);
|
|
211
276
|
if (transformation === void 0) return false;
|
|
@@ -213,18 +278,14 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
213
278
|
return true;
|
|
214
279
|
}
|
|
215
280
|
/**
|
|
216
|
-
* Applies
|
|
281
|
+
* Applies accepted rotation around the view axis and updates the camera's up vector.
|
|
217
282
|
*
|
|
218
|
-
* @param
|
|
219
|
-
* @
|
|
220
|
-
* @param zRotateSpeed - User-configured z-rotation speed multiplier.
|
|
221
|
-
* @param buttonDeadzone - Button value dead zone threshold.
|
|
222
|
-
* @returns `true` when a z-rotation transform was applied.
|
|
283
|
+
* @param angle - Rotation delta in radians.
|
|
284
|
+
* @returns `true` when a nonzero rotation transform was applied.
|
|
223
285
|
*/
|
|
224
|
-
#applyZRotation(
|
|
225
|
-
if (
|
|
286
|
+
#applyZRotation(angle) {
|
|
287
|
+
if (angle === 0) return false;
|
|
226
288
|
const controls = this.#controls;
|
|
227
|
-
const angle = zRotation * zRotateSpeed * deltaTime * Math.PI;
|
|
228
289
|
controls.updateMatrixState();
|
|
229
290
|
controls.object.getWorldDirection(controls._rotationAxis);
|
|
230
291
|
this.#previousUp.copy(controls.object.up);
|
|
@@ -266,10 +327,19 @@ var GamepadArcballControls = class extends GamepadControls {
|
|
|
266
327
|
if (!this.gamepadInput.wasPressed(buttonFocus) || !controls.enabled || !controls.enablePan || !controls.enableFocus || controls.scene === null) return null;
|
|
267
328
|
return controls.unprojectOnObj(this.#centerNdc, controls.object);
|
|
268
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Releases session ownership before dispatching the native `end` event once.
|
|
332
|
+
* Blocks recursive updates during finalization, including disconnection and disposal.
|
|
333
|
+
*/
|
|
269
334
|
#endInteraction() {
|
|
270
335
|
if (!this.#wasInteracting) return;
|
|
271
336
|
this.#wasInteracting = false;
|
|
272
|
-
this.#
|
|
337
|
+
this.#ending = true;
|
|
338
|
+
try {
|
|
339
|
+
this.#controls.dispatchEvent({ type: "end" });
|
|
340
|
+
} finally {
|
|
341
|
+
this.#ending = false;
|
|
342
|
+
}
|
|
273
343
|
}
|
|
274
344
|
};
|
|
275
345
|
//#endregion
|
|
@@ -8,6 +8,8 @@ import { MapControls } from "three/addons/controls/MapControls.js";
|
|
|
8
8
|
* mouse conventions. All options from {@link GamepadOrbitControlsOptions} are available.
|
|
9
9
|
*
|
|
10
10
|
* Call `update()` inside the render loop **before** `MapControls.update()`.
|
|
11
|
+
* Gamepad interaction events, pause semantics, and combined native/wrapper speeds
|
|
12
|
+
* are inherited from {@link GamepadOrbitControls}.
|
|
11
13
|
*/
|
|
12
14
|
export declare class GamepadMapControls extends GamepadOrbitControls {
|
|
13
15
|
/**
|
|
@@ -18,6 +18,8 @@ const DEFAULT_MAP_OPTIONS = {
|
|
|
18
18
|
* mouse conventions. All options from {@link GamepadOrbitControlsOptions} are available.
|
|
19
19
|
*
|
|
20
20
|
* Call `update()` inside the render loop **before** `MapControls.update()`.
|
|
21
|
+
* Gamepad interaction events, pause semantics, and combined native/wrapper speeds
|
|
22
|
+
* are inherited from {@link GamepadOrbitControls}.
|
|
21
23
|
*/
|
|
22
24
|
var GamepadMapControls = class extends GamepadOrbitControls {
|
|
23
25
|
/**
|
|
@@ -9,17 +9,17 @@ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export type GamepadOrbitControlsOptions = GamepadControlsOptions & {
|
|
11
11
|
/**
|
|
12
|
-
* Multiplier on
|
|
12
|
+
* Multiplier on `OrbitControls.rotateSpeed`.
|
|
13
13
|
* @default 1.0
|
|
14
14
|
*/
|
|
15
15
|
rotateSpeed: number;
|
|
16
16
|
/**
|
|
17
|
-
* Multiplier on
|
|
17
|
+
* Multiplier on `OrbitControls.panSpeed`.
|
|
18
18
|
* @default 1.0
|
|
19
19
|
*/
|
|
20
20
|
panSpeed: number;
|
|
21
21
|
/**
|
|
22
|
-
* Multiplier on
|
|
22
|
+
* Multiplier on `OrbitControls.zoomSpeed`.
|
|
23
23
|
* @default 1.0
|
|
24
24
|
*/
|
|
25
25
|
zoomSpeed: number;
|
|
@@ -34,7 +34,7 @@ export type GamepadOrbitControlsOptions = GamepadControlsOptions & {
|
|
|
34
34
|
*/
|
|
35
35
|
panStick: GamepadStickBindingOptions;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Each analog trigger must be strictly above this threshold to drive dolly.
|
|
38
38
|
* @default 0.1
|
|
39
39
|
*/
|
|
40
40
|
buttonDeadzone: number;
|
|
@@ -54,6 +54,8 @@ export type GamepadOrbitControlsOptions = GamepadControlsOptions & {
|
|
|
54
54
|
*
|
|
55
55
|
* Call `update()` inside the render loop **before** `OrbitControls.update()`.
|
|
56
56
|
* Bindings and speeds are configurable via {@link GamepadOrbitControlsOptions}.
|
|
57
|
+
* The wrapper dispatches balanced gamepad `start` and `end` events on the native
|
|
58
|
+
* controls; native operations and damping remain responsible for `change`.
|
|
57
59
|
*/
|
|
58
60
|
export declare class GamepadOrbitControls extends GamepadControls {
|
|
59
61
|
#private;
|
|
@@ -64,10 +66,30 @@ export declare class GamepadOrbitControls extends GamepadControls {
|
|
|
64
66
|
*/
|
|
65
67
|
constructor(controls: OrbitControls, options?: Partial<GamepadOrbitControlsOptions>);
|
|
66
68
|
/**
|
|
67
|
-
*
|
|
69
|
+
* Polls the gamepad and applies input, ignoring updates from synchronous listeners.
|
|
70
|
+
* Pausing through `enabled` retains the owned interaction until input resumes,
|
|
71
|
+
* the gamepad disconnects, or the wrapper is disposed.
|
|
68
72
|
*
|
|
69
73
|
* @param deltaTime - Seconds since the last frame.
|
|
70
74
|
*/
|
|
75
|
+
update(deltaTime: number): void;
|
|
76
|
+
/**
|
|
77
|
+
* Reads each binding once, manages the gamepad session, and applies native operations.
|
|
78
|
+
* Revalidates the cached frame after synchronous events can change permissions.
|
|
79
|
+
*
|
|
80
|
+
* @param deltaTime - Seconds elapsed for this input frame.
|
|
81
|
+
*/
|
|
71
82
|
protected onUpdate(deltaTime: number): void;
|
|
83
|
+
/**
|
|
84
|
+
* Removes gamepad listeners and ends only this wrapper's active interaction.
|
|
85
|
+
* Repeated calls are safe; the native controls and their damping are preserved.
|
|
86
|
+
*/
|
|
87
|
+
dispose(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Ends the owned interaction before forwarding the active gamepad's disconnection.
|
|
90
|
+
*
|
|
91
|
+
* @param gamepad - The gamepad that just disconnected.
|
|
92
|
+
*/
|
|
93
|
+
protected onGamepadDisconnected(gamepad: Gamepad): void;
|
|
72
94
|
}
|
|
73
95
|
//#endregion
|
|
@@ -25,10 +25,18 @@ const DEFAULT_ORBIT_OPTIONS = {
|
|
|
25
25
|
*
|
|
26
26
|
* Call `update()` inside the render loop **before** `OrbitControls.update()`.
|
|
27
27
|
* Bindings and speeds are configurable via {@link GamepadOrbitControlsOptions}.
|
|
28
|
+
* The wrapper dispatches balanced gamepad `start` and `end` events on the native
|
|
29
|
+
* controls; native operations and damping remain responsible for `change`.
|
|
28
30
|
*/
|
|
29
31
|
var GamepadOrbitControls = class extends GamepadControls {
|
|
30
32
|
#controls;
|
|
31
33
|
#options;
|
|
34
|
+
/** Whether this wrapper owns an active gamepad interaction. */
|
|
35
|
+
#interacting = false;
|
|
36
|
+
/** Blocks recursive updates while a frame is being processed. */
|
|
37
|
+
#updating = false;
|
|
38
|
+
/** Blocks recursive updates while the owned interaction is ending. */
|
|
39
|
+
#ending = false;
|
|
32
40
|
/**
|
|
33
41
|
* @param controls - A Three.js `OrbitControls` instance.
|
|
34
42
|
* @param options - Optional overrides for the default behavior.
|
|
@@ -45,39 +53,143 @@ var GamepadOrbitControls = class extends GamepadControls {
|
|
|
45
53
|
};
|
|
46
54
|
}
|
|
47
55
|
/**
|
|
48
|
-
*
|
|
56
|
+
* Polls the gamepad and applies input, ignoring updates from synchronous listeners.
|
|
57
|
+
* Pausing through `enabled` retains the owned interaction until input resumes,
|
|
58
|
+
* the gamepad disconnects, or the wrapper is disposed.
|
|
49
59
|
*
|
|
50
60
|
* @param deltaTime - Seconds since the last frame.
|
|
51
61
|
*/
|
|
62
|
+
update(deltaTime) {
|
|
63
|
+
if (this.#updating || this.#ending) return;
|
|
64
|
+
this.#updating = true;
|
|
65
|
+
try {
|
|
66
|
+
super.update(deltaTime);
|
|
67
|
+
} finally {
|
|
68
|
+
this.#updating = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reads each binding once, manages the gamepad session, and applies native operations.
|
|
73
|
+
* Revalidates the cached frame after synchronous events can change permissions.
|
|
74
|
+
*
|
|
75
|
+
* @param deltaTime - Seconds elapsed for this input frame.
|
|
76
|
+
*/
|
|
52
77
|
onUpdate(deltaTime) {
|
|
53
|
-
if (!this.#
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
if (!this.#controls.enabled) {
|
|
79
|
+
this.#endInteraction();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const { rotateStick, panStick, buttonDeadzone, buttonDollyIn, buttonDollyOut } = this.#options;
|
|
56
83
|
const input = this.gamepadInput;
|
|
57
84
|
const rotate = input.stick(rotateStick.xAxis, rotateStick.yAxis, rotateStick.pipeline);
|
|
58
|
-
if (controls.enableRotate && rotate.x !== 0) {
|
|
59
|
-
controls.rotateLeft(rotate.x * rotateSpeed * deltaTime * Math.PI);
|
|
60
|
-
if (!this.#canApplyInput()) return;
|
|
61
|
-
}
|
|
62
|
-
if (controls.enableRotate && rotate.y !== 0) {
|
|
63
|
-
controls.rotateUp(rotate.y * rotateSpeed * deltaTime * Math.PI);
|
|
64
|
-
if (!this.#canApplyInput()) return;
|
|
65
|
-
}
|
|
66
85
|
const pan = input.stick(panStick.xAxis, panStick.yAxis, panStick.pipeline);
|
|
67
|
-
if (controls.enablePan && (pan.x !== 0 || pan.y !== 0)) {
|
|
68
|
-
controls.pan(pan.x * panSpeed * deltaTime * 500, pan.y * panSpeed * deltaTime * 500);
|
|
69
|
-
if (!this.#canApplyInput()) return;
|
|
70
|
-
}
|
|
71
86
|
const triggerIn = input.buttonValue(buttonDollyIn);
|
|
72
87
|
const triggerOut = input.buttonValue(buttonDollyOut);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
const frame = {
|
|
89
|
+
rotateX: rotate.x,
|
|
90
|
+
rotateY: rotate.y,
|
|
91
|
+
panX: pan.x,
|
|
92
|
+
panY: pan.y,
|
|
93
|
+
dollyIn: triggerIn > buttonDeadzone ? triggerIn : 0,
|
|
94
|
+
dollyOut: triggerOut > buttonDeadzone ? triggerOut : 0
|
|
95
|
+
};
|
|
96
|
+
let actions = this.#acceptActions(frame, deltaTime);
|
|
97
|
+
if (actions === null) return;
|
|
98
|
+
if (!this.#interacting) {
|
|
99
|
+
this.#interacting = true;
|
|
100
|
+
this.#controls.dispatchEvent({ type: "start" });
|
|
101
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
102
|
+
if (actions === null) return;
|
|
103
|
+
}
|
|
104
|
+
const controls = this.#controls;
|
|
105
|
+
if (actions.rotateX !== 0) {
|
|
106
|
+
controls.rotateLeft(actions.rotateX);
|
|
107
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
108
|
+
if (actions === null) return;
|
|
109
|
+
}
|
|
110
|
+
if (actions.rotateY !== 0) {
|
|
111
|
+
controls.rotateUp(actions.rotateY);
|
|
112
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
113
|
+
if (actions === null) return;
|
|
114
|
+
}
|
|
115
|
+
if (actions.panX !== 0 || actions.panY !== 0) {
|
|
116
|
+
controls.pan(actions.panX, actions.panY);
|
|
117
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
118
|
+
if (actions === null) return;
|
|
119
|
+
}
|
|
120
|
+
if (actions.dollyIn !== 0) {
|
|
121
|
+
controls.dollyIn(1 / (1 + actions.dollyIn));
|
|
122
|
+
actions = this.#acceptActions(frame, deltaTime);
|
|
123
|
+
if (actions === null) return;
|
|
124
|
+
}
|
|
125
|
+
if (actions.dollyOut !== 0) {
|
|
126
|
+
controls.dollyOut(1 / (1 + actions.dollyOut));
|
|
127
|
+
this.#acceptActions(frame, deltaTime);
|
|
76
128
|
}
|
|
77
|
-
if (controls.enableZoom && triggerOut > buttonDeadzone) controls.dollyOut(1 / (1 + zoomSpeed * triggerOut * deltaTime));
|
|
78
129
|
}
|
|
79
|
-
|
|
80
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Removes gamepad listeners and ends only this wrapper's active interaction.
|
|
132
|
+
* Repeated calls are safe; the native controls and their damping are preserved.
|
|
133
|
+
*/
|
|
134
|
+
dispose() {
|
|
135
|
+
super.dispose();
|
|
136
|
+
this.#endInteraction();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ends the owned interaction before forwarding the active gamepad's disconnection.
|
|
140
|
+
*
|
|
141
|
+
* @param gamepad - The gamepad that just disconnected.
|
|
142
|
+
*/
|
|
143
|
+
onGamepadDisconnected(gamepad) {
|
|
144
|
+
this.#endInteraction();
|
|
145
|
+
super.onGamepadDisconnected(gamepad);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolves a cached frame against current permissions and native/wrapper speeds.
|
|
149
|
+
* Native disable or lack of actionable input ends the owned session; a wrapper
|
|
150
|
+
* pause stops application while retaining it. Geometric limits do not erase intent.
|
|
151
|
+
*
|
|
152
|
+
* @param input - Already processed sticks and filtered triggers for this frame.
|
|
153
|
+
* @param delta - Frame duration in seconds.
|
|
154
|
+
* @returns Accepted frame deltas, or `null` when input application must stop.
|
|
155
|
+
*/
|
|
156
|
+
#acceptActions(input, delta) {
|
|
157
|
+
const controls = this.#controls;
|
|
158
|
+
if (!controls.enabled) {
|
|
159
|
+
this.#endInteraction();
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
if (!this.enabled || this.gamepad === null) return null;
|
|
163
|
+
const rotate = controls.enableRotate ? controls.rotateSpeed * this.#options.rotateSpeed * delta * Math.PI : 0;
|
|
164
|
+
const pan = controls.enablePan ? controls.panSpeed * this.#options.panSpeed * delta * 500 : 0;
|
|
165
|
+
const zoom = controls.enableZoom ? controls.zoomSpeed * this.#options.zoomSpeed * delta : 0;
|
|
166
|
+
const actions = {
|
|
167
|
+
rotateX: input.rotateX * rotate,
|
|
168
|
+
rotateY: input.rotateY * rotate,
|
|
169
|
+
panX: input.panX * pan,
|
|
170
|
+
panY: input.panY * pan,
|
|
171
|
+
dollyIn: input.dollyIn * zoom,
|
|
172
|
+
dollyOut: input.dollyOut * zoom
|
|
173
|
+
};
|
|
174
|
+
if (!Object.values(actions).some((value) => value !== 0)) {
|
|
175
|
+
this.#endInteraction();
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return actions;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Releases session ownership before dispatching the native `end` event once.
|
|
182
|
+
* Blocks recursive updates during finalization without clearing native input or damping.
|
|
183
|
+
*/
|
|
184
|
+
#endInteraction() {
|
|
185
|
+
if (!this.#interacting) return;
|
|
186
|
+
this.#interacting = false;
|
|
187
|
+
this.#ending = true;
|
|
188
|
+
try {
|
|
189
|
+
this.#controls.dispatchEvent({ type: "end" });
|
|
190
|
+
} finally {
|
|
191
|
+
this.#ending = false;
|
|
192
|
+
}
|
|
81
193
|
}
|
|
82
194
|
};
|
|
83
195
|
//#endregion
|
|
@@ -35,6 +35,8 @@ export type GamepadPointerLockControlsOptions = GamepadControlsOptions & {
|
|
|
35
35
|
* Gamepad input is fully independent of pointer lock state. When the pointer
|
|
36
36
|
* IS locked, mouse and gamepad look inputs are additive.
|
|
37
37
|
* Bindings and speeds are configurable via {@link GamepadPointerLockControlsOptions}.
|
|
38
|
+
* Look dispatches `change` on the native controls after an actual orientation
|
|
39
|
+
* change; movement does not dispatch it or alter pointer lock state.
|
|
38
40
|
*/
|
|
39
41
|
export declare class GamepadPointerLockControls extends GamepadControls {
|
|
40
42
|
#private;
|
|
@@ -44,8 +46,19 @@ export declare class GamepadPointerLockControls extends GamepadControls {
|
|
|
44
46
|
* Any property not provided falls back to its default value.
|
|
45
47
|
*/
|
|
46
48
|
constructor(controls: PointerLockControls, options?: Partial<GamepadPointerLockControlsOptions>);
|
|
49
|
+
/**
|
|
50
|
+
* Polls and applies movement and look, ignoring updates from synchronous listeners.
|
|
51
|
+
* Gamepad input remains independent of the native pointer lock state.
|
|
52
|
+
*
|
|
53
|
+
* @param deltaTime - Seconds since the last frame.
|
|
54
|
+
*/
|
|
55
|
+
update(deltaTime: number): void;
|
|
47
56
|
/**
|
|
48
57
|
* Maps the current gamepad state to `PointerLockControls` movement and look.
|
|
58
|
+
* Dispatches at most one native `change` after look changes orientation by more
|
|
59
|
+
* than `1e-7` radians, treating opposite quaternion signs as equivalent.
|
|
60
|
+
* Translation, zero look gain, and pitch-only input held at a clamp do not
|
|
61
|
+
* dispatch `change`; permitted yaw can still change orientation at a pitch limit.
|
|
49
62
|
*
|
|
50
63
|
* @param deltaTime - Seconds since the last frame.
|
|
51
64
|
*/
|