@relu-interactives/spatial-ecs 0.3.20 → 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 (77) 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/docs/guide/accessing-components.md +7 -6
  19. package/dist/docs/guide/behaviour-data-binding/component-input.md +10 -6
  20. package/dist/docs/guide/behaviour-data-binding/entity-input.md +13 -5
  21. package/dist/docs/guide/camera-rigs.md +2 -2
  22. package/dist/docs/guide/components/animation.md +12 -5
  23. package/dist/docs/guide/components/audio.md +13 -6
  24. package/dist/docs/guide/components/camera.md +10 -4
  25. package/dist/docs/guide/components/entity-type.md +13 -6
  26. package/dist/docs/guide/components/image-target-anchor.md +6 -2
  27. package/dist/docs/guide/components/image-target.md +10 -5
  28. package/dist/docs/guide/components/image.md +10 -4
  29. package/dist/docs/guide/components/instance-slot.md +7 -3
  30. package/dist/docs/guide/components/instanced-mesh.md +15 -7
  31. package/dist/docs/guide/components/light.md +10 -4
  32. package/dist/docs/guide/components/material.md +14 -8
  33. package/dist/docs/guide/components/mesh-geometry.md +10 -4
  34. package/dist/docs/guide/components/mesh.md +10 -4
  35. package/dist/docs/guide/components/model.md +4 -2
  36. package/dist/docs/guide/components/name.md +11 -6
  37. package/dist/docs/guide/components/object3d-ref.md +7 -3
  38. package/dist/docs/guide/components/parent-id.md +10 -4
  39. package/dist/docs/guide/components/physics.md +12 -6
  40. package/dist/docs/guide/components/script.md +10 -4
  41. package/dist/docs/guide/components/sprite.md +10 -4
  42. package/dist/docs/guide/components/transform.md +6 -2
  43. package/dist/docs/guide/components/video.md +10 -4
  44. package/dist/docs/guide/prefabs.md +2 -0
  45. package/dist/docs/guide/runtime-entity-management.md +1 -1
  46. package/dist/docs/guide/spawning/instanced-mesh.md +11 -6
  47. package/dist/docs/guide/systems/input-system.md +4 -1
  48. package/dist/docs/guide/systems/script-object-system.md +9 -1
  49. package/dist/docs/guide/vr-and-hand-poses.md +7 -3
  50. package/dist/docs/guide/world-api.md +23 -15
  51. package/dist/factories/index.cjs +6 -0
  52. package/dist/factories/index.cjs.map +1 -1
  53. package/dist/factories/index.d.cts +2 -2
  54. package/dist/factories/index.d.ts +2 -2
  55. package/dist/factories/index.js +3 -3
  56. package/dist/{index-6l4hn2Sh.d.ts → index-BAeeVJ6f.d.ts} +94 -2
  57. package/dist/{index-g777AmFv.d.cts → index-BRATaqTT.d.ts} +78 -4
  58. package/dist/{index-JRcoel9F.d.ts → index-C1rprGz_.d.cts} +78 -4
  59. package/dist/{index-C2I5PxCI.d.cts → index-SC9Mgjht.d.cts} +94 -2
  60. package/dist/index.cjs +586 -0
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +7 -7
  63. package/dist/index.d.ts +7 -7
  64. package/dist/index.js +26 -8
  65. package/dist/index.js.map +1 -1
  66. package/dist/systems/index.cjs +566 -0
  67. package/dist/systems/index.cjs.map +1 -1
  68. package/dist/systems/index.d.cts +2 -2
  69. package/dist/systems/index.d.ts +2 -2
  70. package/dist/systems/index.js +3 -3
  71. package/dist/types.d.cts +1 -1
  72. package/dist/types.d.ts +1 -1
  73. package/dist/worldSnapshotUtils.d.cts +1 -1
  74. package/dist/worldSnapshotUtils.d.ts +1 -1
  75. package/package.json +4 -2
  76. package/dist/chunk-CDSE7476.js.map +0 -1
  77. package/dist/chunk-H6DQ7JRE.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-2GTFFLGG.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, XREnvironmentComponent, XRAnchorComponent, handPoseDefinitionFor, getHandPosePreset } from './chunk-ZWONFM2F.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) {
