@relu-interactives/spatial-ecs 0.3.14 → 0.3.17

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.
@@ -114,6 +114,17 @@ export default class Rifle extends Behaviour {
114
114
 
115
115
  Every grab setting is a plain field on the component, so a script can change it mid-session — switch on resizing, retune throw strength, or move an attach point in response to what the player is doing.
116
116
 
117
+ ## Vision Pro
118
+
119
+ Vision Pro drives WebXR entirely through **gaze and pinch**. There are no controllers, and the input source for a pinch is created when the pinch starts and destroyed when it ends. That has a few consequences worth designing around:
120
+
121
+ - **Look at a thing and pinch** to select or grab it. The pick ray starts between the user's eyes and is then carried by the hand, so aiming is "pinch, then move your hand", not "point, then press".
122
+ - **There is no hover before a pinch** — the input source does not exist until then. Pointer rays and hover feedback therefore only appear *during* a pinch, not while the user looks around.
123
+ - **Grip/squeeze is never reported.** Set an interactable's **Gesture** to `Pinch` or `Any`; `Full grip` can never trigger on Vision Pro.
124
+ - Grabbing does not teleport the user, the same as on hand tracking elsewhere.
125
+ - Hand tracking (and therefore hand poses) is available on visionOS 2 and later, but asks the user for a second permission. The OS draws the user's real hands.
126
+ - `immersive-ar` is not supported by Safari on visionOS, so VR projects are the only kind that run there.
127
+
117
128
  ## Hand poses
118
129
 
119
130
  Add an **XR Hand Pose** component and list the poses to watch for. Each is either a built-in preset or a custom set of finger curls.
@@ -501,15 +501,20 @@ declare class XRInteractionSystem implements EcsSystem {
501
501
  /** True when any source is holding something. */
502
502
  isGrabbing(): boolean;
503
503
  /**
504
- * Whether this hand's primary action is committed to interaction.
504
+ * Whether this input's primary action is committed to interaction.
505
505
  *
506
- * True while the hand holds something, and also while it merely HOVERS a
506
+ * True while the source holds something, and also while it merely HOVERS a
507
507
  * grabbable — because the pinch that is about to grab must not be read as a
508
508
  * teleport by the locomotion system. Without the hover half, the first pinch
509
509
  * of a grab teleports the player, and so does the pinch that takes an object
510
510
  * from the other hand.
511
+ *
512
+ * Keyed on the INPUT SOURCE rather than handedness. Vision Pro reports
513
+ * `handedness: "none"` for every gaze-pinch (it is hard-coded in WebKit), so a
514
+ * handedness-keyed check silently never matched there and each pinch both
515
+ * grabbed and teleported.
511
516
  */
512
- isHandBusy(world: World, handedness: XrInputState["handedness"]): boolean;
517
+ isSourceBusy(world: World, input: XrInputState): boolean;
513
518
  /** True when THIS hand is holding something. */
514
519
  isGrabbingWith(handedness: XrInputState["handedness"]): boolean;
515
520
  /**
@@ -545,6 +550,8 @@ declare class XRLocomotionSystem implements EcsSystem {
545
550
  private aimValid;
546
551
  /** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
547
552
  private readonly stickAiming;
553
+ /** One-time warning when a committed teleport found nowhere to land. */
554
+ private warnedNoLanding;
548
555
  /** Gates teleport commits on a prior aim. See {@link TeleportAimGate}. */
549
556
  private readonly teleportAim;
550
557
  /** True once we've tried to self-heal camera parenting for this session. */
@@ -501,15 +501,20 @@ declare class XRInteractionSystem implements EcsSystem {
501
501
  /** True when any source is holding something. */
502
502
  isGrabbing(): boolean;
503
503
  /**
504
- * Whether this hand's primary action is committed to interaction.
504
+ * Whether this input's primary action is committed to interaction.
505
505
  *
506
- * True while the hand holds something, and also while it merely HOVERS a
506
+ * True while the source holds something, and also while it merely HOVERS a
507
507
  * grabbable — because the pinch that is about to grab must not be read as a
508
508
  * teleport by the locomotion system. Without the hover half, the first pinch
509
509
  * of a grab teleports the player, and so does the pinch that takes an object
510
510
  * from the other hand.
511
+ *
512
+ * Keyed on the INPUT SOURCE rather than handedness. Vision Pro reports
513
+ * `handedness: "none"` for every gaze-pinch (it is hard-coded in WebKit), so a
514
+ * handedness-keyed check silently never matched there and each pinch both
515
+ * grabbed and teleported.
511
516
  */
512
- isHandBusy(world: World, handedness: XrInputState["handedness"]): boolean;
517
+ isSourceBusy(world: World, input: XrInputState): boolean;
513
518
  /** True when THIS hand is holding something. */
514
519
  isGrabbingWith(handedness: XrInputState["handedness"]): boolean;
515
520
  /**
@@ -545,6 +550,8 @@ declare class XRLocomotionSystem implements EcsSystem {
545
550
  private aimValid;
546
551
  /** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
547
552
  private readonly stickAiming;
553
+ /** One-time warning when a committed teleport found nowhere to land. */
554
+ private warnedNoLanding;
548
555
  /** Gates teleport commits on a prior aim. See {@link TeleportAimGate}. */
549
556
  private readonly teleportAim;
550
557
  /** True once we've tried to self-heal camera parenting for this session. */
package/dist/index.cjs CHANGED
@@ -11728,17 +11728,22 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11728
11728
  return this.grabs.size > 0;
11729
11729
  }
11730
11730
  /**
11731
- * Whether this hand's primary action is committed to interaction.
11731
+ * Whether this input's primary action is committed to interaction.
11732
11732
  *
11733
- * True while the hand holds something, and also while it merely HOVERS a
11733
+ * True while the source holds something, and also while it merely HOVERS a
11734
11734
  * grabbable — because the pinch that is about to grab must not be read as a
11735
11735
  * teleport by the locomotion system. Without the hover half, the first pinch
11736
11736
  * of a grab teleports the player, and so does the pinch that takes an object
11737
11737
  * from the other hand.
11738
+ *
11739
+ * Keyed on the INPUT SOURCE rather than handedness. Vision Pro reports
11740
+ * `handedness: "none"` for every gaze-pinch (it is hard-coded in WebKit), so a
11741
+ * handedness-keyed check silently never matched there and each pinch both
11742
+ * grabbed and teleported.
11738
11743
  */
11739
- isHandBusy(world, handedness) {
11740
- if (this.isGrabbingWith(handedness)) return true;
11741
- const hover = this.getHoverFor(handedness);
11744
+ isSourceBusy(world, input) {
11745
+ if (this.grabs.has(input)) return true;
11746
+ const hover = this.hovers.get(input);
11742
11747
  if (!hover) return false;
11743
11748
  const interactable = world.getComponent(
11744
11749
  hover.entityId,
@@ -11832,7 +11837,7 @@ var _TeleportAimGate = class _TeleportAimGate {
11832
11837
  if (aiming) this.aiming.add(source);
11833
11838
  if (!releasing) return false;
11834
11839
  this.aiming.delete(source);
11835
- return hadAim;
11840
+ return hadAim || aiming;
11836
11841
  }
11837
11842
  /** True if this source currently has an aim engaged. */
11838
11843
  isAiming(source) {
@@ -11870,6 +11875,8 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
11870
11875
  this.aimValid = false;
11871
11876
  /** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
11872
11877
  this.stickAiming = /* @__PURE__ */ new WeakSet();
11878
+ /** One-time warning when a committed teleport found nowhere to land. */
11879
+ this.warnedNoLanding = false;
11873
11880
  /** Gates teleport commits on a prior aim. See {@link TeleportAimGate}. */
11874
11881
  this.teleportAim = new TeleportAimGate();
11875
11882
  /** True once we've tried to self-heal camera parenting for this session. */
@@ -12006,7 +12013,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12006
12013
  const targets = this.collectTeleportTargets(world);
12007
12014
  for (const input of inputs) {
12008
12015
  const source = input;
12009
- if (input.handedness !== "none" && this.interactionSystem?.isHandBusy(world, input.handedness)) {
12016
+ if (this.interactionSystem?.isSourceBusy(world, input)) {
12010
12017
  if (this.teleportAim.cancel(source)) {
12011
12018
  this.aimCurve = [];
12012
12019
  this.landingPoint = null;
@@ -12015,8 +12022,9 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12015
12022
  continue;
12016
12023
  }
12017
12024
  const stick = this.readStickAim(input, source);
12018
- const aiming = input.select || stick.aiming;
12019
- const releasing = input.selectEnd || stick.released;
12025
+ const aiming = input.select || input.selectStart || stick.aiming;
12026
+ const vanished = !input.connected && this.teleportAim.isAiming(source);
12027
+ const releasing = input.selectEnd || stick.released || vanished;
12020
12028
  const commit = this.teleportAim.update(source, aiming, releasing);
12021
12029
  if (!aiming && !commit) continue;
12022
12030
  const points = sampleTeleportArc(
@@ -12036,6 +12044,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12036
12044
  this.landingPoint = null;
12037
12045
  this.aimValid = false;
12038
12046
  } else if (commit) {
12047
+ if (!this.warnedNoLanding) {
12048
+ this.warnedNoLanding = true;
12049
+ console.warn(
12050
+ "[XRLocomotionSystem] Teleport released but the arc did not land on a Teleport Area. Add a teleportArea entity where the player should be able to go."
12051
+ );
12052
+ }
12039
12053
  this.aimCurve = [];
12040
12054
  this.aimValid = false;
12041
12055
  }