@relu-interactives/spatial-ecs 0.3.10 → 0.3.11

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.
Files changed (38) hide show
  1. package/dist/{XRHandPose-CjacJ_16.d.ts → XRHandPose-B6I4y1lj.d.ts} +48 -14
  2. package/dist/{XRHandPose-hAtj9wm-.d.cts → XRHandPose-C-KJeYcJ.d.cts} +48 -14
  3. package/dist/{chunk-5UVBEAGW.js → chunk-FSHRPY65.js} +4 -4
  4. package/dist/{chunk-5UVBEAGW.js.map → chunk-FSHRPY65.js.map} +1 -1
  5. package/dist/{chunk-O2HSIZFC.js → chunk-KLU2M43M.js} +270 -33
  6. package/dist/chunk-KLU2M43M.js.map +1 -0
  7. package/dist/{chunk-ELSZ5U3F.js → chunk-TXEGYR76.js} +5 -4
  8. package/dist/chunk-TXEGYR76.js.map +1 -0
  9. package/dist/{chunk-NJOO7NO6.js → chunk-Y3AC3VZ2.js} +3 -3
  10. package/dist/{chunk-NJOO7NO6.js.map → chunk-Y3AC3VZ2.js.map} +1 -1
  11. package/dist/components/index.cjs +3 -2
  12. package/dist/components/index.cjs.map +1 -1
  13. package/dist/components/index.d.cts +2 -2
  14. package/dist/components/index.d.ts +2 -2
  15. package/dist/components/index.js +1 -1
  16. package/dist/docs/guide/vr-and-hand-poses.md +7 -3
  17. package/dist/factories/index.cjs +3 -2
  18. package/dist/factories/index.cjs.map +1 -1
  19. package/dist/factories/index.d.cts +1 -1
  20. package/dist/factories/index.d.ts +1 -1
  21. package/dist/factories/index.js +3 -3
  22. package/dist/{index-B-KhN8Ox.d.ts → index-Bi_wIo1o.d.ts} +56 -1
  23. package/dist/{index-B7JDsx4a.d.ts → index-CEZp7qLL.d.ts} +2 -3
  24. package/dist/{index-B9JqYr3Q.d.cts → index-DPRnnSI-.d.cts} +2 -3
  25. package/dist/{index-DU-CF-3m.d.cts → index-jlgDou9W.d.cts} +56 -1
  26. package/dist/index.cjs +270 -30
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +5 -5
  29. package/dist/index.d.ts +5 -5
  30. package/dist/index.js +8 -8
  31. package/dist/systems/index.cjs +270 -30
  32. package/dist/systems/index.cjs.map +1 -1
  33. package/dist/systems/index.d.cts +1 -1
  34. package/dist/systems/index.d.ts +1 -1
  35. package/dist/systems/index.js +3 -3
  36. package/package.json +1 -1
  37. package/dist/chunk-ELSZ5U3F.js.map +0 -1
  38. package/dist/chunk-O2HSIZFC.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-NJOO7NO6.js';
2
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, createAttachPoint, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-ELSZ5U3F.js';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-Y3AC3VZ2.js';
2
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, createAttachPoint, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-TXEGYR76.js';
3
3
  import { __name } from './chunk-OY7ZODZP.js';
4
4
  import * as THREE19 from 'three';
5
5
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
@@ -901,6 +901,20 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
901
901
  __name(_PostprocessingSyncSystem, "PostprocessingSyncSystem");
902
902
  var PostprocessingSyncSystem = _PostprocessingSyncSystem;
903
903
  var MIN_SIZE = 1e-4;
