three-gamepad-controls 0.8.0 → 0.10.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 CHANGED
@@ -39,9 +39,11 @@ bun add three-gamepad-controls
39
39
  - [Core](./docs/core.md) — The fundamental building blocks.
40
40
  - [GamepadArcballControls](./docs/gamepad-arcball-controls.md) - Gamepad support for `ArcballControls`.
41
41
  - [GamepadControls](./docs/gamepad-controls.md) — Abstract base class for custom gamepad controls.
42
+ - [GamepadDragControls](./docs/gamepad-drag-controls.md) - Gamepad support for `DragControls`.
42
43
  - [GamepadFirstPersonControls](./docs/gamepad-first-person-controls.md) — Gamepad support for `FirstPersonControls`.
43
44
  - [GamepadFlyControls](./docs/gamepad-fly-controls.md) — Gamepad support for `FlyControls`.
44
45
  - [GamepadMapControls](./docs/gamepad-map-controls.md) — Gamepad support for `MapControls`.
45
46
  - [GamepadOrbitControls](./docs/gamepad-orbit-controls.md) — Gamepad support for `OrbitControls`.
46
47
  - [GamepadPointerLockControls](./docs/gamepad-pointer-lock-controls.md) — Gamepad support for `PointerLockControls`.
47
48
  - [GamepadTrackballControls](./docs/gamepad-trackball-controls.md) — Gamepad support for `TrackballControls`.
