@relu-interactives/spatial-ecs 0.3.6 → 0.3.8
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/{System-TmC2EijF.d.cts → System-31wk-A0g.d.cts} +1 -1
- package/dist/{System-TmC2EijF.d.ts → System-31wk-A0g.d.ts} +1 -1
- package/dist/{XRInteractor-CCUzSqg9.d.ts → XRHandPose-CxPz8VBp.d.ts} +139 -20
- package/dist/{XRInteractor-I2q6TKtF.d.cts → XRHandPose-VuSbe4iz.d.cts} +139 -20
- package/dist/{chunk-FQTOC7HK.js → chunk-KIKIWCEB.js} +14 -5
- package/dist/chunk-KIKIWCEB.js.map +1 -0
- package/dist/{chunk-N4GKZ7IV.js → chunk-KVGGAL5V.js} +227 -3
- package/dist/chunk-KVGGAL5V.js.map +1 -0
- package/dist/{chunk-JRJ3CL5H.js → chunk-L5BP727H.js} +3 -3
- package/dist/{chunk-JRJ3CL5H.js.map → chunk-L5BP727H.js.map} +1 -1
- package/dist/{chunk-WRJ2K3LZ.js → chunk-LMTB4KQ4.js} +1011 -56
- package/dist/chunk-LMTB4KQ4.js.map +1 -0
- package/dist/components/index.cjs +98 -0
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +4 -3
- package/dist/components/index.d.ts +4 -3
- package/dist/components/index.js +1 -1
- package/dist/docs/guide/vr-and-hand-poses.md +111 -0
- package/dist/factories/index.cjs +58 -0
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +6 -3
- package/dist/factories/index.d.ts +6 -3
- package/dist/factories/index.js +3 -3
- package/dist/handPose-dFxqbFFY.d.cts +240 -0
- package/dist/handPose-dFxqbFFY.d.ts +240 -0
- package/dist/{index-W8pUajCW.d.ts → index-BIQoaKAA.d.cts} +9 -3
- package/dist/{index-BltjPEhg.d.cts → index-BxdXgqiI.d.ts} +9 -3
- package/dist/{index-BpbmZuOz.d.ts → index-C7XQyrgE.d.ts} +101 -7
- package/dist/{index-C5FrDt3z.d.cts → index-DyN50JZo.d.cts} +101 -7
- package/dist/index.cjs +1269 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -9
- package/dist/index.d.ts +104 -9
- package/dist/index.js +8 -8
- package/dist/systems/index.cjs +1226 -51
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +3 -2
- package/dist/systems/index.d.ts +3 -2
- package/dist/systems/index.js +3 -3
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/worldSnapshotUtils.d.cts +1 -1
- package/dist/worldSnapshotUtils.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-FQTOC7HK.js.map +0 -1
- package/dist/chunk-N4GKZ7IV.js.map +0 -1
- package/dist/chunk-WRJ2K3LZ.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"
|
|
@@ -4612,11 +4849,65 @@ 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
|
+
const handPose = world.getComponent(targetEntityId, XRHandPoseComponent);
|
|
4896
|
+
if (sourceHandPose && handPose) {
|
|
4897
|
+
if (typeof sourceHandPose.enabled === "boolean") {
|
|
4898
|
+
handPose.enabled = sourceHandPose.enabled;
|
|
4899
|
+
}
|
|
4900
|
+
if (Array.isArray(sourceHandPose.poses)) {
|
|
4901
|
+
handPose.poses = sourceHandPose.poses.map((entry) => asRecord3(entry)).filter(
|
|
4902
|
+
(entry) => entry !== null && typeof entry.id === "string"
|
|
4903
|
+
).map(
|
|
4904
|
+
(entry) => createHandPoseEntry(
|
|
4905
|
+
entry
|
|
4906
|
+
)
|
|
4907
|
+
);
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4620
4911
|
{
|
|
4621
4912
|
const sourceInteractor = asRecord3(
|
|
4622
4913
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -9178,6 +9469,17 @@ var _Behaviour = class _Behaviour {
|
|
|
9178
9469
|
*
|
|
9179
9470
|
* @param input - The source doing the grabbing.
|
|
9180
9471
|
*/
|
|
9472
|
+
/**
|
|
9473
|
+
* A hand pose declared on this entity started being held.
|
|
9474
|
+
*
|
|
9475
|
+
* Fires once the pose has been sustained for its hold time, not the instant
|
|
9476
|
+
* the fingers pass through it.
|
|
9477
|
+
*/
|
|
9478
|
+
onHandPoseStart(_event) {
|
|
9479
|
+
}
|
|
9480
|
+
/** A held hand pose ended, or its hand stopped being tracked. */
|
|
9481
|
+
onHandPoseEnd(_event) {
|
|
9482
|
+
}
|
|
9181
9483
|
onGrab(input) {
|
|
9182
9484
|
}
|
|
9183
9485
|
/** Called when this entity is released. Optional XR hook. */
|
|
@@ -10320,13 +10622,65 @@ function blankState(source) {
|
|
|
10320
10622
|
squeezeEnd: false,
|
|
10321
10623
|
axes: [],
|
|
10322
10624
|
hasAxes: false,
|
|
10323
|
-
buttons: []
|
|
10625
|
+
buttons: [],
|
|
10626
|
+
joints: null
|
|
10324
10627
|
};
|
|
10325
10628
|
}
|
|
10326
10629
|
__name(blankState, "blankState");
|
|
10630
|
+
var JOINT_MATRIX_BUFFER = new Float32Array(25 * 16);
|
|
10631
|
+
function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
|
|
10632
|
+
const names = [];
|
|
10633
|
+
const spaces = [];
|
|
10634
|
+
for (const [name, space] of hand) {
|
|
10635
|
+
names.push(name);
|
|
10636
|
+
spaces.push(space);
|
|
10637
|
+
}
|
|
10638
|
+
if (spaces.length === 0) return null;
|
|
10639
|
+
const target = existing ?? {};
|
|
10640
|
+
const matrix = new THREE39__namespace.Matrix4();
|
|
10641
|
+
const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
|
|
10642
|
+
const fillPoses = frame.fillPoses;
|
|
10643
|
+
if (typeof fillPoses === "function") {
|
|
10644
|
+
let filled = false;
|
|
10645
|
+
try {
|
|
10646
|
+
filled = fillPoses.call(frame, spaces, referenceSpace, buffer);
|
|
10647
|
+
} catch {
|
|
10648
|
+
filled = false;
|
|
10649
|
+
}
|
|
10650
|
+
if (filled) {
|
|
10651
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
10652
|
+
matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
|
|
10653
|
+
const vector = target[names[i]] ?? new THREE39__namespace.Vector3();
|
|
10654
|
+
vector.setFromMatrixPosition(matrix);
|
|
10655
|
+
target[names[i]] = vector;
|
|
10656
|
+
}
|
|
10657
|
+
return target;
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
let anyTracked = false;
|
|
10661
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
10662
|
+
const pose = frame.getJointPose?.(
|
|
10663
|
+
spaces[i],
|
|
10664
|
+
referenceSpace
|
|
10665
|
+
);
|
|
10666
|
+
if (!pose) {
|
|
10667
|
+
target[names[i]] = void 0;
|
|
10668
|
+
continue;
|
|
10669
|
+
}
|
|
10670
|
+
matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
10671
|
+
const vector = target[names[i]] ?? new THREE39__namespace.Vector3();
|
|
10672
|
+
vector.setFromMatrixPosition(matrix);
|
|
10673
|
+
target[names[i]] = vector;
|
|
10674
|
+
anyTracked = true;
|
|
10675
|
+
}
|
|
10676
|
+
return anyTracked ? target : null;
|
|
10677
|
+
}
|
|
10678
|
+
__name(readHandJoints, "readHandJoints");
|
|
10327
10679
|
var _XRInputSystem = class _XRInputSystem {
|
|
10328
10680
|
constructor() {
|
|
10329
10681
|
this.records = /* @__PURE__ */ new Map();
|
|
10682
|
+
/** Reference-space → world matrix, rebuilt each frame from the XR rig. */
|
|
10683
|
+
this.referenceToWorld = new THREE39__namespace.Matrix4();
|
|
10330
10684
|
this.session = null;
|
|
10331
10685
|
this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
|
|
10332
10686
|
this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
|
|
@@ -10406,6 +10760,14 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
10406
10760
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
10407
10761
|
const frame = renderer.xr.getFrame?.();
|
|
10408
10762
|
const live = /* @__PURE__ */ new Set();
|
|
10763
|
+
const rigMatrix = this.referenceToWorld;
|
|
10764
|
+
const rig = world.getCamera().parent;
|
|
10765
|
+
if (rig) {
|
|
10766
|
+
rig.updateMatrixWorld();
|
|
10767
|
+
rigMatrix.copy(rig.matrixWorld);
|
|
10768
|
+
} else {
|
|
10769
|
+
rigMatrix.identity();
|
|
10770
|
+
}
|
|
10409
10771
|
for (const source of session.inputSources) {
|
|
10410
10772
|
live.add(source);
|
|
10411
10773
|
const record = this.ensureRecord(source);
|
|
@@ -10416,7 +10778,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
10416
10778
|
if (frame && referenceSpace && source.targetRaySpace) {
|
|
10417
10779
|
const pose = frame.getPose(source.targetRaySpace, referenceSpace);
|
|
10418
10780
|
if (pose) {
|
|
10419
|
-
const m = new THREE39__namespace.Matrix4().fromArray(pose.transform.matrix);
|
|
10781
|
+
const m = new THREE39__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
10420
10782
|
state.rayOrigin.setFromMatrixPosition(m);
|
|
10421
10783
|
state.rayDirection.set(0, 0, -1).applyQuaternion(
|
|
10422
10784
|
new THREE39__namespace.Quaternion().setFromRotationMatrix(m)
|
|
@@ -10426,7 +10788,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
10426
10788
|
if (frame && referenceSpace && source.gripSpace) {
|
|
10427
10789
|
const gripPose = frame.getPose(source.gripSpace, referenceSpace);
|
|
10428
10790
|
if (gripPose) {
|
|
10429
|
-
const m = new THREE39__namespace.Matrix4().fromArray(gripPose.transform.matrix);
|
|
10791
|
+
const m = new THREE39__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
|
|
10430
10792
|
state.gripPosition ?? (state.gripPosition = new THREE39__namespace.Vector3());
|
|
10431
10793
|
state.gripQuaternion ?? (state.gripQuaternion = new THREE39__namespace.Quaternion());
|
|
10432
10794
|
state.gripPosition.setFromMatrixPosition(m);
|
|
@@ -10436,6 +10798,17 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
10436
10798
|
state.gripPosition = null;
|
|
10437
10799
|
state.gripQuaternion = null;
|
|
10438
10800
|
}
|
|
10801
|
+
if (frame && referenceSpace && source.hand) {
|
|
10802
|
+
state.joints = readHandJoints(
|
|
10803
|
+
frame,
|
|
10804
|
+
source.hand,
|
|
10805
|
+
referenceSpace,
|
|
10806
|
+
rigMatrix,
|
|
10807
|
+
state.joints
|
|
10808
|
+
);
|
|
10809
|
+
} else {
|
|
10810
|
+
state.joints = null;
|
|
10811
|
+
}
|
|
10439
10812
|
const gamepad = source.gamepad;
|
|
10440
10813
|
if (gamepad) {
|
|
10441
10814
|
state.axes = Array.from(gamepad.axes);
|
|
@@ -10500,20 +10873,84 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
10500
10873
|
};
|
|
10501
10874
|
__name(_XRInputSystem, "XRInputSystem");
|
|
10502
10875
|
var XRInputSystem = _XRInputSystem;
|
|
10876
|
+
var VELOCITY_SAMPLES = 5;
|
|
10877
|
+
var MAX_SAMPLE_AGE_SECONDS = 0.2;
|
|
10878
|
+
var _ThrowVelocityTracker = class _ThrowVelocityTracker {
|
|
10879
|
+
constructor() {
|
|
10880
|
+
this.samples = [];
|
|
10881
|
+
}
|
|
10882
|
+
/** Record where the hand is now. Call once per frame while held. */
|
|
10883
|
+
record(position, time) {
|
|
10884
|
+
this.samples.push({ position: position.clone(), time });
|
|
10885
|
+
if (this.samples.length > VELOCITY_SAMPLES) {
|
|
10886
|
+
this.samples.shift();
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
/**
|
|
10890
|
+
* Velocity to release with, in metres/second.
|
|
10891
|
+
*
|
|
10892
|
+
* Uses the FASTEST segment inside the window rather than the most recent or
|
|
10893
|
+
* the window average. Users decelerate as they open their hand, so the last
|
|
10894
|
+
* segment understates the throw and the average washes it out; peak speed is
|
|
10895
|
+
* what matches the motion the user actually felt they made.
|
|
10896
|
+
*/
|
|
10897
|
+
computeVelocity(now) {
|
|
10898
|
+
const fresh = this.samples.filter(
|
|
10899
|
+
(sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
|
|
10900
|
+
);
|
|
10901
|
+
if (fresh.length < 2) return new THREE39__namespace.Vector3();
|
|
10902
|
+
let best = new THREE39__namespace.Vector3();
|
|
10903
|
+
let bestSpeed = 0;
|
|
10904
|
+
for (let i = 1; i < fresh.length; i += 1) {
|
|
10905
|
+
const previous = fresh[i - 1];
|
|
10906
|
+
const current = fresh[i];
|
|
10907
|
+
const dt = current.time - previous.time;
|
|
10908
|
+
if (dt <= 1e-5) continue;
|
|
10909
|
+
const velocity = new THREE39__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
|
|
10910
|
+
const speed = velocity.length();
|
|
10911
|
+
if (speed > bestSpeed) {
|
|
10912
|
+
bestSpeed = speed;
|
|
10913
|
+
best = velocity;
|
|
10914
|
+
}
|
|
10915
|
+
}
|
|
10916
|
+
return best;
|
|
10917
|
+
}
|
|
10918
|
+
clear() {
|
|
10919
|
+
this.samples.length = 0;
|
|
10920
|
+
}
|
|
10921
|
+
};
|
|
10922
|
+
__name(_ThrowVelocityTracker, "ThrowVelocityTracker");
|
|
10923
|
+
var ThrowVelocityTracker = _ThrowVelocityTracker;
|
|
10924
|
+
function angularVelocityFrom(previous, current, deltaTime) {
|
|
10925
|
+
if (deltaTime <= 1e-5) return new THREE39__namespace.Vector3();
|
|
10926
|
+
const delta = current.clone().multiply(previous.clone().invert());
|
|
10927
|
+
if (delta.w < 0) {
|
|
10928
|
+
delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
|
|
10929
|
+
}
|
|
10930
|
+
const angle = 2 * Math.acos(THREE39__namespace.MathUtils.clamp(delta.w, -1, 1));
|
|
10931
|
+
const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
|
|
10932
|
+
if (sinHalf < 1e-6 || angle < 1e-6) return new THREE39__namespace.Vector3();
|
|
10933
|
+
return new THREE39__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
|
|
10934
|
+
}
|
|
10935
|
+
__name(angularVelocityFrom, "angularVelocityFrom");
|
|
10936
|
+
|
|
10937
|
+
// core/systems/XRInteractionSystem.ts
|
|
10503
10938
|
var RAYLESS_HOLD_DISTANCE = 0.5;
|
|
10504
10939
|
var _XRInteractionSystem = class _XRInteractionSystem {
|
|
10505
10940
|
constructor(inputSystem) {
|
|
10506
10941
|
this.raycaster = new THREE39__namespace.Raycaster();
|
|
10507
|
-
/** Which entity each source is holding. Keyed by source state identity. */
|
|
10508
10942
|
this.grabs = /* @__PURE__ */ new Map();
|
|
10509
10943
|
this.hoveredEntityId = null;
|
|
10944
|
+
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
10945
|
+
this.elapsed = 0;
|
|
10510
10946
|
this.inputSystem = inputSystem;
|
|
10511
10947
|
}
|
|
10512
|
-
update(world) {
|
|
10948
|
+
update(world, deltaTime) {
|
|
10513
10949
|
if (!world.isXrPresenting()) {
|
|
10514
10950
|
this.releaseAll(world);
|
|
10515
10951
|
return;
|
|
10516
10952
|
}
|
|
10953
|
+
this.elapsed += Math.max(0, deltaTime);
|
|
10517
10954
|
const inputs = this.inputSystem.getInputs();
|
|
10518
10955
|
const interactables = this.collectInteractables(world);
|
|
10519
10956
|
this.hoveredEntityId = null;
|
|
@@ -10524,29 +10961,100 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
10524
10961
|
}
|
|
10525
10962
|
const held = this.grabs.get(input);
|
|
10526
10963
|
if (held) {
|
|
10527
|
-
this.moveHeld(world, input, held);
|
|
10528
|
-
if (
|
|
10964
|
+
this.moveHeld(world, input, held, deltaTime);
|
|
10965
|
+
if (this.wantsRelease(world, input, held)) {
|
|
10966
|
+
this.release(world, input);
|
|
10967
|
+
}
|
|
10529
10968
|
continue;
|
|
10530
10969
|
}
|
|
10531
|
-
const
|
|
10532
|
-
if (
|
|
10970
|
+
const candidate = this.findGrabCandidate(world, input, interactables);
|
|
10971
|
+
if (candidate === null) continue;
|
|
10533
10972
|
if (this.hoveredEntityId === null) {
|
|
10534
|
-
this.hoveredEntityId =
|
|
10973
|
+
this.hoveredEntityId = candidate.entityId;
|
|
10535
10974
|
}
|
|
10536
10975
|
const interactable = world.getComponent(
|
|
10537
|
-
|
|
10976
|
+
candidate.entityId,
|
|
10538
10977
|
XRInteractableComponent
|
|
10539
10978
|
);
|
|
10540
|
-
if (
|
|
10541
|
-
|
|
10979
|
+
if (interactable?.grabbable && this.gestureMatches(
|
|
10980
|
+
input,
|
|
10981
|
+
interactable,
|
|
10982
|
+
/* starting */
|
|
10983
|
+
true
|
|
10984
|
+
)) {
|
|
10985
|
+
this.grab(world, input, candidate.entityId, candidate.viaDistance);
|
|
10986
|
+
}
|
|
10987
|
+
}
|
|
10988
|
+
}
|
|
10989
|
+
/** Whether the grab gesture for this object is currently starting. */
|
|
10990
|
+
gestureMatches(input, interactable, starting) {
|
|
10991
|
+
const pinch = starting ? input.selectStart : input.select;
|
|
10992
|
+
const full = starting ? input.squeezeStart : input.squeeze;
|
|
10993
|
+
switch (interactable.grabGesture) {
|
|
10994
|
+
case "pinch":
|
|
10995
|
+
return pinch;
|
|
10996
|
+
case "full":
|
|
10997
|
+
return full;
|
|
10998
|
+
default:
|
|
10999
|
+
return pinch || full;
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
/** True once the gesture that started the grab has ended. */
|
|
11003
|
+
wantsRelease(world, input, held) {
|
|
11004
|
+
const interactable = world.getComponent(
|
|
11005
|
+
held.entityId,
|
|
11006
|
+
XRInteractableComponent
|
|
11007
|
+
);
|
|
11008
|
+
if (!interactable) return true;
|
|
11009
|
+
switch (interactable.grabGesture) {
|
|
11010
|
+
case "pinch":
|
|
11011
|
+
return input.selectEnd || !input.select;
|
|
11012
|
+
case "full":
|
|
11013
|
+
return input.squeezeEnd || !input.squeeze;
|
|
11014
|
+
default:
|
|
11015
|
+
return !input.select && !input.squeeze;
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
/**
|
|
11019
|
+
* Find what this hand would grab: a physically-reachable object first, then
|
|
11020
|
+
* one along the pointer ray.
|
|
11021
|
+
*
|
|
11022
|
+
* Proximity wins over the ray so that reaching for something you are touching
|
|
11023
|
+
* never picks up a distant object behind it, which feels broken.
|
|
11024
|
+
*/
|
|
11025
|
+
findGrabCandidate(world, input, interactables) {
|
|
11026
|
+
const hand = this.handPosition(input);
|
|
11027
|
+
let nearestId = null;
|
|
11028
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
11029
|
+
for (const entry of interactables) {
|
|
11030
|
+
const interactable2 = world.getComponent(
|
|
11031
|
+
entry.entityId,
|
|
11032
|
+
XRInteractableComponent
|
|
11033
|
+
);
|
|
11034
|
+
if (!interactable2) continue;
|
|
11035
|
+
if (interactable2.grabReach === "distance") continue;
|
|
11036
|
+
const position = new THREE39__namespace.Vector3();
|
|
11037
|
+
entry.object.getWorldPosition(position);
|
|
11038
|
+
const distance = position.distanceTo(hand);
|
|
11039
|
+
if (distance <= interactable2.grabRadius && distance < nearestDistance) {
|
|
11040
|
+
nearestDistance = distance;
|
|
11041
|
+
nearestId = entry.entityId;
|
|
10542
11042
|
}
|
|
10543
11043
|
}
|
|
11044
|
+
if (nearestId !== null) {
|
|
11045
|
+
return { entityId: nearestId, viaDistance: false };
|
|
11046
|
+
}
|
|
11047
|
+
const hitId = this.raycast(input, interactables);
|
|
11048
|
+
if (hitId === null) return null;
|
|
11049
|
+
const interactable = world.getComponent(hitId, XRInteractableComponent);
|
|
11050
|
+
const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
|
|
11051
|
+
return { entityId: hitId, viaDistance: viaDistanceAllowed };
|
|
10544
11052
|
}
|
|
10545
11053
|
collectInteractables(world) {
|
|
10546
11054
|
const out = [];
|
|
10547
11055
|
for (const entityId of world.query(XRInteractableComponent, Object3DRef)) {
|
|
10548
11056
|
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
10549
|
-
if (!interactable?.enabled
|
|
11057
|
+
if (!interactable?.enabled) continue;
|
|
10550
11058
|
if (!world.isEntityVisible(entityId)) continue;
|
|
10551
11059
|
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
10552
11060
|
if (object) out.push({ entityId, object });
|
|
@@ -10557,6 +11065,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
10557
11065
|
raycast(input, interactables) {
|
|
10558
11066
|
if (interactables.length === 0) return null;
|
|
10559
11067
|
this.raycaster.set(input.rayOrigin, input.rayDirection);
|
|
11068
|
+
this.raycaster.far = Number.POSITIVE_INFINITY;
|
|
10560
11069
|
let nearestId = null;
|
|
10561
11070
|
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
10562
11071
|
for (const entry of interactables) {
|
|
@@ -10569,43 +11078,120 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
10569
11078
|
}
|
|
10570
11079
|
return nearestId;
|
|
10571
11080
|
}
|
|
10572
|
-
grab(world, input, entityId) {
|
|
11081
|
+
grab(world, input, entityId, viaDistance) {
|
|
10573
11082
|
const transform = world.getComponent(entityId, TransformComponent);
|
|
10574
|
-
|
|
11083
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
11084
|
+
if (!transform || !interactable) return;
|
|
10575
11085
|
const hand = this.handPosition(input);
|
|
10576
|
-
const
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
)
|
|
11086
|
+
const handQuaternion = this.handQuaternion(input);
|
|
11087
|
+
const remote = viaDistance && interactable.distanceBehavior === "remote";
|
|
11088
|
+
let offset;
|
|
11089
|
+
let remoteDistance = 0;
|
|
11090
|
+
if (remote) {
|
|
11091
|
+
const position = new THREE39__namespace.Vector3(
|
|
11092
|
+
transform.position.x,
|
|
11093
|
+
transform.position.y,
|
|
11094
|
+
transform.position.z
|
|
11095
|
+
);
|
|
11096
|
+
remoteDistance = position.distanceTo(input.rayOrigin);
|
|
11097
|
+
offset = new THREE39__namespace.Vector3();
|
|
11098
|
+
} else if (viaDistance) {
|
|
11099
|
+
offset = new THREE39__namespace.Vector3(
|
|
11100
|
+
interactable.grabOffsetPosition.x,
|
|
11101
|
+
interactable.grabOffsetPosition.y,
|
|
11102
|
+
interactable.grabOffsetPosition.z
|
|
11103
|
+
);
|
|
11104
|
+
} else {
|
|
11105
|
+
offset = new THREE39__namespace.Vector3(
|
|
11106
|
+
transform.position.x - hand.x,
|
|
11107
|
+
transform.position.y - hand.y,
|
|
11108
|
+
transform.position.z - hand.z
|
|
11109
|
+
);
|
|
11110
|
+
}
|
|
10581
11111
|
const physics = world.getComponent(entityId, PhysicsComponent);
|
|
10582
11112
|
let restoreBodyType = null;
|
|
10583
|
-
|
|
10584
|
-
if (physics && interactable?.grabMode === "kinematic") {
|
|
11113
|
+
if (physics) {
|
|
10585
11114
|
restoreBodyType = physics.values.rigidbody.type;
|
|
10586
11115
|
physics.values.rigidbody.type = "kinematicPosition";
|
|
10587
11116
|
}
|
|
10588
|
-
|
|
11117
|
+
const tracker = new ThrowVelocityTracker();
|
|
11118
|
+
tracker.record(hand, this.elapsed);
|
|
11119
|
+
this.grabs.set(input, {
|
|
11120
|
+
entityId,
|
|
11121
|
+
offset,
|
|
11122
|
+
rotationOffset: new THREE39__namespace.Quaternion().setFromEuler(
|
|
11123
|
+
new THREE39__namespace.Euler(
|
|
11124
|
+
interactable.grabOffsetRotation.x,
|
|
11125
|
+
interactable.grabOffsetRotation.y,
|
|
11126
|
+
interactable.grabOffsetRotation.z
|
|
11127
|
+
)
|
|
11128
|
+
),
|
|
11129
|
+
remoteDistance,
|
|
11130
|
+
remote,
|
|
11131
|
+
restoreBodyType,
|
|
11132
|
+
tracker,
|
|
11133
|
+
lastHandQuaternion: handQuaternion.clone(),
|
|
11134
|
+
angularVelocity: new THREE39__namespace.Vector3()
|
|
11135
|
+
});
|
|
10589
11136
|
this.dispatch(world, entityId, (b) => b.onGrab(input));
|
|
10590
11137
|
}
|
|
10591
|
-
moveHeld(world, input, held) {
|
|
11138
|
+
moveHeld(world, input, held, deltaTime) {
|
|
10592
11139
|
const transform = world.getComponent(held.entityId, TransformComponent);
|
|
10593
11140
|
if (!transform) return;
|
|
10594
11141
|
const hand = this.handPosition(input);
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
11142
|
+
if (held.remote) {
|
|
11143
|
+
const target = new THREE39__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
|
|
11144
|
+
transform.position.x = target.x;
|
|
11145
|
+
transform.position.y = target.y;
|
|
11146
|
+
transform.position.z = target.z;
|
|
11147
|
+
} else {
|
|
11148
|
+
transform.position.x = hand.x + held.offset.x;
|
|
11149
|
+
transform.position.y = hand.y + held.offset.y;
|
|
11150
|
+
transform.position.z = hand.z + held.offset.z;
|
|
11151
|
+
}
|
|
11152
|
+
held.tracker.record(
|
|
11153
|
+
held.remote ? new THREE39__namespace.Vector3(
|
|
11154
|
+
transform.position.x,
|
|
11155
|
+
transform.position.y,
|
|
11156
|
+
transform.position.z
|
|
11157
|
+
) : hand,
|
|
11158
|
+
this.elapsed
|
|
11159
|
+
);
|
|
11160
|
+
const handQuaternion = this.handQuaternion(input);
|
|
11161
|
+
held.angularVelocity = angularVelocityFrom(
|
|
11162
|
+
held.lastHandQuaternion,
|
|
11163
|
+
handQuaternion,
|
|
11164
|
+
deltaTime
|
|
11165
|
+
);
|
|
11166
|
+
held.lastHandQuaternion = handQuaternion;
|
|
10598
11167
|
}
|
|
10599
11168
|
release(world, input) {
|
|
10600
11169
|
const held = this.grabs.get(input);
|
|
10601
11170
|
if (!held) return;
|
|
10602
11171
|
this.grabs.delete(input);
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
11172
|
+
const interactable = world.getComponent(
|
|
11173
|
+
held.entityId,
|
|
11174
|
+
XRInteractableComponent
|
|
11175
|
+
);
|
|
11176
|
+
const physics = world.getComponent(held.entityId, PhysicsComponent);
|
|
11177
|
+
if (held.restoreBodyType !== null && physics) {
|
|
11178
|
+
physics.values.rigidbody.type = held.restoreBodyType;
|
|
11179
|
+
const body = physics.rigidBody;
|
|
11180
|
+
const dynamic = held.restoreBodyType === "dynamic";
|
|
11181
|
+
if (body && dynamic && interactable?.throwOnRelease) {
|
|
11182
|
+
const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
|
|
11183
|
+
try {
|
|
11184
|
+
body.setLinvel(
|
|
11185
|
+
{ x: velocity.x, y: velocity.y, z: velocity.z },
|
|
11186
|
+
true
|
|
11187
|
+
);
|
|
11188
|
+
const spin = held.angularVelocity;
|
|
11189
|
+
body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
|
|
11190
|
+
} catch {
|
|
11191
|
+
}
|
|
10607
11192
|
}
|
|
10608
11193
|
}
|
|
11194
|
+
held.tracker.clear();
|
|
10609
11195
|
this.dispatch(world, held.entityId, (b) => b.onRelease(input));
|
|
10610
11196
|
}
|
|
10611
11197
|
releaseAll(world) {
|
|
@@ -10614,14 +11200,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
10614
11200
|
}
|
|
10615
11201
|
}
|
|
10616
11202
|
/**
|
|
10617
|
-
* Where the "hand" is
|
|
10618
|
-
*
|
|
10619
|
-
*
|
|
11203
|
+
* Where the "hand" is. Controllers and tracked hands report a grip pose;
|
|
11204
|
+
* gaze and transient-pointer sources have none, so we hold the object a fixed
|
|
11205
|
+
* distance along the ray instead — which is what makes Vision Pro work.
|
|
10620
11206
|
*/
|
|
10621
11207
|
handPosition(input) {
|
|
10622
11208
|
if (input.gripPosition) return input.gripPosition;
|
|
10623
11209
|
return new THREE39__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
|
|
10624
11210
|
}
|
|
11211
|
+
/**
|
|
11212
|
+
* Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
|
|
11213
|
+
* so we derive one from the aim direction — enough for spin on release.
|
|
11214
|
+
*/
|
|
11215
|
+
handQuaternion(input) {
|
|
11216
|
+
if (input.gripQuaternion) return input.gripQuaternion.clone();
|
|
11217
|
+
return new THREE39__namespace.Quaternion().setFromUnitVectors(
|
|
11218
|
+
new THREE39__namespace.Vector3(0, 0, -1),
|
|
11219
|
+
input.rayDirection.clone().normalize()
|
|
11220
|
+
);
|
|
11221
|
+
}
|
|
10625
11222
|
dispatch(world, entityId, visit) {
|
|
10626
11223
|
const script = world.getComponent(entityId, ScriptComponent);
|
|
10627
11224
|
const instances = script?.scriptInstances;
|
|
@@ -10697,6 +11294,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10697
11294
|
/** Curve up to the landing point, for the visual system. Empty when not aiming. */
|
|
10698
11295
|
this.aimCurve = [];
|
|
10699
11296
|
this.landingPoint = null;
|
|
11297
|
+
/** True when the current aim lands somewhere the player may actually stand. */
|
|
11298
|
+
this.aimValid = false;
|
|
11299
|
+
/** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
|
|
11300
|
+
this.stickAiming = /* @__PURE__ */ new WeakSet();
|
|
10700
11301
|
/** True once we've tried to self-heal camera parenting for this session. */
|
|
10701
11302
|
this.parentingChecked = false;
|
|
10702
11303
|
this.inputSystem = inputSystem;
|
|
@@ -10704,6 +11305,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10704
11305
|
update(world, deltaTime) {
|
|
10705
11306
|
this.aimCurve = [];
|
|
10706
11307
|
this.landingPoint = null;
|
|
11308
|
+
this.aimValid = false;
|
|
10707
11309
|
if (!world.isXrPresenting()) {
|
|
10708
11310
|
this.parentingChecked = false;
|
|
10709
11311
|
return;
|
|
@@ -10711,6 +11313,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10711
11313
|
if (!this.parentingChecked) {
|
|
10712
11314
|
this.parentingChecked = true;
|
|
10713
11315
|
this.ensureCameraUnderRig(world);
|
|
11316
|
+
this.warnOnIncompleteSetup(world);
|
|
10714
11317
|
}
|
|
10715
11318
|
const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
|
|
10716
11319
|
if (typeof rigEntityId !== "number") return;
|
|
@@ -10728,6 +11331,26 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10728
11331
|
this.applySmoothMove(world, rig, rigTransform, inputs, deltaTime);
|
|
10729
11332
|
}
|
|
10730
11333
|
}
|
|
11334
|
+
/**
|
|
11335
|
+
* Name what is missing, once per session.
|
|
11336
|
+
*
|
|
11337
|
+
* Inside a headset there is no console and no inspector, so a scene that
|
|
11338
|
+
* simply cannot move gives the author nothing to go on. These messages are
|
|
11339
|
+
* visible over remote debugging and say exactly which entity to add.
|
|
11340
|
+
*/
|
|
11341
|
+
warnOnIncompleteSetup(world) {
|
|
11342
|
+
if (typeof world.query(XRRigComponent)[0] !== "number") {
|
|
11343
|
+
console.warn(
|
|
11344
|
+
"[XRLocomotion] No XR Rig in this scene \u2014 the player cannot move. Add one via Add Object \u2192 VR \u2192 XR Rig (the scene camera is parented to it automatically)."
|
|
11345
|
+
);
|
|
11346
|
+
return;
|
|
11347
|
+
}
|
|
11348
|
+
if (world.query(VRTeleportAreaComponent).length === 0) {
|
|
11349
|
+
console.warn(
|
|
11350
|
+
"[XRLocomotion] No Teleport Area in this scene \u2014 teleport has nowhere to land. Add one via Add Object \u2192 VR \u2192 Teleport Area, or add a Teleport Area component to an existing floor."
|
|
11351
|
+
);
|
|
11352
|
+
}
|
|
11353
|
+
}
|
|
10731
11354
|
/**
|
|
10732
11355
|
* Guarantee the render camera is a CHILD of the rig.
|
|
10733
11356
|
*
|
|
@@ -10795,29 +11418,60 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10795
11418
|
}
|
|
10796
11419
|
applyTeleport(world, transform, inputs) {
|
|
10797
11420
|
const targets = this.collectTeleportTargets(world);
|
|
10798
|
-
if (targets.length === 0) return;
|
|
10799
11421
|
for (const input of inputs) {
|
|
10800
|
-
|
|
11422
|
+
const source = input;
|
|
11423
|
+
const stick = this.readStickAim(input, source);
|
|
11424
|
+
const aiming = input.select || stick.aiming;
|
|
11425
|
+
const commit = input.selectEnd || stick.released;
|
|
11426
|
+
if (!aiming && !commit) continue;
|
|
10801
11427
|
const points = sampleTeleportArc(
|
|
10802
11428
|
input.rayOrigin,
|
|
10803
11429
|
input.rayDirection,
|
|
10804
11430
|
this.arcPoints
|
|
10805
11431
|
);
|
|
10806
11432
|
const hit = this.raycastArc(points, targets);
|
|
10807
|
-
|
|
10808
|
-
this.landingPoint = hit.point;
|
|
10809
|
-
this.
|
|
10810
|
-
if (
|
|
11433
|
+
this.aimCurve = hit ? points.slice(0, hit.segmentIndex + 1).concat(hit.point) : points.slice();
|
|
11434
|
+
this.landingPoint = hit ? hit.point : null;
|
|
11435
|
+
this.aimValid = hit !== null;
|
|
11436
|
+
if (commit && hit) {
|
|
10811
11437
|
transform.position.x = hit.point.x;
|
|
10812
11438
|
transform.position.y = hit.point.y;
|
|
10813
11439
|
transform.position.z = hit.point.z;
|
|
10814
11440
|
this.aimCurve = [];
|
|
10815
11441
|
this.landingPoint = null;
|
|
11442
|
+
this.aimValid = false;
|
|
11443
|
+
} else if (commit) {
|
|
11444
|
+
this.aimCurve = [];
|
|
11445
|
+
this.aimValid = false;
|
|
10816
11446
|
}
|
|
10817
11447
|
}
|
|
10818
11448
|
}
|
|
11449
|
+
/**
|
|
11450
|
+
* Thumbstick-forward aiming with hysteresis.
|
|
11451
|
+
*
|
|
11452
|
+
* Engaging at 0.6 and releasing at 0.3 stops a stick resting near the
|
|
11453
|
+
* threshold from flickering the arc on and off — and the release edge is what
|
|
11454
|
+
* commits the teleport, so chatter there would teleport the player randomly.
|
|
11455
|
+
*/
|
|
11456
|
+
readStickAim(input, source) {
|
|
11457
|
+
if (!input.hasAxes || input.axes.length < 4) {
|
|
11458
|
+
return { aiming: false, released: false };
|
|
11459
|
+
}
|
|
11460
|
+
const forward = -(input.axes[3] ?? 0);
|
|
11461
|
+
const wasAiming = this.stickAiming.has(source);
|
|
11462
|
+
if (!wasAiming && forward > 0.6) {
|
|
11463
|
+
this.stickAiming.add(source);
|
|
11464
|
+
return { aiming: true, released: false };
|
|
11465
|
+
}
|
|
11466
|
+
if (wasAiming && forward < 0.3) {
|
|
11467
|
+
this.stickAiming.delete(source);
|
|
11468
|
+
return { aiming: false, released: true };
|
|
11469
|
+
}
|
|
11470
|
+
return { aiming: wasAiming, released: false };
|
|
11471
|
+
}
|
|
10819
11472
|
/** Walk the arc segment-by-segment, returning the first surface hit. */
|
|
10820
11473
|
raycastArc(points, targets) {
|
|
11474
|
+
if (targets.length === 0) return null;
|
|
10821
11475
|
for (let i = 1; i < points.length; i += 1) {
|
|
10822
11476
|
const from = points[i - 1];
|
|
10823
11477
|
const to = points[i];
|
|
@@ -10848,6 +11502,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
10848
11502
|
getAimCurve() {
|
|
10849
11503
|
return this.aimCurve;
|
|
10850
11504
|
}
|
|
11505
|
+
/** True when the aim lands on a valid teleport area. */
|
|
11506
|
+
isAimValid() {
|
|
11507
|
+
return this.aimValid;
|
|
11508
|
+
}
|
|
10851
11509
|
/** Where the arc currently lands, or null when not aiming at a valid area. */
|
|
10852
11510
|
getLandingPoint() {
|
|
10853
11511
|
return this.landingPoint;
|
|
@@ -11130,6 +11788,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
11130
11788
|
this.handStyleSignature = "";
|
|
11131
11789
|
this.handMaterial = null;
|
|
11132
11790
|
this.interactionSystem = options?.interactionSystem ?? null;
|
|
11791
|
+
this.inputSystem = options?.inputSystem ?? null;
|
|
11133
11792
|
this.group.name = "XRControllerVisuals";
|
|
11134
11793
|
this.group.userData.ecsPreviewHidden = true;
|
|
11135
11794
|
}
|
|
@@ -11146,8 +11805,11 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
11146
11805
|
for (const { index, side } of SLOTS) {
|
|
11147
11806
|
const visuals = this.slots.get(index);
|
|
11148
11807
|
if (!visuals) continue;
|
|
11149
|
-
|
|
11150
|
-
|
|
11808
|
+
const liveKind = this.liveKindFor(side);
|
|
11809
|
+
const hasController = liveKind === null || liveKind === "controller";
|
|
11810
|
+
const hasHand = liveKind === null || liveKind === "hand";
|
|
11811
|
+
visuals.controllerModel.visible = showControllers && hasController;
|
|
11812
|
+
visuals.handModel.visible = showHands && hasHand;
|
|
11151
11813
|
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
11152
11814
|
if (showHands && this.handMaterial) {
|
|
11153
11815
|
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
@@ -11155,6 +11817,20 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
11155
11817
|
this.syncCustomContent(world, visuals, side);
|
|
11156
11818
|
}
|
|
11157
11819
|
}
|
|
11820
|
+
/**
|
|
11821
|
+
* What kind of source is currently driving this hand, or null when we cannot
|
|
11822
|
+
* tell (no input system wired) — in which case callers fall back to showing
|
|
11823
|
+
* everything rather than hiding the user's hands entirely.
|
|
11824
|
+
*/
|
|
11825
|
+
liveKindFor(side) {
|
|
11826
|
+
if (!this.inputSystem) return null;
|
|
11827
|
+
const inputs = this.inputSystem.getInputs();
|
|
11828
|
+
if (inputs.length === 0) return null;
|
|
11829
|
+
for (const input of inputs) {
|
|
11830
|
+
if (input.handedness === side && input.connected) return input.kind;
|
|
11831
|
+
}
|
|
11832
|
+
return "none";
|
|
11833
|
+
}
|
|
11158
11834
|
/** The scene's interactor config, if one was authored. */
|
|
11159
11835
|
resolveInteractor(world) {
|
|
11160
11836
|
const entityId = world.query(XRInteractorComponent)[0];
|
|
@@ -11294,6 +11970,7 @@ __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
|
|
|
11294
11970
|
var XRControllerVisualSystem = _XRControllerVisualSystem;
|
|
11295
11971
|
var MARKER_RADIUS = 0.28;
|
|
11296
11972
|
var ACCENT = new THREE39__namespace.Color("#4da3ff");
|
|
11973
|
+
var BLOCKED = new THREE39__namespace.Color("#ff5a5a");
|
|
11297
11974
|
var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
11298
11975
|
constructor(locomotionSystem) {
|
|
11299
11976
|
this.group = new THREE39__namespace.Group();
|
|
@@ -11324,16 +12001,22 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
11324
12001
|
}
|
|
11325
12002
|
const curve = this.locomotionSystem.getAimCurve();
|
|
11326
12003
|
const landing = this.locomotionSystem.getLandingPoint();
|
|
11327
|
-
|
|
12004
|
+
const valid = this.locomotionSystem.isAimValid();
|
|
12005
|
+
if (curve.length < 2) {
|
|
11328
12006
|
this.setVisible(false);
|
|
11329
12007
|
return;
|
|
11330
12008
|
}
|
|
11331
|
-
this.updateArcGeometry(curve);
|
|
11332
|
-
|
|
11333
|
-
|
|
12009
|
+
this.updateArcGeometry(curve, valid ? ACCENT : BLOCKED);
|
|
12010
|
+
if (valid && landing) {
|
|
12011
|
+
this.marker.position.copy(landing);
|
|
12012
|
+
this.marker.position.y += 0.01;
|
|
12013
|
+
this.marker.visible = true;
|
|
12014
|
+
} else {
|
|
12015
|
+
this.marker.visible = false;
|
|
12016
|
+
}
|
|
11334
12017
|
this.setVisible(true);
|
|
11335
12018
|
}
|
|
11336
|
-
updateArcGeometry(curve) {
|
|
12019
|
+
updateArcGeometry(curve, tint) {
|
|
11337
12020
|
const count = curve.length;
|
|
11338
12021
|
const positions = new Float32Array(count * 3);
|
|
11339
12022
|
const colors = new Float32Array(count * 3);
|
|
@@ -11344,9 +12027,9 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
11344
12027
|
positions[i * 3 + 2] = point.z;
|
|
11345
12028
|
const t = count > 1 ? i / (count - 1) : 0;
|
|
11346
12029
|
const fade = 1 - t * 0.75;
|
|
11347
|
-
colors[i * 3] =
|
|
11348
|
-
colors[i * 3 + 1] =
|
|
11349
|
-
colors[i * 3 + 2] =
|
|
12030
|
+
colors[i * 3] = tint.r * fade;
|
|
12031
|
+
colors[i * 3 + 1] = tint.g * fade;
|
|
12032
|
+
colors[i * 3 + 2] = tint.b * fade;
|
|
11350
12033
|
}
|
|
11351
12034
|
this.arcGeometry.setAttribute(
|
|
11352
12035
|
"position",
|
|
@@ -11407,6 +12090,511 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
11407
12090
|
};
|
|
11408
12091
|
__name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
|
|
11409
12092
|
var XRTeleportVisualSystem = _XRTeleportVisualSystem;
|
|
12093
|
+
var XR_FINGERS = [
|
|
12094
|
+
"thumb",
|
|
12095
|
+
"index",
|
|
12096
|
+
"middle",
|
|
12097
|
+
"ring",
|
|
12098
|
+
"pinky"
|
|
12099
|
+
];
|
|
12100
|
+
var PINCH_FINGERS = [
|
|
12101
|
+
"index",
|
|
12102
|
+
"middle",
|
|
12103
|
+
"ring",
|
|
12104
|
+
"pinky"
|
|
12105
|
+
];
|
|
12106
|
+
var FINGER_CHAINS = {
|
|
12107
|
+
thumb: [
|
|
12108
|
+
"thumb-metacarpal",
|
|
12109
|
+
"thumb-phalanx-proximal",
|
|
12110
|
+
"thumb-phalanx-distal",
|
|
12111
|
+
"thumb-tip"
|
|
12112
|
+
],
|
|
12113
|
+
index: [
|
|
12114
|
+
"index-finger-metacarpal",
|
|
12115
|
+
"index-finger-phalanx-proximal",
|
|
12116
|
+
"index-finger-phalanx-intermediate",
|
|
12117
|
+
"index-finger-phalanx-distal",
|
|
12118
|
+
"index-finger-tip"
|
|
12119
|
+
],
|
|
12120
|
+
middle: [
|
|
12121
|
+
"middle-finger-metacarpal",
|
|
12122
|
+
"middle-finger-phalanx-proximal",
|
|
12123
|
+
"middle-finger-phalanx-intermediate",
|
|
12124
|
+
"middle-finger-phalanx-distal",
|
|
12125
|
+
"middle-finger-tip"
|
|
12126
|
+
],
|
|
12127
|
+
ring: [
|
|
12128
|
+
"ring-finger-metacarpal",
|
|
12129
|
+
"ring-finger-phalanx-proximal",
|
|
12130
|
+
"ring-finger-phalanx-intermediate",
|
|
12131
|
+
"ring-finger-phalanx-distal",
|
|
12132
|
+
"ring-finger-tip"
|
|
12133
|
+
],
|
|
12134
|
+
pinky: [
|
|
12135
|
+
"pinky-finger-metacarpal",
|
|
12136
|
+
"pinky-finger-phalanx-proximal",
|
|
12137
|
+
"pinky-finger-phalanx-intermediate",
|
|
12138
|
+
"pinky-finger-phalanx-distal",
|
|
12139
|
+
"pinky-finger-tip"
|
|
12140
|
+
]
|
|
12141
|
+
};
|
|
12142
|
+
var TIP_JOINTS = {
|
|
12143
|
+
thumb: "thumb-tip",
|
|
12144
|
+
index: "index-finger-tip",
|
|
12145
|
+
middle: "middle-finger-tip",
|
|
12146
|
+
ring: "ring-finger-tip",
|
|
12147
|
+
pinky: "pinky-finger-tip"
|
|
12148
|
+
};
|
|
12149
|
+
var METACARPAL_JOINTS = {
|
|
12150
|
+
thumb: "thumb-metacarpal",
|
|
12151
|
+
index: "index-finger-metacarpal",
|
|
12152
|
+
middle: "middle-finger-metacarpal",
|
|
12153
|
+
ring: "ring-finger-metacarpal",
|
|
12154
|
+
pinky: "pinky-finger-metacarpal"
|
|
12155
|
+
};
|
|
12156
|
+
var FALLBACK_HAND_SCALE = 0.08;
|
|
12157
|
+
var STRAIGHT_COS = 0.95;
|
|
12158
|
+
var CURLED_COS = 0.55;
|
|
12159
|
+
function clamp01(value) {
|
|
12160
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
12161
|
+
}
|
|
12162
|
+
__name(clamp01, "clamp01");
|
|
12163
|
+
function boneDirections(joints, finger) {
|
|
12164
|
+
const chain = FINGER_CHAINS[finger];
|
|
12165
|
+
const directions = [];
|
|
12166
|
+
for (let i = 1; i < chain.length; i += 1) {
|
|
12167
|
+
const from = joints[chain[i - 1]];
|
|
12168
|
+
const to = joints[chain[i]];
|
|
12169
|
+
if (!from || !to) return null;
|
|
12170
|
+
const direction = new THREE39__namespace.Vector3().subVectors(to, from);
|
|
12171
|
+
if (direction.lengthSq() < 1e-12) return null;
|
|
12172
|
+
directions.push(direction.normalize());
|
|
12173
|
+
}
|
|
12174
|
+
return directions.length >= 2 ? directions : null;
|
|
12175
|
+
}
|
|
12176
|
+
__name(boneDirections, "boneDirections");
|
|
12177
|
+
function bendAmounts(directions) {
|
|
12178
|
+
const bends = [];
|
|
12179
|
+
for (let i = 1; i < directions.length; i += 1) {
|
|
12180
|
+
const cos = directions[i - 1].dot(directions[i]);
|
|
12181
|
+
const straightness = clamp01((cos - CURLED_COS) / (STRAIGHT_COS - CURLED_COS));
|
|
12182
|
+
bends.push(1 - straightness);
|
|
12183
|
+
}
|
|
12184
|
+
return bends;
|
|
12185
|
+
}
|
|
12186
|
+
__name(bendAmounts, "bendAmounts");
|
|
12187
|
+
function average(values) {
|
|
12188
|
+
if (values.length === 0) return 0;
|
|
12189
|
+
let sum = 0;
|
|
12190
|
+
for (const value of values) sum += value;
|
|
12191
|
+
return sum / values.length;
|
|
12192
|
+
}
|
|
12193
|
+
__name(average, "average");
|
|
12194
|
+
function computeFingerCurl(joints, finger) {
|
|
12195
|
+
const directions = boneDirections(joints, finger);
|
|
12196
|
+
if (!directions) return null;
|
|
12197
|
+
const bends = bendAmounts(directions);
|
|
12198
|
+
if (bends.length === 0) return null;
|
|
12199
|
+
return {
|
|
12200
|
+
baseCurl: bends[0],
|
|
12201
|
+
tipCurl: average(bends.slice(1)) || bends[bends.length - 1],
|
|
12202
|
+
curl: average(bends)
|
|
12203
|
+
};
|
|
12204
|
+
}
|
|
12205
|
+
__name(computeFingerCurl, "computeFingerCurl");
|
|
12206
|
+
function computePalmWidth(joints) {
|
|
12207
|
+
const index = joints["index-finger-metacarpal"];
|
|
12208
|
+
const pinky = joints["pinky-finger-metacarpal"];
|
|
12209
|
+
if (!index || !pinky) return null;
|
|
12210
|
+
const width = index.distanceTo(pinky);
|
|
12211
|
+
return width > 1e-6 ? width : null;
|
|
12212
|
+
}
|
|
12213
|
+
__name(computePalmWidth, "computePalmWidth");
|
|
12214
|
+
function estimateHandScale(joints) {
|
|
12215
|
+
const measurements = [];
|
|
12216
|
+
const width = computePalmWidth(joints);
|
|
12217
|
+
if (width !== null) measurements.push(width);
|
|
12218
|
+
const wrist = joints.wrist;
|
|
12219
|
+
const middleTip = joints["middle-finger-tip"];
|
|
12220
|
+
if (wrist && middleTip) {
|
|
12221
|
+
const span = wrist.distanceTo(middleTip);
|
|
12222
|
+
if (span > 1e-6) measurements.push(span * 0.5);
|
|
12223
|
+
}
|
|
12224
|
+
if (measurements.length === 0) return FALLBACK_HAND_SCALE;
|
|
12225
|
+
return average(measurements);
|
|
12226
|
+
}
|
|
12227
|
+
__name(estimateHandScale, "estimateHandScale");
|
|
12228
|
+
function computePinch(joints, finger) {
|
|
12229
|
+
const thumbTip = joints["thumb-tip"];
|
|
12230
|
+
const fingerTip = joints[TIP_JOINTS[finger]];
|
|
12231
|
+
if (!thumbTip || !fingerTip) return null;
|
|
12232
|
+
const scale = estimateHandScale(joints);
|
|
12233
|
+
const distance = thumbTip.distanceTo(fingerTip);
|
|
12234
|
+
return clamp01(1 - distance / (scale * 0.7));
|
|
12235
|
+
}
|
|
12236
|
+
__name(computePinch, "computePinch");
|
|
12237
|
+
function computeSpread(joints, a, b) {
|
|
12238
|
+
const aBase = joints[METACARPAL_JOINTS[a]];
|
|
12239
|
+
const aTip = joints[TIP_JOINTS[a]];
|
|
12240
|
+
const bBase = joints[METACARPAL_JOINTS[b]];
|
|
12241
|
+
const bTip = joints[TIP_JOINTS[b]];
|
|
12242
|
+
if (!aBase || !aTip || !bBase || !bTip) return null;
|
|
12243
|
+
const dirA = new THREE39__namespace.Vector3().subVectors(aTip, aBase);
|
|
12244
|
+
const dirB = new THREE39__namespace.Vector3().subVectors(bTip, bBase);
|
|
12245
|
+
if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
|
|
12246
|
+
return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
|
|
12247
|
+
}
|
|
12248
|
+
__name(computeSpread, "computeSpread");
|
|
12249
|
+
function computePalmPose(joints, handedness) {
|
|
12250
|
+
const wrist = joints.wrist;
|
|
12251
|
+
const indexBase = joints["index-finger-metacarpal"];
|
|
12252
|
+
const pinkyBase = joints["pinky-finger-metacarpal"];
|
|
12253
|
+
if (!wrist || !indexBase || !pinkyBase) return null;
|
|
12254
|
+
const u = new THREE39__namespace.Vector3().subVectors(indexBase, wrist);
|
|
12255
|
+
const v = new THREE39__namespace.Vector3().subVectors(pinkyBase, wrist);
|
|
12256
|
+
const normal = new THREE39__namespace.Vector3().crossVectors(u, v);
|
|
12257
|
+
if (normal.lengthSq() < 1e-12) return null;
|
|
12258
|
+
normal.normalize();
|
|
12259
|
+
if (handedness === "left") normal.multiplyScalar(-1);
|
|
12260
|
+
const right = new THREE39__namespace.Vector3().subVectors(indexBase, pinkyBase);
|
|
12261
|
+
if (right.lengthSq() < 1e-12) return null;
|
|
12262
|
+
right.normalize();
|
|
12263
|
+
if (handedness === "left") right.multiplyScalar(-1);
|
|
12264
|
+
const up = new THREE39__namespace.Vector3().crossVectors(normal, right).normalize();
|
|
12265
|
+
const center = new THREE39__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
|
|
12266
|
+
return {
|
|
12267
|
+
center,
|
|
12268
|
+
normal,
|
|
12269
|
+
right,
|
|
12270
|
+
up,
|
|
12271
|
+
width: computePalmWidth(joints) ?? FALLBACK_HAND_SCALE
|
|
12272
|
+
};
|
|
12273
|
+
}
|
|
12274
|
+
__name(computePalmPose, "computePalmPose");
|
|
12275
|
+
function computeHandShape(joints, handedness) {
|
|
12276
|
+
const fingers2 = {};
|
|
12277
|
+
let tracked = 0;
|
|
12278
|
+
for (const finger of XR_FINGERS) {
|
|
12279
|
+
const curl = computeFingerCurl(joints, finger);
|
|
12280
|
+
if (curl) {
|
|
12281
|
+
fingers2[finger] = curl;
|
|
12282
|
+
tracked += 1;
|
|
12283
|
+
} else {
|
|
12284
|
+
fingers2[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
|
|
12285
|
+
}
|
|
12286
|
+
}
|
|
12287
|
+
if (tracked === 0) return null;
|
|
12288
|
+
const pinch = {};
|
|
12289
|
+
for (const finger of PINCH_FINGERS) {
|
|
12290
|
+
pinch[finger] = computePinch(joints, finger) ?? 0;
|
|
12291
|
+
}
|
|
12292
|
+
const thumbBase = joints["thumb-metacarpal"];
|
|
12293
|
+
const thumbTip = joints["thumb-tip"];
|
|
12294
|
+
let thumbDirection = null;
|
|
12295
|
+
if (thumbBase && thumbTip) {
|
|
12296
|
+
const direction = new THREE39__namespace.Vector3().subVectors(thumbTip, thumbBase);
|
|
12297
|
+
if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
|
|
12298
|
+
}
|
|
12299
|
+
return {
|
|
12300
|
+
handedness,
|
|
12301
|
+
fingers: fingers2,
|
|
12302
|
+
pinch,
|
|
12303
|
+
spread: {
|
|
12304
|
+
thumbIndex: computeSpread(joints, "thumb", "index") ?? 0,
|
|
12305
|
+
indexMiddle: computeSpread(joints, "index", "middle") ?? 0,
|
|
12306
|
+
middleRing: computeSpread(joints, "middle", "ring") ?? 0,
|
|
12307
|
+
ringPinky: computeSpread(joints, "ring", "pinky") ?? 0
|
|
12308
|
+
},
|
|
12309
|
+
palm: computePalmPose(joints, handedness),
|
|
12310
|
+
scale: estimateHandScale(joints),
|
|
12311
|
+
thumbDirection
|
|
12312
|
+
};
|
|
12313
|
+
}
|
|
12314
|
+
__name(computeHandShape, "computeHandShape");
|
|
12315
|
+
var MATCH_FALLOFF = 0.25;
|
|
12316
|
+
var NO_MATCH = {
|
|
12317
|
+
matched: false,
|
|
12318
|
+
confidence: 0,
|
|
12319
|
+
worstConstraint: null
|
|
12320
|
+
};
|
|
12321
|
+
function scoreAgainst(actual, target, tolerance) {
|
|
12322
|
+
const error = Math.abs(actual - target);
|
|
12323
|
+
if (error <= tolerance) return 1;
|
|
12324
|
+
return clamp01(1 - (error - tolerance) / MATCH_FALLOFF);
|
|
12325
|
+
}
|
|
12326
|
+
__name(scoreAgainst, "scoreAgainst");
|
|
12327
|
+
var WORLD_DIRECTIONS = {
|
|
12328
|
+
up: new THREE39__namespace.Vector3(0, 1, 0),
|
|
12329
|
+
down: new THREE39__namespace.Vector3(0, -1, 0),
|
|
12330
|
+
forward: new THREE39__namespace.Vector3(0, 0, -1),
|
|
12331
|
+
back: new THREE39__namespace.Vector3(0, 0, 1),
|
|
12332
|
+
left: new THREE39__namespace.Vector3(-1, 0, 0),
|
|
12333
|
+
right: new THREE39__namespace.Vector3(1, 0, 0)
|
|
12334
|
+
};
|
|
12335
|
+
function axisVector(shape, axis) {
|
|
12336
|
+
if (axis === "thumb") return shape.thumbDirection;
|
|
12337
|
+
if (!shape.palm) return null;
|
|
12338
|
+
return axis === "palm-normal" ? shape.palm.normal : shape.palm.up;
|
|
12339
|
+
}
|
|
12340
|
+
__name(axisVector, "axisVector");
|
|
12341
|
+
function resolveDirection(direction, shape, context) {
|
|
12342
|
+
if (direction !== "toward-head" && direction !== "away-from-head") {
|
|
12343
|
+
return WORLD_DIRECTIONS[direction];
|
|
12344
|
+
}
|
|
12345
|
+
const head = context?.headPosition;
|
|
12346
|
+
const origin = shape.palm?.center;
|
|
12347
|
+
if (!head || !origin) return null;
|
|
12348
|
+
const toHead = new THREE39__namespace.Vector3().subVectors(head, origin);
|
|
12349
|
+
if (toHead.lengthSq() < 1e-12) return null;
|
|
12350
|
+
toHead.normalize();
|
|
12351
|
+
return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
|
|
12352
|
+
}
|
|
12353
|
+
__name(resolveDirection, "resolveDirection");
|
|
12354
|
+
function matchHandPose(shape, definition, context) {
|
|
12355
|
+
if (definition.hand && definition.hand !== shape.handedness) return NO_MATCH;
|
|
12356
|
+
let confidence = 1;
|
|
12357
|
+
let worstScore = 1;
|
|
12358
|
+
let worstConstraint = null;
|
|
12359
|
+
const consider = /* @__PURE__ */ __name((score, label) => {
|
|
12360
|
+
if (score < confidence) confidence = score;
|
|
12361
|
+
if (score < worstScore) {
|
|
12362
|
+
worstScore = score;
|
|
12363
|
+
worstConstraint = label;
|
|
12364
|
+
}
|
|
12365
|
+
}, "consider");
|
|
12366
|
+
for (const finger of XR_FINGERS) {
|
|
12367
|
+
const target = definition.fingers[finger];
|
|
12368
|
+
if (!target) continue;
|
|
12369
|
+
const actual = shape.fingers[finger];
|
|
12370
|
+
consider(scoreAgainst(actual.curl, target.curl, target.tolerance), finger);
|
|
12371
|
+
if (target.baseCurl !== void 0) {
|
|
12372
|
+
consider(
|
|
12373
|
+
scoreAgainst(actual.baseCurl, target.baseCurl, target.tolerance),
|
|
12374
|
+
`${finger} knuckle`
|
|
12375
|
+
);
|
|
12376
|
+
}
|
|
12377
|
+
if (target.tipCurl !== void 0) {
|
|
12378
|
+
consider(
|
|
12379
|
+
scoreAgainst(actual.tipCurl, target.tipCurl, target.tolerance),
|
|
12380
|
+
`${finger} tip`
|
|
12381
|
+
);
|
|
12382
|
+
}
|
|
12383
|
+
}
|
|
12384
|
+
if (definition.pinch) {
|
|
12385
|
+
const { finger, target, tolerance } = definition.pinch;
|
|
12386
|
+
consider(
|
|
12387
|
+
scoreAgainst(shape.pinch[finger], target, tolerance),
|
|
12388
|
+
`${finger} pinch`
|
|
12389
|
+
);
|
|
12390
|
+
}
|
|
12391
|
+
if (definition.orientation) {
|
|
12392
|
+
const { axis, direction, toleranceDegrees } = definition.orientation;
|
|
12393
|
+
const handAxis = axisVector(shape, axis);
|
|
12394
|
+
const wanted = resolveDirection(direction, shape, context);
|
|
12395
|
+
if (!handAxis || !wanted) {
|
|
12396
|
+
consider(0, "orientation");
|
|
12397
|
+
} else {
|
|
12398
|
+
const angle = THREE39__namespace.MathUtils.radToDeg(
|
|
12399
|
+
Math.acos(THREE39__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
|
|
12400
|
+
);
|
|
12401
|
+
const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
|
|
12402
|
+
consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
return {
|
|
12406
|
+
matched: confidence >= 1,
|
|
12407
|
+
confidence,
|
|
12408
|
+
worstConstraint: confidence >= 1 ? null : worstConstraint
|
|
12409
|
+
};
|
|
12410
|
+
}
|
|
12411
|
+
__name(matchHandPose, "matchHandPose");
|
|
12412
|
+
var DEFAULT_ENTER_CONFIDENCE = 0.8;
|
|
12413
|
+
var DEFAULT_EXIT_CONFIDENCE = 0.55;
|
|
12414
|
+
var DEFAULT_MIN_HOLD_SECONDS = 0.15;
|
|
12415
|
+
var DEFAULT_LOST_GRACE_SECONDS = 0.25;
|
|
12416
|
+
var _HandPoseTracker = class _HandPoseTracker {
|
|
12417
|
+
constructor(options) {
|
|
12418
|
+
this.active = false;
|
|
12419
|
+
this.holdingSince = null;
|
|
12420
|
+
this.lostSince = null;
|
|
12421
|
+
this.enterConfidence = options?.enterConfidence ?? DEFAULT_ENTER_CONFIDENCE;
|
|
12422
|
+
const exit = options?.exitConfidence ?? DEFAULT_EXIT_CONFIDENCE;
|
|
12423
|
+
this.exitConfidence = Math.min(exit, this.enterConfidence - 1e-3);
|
|
12424
|
+
this.minHoldSeconds = options?.minHoldSeconds ?? DEFAULT_MIN_HOLD_SECONDS;
|
|
12425
|
+
this.lostGraceSeconds = options?.lostGraceSeconds ?? DEFAULT_LOST_GRACE_SECONDS;
|
|
12426
|
+
}
|
|
12427
|
+
isActive() {
|
|
12428
|
+
return this.active;
|
|
12429
|
+
}
|
|
12430
|
+
/** Feed this frame's confidence. Returns an event on a transition. */
|
|
12431
|
+
update(confidence, now) {
|
|
12432
|
+
this.lostSince = null;
|
|
12433
|
+
if (this.active) {
|
|
12434
|
+
if (confidence < this.exitConfidence) {
|
|
12435
|
+
this.active = false;
|
|
12436
|
+
this.holdingSince = null;
|
|
12437
|
+
return "end";
|
|
12438
|
+
}
|
|
12439
|
+
return null;
|
|
12440
|
+
}
|
|
12441
|
+
if (confidence >= this.enterConfidence) {
|
|
12442
|
+
if (this.holdingSince === null) this.holdingSince = now;
|
|
12443
|
+
if (now - this.holdingSince >= this.minHoldSeconds) {
|
|
12444
|
+
this.active = true;
|
|
12445
|
+
return "start";
|
|
12446
|
+
}
|
|
12447
|
+
return null;
|
|
12448
|
+
}
|
|
12449
|
+
this.holdingSince = null;
|
|
12450
|
+
return null;
|
|
12451
|
+
}
|
|
12452
|
+
/** Feed a frame where the hand isn't tracked at all. */
|
|
12453
|
+
miss(now) {
|
|
12454
|
+
this.holdingSince = null;
|
|
12455
|
+
if (!this.active) return null;
|
|
12456
|
+
if (this.lostSince === null) this.lostSince = now;
|
|
12457
|
+
if (now - this.lostSince < this.lostGraceSeconds) return null;
|
|
12458
|
+
this.active = false;
|
|
12459
|
+
this.lostSince = null;
|
|
12460
|
+
return "end";
|
|
12461
|
+
}
|
|
12462
|
+
reset() {
|
|
12463
|
+
this.active = false;
|
|
12464
|
+
this.holdingSince = null;
|
|
12465
|
+
this.lostSince = null;
|
|
12466
|
+
}
|
|
12467
|
+
};
|
|
12468
|
+
__name(_HandPoseTracker, "HandPoseTracker");
|
|
12469
|
+
var HandPoseTracker = _HandPoseTracker;
|
|
12470
|
+
|
|
12471
|
+
// core/systems/XRHandPoseSystem.ts
|
|
12472
|
+
var _XRHandPoseSystem = class _XRHandPoseSystem {
|
|
12473
|
+
constructor(inputSystem) {
|
|
12474
|
+
this.trackers = /* @__PURE__ */ new Map();
|
|
12475
|
+
this.shapes = /* @__PURE__ */ new Map();
|
|
12476
|
+
/** Reused so head-relative orientation checks allocate nothing per frame. */
|
|
12477
|
+
this.headPosition = new THREE39__namespace.Vector3();
|
|
12478
|
+
this.elapsed = 0;
|
|
12479
|
+
this.inputSystem = inputSystem;
|
|
12480
|
+
}
|
|
12481
|
+
update(world, deltaTime) {
|
|
12482
|
+
if (!world.isXrPresenting()) {
|
|
12483
|
+
this.resetAll();
|
|
12484
|
+
return;
|
|
12485
|
+
}
|
|
12486
|
+
this.elapsed += deltaTime;
|
|
12487
|
+
this.refreshShapes();
|
|
12488
|
+
const entries = this.collectEntries(world);
|
|
12489
|
+
if (entries.length === 0) return;
|
|
12490
|
+
world.getCamera().getWorldPosition(this.headPosition);
|
|
12491
|
+
const context = { headPosition: this.headPosition };
|
|
12492
|
+
for (const { entityId, entry } of entries) {
|
|
12493
|
+
const definition = handPoseDefinitionFor(entry, getHandPosePreset);
|
|
12494
|
+
if (!definition) continue;
|
|
12495
|
+
for (const handedness of ["left", "right"]) {
|
|
12496
|
+
if (entry.hand && entry.hand !== handedness) continue;
|
|
12497
|
+
const key = `${entityId}:${entry.id}`;
|
|
12498
|
+
const trackers = this.trackersFor(handedness);
|
|
12499
|
+
let tracker = trackers.get(key);
|
|
12500
|
+
if (!tracker) {
|
|
12501
|
+
tracker = new HandPoseTracker({
|
|
12502
|
+
minHoldSeconds: entry.minHoldSeconds
|
|
12503
|
+
});
|
|
12504
|
+
trackers.set(key, tracker);
|
|
12505
|
+
}
|
|
12506
|
+
const shape = this.shapes.get(handedness) ?? null;
|
|
12507
|
+
if (!shape) {
|
|
12508
|
+
if (tracker.miss(this.elapsed) === "end") {
|
|
12509
|
+
this.dispatch(world, entityId, "end", {
|
|
12510
|
+
poseId: entry.id,
|
|
12511
|
+
poseName: entry.name,
|
|
12512
|
+
handedness,
|
|
12513
|
+
confidence: 0
|
|
12514
|
+
});
|
|
12515
|
+
}
|
|
12516
|
+
continue;
|
|
12517
|
+
}
|
|
12518
|
+
const match = matchHandPose(shape, definition, context);
|
|
12519
|
+
const event = tracker.update(match.confidence, this.elapsed);
|
|
12520
|
+
if (event) {
|
|
12521
|
+
this.dispatch(world, entityId, event, {
|
|
12522
|
+
poseId: entry.id,
|
|
12523
|
+
poseName: entry.name,
|
|
12524
|
+
handedness,
|
|
12525
|
+
confidence: match.confidence
|
|
12526
|
+
});
|
|
12527
|
+
}
|
|
12528
|
+
}
|
|
12529
|
+
}
|
|
12530
|
+
}
|
|
12531
|
+
/** Derive each hand's shape once for the whole frame. */
|
|
12532
|
+
refreshShapes() {
|
|
12533
|
+
for (const handedness of ["left", "right"]) {
|
|
12534
|
+
const input = this.inputSystem.getInput(handedness);
|
|
12535
|
+
const joints = input?.joints ?? null;
|
|
12536
|
+
this.shapes.set(
|
|
12537
|
+
handedness,
|
|
12538
|
+
joints ? computeHandShape(joints, handedness) : null
|
|
12539
|
+
);
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
collectEntries(world) {
|
|
12543
|
+
const collected = [];
|
|
12544
|
+
for (const entityId of world.query(XRHandPoseComponent)) {
|
|
12545
|
+
const component = world.getComponent(entityId, XRHandPoseComponent);
|
|
12546
|
+
if (!component?.enabled) continue;
|
|
12547
|
+
for (const entry of component.poses) {
|
|
12548
|
+
if (entry.enabled) collected.push({ entityId, entry });
|
|
12549
|
+
}
|
|
12550
|
+
}
|
|
12551
|
+
return collected;
|
|
12552
|
+
}
|
|
12553
|
+
trackersFor(handedness) {
|
|
12554
|
+
let trackers = this.trackers.get(handedness);
|
|
12555
|
+
if (!trackers) {
|
|
12556
|
+
trackers = /* @__PURE__ */ new Map();
|
|
12557
|
+
this.trackers.set(handedness, trackers);
|
|
12558
|
+
}
|
|
12559
|
+
return trackers;
|
|
12560
|
+
}
|
|
12561
|
+
/**
|
|
12562
|
+
* Deliver to the entity's own Behaviours.
|
|
12563
|
+
*
|
|
12564
|
+
* Scoped to the entity that declared the pose rather than broadcast, so two
|
|
12565
|
+
* objects listening for the same gesture stay independent.
|
|
12566
|
+
*/
|
|
12567
|
+
dispatch(world, entityId, event, detail) {
|
|
12568
|
+
const script = world.getComponent(entityId, ScriptComponent);
|
|
12569
|
+
const instances = script?.scriptInstances;
|
|
12570
|
+
if (!instances) return;
|
|
12571
|
+
for (const instance of instances.values()) {
|
|
12572
|
+
if (!(instance instanceof Behaviour)) continue;
|
|
12573
|
+
try {
|
|
12574
|
+
if (event === "start") instance.onHandPoseStart(detail);
|
|
12575
|
+
else instance.onHandPoseEnd(detail);
|
|
12576
|
+
} catch (error) {
|
|
12577
|
+
console.error("[XRHandPoseSystem] Behaviour hook failed:", error);
|
|
12578
|
+
}
|
|
12579
|
+
}
|
|
12580
|
+
}
|
|
12581
|
+
resetAll() {
|
|
12582
|
+
for (const trackers of this.trackers.values()) {
|
|
12583
|
+
for (const tracker of trackers.values()) tracker.reset();
|
|
12584
|
+
}
|
|
12585
|
+
this.shapes.clear();
|
|
12586
|
+
}
|
|
12587
|
+
/** Live shape for a hand — powers the editor's pose readout. */
|
|
12588
|
+
getHandShape(handedness) {
|
|
12589
|
+
return this.shapes.get(handedness) ?? null;
|
|
12590
|
+
}
|
|
12591
|
+
/** Whether a given entity's pose is currently held. */
|
|
12592
|
+
isPoseActive(entityId, poseId, handedness) {
|
|
12593
|
+
return this.trackers.get(handedness)?.get(`${entityId}:${poseId}`)?.isActive() ?? false;
|
|
12594
|
+
}
|
|
12595
|
+
};
|
|
12596
|
+
__name(_XRHandPoseSystem, "XRHandPoseSystem");
|
|
12597
|
+
var XRHandPoseSystem = _XRHandPoseSystem;
|
|
11410
12598
|
|
|
11411
12599
|
// core/World.ts
|
|
11412
12600
|
var _World = class _World {
|
|
@@ -12790,15 +13978,26 @@ exports.AnimationStateManager = AnimationStateManager;
|
|
|
12790
13978
|
exports.AnimationSystem = AnimationSystem;
|
|
12791
13979
|
exports.AudioSystem = AudioSystem;
|
|
12792
13980
|
exports.Behaviour = Behaviour;
|
|
13981
|
+
exports.CURLED_COS = CURLED_COS;
|
|
12793
13982
|
exports.CameraSyncSystem = CameraSyncSystem;
|
|
12794
13983
|
exports.Component = Component;
|
|
12795
13984
|
exports.CoreComponents = components_exports;
|
|
13985
|
+
exports.DEFAULT_ENTER_CONFIDENCE = DEFAULT_ENTER_CONFIDENCE;
|
|
13986
|
+
exports.DEFAULT_EXIT_CONFIDENCE = DEFAULT_EXIT_CONFIDENCE;
|
|
13987
|
+
exports.DEFAULT_LOST_GRACE_SECONDS = DEFAULT_LOST_GRACE_SECONDS;
|
|
13988
|
+
exports.DEFAULT_MIN_HOLD_SECONDS = DEFAULT_MIN_HOLD_SECONDS;
|
|
12796
13989
|
exports.EnvironmentSyncSystem = EnvironmentSyncSystem;
|
|
13990
|
+
exports.FALLBACK_HAND_SCALE = FALLBACK_HAND_SCALE;
|
|
13991
|
+
exports.HAND_POSE_PRESETS = HAND_POSE_PRESETS;
|
|
13992
|
+
exports.HAND_POSE_PRESET_IDS = HAND_POSE_PRESET_IDS;
|
|
12797
13993
|
exports.HYDRATABLE_ENTITY_KINDS = HYDRATABLE_ENTITY_KINDS;
|
|
13994
|
+
exports.HandPoseTracker = HandPoseTracker;
|
|
12798
13995
|
exports.InputSystem = InputSystem;
|
|
12799
13996
|
exports.InstancedMeshSystem = InstancedMeshSystem;
|
|
12800
13997
|
exports.KeyboardKeys = KeyboardKeys;
|
|
12801
13998
|
exports.LightSyncSystem = LightSyncSystem;
|
|
13999
|
+
exports.MATCH_FALLOFF = MATCH_FALLOFF;
|
|
14000
|
+
exports.MAX_SAMPLE_AGE_SECONDS = MAX_SAMPLE_AGE_SECONDS;
|
|
12802
14001
|
exports.MaterialSyncSystem = MaterialSyncSystem;
|
|
12803
14002
|
exports.MeshGeometrySystem = MeshGeometrySystem;
|
|
12804
14003
|
exports.NODE_KEY_SEPARATOR = NODE_KEY_SEPARATOR;
|
|
@@ -12807,6 +14006,7 @@ exports.OVERRIDE_DIFF_EXCLUDED_COMPONENTS = OVERRIDE_DIFF_EXCLUDED_COMPONENTS;
|
|
|
12807
14006
|
exports.OVERRIDE_ROOT_EXCLUDED_COMPONENTS = OVERRIDE_ROOT_EXCLUDED_COMPONENTS;
|
|
12808
14007
|
exports.ObjectManagementSystem = ObjectManagementSystem;
|
|
12809
14008
|
exports.ObjectVisibilitySystem = ObjectVisibilitySystem;
|
|
14009
|
+
exports.PINCH_FINGERS = PINCH_FINGERS;
|
|
12810
14010
|
exports.PREFAB_FILE_VERSION = PREFAB_FILE_VERSION;
|
|
12811
14011
|
exports.PREFAB_FILE_VERSION_NESTED = PREFAB_FILE_VERSION_NESTED;
|
|
12812
14012
|
exports.PhysicsSyncSystem = PhysicsSyncSystem;
|
|
@@ -12814,19 +14014,25 @@ exports.PostprocessingSyncSystem = PostprocessingSyncSystem;
|
|
|
12814
14014
|
exports.RenderSystem = RenderSystem;
|
|
12815
14015
|
exports.SCRIPT_OBJECT_ROOT_FLAG = SCRIPT_OBJECT_ROOT_FLAG;
|
|
12816
14016
|
exports.SHARED_MODEL_RESOURCE_FLAG = SHARED_MODEL_RESOURCE_FLAG;
|
|
14017
|
+
exports.STRAIGHT_COS = STRAIGHT_COS;
|
|
12817
14018
|
exports.ScriptBehaviourSystem = ScriptBehaviourSystem;
|
|
12818
14019
|
exports.ScriptObject = ScriptObject;
|
|
12819
14020
|
exports.ScriptObjectSystem = ScriptObjectSystem;
|
|
14021
|
+
exports.ThrowVelocityTracker = ThrowVelocityTracker;
|
|
12820
14022
|
exports.TransformSyncSystem = TransformSyncSystem;
|
|
14023
|
+
exports.VELOCITY_SAMPLES = VELOCITY_SAMPLES;
|
|
12821
14024
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
12822
14025
|
exports.VideoSystem = VideoSystem;
|
|
12823
14026
|
exports.World = World;
|
|
12824
14027
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
14028
|
+
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
12825
14029
|
exports.XRInputSystem = XRInputSystem;
|
|
12826
14030
|
exports.XRInteractionSystem = XRInteractionSystem;
|
|
12827
14031
|
exports.XRLocomotionSystem = XRLocomotionSystem;
|
|
12828
14032
|
exports.XRSessionSystem = XRSessionSystem;
|
|
12829
14033
|
exports.XRTeleportVisualSystem = XRTeleportVisualSystem;
|
|
14034
|
+
exports.XR_FINGERS = XR_FINGERS;
|
|
14035
|
+
exports.angularVelocityFrom = angularVelocityFrom;
|
|
12830
14036
|
exports.applyHandMaterial = applyHandMaterial;
|
|
12831
14037
|
exports.applyPrefabToSnapshotInstances = applyPrefabToSnapshotInstances;
|
|
12832
14038
|
exports.arcGroundCrossing = arcGroundCrossing;
|
|
@@ -12836,11 +14042,18 @@ exports.buildNodeMapsFromSnapshot = buildNodeMapsFromSnapshot;
|
|
|
12836
14042
|
exports.canonicalizeComponentValue = canonicalizeComponentValue;
|
|
12837
14043
|
exports.clearModelCache = clearModelCache;
|
|
12838
14044
|
exports.clearPrefabCache = clearPrefabCache;
|
|
14045
|
+
exports.cloneHandPose = cloneHandPose;
|
|
12839
14046
|
exports.collectDiffPaths = collectDiffPaths;
|
|
12840
14047
|
exports.collectDirectPrefabDependencies = collectDirectPrefabDependencies;
|
|
12841
14048
|
exports.collectEntitySubtree = collectEntitySubtree;
|
|
12842
14049
|
exports.collectSnapshotSubtreeIds = collectSnapshotSubtreeIds;
|
|
14050
|
+
exports.computeFingerCurl = computeFingerCurl;
|
|
14051
|
+
exports.computeHandShape = computeHandShape;
|
|
12843
14052
|
exports.computeInstanceOverrides = computeInstanceOverrides;
|
|
14053
|
+
exports.computePalmPose = computePalmPose;
|
|
14054
|
+
exports.computePalmWidth = computePalmWidth;
|
|
14055
|
+
exports.computePinch = computePinch;
|
|
14056
|
+
exports.computeSpread = computeSpread;
|
|
12844
14057
|
exports.createAnimationComponent = createAnimationComponent;
|
|
12845
14058
|
exports.createAudioComponent = createAudioComponent;
|
|
12846
14059
|
exports.createCameraComponent = createCameraComponent;
|
|
@@ -12870,6 +14083,7 @@ exports.createSpriteComponent = createSpriteComponent;
|
|
|
12870
14083
|
exports.createTransformComponent = createTransformComponent;
|
|
12871
14084
|
exports.createVRTeleportAreaComponent = createVRTeleportAreaComponent;
|
|
12872
14085
|
exports.createVideoComponent = createVideoComponent;
|
|
14086
|
+
exports.createXRHandPoseComponent = createXRHandPoseComponent;
|
|
12873
14087
|
exports.createXRInteractableComponent = createXRInteractableComponent;
|
|
12874
14088
|
exports.createXRInteractorComponent = createXRInteractorComponent;
|
|
12875
14089
|
exports.createXRInteractorSideComponent = createXRInteractorSideComponent;
|
|
@@ -12877,15 +14091,19 @@ exports.createXRRigComponent = createXRRigComponent;
|
|
|
12877
14091
|
exports.decanonicalizeComponentValue = decanonicalizeComponentValue;
|
|
12878
14092
|
exports.despawnPrefab = despawnPrefab;
|
|
12879
14093
|
exports.encodeOverride = encodeOverride;
|
|
14094
|
+
exports.estimateHandScale = estimateHandScale;
|
|
12880
14095
|
exports.findSnapshotRootEntityIds = findSnapshotRootEntityIds;
|
|
12881
14096
|
exports.findTopLevelNestedRegions = findTopLevelNestedRegions;
|
|
14097
|
+
exports.getHandPosePreset = getHandPosePreset;
|
|
12882
14098
|
exports.groupOverridePaths = groupOverridePaths;
|
|
14099
|
+
exports.handPoseFromCurls = handPoseFromCurls;
|
|
12883
14100
|
exports.hydrateWorldFromSnapshot = hydrateWorldFromSnapshot;
|
|
12884
14101
|
exports.hydrateWorldFromSnapshotDetailed = hydrateWorldFromSnapshotDetailed;
|
|
12885
14102
|
exports.indexSnapshotByEntity = indexSnapshotByEntity;
|
|
12886
14103
|
exports.instantiateSubtree = instantiateSubtree;
|
|
12887
14104
|
exports.loadPrefab = loadPrefab;
|
|
12888
14105
|
exports.loadSharedGltf = loadSharedGltf;
|
|
14106
|
+
exports.matchHandPose = matchHandPose;
|
|
12889
14107
|
exports.mergeComponentValue = mergeComponentValue;
|
|
12890
14108
|
exports.parseOverride = parseOverride;
|
|
12891
14109
|
exports.parsePrefabAsset = parsePrefabAsset;
|