@@ -2986,6 +3000,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2986
3000
  if (typeof sourceXrRig.fallbackEyeHeight === "number") {
2987
3001
  rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
2988
3002
  }
3003
+ if (typeof sourceXrRig.passthrough === "boolean") {
3004
+ rig.passthrough = sourceXrRig.passthrough;
3005
+ }
3006
+ if (typeof sourceXrRig.depthOcclusion === "boolean") {
3007
+ rig.depthOcclusion = sourceXrRig.depthOcclusion;
3008
+ }
3009
+ if (typeof sourceXrRig.environmentPhysics === "string") {
3010
+ rig.environmentPhysics = sourceXrRig.environmentPhysics;
3011
+ }
3012
+ if (typeof sourceXrRig.environmentDebug === "boolean") {
3013
+ rig.environmentDebug = sourceXrRig.environmentDebug;
3014
+ }
3015
+ if (typeof sourceXrRig.hitTest === "boolean") {
3016
+ rig.hitTest = sourceXrRig.hitTest;
3017
+ }
3018
+ if (typeof sourceXrRig.anchors === "boolean") {
3019
+ rig.anchors = sourceXrRig.anchors;
3020
+ }
2989
3021
  }
2990
3022
  }
2991
3023
  {
@@ -7794,6 +7826,509 @@ var _XRSessionSystem = class _XRSessionSystem {
7794
7826
  };
7795
7827
  __name(_XRSessionSystem, "XRSessionSystem");
7796
7828
  var XRSessionSystem = _XRSessionSystem;
7829
+ var PLANE_COLLIDER_THICKNESS = 0.02;
7830
+ var MIN_PLANE_EXTENT = 0.01;
7831
+ function planeColliderFromPolygon(polygon) {
7832
+ if (!polygon || polygon.length < 3) return null;
7833
+ let minX = Number.POSITIVE_INFINITY;
7834
+ let maxX = Number.NEGATIVE_INFINITY;
7835
+ let minZ = Number.POSITIVE_INFINITY;
7836
+ let maxZ = Number.NEGATIVE_INFINITY;
7837
+ for (const point of polygon) {
7838
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
7839
+ minX = Math.min(minX, point.x);
7840
+ maxX = Math.max(maxX, point.x);
7841
+ minZ = Math.min(minZ, point.z);
7842
+ maxZ = Math.max(maxZ, point.z);
7843
+ }
7844
+ const width = maxX - minX;
7845
+ const depth = maxZ - minZ;
7846
+ if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
7847
+ return {
7848
+ // Thin on Y: that is the plane's normal in plane space.
7849
+ size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
7850
+ // The polygon is not necessarily centred on its own origin, so the box has
7851
+ // to be offset to sit over it. Pushed DOWN half its thickness so the
7852
+ // collider's top face lines up with the real surface — otherwise objects
7853
+ // rest a centimetre above the floor, or sink a centimetre into it.
7854
+ offset: {
7855
+ x: (minX + maxX) / 2,
7856
+ y: -PLANE_COLLIDER_THICKNESS / 2,
7857
+ z: (minZ + maxZ) / 2
7858
+ }
7859
+ };
7860
+ }
7861
+ __name(planeColliderFromPolygon, "planeColliderFromPolygon");
7862
+ function writeXrMeshGeometry(vertices, indices, existing) {
7863
+ const geometry = existing ?? new THREE19.BufferGeometry();
7864
+ geometry.setAttribute("position", new THREE19.BufferAttribute(vertices, 3));
7865
+ geometry.setIndex(new THREE19.BufferAttribute(indices, 1));
7866
+ geometry.computeBoundingSphere();
7867
+ geometry.computeVertexNormals();
7868
+ return geometry;
7869
+ }
7870
+ __name(writeXrMeshGeometry, "writeXrMeshGeometry");
7871
+ function decomposeXrPose(matrixElements, target) {
7872
+ const matrix = new THREE19.Matrix4().fromArray(
7873
+ matrixElements
7874
+ );
7875
+ const position = new THREE19.Vector3();
7876
+ const quaternion = new THREE19.Quaternion();
7877
+ const scale = new THREE19.Vector3();
7878
+ matrix.decompose(position, quaternion, scale);
7879
+ const euler = new THREE19.Euler().setFromQuaternion(quaternion, "XYZ");
7880
+ target.position.x = position.x;
7881
+ target.position.y = position.y;
7882
+ target.position.z = position.z;
7883
+ target.rotation.x = euler.x;
7884
+ target.rotation.y = euler.y;
7885
+ target.rotation.z = euler.z;
7886
+ }
7887
+ __name(decomposeXrPose, "decomposeXrPose");
7888
+
7889
+ // core/systems/XREnvironmentSystem.ts
7890
+ var DEBUG_COLOR = 5088255;
7891
+ var _XREnvironmentSystem = class _XREnvironmentSystem {
7892
+ constructor() {
7893
+ /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
7894
+ this.planes = /* @__PURE__ */ new Map();
7895
+ this.meshes = /* @__PURE__ */ new Map();
7896
+ this.presenting = false;
7897
+ this.debugVisible = false;
7898
+ }
7899
+ update(world) {
7900
+ const presenting = world.isXrPresenting();
7901
+ if (!presenting) {
7902
+ if (this.presenting) {
7903
+ this.presenting = false;
7904
+ this.clearAll(world);
7905
+ }
7906
+ return;
7907
+ }
7908
+ this.presenting = true;
7909
+ const rig = this.findRig(world);
7910
+ const mode = rig?.environmentPhysics ?? "none";
7911
+ if (mode === "none") {
7912
+ if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
7913
+ return;
7914
+ }
7915
+ const debug = rig?.environmentDebug === true;
7916
+ if (debug !== this.debugVisible) {
7917
+ this.debugVisible = debug;
7918
+ this.applyDebugVisibility(world);
7919
+ }
7920
+ const xr = world.getRenderer().xr;
7921
+ const frame = xr.getFrame?.();
7922
+ const referenceSpace = xr.getReferenceSpace();
7923
+ if (!frame || !referenceSpace) return;
7924
+ const meshesAvailable = Boolean(frame.detectedMeshes);
7925
+ const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
7926
+ const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
7927
+ if (usePlanes) {
7928
+ this.syncPlanes(world, frame, referenceSpace);
7929
+ } else if (this.planes.size > 0) {
7930
+ this.clearMap(world, this.planes);
7931
+ }
7932
+ if (useMesh) {
7933
+ this.syncMeshes(world, frame, referenceSpace);
7934
+ } else if (this.meshes.size > 0) {
7935
+ this.clearMap(world, this.meshes);
7936
+ }
7937
+ }
7938
+ /** How many pieces of real geometry are currently colliding. Script-facing. */
7939
+ getDetectedCount() {
7940
+ return { planes: this.planes.size, meshes: this.meshes.size };
7941
+ }
7942
+ findRig(world) {
7943
+ const ids = world.query(XRRigComponent);
7944
+ if (ids.length === 0) return null;
7945
+ return world.getComponent(ids[0], XRRigComponent) ?? null;
7946
+ }
7947
+ syncPlanes(world, frame, referenceSpace) {
7948
+ const detected = frame.detectedPlanes;
7949
+ if (!detected) return;
7950
+ for (const [plane, entry] of this.planes) {
7951
+ if (!detected.has(plane)) {
7952
+ this.destroyEntry(world, entry);
7953
+ this.planes.delete(plane);
7954
+ }
7955
+ }
7956
+ for (const plane of detected) {
7957
+ const pose = frame.getPose(plane.planeSpace, referenceSpace);
7958
+ if (!pose) continue;
7959
+ let entry = this.planes.get(plane);
7960
+ const changed = plane.lastChangedTime ?? 0;
7961
+ if (!entry) {
7962
+ const created = this.createPlaneEntity(world, plane);
7963
+ if (!created) continue;
7964
+ entry = created;
7965
+ this.planes.set(plane, entry);
7966
+ } else if (changed !== entry.lastChangedTime) {
7967
+ entry.lastChangedTime = changed;
7968
+ this.applyPlaneCollider(world, entry.entityId, plane);
7969
+ }
7970
+ this.applyPose(world, entry.entityId, pose);
7971
+ }
7972
+ }
7973
+ syncMeshes(world, frame, referenceSpace) {
7974
+ const detected = frame.detectedMeshes;
7975
+ if (!detected) return;
7976
+ for (const [mesh, entry] of this.meshes) {
7977
+ if (!detected.has(mesh)) {
7978
+ this.destroyEntry(world, entry);
7979
+ this.meshes.delete(mesh);
7980
+ }
7981
+ }
7982
+ for (const mesh of detected) {
7983
+ const pose = frame.getPose(mesh.meshSpace, referenceSpace);
7984
+ if (!pose) continue;
7985
+ let entry = this.meshes.get(mesh);
7986
+ const changed = mesh.lastChangedTime ?? 0;
7987
+ if (!entry) {
7988
+ const created = this.createMeshEntity(world, mesh);
7989
+ if (!created) continue;
7990
+ entry = created;
7991
+ this.meshes.set(mesh, entry);
7992
+ } else if (changed !== entry.lastChangedTime) {
7993
+ entry.lastChangedTime = changed;
7994
+ writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
7995
+ }
7996
+ this.applyPose(world, entry.entityId, pose);
7997
+ }
7998
+ }
7999
+ createPlaneEntity(world, plane) {
8000
+ const collider = planeColliderFromPolygon(plane.polygon);
8001
+ if (!collider) return null;
8002
+ const entityId = world.createEntity();
8003
+ world.addComponents(entityId, [
8004
+ new TransformComponent(),
8005
+ new XREnvironmentComponent(
8006
+ "plane",
8007
+ plane.semanticLabel ?? null,
8008
+ normalizeOrientation(plane.orientation)
8009
+ )
8010
+ ]);
8011
+ this.applyPlaneCollider(world, entityId, plane);
8012
+ const entry = {
8013
+ entityId,
8014
+ lastChangedTime: plane.lastChangedTime ?? 0,
8015
+ geometry: null,
8016
+ material: null
8017
+ };
8018
+ if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
8019
+ return entry;
8020
+ }
8021
+ applyPlaneCollider(world, entityId, plane) {
8022
+ const collider = planeColliderFromPolygon(plane.polygon);
8023
+ if (!collider) return;
8024
+ const existing = world.getComponent(entityId, PhysicsComponent);
8025
+ const values = {
8026
+ // Fixed: the real room does not move because something hit it.
8027
+ rigidbody: { type: "fixed" },
8028
+ collider: {
8029
+ shape: "cuboid",
8030
+ size: collider.size,
8031
+ offsetPosition: collider.offset
8032
+ }
8033
+ };
8034
+ if (existing) {
8035
+ existing.setValues(values);
8036
+ } else {
8037
+ world.addComponent(entityId, new PhysicsComponent(values));
8038
+ }
8039
+ }
8040
+ createMeshEntity(world, mesh) {
8041
+ if (!mesh.vertices?.length || !mesh.indices?.length) return null;
8042
+ const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
8043
+ const material = new THREE19.MeshBasicMaterial({
8044
+ color: DEBUG_COLOR,
8045
+ wireframe: true
8046
+ });
8047
+ const root = new THREE19.Group();
8048
+ const child = new THREE19.Mesh(geometry, material);
8049
+ child.visible = this.debugVisible;
8050
+ root.add(child);
8051
+ const entityId = world.createEntity();
8052
+ world.addComponents(entityId, [
8053
+ new TransformComponent(),
8054
+ new Object3DRef(root),
8055
+ new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
8056
+ new PhysicsComponent({
8057
+ rigidbody: { type: "fixed" },
8058
+ collider: { shape: "trimesh" }
8059
+ })
8060
+ ]);
8061
+ return {
8062
+ entityId,
8063
+ lastChangedTime: mesh.lastChangedTime ?? 0,
8064
+ geometry,
8065
+ material
8066
+ };
8067
+ }
8068
+ /** A thin box so a detected plane can be SEEN, not just collided with. */
8069
+ attachPlaneDebug(world, entry, collider) {
8070
+ const geometry = new THREE19.BoxGeometry(
8071
+ collider.size.x,
8072
+ collider.size.y,
8073
+ collider.size.z
8074
+ );
8075
+ const material = new THREE19.MeshBasicMaterial({
8076
+ color: DEBUG_COLOR,
8077
+ wireframe: true
8078
+ });
8079
+ const root = new THREE19.Group();
8080
+ root.add(new THREE19.Mesh(geometry, material));
8081
+ entry.geometry = geometry;
8082
+ entry.material = material;
8083
+ world.addComponent(entry.entityId, new Object3DRef(root));
8084
+ }
8085
+ applyPose(world, entityId, pose) {
8086
+ const transform = world.getComponent(entityId, TransformComponent);
8087
+ if (!transform) return;
8088
+ decomposeXrPose(pose.transform.matrix, transform);
8089
+ }
8090
+ applyDebugVisibility(world) {
8091
+ for (const map of [this.planes, this.meshes]) {
8092
+ for (const entry of map.values()) {
8093
+ const ref = world.getComponent(entry.entityId, Object3DRef);
8094
+ ref?.object.traverse((child) => {
8095
+ if (child.isMesh) child.visible = this.debugVisible;
8096
+ });
8097
+ }
8098
+ }
8099
+ }
8100
+ destroyEntry(world, entry) {
8101
+ entry.geometry?.dispose();
8102
+ entry.material?.dispose();
8103
+ world.destroyEntity(entry.entityId);
8104
+ }
8105
+ clearMap(world, map) {
8106
+ for (const entry of map.values()) this.destroyEntry(world, entry);
8107
+ map.clear();
8108
+ }
8109
+ clearAll(world) {
8110
+ this.clearMap(world, this.planes);
8111
+ this.clearMap(world, this.meshes);
8112
+ }
8113
+ dispose(world) {
8114
+ this.clearAll(world);
8115
+ }
8116
+ };
8117
+ __name(_XREnvironmentSystem, "XREnvironmentSystem");
8118
+ var XREnvironmentSystem = _XREnvironmentSystem;
8119
+ function normalizeOrientation(value) {
8120
+ return value === "horizontal" || value === "vertical" ? value : null;
8121
+ }
8122
+ __name(normalizeOrientation, "normalizeOrientation");
8123
+ var _XRHitTestSystem = class _XRHitTestSystem {
8124
+ constructor() {
8125
+ this.sources = /* @__PURE__ */ new Map();
8126
+ /** Targets whose async request is in flight, so it is not fired twice. */
8127
+ this.pending = /* @__PURE__ */ new Set();
8128
+ this.results = /* @__PURE__ */ new Map();
8129
+ this.presenting = false;
8130
+ this.enabled = false;
8131
+ }
8132
+ update(world) {
8133
+ const presenting = world.isXrPresenting();
8134
+ if (!presenting) {
8135
+ if (this.presenting) {
8136
+ this.presenting = false;
8137
+ this.teardown();
8138
+ }
8139
+ return;
8140
+ }
8141
+ if (!this.presenting) {
8142
+ this.presenting = true;
8143
+ this.enabled = this.rigWantsHitTest(world);
8144
+ }
8145
+ if (!this.enabled) return;
8146
+ const xr = world.getRenderer().xr;
8147
+ const session = xr.getSession();
8148
+ const referenceSpace = xr.getReferenceSpace();
8149
+ const frame = xr.getFrame?.();
8150
+ if (!session || !referenceSpace || !frame) return;
8151
+ this.ensureViewerSource(session);
8152
+ this.ensureInputSources(session);
8153
+ this.results.clear();
8154
+ for (const [key, source] of this.sources) {
8155
+ const hits = frame.getHitTestResults(source);
8156
+ if (!hits || hits.length === 0) continue;
8157
+ const pose = hits[0].getPose(referenceSpace);
8158
+ if (!pose) continue;
8159
+ const matrix = new THREE19.Matrix4().fromArray(
8160
+ pose.transform.matrix
8161
+ );
8162
+ const position = new THREE19.Vector3();
8163
+ const quaternion = new THREE19.Quaternion();
8164
+ matrix.decompose(position, quaternion, new THREE19.Vector3());
8165
+ this.results.set(key, { position, quaternion });
8166
+ }
8167
+ }
8168
+ /**
8169
+ * Where the given ray meets the real world this frame, or null.
8170
+ *
8171
+ * Null is the normal case, not an error: the source may still be being
8172
+ * requested, the ray may point at nothing recognised, or the runtime may not
8173
+ * have granted `hit-test` at all.
8174
+ */
8175
+ getHitResult(target = "viewer") {
8176
+ return this.results.get(target) ?? null;
8177
+ }
8178
+ rigWantsHitTest(world) {
8179
+ const ids = world.query(XRRigComponent);
8180
+ if (ids.length === 0) return false;
8181
+ return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
8182
+ }
8183
+ ensureViewerSource(session) {
8184
+ if (this.sources.has("viewer") || this.pending.has("viewer")) return;
8185
+ if (typeof session.requestHitTestSource !== "function") return;
8186
+ this.pending.add("viewer");
8187
+ void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
8188
+ if (source && this.presenting) this.sources.set("viewer", source);
8189
+ else source?.cancel?.();
8190
+ }).catch(() => {
8191
+ }).finally(() => this.pending.delete("viewer"));
8192
+ }
8193
+ ensureInputSources(session) {
8194
+ if (typeof session.requestHitTestSource !== "function") return;
8195
+ if (!session.inputSources) return;
8196
+ for (const input of session.inputSources) {
8197
+ const handedness = input.handedness;
8198
+ if (!handedness || handedness === "none") continue;
8199
+ if (!input.targetRaySpace) continue;
8200
+ if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
8201
+ this.pending.add(handedness);
8202
+ const request = session.requestHitTestSource({
8203
+ space: input.targetRaySpace
8204
+ });
8205
+ if (!request) {
8206
+ this.pending.delete(handedness);
8207
+ continue;
8208
+ }
8209
+ void request.then((source) => {
8210
+ if (source && this.presenting) this.sources.set(handedness, source);
8211
+ else source?.cancel?.();
8212
+ }).catch(() => {
8213
+ }).finally(() => this.pending.delete(handedness));
8214
+ }
8215
+ }
8216
+ teardown() {
8217
+ for (const source of this.sources.values()) source.cancel?.();
8218
+ this.sources.clear();
8219
+ this.results.clear();
8220
+ this.pending.clear();
8221
+ this.enabled = false;
8222
+ }
8223
+ dispose() {
8224
+ this.presenting = false;
8225
+ this.teardown();
8226
+ }
8227
+ };
8228
+ __name(_XRHitTestSystem, "XRHitTestSystem");
8229
+ var XRHitTestSystem = _XRHitTestSystem;
8230
+ var _XRAnchorSystem = class _XRAnchorSystem {
8231
+ constructor() {
8232
+ this.anchors = /* @__PURE__ */ new Map();
8233
+ /** In-flight creations, so one entity is not anchored twice. */
8234
+ this.pending = /* @__PURE__ */ new Set();
8235
+ this.presenting = false;
8236
+ }
8237
+ update(world) {
8238
+ const presenting = world.isXrPresenting();
8239
+ if (!presenting) {
8240
+ if (this.presenting) {
8241
+ this.presenting = false;
8242
+ this.releaseAll();
8243
+ }
8244
+ return;
8245
+ }
8246
+ this.presenting = true;
8247
+ const xr = world.getRenderer().xr;
8248
+ const referenceSpace = xr.getReferenceSpace();
8249
+ const frame = xr.getFrame?.();
8250
+ if (!frame || !referenceSpace) return;
8251
+ const entityIds = world.query(XRAnchorComponent, TransformComponent);
8252
+ const live = new Set(entityIds);
8253
+ for (const [entityId, anchor] of this.anchors) {
8254
+ if (!live.has(entityId)) {
8255
+ anchor.delete?.();
8256
+ this.anchors.delete(entityId);
8257
+ }
8258
+ }
8259
+ for (const entityId of entityIds) {
8260
+ const component = world.getComponent(entityId, XRAnchorComponent);
8261
+ const transform = world.getComponent(entityId, TransformComponent);
8262
+ if (!component || !transform) continue;
8263
+ if (!component.enabled) {
8264
+ const existing = this.anchors.get(entityId);
8265
+ if (existing) {
8266
+ existing.delete?.();
8267
+ this.anchors.delete(entityId);
8268
+ }
8269
+ continue;
8270
+ }
8271
+ const anchor = this.anchors.get(entityId);
8272
+ if (!anchor) {
8273
+ this.requestAnchor(entityId, transform, frame, referenceSpace);
8274
+ continue;
8275
+ }
8276
+ const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
8277
+ if (pose) decomposeXrPose(pose.transform.matrix, transform);
8278
+ }
8279
+ }
8280
+ /** Whether this entity's pose is currently being driven by an anchor. */
8281
+ isAnchored(entityId) {
8282
+ return this.anchors.has(entityId);
8283
+ }
8284
+ requestAnchor(entityId, transform, frame, referenceSpace) {
8285
+ if (this.pending.has(entityId)) return;
8286
+ if (typeof frame.createAnchor !== "function") return;
8287
+ const RigidTransform = globalThis.XRRigidTransform;
8288
+ if (!RigidTransform) return;
8289
+ const quaternion = new THREE19.Quaternion().setFromEuler(
8290
+ new THREE19.Euler(
8291
+ transform.rotation.x,
8292
+ transform.rotation.y,
8293
+ transform.rotation.z,
8294
+ "XYZ"
8295
+ )
8296
+ );
8297
+ const pose = new RigidTransform(
8298
+ {
8299
+ x: transform.position.x,
8300
+ y: transform.position.y,
8301
+ z: transform.position.z,
8302
+ w: 1
8303
+ },
8304
+ {
8305
+ x: quaternion.x,
8306
+ y: quaternion.y,
8307
+ z: quaternion.z,
8308
+ w: quaternion.w
8309
+ }
8310
+ );
8311
+ const request = frame.createAnchor(pose, referenceSpace);
8312
+ if (!request) return;
8313
+ this.pending.add(entityId);
8314
+ void request.then((anchor) => {
8315
+ if (anchor && this.presenting) this.anchors.set(entityId, anchor);
8316
+ else anchor?.delete?.();
8317
+ }).catch(() => {
8318
+ }).finally(() => this.pending.delete(entityId));
8319
+ }
8320
+ releaseAll() {
8321
+ for (const anchor of this.anchors.values()) anchor.delete?.();
8322
+ this.anchors.clear();
8323
+ this.pending.clear();
8324
+ }
8325
+ dispose() {
8326
+ this.presenting = false;
8327
+ this.releaseAll();
8328
+ }
8329
+ };
8330
+ __name(_XRAnchorSystem, "XRAnchorSystem");
8331
+ var XRAnchorSystem = _XRAnchorSystem;
7797
8332
  function buildGeometry2(area) {
7798
8333
  if (area.shape === "circle") {
7799
8334
  const radius = Math.max(5e-3, area.width / 2);
@@ -9028,6 +9563,6 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9028
9563
  __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9029
9564
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9030
9565
 
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
9566
+ 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 };
9567
+ //# sourceMappingURL=chunk-24ZMSBR7.js.map
9568
+ //# sourceMappingURL=chunk-24ZMSBR7.js.map