49
+ - [GamepadTransformControls](./docs/gamepad-transform-controls.md) — Gamepad support for `TransformControls`.
@@ -0,0 +1,87 @@
1
+ import { GamepadControls } from "./gamepad-controls.js";
2
+ import { DragControls } from "three/addons/controls/DragControls.js";
3
+
4
+ //#region src/gamepad-drag-controls.d.ts
5
+ /**
6
+ * Configuration for {@link GamepadDragControls}.
7
+ *
8
+ * Every property has a sensible default, so you only need to pass the properties you want to override.
9
+ */
10
+ type GamepadDragControlsOptions = {
11
+ /**
12
+ * Screen-relative translation speed multiplier.
13
+ * @default 1.0
14
+ */
15
+ dragSpeed: number;
16
+ /**
17
+ * Multiplier on `DragControls.rotateSpeed` for rotation.
18
+ * @default 1.0
19
+ */
20
+ rotateSpeed: number;
21
+ /**
22
+ * Axis dead zone threshold in the range `[0, 1]`.
23
+ * @default 0.1
24
+ */
25
+ deadzone: number;
26
+ /**
27
+ * Axis index for **horizontal** dragging.
28
+ * @default 0 - Left stick X
29
+ */
30
+ axisDragX: number;
31
+ /**
32
+ * Axis index for **vertical** dragging.
33
+ * @default 1 - Left stick Y
34
+ */
35
+ axisDragY: number;
36
+ /**
37
+ * Axis index for **horizontal** object rotation.
38
+ * @default 2 - Right stick X
39
+ */
40
+ axisRotateX: number;
41
+ /**
42
+ * Axis index for **vertical** object rotation.
43
+ * @default 3 - Right stick Y
44
+ */
45
+ axisRotateY: number;
46
+ /**
47
+ * Button index for grabbing and dropping the object under the center reticle.
48
+ * @default 0 - South face button
49
+ */
50
+ buttonSelect: number;
51
+ };
52
+ /**
53
+ * Adds gamepad support to Three.js `DragControls`.
54
+ *
55
+ * The center of the viewport acts as a logical reticle. Press the select button
56
+ * once to grab the centered object and again to drop it.
57
+ */
58
+ declare class GamepadDragControls extends GamepadControls {
59
+ #private;
60
+ /**
61
+ * @param controls - A Three.js `DragControls` instance.
62
+ * @param options - Optional overrides for the default behavior.
63
+ * Any property not provided falls back to its default value.
64
+ */
65
+ constructor(controls: DragControls, options?: Partial<GamepadDragControlsOptions>);
66
+ /**
67
+ * Maps the current gamepad state to `DragControls` hover, grab/drop, drag,
68
+ * and rotate behavior.
69
+ *
70
+ * @param deltaTime - Seconds since the last frame.
71
+ * @param gamepad - Fresh gamepad snapshot provided by the base class.
72
+ */
73
+ protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
74
+ /**
75
+ * Releases any selected object and removes hover state before disposing the
76
+ * gamepad lifecycle listeners.
77
+ */
78
+ dispose(): void;
79
+ /**
80
+ * Releases the selected object if the active gamepad disconnects mid-drag.
81
+ *
82
+ * @param gamepad - The gamepad that just disconnected.
83
+ */
84
+ protected onGamepadDisconnected(gamepad: Gamepad): void;
85
+ }
86
+ //#endregion
87
+ export { GamepadDragControls, GamepadDragControlsOptions };
@@ -0,0 +1,260 @@
1
+ import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
+ import { GamepadControls } from "./gamepad-controls.js";
3
+ import { Matrix4, Vector2, Vector3 } from "three";
4
+ //#region src/gamepad-drag-controls.ts
5
+ /**
6
+ * Default options merged in the constructor when no explicit configuration is provided.
7
+ */
8
+ const DEFAULT_DRAG_OPTIONS = {
9
+ dragSpeed: 1,
10
+ rotateSpeed: 1,
11
+ deadzone: .1,
12
+ axisDragX: GAMEPAD_AXIS.LeftX,
13
+ axisDragY: GAMEPAD_AXIS.LeftY,
14
+ axisRotateX: GAMEPAD_AXIS.RightX,
15
+ axisRotateY: GAMEPAD_AXIS.RightY,
16
+ buttonSelect: GAMEPAD_BUTTON.South
17
+ };
18
+ /**
19
+ * Adds gamepad support to Three.js `DragControls`.
20
+ *
21
+ * The center of the viewport acts as a logical reticle. Press the select button
22
+ * once to grab the centered object and again to drop it.
23
+ */
24
+ var GamepadDragControls = class extends GamepadControls {
25
+ #controls;
26
+ #options;
27
+ #centerNdc;
28
+ #intersections;
29
+ #parentInverse;
30
+ #selectedWorldPosition;
31
+ #selectedLocalPosition;
32
+ #cameraWorldPosition;
33
+ #cameraForward;
34
+ #cameraRight;
35
+ #cameraUp;
36
+ #cameraToSelected;
37
+ #viewSize;
38
+ #hovered = null;
39
+ #selected = null;
40
+ #selectButtonPressed = false;
41
+ /**
42
+ * @param controls - A Three.js `DragControls` instance.
43
+ * @param options - Optional overrides for the default behavior.
44
+ * Any property not provided falls back to its default value.
45
+ */
46
+ constructor(controls, options) {
47
+ super();
48
+ this.#controls = controls;
49
+ this.#options = {
50
+ ...DEFAULT_DRAG_OPTIONS,
51
+ ...options
52
+ };
53
+ this.#centerNdc = new Vector2(0, 0);
54
+ this.#intersections = [];
55
+ this.#parentInverse = new Matrix4();
56
+ this.#selectedWorldPosition = new Vector3();
57
+ this.#selectedLocalPosition = new Vector3();
58
+ this.#cameraWorldPosition = new Vector3();
59
+ this.#cameraForward = new Vector3();
60
+ this.#cameraRight = new Vector3();
61
+ this.#cameraUp = new Vector3();
62
+ this.#cameraToSelected = new Vector3();
63
+ this.#viewSize = new Vector2();
64
+ }
65
+ /**
66
+ * Maps the current gamepad state to `DragControls` hover, grab/drop, drag,
67
+ * and rotate behavior.
68
+ *
69
+ * @param deltaTime - Seconds since the last frame.
70
+ * @param gamepad - Fresh gamepad snapshot provided by the base class.
71
+ */
72
+ onUpdate(deltaTime, gamepad) {
73
+ const controls = this.#controls;
74
+ const selectPressed = this.#getButtonPressed(gamepad, this.#options.buttonSelect);
75
+ const selectStarted = selectPressed && !this.#selectButtonPressed;
76
+ this.#selectButtonPressed = selectPressed;
77
+ if (!controls.enabled) {
78
+ this.#releaseSelected();
79
+ this.#clearHover();
80
+ return;
81
+ }
82
+ if (this.#selected !== null) {
83
+ if (selectStarted) {
84
+ this.#releaseSelected();
85
+ return;
86
+ }
87
+ this.#updateSelected(deltaTime, gamepad);
88
+ return;
89
+ }
90
+ const hit = this.#intersectCenter();
91
+ this.#updateHover(hit?.object ?? null);
92
+ if (selectStarted && hit !== void 0) this.#grabObject(hit.object);
93
+ }
94
+ /**
95
+ * Releases any selected object and removes hover state before disposing the
96
+ * gamepad lifecycle listeners.
97
+ */
98
+ dispose() {
99
+ this.#releaseSelected();
100
+ this.#clearHover();
101
+ super.dispose();
102
+ }
103
+ /**
104
+ * Releases the selected object if the active gamepad disconnects mid-drag.
105
+ *
106
+ * @param gamepad - The gamepad that just disconnected.
107
+ */
108
+ onGamepadDisconnected(gamepad) {
109
+ this.#releaseSelected();
110
+ this.#clearHover();
111
+ super.onGamepadDisconnected(gamepad);
112
+ }
113
+ #updateSelected(deltaTime, gamepad) {
114
+ const selected = this.#selected;
115
+ if (selected === null) return;
116
+ const { dragSpeed, rotateSpeed, deadzone, axisDragX, axisDragY, axisRotateX, axisRotateY } = this.#options;
117
+ const dragX = this.#applyDeadzone(gamepad.axes[axisDragX] ?? 0, deadzone);
118
+ const dragY = this.#applyDeadzone(gamepad.axes[axisDragY] ?? 0, deadzone);
119
+ const rotateX = this.#applyDeadzone(gamepad.axes[axisRotateX] ?? 0, deadzone);
120
+ const rotateY = this.#applyDeadzone(gamepad.axes[axisRotateY] ?? 0, deadzone);
121
+ const dragged = this.#applyDrag(deltaTime, dragX, dragY, dragSpeed);
122
+ const rotated = this.#applyRotation(deltaTime, rotateX, rotateY, rotateSpeed);
123
+ if (dragged || rotated) this.#controls.dispatchEvent({
124
+ type: "drag",
125
+ object: selected
126
+ });
127
+ }
128
+ #applyDrag(deltaTime, dragX, dragY, dragSpeed) {
129
+ if (this.#selected === null || dragX === 0 && dragY === 0) return false;
130
+ this.#updateCameraAxes();
131
+ this.#updateViewSizeAtSelectedDepth();
132
+ const scale = dragSpeed * deltaTime;
133
+ this.#selectedWorldPosition.addScaledVector(this.#cameraRight, dragX * this.#viewSize.x * scale);
134
+ this.#selectedWorldPosition.addScaledVector(this.#cameraUp, -dragY * this.#viewSize.y * scale);
135
+ this.#applySelectedWorldPosition();
136
+ return true;
137
+ }
138
+ #applyRotation(deltaTime, rotateX, rotateY, rotateSpeed) {
139
+ const selected = this.#selected;
140
+ if (selected === null || rotateX === 0 && rotateY === 0) return false;
141
+ this.#updateCameraAxes();
142
+ const scale = this.#controls.rotateSpeed * rotateSpeed * deltaTime * Math.PI;
143
+ if (rotateX !== 0) selected.rotateOnWorldAxis(this.#cameraUp, rotateX * scale);
144
+ if (rotateY !== 0) selected.rotateOnWorldAxis(this.#cameraRight, rotateY * scale);
145
+ return true;
146
+ }
147
+ #intersectCenter() {
148
+ const controls = this.#controls;
149
+ this.#intersections.length = 0;
150
+ controls.raycaster.setFromCamera(this.#centerNdc, controls.object);
151
+ controls.raycaster.intersectObjects(controls.objects, controls.recursive, this.#intersections);
152
+ return this.#intersections[0];
153
+ }
154
+ #updateHover(object) {
155
+ if (this.#hovered === object) return;
156
+ this.#clearHover();
157
+ if (object === null) return;
158
+ this.#hovered = object;
159
+ this.#controls.dispatchEvent({
160
+ type: "hoveron",
161
+ object
162
+ });
163
+ }
164
+ #clearHover() {
165
+ if (this.#hovered === null) return;
166
+ const object = this.#hovered;
167
+ this.#hovered = null;
168
+ this.#controls.dispatchEvent({
169
+ type: "hoveroff",
170
+ object
171
+ });
172
+ }
173
+ #grabObject(object) {
174
+ const selected = this.#getSelectedObject(object);
175
+ selected.updateWorldMatrix(true, false);
176
+ this.#selected = selected;
177
+ this.#selectedWorldPosition.setFromMatrixPosition(selected.matrixWorld);
178
+ this.#controls.dispatchEvent({
179
+ type: "dragstart",
180
+ object: selected
181
+ });
182
+ }
183
+ #releaseSelected() {
184
+ if (this.#selected === null) return;
185
+ const selected = this.#selected;
186
+ this.#selected = null;
187
+ this.#controls.dispatchEvent({
188
+ type: "dragend",
189
+ object: selected
190
+ });
191
+ }
192
+ #getSelectedObject(object) {
193
+ if (!this.#controls.transformGroup) return object;
194
+ return this.#findOutermostGroup(object) ?? object;
195
+ }
196
+ #findOutermostGroup(object) {
197
+ let group = null;
198
+ let current = object;
199
+ while (current !== null) {
200
+ if (current.isGroup === true) group = current;
201
+ current = current.parent;
202
+ }
203
+ return group;
204
+ }
205
+ #applySelectedWorldPosition() {
206
+ const selected = this.#selected;
207
+ if (selected === null) return;
208
+ if (selected.parent === null) {
209
+ selected.position.copy(this.#selectedWorldPosition);
210
+ selected.updateMatrixWorld();
211
+ return;
212
+ }
213
+ selected.parent.updateWorldMatrix(true, false);
214
+ this.#parentInverse.copy(selected.parent.matrixWorld).invert();
215
+ this.#selectedLocalPosition.copy(this.#selectedWorldPosition).applyMatrix4(this.#parentInverse);
216
+ selected.position.copy(this.#selectedLocalPosition);
217
+ selected.updateMatrixWorld();
218
+ }
219
+ #updateCameraAxes() {
220
+ const camera = this.#controls.object;
221
+ this.#cameraRight.set(1, 0, 0).applyQuaternion(camera.quaternion).normalize();
222
+ this.#cameraUp.set(0, 1, 0).applyQuaternion(camera.quaternion).normalize();
223
+ camera.getWorldDirection(this.#cameraForward).normalize();
224
+ }
225
+ #updateViewSizeAtSelectedDepth() {
226
+ const camera = this.#controls.object;
227
+ if (this.#isOrthographicCamera(camera)) {
228
+ this.#viewSize.set(Math.abs(camera.right - camera.left) / camera.zoom, Math.abs(camera.top - camera.bottom) / camera.zoom);
229
+ return;
230
+ }
231
+ if (this.#isPerspectiveCamera(camera)) {
232
+ camera.getWorldPosition(this.#cameraWorldPosition);
233
+ const depth = Math.max(Number.EPSILON, this.#cameraToSelected.copy(this.#selectedWorldPosition).sub(this.#cameraWorldPosition).dot(this.#cameraForward));
234
+ const height = 2 * Math.tan(camera.fov * Math.PI / 360) * depth;
235
+ this.#viewSize.set(height * camera.aspect, height);
236
+ return;
237
+ }
238
+ this.#viewSize.set(1, 1);
239
+ }
240
+ #isPerspectiveCamera(camera) {
241
+ return camera.isPerspectiveCamera === true;
242
+ }
243
+ #isOrthographicCamera(camera) {
244
+ return camera.isOrthographicCamera === true;
245
+ }
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
+ };
259
+ //#endregion
260
+ export { GamepadDragControls };
@@ -0,0 +1,132 @@
1
+ import { GamepadControls } from "./gamepad-controls.js";
2
+ import { TransformControls } from "three/addons/controls/TransformControls.js";
3
+
4
+ //#region src/gamepad-transform-controls.d.ts
5
+ /**
6
+ * Configuration for {@link GamepadTransformControls}.
7
+ *
8
+ * Every property has a sensible default, so you only need to pass the properties you want to override.
9
+ */
10
+ type GamepadTransformControlsOptions = {
11
+ /**
12
+ * Screen-relative translation speed multiplier.
13
+ * @default 1.0
14
+ */
15
+ translateSpeed: number;
16
+ /**
17
+ * Rotation speed multiplier.
18
+ * @default 1.0
19
+ */
20
+ rotateSpeed: number;
21
+ /**
22
+ * Scale speed multiplier.
23
+ * @default 1.0
24
+ */
25
+ scaleSpeed: number;
26
+ /**
27
+ * Axis dead zone threshold in the range `[0, 1]`.
28
+ * @default 0.1
29
+ */
30
+ deadzone: number;
31
+ /**
32
+ * Axis index for **horizontal** transform input.
33
+ * @default 0 - Left stick X
34
+ */
35
+ axisTransformX: number;
36
+ /**
37
+ * Axis index for **vertical** transform input.
38
+ * @default 1 - Left stick Y
39
+ */
40
+ axisTransformY: number;
41
+ /**
42
+ * Button index for selecting translate mode.
43
+ * @default 0 - South face button
44
+ */
45
+ buttonTranslate: number;
46
+ /**
47
+ * Button index for selecting rotate mode.
48
+ * @default 1 - East face button
49
+ */
50
+ buttonRotate: number;
51
+ /**
52
+ * Button index for selecting scale mode.
53
+ * @default 2 - West face button
54
+ */
55
+ buttonScale: number;
56
+ /**
57
+ * Button index for toggling world/local space.
58
+ * @default 3 - North face button
59
+ */
60
+ buttonToggleSpace: number;
61
+ /**
62
+ * Button index for selecting the X axis.
63
+ * @default 15 - D-pad right
64
+ */
65
+ buttonAxisX: number;
66
+ /**
67
+ * Button index for selecting the Y axis.
68
+ * @default 12 - D-pad up
69
+ */
70
+ buttonAxisY: number;
71
+ /**
72
+ * Button index for selecting the Z axis.
73
+ * @default 14 - D-pad left
74
+ */
75
+ buttonAxisZ: number;
76
+ /**
77
+ * Button index for cycling composite axes in the active mode.
78
+ * @default 13 - D-pad down
79
+ */
80
+ buttonAxisComposite: number;
81
+ /**
82
+ * Button index for selecting the previous valid axis.
83
+ * @default 4 - Left shoulder
84
+ */
85
+ buttonAxisPrevious: number;
86
+ /**
87
+ * Button index for selecting the next valid axis.
88
+ * @default 5 - Right shoulder
89
+ */
90
+ buttonAxisNext: number;
91
+ /**
92
+ * Button index for resetting the active transform.
93
+ * @default 9 - Start button
94
+ */
95
+ buttonReset: number;
96
+ };
97
+ /**
98
+ * Adds gamepad support to Three.js `TransformControls`.
99
+ *
100
+ * Modes and axes are selected explicitly with buttons. Moving the transform
101
+ * stick starts a native-style transform interaction, and releasing it ends
102
+ * the interaction while keeping the active axis highlighted.
103
+ */
104
+ declare class GamepadTransformControls extends GamepadControls {
105
+ #private;
106
+ /**
107
+ * @param controls - A Three.js `TransformControls` instance.
108
+ * @param options - Optional overrides for the default behavior.
109
+ * Any property not provided falls back to its default value.
110
+ */
111
+ constructor(controls: TransformControls, options?: Partial<GamepadTransformControlsOptions>);
112
+ /**
113
+ * Maps the current gamepad state to `TransformControls` mode, axis,
114
+ * translate, rotate, scale, and reset behavior.
115
+ *
116
+ * @param deltaTime - Seconds since the last frame.
117
+ * @param gamepad - Fresh gamepad snapshot provided by the base class.
118
+ */
119
+ protected onUpdate(deltaTime: number, gamepad: Gamepad): void;
120
+ /**
121
+ * Ends any active transform before disposing the gamepad lifecycle listeners.
122
+ */
123
+ dispose(): void;
124
+ /**
125
+ * Ends any active transform if the active gamepad disconnects mid-drag.
126
+ *
127
+ * @param gamepad - The gamepad that just disconnected.
128
+ */
129
+ protected onGamepadDisconnected(gamepad: Gamepad): void;
130
+ }
131
+ //#endregion
132
+ export { GamepadTransformControls, GamepadTransformControlsOptions };
@@ -0,0 +1,634 @@
1
+ import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
+ import { GamepadControls } from "./gamepad-controls.js";
3
+ import { Matrix4, Quaternion, Vector2, Vector3 } from "three";
4
+ //#region src/gamepad-transform-controls.ts
5
+ /**
6
+ * Default options merged in the constructor when no explicit configuration is provided.
7
+ */
8
+ const DEFAULT_TRANSFORM_OPTIONS = {
9
+ translateSpeed: 1,
10
+ rotateSpeed: 1,
11
+ scaleSpeed: 1,
12
+ deadzone: .1,
13
+ axisTransformX: GAMEPAD_AXIS.LeftX,
14
+ axisTransformY: GAMEPAD_AXIS.LeftY,
15
+ buttonTranslate: GAMEPAD_BUTTON.South,
16
+ buttonRotate: GAMEPAD_BUTTON.East,
17
+ buttonScale: GAMEPAD_BUTTON.West,
18
+ buttonToggleSpace: GAMEPAD_BUTTON.North,
19
+ buttonAxisX: GAMEPAD_BUTTON.DPadRight,
20
+ buttonAxisY: GAMEPAD_BUTTON.DPadUp,
21
+ buttonAxisZ: GAMEPAD_BUTTON.DPadLeft,
22
+ buttonAxisComposite: GAMEPAD_BUTTON.DPadDown,
23
+ buttonAxisPrevious: GAMEPAD_BUTTON.LeftShoulder,
24
+ buttonAxisNext: GAMEPAD_BUTTON.RightShoulder,
25
+ buttonReset: GAMEPAD_BUTTON.Start
26
+ };
27
+ const MODE_AXES = {
28
+ translate: [
29
+ "X",
30
+ "Y",
31
+ "Z",
32
+ "XY",
33
+ "YZ",
34
+ "XZ",
35
+ "XYZ"
36
+ ],
37
+ rotate: [
38
+ "X",
39
+ "Y",
40
+ "Z",
41
+ "E",
42
+ "XYZE"
43
+ ],
44
+ scale: [
45
+ "X",
46
+ "Y",
47
+ "Z",
48
+ "XYZ"
49
+ ]
50
+ };
51
+ const COMPOSITE_AXES = {
52
+ translate: [
53
+ "XY",
54
+ "YZ",
55
+ "XZ",
56
+ "XYZ"
57
+ ],
58
+ rotate: ["E", "XYZE"],
59
+ scale: ["XYZ"]
60
+ };
61
+ const PROJECTED_AXIS_EPSILON = .001;
62
+ /**
63
+ * Adds gamepad support to Three.js `TransformControls`.
64
+ *
65
+ * Modes and axes are selected explicitly with buttons. Moving the transform
66
+ * stick starts a native-style transform interaction, and releasing it ends
67
+ * the interaction while keeping the active axis highlighted.
68
+ */
69
+ var GamepadTransformControls = class extends GamepadControls {
70
+ #controls;
71
+ #options;
72
+ #pressedButtons;
73
+ #activeAxisByMode;
74
+ #viewSize;
75
+ #parentInverse;
76
+ #cameraWorldPosition;
77
+ #cameraForward;
78
+ #cameraRight;
79
+ #cameraUp;
80
+ #eye;
81
+ #axisWorld;
82
+ #axisWorld2;
83
+ #axisLocal;
84
+ #worldDelta;
85
+ #localDelta;
86
+ #worldPositionStart;
87
+ #worldPosition;
88
+ #positionStart;
89
+ #accumulatedPosition;
90
+ #snappedPosition;
91
+ #parentPosition;
92
+ #parentScale;
93
+ #scaleStart;
94
+ #accumulatedScale;
95
+ #snappedScale;
96
+ #worldQuaternionStart;
97
+ #quaternionStart;
98
+ #parentQuaternion;
99
+ #parentQuaternionInv;
100
+ #rotationQuaternion;
101
+ #rotationQuaternion2;
102
+ #tempQuaternion;
103
+ #isTransforming = false;
104
+ #rotationAmount = 0;
105
+ #freeRotationX = 0;
106
+ #freeRotationY = 0;
107
+ /**
108
+ * @param controls - A Three.js `TransformControls` instance.
109
+ * @param options - Optional overrides for the default behavior.
110
+ * Any property not provided falls back to its default value.
111
+ */
112
+ constructor(controls, options) {
113
+ super();
114
+ this.#controls = controls;
115
+ this.#options = {
116
+ ...DEFAULT_TRANSFORM_OPTIONS,
117
+ ...options
118
+ };
119
+ this.#pressedButtons = /* @__PURE__ */ new Set();
120
+ this.#activeAxisByMode = {
121
+ translate: "X",
122
+ rotate: "X",
123
+ scale: "X"
124
+ };
125
+ this.#viewSize = new Vector2();
126
+ this.#parentInverse = new Matrix4();
127
+ this.#cameraWorldPosition = new Vector3();
128
+ this.#cameraForward = new Vector3();
129
+ this.#cameraRight = new Vector3();
130
+ this.#cameraUp = new Vector3();
131
+ this.#eye = new Vector3();
132
+ this.#axisWorld = new Vector3();
133
+ this.#axisWorld2 = new Vector3();
134
+ this.#axisLocal = new Vector3();
135
+ this.#worldDelta = new Vector3();
136
+ this.#localDelta = new Vector3();
137
+ this.#worldPositionStart = new Vector3();
138
+ this.#worldPosition = new Vector3();
139
+ this.#positionStart = new Vector3();
140
+ this.#accumulatedPosition = new Vector3();
141
+ this.#snappedPosition = new Vector3();
142
+ this.#parentPosition = new Vector3();
143
+ this.#parentScale = new Vector3(1, 1, 1);
144
+ this.#scaleStart = new Vector3();
145
+ this.#accumulatedScale = new Vector3();
146
+ this.#snappedScale = new Vector3();
147
+ this.#worldQuaternionStart = new Quaternion();
148
+ this.#quaternionStart = new Quaternion();
149
+ this.#parentQuaternion = new Quaternion();
150
+ this.#parentQuaternionInv = new Quaternion();
151
+ this.#rotationQuaternion = new Quaternion();
152
+ this.#rotationQuaternion2 = new Quaternion();
153
+ this.#tempQuaternion = new Quaternion();
154
+ }
155
+ /**
156
+ * Maps the current gamepad state to `TransformControls` mode, axis,
157
+ * translate, rotate, scale, and reset behavior.
158
+ *
159
+ * @param deltaTime - Seconds since the last frame.
160
+ * @param gamepad - Fresh gamepad snapshot provided by the base class.
161
+ */
162
+ onUpdate(deltaTime, gamepad) {
163
+ const startedButtons = this.#getStartedButtons(gamepad);
164
+ this.#handleModeAndAxisButtons(startedButtons);
165
+ if (startedButtons.has(this.#options.buttonReset)) this.#resetActiveTransform();
166
+ const controls = this.#controls;
167
+ if (!controls.enabled || controls.object === void 0) {
168
+ this.#endTransform(true);
169
+ return;
170
+ }
171
+ if (this.#ensureValidAxis() === null) {
172
+ this.#endTransform(true);
173
+ return;
174
+ }
175
+ const transformX = this.#applyDeadzone(gamepad.axes[this.#options.axisTransformX] ?? 0, this.#options.deadzone);
176
+ const transformY = this.#applyDeadzone(gamepad.axes[this.#options.axisTransformY] ?? 0, this.#options.deadzone);
177
+ if (transformX === 0 && transformY === 0) {
178
+ this.#endTransform(false);
179
+ return;
180
+ }
181
+ if (!this.#isTransforming && !this.#startTransform()) return;
182
+ if (this.#applyCurrentTransform(deltaTime, transformX, transformY)) {
183
+ controls.dispatchEvent({ type: "change" });
184
+ controls.dispatchEvent({ type: "objectChange" });
185
+ }
186
+ }
187
+ /**
188
+ * Ends any active transform before disposing the gamepad lifecycle listeners.
189
+ */
190
+ dispose() {
191
+ this.#endTransform(true);
192
+ this.#pressedButtons.clear();
193
+ super.dispose();
194
+ }
195
+ /**
196
+ * Ends any active transform if the active gamepad disconnects mid-drag.
197
+ *
198
+ * @param gamepad - The gamepad that just disconnected.
199
+ */
200
+ onGamepadDisconnected(gamepad) {
201
+ this.#endTransform(true);
202
+ this.#pressedButtons.clear();
203
+ super.onGamepadDisconnected(gamepad);
204
+ }
205
+ #handleModeAndAxisButtons(startedButtons) {
206
+ const { buttonTranslate, buttonRotate, buttonScale, buttonToggleSpace, buttonAxisX, buttonAxisY, buttonAxisZ, buttonAxisComposite, buttonAxisPrevious, buttonAxisNext } = this.#options;
207
+ if (startedButtons.has(buttonTranslate)) this.#setMode("translate");
208
+ if (startedButtons.has(buttonRotate)) this.#setMode("rotate");
209
+ if (startedButtons.has(buttonScale)) this.#setMode("scale");
210
+ if (startedButtons.has(buttonToggleSpace)) this.#toggleSpace();
211
+ if (startedButtons.has(buttonAxisX)) this.#selectAxis("X");
212
+ if (startedButtons.has(buttonAxisY)) this.#selectAxis("Y");
213
+ if (startedButtons.has(buttonAxisZ)) this.#selectAxis("Z");
214
+ if (startedButtons.has(buttonAxisComposite)) this.#cycleCompositeAxis();
215
+ if (startedButtons.has(buttonAxisPrevious)) this.#cycleAxis(-1);
216
+ if (startedButtons.has(buttonAxisNext)) this.#cycleAxis(1);
217
+ }
218
+ #setMode(mode) {
219
+ if (this.#controls.mode === mode) return;
220
+ this.#endTransform(false);
221
+ this.#controls.setMode(mode);
222
+ this.#ensureValidAxis();
223
+ }
224
+ #toggleSpace() {
225
+ const nextSpace = this.#controls.space === "world" ? "local" : "world";
226
+ this.#endTransform(false);
227
+ this.#controls.setSpace(nextSpace);
228
+ }
229
+ #selectAxis(axis) {
230
+ if (!this.#isAxisAllowed(this.#controls.mode, axis)) return;
231
+ this.#endTransform(false);
232
+ this.#activeAxisByMode[this.#controls.mode] = axis;
233
+ this.#ensureValidAxis();
234
+ }
235
+ #cycleCompositeAxis() {
236
+ const validAxes = COMPOSITE_AXES[this.#controls.mode].filter((axis) => this.#isAxisVisible(axis));
237
+ this.#cycleThroughAxes(validAxes, 1);
238
+ }
239
+ #cycleAxis(direction) {
240
+ this.#cycleThroughAxes(this.#getValidAxes(this.#controls.mode), direction);
241
+ }
242
+ #cycleThroughAxes(axes, direction) {
243
+ if (axes.length === 0) {
244
+ this.#setActiveAxis(null);
245
+ return;
246
+ }
247
+ this.#endTransform(false);
248
+ const current = this.#activeAxisByMode[this.#controls.mode];
249
+ const currentIndex = current === null ? -1 : axes.indexOf(current);
250
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + axes.length) % axes.length;
251
+ this.#activeAxisByMode[this.#controls.mode] = axes[nextIndex] ?? null;
252
+ this.#ensureValidAxis();
253
+ }
254
+ #ensureValidAxis() {
255
+ const mode = this.#controls.mode;
256
+ const current = this.#activeAxisByMode[mode];
257
+ const validAxes = this.#getValidAxes(mode);
258
+ const nextAxis = current !== null && validAxes.includes(current) ? current : validAxes[0] ?? null;
259
+ this.#activeAxisByMode[mode] = nextAxis;
260
+ if (this.#controls.axis !== nextAxis) this.#controls.axis = nextAxis;
261
+ return nextAxis;
262
+ }
263
+ #setActiveAxis(axis) {
264
+ this.#activeAxisByMode[this.#controls.mode] = axis;
265
+ if (this.#controls.axis !== axis) this.#controls.axis = axis;
266
+ }
267
+ #getValidAxes(mode) {
268
+ return MODE_AXES[mode].filter((axis) => this.#isAxisVisible(axis));
269
+ }
270
+ #isAxisAllowed(mode, axis) {
271
+ return MODE_AXES[mode].includes(axis) && this.#isAxisVisible(axis);
272
+ }
273
+ #isAxisVisible(axis) {
274
+ const controls = this.#controls;
275
+ switch (axis) {
276
+ case "X": return controls.showX;
277
+ case "Y": return controls.showY;
278
+ case "Z": return controls.showZ;
279
+ case "XY": return controls.showX && controls.showY && controls.showXY;
280
+ case "YZ": return controls.showY && controls.showZ && controls.showYZ;
281
+ case "XZ": return controls.showX && controls.showZ && controls.showXZ;
282
+ case "XYZ":
283
+ case "XYZE": return controls.showX && controls.showY && controls.showZ;
284
+ case "E": return true;
285
+ }
286
+ }
287
+ #startTransform() {
288
+ const controls = this.#controls;
289
+ const object = controls.object;
290
+ if (object === void 0 || controls.axis === null) return false;
291
+ this.#captureTransformStart(object);
292
+ controls.dragging = true;
293
+ this.#isTransforming = true;
294
+ controls.dispatchEvent({
295
+ type: "mouseDown",
296
+ mode: controls.mode
297
+ });
298
+ return true;
299
+ }
300
+ #endTransform(clearAxis) {
301
+ const controls = this.#controls;
302
+ if (this.#isTransforming) {
303
+ this.#isTransforming = false;
304
+ controls.dispatchEvent({
305
+ type: "mouseUp",
306
+ mode: controls.mode
307
+ });
308
+ controls.dragging = false;
309
+ }
310
+ if (clearAxis) this.#setActiveAxis(null);
311
+ }
312
+ #resetActiveTransform() {
313
+ const object = this.#controls.object;
314
+ if (!this.#isTransforming || object === void 0) return;
315
+ this.#controls.reset();
316
+ this.#accumulatedPosition.copy(object.position);
317
+ this.#accumulatedScale.copy(object.scale);
318
+ this.#rotationAmount = 0;
319
+ this.#freeRotationX = 0;
320
+ this.#freeRotationY = 0;
321
+ }
322
+ #captureTransformStart(object) {
323
+ object.updateWorldMatrix(true, false);
324
+ object.parent?.updateWorldMatrix(true, false);
325
+ this.#positionStart.copy(object.position);
326
+ this.#quaternionStart.copy(object.quaternion);
327
+ this.#scaleStart.copy(object.scale);
328
+ this.#accumulatedPosition.copy(object.position);
329
+ this.#accumulatedScale.copy(object.scale);
330
+ object.matrixWorld.decompose(this.#worldPositionStart, this.#worldQuaternionStart, this.#snappedScale);
331
+ this.#captureParentTransform(object);
332
+ this.#controls._positionStart.copy(this.#positionStart);
333
+ this.#controls._quaternionStart.copy(this.#quaternionStart);
334
+ this.#controls._scaleStart.copy(this.#scaleStart);
335
+ this.#controls.pointStart.set(0, 0, 0);
336
+ this.#controls.pointEnd.set(0, 0, 0);
337
+ this.#rotationAmount = 0;
338
+ this.#freeRotationX = 0;
339
+ this.#freeRotationY = 0;
340
+ }
341
+ #captureParentTransform(object) {
342
+ if (object.parent === null) {
343
+ this.#parentPosition.set(0, 0, 0);
344
+ this.#parentQuaternion.identity();
345
+ this.#parentQuaternionInv.identity();
346
+ this.#parentScale.set(1, 1, 1);
347
+ return;
348
+ }
349
+ object.parent.updateWorldMatrix(true, false);
350
+ object.parent.matrixWorld.decompose(this.#parentPosition, this.#parentQuaternion, this.#parentScale);
351
+ this.#parentQuaternionInv.copy(this.#parentQuaternion).invert();
352
+ }
353
+ #applyCurrentTransform(deltaTime, transformX, transformY) {
354
+ switch (this.#controls.mode) {
355
+ case "translate": return this.#applyTranslate(deltaTime, transformX, transformY);
356
+ case "rotate": return this.#applyRotate(deltaTime, transformX, transformY);
357
+ case "scale": return this.#applyScale(deltaTime, transformX, transformY);
358
+ }
359
+ }
360
+ #applyTranslate(deltaTime, transformX, transformY) {
361
+ const controls = this.#controls;
362
+ const object = controls.object;
363
+ const axis = controls.axis;
364
+ if (object === void 0 || axis === null) return false;
365
+ this.#updateCameraState(object);
366
+ this.#worldDelta.set(0, 0, 0);
367
+ const scale = controls.axis === "XYZ" ? "world" : controls.space;
368
+ const speed = controls.axis === "XYZ" ? this.#options.translateSpeed * deltaTime : (this.#viewSize.x + this.#viewSize.y) / 2 * this.#options.translateSpeed * deltaTime;
369
+ if (axis === "XYZ") {
370
+ this.#worldDelta.addScaledVector(this.#cameraRight, transformX * this.#viewSize.x * speed);
371
+ this.#worldDelta.addScaledVector(this.#cameraUp, -transformY * this.#viewSize.y * speed);
372
+ } else this.#addAxisTranslation(axis, scale, transformX, transformY, speed);
373
+ if (this.#worldDelta.lengthSq() === 0) return false;
374
+ this.#worldDeltaToLocalDelta(this.#worldDelta, this.#localDelta);
375
+ this.#accumulatedPosition.add(this.#localDelta);
376
+ this.#applyAccumulatedPosition(object, axis, scale);
377
+ return true;
378
+ }
379
+ #addAxisTranslation(axis, space, transformX, transformY, distance) {
380
+ for (const letter of [
381
+ "X",
382
+ "Y",
383
+ "Z"
384
+ ]) {
385
+ if (!axis.includes(letter)) continue;
386
+ this.#getTransformAxisWorld(letter, space, this.#axisWorld);
387
+ this.#worldDelta.addScaledVector(this.#axisWorld, this.#getProjectedAxisInput(this.#axisWorld, transformX, transformY, this.#getDominantInput(transformX, -transformY)) * distance);
388
+ }
389
+ }
390
+ #applyAccumulatedPosition(object, axis, space) {
391
+ this.#snappedPosition.copy(this.#accumulatedPosition);
392
+ if (this.#controls.translationSnap !== null) this.#snapPosition(object, axis, space, this.#controls.translationSnap);
393
+ this.#snappedPosition.x = Math.max(this.#controls.minX, Math.min(this.#controls.maxX, this.#snappedPosition.x));
394
+ this.#snappedPosition.y = Math.max(this.#controls.minY, Math.min(this.#controls.maxY, this.#snappedPosition.y));
395
+ this.#snappedPosition.z = Math.max(this.#controls.minZ, Math.min(this.#controls.maxZ, this.#snappedPosition.z));
396
+ object.position.copy(this.#snappedPosition);
397
+ object.updateMatrixWorld();
398
+ }
399
+ #snapPosition(object, axis, space, snap) {
400
+ if (snap <= 0) return;
401
+ if (space === "world") {
402
+ this.#localPositionToWorld(object, this.#snappedPosition);
403
+ for (const letter of [
404
+ "X",
405
+ "Y",
406
+ "Z"
407
+ ]) if (axis.includes(letter)) this.#setVectorComponent(this.#worldPosition, letter, this.#snapValue(this.#getVectorComponent(this.#worldPosition, letter), snap));
408
+ this.#worldPositionToLocal(object, this.#worldPosition);
409
+ return;
410
+ }
411
+ this.#tempQuaternion.copy(this.#quaternionStart).invert();
412
+ this.#snappedPosition.applyQuaternion(this.#tempQuaternion);
413
+ for (const letter of [
414
+ "X",
415
+ "Y",
416
+ "Z"
417
+ ]) if (axis.includes(letter)) this.#setVectorComponent(this.#snappedPosition, letter, this.#snapValue(this.#getVectorComponent(this.#snappedPosition, letter), snap));
418
+ this.#snappedPosition.applyQuaternion(this.#quaternionStart);
419
+ }
420
+ #applyRotate(deltaTime, transformX, transformY) {
421
+ const controls = this.#controls;
422
+ const object = controls.object;
423
+ const axis = controls.axis;
424
+ if (object === void 0 || axis === null) return false;
425
+ this.#updateCameraState(object);
426
+ const angleScale = this.#options.rotateSpeed * deltaTime * Math.PI;
427
+ if (axis === "XYZE") {
428
+ this.#freeRotationX += transformX * angleScale;
429
+ this.#freeRotationY += -transformY * angleScale;
430
+ this.#applyFreeRotation(object);
431
+ return true;
432
+ }
433
+ let input;
434
+ if (axis === "E") {
435
+ this.#axisWorld.copy(this.#cameraForward).normalize();
436
+ input = this.#getDominantInput(transformX, -transformY);
437
+ } else if (axis === "X" || axis === "Y" || axis === "Z") {
438
+ const space = controls.space;
439
+ this.#getTransformAxisWorld(axis, space, this.#axisWorld);
440
+ input = this.#getRotationAxisInput(this.#axisWorld, transformX, transformY);
441
+ } else return false;
442
+ this.#rotationAmount += input * angleScale;
443
+ const angle = this.#snapRotation(this.#rotationAmount);
444
+ if (axis !== "E" && controls.space === "local") {
445
+ this.#setUnitAxis(axis, this.#axisLocal);
446
+ this.#rotationQuaternion.setFromAxisAngle(this.#axisLocal, angle);
447
+ object.quaternion.copy(this.#quaternionStart).multiply(this.#rotationQuaternion).normalize();
448
+ } else this.#applyWorldRotation(object, this.#axisWorld, angle);
449
+ object.updateMatrixWorld();
450
+ return true;
451
+ }
452
+ #applyWorldRotation(object, worldAxis, angle) {
453
+ this.#axisLocal.copy(worldAxis).applyQuaternion(this.#parentQuaternionInv).normalize();
454
+ this.#rotationQuaternion.setFromAxisAngle(this.#axisLocal, angle);
455
+ object.quaternion.copy(this.#rotationQuaternion).multiply(this.#quaternionStart).normalize();
456
+ }
457
+ #applyFreeRotation(object) {
458
+ const angleX = this.#snapRotation(this.#freeRotationX);
459
+ const angleY = this.#snapRotation(this.#freeRotationY);
460
+ this.#axisWorld.copy(this.#cameraUp).normalize();
461
+ this.#axisWorld2.copy(this.#cameraRight).normalize();
462
+ this.#axisLocal.copy(this.#axisWorld).applyQuaternion(this.#parentQuaternionInv).normalize();
463
+ this.#rotationQuaternion.setFromAxisAngle(this.#axisLocal, angleX);
464
+ this.#axisLocal.copy(this.#axisWorld2).applyQuaternion(this.#parentQuaternionInv).normalize();
465
+ this.#rotationQuaternion2.setFromAxisAngle(this.#axisLocal, angleY);
466
+ object.quaternion.copy(this.#rotationQuaternion2).multiply(this.#rotationQuaternion).multiply(this.#quaternionStart).normalize();
467
+ object.updateMatrixWorld();
468
+ }
469
+ #applyScale(deltaTime, transformX, transformY) {
470
+ const controls = this.#controls;
471
+ const object = controls.object;
472
+ const axis = controls.axis;
473
+ if (object === void 0 || axis === null) return false;
474
+ this.#updateCameraState(object);
475
+ const input = axis === "XYZ" ? this.#getDominantInput(transformX, -transformY) : this.#getProjectedScaleInput(axis, transformX, transformY);
476
+ const factor = Math.exp(input * this.#options.scaleSpeed * deltaTime);
477
+ if (!Number.isFinite(factor) || factor === 1) return false;
478
+ if (axis.includes("X")) this.#accumulatedScale.x *= factor;
479
+ if (axis.includes("Y")) this.#accumulatedScale.y *= factor;
480
+ if (axis.includes("Z")) this.#accumulatedScale.z *= factor;
481
+ this.#snappedScale.copy(this.#accumulatedScale);
482
+ if (controls.scaleSnap !== null && controls.scaleSnap > 0) {
483
+ if (axis.includes("X")) this.#snappedScale.x = this.#snapScale(this.#snappedScale.x, controls.scaleSnap);
484
+ if (axis.includes("Y")) this.#snappedScale.y = this.#snapScale(this.#snappedScale.y, controls.scaleSnap);
485
+ if (axis.includes("Z")) this.#snappedScale.z = this.#snapScale(this.#snappedScale.z, controls.scaleSnap);
486
+ }
487
+ object.scale.copy(this.#snappedScale);
488
+ object.updateMatrixWorld();
489
+ return true;
490
+ }
491
+ #getProjectedScaleInput(axis, transformX, transformY) {
492
+ if (axis !== "X" && axis !== "Y" && axis !== "Z") return this.#getDominantInput(transformX, -transformY);
493
+ this.#getTransformAxisWorld(axis, "local", this.#axisWorld);
494
+ return this.#getProjectedAxisInput(this.#axisWorld, transformX, transformY, this.#getDominantInput(transformX, -transformY));
495
+ }
496
+ #updateCameraState(object) {
497
+ const camera = this.#controls.camera;
498
+ camera.updateMatrixWorld();
499
+ object.updateWorldMatrix(true, false);
500
+ this.#cameraRight.setFromMatrixColumn(camera.matrixWorld, 0).normalize();
501
+ this.#cameraUp.setFromMatrixColumn(camera.matrixWorld, 1).normalize();
502
+ camera.getWorldDirection(this.#cameraForward).normalize();
503
+ camera.getWorldPosition(this.#cameraWorldPosition);
504
+ this.#worldPosition.setFromMatrixPosition(object.matrixWorld);
505
+ this.#eye.copy(this.#cameraWorldPosition).sub(this.#worldPosition).normalize();
506
+ this.#updateViewSizeAtObjectDepth(camera);
507
+ }
508
+ #updateViewSizeAtObjectDepth(camera) {
509
+ if (this.#isOrthographicCamera(camera)) {
510
+ this.#viewSize.set(Math.abs(camera.right - camera.left) / camera.zoom, Math.abs(camera.top - camera.bottom) / camera.zoom);
511
+ return;
512
+ }
513
+ if (this.#isPerspectiveCamera(camera)) {
514
+ const depth = Math.max(Number.EPSILON, this.#worldPosition.subVectors(this.#worldPosition, this.#cameraWorldPosition).dot(this.#cameraForward));
515
+ const height = 2 * Math.tan(camera.fov * Math.PI / 360) * depth;
516
+ this.#viewSize.set(height * camera.aspect, height);
517
+ return;
518
+ }
519
+ this.#viewSize.set(1, 1);
520
+ }
521
+ #getTransformAxisWorld(axis, space, target) {
522
+ this.#setUnitAxis(axis, target);
523
+ if (space === "local") target.applyQuaternion(this.#worldQuaternionStart);
524
+ return target.normalize();
525
+ }
526
+ #getRotationAxisInput(axisWorld, transformX, transformY) {
527
+ this.#axisWorld2.crossVectors(axisWorld, this.#eye);
528
+ if (this.#axisWorld2.lengthSq() < PROJECTED_AXIS_EPSILON) return this.#getDominantInput(transformX, -transformY);
529
+ return this.#getProjectedAxisInput(this.#axisWorld2.normalize(), transformX, transformY, this.#getDominantInput(transformX, -transformY));
530
+ }
531
+ #getProjectedAxisInput(axisWorld, transformX, transformY, fallback) {
532
+ const screenX = axisWorld.dot(this.#cameraRight);
533
+ const screenY = -axisWorld.dot(this.#cameraUp);
534
+ const length = Math.hypot(screenX, screenY);
535
+ if (length < PROJECTED_AXIS_EPSILON) return fallback;
536
+ return (transformX * screenX + transformY * screenY) / length;
537
+ }
538
+ #worldDeltaToLocalDelta(worldDelta, target) {
539
+ target.copy(worldDelta).applyQuaternion(this.#parentQuaternionInv);
540
+ this.#divideByParentScale(target);
541
+ return target;
542
+ }
543
+ #localPositionToWorld(object, localPosition) {
544
+ if (object.parent === null) {
545
+ this.#worldPosition.copy(localPosition);
546
+ return;
547
+ }
548
+ object.parent.updateWorldMatrix(true, false);
549
+ this.#worldPosition.copy(localPosition).applyMatrix4(object.parent.matrixWorld);
550
+ }
551
+ #worldPositionToLocal(object, worldPosition) {
552
+ if (object.parent === null) {
553
+ this.#snappedPosition.copy(worldPosition);
554
+ return;
555
+ }
556
+ object.parent.updateWorldMatrix(true, false);
557
+ this.#parentInverse.copy(object.parent.matrixWorld).invert();
558
+ this.#snappedPosition.copy(worldPosition).applyMatrix4(this.#parentInverse);
559
+ }
560
+ #divideByParentScale(target) {
561
+ target.x = this.#parentScale.x === 0 ? 0 : target.x / this.#parentScale.x;
562
+ target.y = this.#parentScale.y === 0 ? 0 : target.y / this.#parentScale.y;
563
+ target.z = this.#parentScale.z === 0 ? 0 : target.z / this.#parentScale.z;
564
+ }
565
+ #setUnitAxis(axis, target) {
566
+ switch (axis) {
567
+ case "X": return target.set(1, 0, 0);
568
+ case "Y": return target.set(0, 1, 0);
569
+ case "Z": return target.set(0, 0, 1);
570
+ }
571
+ }
572
+ #getVectorComponent(vector, axis) {
573
+ switch (axis) {
574
+ case "X": return vector.x;
575
+ case "Y": return vector.y;
576
+ case "Z": return vector.z;
577
+ }
578
+ }
579
+ #setVectorComponent(vector, axis, value) {
580
+ switch (axis) {
581
+ case "X":
582
+ vector.x = value;
583
+ return;
584
+ case "Y":
585
+ vector.y = value;
586
+ return;
587
+ case "Z":
588
+ vector.z = value;
589
+ return;
590
+ }
591
+ }
592
+ #snapRotation(value) {
593
+ const snap = this.#controls.rotationSnap;
594
+ return snap === null || snap <= 0 ? value : this.#snapValue(value, snap);
595
+ }
596
+ #snapValue(value, snap) {
597
+ return Math.round(value / snap) * snap;
598
+ }
599
+ #snapScale(value, snap) {
600
+ return this.#snapValue(value, snap) || snap;
601
+ }
602
+ #getDominantInput(inputX, inputY) {
603
+ return Math.abs(inputX) >= Math.abs(inputY) ? inputX : inputY;
604
+ }
605
+ #getStartedButtons(gamepad) {
606
+ const startedButtons = /* @__PURE__ */ new Set();
607
+ for (let index = 0; index < gamepad.buttons.length; index += 1) {
608
+ const pressed = gamepad.buttons[index]?.pressed ?? false;
609
+ const wasPressed = this.#pressedButtons.has(index);
610
+ if (pressed) {
611
+ this.#pressedButtons.add(index);
612
+ if (!wasPressed) startedButtons.add(index);
613
+ } else this.#pressedButtons.delete(index);
614
+ }
615
+ return startedButtons;
616
+ }
617
+ #isPerspectiveCamera(camera) {
618
+ return camera.isPerspectiveCamera === true;
619
+ }
620
+ #isOrthographicCamera(camera) {
621
+ return camera.isOrthographicCamera === true;
622
+ }
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
+ };
633
+ //#endregion
634
+ export { GamepadTransformControls };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue } from "./core.js";
2
2
  import { GamepadControls, GamepadControlsEventMap } from "./gamepad-controls.js";
