@relu-interactives/spatial-ecs 0.4.5 → 0.5.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 (47) hide show
  1. package/dist/{System-ln4Q7Eot.d.cts → System-V_mv07le.d.cts} +2 -2
  2. package/dist/{System-ln4Q7Eot.d.ts → System-V_mv07le.d.ts} +2 -2
  3. package/dist/{XRHandPose-CxPv-LBo.d.cts → XRHandPose-CQTLGxFC.d.cts} +218 -2
  4. package/dist/{XRHandPose-BImIa4ZS.d.ts → XRHandPose-CU3vNFuS.d.ts} +218 -2
  5. package/dist/{chunk-4AK4I3QF.js → chunk-BUEKOSR5.js} +102 -3
  6. package/dist/chunk-BUEKOSR5.js.map +1 -0
  7. package/dist/{chunk-SGADE55G.js → chunk-RXBLOO5B.js} +249 -90
  8. package/dist/chunk-RXBLOO5B.js.map +1 -0
  9. package/dist/{chunk-4DWZK4X4.js → chunk-WJD2VKEH.js} +3 -3
  10. package/dist/chunk-WJD2VKEH.js.map +1 -0
  11. package/dist/{chunk-QIIWFP3V.js → chunk-Z6ZMR3CS.js} +1189 -18
  12. package/dist/chunk-Z6ZMR3CS.js.map +1 -0
  13. package/dist/components/index.cjs +99 -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/spatial-ui.md +165 -0
  19. package/dist/factories/index.cjs +372 -114
  20. package/dist/factories/index.cjs.map +1 -1
  21. package/dist/factories/index.d.cts +13 -3
  22. package/dist/factories/index.d.ts +13 -3
  23. package/dist/factories/index.js +3 -3
  24. package/dist/{index-CuGqxuk-.d.ts → index-C3ik3sA1.d.ts} +16 -3
  25. package/dist/{index-BphKHaV-.d.ts → index-CppKDxbu.d.ts} +145 -3
  26. package/dist/{index-CQsHUez3.d.cts → index-Dlr-7-4m.d.cts} +16 -3
  27. package/dist/{index-CH7qMsQT.d.cts → index-I2IsocYW.d.cts} +145 -3
  28. package/dist/index.cjs +1798 -360
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +42 -9
  31. package/dist/index.d.ts +42 -9
  32. package/dist/index.js +10 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/systems/index.cjs +1280 -13
  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-4AK4I3QF.js.map +0 -1
  45. package/dist/chunk-4DWZK4X4.js.map +0 -1
  46. package/dist/chunk-QIIWFP3V.js.map +0 -1
  47. package/dist/chunk-SGADE55G.js.map +0 -1
@@ -1,5 +1,5 @@
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';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-WJD2VKEH.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, UIPanelComponent, UITextComponent, UIButtonComponent, UIToggleComponent, UISliderComponent, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, XREnvironmentComponent, handPoseDefinitionFor, getHandPosePreset } from './chunk-BUEKOSR5.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';
@@ -2848,6 +2848,11 @@ function buildComponentValueMaps(snapshot) {
2848
2848
  "XRHandPoseComponent"
2849
2849
  ),
2850
2850
  xrAnchorValues: readByComponent(snapshot.components, "XRAnchorComponent"),
2851
+ uiPanelValues: readByComponent(snapshot.components, "UIPanelComponent"),
2852
+ uiTextValues: readByComponent(snapshot.components, "UITextComponent"),
2853
+ uiButtonValues: readByComponent(snapshot.components, "UIButtonComponent"),
2854
+ uiToggleValues: readByComponent(snapshot.components, "UIToggleComponent"),
2855
+ uiSliderValues: readByComponent(snapshot.components, "UISliderComponent"),
2851
2856
  xrInteractorSideValues: readByComponent(
2852
2857
  snapshot.components,
2853
2858
  "XRInteractorSideComponent"
@@ -3169,6 +3174,49 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
3169
3174
  }
3170
3175
  }
3171
3176
  }
