@relu-interactives/spatial-ecs 0.3.21 → 0.4.0

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 (44) hide show
  1. package/dist/{System-BhnLw0fZ.d.cts → System-QjjSc6LZ.d.cts} +14 -0
  2. package/dist/{System-BhnLw0fZ.d.ts → System-QjjSc6LZ.d.ts} +14 -0
  3. package/dist/{XRHandPose-CLl4MiHx.d.cts → XRHandPose-Capx-RTD.d.cts} +47 -2
  4. package/dist/{XRHandPose-CrzI57JA.d.ts → XRHandPose-Csd3QGkj.d.ts} +47 -2
  5. package/dist/{chunk-CDSE7476.js → chunk-24ZMSBR7.js} +540 -5
  6. package/dist/chunk-24ZMSBR7.js.map +1 -0
  7. package/dist/{chunk-D6KXWNYT.js → chunk-2GTFFLGG.js} +3 -3
  8. package/dist/{chunk-D6KXWNYT.js.map → chunk-2GTFFLGG.js.map} +1 -1
  9. package/dist/{chunk-P42OE3RU.js → chunk-OWQPGEBB.js} +4 -4
  10. package/dist/{chunk-P42OE3RU.js.map → chunk-OWQPGEBB.js.map} +1 -1
  11. package/dist/{chunk-H6DQ7JRE.js → chunk-ZWONFM2F.js} +33 -3
  12. package/dist/chunk-ZWONFM2F.js.map +1 -0
  13. package/dist/components/index.cjs +30 -0
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +3 -3
  16. package/dist/components/index.d.ts +3 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/factories/index.cjs +6 -0
  19. package/dist/factories/index.cjs.map +1 -1
  20. package/dist/factories/index.d.cts +2 -2
  21. package/dist/factories/index.d.ts +2 -2
  22. package/dist/factories/index.js +3 -3
  23. package/dist/{index-6l4hn2Sh.d.ts → index-BAeeVJ6f.d.ts} +94 -2
  24. package/dist/{index-g777AmFv.d.cts → index-BRATaqTT.d.ts} +78 -4
  25. package/dist/{index-JRcoel9F.d.ts → index-C1rprGz_.d.cts} +78 -4
  26. package/dist/{index-C2I5PxCI.d.cts → index-SC9Mgjht.d.cts} +94 -2
  27. package/dist/index.cjs +586 -0
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +7 -7
  30. package/dist/index.d.ts +7 -7
  31. package/dist/index.js +26 -8
  32. package/dist/index.js.map +1 -1
  33. package/dist/systems/index.cjs +566 -0
  34. package/dist/systems/index.cjs.map +1 -1
  35. package/dist/systems/index.d.cts +2 -2
  36. package/dist/systems/index.d.ts +2 -2
  37. package/dist/systems/index.js +3 -3
  38. package/dist/types.d.cts +1 -1
  39. package/dist/types.d.ts +1 -1
  40. package/dist/worldSnapshotUtils.d.cts +1 -1
  41. package/dist/worldSnapshotUtils.d.ts +1 -1
  42. package/package.json +1 -1
  43. package/dist/chunk-CDSE7476.js.map +0 -1
  44. package/dist/chunk-H6DQ7JRE.js.map +0 -1
@@ -2154,11 +2154,39 @@ var _XRRigComponent = class _XRRigComponent extends Component {
2154
2154
  this.teleportArcColor = options?.teleportArcColor ?? "#4da3ff";
2155
2155
  this.teleportArcInvalidColor = options?.teleportArcInvalidColor ?? "#ff5a5a";
2156
2156
  this.teleportArcThickness = options?.teleportArcThickness ?? 0.012;
2157
+ this.passthrough = options?.passthrough ?? false;
2158
+ this.depthOcclusion = options?.depthOcclusion ?? false;
2159
+ this.environmentPhysics = options?.environmentPhysics ?? "none";
2160
+ this.environmentDebug = options?.environmentDebug ?? false;
2161
+ this.hitTest = options?.hitTest ?? false;
2162
+ this.anchors = options?.anchors ?? false;
2157
2163
  }
