@relu-interactives/spatial-ecs 0.3.7 → 0.3.9

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.
Files changed (47) hide show
  1. package/dist/{System-TmC2EijF.d.cts → System-31wk-A0g.d.cts} +1 -1
  2. package/dist/{System-TmC2EijF.d.ts → System-31wk-A0g.d.ts} +1 -1
  3. package/dist/{XRInteractor-CCUzSqg9.d.ts → XRHandPose-CxPz8VBp.d.ts} +139 -20
  4. package/dist/{XRInteractor-I2q6TKtF.d.cts → XRHandPose-VuSbe4iz.d.cts} +139 -20
  5. package/dist/{chunk-FQTOC7HK.js → chunk-KIKIWCEB.js} +14 -5
  6. package/dist/chunk-KIKIWCEB.js.map +1 -0
  7. package/dist/{chunk-N4GKZ7IV.js → chunk-KVGGAL5V.js} +227 -3
  8. package/dist/chunk-KVGGAL5V.js.map +1 -0
  9. package/dist/{chunk-JRJ3CL5H.js → chunk-L5BP727H.js} +3 -3
  10. package/dist/{chunk-JRJ3CL5H.js.map → chunk-L5BP727H.js.map} +1 -1
  11. package/dist/{chunk-O6FJA4XW.js → chunk-W5TSYV6Q.js} +946 -50
  12. package/dist/chunk-W5TSYV6Q.js.map +1 -0
  13. package/dist/components/index.cjs +98 -0
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +4 -3
  16. package/dist/components/index.d.ts +4 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/vr-and-hand-poses.md +111 -0
  19. package/dist/factories/index.cjs +58 -0
  20. package/dist/factories/index.cjs.map +1 -1
  21. package/dist/factories/index.d.cts +6 -3
  22. package/dist/factories/index.d.ts +6 -3
  23. package/dist/factories/index.js +3 -3
  24. package/dist/handPose-dFxqbFFY.d.cts +240 -0
  25. package/dist/handPose-dFxqbFFY.d.ts +240 -0
  26. package/dist/{index-BltjPEhg.d.cts → index-BIQoaKAA.d.cts} +9 -3
  27. package/dist/{index-W8pUajCW.d.ts → index-BxdXgqiI.d.ts} +9 -3
  28. package/dist/{index-_q9TVDI5.d.ts → index-C7XQyrgE.d.ts} +79 -7
  29. package/dist/{index-B9nwRDf9.d.cts → index-DyN50JZo.d.cts} +79 -7
  30. package/dist/index.cjs +1205 -45
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +118 -9
  33. package/dist/index.d.ts +118 -9
  34. package/dist/index.js +8 -8
  35. package/dist/systems/index.cjs +1161 -45
  36. package/dist/systems/index.cjs.map +1 -1
  37. package/dist/systems/index.d.cts +3 -2
  38. package/dist/systems/index.d.ts +3 -2
  39. package/dist/systems/index.js +3 -3
  40. package/dist/types.d.cts +1 -1
  41. package/dist/types.d.ts +1 -1
  42. package/dist/worldSnapshotUtils.d.cts +1 -1
  43. package/dist/worldSnapshotUtils.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-FQTOC7HK.js.map +0 -1
  46. package/dist/chunk-N4GKZ7IV.js.map +0 -1
  47. package/dist/chunk-O6FJA4XW.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -2611,6 +2611,218 @@ function createVRTeleportAreaComponent(world, entityId, ...args) {
2611
2611
  }
2612
2612
  __name(createVRTeleportAreaComponent, "createVRTeleportAreaComponent");
2613
2613
 
2614
+ // core/xr/handPosePresets.ts
2615
+ var WIDE = 0.35;
2616
+ var NARROW = 0.25;
2617
+ var EXTENDED = { curl: 0.05, tolerance: WIDE };
2618
+ var CURLED = { curl: 0.95, tolerance: WIDE };
2619
+ var THUMB_TUCKED = { curl: 0.7, tolerance: NARROW + 0.1 };
2620
+ var THUMB_RELAXED = { curl: 0.6, tolerance: 0.4 };
2621
+ function fingers(thumb, index, middle, ring, pinky) {
2622
+ return { thumb, index, middle, ring, pinky };
2623
+ }
2624
+ __name(fingers, "fingers");
2625
+ var THUMB_UP = {
2626
+ axis: "thumb",
2627
+ direction: "up",
2628
+ toleranceDegrees: 45
2629
+ };
2630
+ var THUMB_DOWN = {
2631
+ axis: "thumb",
2632
+ direction: "down",
2633
+ toleranceDegrees: 45
2634
+ };
2635
+ var HAND_POSE_PRESETS = {
2636
+ fist: {
2637
+ id: "fist",
2638
+ name: "Fist",
2639
+ fingers: fingers(THUMB_TUCKED, CURLED, CURLED, CURLED, CURLED)
2640
+ },
2641
+ "open-palm": {
2642
+ id: "open-palm",
2643
+ name: "Open Palm",
2644
+ fingers: fingers(EXTENDED, EXTENDED, EXTENDED, EXTENDED, EXTENDED)
2645
+ },
2646
+ point: {
2647
+ id: "point",
2648
+ name: "Point",
2649
+ fingers: fingers(THUMB_RELAXED, EXTENDED, CURLED, CURLED, CURLED)
2650
+ },
2651
+ "thumbs-up": {
2652
+ id: "thumbs-up",
2653
+ name: "Thumbs Up",
2654
+ fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, CURLED),
2655
+ orientation: THUMB_UP
2656
+ },
2657
+ "thumbs-down": {
2658
+ id: "thumbs-down",
2659
+ name: "Thumbs Down",
2660
+ fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, CURLED),
2661
+ orientation: THUMB_DOWN
2662
+ },
2663
+ peace: {
2664
+ id: "peace",
2665
+ name: "Peace",
2666
+ fingers: fingers(THUMB_RELAXED, EXTENDED, EXTENDED, CURLED, CURLED)
2667
+ },
2668
+ pinch: {
2669
+ id: "pinch",
2670
+ name: "Pinch",
2671
+ // Curl is left loose: what defines a pinch is the tips MEETING, and people
2672
+ // pinch with wildly different amounts of finger bend.
2673
+ fingers: fingers(
2674
+ { curl: 0.5, tolerance: 0.5 },
2675
+ { curl: 0.5, tolerance: 0.5 },
2676
+ { curl: 0.5, tolerance: 0.5 },
2677
+ { curl: 0.5, tolerance: 0.5 },
2678
+ { curl: 0.5, tolerance: 0.5 }
2679
+ ),
2680
+ pinch: { finger: "index", target: 1, tolerance: 0.25 }
2681
+ },
2682
+ ok: {
2683
+ id: "ok",
2684
+ name: "OK Sign",
2685
+ // Thumb and index meet in a ring while the rest stay up — that contrast with
2686
+ // a plain pinch is the whole gesture, so the other three are constrained.
2687
+ fingers: fingers(
2688
+ { curl: 0.5, tolerance: 0.4 },
2689
+ { curl: 0.55, tolerance: 0.35 },
2690
+ EXTENDED,
2691
+ EXTENDED,
2692
+ EXTENDED
2693
+ ),
2694
+ pinch: { finger: "index", target: 1, tolerance: 0.3 }
2695
+ },
2696
+ gun: {
2697
+ id: "gun",
2698
+ name: "Gun",
2699
+ fingers: fingers(EXTENDED, EXTENDED, CURLED, CURLED, CURLED)
2700
+ },
2701
+ rock: {
2702
+ id: "rock",
2703
+ name: "Rock Horns",
2704
+ fingers: fingers(THUMB_TUCKED, EXTENDED, CURLED, CURLED, EXTENDED)
2705
+ },
2706
+ shaka: {
2707
+ id: "shaka",
2708
+ name: "Shaka",
2709
+ fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, EXTENDED)
2710
+ },
2711
+ "palm-up": {
2712
+ id: "palm-up",
2713
+ name: "Palm Up",
2714
+ fingers: fingers(EXTENDED, EXTENDED, EXTENDED, EXTENDED, EXTENDED),
2715
+ orientation: {
2716
+ axis: "palm-normal",
2717
+ direction: "up",
2718
+ toleranceDegrees: 40
2719
+ }
2720
+ }
2721
+ };
2722
+ var HAND_POSE_PRESET_IDS = Object.keys(HAND_POSE_PRESETS);
2723
+ function getHandPosePreset(id) {
2724
+ const preset = HAND_POSE_PRESETS[id];
2725
+ if (!preset) return null;
2726
+ return cloneHandPose(preset);
2727
+ }
2728
+ __name(getHandPosePreset, "getHandPosePreset");
2729
+ function cloneHandPose(definition) {
2730
+ return {
2731
+ ...definition,
2732
+ fingers: {
2733
+ thumb: { ...definition.fingers.thumb },
2734
+ index: { ...definition.fingers.index },
2735
+ middle: { ...definition.fingers.middle },
2736
+ ring: { ...definition.fingers.ring },
2737
+ pinky: { ...definition.fingers.pinky }
2738
+ },
2739
+ pinch: definition.pinch ? { ...definition.pinch } : void 0,
2740
+ orientation: definition.orientation ? { ...definition.orientation } : void 0
2741
+ };
2742
+ }
2743
+ __name(cloneHandPose, "cloneHandPose");
2744
+ function handPoseFromCurls(id, name, curls, options) {
2745
+ const tolerance = options?.tolerance ?? NARROW;
2746
+ const target = /* @__PURE__ */ __name((curl) => ({
2747
+ curl: Math.min(1, Math.max(0, curl)),
2748
+ tolerance
2749
+ }), "target");
2750
+ return {
2751
+ id,
2752
+ name,
2753
+ fingers: {
2754
+ thumb: target(curls.thumb),
2755
+ index: target(curls.index),
2756
+ middle: target(curls.middle),
2757
+ ring: target(curls.ring),
2758
+ pinky: target(curls.pinky)
2759
+ },
2760
+ pinch: options?.pinch,
2761
+ orientation: options?.orientation,
2762
+ hand: options?.hand
2763
+ };
2764
+ }
2765
+ __name(handPoseFromCurls, "handPoseFromCurls");
2766
+
2767
+ // core/components/XRHandPose.ts
2768
+ var DEFAULT_POSE_CURLS = {
2769
+ thumb: 0,
2770
+ index: 0,
2771
+ middle: 0,
2772
+ ring: 0,
2773
+ pinky: 0
2774
+ };
2775
+ function createHandPoseEntry(partial) {
2776
+ return {
2777
+ id: partial.id,
2778
+ name: partial.name ?? partial.id,
2779
+ preset: partial.preset ?? null,
2780
+ curls: { ...DEFAULT_POSE_CURLS, ...partial.curls },
2781
+ tolerance: partial.tolerance ?? 0.25,
2782
+ hand: partial.hand ?? null,
2783
+ orientation: partial.orientation ?? null,
2784
+ minHoldSeconds: partial.minHoldSeconds ?? 0.15,
2785
+ enabled: partial.enabled ?? true
2786
+ };
2787
+ }
2788
+ __name(createHandPoseEntry, "createHandPoseEntry");
2789
+ function handPoseDefinitionFor(entry, resolvePreset) {
2790
+ if (entry.preset) {
2791
+ const preset = resolvePreset(entry.preset);
2792
+ if (!preset) return null;
2793
+ return {
2794
+ ...preset,
2795
+ id: entry.id,
2796
+ name: entry.name,
2797
+ hand: entry.hand ?? preset.hand,
2798
+ orientation: entry.orientation ?? preset.orientation
2799
+ };
2800
+ }
2801
+ return handPoseFromCurls(entry.id, entry.name, entry.curls, {
2802
+ tolerance: entry.tolerance,
2803
+ orientation: entry.orientation ?? void 0,
2804
+ hand: entry.hand ?? void 0
2805
+ });
2806
+ }
2807
+ __name(handPoseDefinitionFor, "handPoseDefinitionFor");
2808
+ var _XRHandPoseComponent = class _XRHandPoseComponent extends Component {
2809
+ constructor(options) {
2810
+ super();
2811
+ this.enabled = options?.enabled ?? true;
2812
+ this.poses = (options?.poses ?? []).map((pose) => createHandPoseEntry(pose));
2813
+ }
2814
+ };
2815
+ __name(_XRHandPoseComponent, "XRHandPoseComponent");
2816
+ var XRHandPoseComponent = _XRHandPoseComponent;
2817
+
2818
+ // core/factories/components/XRHandPoseFactory.ts
2819
+ function createXRHandPoseComponent(world, entityId, ...args) {
2820
+ const component = new XRHandPoseComponent(...args);
2821
+ world.addComponent(entityId, component);
2822
+ return component;
2823
+ }
2824
+ __name(createXRHandPoseComponent, "createXRHandPoseComponent");
2825
+
2614
2826
  // core/components/XRInteractable.ts
