@relu-interactives/spatial-ecs 0.3.21 → 0.4.1

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 (46) hide show
  1. package/dist/{System-BhnLw0fZ.d.cts → System-ln4Q7Eot.d.cts} +15 -1
  2. package/dist/{System-BhnLw0fZ.d.ts → System-ln4Q7Eot.d.ts} +15 -1
  3. package/dist/{XRHandPose-CrzI57JA.d.ts → XRHandPose-BImIa4ZS.d.ts} +82 -2
  4. package/dist/{XRHandPose-CLl4MiHx.d.cts → XRHandPose-CxPv-LBo.d.cts} +82 -2
  5. package/dist/{chunk-H6DQ7JRE.js → chunk-4AK4I3QF.js} +33 -3
  6. package/dist/chunk-4AK4I3QF.js.map +1 -0
  7. package/dist/{chunk-D6KXWNYT.js → chunk-4DWZK4X4.js} +3 -3
  8. package/dist/{chunk-D6KXWNYT.js.map → chunk-4DWZK4X4.js.map} +1 -1
  9. package/dist/{chunk-CDSE7476.js → chunk-LAOFCFEI.js} +554 -5
  10. package/dist/chunk-LAOFCFEI.js.map +1 -0
  11. package/dist/{chunk-P42OE3RU.js → chunk-SGADE55G.js} +14 -5
  12. package/dist/chunk-SGADE55G.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/docs/guide/mixed-reality.md +120 -0
  19. package/dist/factories/index.cjs +26 -0
  20. package/dist/factories/index.cjs.map +1 -1
  21. package/dist/factories/index.d.cts +5 -3
  22. package/dist/factories/index.d.ts +5 -3
  23. package/dist/factories/index.js +3 -3
  24. package/dist/{index-C2I5PxCI.d.cts → index-CBdnIyI8.d.cts} +94 -2
  25. package/dist/{index-g777AmFv.d.cts → index-CQsHUez3.d.cts} +42 -4
  26. package/dist/{index-JRcoel9F.d.ts → index-CuGqxuk-.d.ts} +42 -4
  27. package/dist/{index-6l4hn2Sh.d.ts → index-n7SeH119.d.ts} +94 -2
  28. package/dist/index.cjs +610 -0
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +9 -8
  31. package/dist/index.d.ts +9 -8
  32. package/dist/index.js +26 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/systems/index.cjs +580 -0
  35. package/dist/systems/index.cjs.map +1 -1
  36. package/dist/systems/index.d.cts +2 -2
  37. package/dist/systems/index.d.ts +2 -2
  38. package/dist/systems/index.js +3 -3
  39. package/dist/types.d.cts +1 -1
  40. package/dist/types.d.ts +1 -1
  41. package/dist/worldSnapshotUtils.d.cts +1 -1
  42. package/dist/worldSnapshotUtils.d.ts +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-CDSE7476.js.map +0 -1
  45. package/dist/chunk-H6DQ7JRE.js.map +0 -1
  46. package/dist/chunk-P42OE3RU.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-D6KXWNYT.js';
2
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, createAttachPoint, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-H6DQ7JRE.js';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-4DWZK4X4.js';
2
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, createAttachPoint, XRHandPoseComponent, createHandPoseEntry, XRAnchorComponent, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, XREnvironmentComponent, handPoseDefinitionFor, getHandPosePreset } from './chunk-4AK4I3QF.js';
3
3
  import { __name } from './chunk-OY7ZODZP.js';
4
4
  import * as THREE19 from 'three';
5
5
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
@@ -633,6 +633,8 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
633
633
  this.signature = null;
634
634
  this.activeTexture = null;
635
635
  this.pendingImageUrl = null;
636
+ /** Whether the skybox is currently withheld because passthrough is on. */
637
+ this.passthroughSuppressed = false;
636
638
  this.scene = scene;
637
639
  }
638
640
  disposeActiveTexture() {
@@ -717,6 +719,18 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
717
719
  this.scene.fog = new THREE19.FogExp2(fogColor, density);
718
720
  }
