@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.
package/dist/index.cjs CHANGED
@@ -12059,6 +12059,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12059
12059
  const rig = world.getComponent(rigEntityId, XRRigComponent);
12060
12060
  const rigTransform = world.getComponent(rigEntityId, TransformComponent);
12061
12061
  if (!rig || !rigTransform) return;
12062
+ if (world.isXrPassthrough()) return;
12062
12063
  const inputs = this.inputSystem.getInputs();
12063
12064
  if (inputs.length === 0) return;
12064
12065
  if (rig.snapTurn) {
@@ -12305,6 +12306,8 @@ var _XRSessionSystem = class _XRSessionSystem {
12305
12306
  this.capabilities = NO_XR_CAPABILITIES;
12306
12307
  /** Y compensation applied when a floor-relative space was requested but not granted. */
12307
12308
  this.appliedOriginOffset = 0;
12309
+ /** The live session, so an async floor probe can tell if it has been replaced. */
12310
+ this.session = null;
12308
12311
  this.inputSystem = inputSystem ?? null;
12309
12312
  }
12310
12313
  update(world) {
@@ -12319,10 +12322,37 @@ var _XRSessionSystem = class _XRSessionSystem {
12319
12322
  }
12320
12323
  if (presenting) {
12321
12324
  this.dispatchInputEdges(world);
12325
+ this.muteDepthOcclusionColor(world);
12326
+ }
12327
+ }
12328
+ /**
12329
+ * Stop three's depth-occlusion pass from writing COLOR.
12330
+ *
12331
+ * With depth sensing granted, three draws a full-screen, head-locked mesh
12332
+ * every frame whose fragment shader assigns ONLY `gl_FragDepth` — that is
12333
+ * the whole occlusion mechanism — but its material never sets
12334
+ * `colorWrite: false`, so the colour output is UNDEFINED per GLSL. Desktop
12335
+ * drivers happen to zero it, which is why this ships unnoticed; Quest's
12336
+ * mobile GPU frequently leaves the last-computed temporary in the register —
12337
+ * the sampled DEPTH value — so the pass paints the depth camera's view over
12338
+ * the screen as faint head-locked contour bands. On device that reads as "a
12339
+ * large wireframe dome that follows my head".
12340
+ *
12341
+ * The mesh is created lazily inside three's render, so this is re-checked
12342
+ * every frame rather than hooked once; the write is idempotent and the
12343
+ * check is two property reads. Depth-only is the mesh's entire purpose —
12344
+ * occlusion is unaffected.
12345
+ */
12346
+ muteDepthOcclusionColor(world) {
12347
+ const xr = world.getRenderer().xr;
12348
+ const material = xr.getDepthSensingMesh?.()?.material;
12349
+ if (material && material.colorWrite !== false) {
12350
+ material.colorWrite = false;
12322
12351
  }
12323
12352
  }
12324
12353
  handleSessionStart(world) {
12325
12354
  const session = world.getRenderer().xr.getSession();
12355
+ this.session = session ?? null;
12326
12356
  const mode = session && session.environmentBlendMode !== "opaque" ? "immersive-ar" : "immersive-vr";
12327
12357
  this.capabilities = readXrCapabilities(session ?? null, mode);
12328
12358
  if (session && this.inputSystem) {
@@ -12361,16 +12391,16 @@ var _XRSessionSystem = class _XRSessionSystem {
12361
12391
  renderer.xr.setReferenceSpaceType(wantsFloor ? "local-floor" : "local");
12362
12392
  } catch {
12363
12393
  }
12364
- const granted = session?.enabledFeatures;
12365
- const floorGranted = Array.isArray(granted) ? granted.includes("local-floor") : (
12366
- // No report at all: trust the request, since a wrong offset is worse
12367
- // than none on devices that did honour it.
12368
- true
12369
- );
12370
- this.appliedOriginOffset = wantsFloor && !floorGranted ? rig.fallbackEyeHeight : 0;
12371
- if (this.appliedOriginOffset !== 0) {
12394
+ this.appliedOriginOffset = 0;
12395
+ if (!wantsFloor || !session) return;
12396
+ const sessionAtRequest = session;
12397
+ session.requestReferenceSpace("local-floor").then(() => {
12398
+ }).catch(() => {
12399
+ if (this.session !== sessionAtRequest) return;
12400
+ if (this.appliedOriginOffset !== 0) return;
12401
+ this.appliedOriginOffset = rig.fallbackEyeHeight;
12372
12402
  transform.position.y -= this.appliedOriginOffset;
12373
- }
12403
+ });
12374
12404
  }
12375
12405
  handleSessionEnd(world) {
12376
12406
  if (this.appliedOriginOffset !== 0) {
@@ -12381,6 +12411,7 @@ var _XRSessionSystem = class _XRSessionSystem {
12381
12411
  }
12382
12412
  this.appliedOriginOffset = 0;
12383
12413
  }
12414
+ this.session = null;
12384
12415
  this.capabilities = NO_XR_CAPABILITIES;
12385
12416
  this.inputSystem?.detachSession();
12386
12417
  this.forEachBehaviour(world, (behaviour) => {
@@ -12470,8 +12501,12 @@ function planeColliderFromPolygon(polygon) {
12470
12501
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
12471
12502
  function writeXrMeshGeometry(vertices, indices, existing) {
12472
12503
  const geometry = existing ?? new THREE51__namespace.BufferGeometry();
12473
- geometry.setAttribute("position", new THREE51__namespace.BufferAttribute(vertices, 3));
12474
- geometry.setIndex(new THREE51__namespace.BufferAttribute(indices, 1));
12504
+ geometry.setAttribute(
12505
+ "position",
12506
+ new THREE51__namespace.BufferAttribute(vertices.slice(), 3)
12507
+ );
12508
+ geometry.setIndex(new THREE51__namespace.BufferAttribute(indices.slice(), 1));
12509
+ geometry.deleteAttribute("normal");
12475
12510
  geometry.computeBoundingSphere();
12476
12511
  geometry.computeVertexNormals();
12477
12512
  return geometry;
@@ -12497,6 +12532,42 @@ __name(decomposeXrPose, "decomposeXrPose");
12497
12532
 
12498
12533
  // core/systems/XREnvironmentSystem.ts
12499
12534
  var DEBUG_COLOR = 5088255;
12535
+ function debugColorFor(kind, semanticLabel) {
12536
+ switch (semanticLabel) {
12537
+ case "floor":
12538
+ return 5100666;
12539
+ // green — the surface everything should rest on
12540
+ case "ceiling":
12541
+ return 11693055;
12542
+ // purple
12543
+ case "wall":
12544
+ case "wall face":
12545
+ return DEBUG_COLOR;
12546
+ // blue, the family's base colour
12547
+ case "global mesh":
12548
+ return 9079442;
12549
+ // dim gray — raw scan, not an object
12550
+ default:
12551
+ return semanticLabel ? 16747594 : DEBUG_COLOR;
12552
+ }
12553
+ }
12554
+ __name(debugColorFor, "debugColorFor");
12555
+ var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
12556
+ var DEBUG_RENDER_ORDER = 900;
12557
+ var ROOM_CAPTURE_DELAY_SECONDS = 3;
12558
+ var SYNTHETIC_FLOOR_EXTENT = 50;
12559
+ var SYNTHETIC_FLOOR_PLANE = {
12560
+ planeSpace: {},
12561
+ polygon: [
12562
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
12563
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
12564
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 },
12565
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 }
12566
+ ],
12567
+ orientation: "horizontal",
12568
+ semanticLabel: "floor",
12569
+ lastChangedTime: 0
12570
+ };
12500
12571
  var _XREnvironmentSystem = class _XREnvironmentSystem {
12501
12572
  constructor() {
12502
12573
  /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
@@ -12504,17 +12575,24 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12504
12575
  this.meshes = /* @__PURE__ */ new Map();
12505
12576
  this.presenting = false;
12506
12577
  this.debugVisible = false;
12578
+ /** Seconds presenting, used to decide the room has genuinely nothing in it. */
12579
+ this.elapsed = 0;
12580
+ /** `initiateRoomCapture` may be called only ONCE per session. */
12581
+ this.roomCaptureRequested = false;
12507
12582
  }
12508
- update(world) {
12583
+ update(world, deltaTime = 0) {
12509
12584
  const presenting = world.isXrPresenting();
12510
12585
  if (!presenting) {
12511
12586
  if (this.presenting) {
12512
12587
  this.presenting = false;
12588
+ this.elapsed = 0;
12589
+ this.roomCaptureRequested = false;
12513
12590
  this.clearAll(world);
12514
12591
  }
12515
12592
  return;
12516
12593
  }
12517
12594
  this.presenting = true;
12595
+ this.elapsed += deltaTime;
12518
12596
  const rig = this.findRig(world);
12519
12597
  const mode = rig?.environmentPhysics ?? "none";
12520
12598
  if (mode === "none") {
@@ -12530,6 +12608,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12530
12608
  const frame = xr.getFrame?.();
12531
12609
  const referenceSpace = xr.getReferenceSpace();
12532
12610
  if (!frame || !referenceSpace) return;
12611
+ if (frame.detectedPlanes === void 0 && frame.detectedMeshes === void 0) {
12612
+ this.ensureSyntheticFloor(world);
12613
+ return;
12614
+ }
12615
+ this.removeSyntheticFloor(world);
12533
12616
  const meshesAvailable = Boolean(frame.detectedMeshes);
12534
12617
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
12535
12618
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
@@ -12543,11 +12626,84 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12543
12626
  } else if (this.meshes.size > 0) {
12544
12627
  this.clearMap(world, this.meshes);
12545
12628
  }
12629
+ this.maybeCaptureRoom(world, frame);
12630
+ }
12631
+ /**
12632
+ * Ask the runtime to scan the room when it turns out we have no room.
12633
+ *
12634
+ * Plane and mesh detection do NOT scan anything — they replay a scan the user
12635
+ * captured earlier through the system's own room setup. Most people have
12636
+ * never run it, and those who have may have run it in a DIFFERENT room, so a
12637
+ * mixed-reality experience opens against either nothing at all or someone
12638
+ * else's furniture, with no prompt and no error.
12639
+ *
12640
+ * `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
12641
+ * from inside the session, and exists precisely for this case. Contract, from
12642
+ * Meta's docs: callable ONCE per session, and only worth calling once we are
12643
+ * confident the room really is empty — the arrays populate a beat after the
12644
+ * session starts, so a check on frame one would fire for everyone.
12645
+ */
12646
+ maybeCaptureRoom(world, frame) {
12647
+ if (this.roomCaptureRequested) return;
12648
+ if (this.elapsed < ROOM_CAPTURE_DELAY_SECONDS) return;
12649
+ if (this.planes.size > 0 || this.meshes.size > 0) return;
12650
+ if ((frame.detectedPlanes?.size ?? 0) > 0) return;
12651
+ if ((frame.detectedMeshes?.size ?? 0) > 0) return;
12652
+ const session = world.getRenderer().xr.getSession?.();
12653
+ if (typeof session?.initiateRoomCapture !== "function") return;
12654
+ this.roomCaptureRequested = true;
12655
+ session.initiateRoomCapture()?.catch(() => {
12656
+ });
12546
12657
  }
12547
12658
  /** How many pieces of real geometry are currently colliding. Script-facing. */
12548
12659
  getDetectedCount() {
12549
12660
  return { planes: this.planes.size, meshes: this.meshes.size };
12550
12661
  }
12662
+ /**
12663
+ * A census of every surface currently spawned: what it is, where it sits and
12664
+ * roughly how big it is. Built for the template's post-session readout, so a
12665
+ * mystery shape on the headset can be looked up by its label instead of
12666
+ * guessed at — a small-radius entry at head height IS the mystery shape, and
12667
+ * a clean list means whatever was seen did not come from this system.
12668
+ */
12669
+ getDebugSnapshot(world) {
12670
+ const snapshot = [];
12671
+ for (const [plane, entry] of this.planes) {
12672
+ const transform = world.getComponent(entry.entityId, TransformComponent);
12673
+ const collider = planeColliderFromPolygon(plane.polygon);
12674
+ snapshot.push({
12675
+ kind: "plane",
12676
+ label: plane.semanticLabel ?? null,
12677
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
12678
+ radius: collider ? Math.hypot(collider.size.x, collider.size.z) / 2 : 0,
12679
+ emulated: entry.emulated
12680
+ });
12681
+ }
12682
+ for (const [mesh, entry] of this.meshes) {
12683
+ const transform = world.getComponent(entry.entityId, TransformComponent);
12684
+ snapshot.push({
12685
+ kind: "mesh",
12686
+ label: mesh.semanticLabel ?? null,
12687
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
12688
+ radius: entry.geometry?.boundingSphere?.radius ?? 0,
12689
+ emulated: entry.emulated
12690
+ });
12691
+ }
12692
+ return snapshot;
12693
+ }
12694
+ /** Create the no-scan floor once; it lives in the ordinary plane map. */
12695
+ ensureSyntheticFloor(world) {
12696
+ if (this.planes.has(SYNTHETIC_FLOOR_PLANE)) return;
12697
+ const created = this.createPlaneEntity(world, SYNTHETIC_FLOOR_PLANE);
12698
+ if (created) this.planes.set(SYNTHETIC_FLOOR_PLANE, created);
12699
+ }
12700
+ /** Real scene data exists (or arrived) — the stand-in must go. */
12701
+ removeSyntheticFloor(world) {
12702
+ const entry = this.planes.get(SYNTHETIC_FLOOR_PLANE);
12703
+ if (!entry) return;
12704
+ this.destroyEntry(world, entry);
12705
+ this.planes.delete(SYNTHETIC_FLOOR_PLANE);
12706
+ }
12551
12707
  findRig(world) {
12552
12708
  const ids = world.query(XRRigComponent);
12553
12709
  if (ids.length === 0) return null;
@@ -12565,9 +12721,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12565
12721
  for (const plane of detected) {
12566
12722
  const pose = frame.getPose(plane.planeSpace, referenceSpace);
12567
12723
  if (!pose) continue;
12724
+ const emulated = pose.emulatedPosition === true;
12568
12725
  let entry = this.planes.get(plane);
12569
12726
  const changed = plane.lastChangedTime ?? 0;
12570
12727
  if (!entry) {
12728
+ if (emulated) continue;
12571
12729
  const created = this.createPlaneEntity(world, plane);
12572
12730
  if (!created) continue;
12573
12731
  entry = created;
@@ -12580,11 +12738,17 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12580
12738
  if (collider) {
12581
12739
  entry.geometry?.dispose();
12582
12740
  entry.geometry = null;
12583
- this.attachPlaneDebug(world, entry, collider);
12741
+ this.attachPlaneDebug(
12742
+ world,
12743
+ entry,
12744
+ collider,
12745
+ plane.semanticLabel ?? null
12746
+ );
12584
12747
  }
12585
12748
  }
12586
12749
  }
12587
- this.applyPose(world, entry.entityId, pose);
12750
+ entry.emulated = emulated;
12751
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
12588
12752
  }
12589
12753
  }
12590
12754
  syncMeshes(world, frame, referenceSpace) {
@@ -12599,9 +12763,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12599
12763
  for (const mesh of detected) {
12600
12764
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
12601
12765
  if (!pose) continue;
12766
+ const emulated = pose.emulatedPosition === true;
12602
12767
  let entry = this.meshes.get(mesh);
12603
12768
  const changed = mesh.lastChangedTime ?? 0;
12604
12769
  if (!entry) {
12770
+ if (emulated) continue;
12605
12771
  const created = this.createMeshEntity(world, mesh);
12606
12772
  if (!created) continue;
12607
12773
  entry = created;
@@ -12610,7 +12776,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12610
12776
  entry.lastChangedTime = changed;
12611
12777
  writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
12612
12778
  }
12613
- this.applyPose(world, entry.entityId, pose);
12779
+ entry.emulated = emulated;
12780
+ if (!emulated) this.applyPose(world, entry.entityId, pose);
12614
12781
  }
12615
12782
  }
12616
12783
  createPlaneEntity(world, plane) {
@@ -12630,9 +12797,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12630
12797
  entityId,
12631
12798
  lastChangedTime: plane.lastChangedTime ?? 0,
12632
12799
  geometry: null,
12633
- material: null
12800
+ material: null,
12801
+ emulated: false
12634
12802
  };
12635
- if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
12803
+ if (this.debugVisible) {
12804
+ this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
12805
+ }
12636
12806
  return entry;
12637
12807
  }
12638
12808
  applyPlaneCollider(world, entityId, plane) {
@@ -12657,15 +12827,36 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12657
12827
  createMeshEntity(world, mesh) {
12658
12828
  if (!mesh.vertices?.length || !mesh.indices?.length) return null;
12659
12829
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
12830
+ const label = mesh.semanticLabel ?? null;
12831
+ const isGlobalMesh = label === "global mesh";
12660
12832
  const material = new THREE51__namespace.MeshBasicMaterial({
12661
- color: DEBUG_COLOR,
12662
- wireframe: true
12833
+ color: debugColorFor("mesh", label),
12834
+ wireframe: true,
12835
+ // The global scan is a coarse triangle soup of the whole room; at full
12836
+ // strength it reads as phantom objects rather than as context.
12837
+ transparent: isGlobalMesh,
12838
+ opacity: isGlobalMesh ? GLOBAL_MESH_DEBUG_OPACITY : 1,
12839
+ // Depth-TESTED, deliberately. The overlay was X-ray for one release —
12840
+ // introduced on the theory that depth occlusion was carving it into a
12841
+ // head-locked dome — but that theory was falsified (the dome was the
12842
+ // depth mesh's unset colour output, plus emulated poses; both fixed).
12843
+ // X-ray had its own on-device failure: the global scan is DENSE, and a
12844
+ // dense wireframe at 0.35 opacity fuses into a translucent sheet — with
12845
+ // no depth test the walls' sheets drew through everything, stacked in
12846
+ // front of the user's eyes ("planes covering my face"). With the depth
12847
+ // buffer now holding CORRECT real-world depth, normal testing gives the
12848
+ // right picture: the near wall hides the far one. depthWrite stays off
12849
+ // so the overlay never occludes actual content.
12850
+ depthTest: true,
12851
+ depthWrite: false
12663
12852
  });
12664
12853
  const root = new THREE51__namespace.Group();
12665
12854
  const child = new THREE51__namespace.Mesh(geometry, material);
12666
12855
  child.visible = this.debugVisible;
12856
+ child.renderOrder = DEBUG_RENDER_ORDER;
12667
12857
  root.add(child);
12668
12858
  const entityId = world.createEntity();
12859
+ root.userData.entityId = entityId;
12669
12860
  world.addComponents(entityId, [
12670
12861
  new TransformComponent(),
12671
12862
  new Object3DRef(root),
@@ -12679,29 +12870,37 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12679
12870
  entityId,
12680
12871
  lastChangedTime: mesh.lastChangedTime ?? 0,
12681
12872
  geometry,
12682
- material
12873
+ material,
12874
+ emulated: false
12683
12875
  };
12684
12876
  }
12685
12877
  /** A thin box so a detected plane can be SEEN, not just collided with. */
12686
- attachPlaneDebug(world, entry, collider) {
12687
- const geometry = new THREE51__namespace.BoxGeometry(
12878
+ attachPlaneDebug(world, entry, collider, semanticLabel) {
12879
+ const box = new THREE51__namespace.BoxGeometry(
12688
12880
  collider.size.x,
12689
12881
  collider.size.y,
12690
12882
  collider.size.z
12691
12883
  );
12692
- const material = new THREE51__namespace.MeshBasicMaterial({
12693
- color: DEBUG_COLOR,
12694
- wireframe: true
12884
+ const geometry = new THREE51__namespace.EdgesGeometry(box);
12885
+ box.dispose();
12886
+ const material = new THREE51__namespace.LineBasicMaterial({
12887
+ color: debugColorFor("plane", semanticLabel),
12888
+ // Same depth policy as the mesh overlay — see createMeshEntity.
12889
+ depthTest: true,
12890
+ depthWrite: false
12695
12891
  });
12696
12892
  const root = new THREE51__namespace.Group();
12697
- const child = new THREE51__namespace.Mesh(geometry, material);
12893
+ const child = new THREE51__namespace.LineSegments(geometry, material);
12894
+ child.renderOrder = DEBUG_RENDER_ORDER;
12698
12895
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
12699
12896
  child.visible = this.debugVisible;
12700
12897
  root.add(child);
12701
12898
  entry.geometry = geometry;
12702
12899
  entry.material = material;
12900
+ root.userData.entityId = entry.entityId;
12703
12901
  const existing = world.getComponent(entry.entityId, Object3DRef);
12704
12902
  if (existing) {
12903
+ existing.object.removeFromParent();
12705
12904
  existing.object = root;
12706
12905
  } else {
12707
12906
  world.addComponent(entry.entityId, new Object3DRef(root));
@@ -12717,19 +12916,31 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12717
12916
  for (const [plane, entry] of this.planes) {
12718
12917
  if (entry.geometry) continue;
12719
12918
  const collider = planeColliderFromPolygon(plane.polygon);
12720
- if (collider) this.attachPlaneDebug(world, entry, collider);
12919
+ if (collider) {
12920
+ this.attachPlaneDebug(
12921
+ world,
12922
+ entry,
12923
+ collider,
12924
+ plane.semanticLabel ?? null
12925
+ );
12926
+ }
12721
12927
  }
12722
12928
  }
12723
12929
  for (const map of [this.planes, this.meshes]) {
12724
12930
  for (const entry of map.values()) {
12725
12931
  const ref = world.getComponent(entry.entityId, Object3DRef);
12726
12932
  ref?.object.traverse((child) => {
12727
- if (child.isMesh) child.visible = this.debugVisible;
12933
+ const drawable = child;
12934
+ if (drawable.isMesh || drawable.isLine) {
12935
+ child.visible = this.debugVisible;
12936
+ }
12728
12937
  });
12729
12938
  }
12730
12939
  }
12731
12940
  }
12732
12941
  destroyEntry(world, entry) {
12942
+ const ref = world.getComponent(entry.entityId, Object3DRef);
12943
+ ref?.object.removeFromParent();
12733
12944
  entry.geometry?.dispose();
12734
12945
  entry.material?.dispose();
12735
12946
  world.destroyEntity(entry.entityId);
@@ -12906,7 +13117,9 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12906
13117
  continue;
12907
13118
  }
12908
13119
  const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
12909
- if (pose) decomposeXrPose(pose.transform.matrix, transform);
13120
+ if (pose && pose.emulatedPosition !== true) {
13121
+ decomposeXrPose(pose.transform.matrix, transform);
13122
+ }
12910
13123
  }
12911
13124
  }
12912
13125
  /** Whether this entity's pose is currently being driven by an anchor. */