2615
2827
  var _XRInteractableComponent = class _XRInteractableComponent extends Component {
2616
2828
  constructor(options) {
@@ -2619,6 +2831,22 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
2619
2831
  this.grabbable = options?.grabbable ?? true;
2620
2832
  this.pointable = options?.pointable ?? true;
2621
2833
  this.grabMode = options?.grabMode ?? "kinematic";
2834
+ this.grabReach = options?.grabReach ?? "both";
2835
+ this.distanceBehavior = options?.distanceBehavior ?? "snap";
2836
+ this.grabGesture = options?.grabGesture ?? "any";
2837
+ this.grabRadius = options?.grabRadius ?? 0.15;
2838
+ this.grabOffsetPosition = options?.grabOffsetPosition ?? {
2839
+ x: 0,
2840
+ y: 0,
2841
+ z: 0
2842
+ };
2843
+ this.grabOffsetRotation = options?.grabOffsetRotation ?? {
2844
+ x: 0,
2845
+ y: 0,
2846
+ z: 0
2847
+ };
2848
+ this.throwOnRelease = options?.throwOnRelease ?? true;
2849
+ this.throwStrength = options?.throwStrength ?? 1;
2622
2850
  this.highlightOnHover = options?.highlightOnHover ?? true;
2623
2851
  }
2624
2852
  };