904
+ function toBodyType(RAPIER, bodyType) {
905
+ switch (bodyType) {
906
+ case "fixed":
907
+ return RAPIER.RigidBodyType.Fixed;
908
+ case "kinematicPosition":
909
+ return RAPIER.RigidBodyType.KinematicPositionBased;
910
+ case "kinematicVelocity":
911
+ return RAPIER.RigidBodyType.KinematicVelocityBased;
912
+ case "dynamic":
913
+ default:
914
+ return RAPIER.RigidBodyType.Dynamic;
915
+ }
916
+ }
917
+ __name(toBodyType, "toBodyType");
904
918
  function createRigidBodyDesc(RAPIER, bodyType) {
905
919
  if (bodyType === "fixed") {
906
920
  return RAPIER.RigidBodyDesc.fixed();
@@ -1199,7 +1213,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1199
1213
  setPhysicsLiveRef(physics, "collider", null);
1200
1214
  continue;
1201
1215
  }
1202
- const rigidBodySignature = JSON.stringify(physics.values.rigidbody);
1216
+ const { type: _desiredBodyType, ...rigidBodyRebuildValues } = physics.values.rigidbody;
1217
+ const rigidBodySignature = JSON.stringify(rigidBodyRebuildValues);
1203
1218
  const previousRigidBodySignature = this.rigidBodySignatures.get(entityId);
1204
1219
  const hasRigidBody = this.rigidBodyHandles.has(entityId);
1205
1220
  const shouldRecreateBody = !hasRigidBody || previousRigidBodySignature !== rigidBodySignature;
@@ -1254,6 +1269,25 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1254
1269
  continue;
1255
1270
  }
1256
1271
  setPhysicsLiveRef(physics, "rigidBody", body);
1272
+ const desiredBodyType = this.isEditorMode ? "fixed" : physics.values.rigidbody.type;
1273
+ const desiredRapierType = toBodyType(this.rapier, desiredBodyType);
1274
+ if (body.bodyType() !== desiredRapierType) {
1275
+ body.setBodyType(desiredRapierType, true);
1276
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
1277
+ body.setAdditionalMass(physics.values.rigidbody.mass, true);
1278
+ }
1279
+ }
1280
+ const pending = physics.pendingVelocity;
1281
+ if (pending) {
1282
+ setPhysicsLiveRef(physics, "pendingVelocity", null);
1283
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
1284
+ try {
1285
+ body.setLinvel(pending.linear, true);
1286
+ body.setAngvel(pending.angular, true);
1287
+ } catch {
1288
+ }
1289
+ }
1290
+ }
1257
1291
  if (this.isEditorMode || physics.values.rigidbody.type !== "dynamic") {
1258
1292
  syncBodyFromTransform(body, transform);
1259
1293
  }
@@ -2981,9 +3015,6 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2981
3015
  if (typeof sourceInteractable.pointable === "boolean") {
2982
3016
  interactable.pointable = sourceInteractable.pointable;
2983
3017
  }
2984
- if (typeof sourceInteractable.grabMode === "string") {
2985
- interactable.grabMode = sourceInteractable.grabMode;
2986
- }
2987
3018
  if (typeof sourceInteractable.grabReach === "string") {
2988
3019
  interactable.grabReach = sourceInteractable.grabReach;
2989
3020
  }
@@ -3042,8 +3073,16 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
3042
3073
  z: typeof grabRot.z === "number" ? grabRot.z : 0
3043
3074
  };
3044
3075
  }
3045
- if (typeof sourceInteractable.highlightOnHover === "boolean") {
3046
- interactable.highlightOnHover = sourceInteractable.highlightOnHover;
3076
+ if (typeof sourceInteractable.hoverBehavior === "string") {
3077
+ interactable.hoverBehavior = sourceInteractable.hoverBehavior;
3078
+ } else if (sourceInteractable.highlightOnHover === false) {
3079
+ interactable.hoverBehavior = "none";
3080
+ }
3081
+ if (typeof sourceInteractable.hoverColor === "string") {
3082
+ interactable.hoverColor = sourceInteractable.hoverColor;
3083
+ }
3084
+ if (typeof sourceInteractable.hoverScale === "number") {
3085
+ interactable.hoverScale = sourceInteractable.hoverScale;
3047
3086
  }
3048
3087
  }
3049
3088
  }
@@ -6537,6 +6576,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6537
6576
  this.raycaster = new THREE19.Raycaster();
6538
6577
  this.grabs = /* @__PURE__ */ new Map();
