three-gamepad-controls 0.24.1 → 0.24.2
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-transform-controls.js +227 -59
- package/package.json +1 -1
|
@@ -99,8 +99,18 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
99
99
|
#rotationQuaternion;
|
|
100
100
|
#rotationQuaternion2;
|
|
101
101
|
#tempQuaternion;
|
|
102
|
-
#
|
|
103
|
-
#
|
|
102
|
+
#segment = null;
|
|
103
|
+
#updating = false;
|
|
104
|
+
#ending = false;
|
|
105
|
+
#disposed = false;
|
|
106
|
+
#interrupted = false;
|
|
107
|
+
#needsNeutral = false;
|
|
108
|
+
#pointerRevision = 0;
|
|
109
|
+
#mouseDownEvent = {
|
|
110
|
+
type: "mouseDown",
|
|
111
|
+
mode: "translate"
|
|
112
|
+
};
|
|
113
|
+
#onNativeMouseDown;
|
|
104
114
|
#rotationAmount = 0;
|
|
105
115
|
#freeRotationX = 0;
|
|
106
116
|
#freeRotationY = 0;
|
|
@@ -151,6 +161,10 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
151
161
|
this.#rotationQuaternion = new Quaternion();
|
|
152
162
|
this.#rotationQuaternion2 = new Quaternion();
|
|
153
163
|
this.#tempQuaternion = new Quaternion();
|
|
164
|
+
this.#onNativeMouseDown = (event) => {
|
|
165
|
+
if (event !== this.#mouseDownEvent) this.#pointerRevision += 1;
|
|
166
|
+
};
|
|
167
|
+
controls.addEventListener("mouseDown", this.#onNativeMouseDown);
|
|
154
168
|
}
|
|
155
169
|
/**
|
|
156
170
|
* Maps the current gamepad state to `TransformControls` mode, axis,
|
|
@@ -159,9 +173,33 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
159
173
|
* @param deltaTime - Seconds since the last frame.
|
|
160
174
|
*/
|
|
161
175
|
onUpdate(deltaTime) {
|
|
176
|
+
if (this.#updating || this.#ending) return;
|
|
177
|
+
this.#updating = true;
|
|
178
|
+
this.#interrupted = false;
|
|
179
|
+
try {
|
|
180
|
+
this.#updateTransform(deltaTime);
|
|
181
|
+
} finally {
|
|
182
|
+
this.#updating = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Processes one frame of selection, reset, and movement while respecting
|
|
187
|
+
* segment ownership and the neutral input required before reacquisition.
|
|
188
|
+
*
|
|
189
|
+
* @param deltaTime - Seconds since the last frame.
|
|
190
|
+
*/
|
|
191
|
+
#updateTransform(deltaTime) {
|
|
162
192
|
const controls = this.#controls;
|
|
193
|
+
const { transformStick } = this.#options;
|
|
194
|
+
const transform = this.gamepadInput.stick(transformStick.xAxis, transformStick.yAxis, transformStick.pipeline);
|
|
195
|
+
const neutral = transform.x === 0 && transform.y === 0;
|
|
196
|
+
this.#reconcileSegment();
|
|
197
|
+
if (neutral) this.#needsNeutral = false;
|
|
163
198
|
if (!this.#canApplyInput()) return;
|
|
164
|
-
if (controls.dragging &&
|
|
199
|
+
if (controls.dragging && this.#segment === null) {
|
|
200
|
+
this.#needsNeutral = !neutral;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
165
203
|
const startedButtons = this.#getStartedButtons();
|
|
166
204
|
this.#handleModeAndAxisButtons(startedButtons);
|
|
167
205
|
if (!this.#canApplyInput()) return;
|
|
@@ -172,31 +210,32 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
172
210
|
this.#endTransform(true);
|
|
173
211
|
return;
|
|
174
212
|
}
|
|
175
|
-
|
|
176
|
-
if (!this.#canApplyInput()) return;
|
|
177
|
-
if (axis === null) {
|
|
178
|
-
this.#endTransform(true);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const { transformStick } = this.#options;
|
|
182
|
-
const transform = this.gamepadInput.stick(transformStick.xAxis, transformStick.yAxis, transformStick.pipeline);
|
|
183
|
-
if (transform.x === 0 && transform.y === 0) {
|
|
213
|
+
if (neutral) {
|
|
184
214
|
this.#endTransform(false);
|
|
185
215
|
return;
|
|
186
216
|
}
|
|
187
|
-
if (
|
|
217
|
+
if (this.#needsNeutral) return;
|
|
218
|
+
const axis = this.#resolveAxis();
|
|
219
|
+
this.#setActiveAxis(axis);
|
|
188
220
|
if (!this.#canApplyInput()) return;
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
if (axis === null) return;
|
|
222
|
+
if (this.#segment === null) this.#startTransform(object, axis);
|
|
223
|
+
if (!this.#canApplyInput()) return;
|
|
224
|
+
const segment = this.#segment;
|
|
225
|
+
if (!segment.started) {
|
|
226
|
+
segment.started = true;
|
|
227
|
+
const context = this.#readContext();
|
|
228
|
+
this.#mouseDownEvent.mode = segment.mode;
|
|
229
|
+
controls.dispatchEvent(this.#mouseDownEvent);
|
|
230
|
+
this.#afterCallback(context);
|
|
195
231
|
}
|
|
196
232
|
if (!this.#canApplyInput()) return;
|
|
197
233
|
if (this.#applyCurrentTransform(object, axis, deltaTime, transform.x, transform.y)) {
|
|
234
|
+
const context = this.#readContext();
|
|
198
235
|
controls.dispatchEvent({ type: "change" });
|
|
236
|
+
if (!this.#afterCallback(context)) return;
|
|
199
237
|
controls.dispatchEvent({ type: "objectChange" });
|
|
238
|
+
this.#afterCallback(context);
|
|
200
239
|
}
|
|
201
240
|
}
|
|
202
241
|
/**
|
|
@@ -204,7 +243,13 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
204
243
|
*/
|
|
205
244
|
dispose() {
|
|
206
245
|
super.dispose();
|
|
207
|
-
this.#
|
|
246
|
+
this.#disposed = true;
|
|
247
|
+
if (this.#ending) return;
|
|
248
|
+
try {
|
|
249
|
+
this.#endTransform(true);
|
|
250
|
+
} finally {
|
|
251
|
+
this.#controls.removeEventListener("mouseDown", this.#onNativeMouseDown);
|
|
252
|
+
}
|
|
208
253
|
}
|
|
209
254
|
/**
|
|
210
255
|
* Ends any active transform if the active gamepad disconnects mid-drag.
|
|
@@ -215,12 +260,91 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
215
260
|
this.#endTransform(true);
|
|
216
261
|
super.onGamepadDisconnected(gamepad);
|
|
217
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Checks whether this update can continue applying gamepad input.
|
|
265
|
+
* Native disable ends the owned segment; a wrapper pause retains it.
|
|
266
|
+
*
|
|
267
|
+
* @returns `true` when input remains enabled, a gamepad is available,
|
|
268
|
+
* and no callback has interrupted this update.
|
|
269
|
+
*/
|
|
218
270
|
#canApplyInput() {
|
|
219
271
|
if (!this.#controls.enabled) {
|
|
220
272
|
this.#endTransform(true);
|
|
221
273
|
return false;
|
|
222
274
|
}
|
|
223
|
-
return this.enabled && this.gamepad !== null;
|
|
275
|
+
return !this.#interrupted && this.enabled && this.gamepad !== null;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Captures the native context and pointer acquisition revision without
|
|
279
|
+
* modifying the control or copying the attached object's transform.
|
|
280
|
+
*
|
|
281
|
+
* @returns A context snapshot for segment and callback validation.
|
|
282
|
+
*/
|
|
283
|
+
#readContext() {
|
|
284
|
+
const { object, mode, space, axis, dragging } = this.#controls;
|
|
285
|
+
return {
|
|
286
|
+
object,
|
|
287
|
+
mode,
|
|
288
|
+
space,
|
|
289
|
+
axis,
|
|
290
|
+
dragging,
|
|
291
|
+
pointerRevision: this.#pointerRevision
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Compares the current native context and pointer revision with a snapshot.
|
|
296
|
+
*
|
|
297
|
+
* @param context - Expected object, selection, dragging state, and pointer revision.
|
|
298
|
+
* @returns `true` when every captured context field still matches.
|
|
299
|
+
*/
|
|
300
|
+
#matchesContext(context) {
|
|
301
|
+
const controls = this.#controls;
|
|
302
|
+
return controls.object === context.object && controls.mode === context.mode && controls.space === context.space && controls.axis === context.axis && controls.dragging === context.dragging && this.#pointerRevision === context.pointerRevision;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Ends the owned segment when its context changes or its axis is disallowed.
|
|
306
|
+
* A change of attached object also requires neutral input before reacquisition.
|
|
307
|
+
*/
|
|
308
|
+
#reconcileSegment() {
|
|
309
|
+
const segment = this.#segment;
|
|
310
|
+
if (segment === null) return;
|
|
311
|
+
if (!this.#matchesContext(segment) || !this.#isAxisAllowed(segment.mode, segment.axis)) {
|
|
312
|
+
if (this.#controls.object !== segment.object) this.#needsNeutral = true;
|
|
313
|
+
this.#endTransform(false);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Revalidates context, segment ownership, and permissions after synchronous
|
|
318
|
+
* callbacks, interrupting this update if its context or segment was invalidated.
|
|
319
|
+
*
|
|
320
|
+
* @param context - Context expected after the operation that invoked callbacks.
|
|
321
|
+
* @returns `true` when this update may continue applying gamepad input.
|
|
322
|
+
*/
|
|
323
|
+
#afterCallback(context) {
|
|
324
|
+
const segment = this.#segment;
|
|
325
|
+
if (!this.#matchesContext(context)) {
|
|
326
|
+
this.#interrupted = true;
|
|
327
|
+
if (this.#controls.object !== context.object) this.#needsNeutral = true;
|
|
328
|
+
}
|
|
329
|
+
this.#reconcileSegment();
|
|
330
|
+
if (segment !== null && this.#segment !== segment) this.#interrupted = true;
|
|
331
|
+
return this.#canApplyInput();
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Writes a native property and revalidates the context after its synchronous
|
|
335
|
+
* notifications, accounting for the intended property change.
|
|
336
|
+
*
|
|
337
|
+
* @param key - Native selection or dragging property to update.
|
|
338
|
+
* @param value - Value to assign to the selected property.
|
|
339
|
+
*/
|
|
340
|
+
#writeProperty(key, value) {
|
|
341
|
+
const context = {
|
|
342
|
+
...this.#readContext(),
|
|
343
|
+
[key]: value
|
|
344
|
+
};
|
|
345
|
+
const controls = this.#controls;
|
|
346
|
+
controls[key] = value;
|
|
347
|
+
this.#afterCallback(context);
|
|
224
348
|
}
|
|
225
349
|
/**
|
|
226
350
|
* Applies mode, space, and axis button transitions from the current frame.
|
|
@@ -249,14 +373,27 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
249
373
|
if (this.#controls.mode === mode) return;
|
|
250
374
|
this.#endTransform(false);
|
|
251
375
|
if (!this.#canApplyInput()) return;
|
|
376
|
+
const context = {
|
|
377
|
+
...this.#readContext(),
|
|
378
|
+
mode
|
|
379
|
+
};
|
|
252
380
|
this.#controls.setMode(mode);
|
|
253
|
-
if (this.#
|
|
381
|
+
if (this.#afterCallback(context)) this.#setActiveAxis(this.#resolveAxis(null));
|
|
254
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Ends the owned segment and toggles between local and world transform space
|
|
385
|
+
* if input remains permitted after the end notification.
|
|
386
|
+
*/
|
|
255
387
|
#toggleSpace() {
|
|
256
388
|
const nextSpace = this.#controls.space === "world" ? "local" : "world";
|
|
257
389
|
this.#endTransform(false);
|
|
258
390
|
if (!this.#canApplyInput()) return;
|
|
391
|
+
const context = {
|
|
392
|
+
...this.#readContext(),
|
|
393
|
+
space: nextSpace
|
|
394
|
+
};
|
|
259
395
|
this.#controls.setSpace(nextSpace);
|
|
396
|
+
this.#afterCallback(context);
|
|
260
397
|
}
|
|
261
398
|
/**
|
|
262
399
|
* Selects an explicit axis when it is valid for the current mode.
|
|
@@ -267,9 +404,11 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
267
404
|
if (!this.#isAxisAllowed(this.#controls.mode, axis)) return;
|
|
268
405
|
this.#endTransform(false);
|
|
269
406
|
if (!this.#canApplyInput()) return;
|
|
270
|
-
this.#
|
|
271
|
-
this.#ensureValidAxis();
|
|
407
|
+
this.#setActiveAxis(axis);
|
|
272
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Selects the next visible composite axis available in the current mode.
|
|
411
|
+
*/
|
|
273
412
|
#cycleCompositeAxis() {
|
|
274
413
|
const validAxes = this.#getVisibleAxes(COMPOSITE_AXES[this.#controls.mode]);
|
|
275
414
|
this.#cycleThroughAxes(validAxes, 1);
|
|
@@ -289,31 +428,30 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
289
428
|
* @param direction - `1` for next axis, `-1` for previous axis.
|
|
290
429
|
*/
|
|
291
430
|
#cycleThroughAxes(axes, direction) {
|
|
431
|
+
this.#endTransform(false);
|
|
432
|
+
if (!this.#canApplyInput()) return;
|
|
292
433
|
if (axes.length === 0) {
|
|
293
434
|
this.#setActiveAxis(null);
|
|
294
435
|
return;
|
|
295
436
|
}
|
|
296
|
-
this.#
|
|
297
|
-
|
|
298
|
-
const current = this.#activeAxisByMode[this.#controls.mode];
|
|
299
|
-
const currentIndex = current === null ? -1 : axes.indexOf(current);
|
|
437
|
+
const current = this.#resolveAxis();
|
|
438
|
+
const currentIndex = axes.indexOf(current);
|
|
300
439
|
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + axes.length) % axes.length;
|
|
301
|
-
this.#
|
|
302
|
-
this.#ensureValidAxis();
|
|
440
|
+
this.#setActiveAxis(axes[nextIndex]);
|
|
303
441
|
}
|
|
304
442
|
/**
|
|
305
|
-
*
|
|
443
|
+
* Resolves selection without writing to the native control or axis memory.
|
|
306
444
|
*
|
|
445
|
+
* @param nativeAxis - Preferred native axis, defaulting to the current selection.
|
|
446
|
+
* Pass `null` to use mode memory before the first allowed axis.
|
|
307
447
|
* @returns The active valid axis, or `null` when no axis is available.
|
|
308
448
|
*/
|
|
309
|
-
#
|
|
449
|
+
#resolveAxis(nativeAxis = this.#controls.axis) {
|
|
310
450
|
const mode = this.#controls.mode;
|
|
451
|
+
if (nativeAxis !== null && this.#isAxisAllowed(mode, nativeAxis)) return nativeAxis;
|
|
311
452
|
const current = this.#activeAxisByMode[mode];
|
|
312
453
|
const validAxes = this.#getValidAxes(mode);
|
|
313
|
-
|
|
314
|
-
this.#activeAxisByMode[mode] = nextAxis;
|
|
315
|
-
if (this.#controls.axis !== nextAxis) this.#controls.axis = nextAxis;
|
|
316
|
-
return nextAxis;
|
|
454
|
+
return current !== null && validAxes.includes(current) ? current : validAxes[0] ?? null;
|
|
317
455
|
}
|
|
318
456
|
/**
|
|
319
457
|
* Updates both the remembered axis for the current mode and the control axis.
|
|
@@ -322,7 +460,7 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
322
460
|
*/
|
|
323
461
|
#setActiveAxis(axis) {
|
|
324
462
|
this.#activeAxisByMode[this.#controls.mode] = axis;
|
|
325
|
-
if (this.#controls.axis !== axis) this.#
|
|
463
|
+
if (this.#controls.axis !== axis) this.#writeProperty("axis", axis);
|
|
326
464
|
}
|
|
327
465
|
/**
|
|
328
466
|
* Returns all visible axes supported by a TransformControls mode.
|
|
@@ -375,41 +513,71 @@ var GamepadTransformControls = class extends GamepadControls {
|
|
|
375
513
|
}
|
|
376
514
|
}
|
|
377
515
|
/**
|
|
378
|
-
*
|
|
516
|
+
* Captures a segment's transform origin and claims dragging ownership before
|
|
517
|
+
* notifying native property listeners. The update publishes `mouseDown` later.
|
|
379
518
|
*
|
|
380
519
|
* @param object - Object attached to TransformControls for this update.
|
|
520
|
+
* @param axis - Valid axis acquired for the new segment.
|
|
381
521
|
*/
|
|
382
|
-
#startTransform(object) {
|
|
383
|
-
const controls = this.#controls;
|
|
522
|
+
#startTransform(object, axis) {
|
|
384
523
|
this.#captureTransformStart(object);
|
|
385
|
-
this.#
|
|
386
|
-
|
|
524
|
+
this.#segment = {
|
|
525
|
+
...this.#readContext(),
|
|
526
|
+
object,
|
|
527
|
+
axis,
|
|
528
|
+
dragging: true,
|
|
529
|
+
started: false
|
|
530
|
+
};
|
|
531
|
+
this.#writeProperty("dragging", true);
|
|
387
532
|
}
|
|
388
533
|
/**
|
|
389
|
-
*
|
|
534
|
+
* Releases the owned segment and ends its published interaction once,
|
|
535
|
+
* preserving pointer ownership and context changes made by callbacks.
|
|
390
536
|
*
|
|
391
|
-
* @param clearAxis - Whether to clear the highlighted axis
|
|
537
|
+
* @param clearAxis - Whether to clear the highlighted axis if ownership
|
|
538
|
+
* and context still permit it after end notifications.
|
|
392
539
|
*/
|
|
393
540
|
#endTransform(clearAxis) {
|
|
394
|
-
|
|
541
|
+
const segment = this.#segment;
|
|
542
|
+
if (segment === null) return;
|
|
395
543
|
const controls = this.#controls;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
this.#
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
544
|
+
const context = this.#readContext();
|
|
545
|
+
this.#segment = null;
|
|
546
|
+
this.#ending = true;
|
|
547
|
+
try {
|
|
548
|
+
if (segment.started) controls.dispatchEvent({
|
|
549
|
+
type: "mouseUp",
|
|
550
|
+
mode: segment.mode
|
|
551
|
+
});
|
|
552
|
+
const unchanged = this.#matchesContext(context);
|
|
553
|
+
if (!unchanged) {
|
|
554
|
+
this.#interrupted = true;
|
|
555
|
+
if (controls.object !== context.object) this.#needsNeutral = true;
|
|
556
|
+
}
|
|
557
|
+
const ownsDragging = this.#pointerRevision === segment.pointerRevision;
|
|
558
|
+
if (ownsDragging && controls.dragging === context.dragging) this.#writeProperty("dragging", false);
|
|
559
|
+
if (clearAxis && ownsDragging && unchanged && this.#matchesContext({
|
|
560
|
+
...context,
|
|
561
|
+
dragging: false
|
|
562
|
+
}) && controls.axis === segment.axis) this.#setActiveAxis(null);
|
|
563
|
+
} finally {
|
|
564
|
+
this.#ending = false;
|
|
565
|
+
if (this.#disposed) controls.removeEventListener("mouseDown", this.#onNativeMouseDown);
|
|
566
|
+
}
|
|
406
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Restores the owned segment's transform origin through native reset and
|
|
570
|
+
* resets its accumulators if callbacks leave the same segment active.
|
|
571
|
+
*/
|
|
407
572
|
#resetActiveTransform() {
|
|
408
|
-
const
|
|
409
|
-
if (
|
|
573
|
+
const segment = this.#segment;
|
|
574
|
+
if (segment === null) return;
|
|
575
|
+
const context = this.#readContext();
|
|
410
576
|
this.#controls.reset();
|
|
411
|
-
this.#
|
|
412
|
-
this.#
|
|
577
|
+
this.#afterCallback(context);
|
|
578
|
+
if (this.#segment !== segment) return;
|
|
579
|
+
this.#accumulatedPosition.copy(segment.object.position);
|
|
580
|
+
this.#accumulatedScale.copy(segment.object.scale);
|
|
413
581
|
this.#rotationAmount = 0;
|
|
414
582
|
this.#freeRotationX = 0;
|
|
415
583
|
this.#freeRotationY = 0;
|
package/package.json
CHANGED