719
721
  update(world) {
722
+ const passthrough = world.isXrPassthrough();
723
+ if (passthrough !== this.passthroughSuppressed) {
724
+ this.passthroughSuppressed = passthrough;
725
+ if (passthrough) {
726
+ this.scene.background = null;
727
+ } else {
728
+ this.signature = null;
729
+ }
730
+ }
731
+ if (passthrough) {
732
+ return;
733
+ }
720
734
  const entityIds = world.query(Environment);
721
735
  if (entityIds.length === 0) {
722
736
  if (this.signature !== null) {
@@ -2833,6 +2847,7 @@ function buildComponentValueMaps(snapshot) {
2833
2847
  snapshot.components,
2834
2848
  "XRHandPoseComponent"
2835
2849
  ),
2850
+ xrAnchorValues: readByComponent(snapshot.components, "XRAnchorComponent"),
2836
2851
  xrInteractorSideValues: readByComponent(
2837
2852
  snapshot.components,
2838
2853
  "XRInteractorSideComponent"
@@ -2986,6 +3001,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2986
3001
  if (typeof sourceXrRig.fallbackEyeHeight === "number") {
2987
3002
  rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
2988
3003
  }
3004
+ if (typeof sourceXrRig.passthrough === "boolean") {
3005
+ rig.passthrough = sourceXrRig.passthrough;
3006
+ }
3007
+ if (typeof sourceXrRig.depthOcclusion === "boolean") {
3008
+ rig.depthOcclusion = sourceXrRig.depthOcclusion;
3009
+ }
3010
+ if (typeof sourceXrRig.environmentPhysics === "string") {
3011
+ rig.environmentPhysics = sourceXrRig.environmentPhysics;
3012
+ }
3013
+ if (typeof sourceXrRig.environmentDebug === "boolean") {
3014
+ rig.environmentDebug = sourceXrRig.environmentDebug;
3015
+ }
3016
+ if (typeof sourceXrRig.hitTest === "boolean") {
3017
+ rig.hitTest = sourceXrRig.hitTest;
3018
+ }
3019
+ if (typeof sourceXrRig.anchors === "boolean") {
3020
+ rig.anchors = sourceXrRig.anchors;
3021
+ }
2989
3022
  }
2990
3023
  }
2991
3024
  {
@@ -3123,6 +3156,19 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
3123
3156
  }
3124
3157
  }
3125
3158
  }
