@relu-interactives/spatial-ecs 0.3.4 → 0.3.5

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.
@@ -429,24 +429,42 @@ declare class XRLocomotionSystem implements EcsSystem {
429
429
  private readonly raycaster;
430
430
  /** Per-source latch so one stick push yields exactly one snap turn. */
431
431
  private readonly turnLatched;
432
- /** Where the pointer currently lands, for the consumer to draw a marker. */
433
- private teleportPoint;
432
+ /** Reused arc buffer — aiming runs every frame. */
433
+ private readonly arcPoints;
434
+ /** Curve up to the landing point, for the visual system. Empty when not aiming. */
435
+ private aimCurve;
436
+ private landingPoint;
437
+ /** True once we've tried to self-heal camera parenting for this session. */
438
+ private parentingChecked;
434
439
  constructor(inputSystem: XRInputSystem);
435
440
  update(world: World, deltaTime: number): void;
441
+ /**
442
+ * Guarantee the render camera is a CHILD of the rig.
443
+ *
444
+ * Locomotion moves the rig, and three composes the head pose against
445
+ * `camera.parent` — so if the camera sits at the scene root instead, moving
446
+ * the rig does nothing at all and the player is frozen in place. Scenes
447
+ * authored before the rig existed (or with the camera re-parented by hand)
448
+ * would otherwise silently fail to move, which is exactly the symptom that is
449
+ * hardest to diagnose from inside a headset. Repairing it via `ParentId` lets
450
+ * `ObjectManagementSystem` reconcile the scene graph normally.
451
+ */
452
+ private ensureCameraUnderRig;
436
453
  /**
437
454
  * Snap turning, not smooth turning — rotating the view continuously is the
438
- * single biggest cause of VR nausea, so the comfort-safe option is the one
455
+ * single biggest cause of VR nausea, so the comfort-safe option is what's
439
456
  * wired to the stick.
440
457
  */
441
458
  private applySnapTurn;
442
459
  private applySmoothMove;
443
460
  private applyTeleport;
461
+ /** Walk the arc segment-by-segment, returning the first surface hit. */
462
+ private raycastArc;
444
463
  private collectTeleportTargets;
445
- /**
446
- * Where the teleport pointer currently lands this frame, or null.
447
- * Consumers use this to render an aim marker.
448
- */
449
- getTeleportPoint(): THREE.Vector3 | null;
464
+ /** Arc points up to the landing spot this frame; empty when not aiming. */
465
+ getAimCurve(): THREE.Vector3[];
466
+ /** Where the arc currently lands, or null when not aiming at a valid area. */
467
+ getLandingPoint(): THREE.Vector3 | null;
450
468
  }
451
469
 
452
470
  /**
@@ -500,8 +518,21 @@ declare class XRControllerVisualSystem implements EcsSystem {
500
518
  private readonly ready;
501
519
  /** How many controller slots to wire. Two covers every current headset. */
502
520
  private readonly slots;
503
- constructor(slots?: number);
521
+ /** Optional — when provided, the aim ray only shows over interactables. */
522
+ private readonly interactionSystem;
523
+ private readonly rays;
524
+ constructor(options?: {
525
+ slots?: number;
526
+ interactionSystem?: XRInteractionSystem;
527
+ });
504
528
  update(world: World): void;