3
3
  import { GamepadArcballControls, GamepadArcballControlsOptions } from "./gamepad-arcball-controls.js";
4
+ import { GamepadDragControls, GamepadDragControlsOptions } from "./gamepad-drag-controls.js";
4
5
  import { GamepadFirstPersonControls, GamepadFirstPersonControlsOptions } from "./gamepad-first-person-controls.js";
5
6
  import { GamepadFlyControls, GamepadFlyControlsOptions } from "./gamepad-fly-controls.js";
6
7
  import { GamepadOrbitControls, GamepadOrbitControlsOptions } from "./gamepad-orbit-controls.js";
7
8
  import { GamepadMapControls } from "./gamepad-map-controls.js";
8
9
  import { GamepadPointerLockControls, GamepadPointerLockControlsOptions } from "./gamepad-pointer-lock-controls.js";
9
10
  import { GamepadTrackballControls, GamepadTrackballControlsOptions } from "./gamepad-trackball-controls.js";
10
- export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadArcballControlsOptions, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, GamepadControls, GamepadControlsEventMap, GamepadFirstPersonControls, GamepadFirstPersonControlsOptions, GamepadFlyControls, GamepadFlyControlsOptions, GamepadMapControls, GamepadOrbitControls, GamepadOrbitControlsOptions, GamepadPointerLockControls, GamepadPointerLockControlsOptions, GamepadTrackballControls, GamepadTrackballControlsOptions };
11
+ import { GamepadTransformControls, GamepadTransformControlsOptions } from "./gamepad-transform-controls.js";
12
+ export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadArcballControlsOptions, GamepadAxisKey, GamepadAxisValue, GamepadButtonKey, GamepadButtonValue, GamepadControls, GamepadControlsEventMap, GamepadDragControls, GamepadDragControlsOptions, GamepadFirstPersonControls, GamepadFirstPersonControlsOptions, GamepadFlyControls, GamepadFlyControlsOptions, GamepadMapControls, GamepadOrbitControls, GamepadOrbitControlsOptions, GamepadPointerLockControls, GamepadPointerLockControlsOptions, GamepadTrackballControls, GamepadTrackballControlsOptions, GamepadTransformControls, GamepadTransformControlsOptions };
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { GAMEPAD_AXIS, GAMEPAD_BUTTON } from "./core.js";
2
2
  import { GamepadControls } from "./gamepad-controls.js";