@@ -2683,6 +2911,7 @@ function registerDefaultComponentFactories(objectManagementSystem) {
2683
2911
  objectManagementSystem.registerComponentFactory("image", createImageComponent);
2684
2912
  objectManagementSystem.registerComponentFactory("xrRig", createXRRigComponent);
2685
2913
  objectManagementSystem.registerComponentFactory("vrTeleportArea", createVRTeleportAreaComponent);
2914
+ objectManagementSystem.registerComponentFactory("xrHandPose", createXRHandPoseComponent);
2686
2915
  objectManagementSystem.registerComponentFactory("xrInteractable", createXRInteractableComponent);
2687
2916
  objectManagementSystem.registerComponentFactory("xrInteractor", createXRInteractorComponent);
2688
2917
  objectManagementSystem.registerComponentFactory("xrInteractorSide", createXRInteractorSideComponent);
@@ -3705,6 +3934,7 @@ __export(components_exports, {
3705
3934
  DEFAULT_INSTANCE_TRANSFORM: () => DEFAULT_INSTANCE_TRANSFORM,
3706
3935
  DEFAULT_MESH_GEOMETRY_PARAMETERS: () => DEFAULT_MESH_GEOMETRY_PARAMETERS,
3707
3936
  DEFAULT_PHYSICS_STATE: () => DEFAULT_PHYSICS_STATE,
3937
+ DEFAULT_POSE_CURLS: () => DEFAULT_POSE_CURLS,
3708
3938
  DEFAULT_POSTPROCESSING_STATE: () => DEFAULT_POSTPROCESSING_STATE,
3709
3939
  DEFAULT_RIGIDBODY_STATE: () => DEFAULT_RIGIDBODY_STATE,
3710
3940
  EntityType: () => EntityType,
@@ -3734,11 +3964,14 @@ __export(components_exports, {
3734
3964
  TransformComponent: () => TransformComponent,
3735
3965
  VRTeleportAreaComponent: () => VRTeleportAreaComponent,
3736
3966
  VideoComponent: () => VideoComponent,
3967
+ XRHandPoseComponent: () => XRHandPoseComponent,
3737
3968
  XRInteractableComponent: () => XRInteractableComponent,
3738
3969
  XRInteractorComponent: () => XRInteractorComponent,
3739
3970
  XRInteractorSideComponent: () => XRInteractorSideComponent,
3740
3971
  XRRigComponent: () => XRRigComponent,
3972
+ createHandPoseEntry: () => createHandPoseEntry,
3741
3973
  defaultMaterialValue: () => defaultMaterialValue,
3974
+ handPoseDefinitionFor: () => handPoseDefinitionFor,
3742
3975
  normalizePhysicsState: () => normalizePhysicsState,
3743
3976
  setPhysicsLiveRef: () => setPhysicsLiveRef
3744
3977
  });
@@ -4427,6 +4660,10 @@ function buildComponentValueMaps(snapshot) {
4427
4660
  snapshot.components,
4428
4661
  "XRInteractorComponent"
4429
4662
  ),
4663
+ xrHandPoseValues: readByComponent(
4664
+ snapshot.components,
4665
+ "XRHandPoseComponent"
4666
+ ),
4430
4667
  xrInteractorSideValues: readByComponent(
4431
4668
  snapshot.components,
4432
4669
  "XRInteractorSideComponent"
@@ -4540,13 +4777,9 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
4540
4777
  runtimeMessage: typeof entry.runtimeMessage === "string" ? entry.runtimeMessage : void 0
4541
4778
  };
4542
4779
  }).filter((entry) => entry.className || entry.scriptUrl) : [];
4543
- if (!entries.length) {
4544
- return;
4780
+ if (entries.length) {
4781
+ world.addComponent(targetEntityId, new ScriptComponent(entries));
4545
4782
  }
4546
- world.addComponent(
4547
- targetEntityId,
4548
- new ScriptComponent(entries)
4549
- );
4550
4783
  }
4551
4784
  const sourceScriptObject = asRecord3(values.scriptObjectValues.get(sourceEntityId));
4552
4785
  if (sourceScriptObject) {
@@ -4595,10 +4828,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
4595
4828
  const sourceInteractable = asRecord3(
4596
4829
  values.xrInteractableValues.get(sourceEntityId)
4597
4830
  );
4598
- const interactable = world.getComponent(
4831
+ let interactable = world.getComponent(
4599
4832
  targetEntityId,
4600
4833
  XRInteractableComponent
4601
4834
  );
4835
+ if (sourceInteractable && !interactable) {
4836
+ interactable = new XRInteractableComponent();
4837
+ world.addComponent(targetEntityId, interactable);
4838
+ }
4602
4839
  if (sourceInteractable && interactable) {
4603
4840
  if (typeof sourceInteractable.enabled === "boolean") {
4604
4841
  interactable.enabled = sourceInteractable.enabled;
@@ -4612,11 +4849,69 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
4612
4849
  if (typeof sourceInteractable.grabMode === "string") {
4613
4850
  interactable.grabMode = sourceInteractable.grabMode;
4614
4851
  }
4852
+ if (typeof sourceInteractable.grabReach === "string") {
4853
+ interactable.grabReach = sourceInteractable.grabReach;
4854
+ }
4855
+ if (typeof sourceInteractable.distanceBehavior === "string") {
4856
+ interactable.distanceBehavior = sourceInteractable.distanceBehavior;
4857
+ }
4858
+ if (typeof sourceInteractable.grabGesture === "string") {
4859
+ interactable.grabGesture = sourceInteractable.grabGesture;
4860
+ }
4861
+ if (typeof sourceInteractable.grabRadius === "number") {
4862
+ interactable.grabRadius = sourceInteractable.grabRadius;
4863
+ }
4864
+ if (typeof sourceInteractable.throwOnRelease === "boolean") {
4865
+ interactable.throwOnRelease = sourceInteractable.throwOnRelease;
4866
+ }
4867
+ if (typeof sourceInteractable.throwStrength === "number") {
4868
+ interactable.throwStrength = sourceInteractable.throwStrength;
4869
+ }
4870
+ const grabPos = asRecord3(sourceInteractable.grabOffsetPosition);
4871
+ if (grabPos) {
4872
+ interactable.grabOffsetPosition = {
4873
+ x: typeof grabPos.x === "number" ? grabPos.x : 0,
4874
+ y: typeof grabPos.y === "number" ? grabPos.y : 0,
4875
+ z: typeof grabPos.z === "number" ? grabPos.z : 0
4876
+ };
4877
+ }
4878
+ const grabRot = asRecord3(sourceInteractable.grabOffsetRotation);
4879
+ if (grabRot) {
4880
+ interactable.grabOffsetRotation = {
4881
+ x: typeof grabRot.x === "number" ? grabRot.x : 0,
4882
+ y: typeof grabRot.y === "number" ? grabRot.y : 0,
4883
+ z: typeof grabRot.z === "number" ? grabRot.z : 0
4884
+ };
4885
+ }
4615
4886
  if (typeof sourceInteractable.highlightOnHover === "boolean") {
4616
4887
  interactable.highlightOnHover = sourceInteractable.highlightOnHover;
4617
4888
  }
4618
4889
  }
4619
4890
  }
4891
+ {
4892
+ const sourceHandPose = asRecord3(
4893
+ values.xrHandPoseValues.get(sourceEntityId)
4894
+ );
4895
+ let handPose = world.getComponent(targetEntityId, XRHandPoseComponent);
4896
+ if (sourceHandPose && !handPose) {
4897
+ handPose = new XRHandPoseComponent();
4898
+ world.addComponent(targetEntityId, handPose);
4899
+ }
4900
+ if (sourceHandPose && handPose) {
4901
+ if (typeof sourceHandPose.enabled === "boolean") {
4902
+ handPose.enabled = sourceHandPose.enabled;
4903
+ }
4904
+ if (Array.isArray(sourceHandPose.poses)) {
4905
+ handPose.poses = sourceHandPose.poses.map((entry) => asRecord3(entry)).filter(
4906
+ (entry) => entry !== null && typeof entry.id === "string"
4907
+ ).map(
4908
+ (entry) => createHandPoseEntry(
4909
+ entry
4910
+ )
4911
+ );
4912
+ }
4913
+ }
4914
+ }
4620
4915
  {
4621
4916
  const sourceInteractor = asRecord3(
4622
4917
  values.xrInteractorValues.get(sourceEntityId)
@@ -9178,6 +9473,17 @@ var _Behaviour = class _Behaviour {
9178
9473
  *
9179
9474
  * @param input - The source doing the grabbing.
9180
9475
  */
9476
+ /**
9477
+ * A hand pose declared on this entity started being held.
9478
+ *
9479
+ * Fires once the pose has been sustained for its hold time, not the instant
9480
+ * the fingers pass through it.
9481
+ */
9482
+ onHandPoseStart(_event) {
9483
+ }
9484
+ /** A held hand pose ended, or its hand stopped being tracked. */
9485
+ onHandPoseEnd(_event) {
9486
+ }
9181
9487
  onGrab(input) {
9182
9488
  }
9183
9489
  /** Called when this entity is released. Optional XR hook. */
@@ -10320,13 +10626,65 @@ function blankState(source) {
10320
10626
  squeezeEnd: false,
10321
10627
  axes: [],
10322
10628
  hasAxes: false,
10323
- buttons: []
10629
+ buttons: [],
10630
+ joints: null
10324
10631
  };
10325
10632
  }
10326
10633
  __name(blankState, "blankState");
10634
+ var JOINT_MATRIX_BUFFER = new Float32Array(25 * 16);
10635
+ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
10636
+ const names = [];
10637
+ const spaces = [];
10638
+ for (const [name, space] of hand) {
10639
+ names.push(name);
10640
+ spaces.push(space);
10641
+ }
10642
+ if (spaces.length === 0) return null;
10643
+ const target = existing ?? {};
10644
+ const matrix = new THREE39__namespace.Matrix4();
10645
+ const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
10646
+ const fillPoses = frame.fillPoses;
10647
+ if (typeof fillPoses === "function") {
10648
+ let filled = false;
10649
+ try {
10650
+ filled = fillPoses.call(frame, spaces, referenceSpace, buffer);
10651
+ } catch {
10652
+ filled = false;
10653
+ }
10654
+ if (filled) {
10655
+ for (let i = 0; i < names.length; i += 1) {
10656
+ matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
10657
+ const vector = target[names[i]] ?? new THREE39__namespace.Vector3();
10658
+ vector.setFromMatrixPosition(matrix);
10659
+ target[names[i]] = vector;
10660
+ }
10661
+ return target;
10662
+ }
10663
+ }
10664
+ let anyTracked = false;
10665
+ for (let i = 0; i < names.length; i += 1) {
10666
+ const pose = frame.getJointPose?.(
10667
+ spaces[i],
10668
+ referenceSpace
10669
+ );
10670
+ if (!pose) {
10671
+ target[names[i]] = void 0;
10672
+ continue;
10673
+ }
10674
+ matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
10675
+ const vector = target[names[i]] ?? new THREE39__namespace.Vector3();
10676
+ vector.setFromMatrixPosition(matrix);
10677
+ target[names[i]] = vector;
10678
+ anyTracked = true;
10679
+ }
10680
+ return anyTracked ? target : null;
10681
+ }
10682
+ __name(readHandJoints, "readHandJoints");
10327
10683
  var _XRInputSystem = class _XRInputSystem {
10328
10684
  constructor() {
10329
10685
  this.records = /* @__PURE__ */ new Map();
10686
+ /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
10687
+ this.referenceToWorld = new THREE39__namespace.Matrix4();
10330
10688
  this.session = null;
10331
10689
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
10332
10690
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -10406,6 +10764,14 @@ var _XRInputSystem = class _XRInputSystem {
10406
10764
  const referenceSpace = renderer.xr.getReferenceSpace();
10407
10765
  const frame = renderer.xr.getFrame?.();
10408
10766
  const live = /* @__PURE__ */ new Set();
10767
+ const rigMatrix = this.referenceToWorld;
10768
+ const rig = world.getCamera().parent;
10769
+ if (rig) {
10770
+ rig.updateMatrixWorld();
10771
+ rigMatrix.copy(rig.matrixWorld);
10772
+ } else {
10773
+ rigMatrix.identity();
10774
+ }
10409
10775
  for (const source of session.inputSources) {
10410
10776
  live.add(source);
10411
10777
  const record = this.ensureRecord(source);
@@ -10416,7 +10782,7 @@ var _XRInputSystem = class _XRInputSystem {
10416
10782
  if (frame && referenceSpace && source.targetRaySpace) {
10417
10783
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
10418
10784
  if (pose) {
10419
- const m = new THREE39__namespace.Matrix4().fromArray(pose.transform.matrix);
10785
+ const m = new THREE39__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
10420
10786
  state.rayOrigin.setFromMatrixPosition(m);
10421
10787
  state.rayDirection.set(0, 0, -1).applyQuaternion(
10422
10788
  new THREE39__namespace.Quaternion().setFromRotationMatrix(m)
@@ -10426,7 +10792,7 @@ var _XRInputSystem = class _XRInputSystem {
10426
10792
  if (frame && referenceSpace && source.gripSpace) {
10427
10793
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
10428
10794
  if (gripPose) {
10429
- const m = new THREE39__namespace.Matrix4().fromArray(gripPose.transform.matrix);
10795
+ const m = new THREE39__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
10430
10796
  state.gripPosition ?? (state.gripPosition = new THREE39__namespace.Vector3());
10431
10797
  state.gripQuaternion ?? (state.gripQuaternion = new THREE39__namespace.Quaternion());
10432
10798
  state.gripPosition.setFromMatrixPosition(m);
@@ -10436,6 +10802,17 @@ var _XRInputSystem = class _XRInputSystem {
10436
10802
  state.gripPosition = null;
10437
10803
  state.gripQuaternion = null;
10438
10804
  }
10805
+ if (frame && referenceSpace && source.hand) {
10806
+ state.joints = readHandJoints(
10807
+ frame,
10808
+ source.hand,
10809
+ referenceSpace,
10810
+ rigMatrix,
10811
+ state.joints
10812
+ );
10813
+ } else {
10814
+ state.joints = null;
10815
+ }
10439
10816
  const gamepad = source.gamepad;
10440
10817
  if (gamepad) {
10441
10818
  state.axes = Array.from(gamepad.axes);
@@ -10500,20 +10877,86 @@ var _XRInputSystem = class _XRInputSystem {
10500
10877
  };
10501
10878
  __name(_XRInputSystem, "XRInputSystem");
10502
10879
  var XRInputSystem = _XRInputSystem;
10880
+ var VELOCITY_SAMPLES = 5;
10881
+ var MAX_SAMPLE_AGE_SECONDS = 0.2;
10882
+ var MAX_PLAUSIBLE_SPEED = 20;
10883
+ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
10884
+ constructor() {
10885
+ this.samples = [];
10886
+ }
10887
+ /** Record where the hand is now. Call once per frame while held. */
10888
+ record(position, time) {
10889
+ this.samples.push({ position: position.clone(), time });
10890
+ if (this.samples.length > VELOCITY_SAMPLES) {
10891
+ this.samples.shift();
10892
+ }
10893
+ }
10894
+ /**
10895
+ * Velocity to release with, in metres/second.
10896
+ *
10897
+ * Uses the FASTEST segment inside the window rather than the most recent or
10898
+ * the window average. Users decelerate as they open their hand, so the last
10899
+ * segment understates the throw and the average washes it out; peak speed is
10900
+ * what matches the motion the user actually felt they made.
10901
+ */
10902
+ computeVelocity(now) {
10903
+ const fresh = this.samples.filter(
10904
+ (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
10905
+ );
10906
+ if (fresh.length < 2) return new THREE39__namespace.Vector3();
10907
+ let best = new THREE39__namespace.Vector3();
10908
+ let bestSpeed = 0;
10909
+ for (let i = 1; i < fresh.length; i += 1) {
10910
+ const previous = fresh[i - 1];
10911
+ const current = fresh[i];
10912
+ const dt = current.time - previous.time;
10913
+ if (dt <= 1e-5) continue;
10914
+ const velocity = new THREE39__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
10915
+ const speed = velocity.length();
10916
+ if (speed > MAX_PLAUSIBLE_SPEED) continue;
10917
+ if (speed > bestSpeed) {
10918
+ bestSpeed = speed;
10919
+ best = velocity;
10920
+ }
10921
+ }
10922
+ return best;
10923
+ }
10924
+ clear() {
10925
+ this.samples.length = 0;
10926
+ }
10927
+ };
10928
+ __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
10929
+ var ThrowVelocityTracker = _ThrowVelocityTracker;
10930
+ function angularVelocityFrom(previous, current, deltaTime) {
10931
+ if (deltaTime <= 1e-5) return new THREE39__namespace.Vector3();
10932
+ const delta = current.clone().multiply(previous.clone().invert());
10933
+ if (delta.w < 0) {
10934
+ delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
10935
+ }
10936
+ const angle = 2 * Math.acos(THREE39__namespace.MathUtils.clamp(delta.w, -1, 1));
10937
+ const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
10938
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE39__namespace.Vector3();
10939
+ return new THREE39__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
10940
+ }
10941
+ __name(angularVelocityFrom, "angularVelocityFrom");
10942
+
10943
+ // core/systems/XRInteractionSystem.ts
10503
10944
  var RAYLESS_HOLD_DISTANCE = 0.5;
10504
10945
  var _XRInteractionSystem = class _XRInteractionSystem {
10505
10946
  constructor(inputSystem) {
10506
10947
  this.raycaster = new THREE39__namespace.Raycaster();
10507
- /** Which entity each source is holding. Keyed by source state identity. */
10508
10948
  this.grabs = /* @__PURE__ */ new Map();
10509
10949
  this.hoveredEntityId = null;
10950
+ /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
10951
+ this.elapsed = 0;
10510
10952
  this.inputSystem = inputSystem;
10511
10953
  }
10512
- update(world) {
10954
+ update(world, deltaTime) {
10513
10955
  if (!world.isXrPresenting()) {
10514
10956
  this.releaseAll(world);
10515
10957
  return;
10516
10958
  }
10959
+ this.elapsed += Math.max(0, deltaTime);
10517
10960
  const inputs = this.inputSystem.getInputs();
10518
10961
  const interactables = this.collectInteractables(world);
10519
10962
  this.hoveredEntityId = null;
@@ -10524,32 +10967,105 @@ var _XRInteractionSystem = class _XRInteractionSystem {
10524
10967
  }
10525
10968
  const held = this.grabs.get(input);
10526
10969
  if (held) {
10527
- this.moveHeld(world, input, held);
10528
- if (input.selectEnd) this.release(world, input);
10970
+ this.moveHeld(world, input, held, deltaTime);
10971
+ if (this.wantsRelease(world, input, held)) {
10972
+ this.release(world, input);
10973
+ }
10529
10974
  continue;
10530
10975
  }
10531
- const hitEntityId = this.raycast(input, interactables);
10532
- if (hitEntityId === null) continue;
10976
+ const candidate = this.findGrabCandidate(world, input, interactables);
10977
+ if (candidate === null) continue;
10533
10978
  if (this.hoveredEntityId === null) {
10534
- this.hoveredEntityId = hitEntityId;
10979
+ this.hoveredEntityId = candidate.entityId;
10535
10980
  }
10536
10981
  const interactable = world.getComponent(
10537
- hitEntityId,
10982
+ candidate.entityId,
10538
10983
  XRInteractableComponent
10539
10984
  );
10540
- if (input.selectStart && interactable?.grabbable) {
10541
- this.grab(world, input, hitEntityId);
10985
+ if (interactable?.grabbable && this.gestureMatches(
10986
+ input,
10987
+ interactable,
10988
+ /* starting */
10989
+ true
10990
+ )) {
10991
+ this.grab(world, input, candidate.entityId, candidate.viaDistance);
10992
+ }
10993
+ }
10994
+ }
10995
+ /** Whether the grab gesture for this object is currently starting. */
10996
+ gestureMatches(input, interactable, starting) {
10997
+ const pinch = starting ? input.selectStart : input.select;
10998
+ const full = starting ? input.squeezeStart : input.squeeze;
10999
+ switch (interactable.grabGesture) {
11000
+ case "pinch":
11001
+ return pinch;
11002
+ case "full":
11003
+ return full;
11004
+ default:
11005
+ return pinch || full;
11006
+ }
11007
+ }
11008
+ /** True once the gesture that started the grab has ended. */
11009
+ wantsRelease(world, input, held) {
11010
+ const interactable = world.getComponent(
11011
+ held.entityId,
11012
+ XRInteractableComponent
11013
+ );
11014
+ if (!interactable) return true;
11015
+ switch (interactable.grabGesture) {
11016
+ case "pinch":
11017
+ return input.selectEnd || !input.select;
11018
+ case "full":
11019
+ return input.squeezeEnd || !input.squeeze;
11020
+ default:
11021
+ return !input.select && !input.squeeze;
11022
+ }
11023
+ }
11024
+ /**
11025
+ * Find what this hand would grab: a physically-reachable object first, then
11026
+ * one along the pointer ray.
11027
+ *
11028
+ * Proximity wins over the ray so that reaching for something you are touching
11029
+ * never picks up a distant object behind it, which feels broken.
11030
+ */
11031
+ findGrabCandidate(world, input, interactables) {
11032
+ const hand = this.handPosition(input);
11033
+ let nearestId = null;
11034
+ let nearestDistance = Number.POSITIVE_INFINITY;
11035
+ for (const entry of interactables) {
11036
+ const interactable2 = world.getComponent(
11037
+ entry.entityId,
11038
+ XRInteractableComponent
11039
+ );
11040
+ if (!interactable2) continue;
11041
+ if (interactable2.grabReach === "distance") continue;
11042
+ const position = new THREE39__namespace.Vector3();
11043
+ entry.object.getWorldPosition(position);
11044
+ const distance = position.distanceTo(hand);
11045
+ if (distance <= interactable2.grabRadius && distance < nearestDistance) {
11046
+ nearestDistance = distance;
11047
+ nearestId = entry.entityId;
10542
11048
  }
10543
11049
  }
11050
+ if (nearestId !== null) {
11051
+ return { entityId: nearestId, viaDistance: false };
11052
+ }
11053
+ const hitId = this.raycast(input, interactables);
11054
+ if (hitId === null) return null;
11055
+ const interactable = world.getComponent(hitId, XRInteractableComponent);
11056
+ const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
11057
+ return { entityId: hitId, viaDistance: viaDistanceAllowed };
10544
11058
  }
10545
11059
  collectInteractables(world) {
10546
11060
  const out = [];
10547
11061
  for (const entityId of world.query(XRInteractableComponent, Object3DRef)) {
10548
11062
  const interactable = world.getComponent(entityId, XRInteractableComponent);
10549
- if (!interactable?.enabled || !interactable.pointable) continue;
11063
+ if (!interactable?.enabled) continue;
10550
11064
  if (!world.isEntityVisible(entityId)) continue;
10551
11065
  const object = world.getComponent(entityId, Object3DRef)?.object;
10552
- if (object) out.push({ entityId, object });
11066
+ if (object) {
11067
+ out.push({ entityId, object, pointable: interactable.pointable });
11068
+ }
10553
11069
  }
10554
11070
  return out;
10555
11071
  }
@@ -10557,9 +11073,11 @@ var _XRInteractionSystem = class _XRInteractionSystem {
10557
11073
  raycast(input, interactables) {
10558
11074
  if (interactables.length === 0) return null;
10559
11075
  this.raycaster.set(input.rayOrigin, input.rayDirection);
11076
+ this.raycaster.far = Number.POSITIVE_INFINITY;
10560
11077
  let nearestId = null;
10561
11078
  let nearestDistance = Number.POSITIVE_INFINITY;
10562
11079
  for (const entry of interactables) {
11080
+ if (!entry.pointable) continue;
10563
11081
  const hit = this.raycaster.intersectObject(entry.object, true)[0];
10564
11082
  if (!hit) continue;
10565
11083
  if (hit.distance < nearestDistance) {
@@ -10569,43 +11087,120 @@ var _XRInteractionSystem = class _XRInteractionSystem {
10569
11087
  }
10570
11088
  return nearestId;
10571
11089
  }
10572
- grab(world, input, entityId) {
11090
+ grab(world, input, entityId, viaDistance) {
10573
11091
  const transform = world.getComponent(entityId, TransformComponent);
10574
- if (!transform) return;
11092
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
11093
+ if (!transform || !interactable) return;
10575
11094
  const hand = this.handPosition(input);
10576
- const offset = new THREE39__namespace.Vector3(
10577
- transform.position.x - hand.x,
10578
- transform.position.y - hand.y,
10579
- transform.position.z - hand.z
10580
- );
11095
+ const handQuaternion = this.handQuaternion(input);
11096
+ const remote = viaDistance && interactable.distanceBehavior === "remote";
11097
+ let offset;
11098
+ let remoteDistance = 0;
11099
+ if (remote) {
11100
+ const position = new THREE39__namespace.Vector3(
11101
+ transform.position.x,
11102
+ transform.position.y,
11103
+ transform.position.z
11104
+ );
11105
+ remoteDistance = position.distanceTo(input.rayOrigin);
11106
+ offset = new THREE39__namespace.Vector3();
11107
+ } else if (viaDistance) {
11108
+ offset = new THREE39__namespace.Vector3(
11109
+ interactable.grabOffsetPosition.x,
11110
+ interactable.grabOffsetPosition.y,
11111
+ interactable.grabOffsetPosition.z
11112
+ );
11113
+ } else {
11114
+ offset = new THREE39__namespace.Vector3(
11115
+ transform.position.x - hand.x,
11116
+ transform.position.y - hand.y,
11117
+ transform.position.z - hand.z
11118
+ );
11119
+ }
10581
11120
  const physics = world.getComponent(entityId, PhysicsComponent);
10582
11121
  let restoreBodyType = null;
10583
- const interactable = world.getComponent(entityId, XRInteractableComponent);
10584
- if (physics && interactable?.grabMode === "kinematic") {
11122
+ if (physics) {
10585
11123
  restoreBodyType = physics.values.rigidbody.type;
10586
11124
  physics.values.rigidbody.type = "kinematicPosition";
10587
11125
  }
10588
- this.grabs.set(input, { entityId, offset, restoreBodyType });
11126
+ const tracker = new ThrowVelocityTracker();
11127
+ tracker.record(hand, this.elapsed);
11128
+ this.grabs.set(input, {
11129
+ entityId,
11130
+ offset,
11131
+ rotationOffset: new THREE39__namespace.Quaternion().setFromEuler(
11132
+ new THREE39__namespace.Euler(
11133
+ interactable.grabOffsetRotation.x,
11134
+ interactable.grabOffsetRotation.y,
11135
+ interactable.grabOffsetRotation.z
11136
+ )
11137
+ ),
11138
+ remoteDistance,
11139
+ remote,
11140
+ restoreBodyType,
11141
+ tracker,
11142
+ lastHandQuaternion: handQuaternion.clone(),
11143
+ angularVelocity: new THREE39__namespace.Vector3()
11144
+ });
10589
11145
  this.dispatch(world, entityId, (b) => b.onGrab(input));
10590
11146
  }
10591
- moveHeld(world, input, held) {
11147
+ moveHeld(world, input, held, deltaTime) {
10592
11148
  const transform = world.getComponent(held.entityId, TransformComponent);
10593
11149
  if (!transform) return;
10594
11150
  const hand = this.handPosition(input);
10595
- transform.position.x = hand.x + held.offset.x;
10596
- transform.position.y = hand.y + held.offset.y;
10597
- transform.position.z = hand.z + held.offset.z;
11151
+ if (held.remote) {
11152
+ const target = new THREE39__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
11153
+ transform.position.x = target.x;
11154
+ transform.position.y = target.y;
11155
+ transform.position.z = target.z;
11156
+ } else {
11157
+ transform.position.x = hand.x + held.offset.x;
11158
+ transform.position.y = hand.y + held.offset.y;
11159
+ transform.position.z = hand.z + held.offset.z;
11160
+ }
11161
+ held.tracker.record(
11162
+ held.remote ? new THREE39__namespace.Vector3(
11163
+ transform.position.x,
11164
+ transform.position.y,
11165
+ transform.position.z
11166
+ ) : hand,
11167
+ this.elapsed
11168
+ );
11169
+ const handQuaternion = this.handQuaternion(input);
11170
+ held.angularVelocity = angularVelocityFrom(
11171
+ held.lastHandQuaternion,
11172
+ handQuaternion,
11173
+ deltaTime
11174
+ );
11175
+ held.lastHandQuaternion = handQuaternion;
10598
11176
  }
10599
11177
  release(world, input) {
10600
11178
  const held = this.grabs.get(input);
10601
11179
  if (!held) return;
10602
11180
  this.grabs.delete(input);
10603
- if (held.restoreBodyType !== null) {
10604
- const physics = world.getComponent(held.entityId, PhysicsComponent);
10605
- if (physics) {
10606
- physics.values.rigidbody.type = held.restoreBodyType;
11181
+ const interactable = world.getComponent(
11182
+ held.entityId,
11183
+ XRInteractableComponent
11184
+ );
11185
+ const physics = world.getComponent(held.entityId, PhysicsComponent);
11186
+ if (held.restoreBodyType !== null && physics) {
11187
+ physics.values.rigidbody.type = held.restoreBodyType;
11188
+ const body = physics.rigidBody;
11189
+ const dynamic = held.restoreBodyType === "dynamic";
11190
+ if (body && dynamic && interactable?.throwOnRelease) {
11191
+ const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
11192
+ try {
11193
+ body.setLinvel(
11194
+ { x: velocity.x, y: velocity.y, z: velocity.z },
11195
+ true
11196
+ );
11197
+ const spin = held.angularVelocity;
11198
+ body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
11199
+ } catch {
11200
+ }
10607
11201
  }
10608
11202
  }
11203
+ held.tracker.clear();
10609
11204
  this.dispatch(world, held.entityId, (b) => b.onRelease(input));
10610
11205
  }
10611
11206
  releaseAll(world) {
@@ -10614,14 +11209,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
10614
11209
  }
10615
11210
  }
10616
11211
  /**
10617
- * Where the "hand" is for this source. Controllers and tracked hands report a
10618
- * grip pose; gaze and transient-pointer sources have none, so we hold the
10619
- * object a fixed distance along the ray instead.
11212
+ * Where the "hand" is. Controllers and tracked hands report a grip pose;
11213
+ * gaze and transient-pointer sources have none, so we hold the object a fixed
11214
+ * distance along the ray instead — which is what makes Vision Pro work.
10620
11215
  */
10621
11216
  handPosition(input) {
10622
11217
  if (input.gripPosition) return input.gripPosition;
10623
11218
  return new THREE39__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
10624
11219
  }
11220
+ /**
11221
+ * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
11222
+ * so we derive one from the aim direction — enough for spin on release.
11223
+ */
11224
+ handQuaternion(input) {
11225
+ if (input.gripQuaternion) return input.gripQuaternion.clone();
11226
+ return new THREE39__namespace.Quaternion().setFromUnitVectors(
11227
+ new THREE39__namespace.Vector3(0, 0, -1),
11228
+ input.rayDirection.clone().normalize()
11229
+ );
11230
+ }
10625
11231
  dispatch(world, entityId, visit) {
10626
11232
  const script = world.getComponent(entityId, ScriptComponent);
10627
11233
  const instances = script?.scriptInstances;
@@ -11191,6 +11797,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
11191
11797
  this.handStyleSignature = "";
11192
11798
  this.handMaterial = null;
11193
11799
  this.interactionSystem = options?.interactionSystem ?? null;
11800
+ this.inputSystem = options?.inputSystem ?? null;
11194
11801
  this.group.name = "XRControllerVisuals";
11195
11802
  this.group.userData.ecsPreviewHidden = true;
11196
11803
  }
@@ -11207,8 +11814,11 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
11207
11814
  for (const { index, side } of SLOTS) {
11208
11815
  const visuals = this.slots.get(index);
11209
11816
  if (!visuals) continue;
11210
- visuals.controllerModel.visible = showControllers;
11211
- visuals.handModel.visible = showHands;
11817
+ const liveKind = this.liveKindFor(side);
11818
+ const hasController = liveKind === null || liveKind === "controller";
11819
+ const hasHand = liveKind === null || liveKind === "hand";
11820
+ visuals.controllerModel.visible = showControllers && hasController;
11821
+ visuals.handModel.visible = showHands && hasHand;
11212
11822
  visuals.ray.visible = this.shouldShowRay(interactor);
11213
11823
  if (showHands && this.handMaterial) {
11214
11824
  applyHandMaterial(visuals.handModel, this.handMaterial);
@@ -11216,6 +11826,20 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
11216
11826
  this.syncCustomContent(world, visuals, side);
11217
11827
  }
11218
11828
  }
11829
+ /**
11830
+ * What kind of source is currently driving this hand, or null when we cannot
11831
+ * tell (no input system wired) — in which case callers fall back to showing
11832
+ * everything rather than hiding the user's hands entirely.
11833
+ */
11834
+ liveKindFor(side) {
11835
+ if (!this.inputSystem) return null;
11836
+ const inputs = this.inputSystem.getInputs();
11837
+ if (inputs.length === 0) return null;
11838
+ for (const input of inputs) {
11839
+ if (input.handedness === side && input.connected) return input.kind;
11840
+ }
11841
+ return "none";
11842
+ }
11219
11843
  /** The scene's interactor config, if one was authored. */
11220
11844
  resolveInteractor(world) {
11221
11845
  const entityId = world.query(XRInteractorComponent)[0];
@@ -11475,6 +12099,511 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
11475
12099
  };
11476
12100
  __name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
11477
12101
  var XRTeleportVisualSystem = _XRTeleportVisualSystem;
12102
+ var XR_FINGERS = [
12103
+ "thumb",
12104
+ "index",
12105
+ "middle",
12106
+ "ring",
12107
+ "pinky"
12108
+ ];
12109
+ var PINCH_FINGERS = [
12110
+ "index",
12111
+ "middle",
12112
+ "ring",
12113
+ "pinky"
12114
+ ];
12115
+ var FINGER_CHAINS = {
12116
+ thumb: [
12117
+ "thumb-metacarpal",
12118
+ "thumb-phalanx-proximal",
12119
+ "thumb-phalanx-distal",
12120
+ "thumb-tip"
12121
+ ],
12122
+ index: [
12123
+ "index-finger-metacarpal",
12124
+ "index-finger-phalanx-proximal",
12125
+ "index-finger-phalanx-intermediate",
12126
+ "index-finger-phalanx-distal",
12127
+ "index-finger-tip"
12128
+ ],
12129
+ middle: [
12130
+ "middle-finger-metacarpal",
12131
+ "middle-finger-phalanx-proximal",
12132
+ "middle-finger-phalanx-intermediate",
12133
+ "middle-finger-phalanx-distal",
12134
+ "middle-finger-tip"
12135
+ ],
12136
+ ring: [
12137
+ "ring-finger-metacarpal",
12138
+ "ring-finger-phalanx-proximal",
12139
+ "ring-finger-phalanx-intermediate",
12140
+ "ring-finger-phalanx-distal",
12141
+ "ring-finger-tip"
12142
+ ],
12143
+ pinky: [
12144
+ "pinky-finger-metacarpal",
12145
+ "pinky-finger-phalanx-proximal",
12146
+ "pinky-finger-phalanx-intermediate",
12147
+ "pinky-finger-phalanx-distal",
12148
+ "pinky-finger-tip"
12149
+ ]
12150
+ };
12151
+ var TIP_JOINTS = {
12152
+ thumb: "thumb-tip",
12153
+ index: "index-finger-tip",
12154
+ middle: "middle-finger-tip",
12155
+ ring: "ring-finger-tip",
12156
+ pinky: "pinky-finger-tip"
12157
+ };
12158
+ var METACARPAL_JOINTS = {
12159
+ thumb: "thumb-metacarpal",
12160
+ index: "index-finger-metacarpal",
12161
+ middle: "middle-finger-metacarpal",
12162
+ ring: "ring-finger-metacarpal",
12163
+ pinky: "pinky-finger-metacarpal"
12164
+ };
12165
+ var FALLBACK_HAND_SCALE = 0.08;
12166
+ var STRAIGHT_COS = 0.95;
12167
+ var CURLED_COS = 0.55;
12168
+ function clamp01(value) {
12169
+ return value < 0 ? 0 : value > 1 ? 1 : value;
12170
+ }
12171
+ __name(clamp01, "clamp01");
12172
+ function boneDirections(joints, finger) {
12173
+ const chain = FINGER_CHAINS[finger];
12174
+ const directions = [];
12175
+ for (let i = 1; i < chain.length; i += 1) {
12176
+ const from = joints[chain[i - 1]];
12177
+ const to = joints[chain[i]];
12178
+ if (!from || !to) return null;
12179
+ const direction = new THREE39__namespace.Vector3().subVectors(to, from);
12180
+ if (direction.lengthSq() < 1e-12) return null;
12181
+ directions.push(direction.normalize());
12182
+ }
12183
+ return directions.length >= 2 ? directions : null;
12184
+ }
12185
+ __name(boneDirections, "boneDirections");
12186
+ function bendAmounts(directions) {
12187
+ const bends = [];
12188
+ for (let i = 1; i < directions.length; i += 1) {
12189
+ const cos = directions[i - 1].dot(directions[i]);
12190
+ const straightness = clamp01((cos - CURLED_COS) / (STRAIGHT_COS - CURLED_COS));
12191
+ bends.push(1 - straightness);
12192
+ }
12193
+ return bends;
12194
+ }
12195
+ __name(bendAmounts, "bendAmounts");
12196
+ function average(values) {
12197
+ if (values.length === 0) return 0;
12198
+ let sum = 0;
12199
+ for (const value of values) sum += value;
12200
+ return sum / values.length;
12201
+ }
12202
+ __name(average, "average");
12203
+ function computeFingerCurl(joints, finger) {
12204
+ const directions = boneDirections(joints, finger);
12205
+ if (!directions) return null;
12206
+ const bends = bendAmounts(directions);
12207
+ if (bends.length === 0) return null;
12208
+ return {
12209
+ baseCurl: bends[0],
12210
+ tipCurl: average(bends.slice(1)) || bends[bends.length - 1],
12211
+ curl: average(bends)
12212
+ };
12213
+ }
12214
+ __name(computeFingerCurl, "computeFingerCurl");
12215
+ function computePalmWidth(joints) {
12216
+ const index = joints["index-finger-metacarpal"];
12217
+ const pinky = joints["pinky-finger-metacarpal"];
12218
+ if (!index || !pinky) return null;
12219
+ const width = index.distanceTo(pinky);
12220
+ return width > 1e-6 ? width : null;
12221
+ }
12222
+ __name(computePalmWidth, "computePalmWidth");
12223
+ function estimateHandScale(joints) {
12224
+ const measurements = [];
12225
+ const width = computePalmWidth(joints);
12226
+ if (width !== null) measurements.push(width);
12227
+ const wrist = joints.wrist;
12228
+ const middleTip = joints["middle-finger-tip"];
12229
+ if (wrist && middleTip) {
12230
+ const span = wrist.distanceTo(middleTip);
12231
+ if (span > 1e-6) measurements.push(span * 0.5);
12232
+ }
12233
+ if (measurements.length === 0) return FALLBACK_HAND_SCALE;
12234
+ return average(measurements);
12235
+ }
12236
+ __name(estimateHandScale, "estimateHandScale");
12237
+ function computePinch(joints, finger) {
12238
+ const thumbTip = joints["thumb-tip"];
12239
+ const fingerTip = joints[TIP_JOINTS[finger]];
12240
+ if (!thumbTip || !fingerTip) return null;
12241
+ const scale = estimateHandScale(joints);
12242
+ const distance = thumbTip.distanceTo(fingerTip);
12243
+ return clamp01(1 - distance / (scale * 0.7));
12244
+ }
12245
+ __name(computePinch, "computePinch");
12246
+ function computeSpread(joints, a, b) {
12247
+ const aBase = joints[METACARPAL_JOINTS[a]];
12248
+ const aTip = joints[TIP_JOINTS[a]];
12249
+ const bBase = joints[METACARPAL_JOINTS[b]];
12250
+ const bTip = joints[TIP_JOINTS[b]];
12251
+ if (!aBase || !aTip || !bBase || !bTip) return null;
12252
+ const dirA = new THREE39__namespace.Vector3().subVectors(aTip, aBase);
12253
+ const dirB = new THREE39__namespace.Vector3().subVectors(bTip, bBase);
12254
+ if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
12255
+ return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
12256
+ }
12257
+ __name(computeSpread, "computeSpread");
12258
+ function computePalmPose(joints, handedness) {
12259
+ const wrist = joints.wrist;
12260
+ const indexBase = joints["index-finger-metacarpal"];
12261
+ const pinkyBase = joints["pinky-finger-metacarpal"];
12262
+ if (!wrist || !indexBase || !pinkyBase) return null;
12263
+ const u = new THREE39__namespace.Vector3().subVectors(indexBase, wrist);
12264
+ const v = new THREE39__namespace.Vector3().subVectors(pinkyBase, wrist);
12265
+ const normal = new THREE39__namespace.Vector3().crossVectors(u, v);
12266
+ if (normal.lengthSq() < 1e-12) return null;
12267
+ normal.normalize();
12268
+ if (handedness === "left") normal.multiplyScalar(-1);
12269
+ const right = new THREE39__namespace.Vector3().subVectors(indexBase, pinkyBase);
12270
+ if (right.lengthSq() < 1e-12) return null;
12271
+ right.normalize();
12272
+ if (handedness === "left") right.multiplyScalar(-1);
12273
+ const up = new THREE39__namespace.Vector3().crossVectors(normal, right).normalize();
12274
+ const center = new THREE39__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
12275
+ return {
12276
+ center,
12277
+ normal,
12278
+ right,
12279
+ up,
12280
+ width: computePalmWidth(joints) ?? FALLBACK_HAND_SCALE
12281
+ };
12282
+ }
12283
+ __name(computePalmPose, "computePalmPose");
12284
+ function computeHandShape(joints, handedness) {
12285
+ const fingers2 = {};
12286
+ let tracked = 0;
12287
+ for (const finger of XR_FINGERS) {
12288
+ const curl = computeFingerCurl(joints, finger);
12289
+ if (curl) {
12290
+ fingers2[finger] = curl;
12291
+ tracked += 1;
12292
+ } else {
12293
+ fingers2[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
12294
+ }
12295
+ }
12296
+ if (tracked === 0) return null;
12297
+ const pinch = {};
12298
+ for (const finger of PINCH_FINGERS) {
12299
+ pinch[finger] = computePinch(joints, finger) ?? 0;
12300
+ }
12301
+ const thumbBase = joints["thumb-metacarpal"];
12302
+ const thumbTip = joints["thumb-tip"];
12303
+ let thumbDirection = null;
12304
+ if (thumbBase && thumbTip) {
12305
+ const direction = new THREE39__namespace.Vector3().subVectors(thumbTip, thumbBase);
12306
+ if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
12307
+ }
12308
+ return {
12309
+ handedness,
12310
+ fingers: fingers2,
12311
+ pinch,
12312
+ spread: {
12313
+ thumbIndex: computeSpread(joints, "thumb", "index") ?? 0,
12314
+ indexMiddle: computeSpread(joints, "index", "middle") ?? 0,
12315
+ middleRing: computeSpread(joints, "middle", "ring") ?? 0,
12316
+ ringPinky: computeSpread(joints, "ring", "pinky") ?? 0
12317
+ },
12318
+ palm: computePalmPose(joints, handedness),
12319
+ scale: estimateHandScale(joints),
12320
+ thumbDirection
12321
+ };
12322
+ }
12323
+ __name(computeHandShape, "computeHandShape");
12324
+ var MATCH_FALLOFF = 0.25;
12325
+ var NO_MATCH = {
12326
+ matched: false,
12327
+ confidence: 0,
12328
+ worstConstraint: null
12329
+ };
12330
+ function scoreAgainst(actual, target, tolerance) {
12331
+ const error = Math.abs(actual - target);
12332
+ if (error <= tolerance) return 1;
12333
+ return clamp01(1 - (error - tolerance) / MATCH_FALLOFF);
12334
+ }
12335
+ __name(scoreAgainst, "scoreAgainst");
12336
+ var WORLD_DIRECTIONS = {
12337
+ up: new THREE39__namespace.Vector3(0, 1, 0),
12338
+ down: new THREE39__namespace.Vector3(0, -1, 0),
12339
+ forward: new THREE39__namespace.Vector3(0, 0, -1),
12340
+ back: new THREE39__namespace.Vector3(0, 0, 1),
12341
+ left: new THREE39__namespace.Vector3(-1, 0, 0),
12342
+ right: new THREE39__namespace.Vector3(1, 0, 0)
12343
+ };
12344
+ function axisVector(shape, axis) {
12345
+ if (axis === "thumb") return shape.thumbDirection;
12346
+ if (!shape.palm) return null;
12347
+ return axis === "palm-normal" ? shape.palm.normal : shape.palm.up;
12348
+ }
12349
+ __name(axisVector, "axisVector");
12350
+ function resolveDirection(direction, shape, context) {
12351
+ if (direction !== "toward-head" && direction !== "away-from-head") {
12352
+ return WORLD_DIRECTIONS[direction];
12353
+ }
12354
+ const head = context?.headPosition;
12355
+ const origin = shape.palm?.center;
12356
+ if (!head || !origin) return null;
12357
+ const toHead = new THREE39__namespace.Vector3().subVectors(head, origin);
12358
+ if (toHead.lengthSq() < 1e-12) return null;
12359
+ toHead.normalize();
12360
+ return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
12361
+ }
12362
+ __name(resolveDirection, "resolveDirection");
12363
+ function matchHandPose(shape, definition, context) {
12364
+ if (definition.hand && definition.hand !== shape.handedness) return NO_MATCH;
12365
+ let confidence = 1;
12366
+ let worstScore = 1;
12367
+ let worstConstraint = null;
12368
+ const consider = /* @__PURE__ */ __name((score, label) => {
12369
+ if (score < confidence) confidence = score;
12370
+ if (score < worstScore) {
12371
+ worstScore = score;
12372
+ worstConstraint = label;
12373
+ }
12374
+ }, "consider");
12375
+ for (const finger of XR_FINGERS) {
12376
+ const target = definition.fingers[finger];
12377
+ if (!target) continue;
12378
+ const actual = shape.fingers[finger];
12379
+ consider(scoreAgainst(actual.curl, target.curl, target.tolerance), finger);
12380
+ if (target.baseCurl !== void 0) {
12381
+ consider(
12382
+ scoreAgainst(actual.baseCurl, target.baseCurl, target.tolerance),
12383
+ `${finger} knuckle`
12384
+ );
12385
+ }
12386
+ if (target.tipCurl !== void 0) {
12387
+ consider(
12388
+ scoreAgainst(actual.tipCurl, target.tipCurl, target.tolerance),
12389
+ `${finger} tip`
12390
+ );
12391
+ }
12392
+ }
12393
+ if (definition.pinch) {
12394
+ const { finger, target, tolerance } = definition.pinch;
12395
+ consider(
12396
+ scoreAgainst(shape.pinch[finger], target, tolerance),
12397
+ `${finger} pinch`
12398
+ );
12399
+ }
12400
+ if (definition.orientation) {
12401
+ const { axis, direction, toleranceDegrees } = definition.orientation;
12402
+ const handAxis = axisVector(shape, axis);
12403
+ const wanted = resolveDirection(direction, shape, context);
12404
+ if (!handAxis || !wanted) {
12405
+ consider(0, "orientation");
12406
+ } else {
12407
+ const angle = THREE39__namespace.MathUtils.radToDeg(
12408
+ Math.acos(THREE39__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
12409
+ );
12410
+ const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
12411
+ consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
12412
+ }
12413
+ }
12414
+ return {
12415
+ matched: confidence >= 1,
12416
+ confidence,
12417
+ worstConstraint: confidence >= 1 ? null : worstConstraint
12418
+ };
12419
+ }
12420
+ __name(matchHandPose, "matchHandPose");
12421
+ var DEFAULT_ENTER_CONFIDENCE = 0.8;
12422
+ var DEFAULT_EXIT_CONFIDENCE = 0.55;
12423
+ var DEFAULT_MIN_HOLD_SECONDS = 0.15;
12424
+ var DEFAULT_LOST_GRACE_SECONDS = 0.25;
12425
+ var _HandPoseTracker = class _HandPoseTracker {
12426
+ constructor(options) {
12427
+ this.active = false;
12428
+ this.holdingSince = null;
12429
+ this.lostSince = null;
12430
+ this.enterConfidence = options?.enterConfidence ?? DEFAULT_ENTER_CONFIDENCE;
12431
+ const exit = options?.exitConfidence ?? DEFAULT_EXIT_CONFIDENCE;
12432
+ this.exitConfidence = Math.min(exit, this.enterConfidence - 1e-3);
12433
+ this.minHoldSeconds = options?.minHoldSeconds ?? DEFAULT_MIN_HOLD_SECONDS;
12434
+ this.lostGraceSeconds = options?.lostGraceSeconds ?? DEFAULT_LOST_GRACE_SECONDS;
12435
+ }
12436
+ isActive() {
12437
+ return this.active;
12438
+ }
12439
+ /** Feed this frame's confidence. Returns an event on a transition. */
12440
+ update(confidence, now) {
12441
+ this.lostSince = null;
12442
+ if (this.active) {
12443
+ if (confidence < this.exitConfidence) {
12444
+ this.active = false;
12445
+ this.holdingSince = null;
12446
+ return "end";
12447
+ }
12448
+ return null;
12449
+ }
12450
+ if (confidence >= this.enterConfidence) {
12451
+ if (this.holdingSince === null) this.holdingSince = now;
12452
+ if (now - this.holdingSince >= this.minHoldSeconds) {
12453
+ this.active = true;
12454
+ return "start";
12455
+ }
12456
+ return null;
12457
+ }
12458
+ this.holdingSince = null;
12459
+ return null;
12460
+ }
12461
+ /** Feed a frame where the hand isn't tracked at all. */
12462
+ miss(now) {
12463
+ this.holdingSince = null;
12464
+ if (!this.active) return null;
12465
+ if (this.lostSince === null) this.lostSince = now;
12466
+ if (now - this.lostSince < this.lostGraceSeconds) return null;
12467
+ this.active = false;
12468
+ this.lostSince = null;
12469
+ return "end";
12470
+ }
12471
+ reset() {
12472
+ this.active = false;
12473
+ this.holdingSince = null;
12474
+ this.lostSince = null;
12475
+ }
12476
+ };
12477
+ __name(_HandPoseTracker, "HandPoseTracker");
12478
+ var HandPoseTracker = _HandPoseTracker;
12479
+
12480
+ // core/systems/XRHandPoseSystem.ts
12481
+ var _XRHandPoseSystem = class _XRHandPoseSystem {
12482
+ constructor(inputSystem) {
12483
+ this.trackers = /* @__PURE__ */ new Map();
12484
+ this.shapes = /* @__PURE__ */ new Map();
12485
+ /** Reused so head-relative orientation checks allocate nothing per frame. */
12486
+ this.headPosition = new THREE39__namespace.Vector3();
12487
+ this.elapsed = 0;
12488
+ this.inputSystem = inputSystem;
12489
+ }
12490
+ update(world, deltaTime) {
12491
+ if (!world.isXrPresenting()) {
12492
+ this.resetAll();
12493
+ return;
12494
+ }
12495
+ this.elapsed += deltaTime;
12496
+ this.refreshShapes();
12497
+ const entries = this.collectEntries(world);
12498
+ if (entries.length === 0) return;
12499
+ world.getCamera().getWorldPosition(this.headPosition);
12500
+ const context = { headPosition: this.headPosition };
12501
+ for (const { entityId, entry } of entries) {
12502
+ const definition = handPoseDefinitionFor(entry, getHandPosePreset);
12503
+ if (!definition) continue;
12504
+ for (const handedness of ["left", "right"]) {
12505
+ if (entry.hand && entry.hand !== handedness) continue;
12506
+ const key = `${entityId}:${entry.id}`;
12507
+ const trackers = this.trackersFor(handedness);
12508
+ let tracker = trackers.get(key);
12509
+ if (!tracker) {
12510
+ tracker = new HandPoseTracker({
12511
+ minHoldSeconds: entry.minHoldSeconds
12512
+ });
12513
+ trackers.set(key, tracker);
12514
+ }
12515
+ const shape = this.shapes.get(handedness) ?? null;
12516
+ if (!shape) {
12517
+ if (tracker.miss(this.elapsed) === "end") {
12518
+ this.dispatch(world, entityId, "end", {
12519
+ poseId: entry.id,
12520
+ poseName: entry.name,
12521
+ handedness,
12522
+ confidence: 0
12523
+ });
12524
+ }
12525
+ continue;
12526
+ }
12527
+ const match = matchHandPose(shape, definition, context);
12528
+ const event = tracker.update(match.confidence, this.elapsed);
12529
+ if (event) {
12530
+ this.dispatch(world, entityId, event, {
12531
+ poseId: entry.id,
12532
+ poseName: entry.name,
12533
+ handedness,
12534
+ confidence: match.confidence
12535
+ });
12536
+ }
12537
+ }
12538
+ }
12539
+ }
12540
+ /** Derive each hand's shape once for the whole frame. */
12541
+ refreshShapes() {
12542
+ for (const handedness of ["left", "right"]) {
12543
+ const input = this.inputSystem.getInput(handedness);
12544
+ const joints = input?.joints ?? null;
12545
+ this.shapes.set(
12546
+ handedness,
12547
+ joints ? computeHandShape(joints, handedness) : null
12548
+ );
12549
+ }
12550
+ }
12551
+ collectEntries(world) {
12552
+ const collected = [];
12553
+ for (const entityId of world.query(XRHandPoseComponent)) {
12554
+ const component = world.getComponent(entityId, XRHandPoseComponent);
12555
+ if (!component?.enabled) continue;
12556
+ for (const entry of component.poses) {
12557
+ if (entry.enabled) collected.push({ entityId, entry });
12558
+ }
12559
+ }
12560
+ return collected;
12561
+ }
12562
+ trackersFor(handedness) {
12563
+ let trackers = this.trackers.get(handedness);
12564
+ if (!trackers) {
12565
+ trackers = /* @__PURE__ */ new Map();
12566
+ this.trackers.set(handedness, trackers);
12567
+ }
12568
+ return trackers;
12569
+ }
12570
+ /**
12571
+ * Deliver to the entity's own Behaviours.
12572
+ *
12573
+ * Scoped to the entity that declared the pose rather than broadcast, so two
12574
+ * objects listening for the same gesture stay independent.
12575
+ */
12576
+ dispatch(world, entityId, event, detail) {
12577
+ const script = world.getComponent(entityId, ScriptComponent);
12578
+ const instances = script?.scriptInstances;
12579
+ if (!instances) return;
12580
+ for (const instance of instances.values()) {
12581
+ if (!(instance instanceof Behaviour)) continue;
12582
+ try {
12583
+ if (event === "start") instance.onHandPoseStart(detail);
12584
+ else instance.onHandPoseEnd(detail);
12585
+ } catch (error) {
12586
+ console.error("[XRHandPoseSystem] Behaviour hook failed:", error);
12587
+ }
12588
+ }
12589
+ }
12590
+ resetAll() {
12591
+ for (const trackers of this.trackers.values()) {
12592
+ for (const tracker of trackers.values()) tracker.reset();
12593
+ }
12594
+ this.shapes.clear();
12595
+ }
12596
+ /** Live shape for a hand — powers the editor's pose readout. */
12597
+ getHandShape(handedness) {
12598
+ return this.shapes.get(handedness) ?? null;
12599
+ }
12600
+ /** Whether a given entity's pose is currently held. */
12601
+ isPoseActive(entityId, poseId, handedness) {
12602
+ return this.trackers.get(handedness)?.get(`${entityId}:${poseId}`)?.isActive() ?? false;
12603
+ }
12604
+ };
12605
+ __name(_XRHandPoseSystem, "XRHandPoseSystem");
12606
+ var XRHandPoseSystem = _XRHandPoseSystem;
11478
12607
 
11479
12608
  // core/World.ts
11480
12609
  var _World = class _World {
@@ -12858,15 +13987,27 @@ exports.AnimationStateManager = AnimationStateManager;
12858
13987
  exports.AnimationSystem = AnimationSystem;
12859
13988
  exports.AudioSystem = AudioSystem;
12860
13989
  exports.Behaviour = Behaviour;
13990
+ exports.CURLED_COS = CURLED_COS;
12861
13991
  exports.CameraSyncSystem = CameraSyncSystem;
12862
13992
  exports.Component = Component;
12863
13993
  exports.CoreComponents = components_exports;
13994
+ exports.DEFAULT_ENTER_CONFIDENCE = DEFAULT_ENTER_CONFIDENCE;
13995
+ exports.DEFAULT_EXIT_CONFIDENCE = DEFAULT_EXIT_CONFIDENCE;
13996
+ exports.DEFAULT_LOST_GRACE_SECONDS = DEFAULT_LOST_GRACE_SECONDS;
13997
+ exports.DEFAULT_MIN_HOLD_SECONDS = DEFAULT_MIN_HOLD_SECONDS;
12864
13998
  exports.EnvironmentSyncSystem = EnvironmentSyncSystem;
13999
+ exports.FALLBACK_HAND_SCALE = FALLBACK_HAND_SCALE;
14000
+ exports.HAND_POSE_PRESETS = HAND_POSE_PRESETS;
14001
+ exports.HAND_POSE_PRESET_IDS = HAND_POSE_PRESET_IDS;
12865
14002
  exports.HYDRATABLE_ENTITY_KINDS = HYDRATABLE_ENTITY_KINDS;
14003
+ exports.HandPoseTracker = HandPoseTracker;
12866
14004
  exports.InputSystem = InputSystem;
12867
14005
  exports.InstancedMeshSystem = InstancedMeshSystem;
12868
14006
  exports.KeyboardKeys = KeyboardKeys;
12869
14007
  exports.LightSyncSystem = LightSyncSystem;
14008
+ exports.MATCH_FALLOFF = MATCH_FALLOFF;
14009
+ exports.MAX_PLAUSIBLE_SPEED = MAX_PLAUSIBLE_SPEED;
14010
+ exports.MAX_SAMPLE_AGE_SECONDS = MAX_SAMPLE_AGE_SECONDS;
12870
14011
  exports.MaterialSyncSystem = MaterialSyncSystem;
12871
14012
  exports.MeshGeometrySystem = MeshGeometrySystem;
12872
14013
  exports.NODE_KEY_SEPARATOR = NODE_KEY_SEPARATOR;
@@ -12875,6 +14016,7 @@ exports.OVERRIDE_DIFF_EXCLUDED_COMPONENTS = OVERRIDE_DIFF_EXCLUDED_COMPONENTS;
12875
14016
  exports.OVERRIDE_ROOT_EXCLUDED_COMPONENTS = OVERRIDE_ROOT_EXCLUDED_COMPONENTS;
12876
14017
  exports.ObjectManagementSystem = ObjectManagementSystem;
12877
14018
  exports.ObjectVisibilitySystem = ObjectVisibilitySystem;
14019
+ exports.PINCH_FINGERS = PINCH_FINGERS;
12878
14020
  exports.PREFAB_FILE_VERSION = PREFAB_FILE_VERSION;
12879
14021
  exports.PREFAB_FILE_VERSION_NESTED = PREFAB_FILE_VERSION_NESTED;
12880
14022
  exports.PhysicsSyncSystem = PhysicsSyncSystem;
@@ -12882,19 +14024,25 @@ exports.PostprocessingSyncSystem = PostprocessingSyncSystem;
12882
14024
  exports.RenderSystem = RenderSystem;
12883
14025
  exports.SCRIPT_OBJECT_ROOT_FLAG = SCRIPT_OBJECT_ROOT_FLAG;
12884
14026
  exports.SHARED_MODEL_RESOURCE_FLAG = SHARED_MODEL_RESOURCE_FLAG;
14027
+ exports.STRAIGHT_COS = STRAIGHT_COS;
12885
14028
  exports.ScriptBehaviourSystem = ScriptBehaviourSystem;
12886
14029
  exports.ScriptObject = ScriptObject;
12887
14030
  exports.ScriptObjectSystem = ScriptObjectSystem;
14031
+ exports.ThrowVelocityTracker = ThrowVelocityTracker;
12888
14032
  exports.TransformSyncSystem = TransformSyncSystem;
14033
+ exports.VELOCITY_SAMPLES = VELOCITY_SAMPLES;
12889
14034
  exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
12890
14035
  exports.VideoSystem = VideoSystem;
12891
14036
  exports.World = World;
12892
14037
  exports.XRControllerVisualSystem = XRControllerVisualSystem;
14038
+ exports.XRHandPoseSystem = XRHandPoseSystem;
12893
14039
  exports.XRInputSystem = XRInputSystem;
12894
14040
  exports.XRInteractionSystem = XRInteractionSystem;
12895
14041
  exports.XRLocomotionSystem = XRLocomotionSystem;
12896
14042
  exports.XRSessionSystem = XRSessionSystem;
12897
14043
  exports.XRTeleportVisualSystem = XRTeleportVisualSystem;
14044
+ exports.XR_FINGERS = XR_FINGERS;
14045
+ exports.angularVelocityFrom = angularVelocityFrom;
12898
14046
  exports.applyHandMaterial = applyHandMaterial;
12899
14047
  exports.applyPrefabToSnapshotInstances = applyPrefabToSnapshotInstances;
12900
14048
  exports.arcGroundCrossing = arcGroundCrossing;
@@ -12904,11 +14052,18 @@ exports.buildNodeMapsFromSnapshot = buildNodeMapsFromSnapshot;
12904
14052
  exports.canonicalizeComponentValue = canonicalizeComponentValue;
12905
14053
  exports.clearModelCache = clearModelCache;
12906
14054
  exports.clearPrefabCache = clearPrefabCache;
14055
+ exports.cloneHandPose = cloneHandPose;
12907
14056
  exports.collectDiffPaths = collectDiffPaths;
12908
14057
  exports.collectDirectPrefabDependencies = collectDirectPrefabDependencies;
12909
14058
  exports.collectEntitySubtree = collectEntitySubtree;
12910
14059
  exports.collectSnapshotSubtreeIds = collectSnapshotSubtreeIds;
14060
+ exports.computeFingerCurl = computeFingerCurl;
14061
+ exports.computeHandShape = computeHandShape;
12911
14062
  exports.computeInstanceOverrides = computeInstanceOverrides;
14063
+ exports.computePalmPose = computePalmPose;
14064
+ exports.computePalmWidth = computePalmWidth;
14065
+ exports.computePinch = computePinch;
14066
+ exports.computeSpread = computeSpread;
12912
14067
  exports.createAnimationComponent = createAnimationComponent;
12913
14068
  exports.createAudioComponent = createAudioComponent;
12914
14069
  exports.createCameraComponent = createCameraComponent;
@@ -12938,6 +14093,7 @@ exports.createSpriteComponent = createSpriteComponent;
12938
14093
  exports.createTransformComponent = createTransformComponent;
12939
14094
  exports.createVRTeleportAreaComponent = createVRTeleportAreaComponent;
12940
14095
  exports.createVideoComponent = createVideoComponent;
14096
+ exports.createXRHandPoseComponent = createXRHandPoseComponent;
12941
14097
  exports.createXRInteractableComponent = createXRInteractableComponent;
12942
14098
  exports.createXRInteractorComponent = createXRInteractorComponent;
12943
14099
  exports.createXRInteractorSideComponent = createXRInteractorSideComponent;
@@ -12945,15 +14101,19 @@ exports.createXRRigComponent = createXRRigComponent;
12945
14101
  exports.decanonicalizeComponentValue = decanonicalizeComponentValue;
12946
14102
  exports.despawnPrefab = despawnPrefab;
12947
14103
  exports.encodeOverride = encodeOverride;
14104
+ exports.estimateHandScale = estimateHandScale;
12948
14105
  exports.findSnapshotRootEntityIds = findSnapshotRootEntityIds;
12949
14106
  exports.findTopLevelNestedRegions = findTopLevelNestedRegions;
14107
+ exports.getHandPosePreset = getHandPosePreset;
12950
14108
  exports.groupOverridePaths = groupOverridePaths;
14109
+ exports.handPoseFromCurls = handPoseFromCurls;
12951
14110
  exports.hydrateWorldFromSnapshot = hydrateWorldFromSnapshot;
12952
14111
  exports.hydrateWorldFromSnapshotDetailed = hydrateWorldFromSnapshotDetailed;
12953
14112
  exports.indexSnapshotByEntity = indexSnapshotByEntity;
12954
14113
  exports.instantiateSubtree = instantiateSubtree;
12955
14114
  exports.loadPrefab = loadPrefab;
12956
14115
  exports.loadSharedGltf = loadSharedGltf;
14116
+ exports.matchHandPose = matchHandPose;
12957
14117
  exports.mergeComponentValue = mergeComponentValue;
12958
14118
  exports.parseOverride = parseOverride;
12959
14119
  exports.parsePrefabAsset = parsePrefabAsset;