529
+ /**
530
+ * Show the aim ray only when it is meaningful: hovering something
531
+ * interactive, or mid-grab. A permanently-drawn ray is visual noise, and it
532
+ * implies everything is clickable when most of the scene is not. Teleport
533
+ * aiming has its own arc, so the straight ray stays hidden for that.
534
+ */
535
+ private updateRayVisibility;
505
536
  /**
506
537
  * Parent the visuals to the XR rig when there is one, so locomotion moves the
507
538
  * hands with the player. Falls back to the scene root.
@@ -511,4 +542,20 @@ declare class XRControllerVisualSystem implements EcsSystem {
511
542
  dispose(): void;
512
543
  }
513
544
 
514
- export { AnimationSystem as A, CameraSyncSystem as C, EnvironmentSyncSystem as E, InstancedMeshSystem as I, LightSyncSystem as L, MaterialSyncSystem as M, NO_XR_CAPABILITIES as N, ObjectVisibilitySystem as O, PhysicsSyncSystem as P, SCRIPT_OBJECT_ROOT_FLAG as S, VRTeleportAreaSystem as V, type XrCapabilities as X, type XrInputState as a, AudioSystem as b, MeshGeometrySystem as c, type ModuleLoader as d, PostprocessingSyncSystem as e, ScriptBehaviourSystem as f, ScriptObjectSystem as g, type SelectionContext as h, VideoSystem as i, XRControllerVisualSystem as j, XRInputSystem as k, XRInteractionSystem as l, XRLocomotionSystem as m, XRSessionSystem as n, type XrHandedness as o, type XrInputKind as p, readXrCapabilities as r };
545
+ declare class XRTeleportVisualSystem implements EcsSystem {
546
+ private readonly locomotionSystem;
547
+ private readonly group;
548
+ private readonly arc;
549
+ private readonly arcGeometry;
550
+ private readonly marker;
551
+ private attached;
552
+ constructor(locomotionSystem: XRLocomotionSystem);
553
+ update(world: World): void;
554
+ private updateArcGeometry;
555
+ private createRing;
556
+ private createDot;
557
+ private setVisible;
558
+ dispose(): void;
559
+ }
560
+
561
+ export { AnimationSystem as A, CameraSyncSystem as C, EnvironmentSyncSystem as E, InstancedMeshSystem as I, LightSyncSystem as L, MaterialSyncSystem as M, NO_XR_CAPABILITIES as N, ObjectVisibilitySystem as O, PhysicsSyncSystem as P, SCRIPT_OBJECT_ROOT_FLAG as S, VRTeleportAreaSystem as V, type XrCapabilities as X, type XrInputState as a, AudioSystem as b, MeshGeometrySystem as c, type ModuleLoader as d, PostprocessingSyncSystem as e, ScriptBehaviourSystem as f, ScriptObjectSystem as g, type SelectionContext as h, VideoSystem as i, XRControllerVisualSystem as j, XRInputSystem as k, XRInteractionSystem as l, XRLocomotionSystem as m, XRSessionSystem as n, XRTeleportVisualSystem as o, type XrHandedness as p, type XrInputKind as q, readXrCapabilities as r };
@@ -429,24 +429,42 @@ declare class XRLocomotionSystem implements EcsSystem {
429
429
  private readonly raycaster;
430
430
  /** Per-source latch so one stick push yields exactly one snap turn. */
431
431
  private readonly turnLatched;
432
- /** Where the pointer currently lands, for the consumer to draw a marker. */
433
- private teleportPoint;
432
+ /** Reused arc buffer — aiming runs every frame. */
433
+ private readonly arcPoints;
434
+ /** Curve up to the landing point, for the visual system. Empty when not aiming. */
435
+ private aimCurve;
436
+ private landingPoint;
437
+ /** True once we've tried to self-heal camera parenting for this session. */
438
+ private parentingChecked;
434
439
  constructor(inputSystem: XRInputSystem);
435
440
  update(world: World, deltaTime: number): void;
441
+ /**
442
+ * Guarantee the render camera is a CHILD of the rig.
443
+ *
444
+ * Locomotion moves the rig, and three composes the head pose against
445
+ * `camera.parent` — so if the camera sits at the scene root instead, moving
446
+ * the rig does nothing at all and the player is frozen in place. Scenes
447
+ * authored before the rig existed (or with the camera re-parented by hand)
448
+ * would otherwise silently fail to move, which is exactly the symptom that is
449
+ * hardest to diagnose from inside a headset. Repairing it via `ParentId` lets
450
+ * `ObjectManagementSystem` reconcile the scene graph normally.
451
+ */
452
+ private ensureCameraUnderRig;
436
453
  /**
437
454
  * Snap turning, not smooth turning — rotating the view continuously is the
438
- * single biggest cause of VR nausea, so the comfort-safe option is the one
455
+ * single biggest cause of VR nausea, so the comfort-safe option is what's
439
456
  * wired to the stick.
440
457
  */
441
458
  private applySnapTurn;
442
459
  private applySmoothMove;
443
460
  private applyTeleport;