3
3
  import { GamepadArcballControls } from "./gamepad-arcball-controls.js";
4
+ import { GamepadDragControls } from "./gamepad-drag-controls.js";
4
5
  import { GamepadFirstPersonControls } from "./gamepad-first-person-controls.js";
5
6
  import { GamepadFlyControls } from "./gamepad-fly-controls.js";
6
7
  import { GamepadOrbitControls } from "./gamepad-orbit-controls.js";
7
8
  import { GamepadMapControls } from "./gamepad-map-controls.js";
8
9
  import { GamepadPointerLockControls } from "./gamepad-pointer-lock-controls.js";
9
10
  import { GamepadTrackballControls } from "./gamepad-trackball-controls.js";
10
- export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadControls, GamepadFirstPersonControls, GamepadFlyControls, GamepadMapControls, GamepadOrbitControls, GamepadPointerLockControls, GamepadTrackballControls };
11
+ import { GamepadTransformControls } from "./gamepad-transform-controls.js";
12
+ export { GAMEPAD_AXIS, GAMEPAD_BUTTON, GamepadArcballControls, GamepadControls, GamepadDragControls, GamepadFirstPersonControls, GamepadFlyControls, GamepadMapControls, GamepadOrbitControls, GamepadPointerLockControls, GamepadTrackballControls, GamepadTransformControls };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "three-gamepad-controls",
3
3
  "description": "Gamepad support for Three.js controls.",
4
4
  "author": "Kasnix",
5
- "version": "0.8.0",
5
+ "version": "0.10.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/luckasnix/three-gamepad-controls.git"