@relu-interactives/spatial-ecs 0.3.6 → 0.3.7

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.
@@ -6433,6 +6433,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6433
6433
  /** Curve up to the landing point, for the visual system. Empty when not aiming. */
6434
6434
  this.aimCurve = [];
6435
6435
  this.landingPoint = null;
6436
+ /** True when the current aim lands somewhere the player may actually stand. */
6437
+ this.aimValid = false;
6438
+ /** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
6439
+ this.stickAiming = /* @__PURE__ */ new WeakSet();
6436
6440
  /** True once we've tried to self-heal camera parenting for this session. */
6437
6441
  this.parentingChecked = false;
6438
6442
  this.inputSystem = inputSystem;
@@ -6440,6 +6444,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6440
6444
  update(world, deltaTime) {
6441
6445
  this.aimCurve = [];
6442
6446
  this.landingPoint = null;
6447
+ this.aimValid = false;
6443
6448
  if (!world.isXrPresenting()) {
6444
6449
  this.parentingChecked = false;
6445
6450
  return;
@@ -6447,6 +6452,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6447
6452
  if (!this.parentingChecked) {
6448
6453
  this.parentingChecked = true;
6449
6454
  this.ensureCameraUnderRig(world);
6455
+ this.warnOnIncompleteSetup(world);
6450
6456
  }
6451
6457
  const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
6452
6458
  if (typeof rigEntityId !== "number") return;
@@ -6464,6 +6470,26 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6464
6470
  this.applySmoothMove(world, rig, rigTransform, inputs, deltaTime);
6465
6471
  }
6466
6472
  }
6473
+ /**
6474
+ * Name what is missing, once per session.
6475
+ *
6476
+ * Inside a headset there is no console and no inspector, so a scene that
6477
+ * simply cannot move gives the author nothing to go on. These messages are
6478
+ * visible over remote debugging and say exactly which entity to add.
6479
+ */
6480
+ warnOnIncompleteSetup(world) {
6481
+ if (typeof world.query(XRRigComponent)[0] !== "number") {
6482
+ console.warn(
6483
+ "[XRLocomotion] No XR Rig in this scene \u2014 the player cannot move. Add one via Add Object \u2192 VR \u2192 XR Rig (the scene camera is parented to it automatically)."
6484
+ );
6485
+ return;
6486
+ }
6487
+ if (world.query(VRTeleportAreaComponent).length === 0) {
6488
+ console.warn(
6489
+ "[XRLocomotion] No Teleport Area in this scene \u2014 teleport has nowhere to land. Add one via Add Object \u2192 VR \u2192 Teleport Area, or add a Teleport Area component to an existing floor."
6490
+ );
6491
+ }
6492
+ }
6467
6493
  /**
6468
6494
  * Guarantee the render camera is a CHILD of the rig.
6469
6495
  *
@@ -6531,29 +6557,60 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6531
6557
  }
6532
6558
  applyTeleport(world, transform, inputs) {
6533
6559
  const targets = this.collectTeleportTargets(world);
6534
- if (targets.length === 0) return;
6535
6560
  for (const input of inputs) {
6536
- if (!input.select && !input.selectEnd) continue;
6561
+ const source = input;
6562
+ const stick = this.readStickAim(input, source);
6563
+ const aiming = input.select || stick.aiming;
6564
+ const commit = input.selectEnd || stick.released;
6565
+ if (!aiming && !commit) continue;
6537
6566
  const points = sampleTeleportArc(
6538
6567
  input.rayOrigin,
6539
6568
  input.rayDirection,
6540
6569
  this.arcPoints
6541
6570
  );
6542
6571
  const hit = this.raycastArc(points, targets);
6543
- if (!hit) continue;
6544
- this.landingPoint = hit.point;
6545
- this.aimCurve = points.slice(0, hit.segmentIndex + 1).concat(hit.point);
6546
- if (input.selectEnd) {
6572
+ this.aimCurve = hit ? points.slice(0, hit.segmentIndex + 1).concat(hit.point) : points.slice();
6573
+ this.landingPoint = hit ? hit.point : null;
6574
+ this.aimValid = hit !== null;
6575
+ if (commit && hit) {
6547
6576
  transform.position.x = hit.point.x;
6548
6577
  transform.position.y = hit.point.y;
6549
6578
  transform.position.z = hit.point.z;
6550
6579
  this.aimCurve = [];
6551
6580
  this.landingPoint = null;
6581
+ this.aimValid = false;
6582
+ } else if (commit) {
6583
+ this.aimCurve = [];
6584
+ this.aimValid = false;
6552
6585
  }
6553
6586
  }
6554
6587
  }
6588
+ /**
6589
+ * Thumbstick-forward aiming with hysteresis.
6590
+ *
6591
+ * Engaging at 0.6 and releasing at 0.3 stops a stick resting near the
6592
+ * threshold from flickering the arc on and off — and the release edge is what
6593
+ * commits the teleport, so chatter there would teleport the player randomly.
6594
+ */
6595
+ readStickAim(input, source) {
6596
+ if (!input.hasAxes || input.axes.length < 4) {
6597
+ return { aiming: false, released: false };
6598
+ }
6599
+ const forward = -(input.axes[3] ?? 0);
6600
+ const wasAiming = this.stickAiming.has(source);
6601
+ if (!wasAiming && forward > 0.6) {
6602
+ this.stickAiming.add(source);
6603
+ return { aiming: true, released: false };
6604
+ }
6605
+ if (wasAiming && forward < 0.3) {
6606
+ this.stickAiming.delete(source);
6607
+ return { aiming: false, released: true };
6608
+ }
6609
+ return { aiming: wasAiming, released: false };
6610
+ }
6555
6611
  /** Walk the arc segment-by-segment, returning the first surface hit. */