461
+ /** Walk the arc segment-by-segment, returning the first surface hit. */
462
+ private raycastArc;
444
463
  private collectTeleportTargets;
445
- /**
446
- * Where the teleport pointer currently lands this frame, or null.
447
- * Consumers use this to render an aim marker.
448
- */
449
- getTeleportPoint(): THREE.Vector3 | null;
464
+ /** Arc points up to the landing spot this frame; empty when not aiming. */
465
+ getAimCurve(): THREE.Vector3[];
466
+ /** Where the arc currently lands, or null when not aiming at a valid area. */
467
+ getLandingPoint(): THREE.Vector3 | null;
450
468
  }
451
469
 
452
470
  /**
@@ -500,8 +518,21 @@ declare class XRControllerVisualSystem implements EcsSystem {
500
518
  private readonly ready;
501
519
  /** How many controller slots to wire. Two covers every current headset. */
502
520
  private readonly slots;
503
- constructor(slots?: number);
521
+ /** Optional — when provided, the aim ray only shows over interactables. */
522
+ private readonly interactionSystem;
523
+ private readonly rays;
524
+ constructor(options?: {
525
+ slots?: number;
526
+ interactionSystem?: XRInteractionSystem;
527
+ });
504
528
  update(world: World): void;
529
+ /**
530
+ * Show the aim ray only when it is meaningful: hovering something
531
+ * interactive, or mid-grab. A permanently-drawn ray is visual noise, and it
532
+ * implies everything is clickable when most of the scene is not. Teleport
533
+ * aiming has its own arc, so the straight ray stays hidden for that.
534
+ */
535
+ private updateRayVisibility;
505
536
  /**
506
537
  * Parent the visuals to the XR rig when there is one, so locomotion moves the
507
538
  * hands with the player. Falls back to the scene root.
@@ -511,4 +542,20 @@ declare class XRControllerVisualSystem implements EcsSystem {
511
542
  dispose(): void;
512
543
  }
513
544
 
514
- export { AnimationSystem as A, CameraSyncSystem as C, EnvironmentSyncSystem as E, InstancedMeshSystem as I, LightSyncSystem as L, MaterialSyncSystem as M, NO_XR_CAPABILITIES as N, ObjectVisibilitySystem as O, PhysicsSyncSystem as P, SCRIPT_OBJECT_ROOT_FLAG as S, VRTeleportAreaSystem as V, type XrCapabilities as X, type XrInputState as a, AudioSystem as b, MeshGeometrySystem as c, type ModuleLoader as d, PostprocessingSyncSystem as e, ScriptBehaviourSystem as f, ScriptObjectSystem as g, type SelectionContext as h, VideoSystem as i, XRControllerVisualSystem as j, XRInputSystem as k, XRInteractionSystem as l, XRLocomotionSystem as m, XRSessionSystem as n, type XrHandedness as o, type XrInputKind as p, readXrCapabilities as r };
545
+ declare class XRTeleportVisualSystem implements EcsSystem {
546
+ private readonly locomotionSystem;
547
+ private readonly group;
548
+ private readonly arc;
549
+ private readonly arcGeometry;
550
+ private readonly marker;
551
+ private attached;
552
+ constructor(locomotionSystem: XRLocomotionSystem);
553
+ update(world: World): void;
554
+ private updateArcGeometry;
555
+ private createRing;
556
+ private createDot;
557
+ private setVisible;
558
+ dispose(): void;
559
+ }
560
+
561
+ export { AnimationSystem as A, CameraSyncSystem as C, EnvironmentSyncSystem as E, InstancedMeshSystem as I, LightSyncSystem as L, MaterialSyncSystem as M, NO_XR_CAPABILITIES as N, ObjectVisibilitySystem as O, PhysicsSyncSystem as P, SCRIPT_OBJECT_ROOT_FLAG as S, VRTeleportAreaSystem as V, type XrCapabilities as X, type XrInputState as a, AudioSystem as b, MeshGeometrySystem as c, type ModuleLoader as d, PostprocessingSyncSystem as e, ScriptBehaviourSystem as f, ScriptObjectSystem as g, type SelectionContext as h, VideoSystem as i, XRControllerVisualSystem as j, XRInputSystem as k, XRInteractionSystem as l, XRLocomotionSystem as m, XRSessionSystem as n, XRTeleportVisualSystem as o, type XrHandedness as p, type XrInputKind as q, readXrCapabilities as r };
package/dist/index.cjs CHANGED
@@ -10507,6 +10507,45 @@ var _XRInteractionSystem = class _XRInteractionSystem {
10507
10507
  };
10508
10508
  __name(_XRInteractionSystem, "XRInteractionSystem");
10509
10509
  var XRInteractionSystem = _XRInteractionSystem;
10510
+ var ARC_GRAVITY = 9.8;
10511
+ var ARC_SPEED = 7;
10512
+ var ARC_STEPS = 24;
10513
+ var ARC_MAX_TIME = 2;
10514
+ function sampleTeleportArc(origin, direction, out = [], options) {
10515
+ const speed = options?.speed ?? ARC_SPEED;
10516
+ const gravity = options?.gravity ?? ARC_GRAVITY;
10517
+ const steps = Math.max(2, options?.steps ?? ARC_STEPS);
10518
+ const maxTime = options?.maxTime ?? ARC_MAX_TIME;
10519
+ out.length = 0;
10520
+ const velocity = direction.clone().normalize().multiplyScalar(speed);
10521
+ for (let i = 0; i < steps; i += 1) {
10522
+ const t = i / (steps - 1) * maxTime;
10523
+ out.push(
10524
+ new THREE38__namespace.Vector3(
10525
+ origin.x + velocity.x * t,
10526
+ origin.y + velocity.y * t - 0.5 * gravity * t * t,
10527
+ origin.z + velocity.z * t
10528
+ )
10529
+ );
10530
+ }
10531
+ return out;
10532
+ }
10533
+ __name(sampleTeleportArc, "sampleTeleportArc");
10534
+ function arcGroundCrossing(points, groundY) {
10535
+ for (let i = 1; i < points.length; i += 1) {
10536
+ const previous = points[i - 1];
10537
+ const current = points[i];
10538
+ if (previous.y >= groundY && current.y <= groundY) {
10539
+ const span = previous.y - current.y;
10540
+ const alpha = span > 1e-6 ? (previous.y - groundY) / span : 0;
10541
+ return new THREE38__namespace.Vector3().lerpVectors(previous, current, alpha);
10542
+ }
10543
+ }
10544
+ return null;
10545
+ }
10546
+ __name(arcGroundCrossing, "arcGroundCrossing");
10547
+
10548
+ // core/systems/XRLocomotionSystem.ts
10510
10549
  var UP = new THREE38__namespace.Vector3(0, 1, 0);
10511
10550
  var AXIS_DEADZONE = 0.5;
10512
10551
  var _XRLocomotionSystem = class _XRLocomotionSystem {
@@ -10514,13 +10553,26 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
10514
10553
  this.raycaster = new THREE38__namespace.Raycaster();
10515
10554
  /** Per-source latch so one stick push yields exactly one snap turn. */
