@relu-interactives/spatial-ecs 0.3.10 → 0.3.12
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/{XRHandPose-hAtj9wm-.d.cts → XRHandPose-DawOR0uR.d.cts} +56 -14
- package/dist/{XRHandPose-CjacJ_16.d.ts → XRHandPose-DhAd9Qi6.d.ts} +56 -14
- package/dist/{chunk-NJOO7NO6.js → chunk-7ZIN6TGT.js} +3 -3
- package/dist/{chunk-NJOO7NO6.js.map → chunk-7ZIN6TGT.js.map} +1 -1
- package/dist/{chunk-5UVBEAGW.js → chunk-ID7U4FER.js} +4 -4
- package/dist/{chunk-5UVBEAGW.js.map → chunk-ID7U4FER.js.map} +1 -1
- package/dist/{chunk-O2HSIZFC.js → chunk-LCJ6IH2O.js} +324 -47
- package/dist/chunk-LCJ6IH2O.js.map +1 -0
- package/dist/{chunk-ELSZ5U3F.js → chunk-OMDYLFTN.js} +7 -4
- package/dist/chunk-OMDYLFTN.js.map +1 -0
- package/dist/components/index.cjs +5 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +2 -2
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +1 -1
- package/dist/docs/guide/vr-and-hand-poses.md +7 -3
- package/dist/factories/index.cjs +5 -2
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +1 -1
- package/dist/factories/index.d.ts +1 -1
- package/dist/factories/index.js +3 -3
- package/dist/{index-B7JDsx4a.d.ts → index-B49322Q3.d.ts} +2 -3
- package/dist/{index-DU-CF-3m.d.cts → index-DJ9bn9SF.d.cts} +70 -1
- package/dist/{index-B-KhN8Ox.d.ts → index-M0I5B3JA.d.ts} +70 -1
- package/dist/{index-B9JqYr3Q.d.cts → index-Xm3im4Pt.d.cts} +2 -3
- package/dist/index.cjs +326 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +8 -8
- package/dist/systems/index.cjs +326 -44
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +1 -1
- package/dist/systems/index.d.ts +1 -1
- package/dist/systems/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-ELSZ5U3F.js.map +0 -1
- package/dist/chunk-O2HSIZFC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2830,7 +2830,6 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
|
|
|
2830
2830
|
this.enabled = options?.enabled ?? true;
|
|
2831
2831
|
this.grabbable = options?.grabbable ?? true;
|
|
2832
2832
|
this.pointable = options?.pointable ?? true;
|
|
2833
|
-
this.grabMode = options?.grabMode ?? "kinematic";
|
|
2834
2833
|
this.grabReach = options?.grabReach ?? "both";
|
|
2835
2834
|
this.distanceBehavior = options?.distanceBehavior ?? "snap";
|
|
2836
2835
|
this.grabGesture = options?.grabGesture ?? "any";
|
|
@@ -2855,7 +2854,9 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
|
|
|
2855
2854
|
);
|
|
2856
2855
|
this.throwOnRelease = options?.throwOnRelease ?? true;
|
|
2857
2856
|
this.throwStrength = options?.throwStrength ?? 1;
|
|
2858
|
-
this.
|
|
2857
|
+
this.hoverBehavior = options?.hoverBehavior ?? "color";
|
|
2858
|
+
this.hoverColor = options?.hoverColor ?? "#4da3ff";
|
|
2859
|
+
this.hoverScale = options?.hoverScale ?? 1.06;
|
|
2859
2860
|
}
|
|
2860
2861
|
};
|
|
2861
2862
|
__name(_XRInteractableComponent, "XRInteractableComponent");
|
|
@@ -2890,6 +2891,8 @@ var _XRInteractorComponent = class _XRInteractorComponent extends Component {
|
|
|
2890
2891
|
this.handOpacity = options?.handOpacity ?? 1;
|
|
2891
2892
|
this.handEmissiveIntensity = options?.handEmissiveIntensity ?? 0;
|
|
2892
2893
|
this.showRay = options?.showRay ?? true;
|
|
2894
|
+
this.rayColor = options?.rayColor ?? "#4da3ff";
|
|
2895
|
+
this.rayThickness = options?.rayThickness ?? 6e-3;
|
|
2893
2896
|
}
|
|
2894
2897
|
};
|
|
2895
2898
|
__name(_XRInteractorComponent, "XRInteractorComponent");
|
|
@@ -4866,9 +4869,6 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
4866
4869
|
if (typeof sourceInteractable.pointable === "boolean") {
|
|
4867
4870
|
interactable.pointable = sourceInteractable.pointable;
|
|
4868
4871
|
}
|
|
4869
|
-
if (typeof sourceInteractable.grabMode === "string") {
|
|
4870
|
-
interactable.grabMode = sourceInteractable.grabMode;
|
|
4871
|
-
}
|
|
4872
4872
|
if (typeof sourceInteractable.grabReach === "string") {
|
|
4873
4873
|
interactable.grabReach = sourceInteractable.grabReach;
|
|
4874
4874
|
}
|
|
@@ -4927,8 +4927,16 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
4927
4927
|
z: typeof grabRot.z === "number" ? grabRot.z : 0
|
|
4928
4928
|
};
|
|
4929
4929
|
}
|
|
4930
|
-
if (typeof sourceInteractable.
|
|
4931
|
-
interactable.
|
|
4930
|
+
if (typeof sourceInteractable.hoverBehavior === "string") {
|
|
4931
|
+
interactable.hoverBehavior = sourceInteractable.hoverBehavior;
|
|
4932
|
+
} else if (sourceInteractable.highlightOnHover === false) {
|
|
4933
|
+
interactable.hoverBehavior = "none";
|
|
4934
|
+
}
|
|
4935
|
+
if (typeof sourceInteractable.hoverColor === "string") {
|
|
4936
|
+
interactable.hoverColor = sourceInteractable.hoverColor;
|
|
4937
|
+
}
|
|
4938
|
+
if (typeof sourceInteractable.hoverScale === "number") {
|
|
4939
|
+
interactable.hoverScale = sourceInteractable.hoverScale;
|
|
4932
4940
|
}
|
|
4933
4941
|
}
|
|
4934
4942
|
}
|
|
@@ -4980,6 +4988,12 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
4980
4988
|
if (typeof sourceInteractor.showRay === "boolean") {
|
|
4981
4989
|
interactor.showRay = sourceInteractor.showRay;
|
|
4982
4990
|
}
|
|
4991
|
+
if (typeof sourceInteractor.rayColor === "string") {
|
|
4992
|
+
interactor.rayColor = sourceInteractor.rayColor;
|
|
4993
|
+
}
|
|
4994
|
+
if (typeof sourceInteractor.rayThickness === "number") {
|
|
4995
|
+
interactor.rayThickness = sourceInteractor.rayThickness;
|
|
4996
|
+
}
|
|
4983
4997
|
}
|
|
4984
4998
|
}
|
|
4985
4999
|
{
|
|
@@ -7445,6 +7459,20 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
|
|
|
7445
7459
|
__name(_PostprocessingSyncSystem, "PostprocessingSyncSystem");
|
|
7446
7460
|
var PostprocessingSyncSystem = _PostprocessingSyncSystem;
|
|
7447
7461
|
var MIN_SIZE = 1e-4;
|
|
7462
|
+
function toBodyType(RAPIER, bodyType) {
|
|
7463
|
+
switch (bodyType) {
|
|
7464
|
+
case "fixed":
|
|
7465
|
+
return RAPIER.RigidBodyType.Fixed;
|
|
7466
|
+
case "kinematicPosition":
|
|
7467
|
+
return RAPIER.RigidBodyType.KinematicPositionBased;
|
|
7468
|
+
case "kinematicVelocity":
|
|
7469
|
+
return RAPIER.RigidBodyType.KinematicVelocityBased;
|
|
7470
|
+
case "dynamic":
|
|
7471
|
+
default:
|
|
7472
|
+
return RAPIER.RigidBodyType.Dynamic;
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
__name(toBodyType, "toBodyType");
|
|
7448
7476
|
function createRigidBodyDesc(RAPIER, bodyType) {
|
|
7449
7477
|
if (bodyType === "fixed") {
|
|
7450
7478
|
return RAPIER.RigidBodyDesc.fixed();
|
|
@@ -7743,7 +7771,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
|
|
|
7743
7771
|
setPhysicsLiveRef(physics, "collider", null);
|
|
7744
7772
|
continue;
|
|
7745
7773
|
}
|
|
7746
|
-
const
|
|
7774
|
+
const { type: _desiredBodyType, ...rigidBodyRebuildValues } = physics.values.rigidbody;
|
|
7775
|
+
const rigidBodySignature = JSON.stringify(rigidBodyRebuildValues);
|
|
7747
7776
|
const previousRigidBodySignature = this.rigidBodySignatures.get(entityId);
|
|
7748
7777
|
const hasRigidBody = this.rigidBodyHandles.has(entityId);
|
|
7749
7778
|
const shouldRecreateBody = !hasRigidBody || previousRigidBodySignature !== rigidBodySignature;
|
|
@@ -7798,6 +7827,25 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
|
|
|
7798
7827
|
continue;
|
|
7799
7828
|
}
|
|
7800
7829
|
setPhysicsLiveRef(physics, "rigidBody", body);
|
|
7830
|
+
const desiredBodyType = this.isEditorMode ? "fixed" : physics.values.rigidbody.type;
|
|
7831
|
+
const desiredRapierType = toBodyType(this.rapier, desiredBodyType);
|
|
7832
|
+
if (body.bodyType() !== desiredRapierType) {
|
|
7833
|
+
body.setBodyType(desiredRapierType, true);
|
|
7834
|
+
if (!this.isEditorMode && desiredBodyType === "dynamic") {
|
|
7835
|
+
body.setAdditionalMass(physics.values.rigidbody.mass, true);
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
const pending = physics.pendingVelocity;
|
|
7839
|
+
if (pending) {
|
|
7840
|
+
setPhysicsLiveRef(physics, "pendingVelocity", null);
|
|
7841
|
+
if (!this.isEditorMode && desiredBodyType === "dynamic") {
|
|
7842
|
+
try {
|
|
7843
|
+
body.setLinvel(pending.linear, true);
|
|
7844
|
+
body.setAngvel(pending.angular, true);
|
|
7845
|
+
} catch {
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7801
7849
|
if (this.isEditorMode || physics.values.rigidbody.type !== "dynamic") {
|
|
7802
7850
|
syncBodyFromTransform(body, transform);
|
|
7803
7851
|
}
|
|
@@ -11054,6 +11102,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11054
11102
|
this.raycaster = new THREE51__namespace.Raycaster();
|
|
11055
11103
|
this.grabs = /* @__PURE__ */ new Map();
|
|
11056
11104
|
this.hoveredEntityId = null;
|
|
11105
|
+
/** Per-hand hover, with the hit distance so the ray can stop at the surface. */
|
|
11106
|
+
this.hovers = /* @__PURE__ */ new Map();
|
|
11057
11107
|
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
11058
11108
|
this.elapsed = 0;
|
|
11059
11109
|
this.inputSystem = inputSystem;
|
|
@@ -11067,6 +11117,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11067
11117
|
const inputs = this.inputSystem.getInputs();
|
|
11068
11118
|
const interactables = this.collectInteractables(world);
|
|
11069
11119
|
this.hoveredEntityId = null;
|
|
11120
|
+
this.hovers.clear();
|
|
11070
11121
|
for (const input of inputs) {
|
|
11071
11122
|
if (!input.connected) {
|
|
11072
11123
|
this.release(world, input);
|
|
@@ -11084,6 +11135,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11084
11135
|
if (this.hoveredEntityId === null) {
|
|
11085
11136
|
this.hoveredEntityId = candidate.entityId;
|
|
11086
11137
|
}
|
|
11138
|
+
this.hovers.set(input, {
|
|
11139
|
+
entityId: candidate.entityId,
|
|
11140
|
+
distance: candidate.distance
|
|
11141
|
+
});
|
|
11087
11142
|
const interactable = world.getComponent(
|
|
11088
11143
|
candidate.entityId,
|
|
11089
11144
|
XRInteractableComponent
|
|
@@ -11156,7 +11211,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11156
11211
|
transform.position.x - hand.x,
|
|
11157
11212
|
transform.position.y - hand.y,
|
|
11158
11213
|
transform.position.z - hand.z
|
|
11159
|
-
);
|
|
11214
|
+
).applyQuaternion(handQuaternion.clone().invert());
|
|
11160
11215
|
record.handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
|
|
11161
11216
|
record.remote = false;
|
|
11162
11217
|
record.tracker.clear();
|
|
@@ -11267,13 +11322,22 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11267
11322
|
}
|
|
11268
11323
|
}
|
|
11269
11324
|
if (nearestId !== null) {
|
|
11270
|
-
return {
|
|
11325
|
+
return {
|
|
11326
|
+
entityId: nearestId,
|
|
11327
|
+
viaDistance: false,
|
|
11328
|
+
distance: nearestDistance
|
|
11329
|
+
};
|
|
11271
11330
|
}
|
|
11272
|
-
const
|
|
11273
|
-
if (
|
|
11331
|
+
const hit = this.raycast(input, interactables);
|
|
11332
|
+
if (hit === null) return null;
|
|
11333
|
+
const hitId = hit.entityId;
|
|
11274
11334
|
const interactable = world.getComponent(hitId, XRInteractableComponent);
|
|
11275
11335
|
const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
|
|
11276
|
-
return {
|
|
11336
|
+
return {
|
|
11337
|
+
entityId: hitId,
|
|
11338
|
+
viaDistance: viaDistanceAllowed,
|
|
11339
|
+
distance: hit.distance
|
|
11340
|
+
};
|
|
11277
11341
|
}
|
|
11278
11342
|
collectInteractables(world) {
|
|
11279
11343
|
const out = [];
|
|
@@ -11304,7 +11368,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11304
11368
|
nearestId = entry.entityId;
|
|
11305
11369
|
}
|
|
11306
11370
|
}
|
|
11307
|
-
return nearestId;
|
|
11371
|
+
return nearestId === null ? null : { entityId: nearestId, distance: nearestDistance };
|
|
11308
11372
|
}
|
|
11309
11373
|
/**
|
|
11310
11374
|
* Pick the authored hold this hand should take.
|
|
@@ -11379,7 +11443,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11379
11443
|
transform.position.x - hand.x,
|
|
11380
11444
|
transform.position.y - hand.y,
|
|
11381
11445
|
transform.position.z - hand.z
|
|
11382
|
-
);
|
|
11446
|
+
).applyQuaternion(handQuaternion.clone().invert());
|
|
11383
11447
|
}
|
|
11384
11448
|
const physics = world.getComponent(entityId, PhysicsComponent);
|
|
11385
11449
|
let restoreBodyType = null;
|
|
@@ -11514,9 +11578,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11514
11578
|
transform.position.z = hand.z - anchor.z;
|
|
11515
11579
|
writeEuler(transform.rotation, objectQuaternion);
|
|
11516
11580
|
} else {
|
|
11517
|
-
|
|
11518
|
-
transform.position.
|
|
11519
|
-
transform.position.
|
|
11581
|
+
const carried = held.offset.clone().applyQuaternion(handQuaternion);
|
|
11582
|
+
transform.position.x = hand.x + carried.x;
|
|
11583
|
+
transform.position.y = hand.y + carried.y;
|
|
11584
|
+
transform.position.z = hand.z + carried.z;
|
|
11520
11585
|
writeEuler(transform.rotation, objectQuaternion);
|
|
11521
11586
|
}
|
|
11522
11587
|
held.tracker.record(
|
|
@@ -11569,19 +11634,14 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11569
11634
|
const physics = world.getComponent(held.entityId, PhysicsComponent);
|
|
11570
11635
|
if (held.restoreBodyType !== null && physics) {
|
|
11571
11636
|
physics.values.rigidbody.type = held.restoreBodyType;
|
|
11572
|
-
const body = physics.rigidBody;
|
|
11573
11637
|
const dynamic = held.restoreBodyType === "dynamic";
|
|
11574
|
-
if (
|
|
11638
|
+
if (dynamic && interactable?.throwOnRelease) {
|
|
11575
11639
|
const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
const spin = held.angularVelocity;
|
|
11582
|
-
body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
|
|
11583
|
-
} catch {
|
|
11584
|
-
}
|
|
11640
|
+
const spin = held.angularVelocity;
|
|
11641
|
+
setPhysicsLiveRef(physics, "pendingVelocity", {
|
|
11642
|
+
linear: { x: velocity.x, y: velocity.y, z: velocity.z },
|
|
11643
|
+
angular: { x: spin.x, y: spin.y, z: spin.z }
|
|
11644
|
+
});
|
|
11585
11645
|
}
|
|
11586
11646
|
}
|
|
11587
11647
|
held.tracker.clear();
|
|
@@ -11629,10 +11689,41 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
11629
11689
|
getHoveredEntityId() {
|
|
11630
11690
|
return this.hoveredEntityId;
|
|
11631
11691
|
}
|
|
11692
|
+
/**
|
|
11693
|
+
* What this hand is pointing at, with the distance to it.
|
|
11694
|
+
*
|
|
11695
|
+
* The distance is what lets the pointer ray stop at the surface instead of
|
|
11696
|
+
* passing through the object.
|
|
11697
|
+
*/
|
|
11698
|
+
getHoverFor(handedness) {
|
|
11699
|
+
for (const [input, hover] of this.hovers) {
|
|
11700
|
+
if (input.handedness === handedness) return hover;
|
|
11701
|
+
}
|
|
11702
|
+
return null;
|
|
11703
|
+
}
|
|
11704
|
+
/** Every entity under a pointer this frame — drives hover feedback. */
|
|
11705
|
+
getHoveredEntityIds() {
|
|
11706
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11707
|
+
for (const hover of this.hovers.values()) ids.add(hover.entityId);
|
|
11708
|
+
return ids;
|
|
11709
|
+
}
|
|
11710
|
+
/** Entities currently held, so hover feedback can skip them. */
|
|
11711
|
+
getHeldEntityIds() {
|
|
11712
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11713
|
+
for (const record of this.grabs.values()) ids.add(record.entityId);
|
|
11714
|
+
return ids;
|
|
11715
|
+
}
|
|
11632
11716
|
/** True when any source is holding something. */
|
|
11633
11717
|
isGrabbing() {
|
|
11634
11718
|
return this.grabs.size > 0;
|
|
11635
11719
|
}
|
|
11720
|
+
/** True when THIS hand is holding something. */
|
|
11721
|
+
isGrabbingWith(handedness) {
|
|
11722
|
+
for (const input of this.grabs.keys()) {
|
|
11723
|
+
if (input.handedness === handedness) return true;
|
|
11724
|
+
}
|
|
11725
|
+
return false;
|
|
11726
|
+
}
|
|
11636
11727
|
/**
|
|
11637
11728
|
* How an entity is currently held.
|
|
11638
11729
|
*
|
|
@@ -12189,6 +12280,10 @@ function applyHandMaterial(root, material) {
|
|
|
12189
12280
|
});
|
|
12190
12281
|
}
|
|
12191
12282
|
__name(applyHandMaterial, "applyHandMaterial");
|
|
12283
|
+
|
|
12284
|
+
// core/systems/XRControllerVisualSystem.ts
|
|
12285
|
+
var RAY_LENGTH = 5;
|
|
12286
|
+
var RAY_RADIUS = 3e-3;
|
|
12192
12287
|
var SLOTS = [
|
|
12193
12288
|
{ index: 0, side: "left" },
|
|
12194
12289
|
{ index: 1, side: "right" }
|
|
@@ -12198,6 +12293,8 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
12198
12293
|
this.controllerModelFactory = new XRControllerModelFactory_js.XRControllerModelFactory();
|
|
12199
12294
|
this.handModelFactory = new XRHandModelFactory_js.XRHandModelFactory();
|
|
12200
12295
|
this.group = new THREE51__namespace.Group();
|
|
12296
|
+
/** Reused so a per-frame colour compare allocates nothing. */
|
|
12297
|
+
this.rayColorScratch = new THREE51__namespace.Color();
|
|
12201
12298
|
this.slots = /* @__PURE__ */ new Map();
|
|
12202
12299
|
this.attachedTo = null;
|
|
12203
12300
|
/** Last applied preset signature, so materials rebuild only on real change. */
|
|
@@ -12226,7 +12323,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
12226
12323
|
const hasHand = liveKind === null || liveKind === "hand";
|
|
12227
12324
|
visuals.controllerModel.visible = showControllers && hasController;
|
|
12228
12325
|
visuals.handModel.visible = showHands && hasHand;
|
|
12229
|
-
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
12326
|
+
visuals.ray.visible = this.shouldShowRay(interactor, side);
|
|
12327
|
+
this.fitRayToHover(visuals.ray, side);
|
|
12328
|
+
this.applyRayStyle(visuals.ray, interactor);
|
|
12230
12329
|
if (showHands && this.handMaterial) {
|
|
12231
12330
|
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
12232
12331
|
}
|
|
@@ -12302,10 +12401,35 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
12302
12401
|
* off entirely; otherwise it appears on hover/grab so it isn't permanent
|
|
12303
12402
|
* clutter implying the whole scene is clickable.
|
|
12304
12403
|
*/
|
|
12305
|
-
shouldShowRay(interactor) {
|
|
12404
|
+
shouldShowRay(interactor, side) {
|
|
12306
12405
|
if (interactor && !interactor.showRay) return false;
|
|
12307
12406
|
if (!this.interactionSystem) return true;
|
|
12308
|
-
return this.interactionSystem.
|
|
12407
|
+
return this.interactionSystem.getHoverFor(side) !== null || this.interactionSystem.isGrabbingWith(side);
|
|
12408
|
+
}
|
|
12409
|
+
/** Push the author's ray colour and thickness onto the mesh. */
|
|
12410
|
+
applyRayStyle(ray, interactor) {
|
|
12411
|
+
if (!interactor) return;
|
|
12412
|
+
const material = ray.material;
|
|
12413
|
+
if (material?.color && !material.color.equals(this.rayColorScratch.set(interactor.rayColor))) {
|
|
12414
|
+
material.color.copy(this.rayColorScratch);
|
|
12415
|
+
}
|
|
12416
|
+
const radius = Math.max(5e-4, interactor.rayThickness / 2) / RAY_RADIUS;
|
|
12417
|
+
if (ray.scale.x !== radius) {
|
|
12418
|
+
ray.scale.x = radius;
|
|
12419
|
+
ray.scale.y = radius;
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
/**
|
|
12423
|
+
* Stop the ray at whatever it is pointing at.
|
|
12424
|
+
*
|
|
12425
|
+
* A fixed-length ray drawn with depth testing off punches straight through the
|
|
12426
|
+
* object, so the user cannot tell where the pointer actually lands. Scaling it
|
|
12427
|
+
* to the hit distance turns it into a pointer that meets the surface.
|
|
12428
|
+
*/
|
|
12429
|
+
fitRayToHover(ray, side) {
|
|
12430
|
+
const hover = this.interactionSystem?.getHoverFor(side) ?? null;
|
|
12431
|
+
const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
|
|
12432
|
+
ray.scale.z = length;
|
|
12309
12433
|
}
|
|
12310
12434
|
/** Lazily build per-slot visuals; the device reports profiles late. */
|
|
12311
12435
|
ensureSlots(world) {
|
|
@@ -12345,22 +12469,32 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
12345
12469
|
target.add(this.group);
|
|
12346
12470
|
this.attachedTo = target;
|
|
12347
12471
|
}
|
|
12472
|
+
/**
|
|
12473
|
+
* The pointer ray.
|
|
12474
|
+
*
|
|
12475
|
+
* A cylinder rather than a `THREE.Line`, because WebGL ignores `linewidth` —
|
|
12476
|
+
* every line renders one pixel wide regardless, which is nearly invisible on a
|
|
12477
|
+
* headset. A thin mesh is the only way to get a ray with real thickness
|
|
12478
|
+
* without pulling in a line-rendering extension.
|
|
12479
|
+
*/
|
|
12348
12480
|
createRay() {
|
|
12349
|
-
const geometry = new THREE51__namespace.
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
const material = new THREE51__namespace.LineBasicMaterial({
|
|
12481
|
+
const geometry = new THREE51__namespace.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
|
|
12482
|
+
geometry.rotateX(-Math.PI / 2);
|
|
12483
|
+
geometry.translate(0, 0, -0.5);
|
|
12484
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12354
12485
|
color: "#4da3ff",
|
|
12355
12486
|
transparent: true,
|
|
12356
|
-
opacity: 0.
|
|
12357
|
-
depthTest: false
|
|
12487
|
+
opacity: 0.75,
|
|
12488
|
+
depthTest: false,
|
|
12489
|
+
depthWrite: false
|
|
12358
12490
|
});
|
|
12359
|
-
const
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12491
|
+
const mesh = new THREE51__namespace.Mesh(geometry, material);
|
|
12492
|
+
mesh.name = "XRPointerRay";
|
|
12493
|
+
mesh.renderOrder = 999;
|
|
12494
|
+
mesh.visible = false;
|
|
12495
|
+
mesh.raycast = () => {
|
|
12496
|
+
};
|
|
12497
|
+
return mesh;
|
|
12364
12498
|
}
|
|
12365
12499
|
dispose() {
|
|
12366
12500
|
this.group.removeFromParent();
|
|
@@ -13011,6 +13145,152 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
|
|
|
13011
13145
|
};
|
|
13012
13146
|
__name(_XRHandPoseSystem, "XRHandPoseSystem");
|
|
13013
13147
|
var XRHandPoseSystem = _XRHandPoseSystem;
|
|
13148
|
+
var XR_HOVER_SHELL_FLAG = "__xrHoverShell";
|
|
13149
|
+
var SHELL_INFLATE = 1.04;
|
|
13150
|
+
var SCALE_SMOOTH_SECONDS = 0.09;
|
|
13151
|
+
var _XRHoverVisualSystem = class _XRHoverVisualSystem {
|
|
13152
|
+
constructor(interactionSystem) {
|
|
13153
|
+
this.records = /* @__PURE__ */ new Map();
|
|
13154
|
+
this.interactionSystem = interactionSystem;
|
|
13155
|
+
}
|
|
13156
|
+
update(world, deltaTime) {
|
|
13157
|
+
if (!world.isXrPresenting()) {
|
|
13158
|
+
this.clearAll(world);
|
|
13159
|
+
return;
|
|
13160
|
+
}
|
|
13161
|
+
const hovered = this.interactionSystem.getHoveredEntityIds();
|
|
13162
|
+
const held = this.interactionSystem.getHeldEntityIds();
|
|
13163
|
+
for (const entityId of hovered) {
|
|
13164
|
+
if (held.has(entityId)) continue;
|
|
13165
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
13166
|
+
if (!interactable?.enabled) continue;
|
|
13167
|
+
if (interactable.hoverBehavior === "none") continue;
|
|
13168
|
+
this.begin(world, entityId, interactable);
|
|
13169
|
+
}
|
|
13170
|
+
for (const [entityId, record] of this.records) {
|
|
13171
|
+
if (!hovered.has(entityId) || held.has(entityId)) {
|
|
13172
|
+
record.active = false;
|
|
13173
|
+
}
|
|
13174
|
+
}
|
|
13175
|
+
this.animate(world, deltaTime);
|
|
13176
|
+
}
|
|
13177
|
+
begin(world, entityId, interactable) {
|
|
13178
|
+
const existing = this.records.get(entityId);
|
|
13179
|
+
if (existing) {
|
|
13180
|
+
existing.active = true;
|
|
13181
|
+
return;
|
|
13182
|
+
}
|
|
13183
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
13184
|
+
if (!transform) return;
|
|
13185
|
+
const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
|
|
13186
|
+
this.records.set(entityId, {
|
|
13187
|
+
shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
|
|
13188
|
+
baseScale: new THREE51__namespace.Vector3(
|
|
13189
|
+
transform.scale.x,
|
|
13190
|
+
transform.scale.y,
|
|
13191
|
+
transform.scale.z
|
|
13192
|
+
),
|
|
13193
|
+
progress: 0,
|
|
13194
|
+
active: true
|
|
13195
|
+
});
|
|
13196
|
+
}
|
|
13197
|
+
/** Build an inflated back-face copy of the object's meshes. */
|
|
13198
|
+
addShell(world, entityId, interactable) {
|
|
13199
|
+
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
13200
|
+
if (!object) return null;
|
|
13201
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
13202
|
+
color: new THREE51__namespace.Color(interactable.hoverColor),
|
|
13203
|
+
// Back faces only, so the shell shows just outside the silhouette instead
|
|
13204
|
+
// of covering the object.
|
|
13205
|
+
side: THREE51__namespace.BackSide,
|
|
13206
|
+
transparent: true,
|
|
13207
|
+
opacity: 0.85,
|
|
13208
|
+
depthWrite: false
|
|
13209
|
+
});
|
|
13210
|
+
const group = new THREE51__namespace.Group();
|
|
13211
|
+
group.name = "XRHoverShell";
|
|
13212
|
+
group.userData[XR_HOVER_SHELL_FLAG] = true;
|
|
13213
|
+
group.renderOrder = 998;
|
|
13214
|
+
object.traverse((child) => {
|
|
13215
|
+
if (!(child instanceof THREE51__namespace.Mesh) || !child.geometry) return;
|
|
13216
|
+
if (child.userData[XR_HOVER_SHELL_FLAG]) return;
|
|
13217
|
+
const clone3 = new THREE51__namespace.Mesh(child.geometry, material);
|
|
13218
|
+
child.updateWorldMatrix(true, false);
|
|
13219
|
+
object.updateWorldMatrix(true, false);
|
|
13220
|
+
clone3.matrixAutoUpdate = false;
|
|
13221
|
+
clone3.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE51__namespace.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
|
|
13222
|
+
clone3.userData[XR_HOVER_SHELL_FLAG] = true;
|
|
13223
|
+
clone3.raycast = () => {
|
|
13224
|
+
};
|
|
13225
|
+
group.add(clone3);
|
|
13226
|
+
});
|
|
13227
|
+
if (group.children.length === 0) {
|
|
13228
|
+
material.dispose();
|
|
13229
|
+
return null;
|
|
13230
|
+
}
|
|
13231
|
+
object.add(group);
|
|
13232
|
+
return group;
|
|
13233
|
+
}
|
|
13234
|
+
animate(world, deltaTime) {
|
|
13235
|
+
const step = 1 - Math.exp(-Math.max(0, deltaTime) / SCALE_SMOOTH_SECONDS);
|
|
13236
|
+
for (const [entityId, record] of Array.from(this.records)) {
|
|
13237
|
+
const interactable = world.getComponent(entityId, XRInteractableComponent);
|
|
13238
|
+
const target = record.active ? 1 : 0;
|
|
13239
|
+
record.progress += (target - record.progress) * step;
|
|
13240
|
+
const wantsScale = interactable?.hoverBehavior === "scale" || interactable?.hoverBehavior === "both";
|
|
13241
|
+
if (wantsScale) {
|
|
13242
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
13243
|
+
if (transform) {
|
|
13244
|
+
const factor = 1 + (interactable.hoverScale - 1) * Math.min(1, record.progress);
|
|
13245
|
+
transform.scale.x = record.baseScale.x * factor;
|
|
13246
|
+
transform.scale.y = record.baseScale.y * factor;
|
|
13247
|
+
transform.scale.z = record.baseScale.z * factor;
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
if (!record.active && record.progress < 0.01) {
|
|
13251
|
+
this.finish(world, entityId, record);
|
|
13252
|
+
}
|
|
13253
|
+
}
|
|
13254
|
+
}
|
|
13255
|
+
/** Remove the effect and restore the authored scale exactly. */
|
|
13256
|
+
finish(world, entityId, record) {
|
|
13257
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
13258
|
+
if (transform) {
|
|
13259
|
+
transform.scale.x = record.baseScale.x;
|
|
13260
|
+
transform.scale.y = record.baseScale.y;
|
|
13261
|
+
transform.scale.z = record.baseScale.z;
|
|
13262
|
+
}
|
|
13263
|
+
this.disposeShell(record.shell);
|
|
13264
|
+
this.records.delete(entityId);
|
|
13265
|
+
}
|
|
13266
|
+
disposeShell(shell) {
|
|
13267
|
+
if (!shell) return;
|
|
13268
|
+
shell.parent?.remove(shell);
|
|
13269
|
+
shell.traverse((child) => {
|
|
13270
|
+
if (child instanceof THREE51__namespace.Mesh) {
|
|
13271
|
+
const material = child.material;
|
|
13272
|
+
if (Array.isArray(material)) {
|
|
13273
|
+
for (const entry of material) entry.dispose();
|
|
13274
|
+
} else {
|
|
13275
|
+
material?.dispose();
|
|
13276
|
+
}
|
|
13277
|
+
}
|
|
13278
|
+
});
|
|
13279
|
+
}
|
|
13280
|
+
clearAll(world) {
|
|
13281
|
+
for (const [entityId, record] of Array.from(this.records)) {
|
|
13282
|
+
this.finish(world, entityId, record);
|
|
13283
|
+
}
|
|
13284
|
+
}
|
|
13285
|
+
dispose() {
|
|
13286
|
+
for (const record of this.records.values()) {
|
|
13287
|
+
this.disposeShell(record.shell);
|
|
13288
|
+
}
|
|
13289
|
+
this.records.clear();
|
|
13290
|
+
}
|
|
13291
|
+
};
|
|
13292
|
+
__name(_XRHoverVisualSystem, "XRHoverVisualSystem");
|
|
13293
|
+
var XRHoverVisualSystem = _XRHoverVisualSystem;
|
|
13014
13294
|
|
|
13015
13295
|
// core/World.ts
|
|
13016
13296
|
var _World = class _World {
|
|
@@ -14443,12 +14723,14 @@ exports.VideoSystem = VideoSystem;
|
|
|
14443
14723
|
exports.World = World;
|
|
14444
14724
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
14445
14725
|
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
14726
|
+
exports.XRHoverVisualSystem = XRHoverVisualSystem;
|
|
14446
14727
|
exports.XRInputSystem = XRInputSystem;
|
|
14447
14728
|
exports.XRInteractionSystem = XRInteractionSystem;
|
|
14448
14729
|
exports.XRLocomotionSystem = XRLocomotionSystem;
|
|
14449
14730
|
exports.XRSessionSystem = XRSessionSystem;
|
|
14450
14731
|
exports.XRTeleportVisualSystem = XRTeleportVisualSystem;
|
|
14451
14732
|
exports.XR_FINGERS = XR_FINGERS;
|
|
14733
|
+
exports.XR_HOVER_SHELL_FLAG = XR_HOVER_SHELL_FLAG;
|
|
14452
14734
|
exports.angularVelocityFrom = angularVelocityFrom;
|
|
14453
14735
|
exports.applyHandMaterial = applyHandMaterial;
|
|
14454
14736
|
exports.applyPrefabToSnapshotInstances = applyPrefabToSnapshotInstances;
|