@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.
- 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-O6FJA4XW.js → chunk-W5TSYV6Q.js} +946 -50
- package/dist/chunk-W5TSYV6Q.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-BltjPEhg.d.cts → index-BIQoaKAA.d.cts} +9 -3
- package/dist/{index-W8pUajCW.d.ts → index-BxdXgqiI.d.ts} +9 -3
- package/dist/{index-_q9TVDI5.d.ts → index-C7XQyrgE.d.ts} +79 -7
- package/dist/{index-B9nwRDf9.d.cts → index-DyN50JZo.d.cts} +79 -7
- package/dist/index.cjs +1205 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -9
- package/dist/index.d.ts +118 -9
- package/dist/index.js +8 -8
- package/dist/systems/index.cjs +1161 -45
- 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-O6FJA4XW.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-
|
|
2
|
-
import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance } from './chunk-
|
|
1
|
+
import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-L5BP727H.js';
|
|
2
|
+
import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-KVGGAL5V.js';
|
|
3
3
|
import { __name } from './chunk-OY7ZODZP.js';
|
|
4
4
|
import * as THREE7 from 'three';
|
|
5
5
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
@@ -2795,6 +2795,10 @@ function buildComponentValueMaps(snapshot) {
|
|
|
2795
2795
|
snapshot.components,
|
|
2796
2796
|
"XRInteractorComponent"
|
|
2797
2797
|
),
|
|
2798
|
+
xrHandPoseValues: readByComponent(
|
|
2799
|
+
snapshot.components,
|
|
2800
|
+
"XRHandPoseComponent"
|
|
2801
|
+
),
|
|
2798
2802
|
xrInteractorSideValues: readByComponent(
|
|
2799
2803
|
snapshot.components,
|
|
2800
2804
|
"XRInteractorSideComponent"
|
|
@@ -2908,13 +2912,9 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
2908
2912
|
runtimeMessage: typeof entry.runtimeMessage === "string" ? entry.runtimeMessage : void 0
|
|
2909
2913
|
};
|
|
2910
2914
|
}).filter((entry) => entry.className || entry.scriptUrl) : [];
|
|
2911
|
-
if (
|
|
2912
|
-
|
|
2915
|
+
if (entries.length) {
|
|
2916
|
+
world.addComponent(targetEntityId, new ScriptComponent(entries));
|
|
2913
2917
|
}
|
|
2914
|
-
world.addComponent(
|
|
2915
|
-
targetEntityId,
|
|
2916
|
-
new ScriptComponent(entries)
|
|
2917
|
-
);
|
|
2918
2918
|
}
|
|
2919
2919
|
const sourceScriptObject = asRecord(values.scriptObjectValues.get(sourceEntityId));
|
|
2920
2920
|
if (sourceScriptObject) {
|
|
@@ -2963,10 +2963,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
2963
2963
|
const sourceInteractable = asRecord(
|
|
2964
2964
|
values.xrInteractableValues.get(sourceEntityId)
|
|
2965
2965
|
);
|
|
2966
|
-
|
|
2966
|
+
let interactable = world.getComponent(
|
|
2967
2967
|
targetEntityId,
|
|
2968
2968
|
XRInteractableComponent
|
|
2969
2969
|
);
|
|
2970
|
+
if (sourceInteractable && !interactable) {
|
|
2971
|
+
interactable = new XRInteractableComponent();
|
|
2972
|
+
world.addComponent(targetEntityId, interactable);
|
|
2973
|
+
}
|
|
2970
2974
|
if (sourceInteractable && interactable) {
|
|
2971
2975
|
if (typeof sourceInteractable.enabled === "boolean") {
|
|
2972
2976
|
interactable.enabled = sourceInteractable.enabled;
|
|
@@ -2980,11 +2984,69 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
2980
2984
|
if (typeof sourceInteractable.grabMode === "string") {
|
|
2981
2985
|
interactable.grabMode = sourceInteractable.grabMode;
|
|
2982
2986
|
}
|
|
2987
|
+
if (typeof sourceInteractable.grabReach === "string") {
|
|
2988
|
+
interactable.grabReach = sourceInteractable.grabReach;
|
|
2989
|
+
}
|
|
2990
|
+
if (typeof sourceInteractable.distanceBehavior === "string") {
|
|
2991
|
+
interactable.distanceBehavior = sourceInteractable.distanceBehavior;
|
|
2992
|
+
}
|
|
2993
|
+
if (typeof sourceInteractable.grabGesture === "string") {
|
|
2994
|
+
interactable.grabGesture = sourceInteractable.grabGesture;
|
|
2995
|
+
}
|
|
2996
|
+
if (typeof sourceInteractable.grabRadius === "number") {
|
|
2997
|
+
interactable.grabRadius = sourceInteractable.grabRadius;
|
|
2998
|
+
}
|
|
2999
|
+
if (typeof sourceInteractable.throwOnRelease === "boolean") {
|
|
3000
|
+
interactable.throwOnRelease = sourceInteractable.throwOnRelease;
|
|
3001
|
+
}
|
|
3002
|
+
if (typeof sourceInteractable.throwStrength === "number") {
|
|
3003
|
+
interactable.throwStrength = sourceInteractable.throwStrength;
|
|
3004
|
+
}
|
|
3005
|
+
const grabPos = asRecord(sourceInteractable.grabOffsetPosition);
|
|
3006
|
+
if (grabPos) {
|
|
3007
|
+
interactable.grabOffsetPosition = {
|
|
3008
|
+
x: typeof grabPos.x === "number" ? grabPos.x : 0,
|
|
3009
|
+
y: typeof grabPos.y === "number" ? grabPos.y : 0,
|
|
3010
|
+
z: typeof grabPos.z === "number" ? grabPos.z : 0
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
const grabRot = asRecord(sourceInteractable.grabOffsetRotation);
|
|
3014
|
+
if (grabRot) {
|
|
3015
|
+
interactable.grabOffsetRotation = {
|
|
3016
|
+
x: typeof grabRot.x === "number" ? grabRot.x : 0,
|
|
3017
|
+
y: typeof grabRot.y === "number" ? grabRot.y : 0,
|
|
3018
|
+
z: typeof grabRot.z === "number" ? grabRot.z : 0
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
2983
3021
|
if (typeof sourceInteractable.highlightOnHover === "boolean") {
|
|
2984
3022
|
interactable.highlightOnHover = sourceInteractable.highlightOnHover;
|
|
2985
3023
|
}
|
|
2986
3024
|
}
|
|
2987
3025
|
}
|
|
3026
|
+
{
|
|
3027
|
+
const sourceHandPose = asRecord(
|
|
3028
|
+
values.xrHandPoseValues.get(sourceEntityId)
|
|
3029
|
+
);
|
|
3030
|
+
let handPose = world.getComponent(targetEntityId, XRHandPoseComponent);
|
|
3031
|
+
if (sourceHandPose && !handPose) {
|
|
3032
|
+
handPose = new XRHandPoseComponent();
|
|
3033
|
+
world.addComponent(targetEntityId, handPose);
|
|
3034
|
+
}
|
|
3035
|
+
if (sourceHandPose && handPose) {
|
|
3036
|
+
if (typeof sourceHandPose.enabled === "boolean") {
|
|
3037
|
+
handPose.enabled = sourceHandPose.enabled;
|
|
3038
|
+
}
|
|
3039
|
+
if (Array.isArray(sourceHandPose.poses)) {
|
|
3040
|
+
handPose.poses = sourceHandPose.poses.map((entry) => asRecord(entry)).filter(
|
|
3041
|
+
(entry) => entry !== null && typeof entry.id === "string"
|
|
3042
|
+
).map(
|
|
3043
|
+
(entry) => createHandPoseEntry(
|
|
3044
|
+
entry
|
|
3045
|
+
)
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
2988
3050
|
{
|
|
2989
3051
|
const sourceInteractor = asRecord(
|
|
2990
3052
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -4914,6 +4976,17 @@ var _Behaviour = class _Behaviour {
|
|
|
4914
4976
|
*
|
|
4915
4977
|
* @param input - The source doing the grabbing.
|
|
4916
4978
|
*/
|
|
4979
|
+
/**
|
|
4980
|
+
* A hand pose declared on this entity started being held.
|
|
4981
|
+
*
|
|
4982
|
+
* Fires once the pose has been sustained for its hold time, not the instant
|
|
4983
|
+
* the fingers pass through it.
|
|
4984
|
+
*/
|
|
4985
|
+
onHandPoseStart(_event) {
|
|
4986
|
+
}
|
|
4987
|
+
/** A held hand pose ended, or its hand stopped being tracked. */
|
|
4988
|
+
onHandPoseEnd(_event) {
|
|
4989
|
+
}
|
|
4917
4990
|
onGrab(input) {
|
|
4918
4991
|
}
|
|
4919
4992
|
/** Called when this entity is released. Optional XR hook. */
|
|
@@ -6056,13 +6129,65 @@ function blankState(source) {
|
|
|
6056
6129
|
squeezeEnd: false,
|
|
6057
6130
|
axes: [],
|
|
6058
6131
|
hasAxes: false,
|
|
6059
|
-
buttons: []
|
|
6132
|
+
buttons: [],
|
|
6133
|
+
joints: null
|
|
6060
6134
|
};
|
|
6061
6135
|
}
|
|
6062
6136
|
__name(blankState, "blankState");
|
|
6137
|
+
var JOINT_MATRIX_BUFFER = new Float32Array(25 * 16);
|
|
6138
|
+
function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
|
|
6139
|
+
const names = [];
|
|
6140
|
+
const spaces = [];
|
|
6141
|
+
for (const [name, space] of hand) {
|
|
6142
|
+
names.push(name);
|
|
6143
|
+
spaces.push(space);
|
|
6144
|
+
}
|
|
6145
|
+
if (spaces.length === 0) return null;
|
|
6146
|
+
const target = existing ?? {};
|
|
6147
|
+
const matrix = new THREE7.Matrix4();
|
|
6148
|
+
const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
|
|
6149
|
+
const fillPoses = frame.fillPoses;
|
|
6150
|
+
if (typeof fillPoses === "function") {
|
|
6151
|
+
let filled = false;
|
|
6152
|
+
try {
|
|
6153
|
+
filled = fillPoses.call(frame, spaces, referenceSpace, buffer);
|
|
6154
|
+
} catch {
|
|
6155
|
+
filled = false;
|
|
6156
|
+
}
|
|
6157
|
+
if (filled) {
|
|
6158
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
6159
|
+
matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
|
|
6160
|
+
const vector = target[names[i]] ?? new THREE7.Vector3();
|
|
6161
|
+
vector.setFromMatrixPosition(matrix);
|
|
6162
|
+
target[names[i]] = vector;
|
|
6163
|
+
}
|
|
6164
|
+
return target;
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
let anyTracked = false;
|
|
6168
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
6169
|
+
const pose = frame.getJointPose?.(
|
|
6170
|
+
spaces[i],
|
|
6171
|
+
referenceSpace
|
|
6172
|
+
);
|
|
6173
|
+
if (!pose) {
|
|
6174
|
+
target[names[i]] = void 0;
|
|
6175
|
+
continue;
|
|
6176
|
+
}
|
|
6177
|
+
matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
6178
|
+
const vector = target[names[i]] ?? new THREE7.Vector3();
|
|
6179
|
+
vector.setFromMatrixPosition(matrix);
|
|
6180
|
+
target[names[i]] = vector;
|
|
6181
|
+
anyTracked = true;
|
|
6182
|
+
}
|
|
6183
|
+
return anyTracked ? target : null;
|
|
6184
|
+
}
|
|
6185
|
+
__name(readHandJoints, "readHandJoints");
|
|
6063
6186
|
var _XRInputSystem = class _XRInputSystem {
|
|
6064
6187
|
constructor() {
|
|
6065
6188
|
this.records = /* @__PURE__ */ new Map();
|
|
6189
|
+
/** Reference-space → world matrix, rebuilt each frame from the XR rig. */
|
|
6190
|
+
this.referenceToWorld = new THREE7.Matrix4();
|
|
6066
6191
|
this.session = null;
|
|
6067
6192
|
this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
|
|
6068
6193
|
this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
|
|
@@ -6142,6 +6267,14 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
6142
6267
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
6143
6268
|
const frame = renderer.xr.getFrame?.();
|
|
6144
6269
|
const live = /* @__PURE__ */ new Set();
|
|
6270
|
+
const rigMatrix = this.referenceToWorld;
|
|
6271
|
+
const rig = world.getCamera().parent;
|
|
6272
|
+
if (rig) {
|
|
6273
|
+
rig.updateMatrixWorld();
|
|
6274
|
+
rigMatrix.copy(rig.matrixWorld);
|
|
6275
|
+
} else {
|
|
6276
|
+
rigMatrix.identity();
|
|
6277
|
+
}
|
|
6145
6278
|
for (const source of session.inputSources) {
|
|
6146
6279
|
live.add(source);
|
|
6147
6280
|
const record = this.ensureRecord(source);
|
|
@@ -6152,7 +6285,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
6152
6285
|
if (frame && referenceSpace && source.targetRaySpace) {
|
|
6153
6286
|
const pose = frame.getPose(source.targetRaySpace, referenceSpace);
|
|
6154
6287
|
if (pose) {
|
|
6155
|
-
const m = new THREE7.Matrix4().fromArray(pose.transform.matrix);
|
|
6288
|
+
const m = new THREE7.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
6156
6289
|
state.rayOrigin.setFromMatrixPosition(m);
|
|
6157
6290
|
state.rayDirection.set(0, 0, -1).applyQuaternion(
|
|
6158
6291
|
new THREE7.Quaternion().setFromRotationMatrix(m)
|
|
@@ -6162,7 +6295,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
6162
6295
|
if (frame && referenceSpace && source.gripSpace) {
|
|
6163
6296
|
const gripPose = frame.getPose(source.gripSpace, referenceSpace);
|
|
6164
6297
|
if (gripPose) {
|
|
6165
|
-
const m = new THREE7.Matrix4().fromArray(gripPose.transform.matrix);
|
|
6298
|
+
const m = new THREE7.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
|
|
6166
6299
|
state.gripPosition ?? (state.gripPosition = new THREE7.Vector3());
|
|
6167
6300
|
state.gripQuaternion ?? (state.gripQuaternion = new THREE7.Quaternion());
|
|
6168
6301
|
state.gripPosition.setFromMatrixPosition(m);
|
|
@@ -6172,6 +6305,17 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
6172
6305
|
state.gripPosition = null;
|
|
6173
6306
|
state.gripQuaternion = null;
|
|
6174
6307
|
}
|
|
6308
|
+
if (frame && referenceSpace && source.hand) {
|
|
6309
|
+
state.joints = readHandJoints(
|
|
6310
|
+
frame,
|
|
6311
|
+
source.hand,
|
|
6312
|
+
referenceSpace,
|
|
6313
|
+
rigMatrix,
|
|
6314
|
+
state.joints
|
|
6315
|
+
);
|
|
6316
|
+
} else {
|
|
6317
|
+
state.joints = null;
|
|
6318
|
+
}
|
|
6175
6319
|
const gamepad = source.gamepad;
|
|
6176
6320
|
if (gamepad) {
|
|
6177
6321
|
state.axes = Array.from(gamepad.axes);
|
|
@@ -6236,20 +6380,86 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
6236
6380
|
};
|
|
6237
6381
|
__name(_XRInputSystem, "XRInputSystem");
|
|
6238
6382
|
var XRInputSystem = _XRInputSystem;
|
|
6383
|
+
var VELOCITY_SAMPLES = 5;
|
|
6384
|
+
var MAX_SAMPLE_AGE_SECONDS = 0.2;
|
|
6385
|
+
var MAX_PLAUSIBLE_SPEED = 20;
|
|
6386
|
+
var _ThrowVelocityTracker = class _ThrowVelocityTracker {
|
|
6387
|
+
constructor() {
|
|
6388
|
+
this.samples = [];
|
|
6389
|
+
}
|
|
6390
|
+
/** Record where the hand is now. Call once per frame while held. */
|
|
6391
|
+
record(position, time) {
|
|
6392
|
+
this.samples.push({ position: position.clone(), time });
|
|
6393
|
+
if (this.samples.length > VELOCITY_SAMPLES) {
|
|
6394
|
+
this.samples.shift();
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
/**
|
|
6398
|
+
* Velocity to release with, in metres/second.
|
|
6399
|
+
*
|
|
6400
|
+
* Uses the FASTEST segment inside the window rather than the most recent or
|
|
6401
|
+
* the window average. Users decelerate as they open their hand, so the last
|
|
6402
|
+
* segment understates the throw and the average washes it out; peak speed is
|
|
6403
|
+
* what matches the motion the user actually felt they made.
|
|
6404
|
+
*/
|
|
6405
|
+
computeVelocity(now) {
|
|
6406
|
+
const fresh = this.samples.filter(
|
|
6407
|
+
(sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
|
|
6408
|
+
);
|
|
6409
|
+
if (fresh.length < 2) return new THREE7.Vector3();
|
|
6410
|
+
let best = new THREE7.Vector3();
|
|
6411
|
+
let bestSpeed = 0;
|
|
6412
|
+
for (let i = 1; i < fresh.length; i += 1) {
|
|
6413
|
+
const previous = fresh[i - 1];
|
|
6414
|
+
const current = fresh[i];
|
|
6415
|
+
const dt = current.time - previous.time;
|
|
6416
|
+
if (dt <= 1e-5) continue;
|
|
6417
|
+
const velocity = new THREE7.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
|
|
6418
|
+
const speed = velocity.length();
|
|
6419
|
+
if (speed > MAX_PLAUSIBLE_SPEED) continue;
|
|
6420
|
+
if (speed > bestSpeed) {
|
|
6421
|
+
bestSpeed = speed;
|
|
6422
|
+
best = velocity;
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
return best;
|
|
6426
|
+
}
|
|
6427
|
+
clear() {
|
|
6428
|
+
this.samples.length = 0;
|
|
6429
|
+
}
|
|
6430
|
+
};
|
|
6431
|
+
__name(_ThrowVelocityTracker, "ThrowVelocityTracker");
|
|
6432
|
+
var ThrowVelocityTracker = _ThrowVelocityTracker;
|
|
6433
|
+
function angularVelocityFrom(previous, current, deltaTime) {
|
|
6434
|
+
if (deltaTime <= 1e-5) return new THREE7.Vector3();
|
|
6435
|
+
const delta = current.clone().multiply(previous.clone().invert());
|
|
6436
|
+
if (delta.w < 0) {
|
|
6437
|
+
delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
|
|
6438
|
+
}
|
|
6439
|
+
const angle = 2 * Math.acos(THREE7.MathUtils.clamp(delta.w, -1, 1));
|
|
6440
|
+
const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
|
|
6441
|
+
if (sinHalf < 1e-6 || angle < 1e-6) return new THREE7.Vector3();
|
|
6442
|
+
return new THREE7.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
|
|
6443
|
+
}
|
|
6444
|
+
__name(angularVelocityFrom, "angularVelocityFrom");
|
|
6445
|
+
|
|
6446
|
+
// core/systems/XRInteractionSystem.ts
|
|
6239
6447
|
var RAYLESS_HOLD_DISTANCE = 0.5;
|
|
6240
6448
|
var _XRInteractionSystem = class _XRInteractionSystem {
|
|
6241
6449
|
constructor(inputSystem) {
|
|
6242
6450
|
this.raycaster = new THREE7.Raycaster();
|
|
6243
|
-
/** Which entity each source is holding. Keyed by source state identity. */
|
|
6244
6451
|
this.grabs = /* @__PURE__ */ new Map();
|
|
6245
6452
|
this.hoveredEntityId = null;
|
|
6453
|
+
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
6454
|
+
this.elapsed = 0;
|
|
6246
6455
|
this.inputSystem = inputSystem;
|
|
6247
6456
|
}
|
|
6248
|
-
update(world) {
|
|
6457
|
+
update(world, deltaTime) {
|
|
6249
6458
|
if (!world.isXrPresenting()) {
|
|
6250
6459
|
this.releaseAll(world);
|
|
6251
6460
|
return;
|
|
6252
6461
|
}
|
|
6462
|
+
this.elapsed += Math.max(0, deltaTime);
|
|
6253
6463
|
const inputs = this.inputSystem.getInputs();
|
|
6254
6464
|
const interactables = this.collectInteractables(world);
|
|
6255
6465
|
this.hoveredEntityId = null;
|
|
@@ -6260,32 +6470,105 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
6260
6470
|
}
|
|
6261
6471
|
const held = this.grabs.get(input);
|
|
6262
6472
|
if (held) {
|
|
6263
|
-
this.moveHeld(world, input, held);
|
|
6264
|
-
if (
|
|
6473
|
+
this.moveHeld(world, input, held, deltaTime);
|
|
6474
|
+
if (this.wantsRelease(world, input, held)) {
|
|
6475
|
+
this.release(world, input);
|
|
6476
|
+
}
|
|
6265
6477
|
continue;
|
|
6266
6478
|
}
|
|
6267
|
-
const
|
|
6268
|
-
if (
|
|
6479
|
+
const candidate = this.findGrabCandidate(world, input, interactables);
|
|
6480
|
+
if (candidate === null) continue;
|
|
6269
6481
|
if (this.hoveredEntityId === null) {
|
|
6270
|
-
this.hoveredEntityId =
|
|
6482
|
+
this.hoveredEntityId = candidate.entityId;
|
|
6271
6483
|
}
|
|
6272
6484
|
const interactable = world.getComponent(
|
|
6273
|
-
|
|
6485
|
+
candidate.entityId,
|
|
6274
6486
|
XRInteractableComponent
|
|
6275
6487
|
);
|
|
6276
|
-
if (
|
|
6277
|
-
|
|
6488
|
+
if (interactable?.grabbable && this.gestureMatches(
|
|
6489
|
+
input,
|
|
6490
|
+
interactable,
|
|
6491
|
+
/* starting */
|
|
6492
|
+
true
|
|
6493
|
+
)) {
|
|
6494
|
+
this.grab(world, input, candidate.entityId, candidate.viaDistance);
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
/** Whether the grab gesture for this object is currently starting. */
|
|
6499
|
+
gestureMatches(input, interactable, starting) {
|
|
6500
|
+
const pinch = starting ? input.selectStart : input.select;
|
|
6501
|
+
const full = starting ? input.squeezeStart : input.squeeze;
|
|
6502
|
+
switch (interactable.grabGesture) {
|
|
6503
|
+
case "pinch":
|
|
6504
|
+
return pinch;
|
|
6505
|
+
case "full":
|
|
6506
|
+
return full;
|
|
6507
|
+
default:
|
|
6508
|
+
return pinch || full;
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
/** True once the gesture that started the grab has ended. */
|
|
6512
|
+
wantsRelease(world, input, held) {
|
|
6513
|
+
const interactable = world.getComponent(
|
|
6514
|
+
held.entityId,
|
|
6515
|
+
XRInteractableComponent
|
|
6516
|
+
);
|
|
6517
|
+
if (!interactable) return true;
|
|
6518
|
+
switch (interactable.grabGesture) {
|
|
6519
|
+
case "pinch":
|
|
6520
|
+
return input.selectEnd || !input.select;
|
|
6521
|
+
case "full":
|
|
6522
|
+
return input.squeezeEnd || !input.squeeze;
|
|
6523
|
+
default:
|
|
6524
|
+
return !input.select && !input.squeeze;
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
/**
|
|
6528
|
+
* Find what this hand would grab: a physically-reachable object first, then
|
|
6529
|
+
* one along the pointer ray.
|
|
6530
|
+
*
|
|
6531
|
+
* Proximity wins over the ray so that reaching for something you are touching
|
|
6532
|
+
* never picks up a distant object behind it, which feels broken.
|
|
6533
|
+
*/
|
|
6534
|
+
findGrabCandidate(world, input, interactables) {
|
|
6535
|
+
const hand = this.handPosition(input);
|
|
6536
|
+
let nearestId = null;
|
|
6537
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
6538
|
+
for (const entry of interactables) {
|
|
6539
|
+
const interactable2 = world.getComponent(
|
|
6540
|
+
entry.entityId,
|
|
6541
|
+
XRInteractableComponent
|
|
6542
|
+
);
|
|
6543
|
+
if (!interactable2) continue;
|
|
6544
|
+
if (interactable2.grabReach === "distance") continue;
|
|
6545
|
+
const position = new THREE7.Vector3();
|
|
6546
|
+
entry.object.getWorldPosition(position);
|
|
6547
|
+
const distance = position.distanceTo(hand);
|
|
6548
|
+
if (distance <= interactable2.grabRadius && distance < nearestDistance) {
|
|
6549
|
+
nearestDistance = distance;
|
|
6550
|
+
nearestId = entry.entityId;
|
|
6278
6551
|
}
|
|
6279
6552
|
}
|
|
6553
|
+
if (nearestId !== null) {
|
|
6554
|
+
return { entityId: nearestId, viaDistance: false };
|
|
6555
|
+
}
|
|
6556
|
+
const hitId = this.raycast(input, interactables);
|
|
6557
|
+
if (hitId === null) return null;
|
|
6558
|
+
const interactable = world.getComponent(hitId, XRInteractableComponent);
|
|
6559
|
+
const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
|
|
6560
|
+
return { entityId: hitId, viaDistance: viaDistanceAllowed };
|
|
6280
6561
|
}
|
|
6281
6562
|
collectInteractables(world) {
|
|
6282
6563
|
const out = [];
|
|
6283
6564
|
for (const entityId of world.query(XRInteractableComponent, Object3DRef)) {
|
|
6284
6565
|
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
6285
|
-
if (!interactable?.enabled
|
|
6566
|
+
if (!interactable?.enabled) continue;
|
|
6286
6567
|
if (!world.isEntityVisible(entityId)) continue;
|
|
6287
6568
|
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
6288
|
-
if (object)
|
|
6569
|
+
if (object) {
|
|
6570
|
+
out.push({ entityId, object, pointable: interactable.pointable });
|
|
6571
|
+
}
|
|
6289
6572
|
}
|
|
6290
6573
|
return out;
|
|
6291
6574
|
}
|
|
@@ -6293,9 +6576,11 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
6293
6576
|
raycast(input, interactables) {
|
|
6294
6577
|
if (interactables.length === 0) return null;
|
|
6295
6578
|
this.raycaster.set(input.rayOrigin, input.rayDirection);
|
|
6579
|
+
this.raycaster.far = Number.POSITIVE_INFINITY;
|
|
6296
6580
|
let nearestId = null;
|
|
6297
6581
|
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
6298
6582
|
for (const entry of interactables) {
|
|
6583
|
+
if (!entry.pointable) continue;
|
|
6299
6584
|
const hit = this.raycaster.intersectObject(entry.object, true)[0];
|
|
6300
6585
|
if (!hit) continue;
|
|
6301
6586
|
if (hit.distance < nearestDistance) {
|
|
@@ -6305,43 +6590,120 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
6305
6590
|
}
|
|
6306
6591
|
return nearestId;
|
|
6307
6592
|
}
|
|
6308
|
-
grab(world, input, entityId) {
|
|
6593
|
+
grab(world, input, entityId, viaDistance) {
|
|
6309
6594
|
const transform = world.getComponent(entityId, TransformComponent);
|
|
6310
|
-
|
|
6595
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
6596
|
+
if (!transform || !interactable) return;
|
|
6311
6597
|
const hand = this.handPosition(input);
|
|
6312
|
-
const
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
)
|
|
6598
|
+
const handQuaternion = this.handQuaternion(input);
|
|
6599
|
+
const remote = viaDistance && interactable.distanceBehavior === "remote";
|
|
6600
|
+
let offset;
|
|
6601
|
+
let remoteDistance = 0;
|
|
6602
|
+
if (remote) {
|
|
6603
|
+
const position = new THREE7.Vector3(
|
|
6604
|
+
transform.position.x,
|
|
6605
|
+
transform.position.y,
|
|
6606
|
+
transform.position.z
|
|
6607
|
+
);
|
|
6608
|
+
remoteDistance = position.distanceTo(input.rayOrigin);
|
|
6609
|
+
offset = new THREE7.Vector3();
|
|
6610
|
+
} else if (viaDistance) {
|
|
6611
|
+
offset = new THREE7.Vector3(
|
|
6612
|
+
interactable.grabOffsetPosition.x,
|
|
6613
|
+
interactable.grabOffsetPosition.y,
|
|
6614
|
+
interactable.grabOffsetPosition.z
|
|
6615
|
+
);
|
|
6616
|
+
} else {
|
|
6617
|
+
offset = new THREE7.Vector3(
|
|
6618
|
+
transform.position.x - hand.x,
|
|
6619
|
+
transform.position.y - hand.y,
|
|
6620
|
+
transform.position.z - hand.z
|
|
6621
|
+
);
|
|
6622
|
+
}
|
|
6317
6623
|
const physics = world.getComponent(entityId, PhysicsComponent);
|
|
6318
6624
|
let restoreBodyType = null;
|
|
6319
|
-
|
|
6320
|
-
if (physics && interactable?.grabMode === "kinematic") {
|
|
6625
|
+
if (physics) {
|
|
6321
6626
|
restoreBodyType = physics.values.rigidbody.type;
|
|
6322
6627
|
physics.values.rigidbody.type = "kinematicPosition";
|
|
6323
6628
|
}
|
|
6324
|
-
|
|
6629
|
+
const tracker = new ThrowVelocityTracker();
|
|
6630
|
+
tracker.record(hand, this.elapsed);
|
|
6631
|
+
this.grabs.set(input, {
|
|
6632
|
+
entityId,
|
|
6633
|
+
offset,
|
|
6634
|
+
rotationOffset: new THREE7.Quaternion().setFromEuler(
|
|
6635
|
+
new THREE7.Euler(
|
|
6636
|
+
interactable.grabOffsetRotation.x,
|
|
6637
|
+
interactable.grabOffsetRotation.y,
|
|
6638
|
+
interactable.grabOffsetRotation.z
|
|
6639
|
+
)
|
|
6640
|
+
),
|
|
6641
|
+
remoteDistance,
|
|
6642
|
+
remote,
|
|
6643
|
+
restoreBodyType,
|
|
6644
|
+
tracker,
|
|
6645
|
+
lastHandQuaternion: handQuaternion.clone(),
|
|
6646
|
+
angularVelocity: new THREE7.Vector3()
|
|
6647
|
+
});
|
|
6325
6648
|
this.dispatch(world, entityId, (b) => b.onGrab(input));
|
|
6326
6649
|
}
|
|
6327
|
-
moveHeld(world, input, held) {
|
|
6650
|
+
moveHeld(world, input, held, deltaTime) {
|
|
6328
6651
|
const transform = world.getComponent(held.entityId, TransformComponent);
|
|
6329
6652
|
if (!transform) return;
|
|
6330
6653
|
const hand = this.handPosition(input);
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6654
|
+
if (held.remote) {
|
|
6655
|
+
const target = new THREE7.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
|
|
6656
|
+
transform.position.x = target.x;
|
|
6657
|
+
transform.position.y = target.y;
|
|
6658
|
+
transform.position.z = target.z;
|
|
6659
|
+
} else {
|
|
6660
|
+
transform.position.x = hand.x + held.offset.x;
|
|
6661
|
+
transform.position.y = hand.y + held.offset.y;
|
|
6662
|
+
transform.position.z = hand.z + held.offset.z;
|
|
6663
|
+
}
|
|
6664
|
+
held.tracker.record(
|
|
6665
|
+
held.remote ? new THREE7.Vector3(
|
|
6666
|
+
transform.position.x,
|
|
6667
|
+
transform.position.y,
|
|
6668
|
+
transform.position.z
|
|
6669
|
+
) : hand,
|
|
6670
|
+
this.elapsed
|
|
6671
|
+
);
|
|
6672
|
+
const handQuaternion = this.handQuaternion(input);
|
|
6673
|
+
held.angularVelocity = angularVelocityFrom(
|
|
6674
|
+
held.lastHandQuaternion,
|
|
6675
|
+
handQuaternion,
|
|
6676
|
+
deltaTime
|
|
6677
|
+
);
|
|
6678
|
+
held.lastHandQuaternion = handQuaternion;
|
|
6334
6679
|
}
|
|
6335
6680
|
release(world, input) {
|
|
6336
6681
|
const held = this.grabs.get(input);
|
|
6337
6682
|
if (!held) return;
|
|
6338
6683
|
this.grabs.delete(input);
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6684
|
+
const interactable = world.getComponent(
|
|
6685
|
+
held.entityId,
|
|
6686
|
+
XRInteractableComponent
|
|
6687
|
+
);
|
|
6688
|
+
const physics = world.getComponent(held.entityId, PhysicsComponent);
|
|
6689
|
+
if (held.restoreBodyType !== null && physics) {
|
|
6690
|
+
physics.values.rigidbody.type = held.restoreBodyType;
|
|
6691
|
+
const body = physics.rigidBody;
|
|
6692
|
+
const dynamic = held.restoreBodyType === "dynamic";
|
|
6693
|
+
if (body && dynamic && interactable?.throwOnRelease) {
|
|
6694
|
+
const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
|
|
6695
|
+
try {
|
|
6696
|
+
body.setLinvel(
|
|
6697
|
+
{ x: velocity.x, y: velocity.y, z: velocity.z },
|
|
6698
|
+
true
|
|
6699
|
+
);
|
|
6700
|
+
const spin = held.angularVelocity;
|
|
6701
|
+
body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
|
|
6702
|
+
} catch {
|
|
6703
|
+
}
|
|
6343
6704
|
}
|
|
6344
6705
|
}
|
|
6706
|
+
held.tracker.clear();
|
|
6345
6707
|
this.dispatch(world, held.entityId, (b) => b.onRelease(input));
|
|
6346
6708
|
}
|
|
6347
6709
|
releaseAll(world) {
|
|
@@ -6350,14 +6712,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
6350
6712
|
}
|
|
6351
6713
|
}
|
|
6352
6714
|
/**
|
|
6353
|
-
* Where the "hand" is
|
|
6354
|
-
*
|
|
6355
|
-
*
|
|
6715
|
+
* Where the "hand" is. Controllers and tracked hands report a grip pose;
|
|
6716
|
+
* gaze and transient-pointer sources have none, so we hold the object a fixed
|
|
6717
|
+
* distance along the ray instead — which is what makes Vision Pro work.
|
|
6356
6718
|
*/
|
|
6357
6719
|
handPosition(input) {
|
|
6358
6720
|
if (input.gripPosition) return input.gripPosition;
|
|
6359
6721
|
return new THREE7.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
|
|
6360
6722
|
}
|
|
6723
|
+
/**
|
|
6724
|
+
* Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
|
|
6725
|
+
* so we derive one from the aim direction — enough for spin on release.
|
|
6726
|
+
*/
|
|
6727
|
+
handQuaternion(input) {
|
|
6728
|
+
if (input.gripQuaternion) return input.gripQuaternion.clone();
|
|
6729
|
+
return new THREE7.Quaternion().setFromUnitVectors(
|
|
6730
|
+
new THREE7.Vector3(0, 0, -1),
|
|
6731
|
+
input.rayDirection.clone().normalize()
|
|
6732
|
+
);
|
|
6733
|
+
}
|
|
6361
6734
|
dispatch(world, entityId, visit) {
|
|
6362
6735
|
const script = world.getComponent(entityId, ScriptComponent);
|
|
6363
6736
|
const instances = script?.scriptInstances;
|
|
@@ -6927,6 +7300,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
6927
7300
|
this.handStyleSignature = "";
|
|
6928
7301
|
this.handMaterial = null;
|
|
6929
7302
|
this.interactionSystem = options?.interactionSystem ?? null;
|
|
7303
|
+
this.inputSystem = options?.inputSystem ?? null;
|
|
6930
7304
|
this.group.name = "XRControllerVisuals";
|
|
6931
7305
|
this.group.userData.ecsPreviewHidden = true;
|
|
6932
7306
|
}
|
|
@@ -6943,8 +7317,11 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
6943
7317
|
for (const { index, side } of SLOTS) {
|
|
6944
7318
|
const visuals = this.slots.get(index);
|
|
6945
7319
|
if (!visuals) continue;
|
|
6946
|
-
|
|
6947
|
-
|
|
7320
|
+
const liveKind = this.liveKindFor(side);
|
|
7321
|
+
const hasController = liveKind === null || liveKind === "controller";
|
|
7322
|
+
const hasHand = liveKind === null || liveKind === "hand";
|
|
7323
|
+
visuals.controllerModel.visible = showControllers && hasController;
|
|
7324
|
+
visuals.handModel.visible = showHands && hasHand;
|
|
6948
7325
|
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
6949
7326
|
if (showHands && this.handMaterial) {
|
|
6950
7327
|
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
@@ -6952,6 +7329,20 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
6952
7329
|
this.syncCustomContent(world, visuals, side);
|
|
6953
7330
|
}
|
|
6954
7331
|
}
|
|
7332
|
+
/**
|
|
7333
|
+
* What kind of source is currently driving this hand, or null when we cannot
|
|
7334
|
+
* tell (no input system wired) — in which case callers fall back to showing
|
|
7335
|
+
* everything rather than hiding the user's hands entirely.
|
|
7336
|
+
*/
|
|
7337
|
+
liveKindFor(side) {
|
|
7338
|
+
if (!this.inputSystem) return null;
|
|
7339
|
+
const inputs = this.inputSystem.getInputs();
|
|
7340
|
+
if (inputs.length === 0) return null;
|
|
7341
|
+
for (const input of inputs) {
|
|
7342
|
+
if (input.handedness === side && input.connected) return input.kind;
|
|
7343
|
+
}
|
|
7344
|
+
return "none";
|
|
7345
|
+
}
|
|
6955
7346
|
/** The scene's interactor config, if one was authored. */
|
|
6956
7347
|
resolveInteractor(world) {
|
|
6957
7348
|
const entityId = world.query(XRInteractorComponent)[0];
|
|
@@ -7211,7 +7602,512 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
7211
7602
|
};
|
|
7212
7603
|
__name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
|
|
7213
7604
|
var XRTeleportVisualSystem = _XRTeleportVisualSystem;
|
|
7605
|
+
var XR_FINGERS = [
|
|
7606
|
+
"thumb",
|
|
7607
|
+
"index",
|
|
7608
|
+
"middle",
|
|
7609
|
+
"ring",
|
|
7610
|
+
"pinky"
|
|
7611
|
+
];
|
|
7612
|
+
var PINCH_FINGERS = [
|
|
7613
|
+
"index",
|
|
7614
|
+
"middle",
|
|
7615
|
+
"ring",
|
|
7616
|
+
"pinky"
|
|
7617
|
+
];
|
|
7618
|
+
var FINGER_CHAINS = {
|
|
7619
|
+
thumb: [
|
|
7620
|
+
"thumb-metacarpal",
|
|
7621
|
+
"thumb-phalanx-proximal",
|
|
7622
|
+
"thumb-phalanx-distal",
|
|
7623
|
+
"thumb-tip"
|
|
7624
|
+
],
|
|
7625
|
+
index: [
|
|
7626
|
+
"index-finger-metacarpal",
|
|
7627
|
+
"index-finger-phalanx-proximal",
|
|
7628
|
+
"index-finger-phalanx-intermediate",
|
|
7629
|
+
"index-finger-phalanx-distal",
|
|
7630
|
+
"index-finger-tip"
|
|
7631
|
+
],
|
|
7632
|
+
middle: [
|
|
7633
|
+
"middle-finger-metacarpal",
|
|
7634
|
+
"middle-finger-phalanx-proximal",
|
|
7635
|
+
"middle-finger-phalanx-intermediate",
|
|
7636
|
+
"middle-finger-phalanx-distal",
|
|
7637
|
+
"middle-finger-tip"
|
|
7638
|
+
],
|
|
7639
|
+
ring: [
|
|
7640
|
+
"ring-finger-metacarpal",
|
|
7641
|
+
"ring-finger-phalanx-proximal",
|
|
7642
|
+
"ring-finger-phalanx-intermediate",
|
|
7643
|
+
"ring-finger-phalanx-distal",
|
|
7644
|
+
"ring-finger-tip"
|
|
7645
|
+
],
|
|
7646
|
+
pinky: [
|
|
7647
|
+
"pinky-finger-metacarpal",
|
|
7648
|
+
"pinky-finger-phalanx-proximal",
|
|
7649
|
+
"pinky-finger-phalanx-intermediate",
|
|
7650
|
+
"pinky-finger-phalanx-distal",
|
|
7651
|
+
"pinky-finger-tip"
|
|
7652
|
+
]
|
|
7653
|
+
};
|
|
7654
|
+
var TIP_JOINTS = {
|
|
7655
|
+
thumb: "thumb-tip",
|
|
7656
|
+
index: "index-finger-tip",
|
|
7657
|
+
middle: "middle-finger-tip",
|
|
7658
|
+
ring: "ring-finger-tip",
|
|
7659
|
+
pinky: "pinky-finger-tip"
|
|
7660
|
+
};
|
|
7661
|
+
var METACARPAL_JOINTS = {
|
|
7662
|
+
thumb: "thumb-metacarpal",
|
|
7663
|
+
index: "index-finger-metacarpal",
|
|
7664
|
+
middle: "middle-finger-metacarpal",
|
|
7665
|
+
ring: "ring-finger-metacarpal",
|
|
7666
|
+
pinky: "pinky-finger-metacarpal"
|
|
7667
|
+
};
|
|
7668
|
+
var FALLBACK_HAND_SCALE = 0.08;
|
|
7669
|
+
var STRAIGHT_COS = 0.95;
|
|
7670
|
+
var CURLED_COS = 0.55;
|
|
7671
|
+
function clamp01(value) {
|
|
7672
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
7673
|
+
}
|
|
7674
|
+
__name(clamp01, "clamp01");
|
|
7675
|
+
function boneDirections(joints, finger) {
|
|
7676
|
+
const chain = FINGER_CHAINS[finger];
|
|
7677
|
+
const directions = [];
|
|
7678
|
+
for (let i = 1; i < chain.length; i += 1) {
|
|
7679
|
+
const from = joints[chain[i - 1]];
|
|
7680
|
+
const to = joints[chain[i]];
|
|
7681
|
+
if (!from || !to) return null;
|
|
7682
|
+
const direction = new THREE7.Vector3().subVectors(to, from);
|
|
7683
|
+
if (direction.lengthSq() < 1e-12) return null;
|
|
7684
|
+
directions.push(direction.normalize());
|
|
7685
|
+
}
|
|
7686
|
+
return directions.length >= 2 ? directions : null;
|
|
7687
|
+
}
|
|
7688
|
+
__name(boneDirections, "boneDirections");
|
|
7689
|
+
function bendAmounts(directions) {
|
|
7690
|
+
const bends = [];
|
|
7691
|
+
for (let i = 1; i < directions.length; i += 1) {
|
|
7692
|
+
const cos = directions[i - 1].dot(directions[i]);
|
|
7693
|
+
const straightness = clamp01((cos - CURLED_COS) / (STRAIGHT_COS - CURLED_COS));
|
|
7694
|
+
bends.push(1 - straightness);
|
|
7695
|
+
}
|
|
7696
|
+
return bends;
|
|
7697
|
+
}
|
|
7698
|
+
__name(bendAmounts, "bendAmounts");
|
|
7699
|
+
function average(values) {
|
|
7700
|
+
if (values.length === 0) return 0;
|
|
7701
|
+
let sum = 0;
|
|
7702
|
+
for (const value of values) sum += value;
|
|
7703
|
+
return sum / values.length;
|
|
7704
|
+
}
|
|
7705
|
+
__name(average, "average");
|
|
7706
|
+
function computeFingerCurl(joints, finger) {
|
|
7707
|
+
const directions = boneDirections(joints, finger);
|
|
7708
|
+
if (!directions) return null;
|
|
7709
|
+
const bends = bendAmounts(directions);
|
|
7710
|
+
if (bends.length === 0) return null;
|
|
7711
|
+
return {
|
|
7712
|
+
baseCurl: bends[0],
|
|
7713
|
+
tipCurl: average(bends.slice(1)) || bends[bends.length - 1],
|
|
7714
|
+
curl: average(bends)
|
|
7715
|
+
};
|
|
7716
|
+
}
|
|
7717
|
+
__name(computeFingerCurl, "computeFingerCurl");
|
|
7718
|
+
function computePalmWidth(joints) {
|
|
7719
|
+
const index = joints["index-finger-metacarpal"];
|
|
7720
|
+
const pinky = joints["pinky-finger-metacarpal"];
|
|
7721
|
+
if (!index || !pinky) return null;
|
|
7722
|
+
const width = index.distanceTo(pinky);
|
|
7723
|
+
return width > 1e-6 ? width : null;
|
|
7724
|
+
}
|
|
7725
|
+
__name(computePalmWidth, "computePalmWidth");
|
|
7726
|
+
function estimateHandScale(joints) {
|
|
7727
|
+
const measurements = [];
|
|
7728
|
+
const width = computePalmWidth(joints);
|
|
7729
|
+
if (width !== null) measurements.push(width);
|
|
7730
|
+
const wrist = joints.wrist;
|
|
7731
|
+
const middleTip = joints["middle-finger-tip"];
|
|
7732
|
+
if (wrist && middleTip) {
|
|
7733
|
+
const span = wrist.distanceTo(middleTip);
|
|
7734
|
+
if (span > 1e-6) measurements.push(span * 0.5);
|
|
7735
|
+
}
|
|
7736
|
+
if (measurements.length === 0) return FALLBACK_HAND_SCALE;
|
|
7737
|
+
return average(measurements);
|
|
7738
|
+
}
|
|
7739
|
+
__name(estimateHandScale, "estimateHandScale");
|
|
7740
|
+
function computePinch(joints, finger) {
|
|
7741
|
+
const thumbTip = joints["thumb-tip"];
|
|
7742
|
+
const fingerTip = joints[TIP_JOINTS[finger]];
|
|
7743
|
+
if (!thumbTip || !fingerTip) return null;
|
|
7744
|
+
const scale = estimateHandScale(joints);
|
|
7745
|
+
const distance = thumbTip.distanceTo(fingerTip);
|
|
7746
|
+
return clamp01(1 - distance / (scale * 0.7));
|
|
7747
|
+
}
|
|
7748
|
+
__name(computePinch, "computePinch");
|
|
7749
|
+
function computeSpread(joints, a, b) {
|
|
7750
|
+
const aBase = joints[METACARPAL_JOINTS[a]];
|
|
7751
|
+
const aTip = joints[TIP_JOINTS[a]];
|
|
7752
|
+
const bBase = joints[METACARPAL_JOINTS[b]];
|
|
7753
|
+
const bTip = joints[TIP_JOINTS[b]];
|
|
7754
|
+
if (!aBase || !aTip || !bBase || !bTip) return null;
|
|
7755
|
+
const dirA = new THREE7.Vector3().subVectors(aTip, aBase);
|
|
7756
|
+
const dirB = new THREE7.Vector3().subVectors(bTip, bBase);
|
|
7757
|
+
if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
|
|
7758
|
+
return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
|
|
7759
|
+
}
|
|
7760
|
+
__name(computeSpread, "computeSpread");
|
|
7761
|
+
function computePalmPose(joints, handedness) {
|
|
7762
|
+
const wrist = joints.wrist;
|
|
7763
|
+
const indexBase = joints["index-finger-metacarpal"];
|
|
7764
|
+
const pinkyBase = joints["pinky-finger-metacarpal"];
|
|
7765
|
+
if (!wrist || !indexBase || !pinkyBase) return null;
|
|
7766
|
+
const u = new THREE7.Vector3().subVectors(indexBase, wrist);
|
|
7767
|
+
const v = new THREE7.Vector3().subVectors(pinkyBase, wrist);
|
|
7768
|
+
const normal = new THREE7.Vector3().crossVectors(u, v);
|
|
7769
|
+
if (normal.lengthSq() < 1e-12) return null;
|
|
7770
|
+
normal.normalize();
|
|
7771
|
+
if (handedness === "left") normal.multiplyScalar(-1);
|
|
7772
|
+
const right = new THREE7.Vector3().subVectors(indexBase, pinkyBase);
|
|
7773
|
+
if (right.lengthSq() < 1e-12) return null;
|
|
7774
|
+
right.normalize();
|
|
7775
|
+
if (handedness === "left") right.multiplyScalar(-1);
|
|
7776
|
+
const up = new THREE7.Vector3().crossVectors(normal, right).normalize();
|
|
7777
|
+
const center = new THREE7.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
|
|
7778
|
+
return {
|
|
7779
|
+
center,
|
|
7780
|
+
normal,
|
|
7781
|
+
right,
|
|
7782
|
+
up,
|
|
7783
|
+
width: computePalmWidth(joints) ?? FALLBACK_HAND_SCALE
|
|
7784
|
+
};
|
|
7785
|
+
}
|
|
7786
|
+
__name(computePalmPose, "computePalmPose");
|
|
7787
|
+
function computeHandShape(joints, handedness) {
|
|
7788
|
+
const fingers = {};
|
|
7789
|
+
let tracked = 0;
|
|
7790
|
+
for (const finger of XR_FINGERS) {
|
|
7791
|
+
const curl = computeFingerCurl(joints, finger);
|
|
7792
|
+
if (curl) {
|
|
7793
|
+
fingers[finger] = curl;
|
|
7794
|
+
tracked += 1;
|
|
7795
|
+
} else {
|
|
7796
|
+
fingers[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
if (tracked === 0) return null;
|
|
7800
|
+
const pinch = {};
|
|
7801
|
+
for (const finger of PINCH_FINGERS) {
|
|
7802
|
+
pinch[finger] = computePinch(joints, finger) ?? 0;
|
|
7803
|
+
}
|
|
7804
|
+
const thumbBase = joints["thumb-metacarpal"];
|
|
7805
|
+
const thumbTip = joints["thumb-tip"];
|
|
7806
|
+
let thumbDirection = null;
|
|
7807
|
+
if (thumbBase && thumbTip) {
|
|
7808
|
+
const direction = new THREE7.Vector3().subVectors(thumbTip, thumbBase);
|
|
7809
|
+
if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
|
|
7810
|
+
}
|
|
7811
|
+
return {
|
|
7812
|
+
handedness,
|
|
7813
|
+
fingers,
|
|
7814
|
+
pinch,
|
|
7815
|
+
spread: {
|
|
7816
|
+
thumbIndex: computeSpread(joints, "thumb", "index") ?? 0,
|
|
7817
|
+
indexMiddle: computeSpread(joints, "index", "middle") ?? 0,
|
|
7818
|
+
middleRing: computeSpread(joints, "middle", "ring") ?? 0,
|
|
7819
|
+
ringPinky: computeSpread(joints, "ring", "pinky") ?? 0
|
|
7820
|
+
},
|
|
7821
|
+
palm: computePalmPose(joints, handedness),
|
|
7822
|
+
scale: estimateHandScale(joints),
|
|
7823
|
+
thumbDirection
|
|
7824
|
+
};
|
|
7825
|
+
}
|
|
7826
|
+
__name(computeHandShape, "computeHandShape");
|
|
7827
|
+
var MATCH_FALLOFF = 0.25;
|
|
7828
|
+
var NO_MATCH = {
|
|
7829
|
+
matched: false,
|
|
7830
|
+
confidence: 0,
|
|
7831
|
+
worstConstraint: null
|
|
7832
|
+
};
|
|
7833
|
+
function scoreAgainst(actual, target, tolerance) {
|
|
7834
|
+
const error = Math.abs(actual - target);
|
|
7835
|
+
if (error <= tolerance) return 1;
|
|
7836
|
+
return clamp01(1 - (error - tolerance) / MATCH_FALLOFF);
|
|
7837
|
+
}
|
|
7838
|
+
__name(scoreAgainst, "scoreAgainst");
|
|
7839
|
+
var WORLD_DIRECTIONS = {
|
|
7840
|
+
up: new THREE7.Vector3(0, 1, 0),
|
|
7841
|
+
down: new THREE7.Vector3(0, -1, 0),
|
|
7842
|
+
forward: new THREE7.Vector3(0, 0, -1),
|
|
7843
|
+
back: new THREE7.Vector3(0, 0, 1),
|
|
7844
|
+
left: new THREE7.Vector3(-1, 0, 0),
|
|
7845
|
+
right: new THREE7.Vector3(1, 0, 0)
|
|
7846
|
+
};
|
|
7847
|
+
function axisVector(shape, axis) {
|
|
7848
|
+
if (axis === "thumb") return shape.thumbDirection;
|
|
7849
|
+
if (!shape.palm) return null;
|
|
7850
|
+
return axis === "palm-normal" ? shape.palm.normal : shape.palm.up;
|
|
7851
|
+
}
|
|
7852
|
+
__name(axisVector, "axisVector");
|
|
7853
|
+
function resolveDirection(direction, shape, context) {
|
|
7854
|
+
if (direction !== "toward-head" && direction !== "away-from-head") {
|
|
7855
|
+
return WORLD_DIRECTIONS[direction];
|
|
7856
|
+
}
|
|
7857
|
+
const head = context?.headPosition;
|
|
7858
|
+
const origin = shape.palm?.center;
|
|
7859
|
+
if (!head || !origin) return null;
|
|
7860
|
+
const toHead = new THREE7.Vector3().subVectors(head, origin);
|
|
7861
|
+
if (toHead.lengthSq() < 1e-12) return null;
|
|
7862
|
+
toHead.normalize();
|
|
7863
|
+
return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
|
|
7864
|
+
}
|
|
7865
|
+
__name(resolveDirection, "resolveDirection");
|
|
7866
|
+
function matchHandPose(shape, definition, context) {
|
|
7867
|
+
if (definition.hand && definition.hand !== shape.handedness) return NO_MATCH;
|
|
7868
|
+
let confidence = 1;
|
|
7869
|
+
let worstScore = 1;
|
|
7870
|
+
let worstConstraint = null;
|
|
7871
|
+
const consider = /* @__PURE__ */ __name((score, label) => {
|
|
7872
|
+
if (score < confidence) confidence = score;
|
|
7873
|
+
if (score < worstScore) {
|
|
7874
|
+
worstScore = score;
|
|
7875
|
+
worstConstraint = label;
|
|
7876
|
+
}
|
|
7877
|
+
}, "consider");
|
|
7878
|
+
for (const finger of XR_FINGERS) {
|
|
7879
|
+
const target = definition.fingers[finger];
|
|
7880
|
+
if (!target) continue;
|
|
7881
|
+
const actual = shape.fingers[finger];
|
|
7882
|
+
consider(scoreAgainst(actual.curl, target.curl, target.tolerance), finger);
|
|
7883
|
+
if (target.baseCurl !== void 0) {
|
|
7884
|
+
consider(
|
|
7885
|
+
scoreAgainst(actual.baseCurl, target.baseCurl, target.tolerance),
|
|
7886
|
+
`${finger} knuckle`
|
|
7887
|
+
);
|
|
7888
|
+
}
|
|
7889
|
+
if (target.tipCurl !== void 0) {
|
|
7890
|
+
consider(
|
|
7891
|
+
scoreAgainst(actual.tipCurl, target.tipCurl, target.tolerance),
|
|
7892
|
+
`${finger} tip`
|
|
7893
|
+
);
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7896
|
+
if (definition.pinch) {
|
|
7897
|
+
const { finger, target, tolerance } = definition.pinch;
|
|
7898
|
+
consider(
|
|
7899
|
+
scoreAgainst(shape.pinch[finger], target, tolerance),
|
|
7900
|
+
`${finger} pinch`
|
|
7901
|
+
);
|
|
7902
|
+
}
|
|
7903
|
+
if (definition.orientation) {
|
|
7904
|
+
const { axis, direction, toleranceDegrees } = definition.orientation;
|
|
7905
|
+
const handAxis = axisVector(shape, axis);
|
|
7906
|
+
const wanted = resolveDirection(direction, shape, context);
|
|
7907
|
+
if (!handAxis || !wanted) {
|
|
7908
|
+
consider(0, "orientation");
|
|
7909
|
+
} else {
|
|
7910
|
+
const angle = THREE7.MathUtils.radToDeg(
|
|
7911
|
+
Math.acos(THREE7.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
|
|
7912
|
+
);
|
|
7913
|
+
const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
|
|
7914
|
+
consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7917
|
+
return {
|
|
7918
|
+
matched: confidence >= 1,
|
|
7919
|
+
confidence,
|
|
7920
|
+
worstConstraint: confidence >= 1 ? null : worstConstraint
|
|
7921
|
+
};
|
|
7922
|
+
}
|
|
7923
|
+
__name(matchHandPose, "matchHandPose");
|
|
7924
|
+
var DEFAULT_ENTER_CONFIDENCE = 0.8;
|
|
7925
|
+
var DEFAULT_EXIT_CONFIDENCE = 0.55;
|
|
7926
|
+
var DEFAULT_MIN_HOLD_SECONDS = 0.15;
|
|
7927
|
+
var DEFAULT_LOST_GRACE_SECONDS = 0.25;
|
|
7928
|
+
var _HandPoseTracker = class _HandPoseTracker {
|
|
7929
|
+
constructor(options) {
|
|
7930
|
+
this.active = false;
|
|
7931
|
+
this.holdingSince = null;
|
|
7932
|
+
this.lostSince = null;
|
|
7933
|
+
this.enterConfidence = options?.enterConfidence ?? DEFAULT_ENTER_CONFIDENCE;
|
|
7934
|
+
const exit = options?.exitConfidence ?? DEFAULT_EXIT_CONFIDENCE;
|
|
7935
|
+
this.exitConfidence = Math.min(exit, this.enterConfidence - 1e-3);
|
|
7936
|
+
this.minHoldSeconds = options?.minHoldSeconds ?? DEFAULT_MIN_HOLD_SECONDS;
|
|
7937
|
+
this.lostGraceSeconds = options?.lostGraceSeconds ?? DEFAULT_LOST_GRACE_SECONDS;
|
|
7938
|
+
}
|
|
7939
|
+
isActive() {
|
|
7940
|
+
return this.active;
|
|
7941
|
+
}
|
|
7942
|
+
/** Feed this frame's confidence. Returns an event on a transition. */
|
|
7943
|
+
update(confidence, now) {
|
|
7944
|
+
this.lostSince = null;
|
|
7945
|
+
if (this.active) {
|
|
7946
|
+
if (confidence < this.exitConfidence) {
|
|
7947
|
+
this.active = false;
|
|
7948
|
+
this.holdingSince = null;
|
|
7949
|
+
return "end";
|
|
7950
|
+
}
|
|
7951
|
+
return null;
|
|
7952
|
+
}
|
|
7953
|
+
if (confidence >= this.enterConfidence) {
|
|
7954
|
+
if (this.holdingSince === null) this.holdingSince = now;
|
|
7955
|
+
if (now - this.holdingSince >= this.minHoldSeconds) {
|
|
7956
|
+
this.active = true;
|
|
7957
|
+
return "start";
|
|
7958
|
+
}
|
|
7959
|
+
return null;
|
|
7960
|
+
}
|
|
7961
|
+
this.holdingSince = null;
|
|
7962
|
+
return null;
|
|
7963
|
+
}
|
|
7964
|
+
/** Feed a frame where the hand isn't tracked at all. */
|
|
7965
|
+
miss(now) {
|
|
7966
|
+
this.holdingSince = null;
|
|
7967
|
+
if (!this.active) return null;
|
|
7968
|
+
if (this.lostSince === null) this.lostSince = now;
|
|
7969
|
+
if (now - this.lostSince < this.lostGraceSeconds) return null;
|
|
7970
|
+
this.active = false;
|
|
7971
|
+
this.lostSince = null;
|
|
7972
|
+
return "end";
|
|
7973
|
+
}
|
|
7974
|
+
reset() {
|
|
7975
|
+
this.active = false;
|
|
7976
|
+
this.holdingSince = null;
|
|
7977
|
+
this.lostSince = null;
|
|
7978
|
+
}
|
|
7979
|
+
};
|
|
7980
|
+
__name(_HandPoseTracker, "HandPoseTracker");
|
|
7981
|
+
var HandPoseTracker = _HandPoseTracker;
|
|
7982
|
+
|
|
7983
|
+
// core/systems/XRHandPoseSystem.ts
|
|
7984
|
+
var _XRHandPoseSystem = class _XRHandPoseSystem {
|
|
7985
|
+
constructor(inputSystem) {
|
|
7986
|
+
this.trackers = /* @__PURE__ */ new Map();
|
|
7987
|
+
this.shapes = /* @__PURE__ */ new Map();
|
|
7988
|
+
/** Reused so head-relative orientation checks allocate nothing per frame. */
|
|
7989
|
+
this.headPosition = new THREE7.Vector3();
|
|
7990
|
+
this.elapsed = 0;
|
|
7991
|
+
this.inputSystem = inputSystem;
|
|
7992
|
+
}
|
|
7993
|
+
update(world, deltaTime) {
|
|
7994
|
+
if (!world.isXrPresenting()) {
|
|
7995
|
+
this.resetAll();
|
|
7996
|
+
return;
|
|
7997
|
+
}
|
|
7998
|
+
this.elapsed += deltaTime;
|
|
7999
|
+
this.refreshShapes();
|
|
8000
|
+
const entries = this.collectEntries(world);
|
|
8001
|
+
if (entries.length === 0) return;
|
|
8002
|
+
world.getCamera().getWorldPosition(this.headPosition);
|
|
8003
|
+
const context = { headPosition: this.headPosition };
|
|
8004
|
+
for (const { entityId, entry } of entries) {
|
|
8005
|
+
const definition = handPoseDefinitionFor(entry, getHandPosePreset);
|
|
8006
|
+
if (!definition) continue;
|
|
8007
|
+
for (const handedness of ["left", "right"]) {
|
|
8008
|
+
if (entry.hand && entry.hand !== handedness) continue;
|
|
8009
|
+
const key = `${entityId}:${entry.id}`;
|
|
8010
|
+
const trackers = this.trackersFor(handedness);
|
|
8011
|
+
let tracker = trackers.get(key);
|
|
8012
|
+
if (!tracker) {
|
|
8013
|
+
tracker = new HandPoseTracker({
|
|
8014
|
+
minHoldSeconds: entry.minHoldSeconds
|
|
8015
|
+
});
|
|
8016
|
+
trackers.set(key, tracker);
|
|
8017
|
+
}
|
|
8018
|
+
const shape = this.shapes.get(handedness) ?? null;
|
|
8019
|
+
if (!shape) {
|
|
8020
|
+
if (tracker.miss(this.elapsed) === "end") {
|
|
8021
|
+
this.dispatch(world, entityId, "end", {
|
|
8022
|
+
poseId: entry.id,
|
|
8023
|
+
poseName: entry.name,
|
|
8024
|
+
handedness,
|
|
8025
|
+
confidence: 0
|
|
8026
|
+
});
|
|
8027
|
+
}
|
|
8028
|
+
continue;
|
|
8029
|
+
}
|
|
8030
|
+
const match = matchHandPose(shape, definition, context);
|
|
8031
|
+
const event = tracker.update(match.confidence, this.elapsed);
|
|
8032
|
+
if (event) {
|
|
8033
|
+
this.dispatch(world, entityId, event, {
|
|
8034
|
+
poseId: entry.id,
|
|
8035
|
+
poseName: entry.name,
|
|
8036
|
+
handedness,
|
|
8037
|
+
confidence: match.confidence
|
|
8038
|
+
});
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
/** Derive each hand's shape once for the whole frame. */
|
|
8044
|
+
refreshShapes() {
|
|
8045
|
+
for (const handedness of ["left", "right"]) {
|
|
8046
|
+
const input = this.inputSystem.getInput(handedness);
|
|
8047
|
+
const joints = input?.joints ?? null;
|
|
8048
|
+
this.shapes.set(
|
|
8049
|
+
handedness,
|
|
8050
|
+
joints ? computeHandShape(joints, handedness) : null
|
|
8051
|
+
);
|
|
8052
|
+
}
|
|
8053
|
+
}
|
|
8054
|
+
collectEntries(world) {
|
|
8055
|
+
const collected = [];
|
|
8056
|
+
for (const entityId of world.query(XRHandPoseComponent)) {
|
|
8057
|
+
const component = world.getComponent(entityId, XRHandPoseComponent);
|
|
8058
|
+
if (!component?.enabled) continue;
|
|
8059
|
+
for (const entry of component.poses) {
|
|
8060
|
+
if (entry.enabled) collected.push({ entityId, entry });
|
|
8061
|
+
}
|
|
8062
|
+
}
|
|
8063
|
+
return collected;
|
|
8064
|
+
}
|
|
8065
|
+
trackersFor(handedness) {
|
|
8066
|
+
let trackers = this.trackers.get(handedness);
|
|
8067
|
+
if (!trackers) {
|
|
8068
|
+
trackers = /* @__PURE__ */ new Map();
|
|
8069
|
+
this.trackers.set(handedness, trackers);
|
|
8070
|
+
}
|
|
8071
|
+
return trackers;
|
|
8072
|
+
}
|
|
8073
|
+
/**
|
|
8074
|
+
* Deliver to the entity's own Behaviours.
|
|
8075
|
+
*
|
|
8076
|
+
* Scoped to the entity that declared the pose rather than broadcast, so two
|
|
8077
|
+
* objects listening for the same gesture stay independent.
|
|
8078
|
+
*/
|
|
8079
|
+
dispatch(world, entityId, event, detail) {
|
|
8080
|
+
const script = world.getComponent(entityId, ScriptComponent);
|
|
8081
|
+
const instances = script?.scriptInstances;
|
|
8082
|
+
if (!instances) return;
|
|
8083
|
+
for (const instance of instances.values()) {
|
|
8084
|
+
if (!(instance instanceof Behaviour)) continue;
|
|
8085
|
+
try {
|
|
8086
|
+
if (event === "start") instance.onHandPoseStart(detail);
|
|
8087
|
+
else instance.onHandPoseEnd(detail);
|
|
8088
|
+
} catch (error) {
|
|
8089
|
+
console.error("[XRHandPoseSystem] Behaviour hook failed:", error);
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
resetAll() {
|
|
8094
|
+
for (const trackers of this.trackers.values()) {
|
|
8095
|
+
for (const tracker of trackers.values()) tracker.reset();
|
|
8096
|
+
}
|
|
8097
|
+
this.shapes.clear();
|
|
8098
|
+
}
|
|
8099
|
+
/** Live shape for a hand — powers the editor's pose readout. */
|
|
8100
|
+
getHandShape(handedness) {
|
|
8101
|
+
return this.shapes.get(handedness) ?? null;
|
|
8102
|
+
}
|
|
8103
|
+
/** Whether a given entity's pose is currently held. */
|
|
8104
|
+
isPoseActive(entityId, poseId, handedness) {
|
|
8105
|
+
return this.trackers.get(handedness)?.get(`${entityId}:${poseId}`)?.isActive() ?? false;
|
|
8106
|
+
}
|
|
8107
|
+
};
|
|
8108
|
+
__name(_XRHandPoseSystem, "XRHandPoseSystem");
|
|
8109
|
+
var XRHandPoseSystem = _XRHandPoseSystem;
|
|
7214
8110
|
|
|
7215
|
-
export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TransformSyncSystem, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeInstanceOverrides, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
|
|
7216
|
-
//# sourceMappingURL=chunk-
|
|
7217
|
-
//# sourceMappingURL=chunk-
|
|
8111
|
+
export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRHandPoseSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
|
|
8112
|
+
//# sourceMappingURL=chunk-W5TSYV6Q.js.map
|
|
8113
|
+
//# sourceMappingURL=chunk-W5TSYV6Q.js.map
|