10516
10555
  this.turnLatched = /* @__PURE__ */ new WeakSet();
10517
- /** Where the pointer currently lands, for the consumer to draw a marker. */
10518
- this.teleportPoint = null;
10556
+ /** Reused arc buffer — aiming runs every frame. */
10557
+ this.arcPoints = [];
10558
+ /** Curve up to the landing point, for the visual system. Empty when not aiming. */
10559
+ this.aimCurve = [];
10560
+ this.landingPoint = null;
10561
+ /** True once we've tried to self-heal camera parenting for this session. */
10562
+ this.parentingChecked = false;
10519
10563
  this.inputSystem = inputSystem;
10520
10564
  }
10521
10565
  update(world, deltaTime) {
10522
- this.teleportPoint = null;
10523
- if (!world.isXrPresenting()) return;
10566
+ this.aimCurve = [];
10567
+ this.landingPoint = null;
10568
+ if (!world.isXrPresenting()) {
10569
+ this.parentingChecked = false;
10570
+ return;
10571
+ }
10572
+ if (!this.parentingChecked) {
10573
+ this.parentingChecked = true;
10574
+ this.ensureCameraUnderRig(world);
10575
+ }
10524
10576
  const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
10525
10577
  if (typeof rigEntityId !== "number") return;
10526
10578
  const rig = world.getComponent(rigEntityId, XRRigComponent);
@@ -10537,9 +10589,37 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
10537
10589
  this.applySmoothMove(world, rig, rigTransform, inputs, deltaTime);
10538
10590
  }