6539
6578
  this.hoveredEntityId = null;
6579
+ /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
6580
+ this.hovers = /* @__PURE__ */ new Map();
6540
6581
  /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
6541
6582
  this.elapsed = 0;
6542
6583
  this.inputSystem = inputSystem;
@@ -6550,6 +6591,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6550
6591
  const inputs = this.inputSystem.getInputs();
6551
6592
  const interactables = this.collectInteractables(world);
6552
6593
  this.hoveredEntityId = null;
6594
+ this.hovers.clear();
6553
6595
  for (const input of inputs) {
6554
6596
  if (!input.connected) {
6555
6597
  this.release(world, input);
@@ -6567,6 +6609,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6567
6609
  if (this.hoveredEntityId === null) {
6568
6610
  this.hoveredEntityId = candidate.entityId;
6569
6611
  }
6612
+ this.hovers.set(input, {
6613
+ entityId: candidate.entityId,
6614
+ distance: candidate.distance
6615
+ });
6570
6616
  const interactable = world.getComponent(
6571
6617
  candidate.entityId,
6572
6618
  XRInteractableComponent
@@ -6639,7 +6685,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6639
6685
  transform.position.x - hand.x,
6640
6686
  transform.position.y - hand.y,
6641
6687
  transform.position.z - hand.z
6642
- );
6688
+ ).applyQuaternion(handQuaternion.clone().invert());
6643
6689
  record.handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
6644
6690
  record.remote = false;
6645
6691
  record.tracker.clear();
@@ -6750,13 +6796,22 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6750
6796
  }
6751
6797
  }
6752
6798
  if (nearestId !== null) {
6753
- return { entityId: nearestId, viaDistance: false };
6799
+ return {
6800
+ entityId: nearestId,
6801
+ viaDistance: false,
6802
+ distance: nearestDistance
6803
+ };
6754
6804
  }
6755
- const hitId = this.raycast(input, interactables);
6756
- if (hitId === null) return null;
6805
+ const hit = this.raycast(input, interactables);
6806
+ if (hit === null) return null;
6807
+ const hitId = hit.entityId;
6757
6808
  const interactable = world.getComponent(hitId, XRInteractableComponent);
6758
6809
  const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
6759
- return { entityId: hitId, viaDistance: viaDistanceAllowed };
6810
+ return {
6811
+ entityId: hitId,
6812
+ viaDistance: viaDistanceAllowed,
6813
+ distance: hit.distance
6814
+ };
6760
6815
  }
6761
6816
  collectInteractables(world) {
6762
6817
  const out = [];
@@ -6787,7 +6842,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6787
6842
  nearestId = entry.entityId;
6788
6843
  }
6789
6844
  }
6790
- return nearestId;
6845
+ return nearestId === null ? null : { entityId: nearestId, distance: nearestDistance };
6791
6846
  }