6556
6612
  raycastArc(points, targets) {
6613
+ if (targets.length === 0) return null;
6557
6614
  for (let i = 1; i < points.length; i += 1) {
6558
6615
  const from = points[i - 1];
6559
6616
  const to = points[i];
@@ -6584,6 +6641,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6584
6641
  getAimCurve() {
6585
6642
  return this.aimCurve;
6586
6643
  }
6644
+ /** True when the aim lands on a valid teleport area. */
6645
+ isAimValid() {
6646
+ return this.aimValid;
6647
+ }
6587
6648
  /** Where the arc currently lands, or null when not aiming at a valid area. */
6588
6649
  getLandingPoint() {
6589
6650
  return this.landingPoint;
@@ -7030,6 +7091,7 @@ __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
7030
7091
  var XRControllerVisualSystem = _XRControllerVisualSystem;
7031
7092
  var MARKER_RADIUS = 0.28;
7032
7093
  var ACCENT = new THREE7.Color("#4da3ff");
7094
+ var BLOCKED = new THREE7.Color("#ff5a5a");
7033
7095
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
7034
7096
  constructor(locomotionSystem) {
7035
7097
  this.group = new THREE7.Group();
@@ -7060,16 +7122,22 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
7060
7122
  }
7061
7123
  const curve = this.locomotionSystem.getAimCurve();
7062
7124
  const landing = this.locomotionSystem.getLandingPoint();
7063
- if (!landing || curve.length < 2) {
7125
+ const valid = this.locomotionSystem.isAimValid();
7126
+ if (curve.length < 2) {
7064
7127
  this.setVisible(false);
7065
7128
  return;
7066
7129
  }
7067
- this.updateArcGeometry(curve);
7068
- this.marker.position.copy(landing);
7069
- this.marker.position.y += 0.01;
7130
+ this.updateArcGeometry(curve, valid ? ACCENT : BLOCKED);
7131
+ if (valid && landing) {
7132
+ this.marker.position.copy(landing);
7133
+ this.marker.position.y += 0.01;
7134
+ this.marker.visible = true;
7135
+ } else {
7136
+ this.marker.visible = false;
7137
+ }
7070
7138
  this.setVisible(true);
7071
7139
  }
7072
- updateArcGeometry(curve) {
7140
+ updateArcGeometry(curve, tint) {
7073
7141
  const count = curve.length;
7074
7142
  const positions = new Float32Array(count * 3);
7075
7143
  const colors = new Float32Array(count * 3);
@@ -7080,9 +7148,9 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
7080
7148
  positions[i * 3 + 2] = point.z;
7081
7149
  const t = count > 1 ? i / (count - 1) : 0;
7082
7150
  const fade = 1 - t * 0.75;
7083
- colors[i * 3] = ACCENT.r * fade;
7084
- colors[i * 3 + 1] = ACCENT.g * fade;
7085
- colors[i * 3 + 2] = ACCENT.b * fade;
7151
+ colors[i * 3] = tint.r * fade;
7152
+ colors[i * 3 + 1] = tint.g * fade;
7153
+ colors[i * 3 + 2] = tint.b * fade;
7086
7154
  }
7087
7155
  this.arcGeometry.setAttribute(
7088
7156
  "position",
@@ -7145,5 +7213,5 @@ __name(_XRTeleportVisualSystem, "XRTeleportVisualSystem");
7145
7213
  var XRTeleportVisualSystem = _XRTeleportVisualSystem;
7146
7214
 
7147
7215
  export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TransformSyncSystem, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeInstanceOverrides, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
7148
- //# sourceMappingURL=chunk-WRJ2K3LZ.js.map
7149
- //# sourceMappingURL=chunk-WRJ2K3LZ.js.map
7216
+ //# sourceMappingURL=chunk-O6FJA4XW.js.map
7217
+ //# sourceMappingURL=chunk-O6FJA4XW.js.map