10539
10591
  }
10592
+ /**
10593
+ * Guarantee the render camera is a CHILD of the rig.
10594
+ *
10595
+ * Locomotion moves the rig, and three composes the head pose against
10596
+ * `camera.parent` — so if the camera sits at the scene root instead, moving
10597
+ * the rig does nothing at all and the player is frozen in place. Scenes
10598
+ * authored before the rig existed (or with the camera re-parented by hand)
10599
+ * would otherwise silently fail to move, which is exactly the symptom that is
10600
+ * hardest to diagnose from inside a headset. Repairing it via `ParentId` lets
10601
+ * `ObjectManagementSystem` reconcile the scene graph normally.
10602
+ */
10603
+ ensureCameraUnderRig(world) {
10604
+ const rigEntityId = world.query(XRRigComponent)[0];
10605
+ if (typeof rigEntityId !== "number") return;
10606
+ const activeCamera = world.getCamera();
10607
+ for (const entityId of world.query(CameraComponent, Object3DRef)) {
10608
+ if (world.getComponent(entityId, Object3DRef)?.object !== activeCamera) {
10609
+ continue;
10610
+ }
10611
+ const parent = world.getComponent(entityId, ParentId);
10612
+ if (parent) {
10613
+ if (parent.value !== rigEntityId) parent.value = rigEntityId;
10614
+ } else {
10615
+ world.addComponent(entityId, new ParentId(rigEntityId));
10616
+ }
10617
+ return;
10618
+ }
10619
+ }
10540
10620
  /**
10541
10621
  * Snap turning, not smooth turning — rotating the view continuously is the
10542
- * single biggest cause of VR nausea, so the comfort-safe option is the one
10622
+ * single biggest cause of VR nausea, so the comfort-safe option is what's
10543
10623
  * wired to the stick.
10544
10624
  */
10545
10625
  applySnapTurn(rig, transform, inputs) {
@@ -10553,8 +10633,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
10553
10633
  }
10554
10634
  if (this.turnLatched.has(source)) continue;
10555
10635
  this.turnLatched.add(source);
10556
- const step = THREE38__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
10557
- transform.rotation.y -= Math.sign(x) * step;
10636
+ transform.rotation.y -= Math.sign(x) * THREE38__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
10558
10637
  }
10559
10638
  }
10560
10639
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -10579,16 +10658,41 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
10579
10658
  const targets = this.collectTeleportTargets(world);
10580
10659
  if (targets.length === 0) return;
10581
10660
  for (const input of inputs) {
10582
- this.raycaster.set(input.rayOrigin, input.rayDirection);
10583
- const hit = this.raycaster.intersectObjects(targets, true)[0];
10661
+ if (!input.select && !input.selectEnd) continue;
10662
+ const points = sampleTeleportArc(
10663
+ input.rayOrigin,
10664
+ input.rayDirection,
10665
+ this.arcPoints
10666
+ );
10667
+ const hit = this.raycastArc(points, targets);
10584
10668
  if (!hit) continue;
10585
- this.teleportPoint = hit.point.clone();
10669
+ this.landingPoint = hit.point;
10670
+ this.aimCurve = points.slice(0, hit.segmentIndex + 1).concat(hit.point);
10586
10671
  if (input.selectEnd) {
10587
10672
  transform.position.x = hit.point.x;
10588
10673
  transform.position.y = hit.point.y;
10589
10674
  transform.position.z = hit.point.z;
10675
+ this.aimCurve = [];
10676
+ this.landingPoint = null;
10677
+ }
10678
+ }
10679
+ }
10680
+ /** Walk the arc segment-by-segment, returning the first surface hit. */
10681
+ raycastArc(points, targets) {
10682
+ for (let i = 1; i < points.length; i += 1) {
10683
+ const from = points[i - 1];
10684
+ const to = points[i];
10685
+ const delta = new THREE38__namespace.Vector3().subVectors(to, from);
10686
+ const distance = delta.length();
10687
+ if (distance < 1e-6) continue;
10688
+ this.raycaster.set(from, delta.divideScalar(distance));
10689
+ this.raycaster.far = distance;
10690
+ const hit = this.raycaster.intersectObjects(targets, true)[0];
10691
+ if (hit) {
10692
+ return { point: hit.point.clone(), segmentIndex: i - 1 };
10590
10693
  }
10591
10694
  }
10695
+ return null;
10592
10696
  }