2158
2164
  };
2159
2165
  __name(_XRRigComponent, "XRRigComponent");
2160
2166
  var XRRigComponent = _XRRigComponent;
2161
2167
 
2168
+ // core/components/XREnvironment.ts
2169
+ var _XREnvironmentComponent = class _XREnvironmentComponent extends Component {
2170
+ constructor(kind, semanticLabel = null, orientation = null) {
2171
+ super();
2172
+ this.kind = kind;
2173
+ this.semanticLabel = semanticLabel;
2174
+ this.orientation = orientation;
2175
+ }
2176
+ };
2177
+ __name(_XREnvironmentComponent, "XREnvironmentComponent");
2178
+ var XREnvironmentComponent = _XREnvironmentComponent;
2179
+
2180
+ // core/components/XRAnchor.ts
2181
+ var _XRAnchorComponent = class _XRAnchorComponent extends Component {
2182
+ constructor(options) {
2183
+ super();
2184
+ this.enabled = options?.enabled ?? true;
2185
+ }
2186
+ };
2187
+ __name(_XRAnchorComponent, "XRAnchorComponent");
2188
+ var XRAnchorComponent = _XRAnchorComponent;
2189
+
2162
2190
  // core/components/XRInteractor.ts
2163
2191
  var _XRInteractorComponent = class _XRInteractorComponent extends Component {
2164
2192
  constructor(options) {
@@ -3045,6 +3073,8 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
3045
3073
  this.signature = null;
3046
3074
  this.activeTexture = null;
3047
3075
  this.pendingImageUrl = null;
3076
+ /** Whether the skybox is currently withheld because passthrough is on. */
3077
+ this.passthroughSuppressed = false;
3048
3078
  this.scene = scene;
3049
3079
  }
3050
3080
  disposeActiveTexture() {
@@ -3129,6 +3159,18 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
3129
3159
  this.scene.fog = new THREE26__namespace.FogExp2(fogColor, density);
3130
3160
  }
3131
3161
  update(world) {
3162
+ const passthrough = world.isXrPassthrough();
3163
+ if (passthrough !== this.passthroughSuppressed) {
3164
+ this.passthroughSuppressed = passthrough;
3165
+ if (passthrough) {
3166
+ this.scene.background = null;
3167
+ } else {
3168
+ this.signature = null;
3169
+ }
3170
+ }
3171
+ if (passthrough) {
3172
+ return;
3173
+ }
3132
3174
  const entityIds = world.query(Environment);
3133
3175
  if (entityIds.length === 0) {
3134
3176
  if (this.signature !== null) {
@@ -5398,6 +5440,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
5398
5440
  if (typeof sourceXrRig.fallbackEyeHeight === "number") {
5399
5441
  rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
5400
5442
  }
5443
+ if (typeof sourceXrRig.passthrough === "boolean") {
5444
+ rig.passthrough = sourceXrRig.passthrough;
5445
+ }
5446
+ if (typeof sourceXrRig.depthOcclusion === "boolean") {
5447
+ rig.depthOcclusion = sourceXrRig.depthOcclusion;
5448
+ }
5449
+ if (typeof sourceXrRig.environmentPhysics === "string") {
5450
+ rig.environmentPhysics = sourceXrRig.environmentPhysics;
5451
+ }
5452
+ if (typeof sourceXrRig.environmentDebug === "boolean") {
5453
+ rig.environmentDebug = sourceXrRig.environmentDebug;
5454
+ }
5455
+ if (typeof sourceXrRig.hitTest === "boolean") {
5456
+ rig.hitTest = sourceXrRig.hitTest;
5457
+ }
5458
+ if (typeof sourceXrRig.anchors === "boolean") {
5459
+ rig.anchors = sourceXrRig.anchors;
5460
+ }
5401
5461
  }
5402
5462
  }
5403
5463
  {
@@ -9650,6 +9710,509 @@ var _XRSessionSystem = class _XRSessionSystem {
9650
9710
  };
9651
9711
  __name(_XRSessionSystem, "XRSessionSystem");
9652
9712
  var XRSessionSystem = _XRSessionSystem;
9713
+ var PLANE_COLLIDER_THICKNESS = 0.02;
9714
+ var MIN_PLANE_EXTENT = 0.01;
9715
+ function planeColliderFromPolygon(polygon) {
9716
+ if (!polygon || polygon.length < 3) return null;
9717
+ let minX = Number.POSITIVE_INFINITY;
9718
+ let maxX = Number.NEGATIVE_INFINITY;
9719
+ let minZ = Number.POSITIVE_INFINITY;
9720
+ let maxZ = Number.NEGATIVE_INFINITY;
9721
+ for (const point of polygon) {
9722
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
9723
+ minX = Math.min(minX, point.x);
9724
+ maxX = Math.max(maxX, point.x);
9725
+ minZ = Math.min(minZ, point.z);
9726
+ maxZ = Math.max(maxZ, point.z);
9727
+ }
9728
+ const width = maxX - minX;
9729
+ const depth = maxZ - minZ;
9730
+ if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
9731
+ return {
9732
+ // Thin on Y: that is the plane's normal in plane space.
9733
+ size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
9734
+ // The polygon is not necessarily centred on its own origin, so the box has
9735
+ // to be offset to sit over it. Pushed DOWN half its thickness so the
9736
+ // collider's top face lines up with the real surface — otherwise objects
9737
+ // rest a centimetre above the floor, or sink a centimetre into it.
9738
+ offset: {
9739
+ x: (minX + maxX) / 2,
9740
+ y: -PLANE_COLLIDER_THICKNESS / 2,
9741
+ z: (minZ + maxZ) / 2
9742
+ }
9743
+ };
9744
+ }
9745
+ __name(planeColliderFromPolygon, "planeColliderFromPolygon");
9746
+ function writeXrMeshGeometry(vertices, indices, existing) {
9747
+ const geometry = existing ?? new THREE26__namespace.BufferGeometry();
9748
+ geometry.setAttribute("position", new THREE26__namespace.BufferAttribute(vertices, 3));
9749
+ geometry.setIndex(new THREE26__namespace.BufferAttribute(indices, 1));
9750
+ geometry.computeBoundingSphere();
9751
+ geometry.computeVertexNormals();
9752
+ return geometry;
9753
+ }
9754
+ __name(writeXrMeshGeometry, "writeXrMeshGeometry");
9755
+ function decomposeXrPose(matrixElements, target) {
9756
+ const matrix = new THREE26__namespace.Matrix4().fromArray(
9757
+ matrixElements
9758
+ );
9759
+ const position = new THREE26__namespace.Vector3();
9760
+ const quaternion = new THREE26__namespace.Quaternion();
9761
+ const scale = new THREE26__namespace.Vector3();
9762
+ matrix.decompose(position, quaternion, scale);
9763
+ const euler = new THREE26__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
9764
+ target.position.x = position.x;
9765
+ target.position.y = position.y;
9766
+ target.position.z = position.z;
9767
+ target.rotation.x = euler.x;
9768
+ target.rotation.y = euler.y;
9769
+ target.rotation.z = euler.z;
9770
+ }
9771
+ __name(decomposeXrPose, "decomposeXrPose");
9772
+
9773
+ // core/systems/XREnvironmentSystem.ts
9774
+ var DEBUG_COLOR = 5088255;
9775
+ var _XREnvironmentSystem = class _XREnvironmentSystem {
9776
+ constructor() {
9777
+ /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
9778
+ this.planes = /* @__PURE__ */ new Map();
9779
+ this.meshes = /* @__PURE__ */ new Map();
9780
+ this.presenting = false;
9781
+ this.debugVisible = false;
9782
+ }
9783
+ update(world) {
9784
+ const presenting = world.isXrPresenting();
9785
+ if (!presenting) {
9786
+ if (this.presenting) {
9787
+ this.presenting = false;
9788
+ this.clearAll(world);
9789
+ }
9790
+ return;
9791
+ }
9792
+ this.presenting = true;
9793
+ const rig = this.findRig(world);
9794
+ const mode = rig?.environmentPhysics ?? "none";
9795
+ if (mode === "none") {
9796
+ if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
9797
+ return;
9798
+ }
9799
+ const debug = rig?.environmentDebug === true;
9800
+ if (debug !== this.debugVisible) {
9801
+ this.debugVisible = debug;
9802
+ this.applyDebugVisibility(world);
9803
+ }
9804
+ const xr = world.getRenderer().xr;
9805
+ const frame = xr.getFrame?.();
9806
+ const referenceSpace = xr.getReferenceSpace();
9807
+ if (!frame || !referenceSpace) return;
9808
+ const meshesAvailable = Boolean(frame.detectedMeshes);
9809
+ const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
9810
+ const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
9811
+ if (usePlanes) {
9812
+ this.syncPlanes(world, frame, referenceSpace);
9813
+ } else if (this.planes.size > 0) {
9814
+ this.clearMap(world, this.planes);
9815
+ }
9816
+ if (useMesh) {
9817
+ this.syncMeshes(world, frame, referenceSpace);
9818
+ } else if (this.meshes.size > 0) {
9819
+ this.clearMap(world, this.meshes);
9820
+ }
9821
+ }
9822
+ /** How many pieces of real geometry are currently colliding. Script-facing. */
9823
+ getDetectedCount() {
9824
+ return { planes: this.planes.size, meshes: this.meshes.size };
9825
+ }
9826
+ findRig(world) {
9827
+ const ids = world.query(XRRigComponent);
9828
+ if (ids.length === 0) return null;
9829
+ return world.getComponent(ids[0], XRRigComponent) ?? null;
9830
+ }
9831
+ syncPlanes(world, frame, referenceSpace) {
9832
+ const detected = frame.detectedPlanes;
9833
+ if (!detected) return;
9834
+ for (const [plane, entry] of this.planes) {
9835
+ if (!detected.has(plane)) {
9836
+ this.destroyEntry(world, entry);
9837
+ this.planes.delete(plane);
9838
+ }
9839
+ }
9840
+ for (const plane of detected) {
9841
+ const pose = frame.getPose(plane.planeSpace, referenceSpace);
9842
+ if (!pose) continue;
9843
+ let entry = this.planes.get(plane);
9844
+ const changed = plane.lastChangedTime ?? 0;
9845
+ if (!entry) {
9846
+ const created = this.createPlaneEntity(world, plane);
9847
+ if (!created) continue;
9848
+ entry = created;
9849
+ this.planes.set(plane, entry);
9850
+ } else if (changed !== entry.lastChangedTime) {
9851
+ entry.lastChangedTime = changed;
9852
+ this.applyPlaneCollider(world, entry.entityId, plane);
9853
+ }
9854
+ this.applyPose(world, entry.entityId, pose);
9855
+ }
9856
+ }
9857
+ syncMeshes(world, frame, referenceSpace) {
9858
+ const detected = frame.detectedMeshes;
9859
+ if (!detected) return;
9860
+ for (const [mesh, entry] of this.meshes) {
9861
+ if (!detected.has(mesh)) {
9862
+ this.destroyEntry(world, entry);
9863
+ this.meshes.delete(mesh);
9864
+ }
9865
+ }
9866
+ for (const mesh of detected) {
9867
+ const pose = frame.getPose(mesh.meshSpace, referenceSpace);
9868
+ if (!pose) continue;
9869
+ let entry = this.meshes.get(mesh);
9870
+ const changed = mesh.lastChangedTime ?? 0;
9871
+ if (!entry) {
9872
+ const created = this.createMeshEntity(world, mesh);
9873
+ if (!created) continue;
9874
+ entry = created;
9875
+ this.meshes.set(mesh, entry);
9876
+ } else if (changed !== entry.lastChangedTime) {
9877
+ entry.lastChangedTime = changed;
9878
+ writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
9879
+ }
9880
+ this.applyPose(world, entry.entityId, pose);
9881
+ }
9882
+ }
9883
+ createPlaneEntity(world, plane) {
9884
+ const collider = planeColliderFromPolygon(plane.polygon);
9885
+ if (!collider) return null;
9886
+ const entityId = world.createEntity();
9887
+ world.addComponents(entityId, [
9888
+ new TransformComponent(),
9889
+ new XREnvironmentComponent(
9890
+ "plane",
9891
+ plane.semanticLabel ?? null,
9892
+ normalizeOrientation(plane.orientation)
9893
+ )
9894
+ ]);
9895
+ this.applyPlaneCollider(world, entityId, plane);
9896
+ const entry = {
9897
+ entityId,
9898
+ lastChangedTime: plane.lastChangedTime ?? 0,
9899
+ geometry: null,
9900
+ material: null
9901
+ };
9902
+ if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
9903
+ return entry;
9904
+ }
9905
+ applyPlaneCollider(world, entityId, plane) {
9906
+ const collider = planeColliderFromPolygon(plane.polygon);
9907
+ if (!collider) return;
9908
+ const existing = world.getComponent(entityId, PhysicsComponent);
9909
+ const values = {
9910
+ // Fixed: the real room does not move because something hit it.
9911
+ rigidbody: { type: "fixed" },
9912
+ collider: {
9913
+ shape: "cuboid",
9914
+ size: collider.size,
9915
+ offsetPosition: collider.offset
9916
+ }
9917
+ };
9918
+ if (existing) {
9919
+ existing.setValues(values);
9920
+ } else {
9921
+ world.addComponent(entityId, new PhysicsComponent(values));
9922
+ }
9923
+ }
9924
+ createMeshEntity(world, mesh) {
9925
+ if (!mesh.vertices?.length || !mesh.indices?.length) return null;
9926
+ const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
9927
+ const material = new THREE26__namespace.MeshBasicMaterial({
9928
+ color: DEBUG_COLOR,
9929
+ wireframe: true
9930
+ });
9931
+ const root = new THREE26__namespace.Group();
9932
+ const child = new THREE26__namespace.Mesh(geometry, material);
9933
+ child.visible = this.debugVisible;
9934
+ root.add(child);
9935
+ const entityId = world.createEntity();
9936
+ world.addComponents(entityId, [
9937
+ new TransformComponent(),
9938
+ new Object3DRef(root),
9939
+ new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
9940
+ new PhysicsComponent({
9941
+ rigidbody: { type: "fixed" },
9942
+ collider: { shape: "trimesh" }
9943
+ })
9944
+ ]);
9945
+ return {
9946
+ entityId,
9947
+ lastChangedTime: mesh.lastChangedTime ?? 0,
9948
+ geometry,
9949
+ material
9950
+ };
9951
+ }
9952
+ /** A thin box so a detected plane can be SEEN, not just collided with. */
9953
+ attachPlaneDebug(world, entry, collider) {
9954
+ const geometry = new THREE26__namespace.BoxGeometry(
9955
+ collider.size.x,
9956
+ collider.size.y,
9957
+ collider.size.z
9958
+ );
9959
+ const material = new THREE26__namespace.MeshBasicMaterial({
9960
+ color: DEBUG_COLOR,
9961
+ wireframe: true
9962
+ });
9963
+ const root = new THREE26__namespace.Group();
9964
+ root.add(new THREE26__namespace.Mesh(geometry, material));
9965
+ entry.geometry = geometry;
9966
+ entry.material = material;
9967
+ world.addComponent(entry.entityId, new Object3DRef(root));
9968
+ }
9969
+ applyPose(world, entityId, pose) {
9970
+ const transform = world.getComponent(entityId, TransformComponent);
9971
+ if (!transform) return;
9972
+ decomposeXrPose(pose.transform.matrix, transform);
9973
+ }
9974
+ applyDebugVisibility(world) {
9975
+ for (const map of [this.planes, this.meshes]) {
9976
+ for (const entry of map.values()) {
9977
+ const ref = world.getComponent(entry.entityId, Object3DRef);
9978
+ ref?.object.traverse((child) => {
9979
+ if (child.isMesh) child.visible = this.debugVisible;
9980
+ });
9981
+ }
9982
+ }
9983
+ }
9984
+ destroyEntry(world, entry) {
9985
+ entry.geometry?.dispose();
9986
+ entry.material?.dispose();
9987
+ world.destroyEntity(entry.entityId);
9988
+ }
9989
+ clearMap(world, map) {
9990
+ for (const entry of map.values()) this.destroyEntry(world, entry);
9991
+ map.clear();
9992
+ }
9993
+ clearAll(world) {
9994
+ this.clearMap(world, this.planes);
9995
+ this.clearMap(world, this.meshes);
9996
+ }
9997
+ dispose(world) {
9998
+ this.clearAll(world);
9999
+ }
10000
+ };
10001
+ __name(_XREnvironmentSystem, "XREnvironmentSystem");
10002
+ var XREnvironmentSystem = _XREnvironmentSystem;
10003
+ function normalizeOrientation(value) {
10004
+ return value === "horizontal" || value === "vertical" ? value : null;
10005
+ }
10006
+ __name(normalizeOrientation, "normalizeOrientation");
10007
+ var _XRHitTestSystem = class _XRHitTestSystem {
10008
+ constructor() {
10009
+ this.sources = /* @__PURE__ */ new Map();
10010
+ /** Targets whose async request is in flight, so it is not fired twice. */
10011
+ this.pending = /* @__PURE__ */ new Set();
10012
+ this.results = /* @__PURE__ */ new Map();
10013
+ this.presenting = false;
10014
+ this.enabled = false;
10015
+ }
10016
+ update(world) {
10017
+ const presenting = world.isXrPresenting();
10018
+ if (!presenting) {
10019
+ if (this.presenting) {
10020
+ this.presenting = false;
10021
+ this.teardown();
10022
+ }
10023
+ return;
10024
+ }
10025
+ if (!this.presenting) {
10026
+ this.presenting = true;
10027
+ this.enabled = this.rigWantsHitTest(world);
10028
+ }
10029
+ if (!this.enabled) return;
10030
+ const xr = world.getRenderer().xr;
10031
+ const session = xr.getSession();
10032
+ const referenceSpace = xr.getReferenceSpace();
10033
+ const frame = xr.getFrame?.();
10034
+ if (!session || !referenceSpace || !frame) return;
10035
+ this.ensureViewerSource(session);
10036
+ this.ensureInputSources(session);
10037
+ this.results.clear();
10038
+ for (const [key, source] of this.sources) {
10039
+ const hits = frame.getHitTestResults(source);
10040
+ if (!hits || hits.length === 0) continue;
10041
+ const pose = hits[0].getPose(referenceSpace);
10042
+ if (!pose) continue;
10043
+ const matrix = new THREE26__namespace.Matrix4().fromArray(
10044
+ pose.transform.matrix
10045
+ );
10046
+ const position = new THREE26__namespace.Vector3();
10047
+ const quaternion = new THREE26__namespace.Quaternion();
10048
+ matrix.decompose(position, quaternion, new THREE26__namespace.Vector3());
10049
+ this.results.set(key, { position, quaternion });
10050
+ }
10051
+ }
10052
+ /**
10053
+ * Where the given ray meets the real world this frame, or null.
10054
+ *
10055
+ * Null is the normal case, not an error: the source may still be being
10056
+ * requested, the ray may point at nothing recognised, or the runtime may not
10057
+ * have granted `hit-test` at all.
10058
+ */
10059
+ getHitResult(target = "viewer") {
10060
+ return this.results.get(target) ?? null;
10061
+ }
10062
+ rigWantsHitTest(world) {
10063
+ const ids = world.query(XRRigComponent);
10064
+ if (ids.length === 0) return false;
10065
+ return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
10066
+ }
10067
+ ensureViewerSource(session) {
10068
+ if (this.sources.has("viewer") || this.pending.has("viewer")) return;
10069
+ if (typeof session.requestHitTestSource !== "function") return;
10070
+ this.pending.add("viewer");
10071
+ void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
10072
+ if (source && this.presenting) this.sources.set("viewer", source);
10073
+ else source?.cancel?.();
10074
+ }).catch(() => {
10075
+ }).finally(() => this.pending.delete("viewer"));
10076
+ }
10077
+ ensureInputSources(session) {
10078
+ if (typeof session.requestHitTestSource !== "function") return;
10079
+ if (!session.inputSources) return;
10080
+ for (const input of session.inputSources) {
10081
+ const handedness = input.handedness;
10082
+ if (!handedness || handedness === "none") continue;
10083
+ if (!input.targetRaySpace) continue;
10084
+ if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
10085
+ this.pending.add(handedness);
10086
+ const request = session.requestHitTestSource({
10087
+ space: input.targetRaySpace
10088
+ });
10089
+ if (!request) {
10090
+ this.pending.delete(handedness);
10091
+ continue;
10092
+ }
10093
+ void request.then((source) => {
10094
+ if (source && this.presenting) this.sources.set(handedness, source);
10095
+ else source?.cancel?.();
10096
+ }).catch(() => {
10097
+ }).finally(() => this.pending.delete(handedness));
10098
+ }
10099
+ }
10100
+ teardown() {
10101
+ for (const source of this.sources.values()) source.cancel?.();
10102
+ this.sources.clear();
10103
+ this.results.clear();
10104
+ this.pending.clear();
10105
+ this.enabled = false;
10106
+ }
10107
+ dispose() {
10108
+ this.presenting = false;
10109
+ this.teardown();
10110
+ }
10111
+ };
10112
+ __name(_XRHitTestSystem, "XRHitTestSystem");
10113
+ var XRHitTestSystem = _XRHitTestSystem;
10114
+ var _XRAnchorSystem = class _XRAnchorSystem {
10115
+ constructor() {
10116
+ this.anchors = /* @__PURE__ */ new Map();
10117
+ /** In-flight creations, so one entity is not anchored twice. */
10118
+ this.pending = /* @__PURE__ */ new Set();
10119
+ this.presenting = false;
10120
+ }
10121
+ update(world) {
10122
+ const presenting = world.isXrPresenting();
10123
+ if (!presenting) {
10124
+ if (this.presenting) {
10125
+ this.presenting = false;
10126
+ this.releaseAll();
10127
+ }
10128
+ return;
10129
+ }
10130
+ this.presenting = true;
10131
+ const xr = world.getRenderer().xr;
10132
+ const referenceSpace = xr.getReferenceSpace();
10133
+ const frame = xr.getFrame?.();
10134
+ if (!frame || !referenceSpace) return;
10135
+ const entityIds = world.query(XRAnchorComponent, TransformComponent);
10136
+ const live = new Set(entityIds);
10137
+ for (const [entityId, anchor] of this.anchors) {
10138
+ if (!live.has(entityId)) {
10139
+ anchor.delete?.();
10140
+ this.anchors.delete(entityId);
10141
+ }
10142
+ }
10143
+ for (const entityId of entityIds) {
10144
+ const component = world.getComponent(entityId, XRAnchorComponent);
10145
+ const transform = world.getComponent(entityId, TransformComponent);
10146
+ if (!component || !transform) continue;
10147
+ if (!component.enabled) {
10148
+ const existing = this.anchors.get(entityId);
10149
+ if (existing) {
10150
+ existing.delete?.();
10151
+ this.anchors.delete(entityId);
10152
+ }
10153
+ continue;
10154
+ }
10155
+ const anchor = this.anchors.get(entityId);
10156
+ if (!anchor) {
10157
+ this.requestAnchor(entityId, transform, frame, referenceSpace);
10158
+ continue;
10159
+ }
10160
+ const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
10161
+ if (pose) decomposeXrPose(pose.transform.matrix, transform);
10162
+ }
10163
+ }
10164
+ /** Whether this entity's pose is currently being driven by an anchor. */
10165
+ isAnchored(entityId) {
10166
+ return this.anchors.has(entityId);
10167
+ }
10168
+ requestAnchor(entityId, transform, frame, referenceSpace) {
10169
+ if (this.pending.has(entityId)) return;
10170
+ if (typeof frame.createAnchor !== "function") return;
10171
+ const RigidTransform = globalThis.XRRigidTransform;
10172
+ if (!RigidTransform) return;
10173
+ const quaternion = new THREE26__namespace.Quaternion().setFromEuler(
10174
+ new THREE26__namespace.Euler(
10175
+ transform.rotation.x,
10176
+ transform.rotation.y,
10177
+ transform.rotation.z,
10178
+ "XYZ"
10179
+ )
10180
+ );
10181
+ const pose = new RigidTransform(
10182
+ {
10183
+ x: transform.position.x,
10184
+ y: transform.position.y,
10185
+ z: transform.position.z,
10186
+ w: 1
10187
+ },
10188
+ {
10189
+ x: quaternion.x,
10190
+ y: quaternion.y,
10191
+ z: quaternion.z,
10192
+ w: quaternion.w
10193
+ }
10194
+ );
10195
+ const request = frame.createAnchor(pose, referenceSpace);
10196
+ if (!request) return;
10197
+ this.pending.add(entityId);
10198
+ void request.then((anchor) => {
10199
+ if (anchor && this.presenting) this.anchors.set(entityId, anchor);
10200
+ else anchor?.delete?.();
10201
+ }).catch(() => {
10202
+ }).finally(() => this.pending.delete(entityId));
10203
+ }
10204
+ releaseAll() {
10205
+ for (const anchor of this.anchors.values()) anchor.delete?.();
10206
+ this.anchors.clear();
10207
+ this.pending.clear();
10208
+ }
10209
+ dispose() {
10210
+ this.presenting = false;
10211
+ this.releaseAll();
10212
+ }
10213
+ };
10214
+ __name(_XRAnchorSystem, "XRAnchorSystem");
10215
+ var XRAnchorSystem = _XRAnchorSystem;
9653
10216
  function buildGeometry2(area) {
9654
10217
  if (area.shape === "circle") {
9655
10218
  const radius = Math.max(5e-3, area.width / 2);
@@ -10905,8 +11468,11 @@ exports.ScriptObjectSystem = ScriptObjectSystem;
10905
11468
  exports.TransformSyncSystem = TransformSyncSystem;
10906
11469
  exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
10907
11470
  exports.VideoSystem = VideoSystem;
11471
+ exports.XRAnchorSystem = XRAnchorSystem;
10908
11472
  exports.XRControllerVisualSystem = XRControllerVisualSystem;
11473
+ exports.XREnvironmentSystem = XREnvironmentSystem;
10909
11474
  exports.XRHandPoseSystem = XRHandPoseSystem;
11475
+ exports.XRHitTestSystem = XRHitTestSystem;
10910
11476
  exports.XRHoverVisualSystem = XRHoverVisualSystem;
10911
11477
  exports.XRInputSystem = XRInputSystem;
10912
11478
  exports.XRInteractionSystem = XRInteractionSystem;