@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
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"
|
|
@@ -5054,13 +5277,9 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5054
5277
|
runtimeMessage: typeof entry.runtimeMessage === "string" ? entry.runtimeMessage : void 0
|
|
5055
5278
|
};
|
|
5056
5279
|
}).filter((entry) => entry.className || entry.scriptUrl) : [];
|
|
5057
|
-
if (
|
|
5058
|
-
|
|
5280
|
+
if (entries.length) {
|
|
5281
|
+
world.addComponent(targetEntityId, new ScriptComponent(entries));
|
|
5059
5282
|
}
|
|
5060
|
-
world.addComponent(
|
|
5061
|
-
targetEntityId,
|
|
5062
|
-
new ScriptComponent(entries)
|
|
5063
|
-
);
|
|
5064
5283
|
}
|
|
5065
5284
|
const sourceScriptObject = asRecord3(values.scriptObjectValues.get(sourceEntityId));
|
|
5066
5285
|
if (sourceScriptObject) {
|
|
@@ -5109,10 +5328,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5109
5328
|
const sourceInteractable = asRecord3(
|
|
5110
5329
|
values.xrInteractableValues.get(sourceEntityId)
|
|
5111
5330
|
);
|
|
5112
|
-
|
|
5331
|
+
let interactable = world.getComponent(
|
|
5113
5332
|
targetEntityId,
|
|
5114
5333
|
XRInteractableComponent
|
|
5115
5334
|
);
|
|
5335
|
+
if (sourceInteractable && !interactable) {
|
|
5336
|
+
interactable = new XRInteractableComponent();
|
|
5337
|
+
world.addComponent(targetEntityId, interactable);
|
|
5338
|
+
}
|
|
5116
5339
|
if (sourceInteractable && interactable) {
|
|
5117
5340
|
if (typeof sourceInteractable.enabled === "boolean") {
|
|
5118
5341
|
interactable.enabled = sourceInteractable.enabled;
|
|
@@ -5126,11 +5349,69 @@ 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
|
+
let handPose = world.getComponent(targetEntityId, XRHandPoseComponent);
|
|
5396
|
+
if (sourceHandPose && !handPose) {
|
|
5397
|
+
handPose = new XRHandPoseComponent();
|
|
5398
|
+
world.addComponent(targetEntityId, handPose);
|
|
5399
|
+
}
|
|
5400
|
+
if (sourceHandPose && handPose) {
|
|
5401
|
+
if (typeof sourceHandPose.enabled === "boolean") {
|
|
5402
|
+
handPose.enabled = sourceHandPose.enabled;
|
|
5403
|
+
}
|
|
5404
|
+
if (Array.isArray(sourceHandPose.poses)) {
|
|
5405
|
+
handPose.poses = sourceHandPose.poses.map((entry) => asRecord3(entry)).filter(
|
|
5406
|
+
(entry) => entry !== null && typeof entry.id === "string"
|
|
5407
|
+
).map(
|
|
5408
|
+
(entry) => createHandPoseEntry(
|
|
5409
|
+
entry
|
|
5410
|
+
)
|
|
5411
|
+
);
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5134
5415
|
{
|
|
5135
5416
|
const sourceInteractor = asRecord3(
|
|
5136
5417
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -6517,6 +6798,17 @@ var _Behaviour = class _Behaviour {
|
|
|
6517
6798
|
*
|
|
6518
6799
|
* @param input - The source doing the grabbing.
|
|
6519
6800
|
*/
|
|
6801
|
+
/**
|
|
6802
|
+
* A hand pose declared on this entity started being held.
|
|
6803
|
+
*
|
|
6804
|
+
* Fires once the pose has been sustained for its hold time, not the instant
|
|
6805
|
+
* the fingers pass through it.
|
|
6806
|
+
*/
|
|
6807
|
+
onHandPoseStart(_event) {
|
|
6808
|
+
}
|
|
6809
|
+
/** A held hand pose ended, or its hand stopped being tracked. */
|
|
6810
|
+
onHandPoseEnd(_event) {
|
|
6811
|
+
}
|
|
6520
6812
|
onGrab(input) {
|
|
6521
6813
|
}
|
|
6522
6814
|
/** Called when this entity is released. Optional XR hook. */
|
|
@@ -7659,13 +7951,65 @@ function blankState(source) {
|
|
|
7659
7951
|
squeezeEnd: false,
|
|
7660
7952
|
axes: [],
|
|
7661
7953
|
hasAxes: false,
|
|
7662
|
-
buttons: []
|
|
7954
|
+
buttons: [],
|
|
7955
|
+
joints: null
|
|
7663
7956
|
};
|
|
7664
7957
|
}
|
|
7665
7958
|
__name(blankState, "blankState");
|
|
7959
|
+
var JOINT_MATRIX_BUFFER = new Float32Array(25 * 16);
|
|
7960
|
+
function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
|
|
7961
|
+
const names = [];
|
|
7962
|
+
const spaces = [];
|
|
7963
|
+
for (const [name, space] of hand) {
|
|
7964
|
+
names.push(name);
|
|
7965
|
+
spaces.push(space);
|
|
7966
|
+
}
|
|
7967
|
+
if (spaces.length === 0) return null;
|
|
7968
|
+
const target = existing ?? {};
|
|
7969
|
+
const matrix = new THREE14__namespace.Matrix4();
|
|
7970
|
+
const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
|
|
7971
|
+
const fillPoses = frame.fillPoses;
|
|
7972
|
+
if (typeof fillPoses === "function") {
|
|
7973
|
+
let filled = false;
|
|
7974
|
+
try {
|
|
7975
|
+
filled = fillPoses.call(frame, spaces, referenceSpace, buffer);
|
|
7976
|
+
} catch {
|
|
7977
|
+
filled = false;
|
|
7978
|
+
}
|
|
7979
|
+
if (filled) {
|
|
7980
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
7981
|
+
matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
|
|
7982
|
+
const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
|
|
7983
|
+
vector.setFromMatrixPosition(matrix);
|
|
7984
|
+
target[names[i]] = vector;
|
|
7985
|
+
}
|
|
7986
|
+
return target;
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7989
|
+
let anyTracked = false;
|
|
7990
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
7991
|
+
const pose = frame.getJointPose?.(
|
|
7992
|
+
spaces[i],
|
|
7993
|
+
referenceSpace
|
|
7994
|
+
);
|
|
7995
|
+
if (!pose) {
|
|
7996
|
+
target[names[i]] = void 0;
|
|
7997
|
+
continue;
|
|
7998
|
+
}
|
|
7999
|
+
matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
8000
|
+
const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
|
|
8001
|
+
vector.setFromMatrixPosition(matrix);
|
|
8002
|
+
target[names[i]] = vector;
|
|
8003
|
+
anyTracked = true;
|
|
8004
|
+
}
|
|
8005
|
+
return anyTracked ? target : null;
|
|
8006
|
+
}
|
|
8007
|
+
__name(readHandJoints, "readHandJoints");
|
|
7666
8008
|
var _XRInputSystem = class _XRInputSystem {
|
|
7667
8009
|
constructor() {
|
|
7668
8010
|
this.records = /* @__PURE__ */ new Map();
|
|
8011
|
+
/** Reference-space → world matrix, rebuilt each frame from the XR rig. */
|
|
8012
|
+
this.referenceToWorld = new THREE14__namespace.Matrix4();
|
|
7669
8013
|
this.session = null;
|
|
7670
8014
|
this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
|
|
7671
8015
|
this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
|
|
@@ -7745,6 +8089,14 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7745
8089
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
|
7746
8090
|
const frame = renderer.xr.getFrame?.();
|
|
7747
8091
|
const live = /* @__PURE__ */ new Set();
|
|
8092
|
+
const rigMatrix = this.referenceToWorld;
|
|
8093
|
+
const rig = world.getCamera().parent;
|
|
8094
|
+
if (rig) {
|
|
8095
|
+
rig.updateMatrixWorld();
|
|
8096
|
+
rigMatrix.copy(rig.matrixWorld);
|
|
8097
|
+
} else {
|
|
8098
|
+
rigMatrix.identity();
|
|
8099
|
+
}
|
|
7748
8100
|
for (const source of session.inputSources) {
|
|
7749
8101
|
live.add(source);
|
|
7750
8102
|
const record = this.ensureRecord(source);
|
|
@@ -7755,7 +8107,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7755
8107
|
if (frame && referenceSpace && source.targetRaySpace) {
|
|
7756
8108
|
const pose = frame.getPose(source.targetRaySpace, referenceSpace);
|
|
7757
8109
|
if (pose) {
|
|
7758
|
-
const m = new THREE14__namespace.Matrix4().fromArray(pose.transform.matrix);
|
|
8110
|
+
const m = new THREE14__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
|
|
7759
8111
|
state.rayOrigin.setFromMatrixPosition(m);
|
|
7760
8112
|
state.rayDirection.set(0, 0, -1).applyQuaternion(
|
|
7761
8113
|
new THREE14__namespace.Quaternion().setFromRotationMatrix(m)
|
|
@@ -7765,7 +8117,7 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7765
8117
|
if (frame && referenceSpace && source.gripSpace) {
|
|
7766
8118
|
const gripPose = frame.getPose(source.gripSpace, referenceSpace);
|
|
7767
8119
|
if (gripPose) {
|
|
7768
|
-
const m = new THREE14__namespace.Matrix4().fromArray(gripPose.transform.matrix);
|
|
8120
|
+
const m = new THREE14__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
|
|
7769
8121
|
state.gripPosition ?? (state.gripPosition = new THREE14__namespace.Vector3());
|
|
7770
8122
|
state.gripQuaternion ?? (state.gripQuaternion = new THREE14__namespace.Quaternion());
|
|
7771
8123
|
state.gripPosition.setFromMatrixPosition(m);
|
|
@@ -7775,6 +8127,17 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7775
8127
|
state.gripPosition = null;
|
|
7776
8128
|
state.gripQuaternion = null;
|
|
7777
8129
|
}
|
|
8130
|
+
if (frame && referenceSpace && source.hand) {
|
|
8131
|
+
state.joints = readHandJoints(
|
|
8132
|
+
frame,
|
|
8133
|
+
source.hand,
|
|
8134
|
+
referenceSpace,
|
|
8135
|
+
rigMatrix,
|
|
8136
|
+
state.joints
|
|
8137
|
+
);
|
|
8138
|
+
} else {
|
|
8139
|
+
state.joints = null;
|
|
8140
|
+
}
|
|
7778
8141
|
const gamepad = source.gamepad;
|
|
7779
8142
|
if (gamepad) {
|
|
7780
8143
|
state.axes = Array.from(gamepad.axes);
|
|
@@ -7839,20 +8202,86 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7839
8202
|
};
|
|
7840
8203
|
__name(_XRInputSystem, "XRInputSystem");
|
|
7841
8204
|
var XRInputSystem = _XRInputSystem;
|
|
8205
|
+
var VELOCITY_SAMPLES = 5;
|
|
8206
|
+
var MAX_SAMPLE_AGE_SECONDS = 0.2;
|
|
8207
|
+
var MAX_PLAUSIBLE_SPEED = 20;
|
|
8208
|
+
var _ThrowVelocityTracker = class _ThrowVelocityTracker {
|
|
8209
|
+
constructor() {
|
|
8210
|
+
this.samples = [];
|
|
8211
|
+
}
|
|
8212
|
+
/** Record where the hand is now. Call once per frame while held. */
|
|
8213
|
+
record(position, time) {
|
|
8214
|
+
this.samples.push({ position: position.clone(), time });
|
|
8215
|
+
if (this.samples.length > VELOCITY_SAMPLES) {
|
|
8216
|
+
this.samples.shift();
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
/**
|
|
8220
|
+
* Velocity to release with, in metres/second.
|
|
8221
|
+
*
|
|
8222
|
+
* Uses the FASTEST segment inside the window rather than the most recent or
|
|
8223
|
+
* the window average. Users decelerate as they open their hand, so the last
|
|
8224
|
+
* segment understates the throw and the average washes it out; peak speed is
|
|
8225
|
+
* what matches the motion the user actually felt they made.
|
|
8226
|
+
*/
|
|
8227
|
+
computeVelocity(now) {
|
|
8228
|
+
const fresh = this.samples.filter(
|
|
8229
|
+
(sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
|
|
8230
|
+
);
|
|
8231
|
+
if (fresh.length < 2) return new THREE14__namespace.Vector3();
|
|
8232
|
+
let best = new THREE14__namespace.Vector3();
|
|
8233
|
+
let bestSpeed = 0;
|
|
8234
|
+
for (let i = 1; i < fresh.length; i += 1) {
|
|
8235
|
+
const previous = fresh[i - 1];
|
|
8236
|
+
const current = fresh[i];
|
|
8237
|
+
const dt = current.time - previous.time;
|
|
8238
|
+
if (dt <= 1e-5) continue;
|
|
8239
|
+
const velocity = new THREE14__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
|
|
8240
|
+
const speed = velocity.length();
|
|
8241
|
+
if (speed > MAX_PLAUSIBLE_SPEED) continue;
|
|
8242
|
+
if (speed > bestSpeed) {
|
|
8243
|
+
bestSpeed = speed;
|
|
8244
|
+
best = velocity;
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
return best;
|
|
8248
|
+
}
|
|
8249
|
+
clear() {
|
|
8250
|
+
this.samples.length = 0;
|
|
8251
|
+
}
|
|
8252
|
+
};
|
|
8253
|
+
__name(_ThrowVelocityTracker, "ThrowVelocityTracker");
|
|
8254
|
+
var ThrowVelocityTracker = _ThrowVelocityTracker;
|
|
8255
|
+
function angularVelocityFrom(previous, current, deltaTime) {
|
|
8256
|
+
if (deltaTime <= 1e-5) return new THREE14__namespace.Vector3();
|
|
8257
|
+
const delta = current.clone().multiply(previous.clone().invert());
|
|
8258
|
+
if (delta.w < 0) {
|
|
8259
|
+
delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
|
|
8260
|
+
}
|
|
8261
|
+
const angle = 2 * Math.acos(THREE14__namespace.MathUtils.clamp(delta.w, -1, 1));
|
|
8262
|
+
const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
|
|
8263
|
+
if (sinHalf < 1e-6 || angle < 1e-6) return new THREE14__namespace.Vector3();
|
|
8264
|
+
return new THREE14__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
|
|
8265
|
+
}
|
|
8266
|
+
__name(angularVelocityFrom, "angularVelocityFrom");
|
|
8267
|
+
|
|
8268
|
+
// core/systems/XRInteractionSystem.ts
|
|
7842
8269
|
var RAYLESS_HOLD_DISTANCE = 0.5;
|
|
7843
8270
|
var _XRInteractionSystem = class _XRInteractionSystem {
|
|
7844
8271
|
constructor(inputSystem) {
|
|
7845
8272
|
this.raycaster = new THREE14__namespace.Raycaster();
|
|
7846
|
-
/** Which entity each source is holding. Keyed by source state identity. */
|
|
7847
8273
|
this.grabs = /* @__PURE__ */ new Map();
|
|
7848
8274
|
this.hoveredEntityId = null;
|
|
8275
|
+
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
8276
|
+
this.elapsed = 0;
|
|
7849
8277
|
this.inputSystem = inputSystem;
|
|
7850
8278
|
}
|
|
7851
|
-
update(world) {
|
|
8279
|
+
update(world, deltaTime) {
|
|
7852
8280
|
if (!world.isXrPresenting()) {
|
|
7853
8281
|
this.releaseAll(world);
|
|
7854
8282
|
return;
|
|
7855
8283
|
}
|
|
8284
|
+
this.elapsed += Math.max(0, deltaTime);
|
|
7856
8285
|
const inputs = this.inputSystem.getInputs();
|
|
7857
8286
|
const interactables = this.collectInteractables(world);
|
|
7858
8287
|
this.hoveredEntityId = null;
|
|
@@ -7863,32 +8292,105 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7863
8292
|
}
|
|
7864
8293
|
const held = this.grabs.get(input);
|
|
7865
8294
|
if (held) {
|
|
7866
|
-
this.moveHeld(world, input, held);
|
|
7867
|
-
if (
|
|
8295
|
+
this.moveHeld(world, input, held, deltaTime);
|
|
8296
|
+
if (this.wantsRelease(world, input, held)) {
|
|
8297
|
+
this.release(world, input);
|
|
8298
|
+
}
|
|
7868
8299
|
continue;
|
|
7869
8300
|
}
|
|
7870
|
-
const
|
|
7871
|
-
if (
|
|
8301
|
+
const candidate = this.findGrabCandidate(world, input, interactables);
|
|
8302
|
+
if (candidate === null) continue;
|
|
7872
8303
|
if (this.hoveredEntityId === null) {
|
|
7873
|
-
this.hoveredEntityId =
|
|
8304
|
+
this.hoveredEntityId = candidate.entityId;
|
|
7874
8305
|
}
|
|
7875
8306
|
const interactable = world.getComponent(
|
|
7876
|
-
|
|
8307
|
+
candidate.entityId,
|
|
7877
8308
|
XRInteractableComponent
|
|
7878
8309
|
);
|
|
7879
|
-
if (
|
|
7880
|
-
|
|
8310
|
+
if (interactable?.grabbable && this.gestureMatches(
|
|
8311
|
+
input,
|
|
8312
|
+
interactable,
|
|
8313
|
+
/* starting */
|
|
8314
|
+
true
|
|
8315
|
+
)) {
|
|
8316
|
+
this.grab(world, input, candidate.entityId, candidate.viaDistance);
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
/** Whether the grab gesture for this object is currently starting. */
|
|
8321
|
+
gestureMatches(input, interactable, starting) {
|
|
8322
|
+
const pinch = starting ? input.selectStart : input.select;
|
|
8323
|
+
const full = starting ? input.squeezeStart : input.squeeze;
|
|
8324
|
+
switch (interactable.grabGesture) {
|
|
8325
|
+
case "pinch":
|
|
8326
|
+
return pinch;
|
|
8327
|
+
case "full":
|
|
8328
|
+
return full;
|
|
8329
|
+
default:
|
|
8330
|
+
return pinch || full;
|
|
8331
|
+
}
|
|
8332
|
+
}
|
|
8333
|
+
/** True once the gesture that started the grab has ended. */
|
|
8334
|
+
wantsRelease(world, input, held) {
|
|
8335
|
+
const interactable = world.getComponent(
|
|
8336
|
+
held.entityId,
|
|
8337
|
+
XRInteractableComponent
|
|
8338
|
+
);
|
|
8339
|
+
if (!interactable) return true;
|
|
8340
|
+
switch (interactable.grabGesture) {
|
|
8341
|
+
case "pinch":
|
|
8342
|
+
return input.selectEnd || !input.select;
|
|
8343
|
+
case "full":
|
|
8344
|
+
return input.squeezeEnd || !input.squeeze;
|
|
8345
|
+
default:
|
|
8346
|
+
return !input.select && !input.squeeze;
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
/**
|
|
8350
|
+
* Find what this hand would grab: a physically-reachable object first, then
|
|
8351
|
+
* one along the pointer ray.
|
|
8352
|
+
*
|
|
8353
|
+
* Proximity wins over the ray so that reaching for something you are touching
|
|
8354
|
+
* never picks up a distant object behind it, which feels broken.
|
|
8355
|
+
*/
|
|
8356
|
+
findGrabCandidate(world, input, interactables) {
|
|
8357
|
+
const hand = this.handPosition(input);
|
|
8358
|
+
let nearestId = null;
|
|
8359
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
8360
|
+
for (const entry of interactables) {
|
|
8361
|
+
const interactable2 = world.getComponent(
|
|
8362
|
+
entry.entityId,
|
|
8363
|
+
XRInteractableComponent
|
|
8364
|
+
);
|
|
8365
|
+
if (!interactable2) continue;
|
|
8366
|
+
if (interactable2.grabReach === "distance") continue;
|
|
8367
|
+
const position = new THREE14__namespace.Vector3();
|
|
8368
|
+
entry.object.getWorldPosition(position);
|
|
8369
|
+
const distance = position.distanceTo(hand);
|
|
8370
|
+
if (distance <= interactable2.grabRadius && distance < nearestDistance) {
|
|
8371
|
+
nearestDistance = distance;
|
|
8372
|
+
nearestId = entry.entityId;
|
|
7881
8373
|
}
|
|
7882
8374
|
}
|
|
8375
|
+
if (nearestId !== null) {
|
|
8376
|
+
return { entityId: nearestId, viaDistance: false };
|
|
8377
|
+
}
|
|
8378
|
+
const hitId = this.raycast(input, interactables);
|
|
8379
|
+
if (hitId === null) return null;
|
|
8380
|
+
const interactable = world.getComponent(hitId, XRInteractableComponent);
|
|
8381
|
+
const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
|
|
8382
|
+
return { entityId: hitId, viaDistance: viaDistanceAllowed };
|
|
7883
8383
|
}
|
|
7884
8384
|
collectInteractables(world) {
|
|
7885
8385
|
const out = [];
|
|
7886
8386
|
for (const entityId of world.query(XRInteractableComponent, Object3DRef)) {
|
|
7887
8387
|
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
7888
|
-
if (!interactable?.enabled
|
|
8388
|
+
if (!interactable?.enabled) continue;
|
|
7889
8389
|
if (!world.isEntityVisible(entityId)) continue;
|
|
7890
8390
|
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
7891
|
-
if (object)
|
|
8391
|
+
if (object) {
|
|
8392
|
+
out.push({ entityId, object, pointable: interactable.pointable });
|
|
8393
|
+
}
|
|
7892
8394
|
}
|
|
7893
8395
|
return out;
|
|
7894
8396
|
}
|
|
@@ -7896,9 +8398,11 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7896
8398
|
raycast(input, interactables) {
|
|
7897
8399
|
if (interactables.length === 0) return null;
|
|
7898
8400
|
this.raycaster.set(input.rayOrigin, input.rayDirection);
|
|
8401
|
+
this.raycaster.far = Number.POSITIVE_INFINITY;
|
|
7899
8402
|
let nearestId = null;
|
|
7900
8403
|
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
7901
8404
|
for (const entry of interactables) {
|
|
8405
|
+
if (!entry.pointable) continue;
|
|
7902
8406
|
const hit = this.raycaster.intersectObject(entry.object, true)[0];
|
|
7903
8407
|
if (!hit) continue;
|
|
7904
8408
|
if (hit.distance < nearestDistance) {
|
|
@@ -7908,43 +8412,120 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7908
8412
|
}
|
|
7909
8413
|
return nearestId;
|
|
7910
8414
|
}
|
|
7911
|
-
grab(world, input, entityId) {
|
|
8415
|
+
grab(world, input, entityId, viaDistance) {
|
|
7912
8416
|
const transform = world.getComponent(entityId, TransformComponent);
|
|
7913
|
-
|
|
8417
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
8418
|
+
if (!transform || !interactable) return;
|
|
7914
8419
|
const hand = this.handPosition(input);
|
|
7915
|
-
const
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
)
|
|
8420
|
+
const handQuaternion = this.handQuaternion(input);
|
|
8421
|
+
const remote = viaDistance && interactable.distanceBehavior === "remote";
|
|
8422
|
+
let offset;
|
|
8423
|
+
let remoteDistance = 0;
|
|
8424
|
+
if (remote) {
|
|
8425
|
+
const position = new THREE14__namespace.Vector3(
|
|
8426
|
+
transform.position.x,
|
|
8427
|
+
transform.position.y,
|
|
8428
|
+
transform.position.z
|
|
8429
|
+
);
|
|
8430
|
+
remoteDistance = position.distanceTo(input.rayOrigin);
|
|
8431
|
+
offset = new THREE14__namespace.Vector3();
|
|
8432
|
+
} else if (viaDistance) {
|
|
8433
|
+
offset = new THREE14__namespace.Vector3(
|
|
8434
|
+
interactable.grabOffsetPosition.x,
|
|
8435
|
+
interactable.grabOffsetPosition.y,
|
|
8436
|
+
interactable.grabOffsetPosition.z
|
|
8437
|
+
);
|
|
8438
|
+
} else {
|
|
8439
|
+
offset = new THREE14__namespace.Vector3(
|
|
8440
|
+
transform.position.x - hand.x,
|
|
8441
|
+
transform.position.y - hand.y,
|
|
8442
|
+
transform.position.z - hand.z
|
|
8443
|
+
);
|
|
8444
|
+
}
|
|
7920
8445
|
const physics = world.getComponent(entityId, PhysicsComponent);
|
|
7921
8446
|
let restoreBodyType = null;
|
|
7922
|
-
|
|
7923
|
-
if (physics && interactable?.grabMode === "kinematic") {
|
|
8447
|
+
if (physics) {
|
|
7924
8448
|
restoreBodyType = physics.values.rigidbody.type;
|
|
7925
8449
|
physics.values.rigidbody.type = "kinematicPosition";
|
|
7926
8450
|
}
|
|
7927
|
-
|
|
8451
|
+
const tracker = new ThrowVelocityTracker();
|
|
8452
|
+
tracker.record(hand, this.elapsed);
|
|
8453
|
+
this.grabs.set(input, {
|
|
8454
|
+
entityId,
|
|
8455
|
+
offset,
|
|
8456
|
+
rotationOffset: new THREE14__namespace.Quaternion().setFromEuler(
|
|
8457
|
+
new THREE14__namespace.Euler(
|
|
8458
|
+
interactable.grabOffsetRotation.x,
|
|
8459
|
+
interactable.grabOffsetRotation.y,
|
|
8460
|
+
interactable.grabOffsetRotation.z
|
|
8461
|
+
)
|
|
8462
|
+
),
|
|
8463
|
+
remoteDistance,
|
|
8464
|
+
remote,
|
|
8465
|
+
restoreBodyType,
|
|
8466
|
+
tracker,
|
|
8467
|
+
lastHandQuaternion: handQuaternion.clone(),
|
|
8468
|
+
angularVelocity: new THREE14__namespace.Vector3()
|
|
8469
|
+
});
|
|
7928
8470
|
this.dispatch(world, entityId, (b) => b.onGrab(input));
|
|
7929
8471
|
}
|
|
7930
|
-
moveHeld(world, input, held) {
|
|
8472
|
+
moveHeld(world, input, held, deltaTime) {
|
|
7931
8473
|
const transform = world.getComponent(held.entityId, TransformComponent);
|
|
7932
8474
|
if (!transform) return;
|
|
7933
8475
|
const hand = this.handPosition(input);
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
8476
|
+
if (held.remote) {
|
|
8477
|
+
const target = new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
|
|
8478
|
+
transform.position.x = target.x;
|
|
8479
|
+
transform.position.y = target.y;
|
|
8480
|
+
transform.position.z = target.z;
|
|
8481
|
+
} else {
|
|
8482
|
+
transform.position.x = hand.x + held.offset.x;
|
|
8483
|
+
transform.position.y = hand.y + held.offset.y;
|
|
8484
|
+
transform.position.z = hand.z + held.offset.z;
|
|
8485
|
+
}
|
|
8486
|
+
held.tracker.record(
|
|
8487
|
+
held.remote ? new THREE14__namespace.Vector3(
|
|
8488
|
+
transform.position.x,
|
|
8489
|
+
transform.position.y,
|
|
8490
|
+
transform.position.z
|
|
8491
|
+
) : hand,
|
|
8492
|
+
this.elapsed
|
|
8493
|
+
);
|
|
8494
|
+
const handQuaternion = this.handQuaternion(input);
|
|
8495
|
+
held.angularVelocity = angularVelocityFrom(
|
|
8496
|
+
held.lastHandQuaternion,
|
|
8497
|
+
handQuaternion,
|
|
8498
|
+
deltaTime
|
|
8499
|
+
);
|
|
8500
|
+
held.lastHandQuaternion = handQuaternion;
|
|
7937
8501
|
}
|
|
7938
8502
|
release(world, input) {
|
|
7939
8503
|
const held = this.grabs.get(input);
|
|
7940
8504
|
if (!held) return;
|
|
7941
8505
|
this.grabs.delete(input);
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8506
|
+
const interactable = world.getComponent(
|
|
8507
|
+
held.entityId,
|
|
8508
|
+
XRInteractableComponent
|
|
8509
|
+
);
|
|
8510
|
+
const physics = world.getComponent(held.entityId, PhysicsComponent);
|
|
8511
|
+
if (held.restoreBodyType !== null && physics) {
|
|
8512
|
+
physics.values.rigidbody.type = held.restoreBodyType;
|
|
8513
|
+
const body = physics.rigidBody;
|
|
8514
|
+
const dynamic = held.restoreBodyType === "dynamic";
|
|
8515
|
+
if (body && dynamic && interactable?.throwOnRelease) {
|
|
8516
|
+
const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
|
|
8517
|
+
try {
|
|
8518
|
+
body.setLinvel(
|
|
8519
|
+
{ x: velocity.x, y: velocity.y, z: velocity.z },
|
|
8520
|
+
true
|
|
8521
|
+
);
|
|
8522
|
+
const spin = held.angularVelocity;
|
|
8523
|
+
body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
|
|
8524
|
+
} catch {
|
|
8525
|
+
}
|
|
7946
8526
|
}
|
|
7947
8527
|
}
|
|
8528
|
+
held.tracker.clear();
|
|
7948
8529
|
this.dispatch(world, held.entityId, (b) => b.onRelease(input));
|
|
7949
8530
|
}
|
|
7950
8531
|
releaseAll(world) {
|
|
@@ -7953,14 +8534,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
7953
8534
|
}
|
|
7954
8535
|
}
|
|
7955
8536
|
/**
|
|
7956
|
-
* Where the "hand" is
|
|
7957
|
-
*
|
|
7958
|
-
*
|
|
8537
|
+
* Where the "hand" is. Controllers and tracked hands report a grip pose;
|
|
8538
|
+
* gaze and transient-pointer sources have none, so we hold the object a fixed
|
|
8539
|
+
* distance along the ray instead — which is what makes Vision Pro work.
|
|
7959
8540
|
*/
|
|
7960
8541
|
handPosition(input) {
|
|
7961
8542
|
if (input.gripPosition) return input.gripPosition;
|
|
7962
8543
|
return new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
|
|
7963
8544
|
}
|
|
8545
|
+
/**
|
|
8546
|
+
* Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
|
|
8547
|
+
* so we derive one from the aim direction — enough for spin on release.
|
|
8548
|
+
*/
|
|
8549
|
+
handQuaternion(input) {
|
|
8550
|
+
if (input.gripQuaternion) return input.gripQuaternion.clone();
|
|
8551
|
+
return new THREE14__namespace.Quaternion().setFromUnitVectors(
|
|
8552
|
+
new THREE14__namespace.Vector3(0, 0, -1),
|
|
8553
|
+
input.rayDirection.clone().normalize()
|
|
8554
|
+
);
|
|
8555
|
+
}
|
|
7964
8556
|
dispatch(world, entityId, visit) {
|
|
7965
8557
|
const script = world.getComponent(entityId, ScriptComponent);
|
|
7966
8558
|
const instances = script?.scriptInstances;
|
|
@@ -8517,6 +9109,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8517
9109
|
this.handStyleSignature = "";
|
|
8518
9110
|
this.handMaterial = null;
|
|
8519
9111
|
this.interactionSystem = options?.interactionSystem ?? null;
|
|
9112
|
+
this.inputSystem = options?.inputSystem ?? null;
|
|
8520
9113
|
this.group.name = "XRControllerVisuals";
|
|
8521
9114
|
this.group.userData.ecsPreviewHidden = true;
|
|
8522
9115
|
}
|
|
@@ -8533,8 +9126,11 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8533
9126
|
for (const { index, side } of SLOTS) {
|
|
8534
9127
|
const visuals = this.slots.get(index);
|
|
8535
9128
|
if (!visuals) continue;
|
|
8536
|
-
|
|
8537
|
-
|
|
9129
|
+
const liveKind = this.liveKindFor(side);
|
|
9130
|
+
const hasController = liveKind === null || liveKind === "controller";
|
|
9131
|
+
const hasHand = liveKind === null || liveKind === "hand";
|
|
9132
|
+
visuals.controllerModel.visible = showControllers && hasController;
|
|
9133
|
+
visuals.handModel.visible = showHands && hasHand;
|
|
8538
9134
|
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
8539
9135
|
if (showHands && this.handMaterial) {
|
|
8540
9136
|
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
@@ -8542,6 +9138,20 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8542
9138
|
this.syncCustomContent(world, visuals, side);
|
|
8543
9139
|
}
|
|
8544
9140
|
}
|
|
9141
|
+
/**
|
|
9142
|
+
* What kind of source is currently driving this hand, or null when we cannot
|
|
9143
|
+
* tell (no input system wired) — in which case callers fall back to showing
|
|
9144
|
+
* everything rather than hiding the user's hands entirely.
|
|
9145
|
+
*/
|
|
9146
|
+
liveKindFor(side) {
|
|
9147
|
+
if (!this.inputSystem) return null;
|
|
9148
|
+
const inputs = this.inputSystem.getInputs();
|
|
9149
|
+
if (inputs.length === 0) return null;
|
|
9150
|
+
for (const input of inputs) {
|
|
9151
|
+
if (input.handedness === side && input.connected) return input.kind;
|
|
9152
|
+
}
|
|
9153
|
+
return "none";
|
|
9154
|
+
}
|
|
8545
9155
|
/** The scene's interactor config, if one was authored. */
|
|
8546
9156
|
resolveInteractor(world) {
|
|
8547
9157
|
const entityId = world.query(XRInteractorComponent)[0];
|
|
@@ -8801,6 +9411,511 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
8801
9411
|
};
|
|
8802
9412
|
__name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
|
|
8803
9413
|
var XRTeleportVisualSystem = _XRTeleportVisualSystem;
|
|
9414
|
+
var XR_FINGERS = [
|
|
9415
|
+
"thumb",
|
|
9416
|
+
"index",
|
|
9417
|
+
"middle",
|
|
9418
|
+
"ring",
|
|
9419
|
+
"pinky"
|
|
9420
|
+
];
|
|
9421
|
+
var PINCH_FINGERS = [
|
|
9422
|
+
"index",
|
|
9423
|
+
"middle",
|
|
9424
|
+
"ring",
|
|
9425
|
+
"pinky"
|
|
9426
|
+
];
|
|
9427
|
+
var FINGER_CHAINS = {
|
|
9428
|
+
thumb: [
|
|
9429
|
+
"thumb-metacarpal",
|
|
9430
|
+
"thumb-phalanx-proximal",
|
|
9431
|
+
"thumb-phalanx-distal",
|
|
9432
|
+
"thumb-tip"
|
|
9433
|
+
],
|
|
9434
|
+
index: [
|
|
9435
|
+
"index-finger-metacarpal",
|
|
9436
|
+
"index-finger-phalanx-proximal",
|
|
9437
|
+
"index-finger-phalanx-intermediate",
|
|
9438
|
+
"index-finger-phalanx-distal",
|
|
9439
|
+
"index-finger-tip"
|
|
9440
|
+
],
|
|
9441
|
+
middle: [
|
|
9442
|
+
"middle-finger-metacarpal",
|
|
9443
|
+
"middle-finger-phalanx-proximal",
|
|
9444
|
+
"middle-finger-phalanx-intermediate",
|
|
9445
|
+
"middle-finger-phalanx-distal",
|
|
9446
|
+
"middle-finger-tip"
|
|
9447
|
+
],
|
|
9448
|
+
ring: [
|
|
9449
|
+
"ring-finger-metacarpal",
|
|
9450
|
+
"ring-finger-phalanx-proximal",
|
|
9451
|
+
"ring-finger-phalanx-intermediate",
|
|
9452
|
+
"ring-finger-phalanx-distal",
|
|
9453
|
+
"ring-finger-tip"
|
|
9454
|
+
],
|
|
9455
|
+
pinky: [
|
|
9456
|
+
"pinky-finger-metacarpal",
|
|
9457
|
+
"pinky-finger-phalanx-proximal",
|
|
9458
|
+
"pinky-finger-phalanx-intermediate",
|
|
9459
|
+
"pinky-finger-phalanx-distal",
|
|
9460
|
+
"pinky-finger-tip"
|
|
9461
|
+
]
|
|
9462
|
+
};
|
|
9463
|
+
var TIP_JOINTS = {
|
|
9464
|
+
thumb: "thumb-tip",
|
|
9465
|
+
index: "index-finger-tip",
|
|
9466
|
+
middle: "middle-finger-tip",
|
|
9467
|
+
ring: "ring-finger-tip",
|
|
9468
|
+
pinky: "pinky-finger-tip"
|
|
9469
|
+
};
|
|
9470
|
+
var METACARPAL_JOINTS = {
|
|
9471
|
+
thumb: "thumb-metacarpal",
|
|
9472
|
+
index: "index-finger-metacarpal",
|
|
9473
|
+
middle: "middle-finger-metacarpal",
|
|
9474
|
+
ring: "ring-finger-metacarpal",
|
|
9475
|
+
pinky: "pinky-finger-metacarpal"
|
|
9476
|
+
};
|
|
9477
|
+
var FALLBACK_HAND_SCALE = 0.08;
|
|
9478
|
+
var STRAIGHT_COS = 0.95;
|
|
9479
|
+
var CURLED_COS = 0.55;
|
|
9480
|
+
function clamp01(value) {
|
|
9481
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
9482
|
+
}
|
|
9483
|
+
__name(clamp01, "clamp01");
|
|
9484
|
+
function boneDirections(joints, finger) {
|
|
9485
|
+
const chain = FINGER_CHAINS[finger];
|
|
9486
|
+
const directions = [];
|
|
9487
|
+
for (let i = 1; i < chain.length; i += 1) {
|
|
9488
|
+
const from = joints[chain[i - 1]];
|
|
9489
|
+
const to = joints[chain[i]];
|
|
9490
|
+
if (!from || !to) return null;
|
|
9491
|
+
const direction = new THREE14__namespace.Vector3().subVectors(to, from);
|
|
9492
|
+
if (direction.lengthSq() < 1e-12) return null;
|
|
9493
|
+
directions.push(direction.normalize());
|
|
9494
|
+
}
|
|
9495
|
+
return directions.length >= 2 ? directions : null;
|
|
9496
|
+
}
|
|
9497
|
+
__name(boneDirections, "boneDirections");
|
|
9498
|
+
function bendAmounts(directions) {
|
|
9499
|
+
const bends = [];
|
|
9500
|
+
for (let i = 1; i < directions.length; i += 1) {
|
|
9501
|
+
const cos = directions[i - 1].dot(directions[i]);
|
|
9502
|
+
const straightness = clamp01((cos - CURLED_COS) / (STRAIGHT_COS - CURLED_COS));
|
|
9503
|
+
bends.push(1 - straightness);
|
|
9504
|
+
}
|
|
9505
|
+
return bends;
|
|
9506
|
+
}
|
|
9507
|
+
__name(bendAmounts, "bendAmounts");
|
|
9508
|
+
function average(values) {
|
|
9509
|
+
if (values.length === 0) return 0;
|
|
9510
|
+
let sum = 0;
|
|
9511
|
+
for (const value of values) sum += value;
|
|
9512
|
+
return sum / values.length;
|
|
9513
|
+
}
|
|
9514
|
+
__name(average, "average");
|
|
9515
|
+
function computeFingerCurl(joints, finger) {
|
|
9516
|
+
const directions = boneDirections(joints, finger);
|
|
9517
|
+
if (!directions) return null;
|
|
9518
|
+
const bends = bendAmounts(directions);
|
|
9519
|
+
if (bends.length === 0) return null;
|
|
9520
|
+
return {
|
|
9521
|
+
baseCurl: bends[0],
|
|
9522
|
+
tipCurl: average(bends.slice(1)) || bends[bends.length - 1],
|
|
9523
|
+
curl: average(bends)
|
|
9524
|
+
};
|
|
9525
|
+
}
|
|
9526
|
+
__name(computeFingerCurl, "computeFingerCurl");
|
|
9527
|
+
function computePalmWidth(joints) {
|
|
9528
|
+
const index = joints["index-finger-metacarpal"];
|
|
9529
|
+
const pinky = joints["pinky-finger-metacarpal"];
|
|
9530
|
+
if (!index || !pinky) return null;
|
|
9531
|
+
const width = index.distanceTo(pinky);
|
|
9532
|
+
return width > 1e-6 ? width : null;
|
|
9533
|
+
}
|
|
9534
|
+
__name(computePalmWidth, "computePalmWidth");
|
|
9535
|
+
function estimateHandScale(joints) {
|
|
9536
|
+
const measurements = [];
|
|
9537
|
+
const width = computePalmWidth(joints);
|
|
9538
|
+
if (width !== null) measurements.push(width);
|
|
9539
|
+
const wrist = joints.wrist;
|
|
9540
|
+
const middleTip = joints["middle-finger-tip"];
|
|
9541
|
+
if (wrist && middleTip) {
|
|
9542
|
+
const span = wrist.distanceTo(middleTip);
|
|
9543
|
+
if (span > 1e-6) measurements.push(span * 0.5);
|
|
9544
|
+
}
|
|
9545
|
+
if (measurements.length === 0) return FALLBACK_HAND_SCALE;
|
|
9546
|
+
return average(measurements);
|
|
9547
|
+
}
|
|
9548
|
+
__name(estimateHandScale, "estimateHandScale");
|
|
9549
|
+
function computePinch(joints, finger) {
|
|
9550
|
+
const thumbTip = joints["thumb-tip"];
|
|
9551
|
+
const fingerTip = joints[TIP_JOINTS[finger]];
|
|
9552
|
+
if (!thumbTip || !fingerTip) return null;
|
|
9553
|
+
const scale = estimateHandScale(joints);
|
|
9554
|
+
const distance = thumbTip.distanceTo(fingerTip);
|
|
9555
|
+
return clamp01(1 - distance / (scale * 0.7));
|
|
9556
|
+
}
|
|
9557
|
+
__name(computePinch, "computePinch");
|
|
9558
|
+
function computeSpread(joints, a, b) {
|
|
9559
|
+
const aBase = joints[METACARPAL_JOINTS[a]];
|
|
9560
|
+
const aTip = joints[TIP_JOINTS[a]];
|
|
9561
|
+
const bBase = joints[METACARPAL_JOINTS[b]];
|
|
9562
|
+
const bTip = joints[TIP_JOINTS[b]];
|
|
9563
|
+
if (!aBase || !aTip || !bBase || !bTip) return null;
|
|
9564
|
+
const dirA = new THREE14__namespace.Vector3().subVectors(aTip, aBase);
|
|
9565
|
+
const dirB = new THREE14__namespace.Vector3().subVectors(bTip, bBase);
|
|
9566
|
+
if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
|
|
9567
|
+
return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
|
|
9568
|
+
}
|
|
9569
|
+
__name(computeSpread, "computeSpread");
|
|
9570
|
+
function computePalmPose(joints, handedness) {
|
|
9571
|
+
const wrist = joints.wrist;
|
|
9572
|
+
const indexBase = joints["index-finger-metacarpal"];
|
|
9573
|
+
const pinkyBase = joints["pinky-finger-metacarpal"];
|
|
9574
|
+
if (!wrist || !indexBase || !pinkyBase) return null;
|
|
9575
|
+
const u = new THREE14__namespace.Vector3().subVectors(indexBase, wrist);
|
|
9576
|
+
const v = new THREE14__namespace.Vector3().subVectors(pinkyBase, wrist);
|
|
9577
|
+
const normal = new THREE14__namespace.Vector3().crossVectors(u, v);
|
|
9578
|
+
if (normal.lengthSq() < 1e-12) return null;
|
|
9579
|
+
normal.normalize();
|
|
9580
|
+
if (handedness === "left") normal.multiplyScalar(-1);
|
|
9581
|
+
const right = new THREE14__namespace.Vector3().subVectors(indexBase, pinkyBase);
|
|
9582
|
+
if (right.lengthSq() < 1e-12) return null;
|
|
9583
|
+
right.normalize();
|
|
9584
|
+
if (handedness === "left") right.multiplyScalar(-1);
|
|
9585
|
+
const up = new THREE14__namespace.Vector3().crossVectors(normal, right).normalize();
|
|
9586
|
+
const center = new THREE14__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
|
|
9587
|
+
return {
|
|
9588
|
+
center,
|
|
9589
|
+
normal,
|
|
9590
|
+
right,
|
|
9591
|
+
up,
|
|
9592
|
+
width: computePalmWidth(joints) ?? FALLBACK_HAND_SCALE
|
|
9593
|
+
};
|
|
9594
|
+
}
|
|
9595
|
+
__name(computePalmPose, "computePalmPose");
|
|
9596
|
+
function computeHandShape(joints, handedness) {
|
|
9597
|
+
const fingers2 = {};
|
|
9598
|
+
let tracked = 0;
|
|
9599
|
+
for (const finger of XR_FINGERS) {
|
|
9600
|
+
const curl = computeFingerCurl(joints, finger);
|
|
9601
|
+
if (curl) {
|
|
9602
|
+
fingers2[finger] = curl;
|
|
9603
|
+
tracked += 1;
|
|
9604
|
+
} else {
|
|
9605
|
+
fingers2[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
if (tracked === 0) return null;
|
|
9609
|
+
const pinch = {};
|
|
9610
|
+
for (const finger of PINCH_FINGERS) {
|
|
9611
|
+
pinch[finger] = computePinch(joints, finger) ?? 0;
|
|
9612
|
+
}
|
|
9613
|
+
const thumbBase = joints["thumb-metacarpal"];
|
|
9614
|
+
const thumbTip = joints["thumb-tip"];
|
|
9615
|
+
let thumbDirection = null;
|
|
9616
|
+
if (thumbBase && thumbTip) {
|
|
9617
|
+
const direction = new THREE14__namespace.Vector3().subVectors(thumbTip, thumbBase);
|
|
9618
|
+
if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
|
|
9619
|
+
}
|
|
9620
|
+
return {
|
|
9621
|
+
handedness,
|
|
9622
|
+
fingers: fingers2,
|
|
9623
|
+
pinch,
|
|
9624
|
+
spread: {
|
|
9625
|
+
thumbIndex: computeSpread(joints, "thumb", "index") ?? 0,
|
|
9626
|
+
indexMiddle: computeSpread(joints, "index", "middle") ?? 0,
|
|
9627
|
+
middleRing: computeSpread(joints, "middle", "ring") ?? 0,
|
|
9628
|
+
ringPinky: computeSpread(joints, "ring", "pinky") ?? 0
|
|
9629
|
+
},
|
|
9630
|
+
palm: computePalmPose(joints, handedness),
|
|
9631
|
+
scale: estimateHandScale(joints),
|
|
9632
|
+
thumbDirection
|
|
9633
|
+
};
|
|
9634
|
+
}
|
|
9635
|
+
__name(computeHandShape, "computeHandShape");
|
|
9636
|
+
var MATCH_FALLOFF = 0.25;
|
|
9637
|
+
var NO_MATCH = {
|
|
9638
|
+
matched: false,
|
|
9639
|
+
confidence: 0,
|
|
9640
|
+
worstConstraint: null
|
|
9641
|
+
};
|
|
9642
|
+
function scoreAgainst(actual, target, tolerance) {
|
|
9643
|
+
const error = Math.abs(actual - target);
|
|
9644
|
+
if (error <= tolerance) return 1;
|
|
9645
|
+
return clamp01(1 - (error - tolerance) / MATCH_FALLOFF);
|
|
9646
|
+
}
|
|
9647
|
+
__name(scoreAgainst, "scoreAgainst");
|
|
9648
|
+
var WORLD_DIRECTIONS = {
|
|
9649
|
+
up: new THREE14__namespace.Vector3(0, 1, 0),
|
|
9650
|
+
down: new THREE14__namespace.Vector3(0, -1, 0),
|
|
9651
|
+
forward: new THREE14__namespace.Vector3(0, 0, -1),
|
|
9652
|
+
back: new THREE14__namespace.Vector3(0, 0, 1),
|
|
9653
|
+
left: new THREE14__namespace.Vector3(-1, 0, 0),
|
|
9654
|
+
right: new THREE14__namespace.Vector3(1, 0, 0)
|
|
9655
|
+
};
|
|
9656
|
+
function axisVector(shape, axis) {
|
|
9657
|
+
if (axis === "thumb") return shape.thumbDirection;
|
|
9658
|
+
if (!shape.palm) return null;
|
|
9659
|
+
return axis === "palm-normal" ? shape.palm.normal : shape.palm.up;
|
|
9660
|
+
}
|
|
9661
|
+
__name(axisVector, "axisVector");
|
|
9662
|
+
function resolveDirection(direction, shape, context) {
|
|
9663
|
+
if (direction !== "toward-head" && direction !== "away-from-head") {
|
|
9664
|
+
return WORLD_DIRECTIONS[direction];
|
|
9665
|
+
}
|
|
9666
|
+
const head = context?.headPosition;
|
|
9667
|
+
const origin = shape.palm?.center;
|
|
9668
|
+
if (!head || !origin) return null;
|
|
9669
|
+
const toHead = new THREE14__namespace.Vector3().subVectors(head, origin);
|
|
9670
|
+
if (toHead.lengthSq() < 1e-12) return null;
|
|
9671
|
+
toHead.normalize();
|
|
9672
|
+
return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
|
|
9673
|
+
}
|
|
9674
|
+
__name(resolveDirection, "resolveDirection");
|
|
9675
|
+
function matchHandPose(shape, definition, context) {
|
|
9676
|
+
if (definition.hand && definition.hand !== shape.handedness) return NO_MATCH;
|
|
9677
|
+
let confidence = 1;
|
|
9678
|
+
let worstScore = 1;
|
|
9679
|
+
let worstConstraint = null;
|
|
9680
|
+
const consider = /* @__PURE__ */ __name((score, label) => {
|
|
9681
|
+
if (score < confidence) confidence = score;
|
|
9682
|
+
if (score < worstScore) {
|
|
9683
|
+
worstScore = score;
|
|
9684
|
+
worstConstraint = label;
|
|
9685
|
+
}
|
|
9686
|
+
}, "consider");
|
|
9687
|
+
for (const finger of XR_FINGERS) {
|
|
9688
|
+
const target = definition.fingers[finger];
|
|
9689
|
+
if (!target) continue;
|
|
9690
|
+
const actual = shape.fingers[finger];
|
|
9691
|
+
consider(scoreAgainst(actual.curl, target.curl, target.tolerance), finger);
|
|
9692
|
+
if (target.baseCurl !== void 0) {
|
|
9693
|
+
consider(
|
|
9694
|
+
scoreAgainst(actual.baseCurl, target.baseCurl, target.tolerance),
|
|
9695
|
+
`${finger} knuckle`
|
|
9696
|
+
);
|
|
9697
|
+
}
|
|
9698
|
+
if (target.tipCurl !== void 0) {
|
|
9699
|
+
consider(
|
|
9700
|
+
scoreAgainst(actual.tipCurl, target.tipCurl, target.tolerance),
|
|
9701
|
+
`${finger} tip`
|
|
9702
|
+
);
|
|
9703
|
+
}
|
|
9704
|
+
}
|
|
9705
|
+
if (definition.pinch) {
|
|
9706
|
+
const { finger, target, tolerance } = definition.pinch;
|
|
9707
|
+
consider(
|
|
9708
|
+
scoreAgainst(shape.pinch[finger], target, tolerance),
|
|
9709
|
+
`${finger} pinch`
|
|
9710
|
+
);
|
|
9711
|
+
}
|
|
9712
|
+
if (definition.orientation) {
|
|
9713
|
+
const { axis, direction, toleranceDegrees } = definition.orientation;
|
|
9714
|
+
const handAxis = axisVector(shape, axis);
|
|
9715
|
+
const wanted = resolveDirection(direction, shape, context);
|
|
9716
|
+
if (!handAxis || !wanted) {
|
|
9717
|
+
consider(0, "orientation");
|
|
9718
|
+
} else {
|
|
9719
|
+
const angle = THREE14__namespace.MathUtils.radToDeg(
|
|
9720
|
+
Math.acos(THREE14__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
|
|
9721
|
+
);
|
|
9722
|
+
const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
|
|
9723
|
+
consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
return {
|
|
9727
|
+
matched: confidence >= 1,
|
|
9728
|
+
confidence,
|
|
9729
|
+
worstConstraint: confidence >= 1 ? null : worstConstraint
|
|
9730
|
+
};
|
|
9731
|
+
}
|
|
9732
|
+
__name(matchHandPose, "matchHandPose");
|
|
9733
|
+
var DEFAULT_ENTER_CONFIDENCE = 0.8;
|
|
9734
|
+
var DEFAULT_EXIT_CONFIDENCE = 0.55;
|
|
9735
|
+
var DEFAULT_MIN_HOLD_SECONDS = 0.15;
|
|
9736
|
+
var DEFAULT_LOST_GRACE_SECONDS = 0.25;
|
|
9737
|
+
var _HandPoseTracker = class _HandPoseTracker {
|
|
9738
|
+
constructor(options) {
|
|
9739
|
+
this.active = false;
|
|
9740
|
+
this.holdingSince = null;
|
|
9741
|
+
this.lostSince = null;
|
|
9742
|
+
this.enterConfidence = options?.enterConfidence ?? DEFAULT_ENTER_CONFIDENCE;
|
|
9743
|
+
const exit = options?.exitConfidence ?? DEFAULT_EXIT_CONFIDENCE;
|
|
9744
|
+
this.exitConfidence = Math.min(exit, this.enterConfidence - 1e-3);
|
|
9745
|
+
this.minHoldSeconds = options?.minHoldSeconds ?? DEFAULT_MIN_HOLD_SECONDS;
|
|
9746
|
+
this.lostGraceSeconds = options?.lostGraceSeconds ?? DEFAULT_LOST_GRACE_SECONDS;
|
|
9747
|
+
}
|
|
9748
|
+
isActive() {
|
|
9749
|
+
return this.active;
|
|
9750
|
+
}
|
|
9751
|
+
/** Feed this frame's confidence. Returns an event on a transition. */
|
|
9752
|
+
update(confidence, now) {
|
|
9753
|
+
this.lostSince = null;
|
|
9754
|
+
if (this.active) {
|
|
9755
|
+
if (confidence < this.exitConfidence) {
|
|
9756
|
+
this.active = false;
|
|
9757
|
+
this.holdingSince = null;
|
|
9758
|
+
return "end";
|
|
9759
|
+
}
|
|
9760
|
+
return null;
|
|
9761
|
+
}
|
|
9762
|
+
if (confidence >= this.enterConfidence) {
|
|
9763
|
+
if (this.holdingSince === null) this.holdingSince = now;
|
|
9764
|
+
if (now - this.holdingSince >= this.minHoldSeconds) {
|
|
9765
|
+
this.active = true;
|
|
9766
|
+
return "start";
|
|
9767
|
+
}
|
|
9768
|
+
return null;
|
|
9769
|
+
}
|
|
9770
|
+
this.holdingSince = null;
|
|
9771
|
+
return null;
|
|
9772
|
+
}
|
|
9773
|
+
/** Feed a frame where the hand isn't tracked at all. */
|
|
9774
|
+
miss(now) {
|
|
9775
|
+
this.holdingSince = null;
|
|
9776
|
+
if (!this.active) return null;
|
|
9777
|
+
if (this.lostSince === null) this.lostSince = now;
|
|
9778
|
+
if (now - this.lostSince < this.lostGraceSeconds) return null;
|
|
9779
|
+
this.active = false;
|
|
9780
|
+
this.lostSince = null;
|
|
9781
|
+
return "end";
|
|
9782
|
+
}
|
|
9783
|
+
reset() {
|
|
9784
|
+
this.active = false;
|
|
9785
|
+
this.holdingSince = null;
|
|
9786
|
+
this.lostSince = null;
|
|
9787
|
+
}
|
|
9788
|
+
};
|
|
9789
|
+
__name(_HandPoseTracker, "HandPoseTracker");
|
|
9790
|
+
var HandPoseTracker = _HandPoseTracker;
|
|
9791
|
+
|
|
9792
|
+
// core/systems/XRHandPoseSystem.ts
|
|
9793
|
+
var _XRHandPoseSystem = class _XRHandPoseSystem {
|
|
9794
|
+
constructor(inputSystem) {
|
|
9795
|
+
this.trackers = /* @__PURE__ */ new Map();
|
|
9796
|
+
this.shapes = /* @__PURE__ */ new Map();
|
|
9797
|
+
/** Reused so head-relative orientation checks allocate nothing per frame. */
|
|
9798
|
+
this.headPosition = new THREE14__namespace.Vector3();
|
|
9799
|
+
this.elapsed = 0;
|
|
9800
|
+
this.inputSystem = inputSystem;
|
|
9801
|
+
}
|
|
9802
|
+
update(world, deltaTime) {
|
|
9803
|
+
if (!world.isXrPresenting()) {
|
|
9804
|
+
this.resetAll();
|
|
9805
|
+
return;
|
|
9806
|
+
}
|
|
9807
|
+
this.elapsed += deltaTime;
|
|
9808
|
+
this.refreshShapes();
|
|
9809
|
+
const entries = this.collectEntries(world);
|
|
9810
|
+
if (entries.length === 0) return;
|
|
9811
|
+
world.getCamera().getWorldPosition(this.headPosition);
|
|
9812
|
+
const context = { headPosition: this.headPosition };
|
|
9813
|
+
for (const { entityId, entry } of entries) {
|
|
9814
|
+
const definition = handPoseDefinitionFor(entry, getHandPosePreset);
|
|
9815
|
+
if (!definition) continue;
|
|
9816
|
+
for (const handedness of ["left", "right"]) {
|
|
9817
|
+
if (entry.hand && entry.hand !== handedness) continue;
|
|
9818
|
+
const key = `${entityId}:${entry.id}`;
|
|
9819
|
+
const trackers = this.trackersFor(handedness);
|
|
9820
|
+
let tracker = trackers.get(key);
|
|
9821
|
+
if (!tracker) {
|
|
9822
|
+
tracker = new HandPoseTracker({
|
|
9823
|
+
minHoldSeconds: entry.minHoldSeconds
|
|
9824
|
+
});
|
|
9825
|
+
trackers.set(key, tracker);
|
|
9826
|
+
}
|
|
9827
|
+
const shape = this.shapes.get(handedness) ?? null;
|
|
9828
|
+
if (!shape) {
|
|
9829
|
+
if (tracker.miss(this.elapsed) === "end") {
|
|
9830
|
+
this.dispatch(world, entityId, "end", {
|
|
9831
|
+
poseId: entry.id,
|
|
9832
|
+
poseName: entry.name,
|
|
9833
|
+
handedness,
|
|
9834
|
+
confidence: 0
|
|
9835
|
+
});
|
|
9836
|
+
}
|
|
9837
|
+
continue;
|
|
9838
|
+
}
|
|
9839
|
+
const match = matchHandPose(shape, definition, context);
|
|
9840
|
+
const event = tracker.update(match.confidence, this.elapsed);
|
|
9841
|
+
if (event) {
|
|
9842
|
+
this.dispatch(world, entityId, event, {
|
|
9843
|
+
poseId: entry.id,
|
|
9844
|
+
poseName: entry.name,
|
|
9845
|
+
handedness,
|
|
9846
|
+
confidence: match.confidence
|
|
9847
|
+
});
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
/** Derive each hand's shape once for the whole frame. */
|
|
9853
|
+
refreshShapes() {
|
|
9854
|
+
for (const handedness of ["left", "right"]) {
|
|
9855
|
+
const input = this.inputSystem.getInput(handedness);
|
|
9856
|
+
const joints = input?.joints ?? null;
|
|
9857
|
+
this.shapes.set(
|
|
9858
|
+
handedness,
|
|
9859
|
+
joints ? computeHandShape(joints, handedness) : null
|
|
9860
|
+
);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
collectEntries(world) {
|
|
9864
|
+
const collected = [];
|
|
9865
|
+
for (const entityId of world.query(XRHandPoseComponent)) {
|
|
9866
|
+
const component = world.getComponent(entityId, XRHandPoseComponent);
|
|
9867
|
+
if (!component?.enabled) continue;
|
|
9868
|
+
for (const entry of component.poses) {
|
|
9869
|
+
if (entry.enabled) collected.push({ entityId, entry });
|
|
9870
|
+
}
|
|
9871
|
+
}
|
|
9872
|
+
return collected;
|
|
9873
|
+
}
|
|
9874
|
+
trackersFor(handedness) {
|
|
9875
|
+
let trackers = this.trackers.get(handedness);
|
|
9876
|
+
if (!trackers) {
|
|
9877
|
+
trackers = /* @__PURE__ */ new Map();
|
|
9878
|
+
this.trackers.set(handedness, trackers);
|
|
9879
|
+
}
|
|
9880
|
+
return trackers;
|
|
9881
|
+
}
|
|
9882
|
+
/**
|
|
9883
|
+
* Deliver to the entity's own Behaviours.
|
|
9884
|
+
*
|
|
9885
|
+
* Scoped to the entity that declared the pose rather than broadcast, so two
|
|
9886
|
+
* objects listening for the same gesture stay independent.
|
|
9887
|
+
*/
|
|
9888
|
+
dispatch(world, entityId, event, detail) {
|
|
9889
|
+
const script = world.getComponent(entityId, ScriptComponent);
|
|
9890
|
+
const instances = script?.scriptInstances;
|
|
9891
|
+
if (!instances) return;
|
|
9892
|
+
for (const instance of instances.values()) {
|
|
9893
|
+
if (!(instance instanceof Behaviour)) continue;
|
|
9894
|
+
try {
|
|
9895
|
+
if (event === "start") instance.onHandPoseStart(detail);
|
|
9896
|
+
else instance.onHandPoseEnd(detail);
|
|
9897
|
+
} catch (error) {
|
|
9898
|
+
console.error("[XRHandPoseSystem] Behaviour hook failed:", error);
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
resetAll() {
|
|
9903
|
+
for (const trackers of this.trackers.values()) {
|
|
9904
|
+
for (const tracker of trackers.values()) tracker.reset();
|
|
9905
|
+
}
|
|
9906
|
+
this.shapes.clear();
|
|
9907
|
+
}
|
|
9908
|
+
/** Live shape for a hand — powers the editor's pose readout. */
|
|
9909
|
+
getHandShape(handedness) {
|
|
9910
|
+
return this.shapes.get(handedness) ?? null;
|
|
9911
|
+
}
|
|
9912
|
+
/** Whether a given entity's pose is currently held. */
|
|
9913
|
+
isPoseActive(entityId, poseId, handedness) {
|
|
9914
|
+
return this.trackers.get(handedness)?.get(`${entityId}:${poseId}`)?.isActive() ?? false;
|
|
9915
|
+
}
|
|
9916
|
+
};
|
|
9917
|
+
__name(_XRHandPoseSystem, "XRHandPoseSystem");
|
|
9918
|
+
var XRHandPoseSystem = _XRHandPoseSystem;
|
|
8804
9919
|
|
|
8805
9920
|
exports.AnimationSystem = AnimationSystem;
|
|
8806
9921
|
exports.AudioSystem = AudioSystem;
|
|
@@ -8824,6 +9939,7 @@ exports.TransformSyncSystem = TransformSyncSystem;
|
|
|
8824
9939
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
8825
9940
|
exports.VideoSystem = VideoSystem;
|
|
8826
9941
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
9942
|
+
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
8827
9943
|
exports.XRInputSystem = XRInputSystem;
|
|
8828
9944
|
exports.XRInteractionSystem = XRInteractionSystem;
|
|
8829
9945
|
exports.XRLocomotionSystem = XRLocomotionSystem;
|