10593
10697
  collectTeleportTargets(world) {
10594
10698
  const objects = [];
@@ -10601,12 +10705,13 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
10601
10705
  }
10602
10706
  return objects;
10603
10707
  }
10604
- /**
10605
- * Where the teleport pointer currently lands this frame, or null.
10606
- * Consumers use this to render an aim marker.
10607
- */
10608
- getTeleportPoint() {
10609
- return this.teleportPoint;
10708
+ /** Arc points up to the landing spot this frame; empty when not aiming. */
10709
+ getAimCurve() {
10710
+ return this.aimCurve;
10711
+ }
10712
+ /** Where the arc currently lands, or null when not aiming at a valid area. */
10713
+ getLandingPoint() {
10714
+ return this.landingPoint;
10610
10715
  }
10611
10716
  };
10612
10717
  __name(_XRLocomotionSystem, "XRLocomotionSystem");
@@ -10768,7 +10873,7 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
10768
10873
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
10769
10874
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
10770
10875
  var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10771
- constructor(slots = 2) {
10876
+ constructor(options) {
10772
10877
  this.controllerModelFactory = new XRControllerModelFactory_js.XRControllerModelFactory();
10773
10878
  this.handModelFactory = new XRHandModelFactory_js.XRHandModelFactory();
10774
10879
  this.group = new THREE38__namespace.Group();
@@ -10776,7 +10881,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10776
10881
  this.attachedTo = null;
10777
10882
  /** Index → whether we've already attached visuals for that slot. */
10778
10883
  this.ready = /* @__PURE__ */ new Set();
10779
- this.slots = slots;
10884
+ this.rays = [];
10885
+ this.slots = options?.slots ?? 2;
10886
+ this.interactionSystem = options?.interactionSystem ?? null;
10780
10887
  this.group.name = "XRControllerVisuals";
10781
10888
  this.group.userData.ecsPreviewHidden = true;
10782
10889
  }
@@ -10784,6 +10891,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10784
10891
  const renderer = world.getRenderer();
10785
10892
  if (!renderer.xr?.enabled) return;
10786
10893
  this.ensureAttached(world);
10894
+ this.updateRayVisibility();
10787
10895
  if (this.built) return;
10788
10896
  for (let index = 0; index < this.slots; index += 1) {
10789
10897
  if (this.ready.has(index)) continue;
@@ -10791,7 +10899,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10791
10899
  const grip = renderer.xr.getControllerGrip(index);
10792
10900
  const hand = renderer.xr.getHand(index);
10793
10901
  if (!controller || !grip || !hand) continue;
10794
- controller.add(this.createRay());
10902
+ const ray = this.createRay();
10903
+ this.rays.push(ray);
10904
+ controller.add(ray);
10795
10905
  this.group.add(controller);
10796
10906
  grip.add(this.controllerModelFactory.createControllerModel(grip));
10797
10907
  this.group.add(grip);
@@ -10803,6 +10913,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10803
10913
  this.built = true;
10804
10914
  }
10805
10915
  }