3159
+ {
3160
+ const sourceAnchor = asRecord(values.xrAnchorValues.get(sourceEntityId));
3161
+ let anchor = world.getComponent(targetEntityId, XRAnchorComponent);
3162
+ if (sourceAnchor && !anchor) {
3163
+ anchor = new XRAnchorComponent();
3164
+ world.addComponent(targetEntityId, anchor);
3165
+ }
3166
+ if (sourceAnchor && anchor) {
3167
+ if (typeof sourceAnchor.enabled === "boolean") {
3168
+ anchor.enabled = sourceAnchor.enabled;
3169
+ }
3170
+ }
3171
+ }
3126
3172
  {
3127
3173
  const sourceInteractor = asRecord(
3128
3174
  values.xrInteractorValues.get(sourceEntityId)
@@ -7794,6 +7840,509 @@ var _XRSessionSystem = class _XRSessionSystem {
7794
7840
  };
7795
7841
  __name(_XRSessionSystem, "XRSessionSystem");
7796
7842
  var XRSessionSystem = _XRSessionSystem;
7843
+ var PLANE_COLLIDER_THICKNESS = 0.02;
7844
+ var MIN_PLANE_EXTENT = 0.01;
7845
+ function planeColliderFromPolygon(polygon) {
7846
+ if (!polygon || polygon.length < 3) return null;
7847
+ let minX = Number.POSITIVE_INFINITY;
7848
+ let maxX = Number.NEGATIVE_INFINITY;
7849
+ let minZ = Number.POSITIVE_INFINITY;
7850
+ let maxZ = Number.NEGATIVE_INFINITY;
7851
+ for (const point of polygon) {
7852
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
7853
+ minX = Math.min(minX, point.x);
7854
+ maxX = Math.max(maxX, point.x);
7855
+ minZ = Math.min(minZ, point.z);
7856
+ maxZ = Math.max(maxZ, point.z);
7857
+ }
7858
+ const width = maxX - minX;
7859
+ const depth = maxZ - minZ;
7860
+ if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
7861
+ return {
7862
+ // Thin on Y: that is the plane's normal in plane space.
7863
+ size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
7864
+ // The polygon is not necessarily centred on its own origin, so the box has
7865
+ // to be offset to sit over it. Pushed DOWN half its thickness so the
7866
+ // collider's top face lines up with the real surface — otherwise objects
7867
+ // rest a centimetre above the floor, or sink a centimetre into it.
7868
+ offset: {
7869
+ x: (minX + maxX) / 2,
7870
+ y: -PLANE_COLLIDER_THICKNESS / 2,
7871
+ z: (minZ + maxZ) / 2
7872
+ }
7873
+ };
7874
+ }
7875
+ __name(planeColliderFromPolygon, "planeColliderFromPolygon");
7876
+ function writeXrMeshGeometry(vertices, indices, existing) {
7877
+ const geometry = existing ?? new THREE19.BufferGeometry();
7878
+ geometry.setAttribute("position", new THREE19.BufferAttribute(vertices, 3));
7879
+ geometry.setIndex(new THREE19.BufferAttribute(indices, 1));
7880
+ geometry.computeBoundingSphere();
7881
+ geometry.computeVertexNormals();
7882
+ return geometry;
7883
+ }
7884
+ __name(writeXrMeshGeometry, "writeXrMeshGeometry");
7885
+ function decomposeXrPose(matrixElements, target) {
7886
+ const matrix = new THREE19.Matrix4().fromArray(
7887
+ matrixElements
7888
+ );
7889
+ const position = new THREE19.Vector3();
7890
+ const quaternion = new THREE19.Quaternion();
7891
+ const scale = new THREE19.Vector3();
7892
+ matrix.decompose(position, quaternion, scale);
7893
+ const euler = new THREE19.Euler().setFromQuaternion(quaternion, "XYZ");
7894
+ target.position.x = position.x;
7895
+ target.position.y = position.y;
7896
+ target.position.z = position.z;
7897
+ target.rotation.x = euler.x;
7898
+ target.rotation.y = euler.y;
7899
+ target.rotation.z = euler.z;
7900
+ }
7901
+ __name(decomposeXrPose, "decomposeXrPose");
7902
+
7903
+ // core/systems/XREnvironmentSystem.ts
7904
+ var DEBUG_COLOR = 5088255;
7905
+ var _XREnvironmentSystem = class _XREnvironmentSystem {
7906
+ constructor() {
7907
+ /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
7908
+ this.planes = /* @__PURE__ */ new Map();
7909
+ this.meshes = /* @__PURE__ */ new Map();
7910
+ this.presenting = false;
7911
+ this.debugVisible = false;
7912
+ }
7913
+ update(world) {
7914
+ const presenting = world.isXrPresenting();
7915
+ if (!presenting) {
7916
+ if (this.presenting) {
7917
+ this.presenting = false;
7918
+ this.clearAll(world);
7919
+ }
7920
+ return;
7921
+ }
7922
+ this.presenting = true;
7923
+ const rig = this.findRig(world);
7924
+ const mode = rig?.environmentPhysics ?? "none";
7925
+ if (mode === "none") {
7926
+ if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
7927
+ return;
7928
+ }
7929
+ const debug = rig?.environmentDebug === true;
7930
+ if (debug !== this.debugVisible) {
7931
+ this.debugVisible = debug;
7932
+ this.applyDebugVisibility(world);
7933
+ }
7934
+ const xr = world.getRenderer().xr;
7935
+ const frame = xr.getFrame?.();
7936
+ const referenceSpace = xr.getReferenceSpace();
7937
+ if (!frame || !referenceSpace) return;
7938
+ const meshesAvailable = Boolean(frame.detectedMeshes);
7939
+ const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
7940
+ const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
7941
+ if (usePlanes) {
7942
+ this.syncPlanes(world, frame, referenceSpace);
7943
+ } else if (this.planes.size > 0) {
7944
+ this.clearMap(world, this.planes);
7945
+ }
7946
+ if (useMesh) {
7947
+ this.syncMeshes(world, frame, referenceSpace);
7948
+ } else if (this.meshes.size > 0) {
7949
+ this.clearMap(world, this.meshes);
7950
+ }
7951
+ }
7952
+ /** How many pieces of real geometry are currently colliding. Script-facing. */
7953
+ getDetectedCount() {
7954
+ return { planes: this.planes.size, meshes: this.meshes.size };
7955
+ }
7956
+ findRig(world) {
7957
+ const ids = world.query(XRRigComponent);
7958
+ if (ids.length === 0) return null;
7959
+ return world.getComponent(ids[0], XRRigComponent) ?? null;
7960
+ }
7961
+ syncPlanes(world, frame, referenceSpace) {
7962
+ const detected = frame.detectedPlanes;
7963
+ if (!detected) return;
7964
+ for (const [plane, entry] of this.planes) {
7965
+ if (!detected.has(plane)) {
7966
+ this.destroyEntry(world, entry);
7967
+ this.planes.delete(plane);
7968
+ }
7969
+ }
7970
+ for (const plane of detected) {
7971
+ const pose = frame.getPose(plane.planeSpace, referenceSpace);
7972
+ if (!pose) continue;
7973
+ let entry = this.planes.get(plane);
7974
+ const changed = plane.lastChangedTime ?? 0;
7975
+ if (!entry) {
7976
+ const created = this.createPlaneEntity(world, plane);
7977
+ if (!created) continue;
7978
+ entry = created;
7979
+ this.planes.set(plane, entry);
7980
+ } else if (changed !== entry.lastChangedTime) {
7981
+ entry.lastChangedTime = changed;
7982
+ this.applyPlaneCollider(world, entry.entityId, plane);
7983
+ }
7984
+ this.applyPose(world, entry.entityId, pose);
7985
+ }
7986
+ }
7987
+ syncMeshes(world, frame, referenceSpace) {
7988
+ const detected = frame.detectedMeshes;
7989
+ if (!detected) return;
7990
+ for (const [mesh, entry] of this.meshes) {
7991
+ if (!detected.has(mesh)) {
7992
+ this.destroyEntry(world, entry);
7993
+ this.meshes.delete(mesh);
7994
+ }
7995
+ }
7996
+ for (const mesh of detected) {
7997
+ const pose = frame.getPose(mesh.meshSpace, referenceSpace);
7998
+ if (!pose) continue;
7999
+ let entry = this.meshes.get(mesh);
8000
+ const changed = mesh.lastChangedTime ?? 0;
8001
+ if (!entry) {
8002
+ const created = this.createMeshEntity(world, mesh);
8003
+ if (!created) continue;
8004
+ entry = created;
8005
+ this.meshes.set(mesh, entry);
8006
+ } else if (changed !== entry.lastChangedTime) {
8007
+ entry.lastChangedTime = changed;
8008
+ writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
8009
+ }
8010
+ this.applyPose(world, entry.entityId, pose);
8011
+ }
8012
+ }
8013
+ createPlaneEntity(world, plane) {
8014
+ const collider = planeColliderFromPolygon(plane.polygon);
8015
+ if (!collider) return null;
8016
+ const entityId = world.createEntity();
8017
+ world.addComponents(entityId, [
8018
+ new TransformComponent(),
8019
+ new XREnvironmentComponent(
8020
+ "plane",
8021
+ plane.semanticLabel ?? null,
8022
+ normalizeOrientation(plane.orientation)
8023
+ )
8024
+ ]);
8025
+ this.applyPlaneCollider(world, entityId, plane);
8026
+ const entry = {
8027
+ entityId,
8028
+ lastChangedTime: plane.lastChangedTime ?? 0,
8029
+ geometry: null,
8030
+ material: null
8031
+ };
8032
+ if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
8033
+ return entry;
8034
+ }
8035
+ applyPlaneCollider(world, entityId, plane) {
8036
+ const collider = planeColliderFromPolygon(plane.polygon);
8037
+ if (!collider) return;
8038
+ const existing = world.getComponent(entityId, PhysicsComponent);
8039
+ const values = {
8040
+ // Fixed: the real room does not move because something hit it.
8041
+ rigidbody: { type: "fixed" },
8042
+ collider: {
8043
+ shape: "cuboid",
8044
+ size: collider.size,
8045
+ offsetPosition: collider.offset
8046
+ }
8047
+ };
8048
+ if (existing) {
8049
+ existing.setValues(values);
8050
+ } else {
8051
+ world.addComponent(entityId, new PhysicsComponent(values));
8052
+ }
8053
+ }
8054
+ createMeshEntity(world, mesh) {
8055
+ if (!mesh.vertices?.length || !mesh.indices?.length) return null;
8056
+ const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
8057
+ const material = new THREE19.MeshBasicMaterial({
8058
+ color: DEBUG_COLOR,
8059
+ wireframe: true
8060
+ });
8061
+ const root = new THREE19.Group();
8062
+ const child = new THREE19.Mesh(geometry, material);
8063
+ child.visible = this.debugVisible;
8064
+ root.add(child);
8065
+ const entityId = world.createEntity();
8066
+ world.addComponents(entityId, [
8067
+ new TransformComponent(),
8068
+ new Object3DRef(root),
8069
+ new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
8070
+ new PhysicsComponent({
8071
+ rigidbody: { type: "fixed" },
8072
+ collider: { shape: "trimesh" }
8073
+ })
8074
+ ]);
8075
+ return {
8076
+ entityId,
8077
+ lastChangedTime: mesh.lastChangedTime ?? 0,
8078
+ geometry,
8079
+ material
8080
+ };
8081
+ }
8082
+ /** A thin box so a detected plane can be SEEN, not just collided with. */
8083
+ attachPlaneDebug(world, entry, collider) {
8084
+ const geometry = new THREE19.BoxGeometry(
8085
+ collider.size.x,
8086
+ collider.size.y,
8087
+ collider.size.z
8088
+ );
8089
+ const material = new THREE19.MeshBasicMaterial({
8090
+ color: DEBUG_COLOR,
8091
+ wireframe: true
8092
+ });
8093
+ const root = new THREE19.Group();
8094
+ root.add(new THREE19.Mesh(geometry, material));
8095
+ entry.geometry = geometry;
8096
+ entry.material = material;
8097
+ world.addComponent(entry.entityId, new Object3DRef(root));
8098
+ }
8099
+ applyPose(world, entityId, pose) {
8100
+ const transform = world.getComponent(entityId, TransformComponent);
8101
+ if (!transform) return;
8102
+ decomposeXrPose(pose.transform.matrix, transform);
8103
+ }
8104
+ applyDebugVisibility(world) {
8105
+ for (const map of [this.planes, this.meshes]) {
8106
+ for (const entry of map.values()) {
8107
+ const ref = world.getComponent(entry.entityId, Object3DRef);
8108
+ ref?.object.traverse((child) => {
8109
+ if (child.isMesh) child.visible = this.debugVisible;
8110
+ });
8111
+ }
8112
+ }
8113
+ }
8114
+ destroyEntry(world, entry) {
8115
+ entry.geometry?.dispose();
8116
+ entry.material?.dispose();
8117
+ world.destroyEntity(entry.entityId);
8118
+ }
8119
+ clearMap(world, map) {
8120
+ for (const entry of map.values()) this.destroyEntry(world, entry);
8121
+ map.clear();
8122
+ }
8123
+ clearAll(world) {
8124
+ this.clearMap(world, this.planes);
8125
+ this.clearMap(world, this.meshes);
8126
+ }
8127
+ dispose(world) {
8128
+ this.clearAll(world);
8129
+ }
8130
+ };
8131
+ __name(_XREnvironmentSystem, "XREnvironmentSystem");
8132
+ var XREnvironmentSystem = _XREnvironmentSystem;
8133
+ function normalizeOrientation(value) {
8134
+ return value === "horizontal" || value === "vertical" ? value : null;
8135
+ }
8136
+ __name(normalizeOrientation, "normalizeOrientation");
8137
+ var _XRHitTestSystem = class _XRHitTestSystem {
8138
+ constructor() {
8139
+ this.sources = /* @__PURE__ */ new Map();
8140
+ /** Targets whose async request is in flight, so it is not fired twice. */
8141
+ this.pending = /* @__PURE__ */ new Set();
8142
+ this.results = /* @__PURE__ */ new Map();
8143
+ this.presenting = false;
8144
+ this.enabled = false;
8145
+ }
8146
+ update(world) {
8147
+ const presenting = world.isXrPresenting();
8148
+ if (!presenting) {
8149
+ if (this.presenting) {
8150
+ this.presenting = false;
8151
+ this.teardown();
8152
+ }
8153
+ return;
8154
+ }
8155
+ if (!this.presenting) {
8156
+ this.presenting = true;
8157
+ this.enabled = this.rigWantsHitTest(world);
8158
+ }
8159
+ if (!this.enabled) return;
8160
+ const xr = world.getRenderer().xr;
8161
+ const session = xr.getSession();
8162
+ const referenceSpace = xr.getReferenceSpace();
8163
+ const frame = xr.getFrame?.();
8164
+ if (!session || !referenceSpace || !frame) return;
8165
+ this.ensureViewerSource(session);
8166
+ this.ensureInputSources(session);
8167
+ this.results.clear();
8168
+ for (const [key, source] of this.sources) {
8169
+ const hits = frame.getHitTestResults(source);
8170
+ if (!hits || hits.length === 0) continue;
8171
+ const pose = hits[0].getPose(referenceSpace);
8172
+ if (!pose) continue;
8173
+ const matrix = new THREE19.Matrix4().fromArray(
8174
+ pose.transform.matrix
8175
+ );
8176
+ const position = new THREE19.Vector3();
8177
+ const quaternion = new THREE19.Quaternion();
8178
+ matrix.decompose(position, quaternion, new THREE19.Vector3());
8179
+ this.results.set(key, { position, quaternion });
8180
+ }
8181
+ }
8182
+ /**
8183
+ * Where the given ray meets the real world this frame, or null.
8184
+ *
8185
+ * Null is the normal case, not an error: the source may still be being
8186
+ * requested, the ray may point at nothing recognised, or the runtime may not
8187
+ * have granted `hit-test` at all.
8188
+ */
8189
+ getHitResult(target = "viewer") {
8190
+ return this.results.get(target) ?? null;
8191
+ }
8192
+ rigWantsHitTest(world) {
8193
+ const ids = world.query(XRRigComponent);
8194
+ if (ids.length === 0) return false;
8195
+ return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
8196
+ }
8197
+ ensureViewerSource(session) {
8198
+ if (this.sources.has("viewer") || this.pending.has("viewer")) return;
8199
+ if (typeof session.requestHitTestSource !== "function") return;
8200
+ this.pending.add("viewer");
8201
+ void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
8202
+ if (source && this.presenting) this.sources.set("viewer", source);
8203
+ else source?.cancel?.();
8204
+ }).catch(() => {
8205
+ }).finally(() => this.pending.delete("viewer"));
8206
+ }
8207
+ ensureInputSources(session) {
8208
+ if (typeof session.requestHitTestSource !== "function") return;
8209
+ if (!session.inputSources) return;
8210
+ for (const input of session.inputSources) {
8211
+ const handedness = input.handedness;
8212
+ if (!handedness || handedness === "none") continue;
8213
+ if (!input.targetRaySpace) continue;
8214
+ if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
8215
+ this.pending.add(handedness);
8216
+ const request = session.requestHitTestSource({
8217
+ space: input.targetRaySpace
8218
+ });
8219
+ if (!request) {
8220
+ this.pending.delete(handedness);
8221
+ continue;
8222
+ }
8223
+ void request.then((source) => {
8224
+ if (source && this.presenting) this.sources.set(handedness, source);
8225
+ else source?.cancel?.();
8226
+ }).catch(() => {
8227
+ }).finally(() => this.pending.delete(handedness));
8228
+ }
8229
+ }
8230
+ teardown() {
8231
+ for (const source of this.sources.values()) source.cancel?.();
8232
+ this.sources.clear();
8233
+ this.results.clear();
8234
+ this.pending.clear();
8235
+ this.enabled = false;
8236
+ }
8237
+ dispose() {
8238
+ this.presenting = false;
8239
+ this.teardown();
8240
+ }
8241
+ };
8242
+ __name(_XRHitTestSystem, "XRHitTestSystem");
8243
+ var XRHitTestSystem = _XRHitTestSystem;
8244
+ var _XRAnchorSystem = class _XRAnchorSystem {
8245
+ constructor() {
8246
+ this.anchors = /* @__PURE__ */ new Map();
8247
+ /** In-flight creations, so one entity is not anchored twice. */
8248
+ this.pending = /* @__PURE__ */ new Set();
8249
+ this.presenting = false;
8250
+ }
8251
+ update(world) {
8252
+ const presenting = world.isXrPresenting();
8253
+ if (!presenting) {
8254
+ if (this.presenting) {
8255
+ this.presenting = false;
8256
+ this.releaseAll();
8257
+ }
8258
+ return;
8259
+ }
8260
+ this.presenting = true;
8261
+ const xr = world.getRenderer().xr;
8262
+ const referenceSpace = xr.getReferenceSpace();
8263
+ const frame = xr.getFrame?.();
8264
+ if (!frame || !referenceSpace) return;
8265
+ const entityIds = world.query(XRAnchorComponent, TransformComponent);
8266
+ const live = new Set(entityIds);
8267
+ for (const [entityId, anchor] of this.anchors) {
8268
+ if (!live.has(entityId)) {
8269
+ anchor.delete?.();
8270
+ this.anchors.delete(entityId);
8271
+ }
8272
+ }
8273
+ for (const entityId of entityIds) {
8274
+ const component = world.getComponent(entityId, XRAnchorComponent);
8275
+ const transform = world.getComponent(entityId, TransformComponent);
8276
+ if (!component || !transform) continue;
8277
+ if (!component.enabled) {
8278
+ const existing = this.anchors.get(entityId);
8279
+ if (existing) {
8280
+ existing.delete?.();
8281
+ this.anchors.delete(entityId);
8282
+ }
8283
+ continue;
8284
+ }
8285
+ const anchor = this.anchors.get(entityId);
8286
+ if (!anchor) {
8287
+ this.requestAnchor(entityId, transform, frame, referenceSpace);
8288
+ continue;
8289
+ }
8290
+ const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
8291
+ if (pose) decomposeXrPose(pose.transform.matrix, transform);
8292
+ }
8293
+ }
8294
+ /** Whether this entity's pose is currently being driven by an anchor. */
8295
+ isAnchored(entityId) {
8296
+ return this.anchors.has(entityId);
8297
+ }
8298
+ requestAnchor(entityId, transform, frame, referenceSpace) {
8299
+ if (this.pending.has(entityId)) return;
8300
+ if (typeof frame.createAnchor !== "function") return;
8301
+ const RigidTransform = globalThis.XRRigidTransform;
8302
+ if (!RigidTransform) return;
8303
+ const quaternion = new THREE19.Quaternion().setFromEuler(
8304
+ new THREE19.Euler(
8305
+ transform.rotation.x,
8306
+ transform.rotation.y,
8307
+ transform.rotation.z,
8308
+ "XYZ"
8309
+ )
8310
+ );
8311
+ const pose = new RigidTransform(
8312
+ {
8313
+ x: transform.position.x,
8314
+ y: transform.position.y,
8315
+ z: transform.position.z,
8316
+ w: 1
8317
+ },
8318
+ {
8319
+ x: quaternion.x,
8320
+ y: quaternion.y,
8321
+ z: quaternion.z,
8322
+ w: quaternion.w
8323
+ }
8324
+ );
8325
+ const request = frame.createAnchor(pose, referenceSpace);
8326
+ if (!request) return;
8327
+ this.pending.add(entityId);
8328
+ void request.then((anchor) => {
8329
+ if (anchor && this.presenting) this.anchors.set(entityId, anchor);
8330
+ else anchor?.delete?.();
8331
+ }).catch(() => {
8332
+ }).finally(() => this.pending.delete(entityId));
8333
+ }
8334
+ releaseAll() {
8335
+ for (const anchor of this.anchors.values()) anchor.delete?.();
8336
+ this.anchors.clear();
8337
+ this.pending.clear();
8338
+ }
8339
+ dispose() {
8340
+ this.presenting = false;
8341
+ this.releaseAll();
8342
+ }
8343
+ };
8344
+ __name(_XRAnchorSystem, "XRAnchorSystem");
8345
+ var XRAnchorSystem = _XRAnchorSystem;
7797
8346
  function buildGeometry2(area) {
7798
8347
  if (area.shape === "circle") {
7799
8348
  const radius = Math.max(5e-3, area.width / 2);
@@ -9028,6 +9577,6 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9028
9577
  __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9029
9578
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9030
9579
 
9031
- 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, XRControllerVisualSystem, XRHandPoseSystem, 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 };
9032
- //# sourceMappingURL=chunk-CDSE7476.js.map
9033
- //# sourceMappingURL=chunk-CDSE7476.js.map
9580
+ 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 };
9581
+ //# sourceMappingURL=chunk-LAOFCFEI.js.map
9582
+ //# sourceMappingURL=chunk-LAOFCFEI.js.map