3177
+ {
3178
+ const restoreUi = /* @__PURE__ */ __name((map, Ctor, existing) => {
3179
+ const source = asRecord(map.get(sourceEntityId));
3180
+ if (!source) return;
3181
+ let component = existing;
3182
+ if (!component) {
3183
+ component = new Ctor();
3184
+ world.addComponent(targetEntityId, component);
3185
+ }
3186
+ const target = component;
3187
+ for (const [key, value] of Object.entries(source)) {
3188
+ const current = target[key];
3189
+ if ((typeof value === "string" || typeof value === "number" || typeof value === "boolean") && typeof current === typeof value) {
3190
+ target[key] = value;
3191
+ }
3192
+ }
3193
+ }, "restoreUi");
3194
+ restoreUi(
3195
+ values.uiPanelValues,
3196
+ UIPanelComponent,
3197
+ world.getComponent(targetEntityId, UIPanelComponent)
3198
+ );
3199
+ restoreUi(
3200
+ values.uiTextValues,
3201
+ UITextComponent,
3202
+ world.getComponent(targetEntityId, UITextComponent)
3203
+ );
3204
+ restoreUi(
3205
+ values.uiButtonValues,
3206
+ UIButtonComponent,
3207
+ world.getComponent(targetEntityId, UIButtonComponent)
3208
+ );
3209
+ restoreUi(
3210
+ values.uiToggleValues,
3211
+ UIToggleComponent,
3212
+ world.getComponent(targetEntityId, UIToggleComponent)
3213
+ );
3214
+ restoreUi(
3215
+ values.uiSliderValues,
3216
+ UISliderComponent,
3217
+ world.getComponent(targetEntityId, UISliderComponent)
3218
+ );
3219
+ }
3172
3220
  {
3173
3221
  const sourceInteractor = asRecord(
3174
3222
  values.xrInteractorValues.get(sourceEntityId)
@@ -5134,6 +5182,23 @@ var _Behaviour = class _Behaviour {
5134
5182
  /** Called when this entity is released. Optional XR hook. */
5135
5183
  onRelease(input) {
5136
5184
  }
5185
+ /** A UI button on this entity (or a descendant) was clicked. Clicks fire on
5186
+ * RELEASE while still pointing at the widget — sliding off cancels. */
5187
+ onUiPress(_event) {
5188
+ }
5189
+ /** A pointer began hovering a UI widget on this entity (or a descendant). */
5190
+ onUiHoverStart(_event) {
5191
+ }
5192
+ /** The pointer left the widget. */
5193
+ onUiHoverEnd(_event) {
5194
+ }
5195
+ /** A UI toggle flipped; `event.value` is the new boolean. */
5196
+ onUiToggle(_event) {
5197
+ }
5198
+ /** A UI slider moved; `event.value` is the new number. Fires on real value
5199
+ * changes during the drag and once more on release. */
5200
+ onUiChange(_event) {
5201
+ }
5137
5202
  };
5138
5203
  __name(_Behaviour, "Behaviour");
5139
5204
  /**
@@ -6664,6 +6729,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6664
6729
  this.grabs = /* @__PURE__ */ new Map();
6665
6730
  this.hoveredEntityId = null;
6666
6731
  /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
6732
+ this.busyContributors = [];
6667
6733
  this.hovers = /* @__PURE__ */ new Map();
6668
6734
  /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
6669
6735
  this.elapsed = 0;
@@ -6672,6 +6738,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6672
6738
  update(world, deltaTime) {
6673
6739
  if (!world.isXrPresenting()) {
6674
6740
  this.releaseAll(world);
6741
+ this.hovers.clear();
6742
+ this.hoveredEntityId = null;
6675
6743
  return;
6676
6744
  }
6677
6745
  this.elapsed += Math.max(0, deltaTime);
@@ -7294,13 +7362,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
7294
7362
  */
7295
7363
  isSourceBusy(world, input) {
7296
7364
  if (this.grabs.has(input)) return true;
7365
+ for (const contributor of this.busyContributors) {
7366
+ try {
7367
+ if (contributor(world, input)) return true;
7368
+ } catch {
7369
+ }
7370
+ }
7297
7371
  const hover = this.hovers.get(input);
7298
7372
  if (!hover) return false;
7299
7373
  const interactable = world.getComponent(
7300
7374
  hover.entityId,
7301
7375
  XRInteractableComponent
7302
7376
  );
7303
- return Boolean(interactable?.enabled && interactable.grabbable);
7377
+ return Boolean(
7378
+ interactable?.enabled && (interactable.grabbable || interactable.pointable)
7379
+ );
7380
+ }
7381
+ /** OR-ed into isSourceBusy; see the comment there. */
7382
+ registerBusyContributor(contributor) {
7383
+ this.busyContributors.push(contributor);
7384
+ }
7385
+ /** Hover for a specific input source (handedness-free — Vision Pro's
7386
+ * transient pointers all report handedness "none"). */
7387
+ getHoverForInput(input) {
7388
+ return this.hovers.get(input) ?? null;
7304
7389
  }
7305
7390
  /** True when THIS hand is holding something. */
7306
7391
  isGrabbingWith(handedness) {
@@ -7935,6 +8020,88 @@ function decomposeXrPose(matrixElements, target) {
7935
8020
  }
7936
8021
  __name(decomposeXrPose, "decomposeXrPose");
7937
8022
 
8023
+ // core/xr/poseStability.ts
8024
+ var GLUED_REF_SPEED = 0.12;
8025
+ var STILL_SPEED = 0.05;
8026
+ var GLUED_FRAMES_TO_FREEZE = 3;
8027
+ var STATIC_FRAMES_TO_RECOVER = 10;
8028
+ var SNAP_SPEED = 3;
8029
+ var dist = /* @__PURE__ */ __name((a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z), "dist");
8030
+ var _SurfacePoseGate = class _SurfacePoseGate {
8031
+ constructor() {
8032
+ this.prevRef = null;
8033
+ this.prevView = null;
8034
+ this.gluedStreak = 0;
8035
+ this.staticStreak = 0;
8036
+ this.suspect = false;
8037
+ this.deltas = 0;
8038
+ }
8039
+ /** True while poses are being rejected as head-glued. */
8040
+ get frozen() {
8041
+ return this.suspect;
8042
+ }
8043
+ /**
8044
+ * True once enough motion has been observed to classify the pose at all.
8045
+ * Surface CREATION waits for this: an entity spawned from a glued pose
8046
+ * materializes wherever the head happens to be — a rotated ghost copy of
8047
+ * the room — and freezing it there is barely better than letting it follow.
8048
+ */
8049
+ get warmedUp() {
8050
+ return this.deltas >= GLUED_FRAMES_TO_FREEZE;
8051
+ }
8052
+ /**
8053
+ * Feed this frame's surface position in reference space and in view
8054
+ * (viewer-relative) space. Returns true when the pose should be APPLIED,
8055
+ * false when it should be frozen out.
8056
+ *
8057
+ * `viewPos` may be null when the viewer pose is unavailable (older
8058
+ * runtimes) — the gate then passes everything through, degrading to the
8059
+ * flag-only behavior.
8060
+ */
8061
+ update(refPos, viewPos, deltaTime) {
8062
+ if (!viewPos || deltaTime <= 0) {
8063
+ this.prevRef = { ...refPos };
8064
+ this.prevView = viewPos ? { ...viewPos } : null;
8065
+ return !this.suspect;
8066
+ }
8067
+ const prevRef = this.prevRef;
8068
+ const prevView = this.prevView;
8069
+ this.prevRef = { ...refPos };
8070
+ this.prevView = { ...viewPos };
8071
+ if (!prevRef || !prevView) return !this.suspect;
8072
+ this.deltas += 1;
8073
+ const refSpeed = dist(refPos, prevRef) / deltaTime;
8074
+ const viewSpeed = dist(viewPos, prevView) / deltaTime;
8075
+ const glued = refSpeed > GLUED_REF_SPEED && viewSpeed < STILL_SPEED;
8076
+ const worldStatic = refSpeed < STILL_SPEED && viewSpeed > GLUED_REF_SPEED;
8077
+ if (this.suspect) {
8078
+ if (refSpeed > SNAP_SPEED && viewSpeed > SNAP_SPEED) {
8079
+ this.suspect = false;
8080
+ this.gluedStreak = 0;
8081
+ this.staticStreak = 0;
8082
+ return true;
8083
+ }
8084
+ this.staticStreak = worldStatic ? this.staticStreak + 1 : 0;
8085
+ if (this.staticStreak >= STATIC_FRAMES_TO_RECOVER) {
8086
+ this.suspect = false;
8087
+ this.gluedStreak = 0;
8088
+ this.staticStreak = 0;
8089
+ return true;
8090
+ }
8091
+ return false;
8092
+ }
8093
+ this.gluedStreak = glued ? this.gluedStreak + 1 : 0;
8094
+ if (this.gluedStreak >= GLUED_FRAMES_TO_FREEZE) {
8095
+ this.suspect = true;
8096
+ this.staticStreak = 0;
8097
+ return false;
8098
+ }
8099
+ return true;
8100
+ }
8101
+ };
8102
+ __name(_SurfacePoseGate, "SurfacePoseGate");
8103
+ var SurfacePoseGate = _SurfacePoseGate;
8104
+
7938
8105
  // core/systems/XREnvironmentSystem.ts
7939
8106
  var DEBUG_COLOR = 5088255;
7940
8107
  function debugColorFor(kind, semanticLabel) {
@@ -7978,12 +8145,26 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
7978
8145
  /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
7979
8146
  this.planes = /* @__PURE__ */ new Map();
7980
8147
  this.meshes = /* @__PURE__ */ new Map();
8148
+ /**
8149
+ * Surfaces observed but not yet SPAWNED — their gate is still proving the
8150
+ * pose is world-anchored. Creating from an unflagged head-glued pose (seen
8151
+ * on-device after a suspend/resume regenerated the scan identities) plants
8152
+ * a rotated ghost copy of the room wherever the head happens to be, which
8153
+ * the apply-gate then freezes there — wrong forever. So admission itself
8154
+ * is gated: a few frames of world-anchored behavior first.
8155
+ */
8156
+ this.pendingPlanes = /* @__PURE__ */ new Map();
8157
+ this.pendingMeshes = /* @__PURE__ */ new Map();
7981
8158
  this.presenting = false;
7982
8159
  this.debugVisible = false;
7983
8160
  /** Seconds presenting, used to decide the room has genuinely nothing in it. */
7984
8161
  this.elapsed = 0;
7985
8162
  /** `initiateRoomCapture` may be called only ONCE per session. */
7986
8163
  this.roomCaptureRequested = false;
8164
+ /** This frame's inverse viewer matrix, or null when unavailable. */
8165
+ this.viewerInverse = null;
8166
+ this.tmpViewerMatrix = new THREE19.Matrix4();
8167
+ this.tmpViewPos = new THREE19.Vector3();
7987
8168
  }
7988
8169
  update(world, deltaTime = 0) {
7989
8170
  const presenting = world.isXrPresenting();
@@ -8021,13 +8202,15 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8021
8202
  const meshesAvailable = Boolean(frame.detectedMeshes);
8022
8203
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
8023
8204
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
8205
+ const viewer = frame.getViewerPose?.(referenceSpace);
8206
+ this.viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
8024
8207
  if (usePlanes) {
8025
- this.syncPlanes(world, frame, referenceSpace);
8208
+ this.syncPlanes(world, frame, referenceSpace, deltaTime);
8026
8209
  } else if (this.planes.size > 0) {
8027
8210
  this.clearMap(world, this.planes);
8028
8211
  }
8029
8212
  if (useMesh) {
8030
- this.syncMeshes(world, frame, referenceSpace);
8213
+ this.syncMeshes(world, frame, referenceSpace, deltaTime);
8031
8214
  } else if (this.meshes.size > 0) {
8032
8215
  this.clearMap(world, this.meshes);
8033
8216
  }
@@ -8114,7 +8297,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8114
8297
  if (ids.length === 0) return null;
8115
8298
  return world.getComponent(ids[0], XRRigComponent) ?? null;
8116
8299
  }
8117
- syncPlanes(world, frame, referenceSpace) {
8300
+ syncPlanes(world, frame, referenceSpace, deltaTime) {
8118
8301
  const detected = frame.detectedPlanes;
8119
8302
  if (!detected) return;
8120
8303
  for (const [plane, entry] of this.planes) {
@@ -8123,6 +8306,9 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8123
8306
  this.planes.delete(plane);
8124
8307
  }
8125
8308
  }
8309
+ for (const plane of this.pendingPlanes.keys()) {
8310
+ if (!detected.has(plane)) this.pendingPlanes.delete(plane);
8311
+ }
8126
8312
  for (const plane of detected) {
8127
8313
  const pose = frame.getPose(plane.planeSpace, referenceSpace);
8128
8314
  if (!pose) continue;
@@ -8131,8 +8317,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8131
8317
  const changed = plane.lastChangedTime ?? 0;
8132
8318
  if (!entry) {
8133
8319
  if (emulated) continue;
8320
+ const gate = this.admit(this.pendingPlanes, plane, pose, deltaTime);
8321
+ if (!gate) continue;
8134
8322
  const created = this.createPlaneEntity(world, plane);
8135
8323
  if (!created) continue;
8324
+ created.gate = gate;
8136
8325
  entry = created;
8137
8326
  this.planes.set(plane, entry);
8138
8327
  } else if (changed !== entry.lastChangedTime) {
@@ -8152,11 +8341,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8152
8341
  }
8153
8342
  }
8154
8343
  }
8155
- entry.emulated = emulated;
8156
- if (!emulated) this.applyPose(world, entry.entityId, pose);
8344
+ const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
8345
+ entry.emulated = emulated || entry.gate.frozen;
8346
+ if (allowed) this.applyPose(world, entry.entityId, pose);
8157
8347
  }
8158
8348
  }
8159
- syncMeshes(world, frame, referenceSpace) {
8349
+ syncMeshes(world, frame, referenceSpace, deltaTime) {
8160
8350
  const detected = frame.detectedMeshes;
8161
8351
  if (!detected) return;
8162
8352
  for (const [mesh, entry] of this.meshes) {
@@ -8165,6 +8355,9 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8165
8355
  this.meshes.delete(mesh);
8166
8356
  }
8167
8357
  }
8358
+ for (const mesh of this.pendingMeshes.keys()) {
8359
+ if (!detected.has(mesh)) this.pendingMeshes.delete(mesh);
8360
+ }
8168
8361
  for (const mesh of detected) {
8169
8362
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
8170
8363
  if (!pose) continue;
@@ -8173,17 +8366,56 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8173
8366
  const changed = mesh.lastChangedTime ?? 0;
8174
8367
  if (!entry) {
8175
8368
  if (emulated) continue;
8369
+ const gate = this.admit(this.pendingMeshes, mesh, pose, deltaTime);
8370
+ if (!gate) continue;
8176
8371
  const created = this.createMeshEntity(world, mesh);
8177
8372
  if (!created) continue;
8373
+ created.gate = gate;
8178
8374
  entry = created;
8179
8375
  this.meshes.set(mesh, entry);
8180
8376
  } else if (changed !== entry.lastChangedTime) {
8181
8377
  entry.lastChangedTime = changed;
8182
8378
  writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
8183
8379
  }
8184
- entry.emulated = emulated;
8185
- if (!emulated) this.applyPose(world, entry.entityId, pose);
8380
+ const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
8381
+ entry.emulated = emulated || entry.gate.frozen;
8382
+ if (allowed) this.applyPose(world, entry.entityId, pose);
8383
+ }
8384
+ }
8385
+ /**
8386
+ * Admission control for a NOT-yet-spawned surface. With a viewer pose
8387
+ * available, the surface must behave world-anchored for a few frames before
8388
+ * an entity is created for it; without one (older runtimes, tests) it is
8389
+ * admitted immediately — the gate can't classify anyway. Returns the
8390
+ * warmed-up gate (carried into the entry so its motion history survives),
8391
+ * or null while the surface must keep waiting.
8392
+ */
8393
+ admit(pending, key, pose, deltaTime) {
8394
+ if (!this.viewerInverse) return new SurfacePoseGate();
8395
+ let gate = pending.get(key);
8396
+ if (!gate) {
8397
+ gate = new SurfacePoseGate();
8398
+ pending.set(key, gate);
8399
+ }
8400
+ const allowed = this.gateAllows(gate, pose, deltaTime);
8401
+ if (!allowed || !gate.warmedUp) return null;
8402
+ pending.delete(key);
8403
+ return gate;
8404
+ }
8405
+ /**
8406
+ * Run a surface's pose through its head-glue gate: position in reference
8407
+ * space vs position relative to the viewer. Without a viewer pose the gate
8408
+ * passes everything, degrading to the flag-only behavior.
8409
+ */
8410
+ gateAllows(gate, pose, deltaTime) {
8411
+ const m = pose.transform.matrix;
8412
+ const refPos = { x: m[12], y: m[13], z: m[14] };
8413
+ let viewPos = null;
8414
+ if (this.viewerInverse) {
8415
+ const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(this.viewerInverse);
8416
+ viewPos = { x: v.x, y: v.y, z: v.z };
8186
8417
  }
8418
+ return gate.update(refPos, viewPos, deltaTime);
8187
8419
  }
8188
8420
  createPlaneEntity(world, plane) {
8189
8421
  const collider = planeColliderFromPolygon(plane.polygon);
@@ -8203,7 +8435,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8203
8435
  lastChangedTime: plane.lastChangedTime ?? 0,
8204
8436
  geometry: null,
8205
8437
  material: null,
8206
- emulated: false
8438
+ emulated: false,
8439
+ gate: new SurfacePoseGate()
8207
8440
  };
8208
8441
  if (this.debugVisible) {
8209
8442
  this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
@@ -8276,7 +8509,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8276
8509
  lastChangedTime: mesh.lastChangedTime ?? 0,
8277
8510
  geometry,
8278
8511
  material,
8279
- emulated: false
8512
+ emulated: false,
8513
+ gate: new SurfacePoseGate()
8280
8514
  };
8281
8515
  }
8282
8516
  /** A thin box so a detected plane can be SEEN, not just collided with. */
@@ -8357,6 +8591,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
8357
8591
  clearAll(world) {
8358
8592
  this.clearMap(world, this.planes);
8359
8593
  this.clearMap(world, this.meshes);
8594
+ this.pendingPlanes.clear();
8595
+ this.pendingMeshes.clear();
8360
8596
  }
8361
8597
  dispose(world) {
8362
8598
  this.clearAll(world);
@@ -8368,6 +8604,918 @@ function normalizeOrientation(value) {
8368
8604
  return value === "horizontal" || value === "vertical" ? value : null;
8369
8605
  }
8370
8606
  __name(normalizeOrientation, "normalizeOrientation");
8607
+ var MOUSE_CLICK_TRAVEL_PX = 6;
8608
+ var PRESS_TINT_MIN_SECONDS = 0.1;
8609
+ var PRESS_TINT = 0.72;
8610
+ var HOVER_TINT = 0.9;
8611
+ var _UIInteractionSystem = class _UIInteractionSystem {
8612
+ /**
8613
+ * @param interaction The XR interaction system, or null in a world with no
8614
+ * XR (flat 3D experiences) — the mouse path still works via
8615
+ * `options.inputSystem`.
8616
+ */
8617
+ constructor(interaction, options) {
8618
+ this.xrPresses = /* @__PURE__ */ new Map();
8619
+ this.mousePress = null;
8620
+ this.mouseHover = null;
8621
+ /** entityId → seconds of pressed-tint left to display. */
8622
+ this.tintFloors = /* @__PURE__ */ new Map();
8623
+ this.tinted = /* @__PURE__ */ new Set();
8624
+ this.raycaster = new THREE19.Raycaster();
8625
+ this.interaction = interaction;
8626
+ this.inputSystem = options?.inputSystem ?? null;
8627
+ interaction?.registerBusyContributor(
8628
+ (_world, input) => this.xrPresses.has(input)
8629
+ );
8630
+ }
8631
+ update(world, deltaTime = 0) {
8632
+ if (world.isEditorMode()) return;
8633
+ if (world.isXrPresenting()) {
8634
+ this.updateXr(world);
8635
+ } else {
8636
+ this.updateMouse(world);
8637
+ }
8638
+ this.updateTints(world, deltaTime);
8639
+ }
8640
+ // ── XR path ────────────────────────────────────────────────────────────────
8641
+ updateXr(world) {
8642
+ const interaction = this.interaction;
8643
+ if (!interaction) return;
8644
+ const xrInput = interaction.inputSystem;
8645
+ const inputs = xrInput.getInputs();
8646
+ for (const input of inputs) {
8647
+ const hover = interaction.getHoverForInput(input);
8648
+ const hovered = hover ? this.widgetKind(world, hover.entityId) : null;
8649
+ if (input.selectStart && hover && hovered) {
8650
+ this.xrPresses.set(input, {
8651
+ entityId: hover.entityId,
8652
+ kind: hovered,
8653
+ travelPx: 0
8654
+ });
8655
+ this.beginPressTint(world, hover.entityId);
8656
+ }
8657
+ const press = this.xrPresses.get(input);
8658
+ if (press && press.kind === "slider") {
8659
+ this.dragSlider(world, press.entityId, input);
8660
+ }
8661
+ if (input.selectEnd || !input.connected) {
8662
+ if (press) {
8663
+ this.xrPresses.delete(input);
8664
+ const stillOn = hover?.entityId === press.entityId || // Vision Pro: the transient source is gone from the hover map on
8665
+ // its release frame — confirm against the last latched ray.
8666
+ this.rayHitsWidget(world, press.entityId, input);
8667
+ if (stillOn || press.kind === "slider") {
8668
+ this.fire(world, press, input);
8669
+ }
8670
+ this.endPressTint(press.entityId);
8671
+ }
8672
+ }
8673
+ }
8674
+ for (const [input] of this.xrPresses) {
8675
+ if (!inputs.includes(input)) this.xrPresses.delete(input);
8676
+ }
8677
+ }
8678
+ // ── Mouse path (flat preview) ──────────────────────────────────────────────
8679
+ updateMouse(world) {
8680
+ const input = this.inputSystem;
8681
+ if (!input) return;
8682
+ const hit = this.raycastMouse(world);
8683
+ const hovered = hit && this.widgetKind(world, hit) ? hit : null;
8684
+ if (hovered !== this.mouseHover) {
8685
+ if (this.mouseHover !== null) {
8686
+ this.setTint(world, this.mouseHover, 1);
8687
+ this.dispatchBubbling(
8688
+ world,
8689
+ this.mouseHover,
8690
+ (b, e) => b.onUiHoverEnd(e)
8691
+ );
8692
+ }
8693
+ if (hovered !== null) {
8694
+ this.setTint(world, hovered, HOVER_TINT);
8695
+ this.dispatchBubbling(world, hovered, (b, e) => b.onUiHoverStart(e));
8696
+ }
8697
+ this.mouseHover = hovered;
8698
+ }
8699
+ if (input.getMouseDown("left") && hovered) {
8700
+ const kind = this.widgetKind(world, hovered);
8701
+ if (kind) {
8702
+ this.mousePress = { entityId: hovered, kind, travelPx: 0 };
8703
+ this.beginPressTint(world, hovered);
8704
+ }
8705
+ }
8706
+ if (this.mousePress) {
8707
+ const delta = input.getMouseDelta();
8708
+ this.mousePress.travelPx += Math.hypot(delta.x, delta.y);
8709
+ if (this.mousePress.kind === "slider") {
8710
+ this.dragSliderMouse(world, this.mousePress.entityId);
8711
+ }
8712
+ }
8713
+ if (input.getMouseUp("left") && this.mousePress) {
8714
+ const press = this.mousePress;
8715
+ this.mousePress = null;
8716
+ const stillOn = hovered === press.entityId;
8717
+ const withinTravel = press.travelPx <= MOUSE_CLICK_TRAVEL_PX;
8718
+ if (press.kind === "slider" || stillOn && withinTravel) {
8719
+ this.fire(world, press, null);
8720
+ }
8721
+ this.endPressTint(press.entityId);
8722
+ }
8723
+ }
8724
+ raycastMouse(world) {
8725
+ const input = this.inputSystem;
8726
+ if (!input) return null;
8727
+ const dom = world.getRenderer().domElement;
8728
+ const rect = dom.getBoundingClientRect();
8729
+ let ndcX = 0;
8730
+ let ndcY = 0;
8731
+ if (input.isMousePointerLocked()) {
8732
+ ndcX = 0;
8733
+ ndcY = 0;
8734
+ } else {
8735
+ const pos = input.getMousePosition();
8736
+ ndcX = (pos.x - rect.left) / Math.max(1, rect.width) * 2 - 1;
8737
+ ndcY = -((pos.y - rect.top) / Math.max(1, rect.height) * 2 - 1);
8738
+ if (ndcX < -1 || ndcX > 1 || ndcY < -1 || ndcY > 1) return null;
8739
+ }
8740
+ this.raycaster.setFromCamera(new THREE19.Vector2(ndcX, ndcY), world.getCamera());
8741
+ const hits = world.raycast(
8742
+ this.raycaster.ray.origin,
8743
+ this.raycaster.ray.direction
8744
+ );
8745
+ for (const hit of hits) {
8746
+ if (typeof hit.entityId !== "number") continue;
8747
+ if (this.widgetKind(world, hit.entityId)) return hit.entityId;
8748
+ return null;
8749
+ }
8750
+ return null;
8751
+ }
8752
+ // ── Shared machinery ───────────────────────────────────────────────────────
8753
+ widgetKind(world, entityId) {
8754
+ if (world.getComponent(entityId, UIButtonComponent)) return "button";
8755
+ if (world.getComponent(entityId, UIToggleComponent)) return "toggle";
8756
+ if (world.getComponent(entityId, UISliderComponent)) return "slider";
8757
+ return null;
8758
+ }
8759
+ fire(world, press, input) {
8760
+ if (press.kind === "button") {
8761
+ this.dispatchBubbling(world, press.entityId, (b, e) => b.onUiPress(e), input);
8762
+ return;
8763
+ }
8764
+ if (press.kind === "toggle") {
8765
+ const toggle = world.getComponent(press.entityId, UIToggleComponent);
8766
+ if (!toggle) return;
8767
+ toggle.value = !toggle.value;
8768
+ this.dispatchBubbling(
8769
+ world,
8770
+ press.entityId,
8771
+ (b, e) => b.onUiToggle(e),
8772
+ input,
8773
+ toggle.value
8774
+ );
8775
+ return;
8776
+ }
8777
+ const slider = world.getComponent(press.entityId, UISliderComponent);
8778
+ if (!slider) return;
8779
+ this.dispatchBubbling(
8780
+ world,
8781
+ press.entityId,
8782
+ (b, e) => b.onUiChange(e),
8783
+ input,
8784
+ slider.value
8785
+ );
8786
+ }
8787
+ dragSlider(world, entityId, input) {
8788
+ const point = this.rayPointOnWidget(world, entityId, input);
8789
+ if (point) this.applySliderPoint(world, entityId, point, input);
8790
+ }
8791
+ dragSliderMouse(world, entityId) {
8792
+ const object = this.widgetObject(world, entityId);
8793
+ if (!object) return;
8794
+ const hits = this.raycaster.intersectObject(object, true);
8795
+ if (hits.length > 0) this.applySliderPoint(world, entityId, hits[0].point, null);
8796
+ }
8797
+ applySliderPoint(world, entityId, worldPoint, input) {
8798
+ const slider = world.getComponent(entityId, UISliderComponent);
8799
+ const object = this.widgetObject(world, entityId);
8800
+ if (!slider || !object) return;
8801
+ const local = object.worldToLocal(worldPoint.clone());
8802
+ const inset = slider.height / 2;
8803
+ const usable = Math.max(1e-3, slider.width - inset * 2);
8804
+ const t = Math.min(1, Math.max(0, local.x / usable + 0.5));
8805
+ const next = slider.min + t * (slider.max - slider.min);
8806
+ if (Math.abs(next - slider.value) < 1e-4) return;
8807
+ slider.value = next;
8808
+ this.dispatchBubbling(
8809
+ world,
8810
+ entityId,
8811
+ (b, e) => b.onUiChange(e),
8812
+ input,
8813
+ next
8814
+ );
8815
+ }
8816
+ widgetObject(world, entityId) {
8817
+ return world.getComponent(entityId, Object3DRef)?.object;
8818
+ }
8819
+ rayHitsWidget(world, entityId, input) {
8820
+ const object = this.widgetObject(world, entityId);
8821
+ if (!object) return false;
8822
+ this.raycaster.set(
8823
+ input.rayOrigin.clone(),
8824
+ input.rayDirection.clone().normalize()
8825
+ );
8826
+ return this.raycaster.intersectObject(object, true).length > 0;
8827
+ }
8828
+ rayPointOnWidget(world, entityId, input) {
8829
+ const object = this.widgetObject(world, entityId);
8830
+ if (!object) return null;
8831
+ this.raycaster.set(
8832
+ input.rayOrigin.clone(),
8833
+ input.rayDirection.clone().normalize()
8834
+ );
8835
+ const hits = this.raycaster.intersectObject(object, true);
8836
+ return hits.length > 0 ? hits[0].point : null;
8837
+ }
8838
+ /**
8839
+ * Dispatch to the widget's Behaviours, then BUBBLE up the parent chain so a
8840
+ * panel script hears its children. Bubbling (not a world broadcast) keeps
8841
+ * two spawned copies of the same menu prefab independent.
8842
+ */
8843
+ dispatchBubbling(world, entityId, visit, input = null, value) {
8844
+ const event = {
8845
+ targetEntityId: entityId,
8846
+ actionId: this.actionIdOf(world, entityId),
8847
+ input,
8848
+ ...value === void 0 ? {} : { value }
8849
+ };
8850
+ const visited = /* @__PURE__ */ new Set();
8851
+ let current = entityId;
8852
+ for (; ; ) {
8853
+ if (visited.has(current)) break;
8854
+ visited.add(current);
8855
+ const script = world.getComponent(current, ScriptComponent);
8856
+ const instances = script?.scriptInstances;
8857
+ if (instances) {
8858
+ for (const instance of instances.values()) {
8859
+ if (!(instance instanceof Behaviour)) continue;
8860
+ try {
8861
+ visit(instance, event);
8862
+ } catch (error) {
8863
+ console.error("[UIInteractionSystem] hook failed:", error);
8864
+ }
8865
+ }
8866
+ }
8867
+ const parentId = world.getComponent(current, ParentId)?.value;
8868
+ if (typeof parentId !== "number") break;
8869
+ current = parentId;
8870
+ }
8871
+ }
8872
+ actionIdOf(world, entityId) {
8873
+ const button = world.getComponent(entityId, UIButtonComponent);
8874
+ if (button?.actionId) return button.actionId;
8875
+ const toggle = world.getComponent(entityId, UIToggleComponent);
8876
+ if (toggle?.actionId) return toggle.actionId;
8877
+ const slider = world.getComponent(entityId, UISliderComponent);
8878
+ if (slider?.actionId) return slider.actionId;
8879
+ const name = world.getComponent(entityId, Name)?.value;
8880
+ return typeof name === "string" ? name : String(entityId);
8881
+ }
8882
+ // ── Pressed/hover tint ─────────────────────────────────────────────────────
8883
+ beginPressTint(world, entityId) {
8884
+ this.setTint(world, entityId, PRESS_TINT);
8885
+ this.tintFloors.set(entityId, PRESS_TINT_MIN_SECONDS);
8886
+ }
8887
+ endPressTint(entityId) {
8888
+ if (!this.tintFloors.has(entityId)) {
8889
+ this.tintFloors.set(entityId, 0);
8890
+ }
8891
+ }
8892
+ updateTints(world, deltaTime) {
8893
+ for (const [entityId, remaining] of this.tintFloors) {
8894
+ const next = remaining - deltaTime;
8895
+ const stillHeld = [...this.xrPresses.values()].some((p) => p.entityId === entityId) || this.mousePress?.entityId === entityId;
8896
+ if (stillHeld) continue;
8897
+ if (next <= 0) {
8898
+ this.tintFloors.delete(entityId);
8899
+ this.setTint(
8900
+ world,
8901
+ entityId,
8902
+ this.mouseHover === entityId ? HOVER_TINT : 1
8903
+ );
8904
+ } else {
8905
+ this.tintFloors.set(entityId, next);
8906
+ }
8907
+ }
8908
+ }
8909
+ setTint(world, entityId, scalar) {
8910
+ const object = this.widgetObject(world, entityId);
8911
+ if (!object) return;
8912
+ if (scalar === 1) this.tinted.delete(entityId);
8913
+ else this.tinted.add(entityId);
8914
+ object.traverse((child) => {
8915
+ const material = child.material;
8916
+ if (material?.color) material.color.setScalar(scalar);
8917
+ });
8918
+ }
8919
+ };
8920
+ __name(_UIInteractionSystem, "UIInteractionSystem");
8921
+ var UIInteractionSystem = _UIInteractionSystem;
8922
+
8923
+ // core/ui/uiCanvas.ts
8924
+ var DEFAULT_PIXELS_PER_METER = 1024;
8925
+ var MIN_PIXELS_PER_METER = 256;
8926
+ var MAX_PIXELS_PER_METER = 2048;
8927
+ var MAX_CANVAS_SIDE = 2048;
8928
+ function resolveCanvasSize(widthMeters, heightMeters, pixelDensity = DEFAULT_PIXELS_PER_METER) {
8929
+ const w = Math.max(0.01, widthMeters);
8930
+ const h = Math.max(0.01, heightMeters);
8931
+ const authored = Math.min(
8932
+ MAX_PIXELS_PER_METER,
8933
+ Math.max(MIN_PIXELS_PER_METER, pixelDensity)
8934
+ );
8935
+ const density = Math.min(authored, MAX_CANVAS_SIDE / Math.max(w, h));
8936
+ const even = /* @__PURE__ */ __name((v) => Math.max(2, 2 * Math.round(v * density / 2)), "even");
8937
+ return { pxWidth: even(w), pxHeight: even(h), density };
8938
+ }
8939
+ __name(resolveCanvasSize, "resolveCanvasSize");
8940
+ function wrapText(measure, text, maxWidthPx) {
8941
+ const lines = [];
8942
+ for (const paragraph of text.split("\n")) {
8943
+ const words = paragraph.split(/\s+/).filter((w) => w.length > 0);
8944
+ if (words.length === 0) {
8945
+ lines.push("");
8946
+ continue;
8947
+ }
8948
+ let line = "";
8949
+ for (const word of words) {
8950
+ const candidate = line ? `${line} ${word}` : word;
8951
+ if (measure(candidate) <= maxWidthPx) {
8952
+ line = candidate;
8953
+ continue;
8954
+ }
8955
+ if (line) {
8956
+ lines.push(line);
8957
+ line = "";
8958
+ }
8959
+ if (measure(word) <= maxWidthPx) {
8960
+ line = word;
8961
+ continue;
8962
+ }
8963
+ let chunk = "";
8964
+ for (const char of word) {
8965
+ const next = chunk + char;
8966
+ if (measure(next) <= maxWidthPx || chunk.length === 0) {
8967
+ chunk = next;
8968
+ } else {
8969
+ lines.push(chunk);
8970
+ chunk = char;
8971
+ }
8972
+ }
8973
+ line = chunk;
8974
+ }
8975
+ lines.push(line);
8976
+ }
8977
+ return lines;
8978
+ }
8979
+ __name(wrapText, "wrapText");
8980
+ var ELLIPSIS = "\u2026";
8981
+ function clampLines(measure, lines, maxLines, maxWidthPx) {
8982
+ if (maxLines <= 0) return [];
8983
+ if (lines.length <= maxLines) return lines;
8984
+ const kept = lines.slice(0, maxLines);
8985
+ let last = kept[maxLines - 1];
8986
+ while (last.length > 0 && measure(last + ELLIPSIS) > maxWidthPx) {
8987
+ last = last.slice(0, -1);
8988
+ }
8989
+ kept[maxLines - 1] = last + ELLIPSIS;
8990
+ return kept;
8991
+ }
8992
+ __name(clampLines, "clampLines");
8993
+ var UI_FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
8994
+ function tracePath(ctx, x, y, w, h, r) {
8995
+ const radius = Math.max(0, Math.min(r, w / 2, h / 2));
8996
+ ctx.beginPath();
8997
+ if (typeof ctx.roundRect === "function") {
8998
+ ctx.roundRect(x, y, w, h, radius);
8999
+ return;
9000
+ }
9001
+ ctx.moveTo(x + radius, y);
9002
+ ctx.arcTo(x + w, y, x + w, y + h, radius);
9003
+ ctx.arcTo(x + w, y + h, x, y + h, radius);
9004
+ ctx.arcTo(x, y + h, x, y, radius);
9005
+ ctx.arcTo(x, y, x + w, y, radius);
9006
+ ctx.closePath();
9007
+ }
9008
+ __name(tracePath, "tracePath");
9009
+ function withOpacity(ctx, color, opacity) {
9010
+ if (opacity >= 1) return color;
9011
+ ctx.save();
9012
+ ctx.fillStyle = color;
9013
+ const parsed = ctx.fillStyle;
9014
+ ctx.restore();
9015
+ if (parsed.startsWith("#") && (parsed.length === 7 || parsed.length === 4)) {
9016
+ const hex = parsed.length === 4 ? `#${parsed[1]}${parsed[1]}${parsed[2]}${parsed[2]}${parsed[3]}${parsed[3]}` : parsed;
9017
+ const r = Number.parseInt(hex.slice(1, 3), 16);
9018
+ const g = Number.parseInt(hex.slice(3, 5), 16);
9019
+ const b = Number.parseInt(hex.slice(5, 7), 16);
9020
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
9021
+ }
9022
+ return parsed;
9023
+ }
9024
+ __name(withOpacity, "withOpacity");
9025
+ function drawChrome(ctx, pxWidth, pxHeight, density, style) {
9026
+ ctx.clearRect(0, 0, pxWidth, pxHeight);
9027
+ const border = Math.max(0, style.borderWidthMeters * density);
9028
+ const radius = style.cornerRadiusMeters * density;
9029
+ tracePath(ctx, 0, 0, pxWidth, pxHeight, radius);
9030
+ ctx.fillStyle = withOpacity(
9031
+ ctx,
9032
+ style.backgroundColor,
9033
+ Math.max(0, Math.min(1, style.backgroundOpacity))
9034
+ );
9035
+ ctx.fill();
9036
+ if (border > 0) {
9037
+ tracePath(
9038
+ ctx,
9039
+ border / 2,
9040
+ border / 2,
9041
+ pxWidth - border,
9042
+ pxHeight - border,
9043
+ Math.max(0, radius - border / 2)
9044
+ );
9045
+ ctx.strokeStyle = style.borderColor;
9046
+ ctx.lineWidth = border;
9047
+ ctx.stroke();
9048
+ }
9049
+ }
9050
+ __name(drawChrome, "drawChrome");
9051
+ function drawTextBlock(ctx, pxWidth, pxHeight, density, text, style) {
9052
+ const fontPx = Math.max(4, style.fontSizeMeters * density);
9053
+ const paddingPx = style.paddingMeters * density;
9054
+ const contentW = Math.max(1, pxWidth - paddingPx * 2);
9055
+ const contentH = Math.max(1, pxHeight - paddingPx * 2);
9056
+ const lineHeightPx = fontPx * style.lineHeight;
9057
+ ctx.font = `${style.fontWeight} ${fontPx}px ${UI_FONT_STACK}`;
9058
+ const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width, "measure");
9059
+ const maxLines = Math.max(1, Math.floor(contentH / lineHeightPx));
9060
+ const lines = clampLines(
9061
+ measure,
9062
+ wrapText(measure, text, contentW),
9063
+ maxLines,
9064
+ contentW
9065
+ );
9066
+ const blockH = lines.length * lineHeightPx;
9067
+ const top = style.valign === "top" ? paddingPx : style.valign === "bottom" ? pxHeight - paddingPx - blockH : (pxHeight - blockH) / 2;
9068
+ const anchorX = style.align === "left" ? paddingPx : style.align === "right" ? pxWidth - paddingPx : pxWidth / 2;
9069
+ ctx.save();
9070
+ tracePath(ctx, 0, 0, pxWidth, pxHeight, 0);
9071
+ ctx.clip();
9072
+ ctx.fillStyle = style.color;
9073
+ ctx.textAlign = style.align;
9074
+ ctx.textBaseline = "middle";
9075
+ for (let i = 0; i < lines.length; i += 1) {
9076
+ ctx.fillText(lines[i], anchorX, top + (i + 0.5) * lineHeightPx);
9077
+ }
9078
+ ctx.restore();
9079
+ }
9080
+ __name(drawTextBlock, "drawTextBlock");
9081
+ function panelSignature(widthM, heightM, size, style) {
9082
+ return JSON.stringify([
9083
+ widthM,
9084
+ heightM,
9085
+ size.pxWidth,
9086
+ size.pxHeight,
9087
+ style.backgroundColor,
9088
+ style.backgroundOpacity,
9089
+ style.cornerRadiusMeters,
9090
+ style.borderWidthMeters,
9091
+ style.borderColor
9092
+ ]);
9093
+ }
9094
+ __name(panelSignature, "panelSignature");
9095
+ function textSignature(widthM, heightM, size, text, style) {
9096
+ return JSON.stringify([
9097
+ widthM,
9098
+ heightM,
9099
+ size.pxWidth,
9100
+ size.pxHeight,
9101
+ text,
9102
+ style.fontSizeMeters,
9103
+ style.fontWeight,
9104
+ style.color,
9105
+ style.align,
9106
+ style.valign,
9107
+ style.lineHeight,
9108
+ style.paddingMeters
9109
+ ]);
9110
+ }
9111
+ __name(textSignature, "textSignature");
9112
+ function buttonSignature(widthM, heightM, size, panel, label, text) {
9113
+ return JSON.stringify([
9114
+ panelSignature(widthM, heightM, size, panel),
9115
+ label,
9116
+ text.fontSizeMeters,
9117
+ text.fontWeight,
9118
+ text.color
9119
+ ]);
9120
+ }
9121
+ __name(buttonSignature, "buttonSignature");
9122
+ function trackSignature(widthM, heightM, size, panel, accentColor, knobColor) {
9123
+ return JSON.stringify([
9124
+ panelSignature(widthM, heightM, size, panel),
9125
+ accentColor,
9126
+ knobColor
9127
+ ]);
9128
+ }
9129
+ __name(trackSignature, "trackSignature");
9130
+ function drawRoundedBar(ctx, pxX, pxY, pxW, pxH, color) {
9131
+ tracePath(ctx, pxX, pxY, pxW, pxH, pxH / 2);
9132
+ ctx.fillStyle = color;
9133
+ ctx.fill();
9134
+ }
9135
+ __name(drawRoundedBar, "drawRoundedBar");
9136
+
9137
+ // core/systems/UIPanelSystem.ts
9138
+ var FACE_RENDER_ORDER = 10;
9139
+ var CHILD_Z = 2e-3;
9140
+ var _UIPanelSystem = class _UIPanelSystem {
9141
+ constructor(options) {
9142
+ this.entries = /* @__PURE__ */ new Map();
9143
+ this.createCanvas = options?.createCanvas ?? (() => document.createElement("canvas"));
9144
+ }
9145
+ update(world, deltaTime = 0) {
9146
+ const live = /* @__PURE__ */ new Set();
9147
+ for (const entityId of world.query(UIPanelComponent, Object3DRef)) {
9148
+ const panel = world.getComponent(entityId, UIPanelComponent);
9149
+ if (!panel) continue;
9150
+ live.add(entityId);
9151
+ this.syncPanel(world, entityId, panel);
9152
+ this.applyFollow(world, entityId, panel);
9153
+ }
9154
+ for (const entityId of world.query(UITextComponent, Object3DRef)) {
9155
+ const text = world.getComponent(entityId, UITextComponent);
9156
+ if (!text) continue;
9157
+ live.add(entityId);
9158
+ this.syncText(world, entityId, text);
9159
+ }
9160
+ for (const entityId of world.query(UIButtonComponent, Object3DRef)) {
9161
+ const button = world.getComponent(entityId, UIButtonComponent);
9162
+ if (!button) continue;
9163
+ live.add(entityId);
9164
+ this.syncButton(world, entityId, button);
9165
+ }
9166
+ for (const entityId of world.query(UIToggleComponent, Object3DRef)) {
9167
+ const toggle = world.getComponent(entityId, UIToggleComponent);
9168
+ if (!toggle) continue;
9169
+ live.add(entityId);
9170
+ this.syncToggle(world, entityId, toggle, deltaTime);
9171
+ }
9172
+ for (const entityId of world.query(UISliderComponent, Object3DRef)) {
9173
+ const slider = world.getComponent(entityId, UISliderComponent);
9174
+ if (!slider) continue;
9175
+ live.add(entityId);
9176
+ this.syncSlider(world, entityId, slider);
9177
+ }
9178
+ for (const [entityId, entry] of this.entries) {
9179
+ if (!live.has(entityId)) {
9180
+ this.destroyEntry(entry);
9181
+ this.entries.delete(entityId);
9182
+ }
9183
+ }
9184
+ }
9185
+ /** All GPU/canvas resources; call on world teardown. */
9186
+ dispose() {
9187
+ for (const entry of this.entries.values()) this.destroyEntry(entry);
9188
+ this.entries.clear();
9189
+ }
9190
+ // ── Widgets ────────────────────────────────────────────────────────────────
9191
+ syncPanel(world, entityId, panel) {
9192
+ const size = resolveCanvasSize(panel.width, panel.height, panel.pixelDensity);
9193
+ const style = {
9194
+ backgroundColor: panel.backgroundColor,
9195
+ backgroundOpacity: panel.backgroundOpacity,
9196
+ cornerRadiusMeters: panel.cornerRadius,
9197
+ borderWidthMeters: panel.borderWidth,
9198
+ borderColor: panel.borderColor
9199
+ };
9200
+ const signature = panelSignature(panel.width, panel.height, size, style);
9201
+ this.rebuildIfChanged(world, entityId, signature, panel.width, panel.height, size, (ctx) => {
9202
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, style);
9203
+ });
9204
+ }
9205
+ syncText(world, entityId, text) {
9206
+ const size = resolveCanvasSize(text.width, text.height);
9207
+ const style = {
9208
+ fontSizeMeters: text.fontSize,
9209
+ fontWeight: text.fontWeight,
9210
+ color: text.color,
9211
+ align: text.align,
9212
+ valign: text.valign,
9213
+ lineHeight: text.lineHeight,
9214
+ paddingMeters: text.padding
9215
+ };
9216
+ const signature = textSignature(text.width, text.height, size, text.text, style);
9217
+ this.rebuildIfChanged(world, entityId, signature, text.width, text.height, size, (ctx) => {
9218
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
9219
+ drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, text.text, style);
9220
+ });
9221
+ }
9222
+ syncButton(world, entityId, button) {
9223
+ const size = resolveCanvasSize(button.width, button.height);
9224
+ const chrome = {
9225
+ backgroundColor: button.backgroundColor,
9226
+ backgroundOpacity: button.backgroundOpacity,
9227
+ cornerRadiusMeters: button.cornerRadius,
9228
+ borderWidthMeters: button.borderWidth,
9229
+ borderColor: button.borderColor
9230
+ };
9231
+ const label = {
9232
+ fontSizeMeters: button.fontSize,
9233
+ fontWeight: button.fontWeight,
9234
+ color: button.textColor,
9235
+ align: "center",
9236
+ valign: "middle",
9237
+ lineHeight: 1.2,
9238
+ paddingMeters: 0.01
9239
+ };
9240
+ const signature = buttonSignature(
9241
+ button.width,
9242
+ button.height,
9243
+ size,
9244
+ chrome,
9245
+ button.label,
9246
+ label
9247
+ );
9248
+ this.rebuildIfChanged(world, entityId, signature, button.width, button.height, size, (ctx) => {
9249
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
9250
+ drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, button.label, label);
9251
+ });
9252
+ }
9253
+ syncToggle(world, entityId, toggle, deltaTime) {
9254
+ const size = resolveCanvasSize(toggle.width, toggle.height);
9255
+ const chrome = {
9256
+ backgroundColor: toggle.value ? toggle.accentColor : toggle.trackColor,
9257
+ backgroundOpacity: 1,
9258
+ cornerRadiusMeters: toggle.height / 2,
9259
+ borderWidthMeters: 0,
9260
+ borderColor: "#000000"
9261
+ };
9262
+ const signature = JSON.stringify([
9263
+ trackSignature(
9264
+ toggle.width,
9265
+ toggle.height,
9266
+ size,
9267
+ chrome,
9268
+ toggle.accentColor,
9269
+ toggle.knobColor
9270
+ ),
9271
+ toggle.value
9272
+ ]);
9273
+ const entry = this.rebuildIfChanged(
9274
+ world,
9275
+ entityId,
9276
+ signature,
9277
+ toggle.width,
9278
+ toggle.height,
9279
+ size,
9280
+ (ctx) => {
9281
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
9282
+ }
9283
+ );
9284
+ if (!entry) return;
9285
+ if (!entry.knob) {
9286
+ entry.knob = this.makeDisc(
9287
+ toggle.height * 0.4,
9288
+ toggle.knobColor,
9289
+ entry.face.parent,
9290
+ CHILD_Z
9291
+ );
9292
+ }
9293
+ const inset = toggle.height / 2;
9294
+ const target = toggle.value ? toggle.width / 2 - inset : -(toggle.width / 2 - inset);
9295
+ const knob = entry.knob;
9296
+ const ease = Math.min(1, deltaTime * 14);
9297
+ knob.position.x += (target - knob.position.x) * (deltaTime > 0 ? ease : 1);
9298
+ knob.position.z = CHILD_Z;
9299
+ }
9300
+ syncSlider(world, entityId, slider) {
9301
+ const size = resolveCanvasSize(slider.width, slider.height);
9302
+ const trackThickness = slider.height * 0.32;
9303
+ const knobRadius = slider.height * 0.45;
9304
+ const usable = Math.max(1e-3, slider.width - slider.height);
9305
+ const chrome = {
9306
+ backgroundColor: slider.trackColor,
9307
+ backgroundOpacity: 1,
9308
+ cornerRadiusMeters: trackThickness / 2,
9309
+ borderWidthMeters: 0,
9310
+ borderColor: "#000000"
9311
+ };
9312
+ const signature = trackSignature(
9313
+ slider.width,
9314
+ slider.height,
9315
+ size,
9316
+ chrome,
9317
+ slider.accentColor,
9318
+ slider.knobColor
9319
+ );
9320
+ const entry = this.rebuildIfChanged(
9321
+ world,
9322
+ entityId,
9323
+ signature,
9324
+ slider.width,
9325
+ slider.height,
9326
+ size,
9327
+ (ctx) => {
9328
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
9329
+ const trackWidthM = usable + trackThickness;
9330
+ drawRoundedBar(
9331
+ ctx,
9332
+ (slider.width - trackWidthM) / 2 * size.density,
9333
+ (slider.height - trackThickness) / 2 * size.density,
9334
+ trackWidthM * size.density,
9335
+ trackThickness * size.density,
9336
+ slider.trackColor
9337
+ );
9338
+ }
9339
+ );
9340
+ if (!entry) return;
9341
+ if (!entry.fill) {
9342
+ entry.fill = this.makeQuad(
9343
+ 1,
9344
+ trackThickness,
9345
+ slider.accentColor,
9346
+ entry.face.parent,
9347
+ CHILD_Z / 2
9348
+ );
9349
+ entry.cap = this.makeDisc(
9350
+ trackThickness / 2,
9351
+ slider.accentColor,
9352
+ entry.face.parent,
9353
+ CHILD_Z / 2,
9354
+ Math.PI / 2,
9355
+ Math.PI
9356
+ );
9357
+ entry.cap.position.x = -usable / 2;
9358
+ }
9359
+ if (!entry.knob) {
9360
+ entry.knob = this.makeDisc(
9361
+ knobRadius,
9362
+ slider.knobColor,
9363
+ entry.face.parent,
9364
+ CHILD_Z
9365
+ );
9366
+ }
9367
+ const range = Math.max(1e-6, slider.max - slider.min);
9368
+ const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
9369
+ entry.knob.position.x = -usable / 2 + t * usable;
9370
+ entry.knob.position.z = CHILD_Z;
9371
+ entry.fill.scale.x = Math.max(1e-4, t * usable);
9372
+ entry.fill.position.x = -usable / 2 + t * usable / 2;
9373
+ entry.fill.position.z = CHILD_Z / 2;
9374
+ }
9375
+ // ── Machinery ──────────────────────────────────────────────────────────────
9376
+ rebuildIfChanged(world, entityId, signature, widthM, heightM, size, draw) {
9377
+ const root = world.getComponent(entityId, Object3DRef)?.object;
9378
+ if (!root) return null;
9379
+ let entry = this.entries.get(entityId);
9380
+ if (entry && entry.signature === signature) return entry;
9381
+ if (entry) this.disposeMovingParts(entry);
9382
+ if (!entry) {
9383
+ const canvas2 = this.createCanvas();
9384
+ const texture = new THREE19.CanvasTexture(
9385
+ canvas2
9386
+ );
9387
+ this.configureTexture(world, texture);
9388
+ const material = new THREE19.MeshBasicMaterial({
9389
+ map: texture,
9390
+ transparent: true,
9391
+ depthWrite: false,
9392
+ // UI colours are authored absolutes; scene tone mapping would shift
9393
+ // them (the classic "why is my white UI beige" bug).
9394
+ toneMapped: false,
9395
+ side: THREE19.DoubleSide,
9396
+ premultipliedAlpha: true
9397
+ });
9398
+ const face = new THREE19.Mesh(
9399
+ new THREE19.PlaneGeometry(widthM, heightM),
9400
+ material
9401
+ );
9402
+ face.renderOrder = FACE_RENDER_ORDER;
9403
+ root.add(face);
9404
+ entry = {
9405
+ signature: "",
9406
+ face,
9407
+ texture,
9408
+ material,
9409
+ canvas: canvas2,
9410
+ knob: null,
9411
+ fill: null,
9412
+ cap: null
9413
+ };
9414
+ this.entries.set(entityId, entry);
9415
+ }
9416
+ const { canvas } = entry;
9417
+ if (canvas.width !== size.pxWidth || canvas.height !== size.pxHeight) {
9418
+ canvas.width = size.pxWidth;
9419
+ canvas.height = size.pxHeight;
9420
+ }
9421
+ const params = entry.face.geometry.parameters;
9422
+ if (params.width !== widthM || params.height !== heightM) {
9423
+ entry.face.geometry.dispose();
9424
+ entry.face.geometry = new THREE19.PlaneGeometry(widthM, heightM);
9425
+ }
9426
+ const ctx = canvas.getContext("2d");
9427
+ if (ctx) draw(ctx);
9428
+ entry.texture.needsUpdate = true;
9429
+ entry.signature = signature;
9430
+ return entry;
9431
+ }
9432
+ configureTexture(world, texture) {
9433
+ texture.colorSpace = THREE19.SRGBColorSpace;
9434
+ texture.generateMipmaps = true;
9435
+ texture.minFilter = THREE19.LinearMipmapLinearFilter;
9436
+ texture.magFilter = THREE19.LinearFilter;
9437
+ texture.premultiplyAlpha = true;
9438
+ try {
9439
+ const max = world.getRenderer().capabilities?.getMaxAnisotropy?.();
9440
+ if (typeof max === "number") texture.anisotropy = Math.min(8, max);
9441
+ } catch {
9442
+ }
9443
+ }
9444
+ makeQuad(widthM, heightM, color, parent, z) {
9445
+ return this.addPart(
9446
+ new THREE19.PlaneGeometry(widthM, heightM),
9447
+ color,
9448
+ parent,
9449
+ z
9450
+ );
9451
+ }
9452
+ /** A filled disc (or arc segment, for the fill's rounded cap). */
9453
+ makeDisc(radiusM, color, parent, z, thetaStart = 0, thetaLength = Math.PI * 2) {
9454
+ return this.addPart(
9455
+ new THREE19.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
9456
+ color,
9457
+ parent,
9458
+ z
9459
+ );
9460
+ }
9461
+ addPart(geometry, color, parent, z) {
9462
+ const mesh = new THREE19.Mesh(
9463
+ geometry,
9464
+ new THREE19.MeshBasicMaterial({
9465
+ color,
9466
+ transparent: true,
9467
+ depthWrite: false,
9468
+ toneMapped: false,
9469
+ side: THREE19.DoubleSide
9470
+ })
9471
+ );
9472
+ mesh.renderOrder = FACE_RENDER_ORDER + 1;
9473
+ mesh.position.z = z;
9474
+ mesh.raycast = () => {
9475
+ };
9476
+ parent?.add(mesh);
9477
+ return mesh;
9478
+ }
9479
+ /** Remove + dispose knob/fill/cap so a sync pass rebuilds them fresh. */
9480
+ disposeMovingParts(entry) {
9481
+ for (const key of ["knob", "fill", "cap"]) {
9482
+ const mesh = entry[key];
9483
+ if (!mesh) continue;
9484
+ mesh.removeFromParent();
9485
+ mesh.geometry.dispose();
9486
+ mesh.material.dispose();
9487
+ entry[key] = null;
9488
+ }
9489
+ }
9490
+ /**
9491
+ * Billboard: yaw the ENTITY TRANSFORM toward the camera. Writing the
9492
+ * Object3D directly would be overwritten by `TransformSyncSystem` (which
9493
+ * runs after this system, before render) — the same reason locomotion
9494
+ * writes the rig transform. Skipped in the editor so the gizmo and the
9495
+ * authored rotation stay in charge while authoring.
9496
+ */
9497
+ applyFollow(world, entityId, panel) {
9498
+ if (panel.follow !== "billboard" || world.isEditorMode()) return;
9499
+ const transform = world.getComponent(entityId, TransformComponent);
9500
+ const object = world.getComponent(entityId, Object3DRef)?.object;
9501
+ if (!transform || !object) return;
9502
+ const camera = world.getCamera();
9503
+ const cam = new THREE19.Vector3();
9504
+ const self = new THREE19.Vector3();
9505
+ camera.getWorldPosition(cam);
9506
+ object.getWorldPosition(self);
9507
+ transform.rotation.y = Math.atan2(cam.x - self.x, cam.z - self.z);
9508
+ }
9509
+ destroyEntry(entry) {
9510
+ this.disposeMovingParts(entry);
9511
+ entry.face.removeFromParent();
9512
+ entry.face.geometry.dispose();
9513
+ entry.face.material.dispose();
9514
+ entry.texture.dispose();
9515
+ }
9516
+ };
9517
+ __name(_UIPanelSystem, "UIPanelSystem");
9518
+ var UIPanelSystem = _UIPanelSystem;
8371
9519
  var _XRHitTestSystem = class _XRHitTestSystem {
8372
9520
  constructor() {
8373
9521
  this.sources = /* @__PURE__ */ new Map();
@@ -8480,9 +9628,13 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8480
9628
  this.anchors = /* @__PURE__ */ new Map();
8481
9629
  /** In-flight creations, so one entity is not anchored twice. */
8482
9630
  this.pending = /* @__PURE__ */ new Set();
9631
+ /** Per-anchor head-glue gates — see XREnvironmentSystem's TrackedEntry. */
9632
+ this.gates = /* @__PURE__ */ new Map();
8483
9633
  this.presenting = false;
9634
+ this.tmpViewerMatrix = new THREE19.Matrix4();
9635
+ this.tmpViewPos = new THREE19.Vector3();
8484
9636
  }
8485
- update(world) {
9637
+ update(world, deltaTime = 0) {
8486
9638
  const presenting = world.isXrPresenting();
8487
9639
  if (!presenting) {
8488
9640
  if (this.presenting) {
@@ -8502,8 +9654,11 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8502
9654
  if (!live.has(entityId)) {
8503
9655
  anchor.delete?.();
8504
9656
  this.anchors.delete(entityId);
9657
+ this.gates.delete(entityId);
8505
9658
  }
8506
9659
  }
9660
+ const viewer = frame.getViewerPose?.(referenceSpace);
9661
+ const viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
8507
9662
  for (const entityId of entityIds) {
8508
9663
  const component = world.getComponent(entityId, XRAnchorComponent);
8509
9664
  const transform = world.getComponent(entityId, TransformComponent);
@@ -8513,6 +9668,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8513
9668
  if (existing) {
8514
9669
  existing.delete?.();
8515
9670
  this.anchors.delete(entityId);
9671
+ this.gates.delete(entityId);
8516
9672
  }
8517
9673
  continue;
8518
9674
  }
@@ -8523,7 +9679,21 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8523
9679
  }
8524
9680
  const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
8525
9681
  if (pose && pose.emulatedPosition !== true) {
8526
- decomposeXrPose(pose.transform.matrix, transform);
9682
+ let gate = this.gates.get(entityId);
9683
+ if (!gate) {
9684
+ gate = new SurfacePoseGate();
9685
+ this.gates.set(entityId, gate);
9686
+ }
9687
+ const m = pose.transform.matrix;
9688
+ const refPos = { x: m[12], y: m[13], z: m[14] };
9689
+ let viewPos = null;
9690
+ if (viewerInverse) {
9691
+ const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(viewerInverse);
9692
+ viewPos = { x: v.x, y: v.y, z: v.z };
9693
+ }
9694
+ if (gate.update(refPos, viewPos, deltaTime)) {
9695
+ decomposeXrPose(pose.transform.matrix, transform);
9696
+ }
8527
9697
  }
8528
9698
  }
8529
9699
  }
@@ -8568,6 +9738,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
8568
9738
  }).finally(() => this.pending.delete(entityId));
8569
9739
  }
8570
9740
  releaseAll() {
9741
+ this.gates.clear();
8571
9742
  for (const anchor of this.anchors.values()) anchor.delete?.();
8572
9743
  this.anchors.clear();
8573
9744
  this.pending.clear();
@@ -9813,6 +10984,6 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9813
10984
  __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9814
10985
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9815
10986
 
9816
- export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_DIRECTION_TOLERANCE, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TeleportAimGate, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRAnchorSystem, XRControllerVisualSystem, XREnvironmentSystem, XRHandPoseSystem, XRHitTestSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
9817
- //# sourceMappingURL=chunk-QIIWFP3V.js.map
9818
- //# sourceMappingURL=chunk-QIIWFP3V.js.map
10987
+ 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, UIInteractionSystem, UIPanelSystem, 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 };
10988
+ //# sourceMappingURL=chunk-Z6ZMR3CS.js.map
10989
+ //# sourceMappingURL=chunk-Z6ZMR3CS.js.map