@relu-interactives/spatial-ecs 0.4.3 → 0.4.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.
@@ -7464,6 +7464,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
7464
7464
  const rig = world.getComponent(rigEntityId, XRRigComponent);
7465
7465
  const rigTransform = world.getComponent(rigEntityId, TransformComponent);
7466
7466
  if (!rig || !rigTransform) return;
7467
+ if (world.isXrPassthrough()) return;
7467
7468
  const inputs = this.inputSystem.getInputs();
7468
7469
  if (inputs.length === 0) return;
7469
7470
  if (rig.snapTurn) {
@@ -7710,6 +7711,8 @@ var _XRSessionSystem = class _XRSessionSystem {
7710
7711
  this.capabilities = NO_XR_CAPABILITIES;
7711
7712
  /** Y compensation applied when a floor-relative space was requested but not granted. */
7712
7713
  this.appliedOriginOffset = 0;
7714
+ /** The live session, so an async floor probe can tell if it has been replaced. */
7715
+ this.session = null;
7713
7716
  this.inputSystem = inputSystem ?? null;
7714
7717
  }
7715
7718
  update(world) {
@@ -7724,10 +7727,37 @@ var _XRSessionSystem = class _XRSessionSystem {
7724
7727
  }
7725
7728
  if (presenting) {
7726
7729
  this.dispatchInputEdges(world);
7730
+ this.muteDepthOcclusionColor(world);
7731
+ }
7732
+ }
7733
+ /**
7734
+ * Stop three's depth-occlusion pass from writing COLOR.
7735
+ *
7736
+ * With depth sensing granted, three draws a full-screen, head-locked mesh
7737
+ * every frame whose fragment shader assigns ONLY `gl_FragDepth` — that is
7738
+ * the whole occlusion mechanism — but its material never sets
7739
+ * `colorWrite: false`, so the colour output is UNDEFINED per GLSL. Desktop
7740
+ * drivers happen to zero it, which is why this ships unnoticed; Quest's
7741
+ * mobile GPU frequently leaves the last-computed temporary in the register —
7742
+ * the sampled DEPTH value — so the pass paints the depth camera's view over
7743
+ * the screen as faint head-locked contour bands. On device that reads as "a
7744
+ * large wireframe dome that follows my head".
7745
+ *
7746
+ * The mesh is created lazily inside three's render, so this is re-checked
7747
+ * every frame rather than hooked once; the write is idempotent and the
7748
+ * check is two property reads. Depth-only is the mesh's entire purpose —
7749
+ * occlusion is unaffected.
7750
+ */
7751
+ muteDepthOcclusionColor(world) {
7752
+ const xr = world.getRenderer().xr;
7753
+ const material = xr.getDepthSensingMesh?.()?.material;
7754
+ if (material && material.colorWrite !== false) {
7755
+ material.colorWrite = false;
7727
7756
  }
7728
7757
  }
7729
7758
  handleSessionStart(world) {
7730
7759
  const session = world.getRenderer().xr.getSession();
7760
+ this.session = session ?? null;
7731
7761
  const mode = session && session.environmentBlendMode !== "opaque" ? "immersive-ar" : "immersive-vr";
7732
7762
  this.capabilities = readXrCapabilities(session ?? null, mode);
7733
7763
  if (session && this.inputSystem) {
@@ -7766,16 +7796,16 @@ var _XRSessionSystem = class _XRSessionSystem {
7766
7796
  renderer.xr.setReferenceSpaceType(wantsFloor ? "local-floor" : "local");
7767
7797
  } catch {
7768
7798
  }
7769
- const granted = session?.enabledFeatures;
7770
- const floorGranted = Array.isArray(granted) ? granted.includes("local-floor") : (
7771
- // No report at all: trust the request, since a wrong offset is worse
7772
- // than none on devices that did honour it.
7773
- true
7774
- );
7775
- this.appliedOriginOffset = wantsFloor && !floorGranted ? rig.fallbackEyeHeight : 0;
7776
- if (this.appliedOriginOffset !== 0) {
7799
+ this.appliedOriginOffset = 0;
7800
+ if (!wantsFloor || !session) return;
7801
+ const sessionAtRequest = session;
7802
+ session.requestReferenceSpace("local-floor").then(() => {
7803
+ }).catch(() => {
7804
+ if (this.session !== sessionAtRequest) return;
7805
+ if (this.appliedOriginOffset !== 0) return;
7806
+ this.appliedOriginOffset = rig.fallbackEyeHeight;
7777
7807
  transform.position.y -= this.appliedOriginOffset;
7778
- }
7808
+ });
7779
7809
  }
7780
7810
  handleSessionEnd(world) {
7781
7811
  if (this.appliedOriginOffset !== 0) {
@@ -7786,6 +7816,7 @@ var _XRSessionSystem = class _XRSessionSystem {
7786
7816
  }
7787
7817
  this.appliedOriginOffset = 0;
7788
7818
  }
7819
+ this.session = null;
7789
7820
  this.capabilities = NO_XR_CAPABILITIES;
7790
7821
  this.inputSystem?.detachSession();
7791
7822
  this.forEachBehaviour(world, (behaviour) => {
@@ -7875,8 +7906,12 @@ function planeColliderFromPolygon(polygon) {
7875
7906
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
7876
7907
  function writeXrMeshGeometry(vertices, indices, existing) {
7877
7908
  const geometry = existing ?? new THREE19.BufferGeometry();
7878
- geometry.setAttribute("position", new THREE19.BufferAttribute(vertices, 3));
7879
- geometry.setIndex(new THREE19.BufferAttribute(indices, 1));
7909
+ geometry.setAttribute(
7910
+ "position",
7911
+ new THREE19.BufferAttribute(vertices.slice(), 3)
7912
+ );
7913
+ geometry.setIndex(new THREE19.BufferAttribute(indices.slice(), 1));
7914
+ geometry.deleteAttribute("normal");
7880
7915
  geometry.computeBoundingSphere();
7881
7916
  geometry.computeVertexNormals();
7882
7917
  return geometry;
@@ -7902,6 +7937,42 @@ __name(decomposeXrPose, "decomposeXrPose");
7902
7937
 
7903
7938
  // core/systems/XREnvironmentSystem.ts
7904
7939
  var DEBUG_COLOR = 5088255;
7940
+ function debugColorFor(kind, semanticLabel) {
7941
+ switch (semanticLabel) {
7942
+ case "floor":
7943
+ return 5100666;
7944
+ // green — the surface everything should rest on
7945
+ case "ceiling":
7946
+ return 11693055;
7947
+ // purple
7948
+ case "wall":
7949
+ case "wall face":
7950
+ return DEBUG_COLOR;
7951
+ // blue, the family's base colour
7952
+ case "global mesh":
7953
+ return 9079442;
7954
+ // dim gray — raw scan, not an object
7955
+ default:
7956
+ return semanticLabel ? 16747594 : DEBUG_COLOR;
7957
+ }
7958
+ }
7959
+ __name(debugColorFor, "debugColorFor");
7960
+ var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
7961
+ var DEBUG_RENDER_ORDER = 900;
7962
+ var ROOM_CAPTURE_DELAY_SECONDS = 3;
7963
+ var SYNTHETIC_FLOOR_EXTENT = 50;
7964
+ var SYNTHETIC_FLOOR_PLANE = {
7965
+ planeSpace: {},
7966
+ polygon: [
7967
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
7968
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
7969
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 },
7970
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 }
7971
+ ],
7972
+ orientation: "horizontal",
7973
+ semanticLabel: "floor",
7974
+ lastChangedTime: 0
7975
+ };
7905
7976
  var _XREnvironmentSystem = class _XREnvironmentSystem {
7906
7977
  constructor() {
7907
7978
  /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
@@ -7909,17 +7980,24 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7909
7980
  this.meshes = /* @__PURE__ */ new Map();
7910
7981
  this.presenting = false;
7911
7982
  this.debugVisible = false;
7983
+ /** Seconds presenting, used to decide the room has genuinely nothing in it. */
7984
+ this.elapsed = 0;
7985
+ /** `initiateRoomCapture` may be called only ONCE per session. */
7986
+ this.roomCaptureRequested = false;
7912
7987
  }
7913
- update(world) {
7988
+ update(world, deltaTime = 0) {
7914
7989
  const presenting = world.isXrPresenting();
7915
7990
  if (!presenting) {
7916
7991
  if (this.presenting) {
7917
7992
  this.presenting = false;
7993
+ this.elapsed = 0;
7994
+ this.roomCaptureRequested = false;
7918
7995
  this.clearAll(world);
7919
7996
  }
7920
7997
  return;
7921
7998
  }
7922
7999
  this.presenting = true;
8000
+ this.elapsed += deltaTime;
7923
8001
  const rig = this.findRig(world);
7924
8002
  const mode = rig?.environmentPhysics ?? "none";
7925
8003
  if (mode === "none") {
@@ -7935,6 +8013,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7935
8013
  const frame = xr.getFrame?.();
7936
8014
  const referenceSpace = xr.getReferenceSpace();
7937
8015
  if (!frame || !referenceSpace) return;
8016
+ if (frame.detectedPlanes === void 0 && frame.detectedMeshes === void 0) {
8017
+ this.ensureSyntheticFloor(world);
8018
+ return;
8019
+ }
8020
+ this.removeSyntheticFloor(world);
7938
8021
  const meshesAvailable = Boolean(frame.detectedMeshes);
7939
8022
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
7940
8023
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
@@ -7948,11 +8031,84 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7948
8031
  } else if (this.meshes.size > 0) {
7949
8032
  this.clearMap(world, this.meshes);
7950
8033
  }
8034
+ this.maybeCaptureRoom(world, frame);
8035
+ }
8036
+ /**
8037
+ * Ask the runtime to scan the room when it turns out we have no room.
8038
+ *
8039
+ * Plane and mesh detection do NOT scan anything — they replay a scan the user
8040
+ * captured earlier through the system's own room setup. Most people have
8041
+ * never run it, and those who have may have run it in a DIFFERENT room, so a
8042
+ * mixed-reality experience opens against either nothing at all or someone
8043
+ * else's furniture, with no prompt and no error.
8044
+ *
8045
+ * `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
8046
+ * from inside the session, and exists precisely for this case. Contract, from
8047
+ * Meta's docs: callable ONCE per session, and only worth calling once we are
8048
+ * confident the room really is empty — the arrays populate a beat after the
8049
+ * session starts, so a check on frame one would fire for everyone.
8050
+ */
8051
+ maybeCaptureRoom(world, frame) {
8052
+ if (this.roomCaptureRequested) return;
8053
+ if (this.elapsed < ROOM_CAPTURE_DELAY_SECONDS) return;
8054
+ if (this.planes.size > 0 || this.meshes.size > 0) return;
8055
+ if ((frame.detectedPlanes?.size ?? 0) > 0) return;
8056
+ if ((frame.detectedMeshes?.size ?? 0) > 0) return;
8057
+ const session = world.getRenderer().xr.getSession?.();
8058
+ if (typeof session?.initiateRoomCapture !== "function") return;
8059
+ this.roomCaptureRequested = true;
8060
+ session.initiateRoomCapture()?.catch(() => {
8061
+ });
7951
8062
  }
7952
8063
  /** How many pieces of real geometry are currently colliding. Script-facing. */
7953
8064
  getDetectedCount() {
7954
8065
  return { planes: this.planes.size, meshes: this.meshes.size };
7955
8066
  }
8067
+ /**
8068
+ * A census of every surface currently spawned: what it is, where it sits and
8069
+ * roughly how big it is. Built for the template's post-session readout, so a
8070
+ * mystery shape on the headset can be looked up by its label instead of
8071
+ * guessed at — a small-radius entry at head height IS the mystery shape, and
8072
+ * a clean list means whatever was seen did not come from this system.
8073
+ */
8074
+ getDebugSnapshot(world) {
8075
+ const snapshot = [];
8076
+ for (const [plane, entry] of this.planes) {
8077
+ const transform = world.getComponent(entry.entityId, TransformComponent);
8078
+ const collider = planeColliderFromPolygon(plane.polygon);
8079
+ snapshot.push({
8080
+ kind: "plane",
8081
+ label: plane.semanticLabel ?? null,
8082
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
8083
+ radius: collider ? Math.hypot(collider.size.x, collider.size.z) / 2 : 0,
8084
+ emulated: entry.emulated
8085
+ });
8086
+ }
8087
+ for (const [mesh, entry] of this.meshes) {
8088
+ const transform = world.getComponent(entry.entityId, TransformComponent);
8089
+ snapshot.push({
8090
+ kind: "mesh",
8091
+ label: mesh.semanticLabel ?? null,
8092
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
8093
+ radius: entry.geometry?.boundingSphere?.radius ?? 0,
8094
+ emulated: entry.emulated
8095
+ });
8096
+ }
8097
+ return snapshot;
8098
+ }
8099
+ /** Create the no-scan floor once; it lives in the ordinary plane map. */
8100
+ ensureSyntheticFloor(world) {
8101
+ if (this.planes.has(SYNTHETIC_FLOOR_PLANE)) return;
8102
+ const created = this.createPlaneEntity(world, SYNTHETIC_FLOOR_PLANE);
8103
+ if (created) this.planes.set(SYNTHETIC_FLOOR_PLANE, created);
8104
+ }
8105
+ /** Real scene data exists (or arrived) — the stand-in must go. */
8106
+ removeSyntheticFloor(world) {
8107
+ const entry = this.planes.get(SYNTHETIC_FLOOR_PLANE);
8108
+ if (!entry) return;
8109
+ this.destroyEntry(world, entry);
8110
+ this.planes.delete(SYNTHETIC_FLOOR_PLANE);
8111
+ }
7956
8112
  findRig(world) {
7957
8113
  const ids = world.query(XRRigComponent);
7958
8114
  if (ids.length === 0) return null;
@@ -7970,9 +8126,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7970
8126
  for (const plane of detected) {
7971
8127
  const pose = frame.getPose(plane.planeSpace, referenceSpace);
7972
8128
  if (!pose) continue;
8129
+ const emulated = pose.emulatedPosition === true;
7973
8130
  let entry = this.planes.get(plane);
7974
8131
  const changed = plane.lastChangedTime ?? 0;
7975
8132
  if (!entry) {
8133
+ if (emulated) continue;
7976
8134
  const created = this.createPlaneEntity(world, plane);
7977
8135
  if (!created) continue;
7978
8136
  entry = created;
@@ -7985,11 +8143,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7985
8143
  if (collider) {
7986
8144
  entry.geometry?.dispose();
7987
8145
  entry.geometry = null;
7988
- this.attachPlaneDebug(world, entry, collider);
8146
+ this.attachPlaneDebug(
8147
+ world,
8148
+ entry,
8149
+ collider,
8150
+ plane.semanticLabel ?? null
8151
+ );
7989
8152
  }
7990
8153
  }
7991
8154
  }
7992
- this.applyPose(world, entry.entityId, pose);
8155
+ entry.emulated = emulated;
8156
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
7993
8157
  }
7994
8158
  }
7995
8159
  syncMeshes(world, frame, referenceSpace) {
@@ -8004,9 +8168,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8004
8168
  for (const mesh of detected) {
8005
8169
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
8006
8170
  if (!pose) continue;
8171
+ const emulated = pose.emulatedPosition === true;
8007
8172
  let entry = this.meshes.get(mesh);
8008
8173
  const changed = mesh.lastChangedTime ?? 0;
8009
8174
  if (!entry) {
8175
+ if (emulated) continue;
8010
8176
  const created = this.createMeshEntity(world, mesh);
8011
8177
  if (!created) continue;
8012
8178
  entry = created;
@@ -8015,7 +8181,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8015
8181
  entry.lastChangedTime = changed;
8016
8182
  writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
8017
8183
  }
8018
- this.applyPose(world, entry.entityId, pose);
8184
+ entry.emulated = emulated;
8185
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
8019
8186
  }
8020
8187
  }
8021
8188
  createPlaneEntity(world, plane) {
@@ -8035,9 +8202,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8035
8202
  entityId,
8036
8203
  lastChangedTime: plane.lastChangedTime ?? 0,
8037
8204
  geometry: null,
8038
- material: null
8205
+ material: null,
8206
+ emulated: false
8039
8207
  };
8040
- if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
8208
+ if (this.debugVisible) {
8209
+ this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
8210
+ }
8041
8211
  return entry;
8042
8212
  }
8043
8213
  applyPlaneCollider(world, entityId, plane) {
@@ -8062,15 +8232,36 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8062
8232
  createMeshEntity(world, mesh) {
8063
8233
  if (!mesh.vertices?.length || !mesh.indices?.length) return null;
8064
8234
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
8235
+ const label = mesh.semanticLabel ?? null;
8236
+ const isGlobalMesh = label === "global mesh";
8065
8237
  const material = new THREE19.MeshBasicMaterial({
8066
- color: DEBUG_COLOR,
8067
- wireframe: true
8238
+ color: debugColorFor("mesh", label),
8239
+ wireframe: true,
8240
+ // The global scan is a coarse triangle soup of the whole room; at full
8241
+ // strength it reads as phantom objects rather than as context.
8242
+ transparent: isGlobalMesh,
8243
+ opacity: isGlobalMesh ? GLOBAL_MESH_DEBUG_OPACITY : 1,
8244
+ // Depth-TESTED, deliberately. The overlay was X-ray for one release —
8245
+ // introduced on the theory that depth occlusion was carving it into a
8246
+ // head-locked dome — but that theory was falsified (the dome was the
8247
+ // depth mesh's unset colour output, plus emulated poses; both fixed).
8248
+ // X-ray had its own on-device failure: the global scan is DENSE, and a
8249
+ // dense wireframe at 0.35 opacity fuses into a translucent sheet — with
8250
+ // no depth test the walls' sheets drew through everything, stacked in
8251
+ // front of the user's eyes ("planes covering my face"). With the depth
8252
+ // buffer now holding CORRECT real-world depth, normal testing gives the
8253
+ // right picture: the near wall hides the far one. depthWrite stays off
8254
+ // so the overlay never occludes actual content.
8255
+ depthTest: true,
8256
+ depthWrite: false
8068
8257
  });
8069
8258
  const root = new THREE19.Group();
8070
8259
  const child = new THREE19.Mesh(geometry, material);
8071
8260
  child.visible = this.debugVisible;
8261
+ child.renderOrder = DEBUG_RENDER_ORDER;
8072
8262
  root.add(child);
8073
8263
  const entityId = world.createEntity();
8264
+ root.userData.entityId = entityId;
8074
8265
  world.addComponents(entityId, [
8075
8266
  new TransformComponent(),
8076
8267
  new Object3DRef(root),
@@ -8084,29 +8275,37 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8084
8275
  entityId,
8085
8276
  lastChangedTime: mesh.lastChangedTime ?? 0,
8086
8277
  geometry,
8087
- material
8278
+ material,
8279
+ emulated: false
8088
8280
  };
8089
8281
  }
8090
8282
  /** A thin box so a detected plane can be SEEN, not just collided with. */
8091
- attachPlaneDebug(world, entry, collider) {
8092
- const geometry = new THREE19.BoxGeometry(
8283
+ attachPlaneDebug(world, entry, collider, semanticLabel) {
8284
+ const box = new THREE19.BoxGeometry(
8093
8285
  collider.size.x,
8094
8286
  collider.size.y,
8095
8287
  collider.size.z
8096
8288
  );
8097
- const material = new THREE19.MeshBasicMaterial({
8098
- color: DEBUG_COLOR,
8099
- wireframe: true
8289
+ const geometry = new THREE19.EdgesGeometry(box);
8290
+ box.dispose();
8291
+ const material = new THREE19.LineBasicMaterial({
8292
+ color: debugColorFor("plane", semanticLabel),
8293
+ // Same depth policy as the mesh overlay — see createMeshEntity.
8294
+ depthTest: true,
8295
+ depthWrite: false
8100
8296
  });
8101
8297
  const root = new THREE19.Group();
8102
- const child = new THREE19.Mesh(geometry, material);
8298
+ const child = new THREE19.LineSegments(geometry, material);
8299
+ child.renderOrder = DEBUG_RENDER_ORDER;
8103
8300
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
8104
8301
  child.visible = this.debugVisible;
8105
8302
  root.add(child);
8106
8303
  entry.geometry = geometry;
8107
8304
  entry.material = material;
8305
+ root.userData.entityId = entry.entityId;
8108
8306
  const existing = world.getComponent(entry.entityId, Object3DRef);
8109
8307
  if (existing) {
8308
+ existing.object.removeFromParent();
8110
8309
  existing.object = root;
8111
8310
  } else {
8112
8311
  world.addComponent(entry.entityId, new Object3DRef(root));
@@ -8122,19 +8321,31 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8122
8321
  for (const [plane, entry] of this.planes) {
8123
8322
  if (entry.geometry) continue;
8124
8323
  const collider = planeColliderFromPolygon(plane.polygon);
8125
- if (collider) this.attachPlaneDebug(world, entry, collider);
8324
+ if (collider) {
8325
+ this.attachPlaneDebug(
8326
+ world,
8327
+ entry,
8328
+ collider,
8329
+ plane.semanticLabel ?? null
8330
+ );
8331
+ }
8126
8332
  }
8127
8333
  }
8128
8334
  for (const map of [this.planes, this.meshes]) {
8129
8335
  for (const entry of map.values()) {
8130
8336
  const ref = world.getComponent(entry.entityId, Object3DRef);
8131
8337
  ref?.object.traverse((child) => {
8132
- if (child.isMesh) child.visible = this.debugVisible;
8338
+ const drawable = child;
8339
+ if (drawable.isMesh || drawable.isLine) {
8340
+ child.visible = this.debugVisible;
8341
+ }
8133
8342
  });
8134
8343
  }
8135
8344
  }
8136
8345
  }
8137
8346
  destroyEntry(world, entry) {
8347
+ const ref = world.getComponent(entry.entityId, Object3DRef);
8348
+ ref?.object.removeFromParent();
8138
8349
  entry.geometry?.dispose();
8139
8350
  entry.material?.dispose();
8140
8351
  world.destroyEntity(entry.entityId);
@@ -8311,7 +8522,9 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8311
8522
  continue;
8312
8523
  }
8313
8524
  const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
8314
- if (pose) decomposeXrPose(pose.transform.matrix, transform);
8525
+ if (pose && pose.emulatedPosition !== true) {
8526
+ decomposeXrPose(pose.transform.matrix, transform);
8527
+ }
8315
8528
  }
8316
8529
  }
8317
8530
  /** Whether this entity's pose is currently being driven by an anchor. */
@@ -9601,5 +9814,5 @@ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9601
9814
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9602
9815
 
9603
9816
  export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_DIRECTION_TOLERANCE, 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, TeleportAimGate, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRAnchorSystem, XRControllerVisualSystem, XREnvironmentSystem, XRHandPoseSystem, XRHitTestSystem, 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 };
9604
- //# sourceMappingURL=chunk-6UZC6M7V.js.map
9605
- //# sourceMappingURL=chunk-6UZC6M7V.js.map
9817
+ //# sourceMappingURL=chunk-QIIWFP3V.js.map
9818
+ //# sourceMappingURL=chunk-QIIWFP3V.js.map