6792
6847
  /**
6793
6848
  * Pick the authored hold this hand should take.
@@ -6862,7 +6917,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6862
6917
  transform.position.x - hand.x,
6863
6918
  transform.position.y - hand.y,
6864
6919
  transform.position.z - hand.z
6865
- );
6920
+ ).applyQuaternion(handQuaternion.clone().invert());
6866
6921
  }
6867
6922
  const physics = world.getComponent(entityId, PhysicsComponent);
6868
6923
  let restoreBodyType = null;
@@ -6997,9 +7052,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6997
7052
  transform.position.z = hand.z - anchor.z;
6998
7053
  writeEuler(transform.rotation, objectQuaternion);
6999
7054
  } else {
7000
- transform.position.x = hand.x + held.offset.x;
7001
- transform.position.y = hand.y + held.offset.y;
7002
- transform.position.z = hand.z + held.offset.z;
7055
+ const carried = held.offset.clone().applyQuaternion(handQuaternion);
7056
+ transform.position.x = hand.x + carried.x;
7057
+ transform.position.y = hand.y + carried.y;
7058
+ transform.position.z = hand.z + carried.z;
7003
7059
  writeEuler(transform.rotation, objectQuaternion);
7004
7060
  }
7005
7061
  held.tracker.record(
@@ -7052,19 +7108,14 @@ var _XRInteractionSystem = class _XRInteractionSystem {
7052
7108
  const physics = world.getComponent(held.entityId, PhysicsComponent);
7053
7109
  if (held.restoreBodyType !== null && physics) {
7054
7110
  physics.values.rigidbody.type = held.restoreBodyType;
7055
- const body = physics.rigidBody;
7056
7111
  const dynamic = held.restoreBodyType === "dynamic";
7057
- if (body && dynamic && interactable?.throwOnRelease) {
7112
+ if (dynamic && interactable?.throwOnRelease) {
7058
7113
  const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
7059
- try {
7060
- body.setLinvel(
7061
- { x: velocity.x, y: velocity.y, z: velocity.z },
7062
- true
7063
- );
7064
- const spin = held.angularVelocity;
7065
- body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
7066
- } catch {
7067
- }
7114
+ const spin = held.angularVelocity;
7115
+ setPhysicsLiveRef(physics, "pendingVelocity", {
7116
+ linear: { x: velocity.x, y: velocity.y, z: velocity.z },
7117
+ angular: { x: spin.x, y: spin.y, z: spin.z }
7118
+ });
7068
7119
  }
7069
7120
  }
7070
7121
  held.tracker.clear();
@@ -7112,6 +7163,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
7112
7163
  getHoveredEntityId() {
7113
7164
  return this.hoveredEntityId;
7114
7165
  }
7166
+ /**
7167
+ * What this hand is pointing at, with the distance to it.
7168
+ *
7169
+ * The distance is what lets the pointer ray stop at the surface instead of
7170
+ * passing through the object.
7171
+ */
7172
+ getHoverFor(handedness) {
7173
+ for (const [input, hover] of this.hovers) {
7174
+ if (input.handedness === handedness) return hover;
7175
+ }
7176
+ return null;
7177
+ }
7178
+ /** Every entity under a pointer this frame — drives hover feedback. */
7179
+ getHoveredEntityIds() {
7180
+ const ids = /* @__PURE__ */ new Set();
7181
+ for (const hover of this.hovers.values()) ids.add(hover.entityId);
7182
+ return ids;
7183
+ }
7184
+ /** Entities currently held, so hover feedback can skip them. */
7185
+ getHeldEntityIds() {
7186
+ const ids = /* @__PURE__ */ new Set();
7187
+ for (const record of this.grabs.values()) ids.add(record.entityId);
7188
+ return ids;
7189
+ }
7115
7190
  /** True when any source is holding something. */
7116
7191
  isGrabbing() {
7117
7192
  return this.grabs.size > 0;
@@ -7672,6 +7747,9 @@ function applyHandMaterial(root, material) {
7672
7747
  });
7673
7748
  }
7674
7749
  __name(applyHandMaterial, "applyHandMaterial");
7750
+
7751
+ // core/systems/XRControllerVisualSystem.ts
7752
+ var RAY_LENGTH = 5;
7675
7753
  var SLOTS = [
7676
7754
  { index: 0, side: "left" },
7677
7755
  { index: 1, side: "right" }
@@ -7710,6 +7788,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7710
7788
  visuals.controllerModel.visible = showControllers && hasController;
7711
7789
  visuals.handModel.visible = showHands && hasHand;
7712
7790
  visuals.ray.visible = this.shouldShowRay(interactor);
7791
+ this.fitRayToHover(visuals.ray, side);
7713
7792
  if (showHands && this.handMaterial) {
7714
7793
  applyHandMaterial(visuals.handModel, this.handMaterial);
7715
7794
  }
@@ -7790,6 +7869,18 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7790
7869
  if (!this.interactionSystem) return true;
7791
7870
  return this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
7792
7871
  }
