@relu-interactives/spatial-ecs 0.3.7 → 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-O6FJA4XW.js → chunk-LMTB4KQ4.js} +929 -42
- 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-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 +1187 -37
- 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 +1144 -37
- 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
package/dist/systems/index.cjs
CHANGED
|
@@ -2099,6 +2099,22 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
|
|
|
2099
2099
|
this.grabbable = options?.grabbable ?? true;
|
|
2100
2100
|
this.pointable = options?.pointable ?? true;
|
|
2101
2101
|
this.grabMode = options?.grabMode ?? "kinematic";
|
|
2102
|
+
this.grabReach = options?.grabReach ?? "both";
|
|
2103
|
+
this.distanceBehavior = options?.distanceBehavior ?? "snap";
|
|
2104
|
+
this.grabGesture = options?.grabGesture ?? "any";
|
|
2105
|
+
this.grabRadius = options?.grabRadius ?? 0.15;
|
|
2106
|
+
this.grabOffsetPosition = options?.grabOffsetPosition ?? {
|
|
2107
|
+
x: 0,
|
|
2108
|
+
y: 0,
|
|
2109
|
+
z: 0
|
|
2110
|
+
};
|
|
2111
|
+
this.grabOffsetRotation = options?.grabOffsetRotation ?? {
|
|
2112
|
+
x: 0,
|
|
2113
|
+
y: 0,
|
|
2114
|
+
z: 0
|
|
2115
|
+
};
|
|
2116
|
+
this.throwOnRelease = options?.throwOnRelease ?? true;
|
|
2117
|
+
this.throwStrength = options?.throwStrength ?? 1;
|
|
2102
2118
|
this.highlightOnHover = options?.highlightOnHover ?? true;
|
|
2103
2119
|
}
|
|
2104
2120
|
};
|
|
@@ -2143,6 +2159,209 @@ var _XRInteractorSideComponent = class _XRInteractorSideComponent extends Compon
|
|
|
2143
2159
|
__name(_XRInteractorSideComponent, "XRInteractorSideComponent");
|
|
2144
2160
|
var XRInteractorSideComponent = _XRInteractorSideComponent;
|
|
2145
2161
|
|
|
2162
|
+
// core/xr/handPosePresets.ts
|
|
2163
|
+
var WIDE = 0.35;
|
|
2164
|
+
var NARROW = 0.25;
|
|
2165
|
+
var EXTENDED = { curl: 0.05, tolerance: WIDE };
|
|
2166
|
+
var CURLED = { curl: 0.95, tolerance: WIDE };
|
|
2167
|
+
var THUMB_TUCKED = { curl: 0.7, tolerance: NARROW + 0.1 };
|
|
2168
|
+
var THUMB_RELAXED = { curl: 0.6, tolerance: 0.4 };
|
|
2169
|
+
function fingers(thumb, index, middle, ring, pinky) {
|
|
2170
|
+
return { thumb, index, middle, ring, pinky };
|
|
2171
|
+
}
|
|
2172
|
+
__name(fingers, "fingers");
|
|
2173
|
+
var THUMB_UP = {
|
|
2174
|
+
axis: "thumb",
|
|
2175
|
+
direction: "up",
|
|
2176
|
+
toleranceDegrees: 45
|
|
2177
|
+
};
|
|
2178
|
+
var THUMB_DOWN = {
|
|
2179
|
+
axis: "thumb",
|
|
2180
|
+
direction: "down",
|
|
2181
|
+
toleranceDegrees: 45
|
|
2182
|
+
};
|
|
2183
|
+
var HAND_POSE_PRESETS = {
|
|
2184
|
+
fist: {
|
|
2185
|
+
id: "fist",
|
|
2186
|
+
name: "Fist",
|
|
2187
|
+
fingers: fingers(THUMB_TUCKED, CURLED, CURLED, CURLED, CURLED)
|
|
2188
|
+
},
|
|
2189
|
+
"open-palm": {
|
|
2190
|
+
id: "open-palm",
|
|
2191
|
+
name: "Open Palm",
|
|
2192
|
+
fingers: fingers(EXTENDED, EXTENDED, EXTENDED, EXTENDED, EXTENDED)
|
|
2193
|
+
},
|
|
2194
|
+
point: {
|
|
2195
|
+
id: "point",
|
|
2196
|
+
name: "Point",
|
|
2197
|
+
fingers: fingers(THUMB_RELAXED, EXTENDED, CURLED, CURLED, CURLED)
|
|
2198
|
+
},
|
|
2199
|
+
"thumbs-up": {
|
|
2200
|
+
id: "thumbs-up",
|
|
2201
|
+
name: "Thumbs Up",
|
|
2202
|
+
fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, CURLED),
|
|
2203
|
+
orientation: THUMB_UP
|
|
2204
|
+
},
|
|
2205
|
+
"thumbs-down": {
|
|
2206
|
+
id: "thumbs-down",
|
|
2207
|
+
name: "Thumbs Down",
|
|
2208
|
+
fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, CURLED),
|
|
2209
|
+
orientation: THUMB_DOWN
|
|
2210
|
+
},
|
|
2211
|
+
peace: {
|
|
2212
|
+
id: "peace",
|
|
2213
|
+
name: "Peace",
|
|
2214
|
+
fingers: fingers(THUMB_RELAXED, EXTENDED, EXTENDED, CURLED, CURLED)
|
|
2215
|
+
},
|
|
2216
|
+
pinch: {
|
|
2217
|
+
id: "pinch",
|
|
2218
|
+
name: "Pinch",
|
|
2219
|
+
// Curl is left loose: what defines a pinch is the tips MEETING, and people
|
|
2220
|
+
// pinch with wildly different amounts of finger bend.
|
|
2221
|
+
fingers: fingers(
|
|
2222
|
+
{ curl: 0.5, tolerance: 0.5 },
|
|
2223
|
+
{ curl: 0.5, tolerance: 0.5 },
|
|
2224
|
+
{ curl: 0.5, tolerance: 0.5 },
|
|
2225
|
+
{ curl: 0.5, tolerance: 0.5 },
|
|
2226
|
+
{ curl: 0.5, tolerance: 0.5 }
|
|
2227
|
+
),
|
|
2228
|
+
pinch: { finger: "index", target: 1, tolerance: 0.25 }
|
|
2229
|
+
},
|
|
2230
|
+
ok: {
|
|
2231
|
+
id: "ok",
|
|
2232
|
+
name: "OK Sign",
|
|
2233
|
+
// Thumb and index meet in a ring while the rest stay up — that contrast with
|
|
2234
|
+
// a plain pinch is the whole gesture, so the other three are constrained.
|
|
2235
|
+
fingers: fingers(
|
|
2236
|
+
{ curl: 0.5, tolerance: 0.4 },
|
|
2237
|
+
{ curl: 0.55, tolerance: 0.35 },
|
|
2238
|
+
EXTENDED,
|
|
2239
|
+
EXTENDED,
|
|
2240
|
+
EXTENDED
|
|
2241
|
+
),
|
|
2242
|
+
pinch: { finger: "index", target: 1, tolerance: 0.3 }
|
|
2243
|
+
},
|
|
2244
|
+
gun: {
|
|
2245
|
+
id: "gun",
|
|
2246
|
+
name: "Gun",
|
|
2247
|
+
fingers: fingers(EXTENDED, EXTENDED, CURLED, CURLED, CURLED)
|
|
2248
|
+
},
|
|
2249
|
+
rock: {
|
|
2250
|
+
id: "rock",
|
|
2251
|
+
name: "Rock Horns",
|
|
2252
|
+
fingers: fingers(THUMB_TUCKED, EXTENDED, CURLED, CURLED, EXTENDED)
|
|
2253
|
+
},
|
|
2254
|
+
shaka: {
|
|
2255
|
+
id: "shaka",
|
|
2256
|
+
name: "Shaka",
|
|
2257
|
+
fingers: fingers(EXTENDED, CURLED, CURLED, CURLED, EXTENDED)
|
|
2258
|
+
},
|
|
2259
|
+
"palm-up": {
|
|
2260
|
+
id: "palm-up",
|
|
2261
|
+
name: "Palm Up",
|
|
2262
|
+
fingers: fingers(EXTENDED, EXTENDED, EXTENDED, EXTENDED, EXTENDED),
|
|
2263
|
+
orientation: {
|
|
2264
|
+
axis: "palm-normal",
|
|
2265
|
+
direction: "up",
|
|
2266
|
+
toleranceDegrees: 40
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
function getHandPosePreset(id) {
|
|
2271
|
+
const preset = HAND_POSE_PRESETS[id];
|
|
2272
|
+
if (!preset) return null;
|
|
2273
|
+
return cloneHandPose(preset);
|
|
2274
|
+
}
|
|
2275
|
+
__name(getHandPosePreset, "getHandPosePreset");
|
|
2276
|
+
function cloneHandPose(definition) {
|
|
2277
|
+
return {
|
|
2278
|
+
...definition,
|
|
2279
|
+
fingers: {
|
|
2280
|
+
thumb: { ...definition.fingers.thumb },
|
|
2281
|
+
index: { ...definition.fingers.index },
|
|
2282
|
+
middle: { ...definition.fingers.middle },
|
|
2283
|
+
ring: { ...definition.fingers.ring },
|
|
2284
|
+
pinky: { ...definition.fingers.pinky }
|
|
2285
|
+
},
|
|
2286
|
+
pinch: definition.pinch ? { ...definition.pinch } : void 0,
|
|
2287
|
+
orientation: definition.orientation ? { ...definition.orientation } : void 0
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
__name(cloneHandPose, "cloneHandPose");
|
|
2291
|
+
function handPoseFromCurls(id, name, curls, options) {
|
|
2292
|
+
const tolerance = options?.tolerance ?? NARROW;
|
|
2293
|
+
const target = /* @__PURE__ */ __name((curl) => ({
|
|
2294
|
+
curl: Math.min(1, Math.max(0, curl)),
|
|
2295
|
+
tolerance
|
|
2296
|
+
}), "target");
|
|
2297
|
+
return {
|
|
2298
|
+
id,
|
|
2299
|
+
name,
|
|
2300
|
+
fingers: {
|
|
2301
|
+
thumb: target(curls.thumb),
|
|
2302
|
+
index: target(curls.index),
|
|
2303
|
+
middle: target(curls.middle),
|
|
2304
|
+
ring: target(curls.ring),
|
|
2305
|
+
pinky: target(curls.pinky)
|
|
2306
|
+
},
|
|
2307
|
+
pinch: options?.pinch,
|
|
2308
|
+
orientation: options?.orientation,
|
|
2309
|
+
hand: options?.hand
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
__name(handPoseFromCurls, "handPoseFromCurls");
|
|
2313
|
+
|
|
2314
|
+
// core/components/XRHandPose.ts
|
|
2315
|
+
var DEFAULT_POSE_CURLS = {
|
|
2316
|
+
thumb: 0,
|
|
2317
|
+
index: 0,
|
|
2318
|
+
middle: 0,
|
|
2319
|
+
ring: 0,
|
|
2320
|
+
pinky: 0
|
|
2321
|
+
};
|
|
2322
|
+
function createHandPoseEntry(partial) {
|
|
2323
|
+
return {
|
|
2324
|
+
id: partial.id,
|
|
2325
|
+
name: partial.name ?? partial.id,
|
|
2326
|
+
preset: partial.preset ?? null,
|
|
2327
|
+
curls: { ...DEFAULT_POSE_CURLS, ...partial.curls },
|
|
2328
|
+
tolerance: partial.tolerance ?? 0.25,
|
|
2329
|
+
hand: partial.hand ?? null,
|
|
2330
|
+
orientation: partial.orientation ?? null,
|
|
2331
|
+
minHoldSeconds: partial.minHoldSeconds ?? 0.15,
|
|
2332
|
+
enabled: partial.enabled ?? true
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
__name(createHandPoseEntry, "createHandPoseEntry");
|
|
2336
|
+
function handPoseDefinitionFor(entry, resolvePreset) {
|
|
2337
|
+
if (entry.preset) {
|
|
2338
|
+
const preset = resolvePreset(entry.preset);
|
|
2339
|
+
if (!preset) return null;
|
|
2340
|
+
return {
|
|
2341
|
+
...preset,
|
|
2342
|
+
id: entry.id,
|
|
2343
|
+
name: entry.name,
|
|
2344
|
+
hand: entry.hand ?? preset.hand,
|
|
2345
|
+
orientation: entry.orientation ?? preset.orientation
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
return handPoseFromCurls(entry.id, entry.name, entry.curls, {
|
|
2349
|
+
tolerance: entry.tolerance,
|
|
2350
|
+
orientation: entry.orientation ?? void 0,
|
|
2351
|
+
hand: entry.hand ?? void 0
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
__name(handPoseDefinitionFor, "handPoseDefinitionFor");
|
|
2355
|
+
var _XRHandPoseComponent = class _XRHandPoseComponent extends Component {
|
|
2356
|
+
constructor(options) {
|
|
2357
|
+
super();
|
|
2358
|
+
this.enabled = options?.enabled ?? true;
|
|
2359
|
+
this.poses = (options?.poses ?? []).map((pose) => createHandPoseEntry(pose));
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
__name(_XRHandPoseComponent, "XRHandPoseComponent");
|
|
2363
|
+
var XRHandPoseComponent = _XRHandPoseComponent;
|
|
2364
|
+
|
|
2146
2365
|
// core/utils/dracoDecoderPath.ts
|
|
2147
2366
|
var LOCAL_DRACO_DECODER_RELATIVE_PATH = "./assets/draco/";
|
|
2148
2367
|
var DRACO_DECODER_CDN_PATH = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/";
|
|
@@ -4941,6 +5160,10 @@ function buildComponentValueMaps(snapshot) {
|
|
|
4941
5160
|
snapshot.components,
|
|
4942
5161
|
"XRInteractorComponent"
|
|
4943
5162
|
),
|
|
5163
|
+
xrHandPoseValues: readByComponent(
|
|
5164
|
+
snapshot.components,
|
|
5165
|
+
"XRHandPoseComponent"
|
|
5166
|
+
),
|
|
4944
5167
|
xrInteractorSideValues: readByComponent(
|
|
4945
5168
|
snapshot.components,
|
|
4946
5169
|
"XRInteractorSideComponent"
|
|
@@ -5126,11 +5349,65 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5126
5349
|
if (typeof sourceInteractable.grabMode === "string") {
|
|
5127
5350
|
interactable.grabMode = sourceInteractable.grabMode;
|
|
5128
5351
|
}
|
|
5352
|
+
if (typeof sourceInteractable.grabReach === "string") {
|
|
5353
|
+
interactable.grabReach = sourceInteractable.grabReach;
|
|
5354
|
+
}
|
|
5355
|
+
if (typeof sourceInteractable.distanceBehavior === "string") {
|
|
5356
|
+
interactable.distanceBehavior = sourceInteractable.distanceBehavior;
|
|
5357
|
+
}
|
|
5358
|
+
if (typeof sourceInteractable.grabGesture === "string") {
|
|
5359
|
+
interactable.grabGesture = sourceInteractable.grabGesture;
|
|
5360
|
+
}
|
|
5361
|
+
if (typeof sourceInteractable.grabRadius === "number") {
|
|
5362
|
+
interactable.grabRadius = sourceInteractable.grabRadius;
|
|
5363
|
+
}
|
|
5364
|
+
if (typeof sourceInteractable.throwOnRelease === "boolean") {
|
|
5365
|
+
interactable.throwOnRelease = sourceInteractable.throwOnRelease;
|
|
5366
|
+
}
|
|
5367
|
+
if (typeof sourceInteractable.throwStrength === "number") {
|
|
5368
|
+
interactable.throwStrength = sourceInteractable.throwStrength;
|
|
5369
|
+
}
|
|
5370
|
+
const grabPos = asRecord3(sourceInteractable.grabOffsetPosition);
|
|
5371
|
+
if (grabPos) {
|
|
5372
|
+
interactable.grabOffsetPosition = {
|
|
5373
|
+
x: typeof grabPos.x === "number" ? grabPos.x : 0,
|
|
5374
|
+
y: typeof grabPos.y === "number" ? grabPos.y : 0,
|
|
5375
|
+
z: typeof grabPos.z === "number" ? grabPos.z : 0
|
|
5376
|
+
};
|
|
5377
|
+
}
|
|
5378
|
+
const grabRot = asRecord3(sourceInteractable.grabOffsetRotation);
|
|
5379
|
+
if (grabRot) {
|
|
5380
|
+
interactable.grabOffsetRotation = {
|
|
5381
|
+
x: typeof grabRot.x === "number" ? grabRot.x : 0,
|
|
5382
|
+
y: typeof grabRot.y === "number" ? grabRot.y : 0,
|
|
5383
|
+
z: typeof grabRot.z === "number" ? grabRot.z : 0
|
|
5384
|
+
};
|
|
5385
|
+
}
|
|
5129
5386
|
if (typeof sourceInteractable.highlightOnHover === "boolean") {
|
|
5130
5387
|
interactable.highlightOnHover = sourceInteractable.highlightOnHover;
|
|
5131
5388
|
}
|
|
5132
5389
|
}
|
|
5133
5390
|
}
|
|
5391
|
+
{
|
|
5392
|
+
const sourceHandPose = asRecord3(
|
|
5393
|
+
values.xrHandPoseValues.get(sourceEntityId)
|
|
5394
|
+
);
|
|
5395
|
+
const handPose = world.getComponent(targetEntityId, XRHandPoseComponent);
|
|
5396
|
+
if (sourceHandPose && handPose) {
|
|
5397
|
+
if (typeof sourceHandPose.enabled === "boolean") {
|
|
5398
|
+
handPose.enabled = sourceHandPose.enabled;
|
|
5399
|
+
}
|
|
5400
|
+
if (Array.isArray(sourceHandPose.poses)) {
|
|
5401
|
+
handPose.poses = sourceHandPose.poses.map((entry) => asRecord3(entry)).filter(
|
|
5402
|
+
(entry) => entry !== null && typeof entry.id === "string"
|
|
5403
|
+
).map(
|
|
5404
|
+
(entry) => createHandPoseEntry(
|
|
5405
|
+
entry
|
|
5406
|
+
)
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5134
5411
|
{
|
|
5135
5412
|
const sourceInteractor = asRecord3(
|
|
5136
5413
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -6517,6 +6794,17 @@ var _Behaviour = class _Behaviour {
|
|
|
6517
6794
|
*
|
|
6518
6795
|
* @param input - The source doing the grabbing.
|
|
6519
6796
|
*/
|
|
6797
|
+
/**
|
|
6798
|
+
* A hand pose declared on this entity started being held.
|
|
6799
|
+
*
|
|
6800
|
+
* Fires once the pose has been sustained for its hold time, not the instant
|
|
6801
|
+
* the fingers pass through it.
|
|
6802
|
+
*/
|
|
6803
|
+
onHandPoseStart(_event) {
|
|
6804
|
+
}
|
|
6805
|
+
/** A held hand pose ended, or its hand stopped being tracked. */
|
|
6806
|
+
onHandPoseEnd(_event) {
|
|
6807
|
+
}
|
|
6520
6808
|
onGrab(input) {
|
|
6521
6809
|
}
|
|
6522
6810
|
/** Called when this entity is released. Optional XR hook. */
|
|
@@ -7659,13 +7947,65 @@ function blankState(source) {
|
|
|
7659
7947
|
squeezeEnd: false,
|
|
7660
7948
|
axes: [],
|
|
7661
7949
|
hasAxes: false,
|
|
7662
|
-
buttons: []
|
|
7950
|
+
buttons: [],
|
|
7951
|
+
joints: null
|
|
7663
7952
|
};
|
|
7664
7953
|
}
|
|
7665
7954
|
__name(blankState, "blankState");
|
|
7955
|
+
var JOINT_MATRIX_BUFFER = new Float32Array(25 * 16);
|
|
7956
|
+
function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
|
|
7957
|
+
const names = [];
|
|
7958
|
+
const spaces = [];
|
|
7959
|
+
for (const [name, space] of hand) {
|
|
7960
|
+
names.push(name);
|
|
7961
|
+
spaces.push(space);
|
|
7962
|
+
}
|
|
7963
|
+
if (spaces.length === 0) return null;
|
|
7964
|
+
const target = existing ?? {};
|
|
7965
|
+
const matrix = new THREE14__namespace.Matrix4();
|
|
7966
|
+
const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
|
|
7967
|
+
const fillPoses = frame.fillPoses;
|
|
7968
|
+
if (typeof fillPoses === "function") {
|
|
7969
|
+
let filled = false;
|
|
7970
|
+
try {
|
|
7971
|
+
filled = fillPoses.call(frame, spaces, referenceSpace, buffer);
|
|
7972
|
+
} catch {
|
|
7973
|
+
filled = false;
|
|
7974
|
+
}
|
|
7975
|
+
if (filled) {
|
|
7976
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
7977
|
+
matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
|
|
7978
|
+
const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
|
|
7979
|
+
vector.setFromMatrixPosition(matrix);
|
|
7980
|
+
target[names[i]] = vector;
|
|
7981
|
+
}
|
|
7982
|
+
return target;
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
let anyTracked = false;
|
|
7986
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
7987
|
+
const pose = frame.getJointPose?.(
|
|
7988
|
+
spaces[i],
|
|
7989
|
+
referenceSpace
|
|
7990
|
+
);
|
|
7991
|
+
if (!pose) {
|
|
7992
|
+
target[names[i]] = void 0;
|
|
7993
|
+
continue;
|
|
7994
|
+
}
|
|
7995
|
+
matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
7996
|
+
const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
|
|
7997
|
+
vector.setFromMatrixPosition(matrix);
|
|
7998
|
+
target[names[i]] = vector;
|
|
7999
|
+
anyTracked = true;
|
|
8000
|
+
}
|
|
8001
|
+
return anyTracked ? target : null;
|
|
8002
|
+
}
|
|
8003
|
+
__name(readHandJoints, "readHandJoints");
|
|
7666
8004
|
var _XRInputSystem = class _XRInputSystem {
|
|
7667
8005
|
constructor() {
|
|
7668
8006
|
this.records = /* @__PURE__ */ new Map();
|
|
8007
|
+
/** Reference-space → world matrix, rebuilt each frame from the XR rig. */
|
|
8008
|
+
this.referenceToWorld = new THREE14__namespace.Matrix4();
|
|
7669
8009
|
this.session = null;
|
|
7670
8010
|
this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
|
|
7671
8011
|
this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
|
|
@@ -7745,6 +8085,14 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7745
8085
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
7746
8086
|
const frame = renderer.xr.getFrame?.();
|
|
7747
8087
|
const live = /* @__PURE__ */ new Set();
|
|
8088
|
+
const rigMatrix = this.referenceToWorld;
|
|
8089
|
+
const rig = world.getCamera().parent;
|
|
8090
|
+
if (rig) {
|
|
8091
|
+
rig.updateMatrixWorld();
|
|
8092
|
+
rigMatrix.copy(rig.matrixWorld);
|
|
8093
|
+
} else {
|
|
8094
|
+
rigMatrix.identity();
|
|
8095
|
+
}
|
|
7748
8096
|
for (const source of session.inputSources) {
|
|
7749
8097
|
live.add(source);
|
|
7750
8098
|
const record = this.ensureRecord(source);
|
|
@@ -7755,7 +8103,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7755
8103
|
if (frame && referenceSpace && source.targetRaySpace) {
|
|
7756
8104
|
const pose = frame.getPose(source.targetRaySpace, referenceSpace);
|
|
7757
8105
|
if (pose) {
|
|
7758
|
-
const m = new THREE14__namespace.Matrix4().fromArray(pose.transform.matrix);
|
|
8106
|
+
const m = new THREE14__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
7759
8107
|
state.rayOrigin.setFromMatrixPosition(m);
|
|
7760
8108
|
state.rayDirection.set(0, 0, -1).applyQuaternion(
|
|
7761
8109
|
new THREE14__namespace.Quaternion().setFromRotationMatrix(m)
|
|
@@ -7765,7 +8113,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7765
8113
|
if (frame && referenceSpace && source.gripSpace) {
|
|
7766
8114
|
const gripPose = frame.getPose(source.gripSpace, referenceSpace);
|
|
7767
8115
|
if (gripPose) {
|
|
7768
|
-
const m = new THREE14__namespace.Matrix4().fromArray(gripPose.transform.matrix);
|
|
8116
|
+
const m = new THREE14__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
|
|
7769
8117
|
state.gripPosition ?? (state.gripPosition = new THREE14__namespace.Vector3());
|
|
7770
8118
|
state.gripQuaternion ?? (state.gripQuaternion = new THREE14__namespace.Quaternion());
|
|
7771
8119
|
state.gripPosition.setFromMatrixPosition(m);
|
|
@@ -7775,6 +8123,17 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7775
8123
|
state.gripPosition = null;
|
|
7776
8124
|
state.gripQuaternion = null;
|
|
7777
8125
|
}
|
|
8126
|
+
if (frame && referenceSpace && source.hand) {
|
|
8127
|
+
state.joints = readHandJoints(
|
|
8128
|
+
frame,
|
|
8129
|
+
source.hand,
|
|
8130
|
+
referenceSpace,
|
|
8131
|
+
rigMatrix,
|
|
8132
|
+
state.joints
|
|
8133
|
+
);
|
|
8134
|
+
} else {
|
|
8135
|
+
state.joints = null;
|
|
8136
|
+
}
|
|
7778
8137
|
const gamepad = source.gamepad;
|
|
7779
8138
|
if (gamepad) {
|
|
7780
8139
|
state.axes = Array.from(gamepad.axes);
|
|
@@ -7839,20 +8198,84 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7839
8198
|
};
|
|
7840
8199
|
__name(_XRInputSystem, "XRInputSystem");
|
|
7841
8200
|
var XRInputSystem = _XRInputSystem;
|
|
8201
|
+
var VELOCITY_SAMPLES = 5;
|
|
8202
|
+
var MAX_SAMPLE_AGE_SECONDS = 0.2;
|
|
8203
|
+
var _ThrowVelocityTracker = class _ThrowVelocityTracker {
|
|
8204
|
+
constructor() {
|
|
8205
|
+
this.samples = [];
|
|
8206
|
+
}
|
|
8207
|
+
/** Record where the hand is now. Call once per frame while held. */
|
|
8208
|
+
record(position, time) {
|
|
8209
|
+
this.samples.push({ position: position.clone(), time });
|
|
8210
|
+
if (this.samples.length > VELOCITY_SAMPLES) {
|
|
8211
|
+
this.samples.shift();
|
|
8212
|
+
}
|
|
8213
|
+
}
|
|
8214
|
+
/**
|
|
8215
|
+
* Velocity to release with, in metres/second.
|
|
8216
|
+
*
|
|
8217
|
+
* Uses the FASTEST segment inside the window rather than the most recent or
|
|
8218
|
+
* the window average. Users decelerate as they open their hand, so the last
|
|
8219
|
+
* segment understates the throw and the average washes it out; peak speed is
|
|
8220
|
+
* what matches the motion the user actually felt they made.
|
|
8221
|
+
*/
|
|
8222
|
+
computeVelocity(now) {
|
|
8223
|
+
const fresh = this.samples.filter(
|
|
8224
|
+
(sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
|
|
8225
|
+
);
|
|
8226
|
+
if (fresh.length < 2) return new THREE14__namespace.Vector3();
|
|
8227
|
+
let best = new THREE14__namespace.Vector3();
|
|
8228
|
+
let bestSpeed = 0;
|
|
8229
|
+
for (let i = 1; i < fresh.length; i += 1) {
|
|
8230
|
+
const previous = fresh[i - 1];
|
|
8231
|
+
const current = fresh[i];
|
|
8232
|
+
const dt = current.time - previous.time;
|
|
8233
|
+
if (dt <= 1e-5) continue;
|
|
8234
|
+
const velocity = new THREE14__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
|
|
8235
|
+
const speed = velocity.length();
|
|
8236
|
+
if (speed > bestSpeed) {
|
|
8237
|
+
bestSpeed = speed;
|
|
8238
|
+
best = velocity;
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
return best;
|
|
8242
|
+
}
|
|
8243
|
+
clear() {
|
|
8244
|
+
this.samples.length = 0;
|
|
8245
|
+
}
|
|
8246
|
+
};
|
|
8247
|
+
__name(_ThrowVelocityTracker, "ThrowVelocityTracker");
|
|
8248
|
+
var ThrowVelocityTracker = _ThrowVelocityTracker;
|
|
8249
|
+
function angularVelocityFrom(previous, current, deltaTime) {
|
|
8250
|
+
if (deltaTime <= 1e-5) return new THREE14__namespace.Vector3();
|
|
8251
|
+
const delta = current.clone().multiply(previous.clone().invert());
|
|
8252
|
+
if (delta.w < 0) {
|
|
8253
|
+
delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
|
|
8254
|
+
}
|
|
8255
|
+
const angle = 2 * Math.acos(THREE14__namespace.MathUtils.clamp(delta.w, -1, 1));
|
|
8256
|
+
const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
|
|
8257
|
+
if (sinHalf < 1e-6 || angle < 1e-6) return new THREE14__namespace.Vector3();
|
|
8258
|
+
return new THREE14__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
|
|
8259
|
+
}
|
|
8260
|
+
__name(angularVelocityFrom, "angularVelocityFrom");
|
|
8261
|
+
|
|
8262
|
+
// core/systems/XRInteractionSystem.ts
|
|
7842
8263
|
var RAYLESS_HOLD_DISTANCE = 0.5;
|
|
7843
8264
|
var _XRInteractionSystem = class _XRInteractionSystem {
|
|
7844
8265
|
constructor(inputSystem) {
|
|
7845
8266
|
this.raycaster = new THREE14__namespace.Raycaster();
|
|
7846
|
-
/** Which entity each source is holding. Keyed by source state identity. */
|
|
7847
8267
|
this.grabs = /* @__PURE__ */ new Map();
|
|
7848
8268
|
this.hoveredEntityId = null;
|
|
8269
|
+
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
8270
|
+
this.elapsed = 0;
|
|
7849
8271
|
this.inputSystem = inputSystem;
|
|
7850
8272
|
}
|
|
7851
|
-
update(world) {
|
|
8273
|
+
update(world, deltaTime) {
|
|
7852
8274
|
if (!world.isXrPresenting()) {
|
|
7853
8275
|
this.releaseAll(world);
|
|
7854
8276
|
return;
|
|
7855
8277
|
}
|
|
8278
|
+
this.elapsed += Math.max(0, deltaTime);
|
|
7856
8279
|
const inputs = this.inputSystem.getInputs();
|
|
7857
8280
|
const interactables = this.collectInteractables(world);
|
|
7858
8281
|
this.hoveredEntityId = null;
|
|
@@ -7863,29 +8286,100 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7863
8286
|
}
|
|
7864
8287
|
const held = this.grabs.get(input);
|
|
7865
8288
|
if (held) {
|
|
7866
|
-
this.moveHeld(world, input, held);
|
|
7867
|
-
if (
|
|
8289
|
+
this.moveHeld(world, input, held, deltaTime);
|
|
8290
|
+
if (this.wantsRelease(world, input, held)) {
|
|
8291
|
+
this.release(world, input);
|
|
8292
|
+
}
|
|
7868
8293
|
continue;
|
|
7869
8294
|
}
|
|
7870
|
-
const
|
|
7871
|
-
if (
|
|
8295
|
+
const candidate = this.findGrabCandidate(world, input, interactables);
|
|
8296
|
+
if (candidate === null) continue;
|
|
7872
8297
|
if (this.hoveredEntityId === null) {
|
|
7873
|
-
this.hoveredEntityId =
|
|
8298
|
+
this.hoveredEntityId = candidate.entityId;
|
|
7874
8299
|
}
|
|
7875
8300
|
const interactable = world.getComponent(
|
|
7876
|
-
|
|
8301
|
+
candidate.entityId,
|
|
8302
|
+
XRInteractableComponent
|
|
8303
|
+
);
|
|
8304
|
+
if (interactable?.grabbable && this.gestureMatches(
|
|
8305
|
+
input,
|
|
8306
|
+
interactable,
|
|
8307
|
+
/* starting */
|
|
8308
|
+
true
|
|
8309
|
+
)) {
|
|
8310
|
+
this.grab(world, input, candidate.entityId, candidate.viaDistance);
|
|
8311
|
+
}
|
|
8312
|
+
}
|
|
8313
|
+
}
|
|
8314
|
+
/** Whether the grab gesture for this object is currently starting. */
|
|
8315
|
+
gestureMatches(input, interactable, starting) {
|
|
8316
|
+
const pinch = starting ? input.selectStart : input.select;
|
|
8317
|
+
const full = starting ? input.squeezeStart : input.squeeze;
|
|
8318
|
+
switch (interactable.grabGesture) {
|
|
8319
|
+
case "pinch":
|
|
8320
|
+
return pinch;
|
|
8321
|
+
case "full":
|
|
8322
|
+
return full;
|
|
8323
|
+
default:
|
|
8324
|
+
return pinch || full;
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
/** True once the gesture that started the grab has ended. */
|
|
8328
|
+
wantsRelease(world, input, held) {
|
|
8329
|
+
const interactable = world.getComponent(
|
|
8330
|
+
held.entityId,
|
|
8331
|
+
XRInteractableComponent
|
|
8332
|
+
);
|
|
8333
|
+
if (!interactable) return true;
|
|
8334
|
+
switch (interactable.grabGesture) {
|
|
8335
|
+
case "pinch":
|
|
8336
|
+
return input.selectEnd || !input.select;
|
|
8337
|
+
case "full":
|
|
8338
|
+
return input.squeezeEnd || !input.squeeze;
|
|
8339
|
+
default:
|
|
8340
|
+
return !input.select && !input.squeeze;
|
|
8341
|
+
}
|
|
8342
|
+
}
|
|
8343
|
+
/**
|
|
8344
|
+
* Find what this hand would grab: a physically-reachable object first, then
|
|
8345
|
+
* one along the pointer ray.
|
|
8346
|
+
*
|
|
8347
|
+
* Proximity wins over the ray so that reaching for something you are touching
|
|
8348
|
+
* never picks up a distant object behind it, which feels broken.
|
|
8349
|
+
*/
|
|
8350
|
+
findGrabCandidate(world, input, interactables) {
|
|
8351
|
+
const hand = this.handPosition(input);
|
|
8352
|
+
let nearestId = null;
|
|
8353
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
8354
|
+
for (const entry of interactables) {
|
|
8355
|
+
const interactable2 = world.getComponent(
|
|
8356
|
+
entry.entityId,
|
|
7877
8357
|
XRInteractableComponent
|
|
7878
8358
|
);
|
|
7879
|
-
if (
|
|
7880
|
-
|
|
8359
|
+
if (!interactable2) continue;
|
|
8360
|
+
if (interactable2.grabReach === "distance") continue;
|
|
8361
|
+
const position = new THREE14__namespace.Vector3();
|
|
8362
|
+
entry.object.getWorldPosition(position);
|
|
8363
|
+
const distance = position.distanceTo(hand);
|
|
8364
|
+
if (distance <= interactable2.grabRadius && distance < nearestDistance) {
|
|
8365
|
+
nearestDistance = distance;
|
|
8366
|
+
nearestId = entry.entityId;
|
|
7881
8367
|
}
|
|
7882
8368
|
}
|
|
8369
|
+
if (nearestId !== null) {
|
|
8370
|
+
return { entityId: nearestId, viaDistance: false };
|
|
8371
|
+
}
|
|
8372
|
+
const hitId = this.raycast(input, interactables);
|
|
8373
|
+
if (hitId === null) return null;
|
|
8374
|
+
const interactable = world.getComponent(hitId, XRInteractableComponent);
|
|
8375
|
+
const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
|
|
8376
|
+
return { entityId: hitId, viaDistance: viaDistanceAllowed };
|
|
7883
8377
|
}
|
|
7884
8378
|
collectInteractables(world) {
|
|
7885
8379
|
const out = [];
|
|
7886
8380
|
for (const entityId of world.query(XRInteractableComponent, Object3DRef)) {
|
|
7887
8381
|
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
7888
|
-
if (!interactable?.enabled
|
|
8382
|
+
if (!interactable?.enabled) continue;
|
|
7889
8383
|
if (!world.isEntityVisible(entityId)) continue;
|
|
7890
8384
|
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
7891
8385
|
if (object) out.push({ entityId, object });
|
|
@@ -7896,6 +8390,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7896
8390
|
raycast(input, interactables) {
|
|
7897
8391
|
if (interactables.length === 0) return null;
|
|
7898
8392
|
this.raycaster.set(input.rayOrigin, input.rayDirection);
|
|
8393
|
+
this.raycaster.far = Number.POSITIVE_INFINITY;
|
|
7899
8394
|
let nearestId = null;
|
|
7900
8395
|
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
7901
8396
|
for (const entry of interactables) {
|
|
@@ -7908,43 +8403,120 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7908
8403
|
}
|
|
7909
8404
|
return nearestId;
|
|
7910
8405
|
}
|
|
7911
|
-
grab(world, input, entityId) {
|
|
8406
|
+
grab(world, input, entityId, viaDistance) {
|
|
7912
8407
|
const transform = world.getComponent(entityId, TransformComponent);
|
|
7913
|
-
|
|
8408
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
8409
|
+
if (!transform || !interactable) return;
|
|
7914
8410
|
const hand = this.handPosition(input);
|
|
7915
|
-
const
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
)
|
|
8411
|
+
const handQuaternion = this.handQuaternion(input);
|
|
8412
|
+
const remote = viaDistance && interactable.distanceBehavior === "remote";
|
|
8413
|
+
let offset;
|
|
8414
|
+
let remoteDistance = 0;
|
|
8415
|
+
if (remote) {
|
|
8416
|
+
const position = new THREE14__namespace.Vector3(
|
|
8417
|
+
transform.position.x,
|
|
8418
|
+
transform.position.y,
|
|
8419
|
+
transform.position.z
|
|
8420
|
+
);
|
|
8421
|
+
remoteDistance = position.distanceTo(input.rayOrigin);
|
|
8422
|
+
offset = new THREE14__namespace.Vector3();
|
|
8423
|
+
} else if (viaDistance) {
|
|
8424
|
+
offset = new THREE14__namespace.Vector3(
|
|
8425
|
+
interactable.grabOffsetPosition.x,
|
|
8426
|
+
interactable.grabOffsetPosition.y,
|
|
8427
|
+
interactable.grabOffsetPosition.z
|
|
8428
|
+
);
|
|
8429
|
+
} else {
|
|
8430
|
+
offset = new THREE14__namespace.Vector3(
|
|
8431
|
+
transform.position.x - hand.x,
|
|
8432
|
+
transform.position.y - hand.y,
|
|
8433
|
+
transform.position.z - hand.z
|
|
8434
|
+
);
|
|
8435
|
+
}
|
|
7920
8436
|
const physics = world.getComponent(entityId, PhysicsComponent);
|
|
7921
8437
|
let restoreBodyType = null;
|
|
7922
|
-
|
|
7923
|
-
if (physics && interactable?.grabMode === "kinematic") {
|
|
8438
|
+
if (physics) {
|
|
7924
8439
|
restoreBodyType = physics.values.rigidbody.type;
|
|
7925
8440
|
physics.values.rigidbody.type = "kinematicPosition";
|
|
7926
8441
|
}
|
|
7927
|
-
|
|
8442
|
+
const tracker = new ThrowVelocityTracker();
|
|
8443
|
+
tracker.record(hand, this.elapsed);
|
|
8444
|
+
this.grabs.set(input, {
|
|
8445
|
+
entityId,
|
|
8446
|
+
offset,
|
|
8447
|
+
rotationOffset: new THREE14__namespace.Quaternion().setFromEuler(
|
|
8448
|
+
new THREE14__namespace.Euler(
|
|
8449
|
+
interactable.grabOffsetRotation.x,
|
|
8450
|
+
interactable.grabOffsetRotation.y,
|
|
8451
|
+
interactable.grabOffsetRotation.z
|
|
8452
|
+
)
|
|
8453
|
+
),
|
|
8454
|
+
remoteDistance,
|
|
8455
|
+
remote,
|
|
8456
|
+
restoreBodyType,
|
|
8457
|
+
tracker,
|
|
8458
|
+
lastHandQuaternion: handQuaternion.clone(),
|
|
8459
|
+
angularVelocity: new THREE14__namespace.Vector3()
|
|
8460
|
+
});
|
|
7928
8461
|
this.dispatch(world, entityId, (b) => b.onGrab(input));
|
|
7929
8462
|
}
|
|
7930
|
-
moveHeld(world, input, held) {
|
|
8463
|
+
moveHeld(world, input, held, deltaTime) {
|
|
7931
8464
|
const transform = world.getComponent(held.entityId, TransformComponent);
|
|
7932
8465
|
if (!transform) return;
|
|
7933
8466
|
const hand = this.handPosition(input);
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
8467
|
+
if (held.remote) {
|
|
8468
|
+
const target = new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
|
|
8469
|
+
transform.position.x = target.x;
|
|
8470
|
+
transform.position.y = target.y;
|
|
8471
|
+
transform.position.z = target.z;
|
|
8472
|
+
} else {
|
|
8473
|
+
transform.position.x = hand.x + held.offset.x;
|
|
8474
|
+
transform.position.y = hand.y + held.offset.y;
|
|
8475
|
+
transform.position.z = hand.z + held.offset.z;
|
|
8476
|
+
}
|
|
8477
|
+
held.tracker.record(
|
|
8478
|
+
held.remote ? new THREE14__namespace.Vector3(
|
|
8479
|
+
transform.position.x,
|
|
8480
|
+
transform.position.y,
|
|
8481
|
+
transform.position.z
|
|
8482
|
+
) : hand,
|
|
8483
|
+
this.elapsed
|
|
8484
|
+
);
|
|
8485
|
+
const handQuaternion = this.handQuaternion(input);
|
|
8486
|
+
held.angularVelocity = angularVelocityFrom(
|
|
8487
|
+
held.lastHandQuaternion,
|
|
8488
|
+
handQuaternion,
|
|
8489
|
+
deltaTime
|
|
8490
|
+
);
|
|
8491
|
+
held.lastHandQuaternion = handQuaternion;
|
|
7937
8492
|
}
|
|
7938
8493
|
release(world, input) {
|
|
7939
8494
|
const held = this.grabs.get(input);
|
|
7940
8495
|
if (!held) return;
|
|
7941
8496
|
this.grabs.delete(input);
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8497
|
+
const interactable = world.getComponent(
|
|
8498
|
+
held.entityId,
|
|
8499
|
+
XRInteractableComponent
|
|
8500
|
+
);
|
|
8501
|
+
const physics = world.getComponent(held.entityId, PhysicsComponent);
|
|
8502
|
+
if (held.restoreBodyType !== null && physics) {
|
|
8503
|
+
physics.values.rigidbody.type = held.restoreBodyType;
|
|
8504
|
+
const body = physics.rigidBody;
|
|
8505
|
+
const dynamic = held.restoreBodyType === "dynamic";
|
|
8506
|
+
if (body && dynamic && interactable?.throwOnRelease) {
|
|
8507
|
+
const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
|
|
8508
|
+
try {
|
|
8509
|
+
body.setLinvel(
|
|
8510
|
+
{ x: velocity.x, y: velocity.y, z: velocity.z },
|
|
8511
|
+
true
|
|
8512
|
+
);
|
|
8513
|
+
const spin = held.angularVelocity;
|
|
8514
|
+
body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
|
|
8515
|
+
} catch {
|
|
8516
|
+
}
|
|
7946
8517
|
}
|
|
7947
8518
|
}
|
|
8519
|
+
held.tracker.clear();
|
|
7948
8520
|
this.dispatch(world, held.entityId, (b) => b.onRelease(input));
|
|
7949
8521
|
}
|
|
7950
8522
|
releaseAll(world) {
|
|
@@ -7953,14 +8525,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7953
8525
|
}
|
|
7954
8526
|
}
|
|
7955
8527
|
/**
|
|
7956
|
-
* Where the "hand" is
|
|
7957
|
-
*
|
|
7958
|
-
*
|
|
8528
|
+
* Where the "hand" is. Controllers and tracked hands report a grip pose;
|
|
8529
|
+
* gaze and transient-pointer sources have none, so we hold the object a fixed
|
|
8530
|
+
* distance along the ray instead — which is what makes Vision Pro work.
|
|
7959
8531
|
*/
|
|
7960
8532
|
handPosition(input) {
|
|
7961
8533
|
if (input.gripPosition) return input.gripPosition;
|
|
7962
8534
|
return new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
|
|
7963
8535
|
}
|
|
8536
|
+
/**
|
|
8537
|
+
* Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
|
|
8538
|
+
* so we derive one from the aim direction — enough for spin on release.
|
|
8539
|
+
*/
|
|
8540
|
+
handQuaternion(input) {
|
|
8541
|
+
if (input.gripQuaternion) return input.gripQuaternion.clone();
|
|
8542
|
+
return new THREE14__namespace.Quaternion().setFromUnitVectors(
|
|
8543
|
+
new THREE14__namespace.Vector3(0, 0, -1),
|
|
8544
|
+
input.rayDirection.clone().normalize()
|
|
8545
|
+
);
|
|
8546
|
+
}
|
|
7964
8547
|
dispatch(world, entityId, visit) {
|
|
7965
8548
|
const script = world.getComponent(entityId, ScriptComponent);
|
|
7966
8549
|
const instances = script?.scriptInstances;
|
|
@@ -8517,6 +9100,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8517
9100
|
this.handStyleSignature = "";
|
|
8518
9101
|
this.handMaterial = null;
|
|
8519
9102
|
this.interactionSystem = options?.interactionSystem ?? null;
|
|
9103
|
+
this.inputSystem = options?.inputSystem ?? null;
|
|
8520
9104
|
this.group.name = "XRControllerVisuals";
|
|
8521
9105
|
this.group.userData.ecsPreviewHidden = true;
|
|
8522
9106
|
}
|
|
@@ -8533,8 +9117,11 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8533
9117
|
for (const { index, side } of SLOTS) {
|
|
8534
9118
|
const visuals = this.slots.get(index);
|
|
8535
9119
|
if (!visuals) continue;
|
|
8536
|
-
|
|
8537
|
-
|
|
9120
|
+
const liveKind = this.liveKindFor(side);
|
|
9121
|
+
const hasController = liveKind === null || liveKind === "controller";
|
|
9122
|
+
const hasHand = liveKind === null || liveKind === "hand";
|
|
9123
|
+
visuals.controllerModel.visible = showControllers && hasController;
|
|
9124
|
+
visuals.handModel.visible = showHands && hasHand;
|
|
8538
9125
|
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
8539
9126
|
if (showHands && this.handMaterial) {
|
|
8540
9127
|
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
@@ -8542,6 +9129,20 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8542
9129
|
this.syncCustomContent(world, visuals, side);
|
|
8543
9130
|
}
|
|
8544
9131
|
}
|
|
9132
|
+
/**
|
|
9133
|
+
* What kind of source is currently driving this hand, or null when we cannot
|
|
9134
|
+
* tell (no input system wired) — in which case callers fall back to showing
|
|
9135
|
+
* everything rather than hiding the user's hands entirely.
|
|
9136
|
+
*/
|
|
9137
|
+
liveKindFor(side) {
|
|
9138
|
+
if (!this.inputSystem) return null;
|
|
9139
|
+
const inputs = this.inputSystem.getInputs();
|
|
9140
|
+
if (inputs.length === 0) return null;
|
|
9141
|
+
for (const input of inputs) {
|
|
9142
|
+
if (input.handedness === side && input.connected) return input.kind;
|
|
9143
|
+
}
|
|
9144
|
+
return "none";
|
|
9145
|
+
}
|
|
8545
9146
|
/** The scene's interactor config, if one was authored. */
|
|
8546
9147
|
resolveInteractor(world) {
|
|
8547
9148
|
const entityId = world.query(XRInteractorComponent)[0];
|
|
@@ -8801,6 +9402,511 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
8801
9402
|
};
|
|
8802
9403
|
__name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
|
|
8803
9404
|
var XRTeleportVisualSystem = _XRTeleportVisualSystem;
|
|
9405
|
+
var XR_FINGERS = [
|
|
9406
|
+
"thumb",
|
|
9407
|
+
"index",
|
|
9408
|
+
"middle",
|
|
9409
|
+
"ring",
|
|
9410
|
+
"pinky"
|
|
9411
|
+
];
|
|
9412
|
+
var PINCH_FINGERS = [
|
|
9413
|
+
"index",
|
|
9414
|
+
"middle",
|
|
9415
|
+
"ring",
|
|
9416
|
+
"pinky"
|
|
9417
|
+
];
|
|
9418
|
+
var FINGER_CHAINS = {
|
|
9419
|
+
thumb: [
|
|
9420
|
+
"thumb-metacarpal",
|
|
9421
|
+
"thumb-phalanx-proximal",
|
|
9422
|
+
"thumb-phalanx-distal",
|
|
9423
|
+
"thumb-tip"
|
|
9424
|
+
],
|
|
9425
|
+
index: [
|
|
9426
|
+
"index-finger-metacarpal",
|
|
9427
|
+
"index-finger-phalanx-proximal",
|
|
9428
|
+
"index-finger-phalanx-intermediate",
|
|
9429
|
+
"index-finger-phalanx-distal",
|
|
9430
|
+
"index-finger-tip"
|
|
9431
|
+
],
|
|
9432
|
+
middle: [
|
|
9433
|
+
"middle-finger-metacarpal",
|
|
9434
|
+
"middle-finger-phalanx-proximal",
|
|
9435
|
+
"middle-finger-phalanx-intermediate",
|
|
9436
|
+
"middle-finger-phalanx-distal",
|
|
9437
|
+
"middle-finger-tip"
|
|
9438
|
+
],
|
|
9439
|
+
ring: [
|
|
9440
|
+
"ring-finger-metacarpal",
|
|
9441
|
+
"ring-finger-phalanx-proximal",
|
|
9442
|
+
"ring-finger-phalanx-intermediate",
|
|
9443
|
+
"ring-finger-phalanx-distal",
|
|
9444
|
+
"ring-finger-tip"
|
|
9445
|
+
],
|
|
9446
|
+
pinky: [
|
|
9447
|
+
"pinky-finger-metacarpal",
|
|
9448
|
+
"pinky-finger-phalanx-proximal",
|
|
9449
|
+
"pinky-finger-phalanx-intermediate",
|
|
9450
|
+
"pinky-finger-phalanx-distal",
|
|
9451
|
+
"pinky-finger-tip"
|
|
9452
|
+
]
|
|
9453
|
+
};
|
|
9454
|
+
var TIP_JOINTS = {
|
|
9455
|
+
thumb: "thumb-tip",
|
|
9456
|
+
index: "index-finger-tip",
|
|
9457
|
+
middle: "middle-finger-tip",
|
|
9458
|
+
ring: "ring-finger-tip",
|
|
9459
|
+
pinky: "pinky-finger-tip"
|
|
9460
|
+
};
|
|
9461
|
+
var METACARPAL_JOINTS = {
|
|
9462
|
+
thumb: "thumb-metacarpal",
|
|
9463
|
+
index: "index-finger-metacarpal",
|
|
9464
|
+
middle: "middle-finger-metacarpal",
|
|
9465
|
+
ring: "ring-finger-metacarpal",
|
|
9466
|
+
pinky: "pinky-finger-metacarpal"
|
|
9467
|
+
};
|
|
9468
|
+
var FALLBACK_HAND_SCALE = 0.08;
|
|
9469
|
+
var STRAIGHT_COS = 0.95;
|
|
9470
|
+
var CURLED_COS = 0.55;
|
|
9471
|
+
function clamp01(value) {
|
|
9472
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
9473
|
+
}
|
|
9474
|
+
__name(clamp01, "clamp01");
|
|
9475
|
+
function boneDirections(joints, finger) {
|
|
9476
|
+
const chain = FINGER_CHAINS[finger];
|
|
9477
|
+
const directions = [];
|
|
9478
|
+
for (let i = 1; i < chain.length; i += 1) {
|
|
9479
|
+
const from = joints[chain[i - 1]];
|
|
9480
|
+
const to = joints[chain[i]];
|
|
9481
|
+
if (!from || !to) return null;
|
|
9482
|
+
const direction = new THREE14__namespace.Vector3().subVectors(to, from);
|
|
9483
|
+
if (direction.lengthSq() < 1e-12) return null;
|
|
9484
|
+
directions.push(direction.normalize());
|
|
9485
|
+
}
|
|
9486
|
+
return directions.length >= 2 ? directions : null;
|
|
9487
|
+
}
|
|
9488
|
+
__name(boneDirections, "boneDirections");
|
|
9489
|
+
function bendAmounts(directions) {
|
|
9490
|
+
const bends = [];
|
|
9491
|
+
for (let i = 1; i < directions.length; i += 1) {
|
|
9492
|
+
const cos = directions[i - 1].dot(directions[i]);
|
|
9493
|
+
const straightness = clamp01((cos - CURLED_COS) / (STRAIGHT_COS - CURLED_COS));
|
|
9494
|
+
bends.push(1 - straightness);
|
|
9495
|
+
}
|
|
9496
|
+
return bends;
|
|
9497
|
+
}
|
|
9498
|
+
__name(bendAmounts, "bendAmounts");
|
|
9499
|
+
function average(values) {
|
|
9500
|
+
if (values.length === 0) return 0;
|
|
9501
|
+
let sum = 0;
|
|
9502
|
+
for (const value of values) sum += value;
|
|
9503
|
+
return sum / values.length;
|
|
9504
|
+
}
|
|
9505
|
+
__name(average, "average");
|
|
9506
|
+
function computeFingerCurl(joints, finger) {
|
|
9507
|
+
const directions = boneDirections(joints, finger);
|
|
9508
|
+
if (!directions) return null;
|
|
9509
|
+
const bends = bendAmounts(directions);
|
|
9510
|
+
if (bends.length === 0) return null;
|
|
9511
|
+
return {
|
|
9512
|
+
baseCurl: bends[0],
|
|
9513
|
+
tipCurl: average(bends.slice(1)) || bends[bends.length - 1],
|
|
9514
|
+
curl: average(bends)
|
|
9515
|
+
};
|
|
9516
|
+
}
|
|
9517
|
+
__name(computeFingerCurl, "computeFingerCurl");
|
|
9518
|
+
function computePalmWidth(joints) {
|
|
9519
|
+
const index = joints["index-finger-metacarpal"];
|
|
9520
|
+
const pinky = joints["pinky-finger-metacarpal"];
|
|
9521
|
+
if (!index || !pinky) return null;
|
|
9522
|
+
const width = index.distanceTo(pinky);
|
|
9523
|
+
return width > 1e-6 ? width : null;
|
|
9524
|
+
}
|
|
9525
|
+
__name(computePalmWidth, "computePalmWidth");
|
|
9526
|
+
function estimateHandScale(joints) {
|
|
9527
|
+
const measurements = [];
|
|
9528
|
+
const width = computePalmWidth(joints);
|
|
9529
|
+
if (width !== null) measurements.push(width);
|
|
9530
|
+
const wrist = joints.wrist;
|
|
9531
|
+
const middleTip = joints["middle-finger-tip"];
|
|
9532
|
+
if (wrist && middleTip) {
|
|
9533
|
+
const span = wrist.distanceTo(middleTip);
|
|
9534
|
+
if (span > 1e-6) measurements.push(span * 0.5);
|
|
9535
|
+
}
|
|
9536
|
+
if (measurements.length === 0) return FALLBACK_HAND_SCALE;
|
|
9537
|
+
return average(measurements);
|
|
9538
|
+
}
|
|
9539
|
+
__name(estimateHandScale, "estimateHandScale");
|
|
9540
|
+
function computePinch(joints, finger) {
|
|
9541
|
+
const thumbTip = joints["thumb-tip"];
|
|
9542
|
+
const fingerTip = joints[TIP_JOINTS[finger]];
|
|
9543
|
+
if (!thumbTip || !fingerTip) return null;
|
|
9544
|
+
const scale = estimateHandScale(joints);
|
|
9545
|
+
const distance = thumbTip.distanceTo(fingerTip);
|
|
9546
|
+
return clamp01(1 - distance / (scale * 0.7));
|
|
9547
|
+
}
|
|
9548
|
+
__name(computePinch, "computePinch");
|
|
9549
|
+
function computeSpread(joints, a, b) {
|
|
9550
|
+
const aBase = joints[METACARPAL_JOINTS[a]];
|
|
9551
|
+
const aTip = joints[TIP_JOINTS[a]];
|
|
9552
|
+
const bBase = joints[METACARPAL_JOINTS[b]];
|
|
9553
|
+
const bTip = joints[TIP_JOINTS[b]];
|
|
9554
|
+
if (!aBase || !aTip || !bBase || !bTip) return null;
|
|
9555
|
+
const dirA = new THREE14__namespace.Vector3().subVectors(aTip, aBase);
|
|
9556
|
+
const dirB = new THREE14__namespace.Vector3().subVectors(bTip, bBase);
|
|
9557
|
+
if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
|
|
9558
|
+
return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
|
|
9559
|
+
}
|
|
9560
|
+
__name(computeSpread, "computeSpread");
|
|
9561
|
+
function computePalmPose(joints, handedness) {
|
|
9562
|
+
const wrist = joints.wrist;
|
|
9563
|
+
const indexBase = joints["index-finger-metacarpal"];
|
|
9564
|
+
const pinkyBase = joints["pinky-finger-metacarpal"];
|
|
9565
|
+
if (!wrist || !indexBase || !pinkyBase) return null;
|
|
9566
|
+
const u = new THREE14__namespace.Vector3().subVectors(indexBase, wrist);
|
|
9567
|
+
const v = new THREE14__namespace.Vector3().subVectors(pinkyBase, wrist);
|
|
9568
|
+
const normal = new THREE14__namespace.Vector3().crossVectors(u, v);
|
|
9569
|
+
if (normal.lengthSq() < 1e-12) return null;
|
|
9570
|
+
normal.normalize();
|
|
9571
|
+
if (handedness === "left") normal.multiplyScalar(-1);
|
|
9572
|
+
const right = new THREE14__namespace.Vector3().subVectors(indexBase, pinkyBase);
|
|
9573
|
+
if (right.lengthSq() < 1e-12) return null;
|
|
9574
|
+
right.normalize();
|
|
9575
|
+
if (handedness === "left") right.multiplyScalar(-1);
|
|
9576
|
+
const up = new THREE14__namespace.Vector3().crossVectors(normal, right).normalize();
|
|
9577
|
+
const center = new THREE14__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
|
|
9578
|
+
return {
|
|
9579
|
+
center,
|
|
9580
|
+
normal,
|
|
9581
|
+
right,
|
|
9582
|
+
up,
|
|
9583
|
+
width: computePalmWidth(joints) ?? FALLBACK_HAND_SCALE
|
|
9584
|
+
};
|
|
9585
|
+
}
|
|
9586
|
+
__name(computePalmPose, "computePalmPose");
|
|
9587
|
+
function computeHandShape(joints, handedness) {
|
|
9588
|
+
const fingers2 = {};
|
|
9589
|
+
let tracked = 0;
|
|
9590
|
+
for (const finger of XR_FINGERS) {
|
|
9591
|
+
const curl = computeFingerCurl(joints, finger);
|
|
9592
|
+
if (curl) {
|
|
9593
|
+
fingers2[finger] = curl;
|
|
9594
|
+
tracked += 1;
|
|
9595
|
+
} else {
|
|
9596
|
+
fingers2[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
|
|
9597
|
+
}
|
|
9598
|
+
}
|
|
9599
|
+
if (tracked === 0) return null;
|
|
9600
|
+
const pinch = {};
|
|
9601
|
+
for (const finger of PINCH_FINGERS) {
|
|
9602
|
+
pinch[finger] = computePinch(joints, finger) ?? 0;
|
|
9603
|
+
}
|
|
9604
|
+
const thumbBase = joints["thumb-metacarpal"];
|
|
9605
|
+
const thumbTip = joints["thumb-tip"];
|
|
9606
|
+
let thumbDirection = null;
|
|
9607
|
+
if (thumbBase && thumbTip) {
|
|
9608
|
+
const direction = new THREE14__namespace.Vector3().subVectors(thumbTip, thumbBase);
|
|
9609
|
+
if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
|
|
9610
|
+
}
|
|
9611
|
+
return {
|
|
9612
|
+
handedness,
|
|
9613
|
+
fingers: fingers2,
|
|
9614
|
+
pinch,
|
|
9615
|
+
spread: {
|
|
9616
|
+
thumbIndex: computeSpread(joints, "thumb", "index") ?? 0,
|
|
9617
|
+
indexMiddle: computeSpread(joints, "index", "middle") ?? 0,
|
|
9618
|
+
middleRing: computeSpread(joints, "middle", "ring") ?? 0,
|
|
9619
|
+
ringPinky: computeSpread(joints, "ring", "pinky") ?? 0
|
|
9620
|
+
},
|
|
9621
|
+
palm: computePalmPose(joints, handedness),
|
|
9622
|
+
scale: estimateHandScale(joints),
|
|
9623
|
+
thumbDirection
|
|
9624
|
+
};
|
|
9625
|
+
}
|
|
9626
|
+
__name(computeHandShape, "computeHandShape");
|
|
9627
|
+
var MATCH_FALLOFF = 0.25;
|
|
9628
|
+
var NO_MATCH = {
|
|
9629
|
+
matched: false,
|
|
9630
|
+
confidence: 0,
|
|
9631
|
+
worstConstraint: null
|
|
9632
|
+
};
|
|
9633
|
+
function scoreAgainst(actual, target, tolerance) {
|
|
9634
|
+
const error = Math.abs(actual - target);
|
|
9635
|
+
if (error <= tolerance) return 1;
|
|
9636
|
+
return clamp01(1 - (error - tolerance) / MATCH_FALLOFF);
|
|
9637
|
+
}
|
|
9638
|
+
__name(scoreAgainst, "scoreAgainst");
|
|
9639
|
+
var WORLD_DIRECTIONS = {
|
|
9640
|
+
up: new THREE14__namespace.Vector3(0, 1, 0),
|
|
9641
|
+
down: new THREE14__namespace.Vector3(0, -1, 0),
|
|
9642
|
+
forward: new THREE14__namespace.Vector3(0, 0, -1),
|
|
9643
|
+
back: new THREE14__namespace.Vector3(0, 0, 1),
|
|
9644
|
+
left: new THREE14__namespace.Vector3(-1, 0, 0),
|
|
9645
|
+
right: new THREE14__namespace.Vector3(1, 0, 0)
|
|
9646
|
+
};
|
|
9647
|
+
function axisVector(shape, axis) {
|
|
9648
|
+
if (axis === "thumb") return shape.thumbDirection;
|
|
9649
|
+
if (!shape.palm) return null;
|
|
9650
|
+
return axis === "palm-normal" ? shape.palm.normal : shape.palm.up;
|
|
9651
|
+
}
|
|
9652
|
+
__name(axisVector, "axisVector");
|
|
9653
|
+
function resolveDirection(direction, shape, context) {
|
|
9654
|
+
if (direction !== "toward-head" && direction !== "away-from-head") {
|
|
9655
|
+
return WORLD_DIRECTIONS[direction];
|
|
9656
|
+
}
|
|
9657
|
+
const head = context?.headPosition;
|
|
9658
|
+
const origin = shape.palm?.center;
|
|
9659
|
+
if (!head || !origin) return null;
|
|
9660
|
+
const toHead = new THREE14__namespace.Vector3().subVectors(head, origin);
|
|
9661
|
+
if (toHead.lengthSq() < 1e-12) return null;
|
|
9662
|
+
toHead.normalize();
|
|
9663
|
+
return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
|
|
9664
|
+
}
|
|
9665
|
+
__name(resolveDirection, "resolveDirection");
|
|
9666
|
+
function matchHandPose(shape, definition, context) {
|
|
9667
|
+
if (definition.hand && definition.hand !== shape.handedness) return NO_MATCH;
|
|
9668
|
+
let confidence = 1;
|
|
9669
|
+
let worstScore = 1;
|
|
9670
|
+
let worstConstraint = null;
|
|
9671
|
+
const consider = /* @__PURE__ */ __name((score, label) => {
|
|
9672
|
+
if (score < confidence) confidence = score;
|
|
9673
|
+
if (score < worstScore) {
|
|
9674
|
+
worstScore = score;
|
|
9675
|
+
worstConstraint = label;
|
|
9676
|
+
}
|
|
9677
|
+
}, "consider");
|
|
9678
|
+
for (const finger of XR_FINGERS) {
|
|
9679
|
+
const target = definition.fingers[finger];
|
|
9680
|
+
if (!target) continue;
|
|
9681
|
+
const actual = shape.fingers[finger];
|
|
9682
|
+
consider(scoreAgainst(actual.curl, target.curl, target.tolerance), finger);
|
|
9683
|
+
if (target.baseCurl !== void 0) {
|
|
9684
|
+
consider(
|
|
9685
|
+
scoreAgainst(actual.baseCurl, target.baseCurl, target.tolerance),
|
|
9686
|
+
`${finger} knuckle`
|
|
9687
|
+
);
|
|
9688
|
+
}
|
|
9689
|
+
if (target.tipCurl !== void 0) {
|
|
9690
|
+
consider(
|
|
9691
|
+
scoreAgainst(actual.tipCurl, target.tipCurl, target.tolerance),
|
|
9692
|
+
`${finger} tip`
|
|
9693
|
+
);
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
if (definition.pinch) {
|
|
9697
|
+
const { finger, target, tolerance } = definition.pinch;
|
|
9698
|
+
consider(
|
|
9699
|
+
scoreAgainst(shape.pinch[finger], target, tolerance),
|
|
9700
|
+
`${finger} pinch`
|
|
9701
|
+
);
|
|
9702
|
+
}
|
|
9703
|
+
if (definition.orientation) {
|
|
9704
|
+
const { axis, direction, toleranceDegrees } = definition.orientation;
|
|
9705
|
+
const handAxis = axisVector(shape, axis);
|
|
9706
|
+
const wanted = resolveDirection(direction, shape, context);
|
|
9707
|
+
if (!handAxis || !wanted) {
|
|
9708
|
+
consider(0, "orientation");
|
|
9709
|
+
} else {
|
|
9710
|
+
const angle = THREE14__namespace.MathUtils.radToDeg(
|
|
9711
|
+
Math.acos(THREE14__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
|
|
9712
|
+
);
|
|
9713
|
+
const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
|
|
9714
|
+
consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
|
|
9715
|
+
}
|
|
9716
|
+
}
|
|
9717
|
+
return {
|
|
9718
|
+
matched: confidence >= 1,
|
|
9719
|
+
confidence,
|
|
9720
|
+
worstConstraint: confidence >= 1 ? null : worstConstraint
|
|
9721
|
+
};
|
|
9722
|
+
}
|
|
9723
|
+
__name(matchHandPose, "matchHandPose");
|
|
9724
|
+
var DEFAULT_ENTER_CONFIDENCE = 0.8;
|
|
9725
|
+
var DEFAULT_EXIT_CONFIDENCE = 0.55;
|
|
9726
|
+
var DEFAULT_MIN_HOLD_SECONDS = 0.15;
|
|
9727
|
+
var DEFAULT_LOST_GRACE_SECONDS = 0.25;
|
|
9728
|
+
var _HandPoseTracker = class _HandPoseTracker {
|
|
9729
|
+
constructor(options) {
|
|
9730
|
+
this.active = false;
|
|
9731
|
+
this.holdingSince = null;
|
|
9732
|
+
this.lostSince = null;
|
|
9733
|
+
this.enterConfidence = options?.enterConfidence ?? DEFAULT_ENTER_CONFIDENCE;
|
|
9734
|
+
const exit = options?.exitConfidence ?? DEFAULT_EXIT_CONFIDENCE;
|
|
9735
|
+
this.exitConfidence = Math.min(exit, this.enterConfidence - 1e-3);
|
|
9736
|
+
this.minHoldSeconds = options?.minHoldSeconds ?? DEFAULT_MIN_HOLD_SECONDS;
|
|
9737
|
+
this.lostGraceSeconds = options?.lostGraceSeconds ?? DEFAULT_LOST_GRACE_SECONDS;
|
|
9738
|
+
}
|
|
9739
|
+
isActive() {
|
|
9740
|
+
return this.active;
|
|
9741
|
+
}
|
|
9742
|
+
/** Feed this frame's confidence. Returns an event on a transition. */
|
|
9743
|
+
update(confidence, now) {
|
|
9744
|
+
this.lostSince = null;
|
|
9745
|
+
if (this.active) {
|
|
9746
|
+
if (confidence < this.exitConfidence) {
|
|
9747
|
+
this.active = false;
|
|
9748
|
+
this.holdingSince = null;
|
|
9749
|
+
return "end";
|
|
9750
|
+
}
|
|
9751
|
+
return null;
|
|
9752
|
+
}
|
|
9753
|
+
if (confidence >= this.enterConfidence) {
|
|
9754
|
+
if (this.holdingSince === null) this.holdingSince = now;
|
|
9755
|
+
if (now - this.holdingSince >= this.minHoldSeconds) {
|
|
9756
|
+
this.active = true;
|
|
9757
|
+
return "start";
|
|
9758
|
+
}
|
|
9759
|
+
return null;
|
|
9760
|
+
}
|
|
9761
|
+
this.holdingSince = null;
|
|
9762
|
+
return null;
|
|
9763
|
+
}
|
|
9764
|
+
/** Feed a frame where the hand isn't tracked at all. */
|
|
9765
|
+
miss(now) {
|
|
9766
|
+
this.holdingSince = null;
|
|
9767
|
+
if (!this.active) return null;
|
|
9768
|
+
if (this.lostSince === null) this.lostSince = now;
|
|
9769
|
+
if (now - this.lostSince < this.lostGraceSeconds) return null;
|
|
9770
|
+
this.active = false;
|
|
9771
|
+
this.lostSince = null;
|
|
9772
|
+
return "end";
|
|
9773
|
+
}
|
|
9774
|
+
reset() {
|
|
9775
|
+
this.active = false;
|
|
9776
|
+
this.holdingSince = null;
|
|
9777
|
+
this.lostSince = null;
|
|
9778
|
+
}
|
|
9779
|
+
};
|
|
9780
|
+
__name(_HandPoseTracker, "HandPoseTracker");
|
|
9781
|
+
var HandPoseTracker = _HandPoseTracker;
|
|
9782
|
+
|
|
9783
|
+
// core/systems/XRHandPoseSystem.ts
|
|
9784
|
+
var _XRHandPoseSystem = class _XRHandPoseSystem {
|
|
9785
|
+
constructor(inputSystem) {
|
|
9786
|
+
this.trackers = /* @__PURE__ */ new Map();
|
|
9787
|
+
this.shapes = /* @__PURE__ */ new Map();
|
|
9788
|
+
/** Reused so head-relative orientation checks allocate nothing per frame. */
|
|
9789
|
+
this.headPosition = new THREE14__namespace.Vector3();
|
|
9790
|
+
this.elapsed = 0;
|
|
9791
|
+
this.inputSystem = inputSystem;
|
|
9792
|
+
}
|
|
9793
|
+
update(world, deltaTime) {
|
|
9794
|
+
if (!world.isXrPresenting()) {
|
|
9795
|
+
this.resetAll();
|
|
9796
|
+
return;
|
|
9797
|
+
}
|
|
9798
|
+
this.elapsed += deltaTime;
|
|
9799
|
+
this.refreshShapes();
|
|
9800
|
+
const entries = this.collectEntries(world);
|
|
9801
|
+
if (entries.length === 0) return;
|
|
9802
|
+
world.getCamera().getWorldPosition(this.headPosition);
|
|
9803
|
+
const context = { headPosition: this.headPosition };
|
|
9804
|
+
for (const { entityId, entry } of entries) {
|
|
9805
|
+
const definition = handPoseDefinitionFor(entry, getHandPosePreset);
|
|
9806
|
+
if (!definition) continue;
|
|
9807
|
+
for (const handedness of ["left", "right"]) {
|
|
9808
|
+
if (entry.hand && entry.hand !== handedness) continue;
|
|
9809
|
+
const key = `${entityId}:${entry.id}`;
|
|
9810
|
+
const trackers = this.trackersFor(handedness);
|
|
9811
|
+
let tracker = trackers.get(key);
|
|
9812
|
+
if (!tracker) {
|
|
9813
|
+
tracker = new HandPoseTracker({
|
|
9814
|
+
minHoldSeconds: entry.minHoldSeconds
|
|
9815
|
+
});
|
|
9816
|
+
trackers.set(key, tracker);
|
|
9817
|
+
}
|
|
9818
|
+
const shape = this.shapes.get(handedness) ?? null;
|
|
9819
|
+
if (!shape) {
|
|
9820
|
+
if (tracker.miss(this.elapsed) === "end") {
|
|
9821
|
+
this.dispatch(world, entityId, "end", {
|
|
9822
|
+
poseId: entry.id,
|
|
9823
|
+
poseName: entry.name,
|
|
9824
|
+
handedness,
|
|
9825
|
+
confidence: 0
|
|
9826
|
+
});
|
|
9827
|
+
}
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
const match = matchHandPose(shape, definition, context);
|
|
9831
|
+
const event = tracker.update(match.confidence, this.elapsed);
|
|
9832
|
+
if (event) {
|
|
9833
|
+
this.dispatch(world, entityId, event, {
|
|
9834
|
+
poseId: entry.id,
|
|
9835
|
+
poseName: entry.name,
|
|
9836
|
+
handedness,
|
|
9837
|
+
confidence: match.confidence
|
|
9838
|
+
});
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
}
|
|
9843
|
+
/** Derive each hand's shape once for the whole frame. */
|
|
9844
|
+
refreshShapes() {
|
|
9845
|
+
for (const handedness of ["left", "right"]) {
|
|
9846
|
+
const input = this.inputSystem.getInput(handedness);
|
|
9847
|
+
const joints = input?.joints ?? null;
|
|
9848
|
+
this.shapes.set(
|
|
9849
|
+
handedness,
|
|
9850
|
+
joints ? computeHandShape(joints, handedness) : null
|
|
9851
|
+
);
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
collectEntries(world) {
|
|
9855
|
+
const collected = [];
|
|
9856
|
+
for (const entityId of world.query(XRHandPoseComponent)) {
|
|
9857
|
+
const component = world.getComponent(entityId, XRHandPoseComponent);
|
|
9858
|
+
if (!component?.enabled) continue;
|
|
9859
|
+
for (const entry of component.poses) {
|
|
9860
|
+
if (entry.enabled) collected.push({ entityId, entry });
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
return collected;
|
|
9864
|
+
}
|
|
9865
|
+
trackersFor(handedness) {
|
|
9866
|
+
let trackers = this.trackers.get(handedness);
|
|
9867
|
+
if (!trackers) {
|
|
9868
|
+
trackers = /* @__PURE__ */ new Map();
|
|
9869
|
+
this.trackers.set(handedness, trackers);
|
|
9870
|
+
}
|
|
9871
|
+
return trackers;
|
|
9872
|
+
}
|
|
9873
|
+
/**
|
|
9874
|
+
* Deliver to the entity's own Behaviours.
|
|
9875
|
+
*
|
|
9876
|
+
* Scoped to the entity that declared the pose rather than broadcast, so two
|
|
9877
|
+
* objects listening for the same gesture stay independent.
|
|
9878
|
+
*/
|
|
9879
|
+
dispatch(world, entityId, event, detail) {
|
|
9880
|
+
const script = world.getComponent(entityId, ScriptComponent);
|
|
9881
|
+
const instances = script?.scriptInstances;
|
|
9882
|
+
if (!instances) return;
|
|
9883
|
+
for (const instance of instances.values()) {
|
|
9884
|
+
if (!(instance instanceof Behaviour)) continue;
|
|
9885
|
+
try {
|
|
9886
|
+
if (event === "start") instance.onHandPoseStart(detail);
|
|
9887
|
+
else instance.onHandPoseEnd(detail);
|
|
9888
|
+
} catch (error) {
|
|
9889
|
+
console.error("[XRHandPoseSystem] Behaviour hook failed:", error);
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
resetAll() {
|
|
9894
|
+
for (const trackers of this.trackers.values()) {
|
|
9895
|
+
for (const tracker of trackers.values()) tracker.reset();
|
|
9896
|
+
}
|
|
9897
|
+
this.shapes.clear();
|
|
9898
|
+
}
|
|
9899
|
+
/** Live shape for a hand — powers the editor's pose readout. */
|
|
9900
|
+
getHandShape(handedness) {
|
|
9901
|
+
return this.shapes.get(handedness) ?? null;
|
|
9902
|
+
}
|
|
9903
|
+
/** Whether a given entity's pose is currently held. */
|
|
9904
|
+
isPoseActive(entityId, poseId, handedness) {
|
|
9905
|
+
return this.trackers.get(handedness)?.get(`${entityId}:${poseId}`)?.isActive() ?? false;
|
|
9906
|
+
}
|
|
9907
|
+
};
|
|
9908
|
+
__name(_XRHandPoseSystem, "XRHandPoseSystem");
|
|
9909
|
+
var XRHandPoseSystem = _XRHandPoseSystem;
|
|
8804
9910
|
|
|
8805
9911
|
exports.AnimationSystem = AnimationSystem;
|
|
8806
9912
|
exports.AudioSystem = AudioSystem;
|
|
@@ -8824,6 +9930,7 @@ exports.TransformSyncSystem = TransformSyncSystem;
|
|
|
8824
9930
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
8825
9931
|
exports.VideoSystem = VideoSystem;
|
|
8826
9932
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
9933
|
+
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
8827
9934
|
exports.XRInputSystem = XRInputSystem;
|
|
8828
9935
|
exports.XRInteractionSystem = XRInteractionSystem;
|
|
8829
9936
|
exports.XRLocomotionSystem = XRLocomotionSystem;
|