10916
+ /**
10917
+ * Show the aim ray only when it is meaningful: hovering something
10918
+ * interactive, or mid-grab. A permanently-drawn ray is visual noise, and it
10919
+ * implies everything is clickable when most of the scene is not. Teleport
10920
+ * aiming has its own arc, so the straight ray stays hidden for that.
10921
+ */
10922
+ updateRayVisibility() {
10923
+ if (!this.interactionSystem) return;
10924
+ const active = this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
10925
+ for (const ray of this.rays) ray.visible = active;
10926
+ }
10806
10927
  /**
10807
10928
  * Parent the visuals to the XR rig when there is one, so locomotion moves the
10808
10929
  * hands with the player. Falls back to the scene root.
@@ -10828,6 +10949,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10828
10949
  const line = new THREE38__namespace.Line(geometry, material);
10829
10950
  line.name = "XRPointerRay";
10830
10951
  line.renderOrder = 999;
10952
+ line.visible = this.interactionSystem === null;
10831
10953
  return line;
10832
10954
  }
10833
10955
  dispose() {
@@ -10843,6 +10965,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10843
10965
  }
10844
10966
  });
10845
10967
  this.group.clear();
10968
+ this.rays.length = 0;
10846
10969
  this.ready.clear();
10847
10970
  this.built = false;
10848
10971
  this.attachedTo = null;
@@ -10850,6 +10973,121 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
10850
10973
  };
10851
10974
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
10852
10975
  var XRControllerVisualSystem = _XRControllerVisualSystem;
10976
+ var MARKER_RADIUS = 0.28;
10977
+ var ACCENT = new THREE38__namespace.Color("#4da3ff");
10978
+ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
10979
+ constructor(locomotionSystem) {
10980
+ this.group = new THREE38__namespace.Group();
10981
+ this.arcGeometry = new THREE38__namespace.BufferGeometry();
10982
+ this.marker = new THREE38__namespace.Group();
10983
+ this.attached = false;
10984
+ this.locomotionSystem = locomotionSystem;
10985
+ this.group.name = "XRTeleportVisuals";
10986
+ this.group.userData.ecsPreviewHidden = true;
10987
+ const arcMaterial = new THREE38__namespace.LineBasicMaterial({
10988
+ vertexColors: true,
10989
+ transparent: true,
10990
+ depthTest: false
10991
+ });
10992
+ this.arc = new THREE38__namespace.Line(this.arcGeometry, arcMaterial);
10993
+ this.arc.frustumCulled = false;
10994
+ this.arc.renderOrder = 998;
10995
+ this.group.add(this.arc);
10996
+ this.marker.add(this.createRing(), this.createDot());
10997
+ this.marker.renderOrder = 999;
10998
+ this.group.add(this.marker);
10999
+ this.setVisible(false);
11000
+ }
11001
+ update(world) {
11002
+ if (!this.attached) {
11003
+ world.getScene().add(this.group);
11004
+ this.attached = true;
11005
+ }
11006
+ const curve = this.locomotionSystem.getAimCurve();
11007
+ const landing = this.locomotionSystem.getLandingPoint();
11008
+ if (!landing || curve.length < 2) {
11009
+ this.setVisible(false);
11010
+ return;
11011
+ }
11012
+ this.updateArcGeometry(curve);
11013
+ this.marker.position.copy(landing);
11014
+ this.marker.position.y += 0.01;
11015
+ this.setVisible(true);
11016
+ }
11017
+ updateArcGeometry(curve) {
11018
+ const count = curve.length;
11019
+ const positions = new Float32Array(count * 3);
11020
+ const colors = new Float32Array(count * 3);
11021
+ for (let i = 0; i < count; i += 1) {
11022
+ const point = curve[i];
11023
+ positions[i * 3] = point.x;
11024
+ positions[i * 3 + 1] = point.y;
11025
+ positions[i * 3 + 2] = point.z;
11026
+ const t = count > 1 ? i / (count - 1) : 0;
11027
+ const fade = 1 - t * 0.75;
11028
+ colors[i * 3] = ACCENT.r * fade;
11029
+ colors[i * 3 + 1] = ACCENT.g * fade;
11030
+ colors[i * 3 + 2] = ACCENT.b * fade;
11031
+ }
11032
+ this.arcGeometry.setAttribute(
11033
+ "position",
11034
+ new THREE38__namespace.BufferAttribute(positions, 3)
11035
+ );
11036
+ this.arcGeometry.setAttribute("color", new THREE38__namespace.BufferAttribute(colors, 3));
11037
+ this.arcGeometry.computeBoundingSphere();
11038
+ }
11039
+ createRing() {
11040
+ const geometry = new THREE38__namespace.RingGeometry(
11041
+ MARKER_RADIUS * 0.72,
11042
+ MARKER_RADIUS,
11043
+ 48
11044
+ );
11045
+ geometry.rotateX(-Math.PI / 2);
11046
+ return new THREE38__namespace.Mesh(
11047
+ geometry,
11048
+ new THREE38__namespace.MeshBasicMaterial({
11049
+ color: ACCENT,
11050
+ transparent: true,
11051
+ opacity: 0.9,
11052
+ side: THREE38__namespace.DoubleSide,
11053
+ depthTest: false
11054
+ })
11055
+ );
11056
+ }
11057
+ createDot() {
11058
+ const geometry = new THREE38__namespace.CircleGeometry(MARKER_RADIUS * 0.16, 24);
11059
+ geometry.rotateX(-Math.PI / 2);
11060
+ return new THREE38__namespace.Mesh(
11061
+ geometry,
11062
+ new THREE38__namespace.MeshBasicMaterial({
11063
+ color: ACCENT,
11064
+ transparent: true,
11065
+ opacity: 0.55,
11066
+ side: THREE38__namespace.DoubleSide,
11067
+ depthTest: false
11068
+ })
11069
+ );
11070
+ }
11071
+ setVisible(visible) {
11072
+ this.group.visible = visible;
11073
+ }
11074
+ dispose() {
11075
+ this.group.removeFromParent();
11076
+ this.group.traverse((child) => {
11077
+ const mesh = child;
11078
+ mesh.geometry?.dispose?.();
11079
+ const material = mesh.material;
11080
+ if (Array.isArray(material)) {
11081
+ for (const entry of material) entry.dispose?.();
11082
+ } else {
11083
+ material?.dispose?.();
11084
+ }
11085
+ });
11086
+ this.attached = false;
11087
+ }
11088
+ };
11089
+ __name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
11090
+ var XRTeleportVisualSystem = _XRTeleportVisualSystem;
10853
11091
 
10854
11092
  // core/World.ts
10855
11093
  var _World = class _World {
@@ -12224,6 +12462,10 @@ function serializeEntitySubtree(world, rootId, options) {
12224
12462
  __name(serializeEntitySubtree, "serializeEntitySubtree");
12225
12463
 
12226
12464
  exports.ADDED_NODE_MARKER_COMPONENT = ADDED_NODE_MARKER_COMPONENT;
12465
+ exports.ARC_GRAVITY = ARC_GRAVITY;
12466
+ exports.ARC_MAX_TIME = ARC_MAX_TIME;
12467
+ exports.ARC_SPEED = ARC_SPEED;
12468
+ exports.ARC_STEPS = ARC_STEPS;
12227
12469
  exports.AnimationStateMachine = AnimationStateMachine;
12228
12470
  exports.AnimationStateManager = AnimationStateManager;
12229
12471
  exports.AnimationSystem = AnimationSystem;
@@ -12265,7 +12507,9 @@ exports.XRInputSystem = XRInputSystem;
12265
12507
  exports.XRInteractionSystem = XRInteractionSystem;
12266
12508
  exports.XRLocomotionSystem = XRLocomotionSystem;
12267
12509
  exports.XRSessionSystem = XRSessionSystem;
12510
+ exports.XRTeleportVisualSystem = XRTeleportVisualSystem;
12268
12511
  exports.applyPrefabToSnapshotInstances = applyPrefabToSnapshotInstances;
12512
+ exports.arcGroundCrossing = arcGroundCrossing;
12269
12513
  exports.buildComponentValueMaps = buildComponentValueMaps;
12270
12514
  exports.buildNodeKeyMapsFromSnapshot = buildNodeKeyMapsFromSnapshot;
12271
12515
  exports.buildNodeMapsFromSnapshot = buildNodeMapsFromSnapshot;
@@ -12330,6 +12574,7 @@ exports.remapSnapshotEntityIds = remapSnapshotEntityIds;
12330
12574
  exports.resolveDracoDecoderPath = resolveDracoDecoderPath;
12331
12575
  exports.restoreComponentState = restoreComponentState;
12332
12576
  exports.restorePostCreateComponents = restorePostCreateComponents;
12577
+ exports.sampleTeleportArc = sampleTeleportArc;
12333
12578
  exports.serializeEntitySubtree = serializeEntitySubtree;
12334
12579
  exports.setDracoDecoderPath = setDracoDecoderPath;
12335
12580
  exports.setPrefabAssetUrlResolver = setPrefabAssetUrlResolver;