7872
+ /**
7873
+ * Stop the ray at whatever it is pointing at.
7874
+ *
7875
+ * A fixed-length ray drawn with depth testing off punches straight through the
7876
+ * object, so the user cannot tell where the pointer actually lands. Scaling it
7877
+ * to the hit distance turns it into a pointer that meets the surface.
7878
+ */
7879
+ fitRayToHover(ray, side) {
7880
+ const hover = this.interactionSystem?.getHoverFor(side) ?? null;
7881
+ const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
7882
+ ray.scale.z = length / RAY_LENGTH;
7883
+ }
7793
7884
  /** Lazily build per-slot visuals; the device reports profiles late. */
7794
7885
  ensureSlots(world) {
7795
7886
  const renderer = world.getRenderer();
@@ -7831,7 +7922,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7831
7922
  createRay() {
7832
7923
  const geometry = new THREE19.BufferGeometry().setFromPoints([
7833
7924
  new THREE19.Vector3(0, 0, 0),
7834
- new THREE19.Vector3(0, 0, -5)
7925
+ new THREE19.Vector3(0, 0, -RAY_LENGTH)
7835
7926
  ]);
7836
7927
  const material = new THREE19.LineBasicMaterial({
7837
7928
  color: "#4da3ff",
@@ -8494,7 +8585,153 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
8494
8585
  };
8495
8586
  __name(_XRHandPoseSystem, "XRHandPoseSystem");
8496
8587
  var XRHandPoseSystem = _XRHandPoseSystem;
8588
+ var XR_HOVER_SHELL_FLAG = "__xrHoverShell";
8589
+ var SHELL_INFLATE = 1.04;
8590
+ var SCALE_SMOOTH_SECONDS = 0.09;
8591
+ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
8592
+ constructor(interactionSystem) {
8593
+ this.records = /* @__PURE__ */ new Map();
8594
+ this.interactionSystem = interactionSystem;
8595
+ }
8596
+ update(world, deltaTime) {
8597
+ if (!world.isXrPresenting()) {
8598
+ this.clearAll(world);
8599
+ return;
8600
+ }
8601
+ const hovered = this.interactionSystem.getHoveredEntityIds();
8602
+ const held = this.interactionSystem.getHeldEntityIds();
8603
+ for (const entityId of hovered) {
8604
+ if (held.has(entityId)) continue;
8605
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8606
+ if (!interactable?.enabled) continue;
8607
+ if (interactable.hoverBehavior === "none") continue;
8608
+ this.begin(world, entityId, interactable);
8609
+ }
8610
+ for (const [entityId, record] of this.records) {
8611
+ if (!hovered.has(entityId) || held.has(entityId)) {
8612
+ record.active = false;
8613
+ }
8614
+ }
8615
+ this.animate(world, deltaTime);
8616
+ }
8617
+ begin(world, entityId, interactable) {
8618
+ const existing = this.records.get(entityId);
8619
+ if (existing) {
8620
+ existing.active = true;
8621
+ return;
8622
+ }
8623
+ const transform = world.getComponent(entityId, TransformComponent);
8624
+ if (!transform) return;
8625
+ const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
8626
+ this.records.set(entityId, {
8627
+ shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
8628
+ baseScale: new THREE19.Vector3(
8629
+ transform.scale.x,
8630
+ transform.scale.y,
8631
+ transform.scale.z
8632
+ ),
8633
+ progress: 0,
8634
+ active: true
8635
+ });
8636
+ }
8637
+ /** Build an inflated back-face copy of the object's meshes. */
8638
+ addShell(world, entityId, interactable) {
8639
+ const object = world.getComponent(entityId, Object3DRef)?.object;
8640
+ if (!object) return null;
8641
+ const material = new THREE19.MeshBasicMaterial({
8642
+ color: new THREE19.Color(interactable.hoverColor),
8643
+ // Back faces only, so the shell shows just outside the silhouette instead
8644
+ // of covering the object.
8645
+ side: THREE19.BackSide,
8646
+ transparent: true,
8647
+ opacity: 0.85,
8648
+ depthWrite: false
8649
+ });
8650
+ const group = new THREE19.Group();
8651
+ group.name = "XRHoverShell";
8652
+ group.userData[XR_HOVER_SHELL_FLAG] = true;
8653
+ group.renderOrder = 998;
8654
+ object.traverse((child) => {
8655
+ if (!(child instanceof THREE19.Mesh) || !child.geometry) return;
8656
+ if (child.userData[XR_HOVER_SHELL_FLAG]) return;
8657
+ const clone2 = new THREE19.Mesh(child.geometry, material);
8658
+ child.updateWorldMatrix(true, false);
8659
+ object.updateWorldMatrix(true, false);
8660
+ clone2.matrixAutoUpdate = false;
8661
+ clone2.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE19.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
8662
+ clone2.userData[XR_HOVER_SHELL_FLAG] = true;
8663
+ clone2.raycast = () => {
8664
+ };
8665
+ group.add(clone2);
8666
+ });
8667
+ if (group.children.length === 0) {
8668
+ material.dispose();
8669
+ return null;
8670
+ }
8671
+ object.add(group);
8672
+ return group;
8673
+ }
8674
+ animate(world, deltaTime) {
8675
+ const step = 1 - Math.exp(-Math.max(0, deltaTime) / SCALE_SMOOTH_SECONDS);
8676
+ for (const [entityId, record] of Array.from(this.records)) {
8677
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8678
+ const target = record.active ? 1 : 0;
8679
+ record.progress += (target - record.progress) * step;
8680
+ const wantsScale = interactable?.hoverBehavior === "scale" || interactable?.hoverBehavior === "both";
8681
+ if (wantsScale) {
8682
+ const transform = world.getComponent(entityId, TransformComponent);
8683
+ if (transform) {
8684
+ const factor = 1 + (interactable.hoverScale - 1) * Math.min(1, record.progress);
8685
+ transform.scale.x = record.baseScale.x * factor;
8686
+ transform.scale.y = record.baseScale.y * factor;
8687
+ transform.scale.z = record.baseScale.z * factor;
8688
+ }
8689
+ }
8690
+ if (!record.active && record.progress < 0.01) {
8691
+ this.finish(world, entityId, record);
8692
+ }
8693
+ }
8694
+ }
8695
+ /** Remove the effect and restore the authored scale exactly. */
8696
+ finish(world, entityId, record) {
8697
+ const transform = world.getComponent(entityId, TransformComponent);
8698
+ if (transform) {
8699
+ transform.scale.x = record.baseScale.x;
8700
+ transform.scale.y = record.baseScale.y;
8701
+ transform.scale.z = record.baseScale.z;
8702
+ }
8703
+ this.disposeShell(record.shell);
8704
+ this.records.delete(entityId);
8705
+ }
8706
+ disposeShell(shell) {
8707
+ if (!shell) return;
8708
+ shell.parent?.remove(shell);
8709
+ shell.traverse((child) => {
8710
+ if (child instanceof THREE19.Mesh) {
8711
+ const material = child.material;
8712
+ if (Array.isArray(material)) {
8713
+ for (const entry of material) entry.dispose();
8714
+ } else {
8715
+ material?.dispose();
8716
+ }
8717
+ }
8718
+ });
8719
+ }
8720
+ clearAll(world) {
8721
+ for (const [entityId, record] of Array.from(this.records)) {
8722
+ this.finish(world, entityId, record);
8723
+ }
8724
+ }
8725
+ dispose() {
8726
+ for (const record of this.records.values()) {
8727
+ this.disposeShell(record.shell);
8728
+ }
8729
+ this.records.clear();
8730
+ }
8731
+ };
8732
+ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
8733
+ var XRHoverVisualSystem = _XRHoverVisualSystem;
8497
8734
 
8498
- export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRHandPoseSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
8499
- //# sourceMappingURL=chunk-O2HSIZFC.js.map
8500
- //# sourceMappingURL=chunk-O2HSIZFC.js.map
8735
+ export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRHandPoseSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
8736
+ //# sourceMappingURL=chunk-KLU2M43M.js.map
8737
+ //# sourceMappingURL=chunk-KLU2M43M.js.map