@relu-interactives/spatial-ecs 0.4.4 → 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 (50) 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-OY2OONRR.js → chunk-Z6ZMR3CS.js} +1374 -30
  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/mixed-reality.md +137 -3
  19. package/dist/docs/guide/spatial-ui.md +165 -0
  20. package/dist/docs/guide/vr-and-hand-poses.md +29 -0
  21. package/dist/docs/guide/world-api.md +1 -1
  22. package/dist/factories/index.cjs +372 -114
  23. package/dist/factories/index.cjs.map +1 -1
  24. package/dist/factories/index.d.cts +13 -3
  25. package/dist/factories/index.d.ts +13 -3
  26. package/dist/factories/index.js +3 -3
  27. package/dist/{index-CuGqxuk-.d.ts → index-C3ik3sA1.d.ts} +16 -3
  28. package/dist/{index-B2oHfqkj.d.ts → index-CppKDxbu.d.ts} +187 -3
  29. package/dist/{index-CQsHUez3.d.cts → index-Dlr-7-4m.d.cts} +16 -3
  30. package/dist/{index-7YCk4cmQ.d.cts → index-I2IsocYW.d.cts} +187 -3
  31. package/dist/index.cjs +1977 -366
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +42 -9
  34. package/dist/index.d.ts +42 -9
  35. package/dist/index.js +10 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/systems/index.cjs +1465 -25
  38. package/dist/systems/index.cjs.map +1 -1
  39. package/dist/systems/index.d.cts +2 -2
  40. package/dist/systems/index.d.ts +2 -2
  41. package/dist/systems/index.js +3 -3
  42. package/dist/types.d.cts +1 -1
  43. package/dist/types.d.ts +1 -1
  44. package/dist/worldSnapshotUtils.d.cts +1 -1
  45. package/dist/worldSnapshotUtils.d.ts +1 -1
  46. package/package.json +1 -1
  47. package/dist/chunk-4AK4I3QF.js.map +0 -1
  48. package/dist/chunk-4DWZK4X4.js.map +0 -1
  49. package/dist/chunk-OY2OONRR.js.map +0 -1
  50. package/dist/chunk-SGADE55G.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var THREE51 = require('three');
3
+ var THREE56 = require('three');
4
4
  var SkeletonUtils_js = require('three/examples/jsm/utils/SkeletonUtils.js');
5
5
  var RectAreaLightHelper_js = require('three/examples/jsm/helpers/RectAreaLightHelper.js');
6
6
  var DRACOLoader_js = require('three/examples/jsm/loaders/DRACOLoader.js');
@@ -41,7 +41,7 @@ function _interopNamespace(e) {
41
41
  return Object.freeze(n);
42
42
  }
43
43
 
44
- var THREE51__namespace = /*#__PURE__*/_interopNamespace(THREE51);
44
+ var THREE56__namespace = /*#__PURE__*/_interopNamespace(THREE56);
45
45
 
46
46
  var __defProp = Object.defineProperty;
47
47
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -605,14 +605,14 @@ var _AnimationComponent = class _AnimationComponent extends Component {
605
605
  return;
606
606
  }
607
607
  this.importedClips = clips.map((entry) => {
608
- if (entry instanceof THREE51__namespace.AnimationClip) {
608
+ if (entry instanceof THREE56__namespace.AnimationClip) {
609
609
  return { clip: entry, sourceUrl: null };
610
610
  }
611
611
  if (!entry || typeof entry !== "object") {
612
612
  return null;
613
613
  }
614
614
  const candidate = entry;
615
- if (!(candidate.clip instanceof THREE51__namespace.AnimationClip)) {
615
+ if (!(candidate.clip instanceof THREE56__namespace.AnimationClip)) {
616
616
  return null;
617
617
  }
618
618
  return {
@@ -820,11 +820,11 @@ function normalizeDistanceModel(distanceModel) {
820
820
  }
821
821
  __name(normalizeDistanceModel, "normalizeDistanceModel");
822
822
  function createConfiguredAudio(listener, options) {
823
- const sound = options.positional ? new THREE51__namespace.PositionalAudio(listener) : new THREE51__namespace.Audio(listener);
823
+ const sound = options.positional ? new THREE56__namespace.PositionalAudio(listener) : new THREE56__namespace.Audio(listener);
824
824
  sound.setLoop(options.loop);
825
825
  sound.setVolume(options.volume);
826
826
  sound.setPlaybackRate(options.pitch);
827
- if (sound instanceof THREE51__namespace.PositionalAudio) {
827
+ if (sound instanceof THREE56__namespace.PositionalAudio) {
828
828
  sound.setRefDistance(options.refDistance);
829
829
  sound.setDistanceModel(normalizeDistanceModel(options.distanceModel));
830
830
  sound.setRolloffFactor(options.rolloffFactor);
@@ -834,16 +834,16 @@ function createConfiguredAudio(listener, options) {
834
834
  __name(createConfiguredAudio, "createConfiguredAudio");
835
835
  function createAudioEditorHelper() {
836
836
  if (typeof document === "undefined") {
837
- const geometry = new THREE51__namespace.EdgesGeometry(
838
- new THREE51__namespace.OctahedronGeometry(0.22, 0)
837
+ const geometry = new THREE56__namespace.EdgesGeometry(
838
+ new THREE56__namespace.OctahedronGeometry(0.22, 0)
839
839
  );
840
- const material2 = new THREE51__namespace.LineBasicMaterial({
840
+ const material2 = new THREE56__namespace.LineBasicMaterial({
841
841
  color: "#8bc8ff",
842
842
  transparent: true,
843
843
  opacity: 0.95,
844
844
  depthTest: false
845
845
  });
846
- const fallback = new THREE51__namespace.LineSegments(geometry, material2);
846
+ const fallback = new THREE56__namespace.LineSegments(geometry, material2);
847
847
  fallback.renderOrder = 10;
848
848
  fallback.userData.ecsHelper = true;
849
849
  fallback.visible = false;
@@ -855,7 +855,7 @@ function createAudioEditorHelper() {
855
855
  canvas.height = size;
856
856
  const ctx = canvas.getContext("2d");
857
857
  if (!ctx) {
858
- return new THREE51__namespace.Object3D();
858
+ return new THREE56__namespace.Object3D();
859
859
  }
860
860
  ctx.clearRect(0, 0, size, size);
861
861
  ctx.fillStyle = "rgba(27, 35, 52, 0.9)";
@@ -886,14 +886,14 @@ function createAudioEditorHelper() {
886
886
  ctx.beginPath();
887
887
  ctx.arc(56, 48, 16, -0.8, 0.8);
888
888
  ctx.stroke();
889
- const texture = new THREE51__namespace.CanvasTexture(canvas);
889
+ const texture = new THREE56__namespace.CanvasTexture(canvas);
890
890
  texture.needsUpdate = true;
891
- const material = new THREE51__namespace.SpriteMaterial({
891
+ const material = new THREE56__namespace.SpriteMaterial({
892
892
  map: texture,
893
893
  transparent: true,
894
894
  depthTest: false
895
895
  });
896
- const helper = new THREE51__namespace.Sprite(material);
896
+ const helper = new THREE56__namespace.Sprite(material);
897
897
  helper.scale.set(0.68, 0.68, 0.68);
898
898
  helper.renderOrder = 10;
899
899
  helper.userData.ecsHelper = true;
@@ -927,7 +927,7 @@ var DEFAULT_ORTHO_TOP = 5;
927
927
  var DEFAULT_ORTHO_BOTTOM = -5;
928
928
  var DEFAULT_ZOOM = 1;
929
929
  function inferProjection(camera) {
930
- if (camera instanceof THREE51__namespace.OrthographicCamera) {
930
+ if (camera instanceof THREE56__namespace.OrthographicCamera) {
931
931
  return "orthographic";
932
932
  }
933
933
  return "perspective";
@@ -948,7 +948,7 @@ function readCameraFar(camera) {
948
948
  }
949
949
  __name(readCameraFar, "readCameraFar");
950
950
  function inferFov(camera) {
951
- if (camera instanceof THREE51__namespace.PerspectiveCamera && Number.isFinite(camera.fov)) {
951
+ if (camera instanceof THREE56__namespace.PerspectiveCamera && Number.isFinite(camera.fov)) {
952
952
  return camera.fov;
953
953
  }
954
954
  return DEFAULT_FOV;
@@ -971,28 +971,28 @@ function inferFar(camera) {
971
971
  }
972
972
  __name(inferFar, "inferFar");
973
973
  function inferOrthoLeft(camera) {
974
- if (camera instanceof THREE51__namespace.OrthographicCamera && Number.isFinite(camera.left)) {
974
+ if (camera instanceof THREE56__namespace.OrthographicCamera && Number.isFinite(camera.left)) {
975
975
  return camera.left;
976
976
  }
977
977
  return DEFAULT_ORTHO_LEFT;
978
978
  }
979
979
  __name(inferOrthoLeft, "inferOrthoLeft");
980
980
  function inferOrthoRight(camera) {
981
- if (camera instanceof THREE51__namespace.OrthographicCamera && Number.isFinite(camera.right)) {
981
+ if (camera instanceof THREE56__namespace.OrthographicCamera && Number.isFinite(camera.right)) {
982
982
  return camera.right;
983
983
  }
984
984
  return DEFAULT_ORTHO_RIGHT;
985
985
  }
986
986
  __name(inferOrthoRight, "inferOrthoRight");
987
987
  function inferOrthoTop(camera) {
988
- if (camera instanceof THREE51__namespace.OrthographicCamera && Number.isFinite(camera.top)) {
988
+ if (camera instanceof THREE56__namespace.OrthographicCamera && Number.isFinite(camera.top)) {
989
989
  return camera.top;
990
990
  }
991
991
  return DEFAULT_ORTHO_TOP;
992
992
  }
993
993
  __name(inferOrthoTop, "inferOrthoTop");
994
994
  function inferOrthoBottom(camera) {
995
- if (camera instanceof THREE51__namespace.OrthographicCamera && Number.isFinite(camera.bottom)) {
995
+ if (camera instanceof THREE56__namespace.OrthographicCamera && Number.isFinite(camera.bottom)) {
996
996
  return camera.bottom;
997
997
  }
998
998
  return DEFAULT_ORTHO_BOTTOM;
@@ -1008,7 +1008,7 @@ __name(inferZoom, "inferZoom");
1008
1008
  var _CameraComponent = class _CameraComponent extends Component {
1009
1009
  constructor(payload = {}) {
1010
1010
  super();
1011
- this.camera = payload.camera ?? new THREE51__namespace.PerspectiveCamera();
1011
+ this.camera = payload.camera ?? new THREE56__namespace.PerspectiveCamera();
1012
1012
  this.type = payload.type ?? "3d";
1013
1013
  this.isDefault = typeof payload.isDefault === "boolean" ? payload.isDefault : false;
1014
1014
  this.projection = payload.projection ?? inferProjection(payload.camera);
@@ -1039,7 +1039,7 @@ var Object3DRef = _Object3DRef;
1039
1039
  // core/factories/components/CameraFactory.ts
1040
1040
  function createCameraComponent(world, entityId, payload = {}) {
1041
1041
  const object = world.getComponent(entityId, Object3DRef)?.object;
1042
- const objectCamera = object instanceof THREE51__namespace.Camera ? object : void 0;
1042
+ const objectCamera = object instanceof THREE56__namespace.Camera ? object : void 0;
1043
1043
  const component = new CameraComponent({
1044
1044
  ...payload,
1045
1045
  camera: payload.camera ?? objectCamera
@@ -1609,15 +1609,15 @@ function createValueState(light) {
1609
1609
  castShadow: light.castShadow ?? false,
1610
1610
  frustumCulling: light.frustumCulled
1611
1611
  };
1612
- if (light instanceof THREE51__namespace.PointLight) {
1612
+ if (light instanceof THREE56__namespace.PointLight) {
1613
1613
  base.distance = light.distance;
1614
1614
  base.decay = light.decay;
1615
- } else if (light instanceof THREE51__namespace.SpotLight) {
1615
+ } else if (light instanceof THREE56__namespace.SpotLight) {
1616
1616
  base.distance = light.distance;
1617
1617
  base.decay = light.decay;
1618
1618
  base.angle = light.angle;
1619
1619
  base.penumbra = light.penumbra;
1620
- } else if (light instanceof THREE51__namespace.RectAreaLight) {
1620
+ } else if (light instanceof THREE56__namespace.RectAreaLight) {
1621
1621
  base.width = light.width;
1622
1622
  base.height = light.height;
1623
1623
  }
@@ -1660,13 +1660,13 @@ function toHexColor2(value, fallback) {
1660
1660
  }
1661
1661
  __name(toHexColor2, "toHexColor");
1662
1662
  function getMaterialType(material) {
1663
- if (material instanceof THREE51__namespace.MeshPhysicalMaterial) {
1663
+ if (material instanceof THREE56__namespace.MeshPhysicalMaterial) {
1664
1664
  return "physical";
1665
1665
  }
1666
- if (material instanceof THREE51__namespace.MeshPhongMaterial) {
1666
+ if (material instanceof THREE56__namespace.MeshPhongMaterial) {
1667
1667
  return "phong";
1668
1668
  }
1669
- if (material instanceof THREE51__namespace.MeshBasicMaterial) {
1669
+ if (material instanceof THREE56__namespace.MeshBasicMaterial) {
1670
1670
  return "basic";
1671
1671
  }
1672
1672
  return "standard";
@@ -1701,8 +1701,8 @@ function createMaterialValue(material) {
1701
1701
  opacity: typeof materialRecord.opacity === "number" ? materialRecord.opacity : 1,
1702
1702
  emissive: toHexColor2(materialRecord.emissive, "#000000"),
1703
1703
  emissiveIntensity: typeof materialRecord.emissiveIntensity === "number" ? materialRecord.emissiveIntensity : 0,
1704
- side: typeof materialRecord.side === "number" ? materialRecord.side : THREE51__namespace.FrontSide,
1705
- blending: typeof materialRecord.blending === "number" ? materialRecord.blending : THREE51__namespace.NormalBlending,
1704
+ side: typeof materialRecord.side === "number" ? materialRecord.side : THREE56__namespace.FrontSide,
1705
+ blending: typeof materialRecord.blending === "number" ? materialRecord.blending : THREE56__namespace.NormalBlending,
1706
1706
  depthTest: materialRecord.depthTest !== false,
1707
1707
  depthWrite: materialRecord.depthWrite !== false,
1708
1708
  wireframe: Boolean(materialRecord.wireframe),
@@ -1734,7 +1734,7 @@ function createMaterialValue(material) {
1734
1734
  materialRecord.lightMap
1735
1735
  ),
1736
1736
  envMap: textureImageSrc(materialRecord.envMap),
1737
- textureFiltering: map?.magFilter === THREE51__namespace.NearestFilter ? "sharp" : "smooth",
1737
+ textureFiltering: map?.magFilter === THREE56__namespace.NearestFilter ? "sharp" : "smooth",
1738
1738
  useMipmaps: map?.generateMipmaps !== false
1739
1739
  };
1740
1740
  }
@@ -1749,8 +1749,8 @@ function defaultMaterialValue() {
1749
1749
  opacity: 1,
1750
1750
  emissive: "#000000",
1751
1751
  emissiveIntensity: 0,
1752
- side: THREE51__namespace.FrontSide,
1753
- blending: THREE51__namespace.NormalBlending,
1752
+ side: THREE56__namespace.FrontSide,
1753
+ blending: THREE56__namespace.NormalBlending,
1754
1754
  depthTest: true,
1755
1755
  depthWrite: true,
1756
1756
  wireframe: false,
@@ -2327,14 +2327,14 @@ var _TransformComponent = class _TransformComponent extends Component {
2327
2327
  z: scale.z ?? 1
2328
2328
  };
2329
2329
  }
2330
- toPositionVector3(target = new THREE51.Vector3()) {
2330
+ toPositionVector3(target = new THREE56.Vector3()) {
2331
2331
  return target.set(this.position.x, this.position.y, this.position.z);
2332
2332
  }
2333
- toRotationEuler(target = new THREE51.Euler(), order = "XYZ") {
2333
+ toRotationEuler(target = new THREE56.Euler(), order = "XYZ") {
2334
2334
  return target.set(this.rotation.x, this.rotation.y, this.rotation.z, order);
2335
2335
  }
2336
- toRotationQuaternion(target = new THREE51.Quaternion(), order = "XYZ") {
2337
- const euler = new THREE51.Euler(
2336
+ toRotationQuaternion(target = new THREE56.Quaternion(), order = "XYZ") {
2337
+ const euler = new THREE56.Euler(
2338
2338
  this.rotation.x,
2339
2339
  this.rotation.y,
2340
2340
  this.rotation.z,
@@ -2342,7 +2342,7 @@ var _TransformComponent = class _TransformComponent extends Component {
2342
2342
  );
2343
2343
  return target.setFromEuler(euler);
2344
2344
  }
2345
- toScaleVector3(target = new THREE51.Vector3()) {
2345
+ toScaleVector3(target = new THREE56.Vector3()) {
2346
2346
  return target.set(this.scale.x, this.scale.y, this.scale.z);
2347
2347
  }
2348
2348
  setPosition(input) {
@@ -2360,8 +2360,8 @@ var _TransformComponent = class _TransformComponent extends Component {
2360
2360
  return this;
2361
2361
  }
2362
2362
  setRotation(input, order = "XYZ") {
2363
- if (input instanceof THREE51.Quaternion) {
2364
- const euler = new THREE51.Euler().setFromQuaternion(input, order);
2363
+ if (input instanceof THREE56.Quaternion) {
2364
+ const euler = new THREE56.Euler().setFromQuaternion(input, order);
2365
2365
  this.rotation.x = euler.x;
2366
2366
  this.rotation.y = euler.y;
2367
2367
  this.rotation.z = euler.z;
@@ -2978,6 +2978,140 @@ function createXRInteractorSideComponent(world, entityId, ...args) {
2978
2978
  }
2979
2979
  __name(createXRInteractorSideComponent, "createXRInteractorSideComponent");
2980
2980
 
2981
+ // core/components/UIButton.ts
2982
+ var _UIButtonComponent = class _UIButtonComponent extends Component {
2983
+ constructor(options) {
2984
+ super();
2985
+ this.label = options?.label ?? "Button";
2986
+ this.actionId = options?.actionId ?? "";
2987
+ this.width = options?.width ?? 0.3;
2988
+ this.height = options?.height ?? 0.1;
2989
+ this.fontSize = options?.fontSize ?? 0.03;
2990
+ this.fontWeight = options?.fontWeight ?? 600;
2991
+ this.textColor = options?.textColor ?? "#ffffff";
2992
+ this.backgroundColor = options?.backgroundColor ?? "#2a2a2a";
2993
+ this.backgroundOpacity = options?.backgroundOpacity ?? 1;
2994
+ this.cornerRadius = options?.cornerRadius ?? 0.02;
2995
+ this.borderWidth = options?.borderWidth ?? 2e-3;
2996
+ this.borderColor = options?.borderColor ?? "#3a3a3a";
2997
+ }
2998
+ };
2999
+ __name(_UIButtonComponent, "UIButtonComponent");
3000
+ var UIButtonComponent = _UIButtonComponent;
3001
+
3002
+ // core/factories/components/UIButtonComponentFactory.ts
3003
+ function createUIButtonComponent(world, entityId, ...args) {
3004
+ const component = new UIButtonComponent(...args);
3005
+ world.addComponent(entityId, component);
3006
+ return component;
3007
+ }
3008
+ __name(createUIButtonComponent, "createUIButtonComponent");
3009
+
3010
+ // core/components/UIPanel.ts
3011
+ var _UIPanelComponent = class _UIPanelComponent extends Component {
3012
+ constructor(options) {
3013
+ super();
3014
+ this.width = options?.width ?? 0.6;
3015
+ this.height = options?.height ?? 0.4;
3016
+ this.backgroundColor = options?.backgroundColor ?? "#1c1c1c";
3017
+ this.backgroundOpacity = options?.backgroundOpacity ?? 0.92;
3018
+ this.cornerRadius = options?.cornerRadius ?? 0.02;
3019
+ this.borderWidth = options?.borderWidth ?? 0;
3020
+ this.borderColor = options?.borderColor ?? "#333333";
3021
+ this.pixelDensity = options?.pixelDensity ?? 1024;
3022
+ this.follow = options?.follow ?? "world";
3023
+ }
3024
+ };
3025
+ __name(_UIPanelComponent, "UIPanelComponent");
3026
+ var UIPanelComponent = _UIPanelComponent;
3027
+
3028
+ // core/factories/components/UIPanelComponentFactory.ts
3029
+ function createUIPanelComponent(world, entityId, ...args) {
3030
+ const component = new UIPanelComponent(...args);
3031
+ world.addComponent(entityId, component);
3032
+ return component;
3033
+ }
3034
+ __name(createUIPanelComponent, "createUIPanelComponent");
3035
+
3036
+ // core/components/UISlider.ts
3037
+ var _UISliderComponent = class _UISliderComponent extends Component {
3038
+ constructor(options) {
3039
+ super();
3040
+ this.min = options?.min ?? 0;
3041
+ this.max = options?.max ?? Math.max(1, this.min + 1);
3042
+ if (this.max <= this.min) this.max = this.min + 1;
3043
+ const v = options?.value ?? (this.min + this.max) / 2;
3044
+ this.value = Math.min(this.max, Math.max(this.min, v));
3045
+ this.actionId = options?.actionId ?? "";
3046
+ this.width = options?.width ?? 0.4;
3047
+ this.height = options?.height ?? 0.06;
3048
+ this.trackColor = options?.trackColor ?? "#2a2a2a";
3049
+ this.accentColor = options?.accentColor ?? "#ff6b35";
3050
+ this.knobColor = options?.knobColor ?? "#ffffff";
3051
+ }
3052
+ };
3053
+ __name(_UISliderComponent, "UISliderComponent");
3054
+ var UISliderComponent = _UISliderComponent;
3055
+
3056
+ // core/factories/components/UISliderComponentFactory.ts
3057
+ function createUISliderComponent(world, entityId, ...args) {
3058
+ const component = new UISliderComponent(...args);
3059
+ world.addComponent(entityId, component);
3060
+ return component;
3061
+ }
3062
+ __name(createUISliderComponent, "createUISliderComponent");
3063
+
3064
+ // core/components/UIText.ts
3065
+ var _UITextComponent = class _UITextComponent extends Component {
3066
+ constructor(options) {
3067
+ super();
3068
+ this.text = options?.text ?? "Text";
3069
+ this.width = options?.width ?? 0.5;
3070
+ this.height = options?.height ?? 0.12;
3071
+ this.fontSize = options?.fontSize ?? 0.035;
3072
+ this.fontWeight = options?.fontWeight ?? 500;
3073
+ this.color = options?.color ?? "#ffffff";
3074
+ this.align = options?.align ?? "center";
3075
+ this.valign = options?.valign ?? "middle";
3076
+ this.lineHeight = options?.lineHeight ?? 1.25;
3077
+ this.padding = options?.padding ?? 0.01;
3078
+ }
3079
+ };
3080
+ __name(_UITextComponent, "UITextComponent");
3081
+ var UITextComponent = _UITextComponent;
3082
+
3083
+ // core/factories/components/UITextComponentFactory.ts
3084
+ function createUITextComponent(world, entityId, ...args) {
3085
+ const component = new UITextComponent(...args);
3086
+ world.addComponent(entityId, component);
3087
+ return component;
3088
+ }
3089
+ __name(createUITextComponent, "createUITextComponent");
3090
+
3091
+ // core/components/UIToggle.ts
3092
+ var _UIToggleComponent = class _UIToggleComponent extends Component {
3093
+ constructor(options) {
3094
+ super();
3095
+ this.value = options?.value ?? false;
3096
+ this.actionId = options?.actionId ?? "";
3097
+ this.width = options?.width ?? 0.14;
3098
+ this.height = options?.height ?? 0.07;
3099
+ this.trackColor = options?.trackColor ?? "#2a2a2a";
3100
+ this.accentColor = options?.accentColor ?? "#ff6b35";
3101
+ this.knobColor = options?.knobColor ?? "#ffffff";
3102
+ }
3103
+ };
3104
+ __name(_UIToggleComponent, "UIToggleComponent");
3105
+ var UIToggleComponent = _UIToggleComponent;
3106
+
3107
+ // core/factories/components/UIToggleComponentFactory.ts
3108
+ function createUIToggleComponent(world, entityId, ...args) {
3109
+ const component = new UIToggleComponent(...args);
3110
+ world.addComponent(entityId, component);
3111
+ return component;
3112
+ }
3113
+ __name(createUIToggleComponent, "createUIToggleComponent");
3114
+
2981
3115
  // core/factories/registerDefaultComponentFactories.ts
2982
3116
  function registerDefaultComponentFactories(objectManagementSystem) {
2983
3117
  objectManagementSystem.registerComponentFactory("animation", createAnimationComponent);
@@ -2991,6 +3125,11 @@ function registerDefaultComponentFactories(objectManagementSystem) {
2991
3125
  objectManagementSystem.registerComponentFactory("vrTeleportArea", createVRTeleportAreaComponent);
2992
3126
  objectManagementSystem.registerComponentFactory("xrHandPose", createXRHandPoseComponent);
2993
3127
  objectManagementSystem.registerComponentFactory("xrAnchor", createXRAnchorComponent);
3128
+ objectManagementSystem.registerComponentFactory("uiPanel", createUIPanelComponent);
3129
+ objectManagementSystem.registerComponentFactory("uiText", createUITextComponent);
3130
+ objectManagementSystem.registerComponentFactory("uiButton", createUIButtonComponent);
3131
+ objectManagementSystem.registerComponentFactory("uiToggle", createUIToggleComponent);
3132
+ objectManagementSystem.registerComponentFactory("uiSlider", createUISliderComponent);
2994
3133
  objectManagementSystem.registerComponentFactory("xrInteractable", createXRInteractableComponent);
2995
3134
  objectManagementSystem.registerComponentFactory("xrInteractor", createXRInteractorComponent);
2996
3135
  objectManagementSystem.registerComponentFactory("xrInteractorSide", createXRInteractorSideComponent);
@@ -3037,7 +3176,7 @@ var SHARED_MODEL_RESOURCE_FLAG = "spatialEcsSharedModelResource";
3037
3176
  var modelCache = /* @__PURE__ */ new Map();
3038
3177
  function markSharedResources(root) {
3039
3178
  root.traverse((child) => {
3040
- if (!(child instanceof THREE51__namespace.Mesh)) {
3179
+ if (!(child instanceof THREE56__namespace.Mesh)) {
3041
3180
  return;
3042
3181
  }
3043
3182
  if (child.geometry) {
@@ -3116,10 +3255,10 @@ var _ObjectManagementSystem = class _ObjectManagementSystem {
3116
3255
  }
3117
3256
  getOrCreateAudioListener() {
3118
3257
  const existing = this.camera.userData.ecsAudioListener;
3119
- if (existing instanceof THREE51__namespace.AudioListener) {
3258
+ if (existing instanceof THREE56__namespace.AudioListener) {
3120
3259
  return existing;
3121
3260
  }
3122
- const listener = new THREE51__namespace.AudioListener();
3261
+ const listener = new THREE56__namespace.AudioListener();
3123
3262
  this.camera.add(listener);
3124
3263
  this.camera.userData.ecsAudioListener = listener;
3125
3264
  return listener;
@@ -3193,7 +3332,7 @@ var _ObjectManagementSystem = class _ObjectManagementSystem {
3193
3332
  }
3194
3333
  if (dispose && object3d) {
3195
3334
  object3d.traverse((child) => {
3196
- if (child instanceof THREE51__namespace.Mesh) {
3335
+ if (child instanceof THREE56__namespace.Mesh) {
3197
3336
  if (!child.geometry?.userData?.[SHARED_MODEL_RESOURCE_FLAG]) {
3198
3337
  child.geometry?.dispose();
3199
3338
  }
@@ -3330,10 +3469,10 @@ __name(createBaseEntity, "createBaseEntity");
3330
3469
  // core/factories/objects/AudioFactory.ts
3331
3470
  function getOrCreateListener(camera) {
3332
3471
  const existing = camera.userData.ecsAudioListener;
3333
- if (existing instanceof THREE51__namespace.AudioListener) {
3472
+ if (existing instanceof THREE56__namespace.AudioListener) {
3334
3473
  return existing;
3335
3474
  }
3336
- const listener = new THREE51__namespace.AudioListener();
3475
+ const listener = new THREE56__namespace.AudioListener();
3337
3476
  camera.add(listener);
3338
3477
  camera.userData.ecsAudioListener = listener;
3339
3478
  return listener;
@@ -3346,7 +3485,7 @@ function createAudio({
3346
3485
  payload,
3347
3486
  isEditor
3348
3487
  }) {
3349
- const anchor = new THREE51__namespace.Object3D();
3488
+ const anchor = new THREE56__namespace.Object3D();
3350
3489
  const entityId = createBaseEntity(world, anchor, {
3351
3490
  label: payload.name ?? "Audio",
3352
3491
  entityType: "audio",
@@ -3368,7 +3507,7 @@ function createAudio({
3368
3507
  return entityId;
3369
3508
  }
3370
3509
  if (!isEditor) {
3371
- const loader = new THREE51__namespace.AudioLoader();
3510
+ const loader = new THREE56__namespace.AudioLoader();
3372
3511
  loader.load(
3373
3512
  audio.path,
3374
3513
  (buffer) => {
@@ -3443,7 +3582,7 @@ function applyCameraSettings(camera, options) {
3443
3582
  const near = typeof options.near === "number" ? options.near : DEFAULT_NEAR2;
3444
3583
  const far = typeof options.far === "number" ? options.far : DEFAULT_FAR2;
3445
3584
  const projection = options.projection;
3446
- if (camera instanceof THREE51__namespace.PerspectiveCamera) {
3585
+ if (camera instanceof THREE56__namespace.PerspectiveCamera) {
3447
3586
  if (!projection || projection === "perspective") {
3448
3587
  camera.fov = typeof options.fov === "number" ? options.fov : DEFAULT_FOV2;
3449
3588
  }
@@ -3453,7 +3592,7 @@ function applyCameraSettings(camera, options) {
3453
3592
  camera.updateProjectionMatrix();
3454
3593
  return;
3455
3594
  }
3456
- if (camera instanceof THREE51__namespace.OrthographicCamera) {
3595
+ if (camera instanceof THREE56__namespace.OrthographicCamera) {
3457
3596
  camera.left = typeof options.left === "number" ? options.left : -DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
3458
3597
  camera.right = typeof options.right === "number" ? options.right : DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
3459
3598
  camera.top = typeof options.top === "number" ? options.top : DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
@@ -3474,7 +3613,7 @@ function createCameraObject(options) {
3474
3613
  const bottom = options.bottom ?? -DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
3475
3614
  const left = options.left ?? -DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
3476
3615
  const right = options.right ?? DEFAULT_ORTHOGRAPHIC_HALF_HEIGHT;
3477
- const camera = new THREE51__namespace.OrthographicCamera(
3616
+ const camera = new THREE56__namespace.OrthographicCamera(
3478
3617
  left,
3479
3618
  right,
3480
3619
  top,
@@ -3486,7 +3625,7 @@ function createCameraObject(options) {
3486
3625
  camera.updateProjectionMatrix();
3487
3626
  return camera;
3488
3627
  }
3489
- return new THREE51__namespace.PerspectiveCamera(
3628
+ return new THREE56__namespace.PerspectiveCamera(
3490
3629
  options.fov ?? DEFAULT_FOV2,
3491
3630
  readViewportAspect(),
3492
3631
  near,
@@ -3504,7 +3643,7 @@ function createCamera({ world, scene, camera, payload, isEditor }) {
3504
3643
  transformInput: payload.transform
3505
3644
  });
3506
3645
  const objectRef = world.getComponent(entityId, Object3DRef);
3507
- if (objectRef?.object instanceof THREE51__namespace.Camera) {
3646
+ if (objectRef?.object instanceof THREE56__namespace.Camera) {
3508
3647
  objectRef.object.userData.entityId = entityId;
3509
3648
  }
3510
3649
  const componentOptions = {
@@ -3536,9 +3675,9 @@ function createCapsule({
3536
3675
  scene,
3537
3676
  payload
3538
3677
  }) {
3539
- const geometry = new THREE51__namespace.CapsuleGeometry(0.5, 1, 4, 8);
3540
- const material = new THREE51__namespace.MeshStandardMaterial({ color: "#ffffff" });
3541
- const mesh = new THREE51__namespace.Mesh(geometry, material);
3678
+ const geometry = new THREE56__namespace.CapsuleGeometry(0.5, 1, 4, 8);
3679
+ const material = new THREE56__namespace.MeshStandardMaterial({ color: "#ffffff" });
3680
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
3542
3681
  const entityId = createBaseEntity(world, mesh, {
3543
3682
  label: payload.name ?? "Capsule",
3544
3683
  entityType: "capsule",
@@ -3558,9 +3697,9 @@ function createCapsule({
3558
3697
  }
3559
3698
  __name(createCapsule, "createCapsule");
3560
3699
  function createCube({ world, scene, payload }) {
3561
- const geometry = new THREE51__namespace.BoxGeometry(1, 1, 1);
3562
- const material = new THREE51__namespace.MeshStandardMaterial({ color: "#ffffff" });
3563
- const mesh = new THREE51__namespace.Mesh(geometry, material);
3700
+ const geometry = new THREE56__namespace.BoxGeometry(1, 1, 1);
3701
+ const material = new THREE56__namespace.MeshStandardMaterial({ color: "#ffffff" });
3702
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
3564
3703
  const entityId = createBaseEntity(world, mesh, {
3565
3704
  label: payload.name ?? "Cube",
3566
3705
  entityType: "cube",
@@ -3585,9 +3724,9 @@ function createCylinder({
3585
3724
  scene,
3586
3725
  payload
3587
3726
  }) {
3588
- const geometry = new THREE51__namespace.CylinderGeometry(0.5, 0.5, 1, 20);
3589
- const material = new THREE51__namespace.MeshStandardMaterial({ color: "#ffffff" });
3590
- const mesh = new THREE51__namespace.Mesh(geometry, material);
3727
+ const geometry = new THREE56__namespace.CylinderGeometry(0.5, 0.5, 1, 20);
3728
+ const material = new THREE56__namespace.MeshStandardMaterial({ color: "#ffffff" });
3729
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
3591
3730
  const entityId = createBaseEntity(world, mesh, {
3592
3731
  label: payload.name ?? "Cylinder",
3593
3732
  entityType: "cylinder",
@@ -3607,7 +3746,7 @@ function createCylinder({
3607
3746
  }
3608
3747
  __name(createCylinder, "createCylinder");
3609
3748
  function createEmpty({ world, scene, payload, isEditor }) {
3610
- const object = new THREE51__namespace.Group();
3749
+ const object = new THREE56__namespace.Group();
3611
3750
  const entityId = createBaseEntity(world, object, {
3612
3751
  label: payload.name ?? "Empty",
3613
3752
  entityType: "empty",
@@ -3618,7 +3757,7 @@ function createEmpty({ world, scene, payload, isEditor }) {
3618
3757
  }
3619
3758
  __name(createEmpty, "createEmpty");
3620
3759
  function createImage({ world, scene, payload }) {
3621
- const anchor = new THREE51__namespace.Object3D();
3760
+ const anchor = new THREE56__namespace.Object3D();
3622
3761
  const imagePath = payload.path ?? "";
3623
3762
  const entityId = createBaseEntity(world, anchor, {
3624
3763
  label: payload.name ?? "Image",
@@ -3631,7 +3770,7 @@ function createImage({ world, scene, payload }) {
3631
3770
  if (!imagePath) {
3632
3771
  return entityId;
3633
3772
  }
3634
- const loader = new THREE51__namespace.TextureLoader();
3773
+ const loader = new THREE56__namespace.TextureLoader();
3635
3774
  loader.load(
3636
3775
  imagePath,
3637
3776
  (texture) => {
@@ -3639,13 +3778,13 @@ function createImage({ world, scene, payload }) {
3639
3778
  texture.dispose();
3640
3779
  return;
3641
3780
  }
3642
- const material = new THREE51__namespace.MeshBasicMaterial({
3781
+ const material = new THREE56__namespace.MeshBasicMaterial({
3643
3782
  map: texture,
3644
3783
  transparent: true,
3645
- side: THREE51__namespace.DoubleSide
3784
+ side: THREE56__namespace.DoubleSide
3646
3785
  });
3647
- const geometry = new THREE51__namespace.PlaneGeometry(1, 1);
3648
- const plane = new THREE51__namespace.Mesh(geometry, material);
3786
+ const geometry = new THREE56__namespace.PlaneGeometry(1, 1);
3787
+ const plane = new THREE56__namespace.Mesh(geometry, material);
3649
3788
  plane.userData.entityId = entityId;
3650
3789
  const textureImage = texture.image;
3651
3790
  const width = textureImage?.width ?? 1;
@@ -3691,7 +3830,7 @@ function createImageTarget({
3691
3830
  payload,
3692
3831
  isEditor
3693
3832
  }) {
3694
- const anchor = new THREE51__namespace.Object3D();
3833
+ const anchor = new THREE56__namespace.Object3D();
3695
3834
  const imagePath = payload.path ?? "";
3696
3835
  const entityId = createBaseEntity(world, anchor, {
3697
3836
  label: payload.name ?? "Image Target",
@@ -3708,7 +3847,7 @@ function createImageTarget({
3708
3847
  if (!isEditor || !imagePath) {
3709
3848
  return entityId;
3710
3849
  }
3711
- const loader = new THREE51__namespace.TextureLoader();
3850
+ const loader = new THREE56__namespace.TextureLoader();
3712
3851
  loader.load(
3713
3852
  imagePath,
3714
3853
  (texture) => {
@@ -3716,13 +3855,13 @@ function createImageTarget({
3716
3855
  texture.dispose();
3717
3856
  return;
3718
3857
  }
3719
- const material = new THREE51__namespace.MeshBasicMaterial({
3858
+ const material = new THREE56__namespace.MeshBasicMaterial({
3720
3859
  map: texture,
3721
3860
  transparent: true,
3722
- side: THREE51__namespace.DoubleSide
3861
+ side: THREE56__namespace.DoubleSide
3723
3862
  });
3724
- const geometry = new THREE51__namespace.PlaneGeometry(1, 1);
3725
- const plane = new THREE51__namespace.Mesh(geometry, material);
3863
+ const geometry = new THREE56__namespace.PlaneGeometry(1, 1);
3864
+ const plane = new THREE56__namespace.Mesh(geometry, material);
3726
3865
  plane.userData.entityId = entityId;
3727
3866
  const textureImage = texture.image;
3728
3867
  const width = textureImage?.width ?? 1;
@@ -3773,7 +3912,7 @@ function createInstancedMesh({
3773
3912
  scene,
3774
3913
  payload
3775
3914
  }) {
3776
- const anchor = new THREE51__namespace.Object3D();
3915
+ const anchor = new THREE56__namespace.Object3D();
3777
3916
  const opts = payload.options ?? {};
3778
3917
  const meshKind = isInstancedMeshKind(opts.meshKind) ? opts.meshKind : "cube";
3779
3918
  const meshParameters = opts.meshParameters && typeof opts.meshParameters === "object" ? opts.meshParameters : void 0;
@@ -3844,7 +3983,7 @@ function createAmbientLight({
3844
3983
  scene,
3845
3984
  payload
3846
3985
  }) {
3847
- const light = new THREE51__namespace.AmbientLight("#ffffff", 0.6);
3986
+ const light = new THREE56__namespace.AmbientLight("#ffffff", 0.6);
3848
3987
  const entityId = createBaseEntity(world, light, {
3849
3988
  label: payload.name ?? "Ambient Light",
3850
3989
  entityType: "ambientLight",
@@ -3861,7 +4000,7 @@ function createAreaLight({
3861
4000
  payload,
3862
4001
  isEditor
3863
4002
  }) {
3864
- const light = new THREE51__namespace.RectAreaLight("#ffffff", 5, 4, 4);
4003
+ const light = new THREE56__namespace.RectAreaLight("#ffffff", 5, 4, 4);
3865
4004
  light.lookAt(0, 0, 0);
3866
4005
  const entityId = createBaseEntity(world, light, {
3867
4006
  label: payload.name ?? "Area Light",
@@ -3890,7 +4029,7 @@ function createDirectionalLight({
3890
4029
  payload,
3891
4030
  isEditor
3892
4031
  }) {
3893
- const light = new THREE51__namespace.DirectionalLight("#ffffff", 1.5);
4032
+ const light = new THREE56__namespace.DirectionalLight("#ffffff", 1.5);
3894
4033
  light.castShadow = true;
3895
4034
  const entityId = createBaseEntity(world, light, {
3896
4035
  label: payload.name ?? "Directional Light",
@@ -3901,7 +4040,7 @@ function createDirectionalLight({
3901
4040
  });
3902
4041
  createLightComponent(world, entityId, light);
3903
4042
  if (isEditor) {
3904
- const helper = new THREE51__namespace.DirectionalLightHelper(light, 1.8, "#38bdf8");
4043
+ const helper = new THREE56__namespace.DirectionalLightHelper(light, 1.8, "#38bdf8");
3905
4044
  helper.onBeforeRender = () => {
3906
4045
  helper.update();
3907
4046
  };
@@ -3919,7 +4058,7 @@ function createPointLight({
3919
4058
  payload,
3920
4059
  isEditor
3921
4060
  }) {
3922
- const light = new THREE51__namespace.PointLight("#ffffff", 1, 10);
4061
+ const light = new THREE56__namespace.PointLight("#ffffff", 1, 10);
3923
4062
  const entityId = createBaseEntity(world, light, {
3924
4063
  label: payload.name ?? "Point Light",
3925
4064
  entityType: "pointLight",
@@ -3929,7 +4068,7 @@ function createPointLight({
3929
4068
  });
3930
4069
  createLightComponent(world, entityId, light);
3931
4070
  if (isEditor) {
3932
- const helper = new THREE51__namespace.PointLightHelper(light, 0.35, "#fbbf24");
4071
+ const helper = new THREE56__namespace.PointLightHelper(light, 0.35, "#fbbf24");
3933
4072
  helper.onBeforeRender = () => {
3934
4073
  helper.update();
3935
4074
  };
@@ -3947,7 +4086,7 @@ function createSpotLight({
3947
4086
  payload,
3948
4087
  isEditor
3949
4088
  }) {
3950
- const light = new THREE51__namespace.SpotLight("#ffffff", 1, 20, Math.PI / 6, 0.1, 1);
4089
+ const light = new THREE56__namespace.SpotLight("#ffffff", 1, 20, Math.PI / 6, 0.1, 1);
3951
4090
  const entityId = createBaseEntity(world, light, {
3952
4091
  label: payload.name ?? "Spot Light",
3953
4092
  entityType: "spotLight",
@@ -3957,7 +4096,7 @@ function createSpotLight({
3957
4096
  });
3958
4097
  createLightComponent(world, entityId, light);
3959
4098
  if (isEditor) {
3960
- const helper = new THREE51__namespace.SpotLightHelper(light, "#f59e0b");
4099
+ const helper = new THREE56__namespace.SpotLightHelper(light, "#f59e0b");
3961
4100
  helper.onBeforeRender = () => {
3962
4101
  helper.update();
3963
4102
  };
@@ -4042,6 +4181,11 @@ __export(components_exports, {
4042
4181
  SelectionTransformState: () => SelectionTransformState,
4043
4182
  SpriteComponent: () => SpriteComponent,
4044
4183
  TransformComponent: () => TransformComponent,
4184
+ UIButtonComponent: () => UIButtonComponent,
4185
+ UIPanelComponent: () => UIPanelComponent,
4186
+ UISliderComponent: () => UISliderComponent,
4187
+ UITextComponent: () => UITextComponent,
4188
+ UIToggleComponent: () => UIToggleComponent,
4045
4189
  VRTeleportAreaComponent: () => VRTeleportAreaComponent,
4046
4190
  VideoComponent: () => VideoComponent,
4047
4191
  XRAnchorComponent: () => XRAnchorComponent,
@@ -4124,7 +4268,7 @@ function createModel({
4124
4268
  payload,
4125
4269
  isEditor
4126
4270
  }) {
4127
- const anchor = new THREE51__namespace.Object3D();
4271
+ const anchor = new THREE56__namespace.Object3D();
4128
4272
  const modelPath = payload.path ?? "";
4129
4273
  const entityId = createBaseEntity(world, anchor, {
4130
4274
  label: payload.name ?? "Model",
@@ -4190,12 +4334,12 @@ function createModel({
4190
4334
  }
4191
4335
  __name(createModel, "createModel");
4192
4336
  function createPlane({ world, scene, payload }) {
4193
- const geometry = new THREE51__namespace.PlaneGeometry(100, 100);
4194
- const material = new THREE51__namespace.MeshStandardMaterial({
4337
+ const geometry = new THREE56__namespace.PlaneGeometry(100, 100);
4338
+ const material = new THREE56__namespace.MeshStandardMaterial({
4195
4339
  color: "#ffffff",
4196
- side: THREE51__namespace.DoubleSide
4340
+ side: THREE56__namespace.DoubleSide
4197
4341
  });
4198
- const mesh = new THREE51__namespace.Mesh(geometry, material);
4342
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
4199
4343
  const entityId = createBaseEntity(
4200
4344
  world,
4201
4345
  mesh,
@@ -4231,7 +4375,7 @@ function createScriptObject({
4231
4375
  scene,
4232
4376
  payload
4233
4377
  }) {
4234
- const object = new THREE51__namespace.Group();
4378
+ const object = new THREE56__namespace.Group();
4235
4379
  const entityId = createBaseEntity(world, object, {
4236
4380
  label: payload.name ?? "Script Object",
4237
4381
  entityType: "scriptObject",
@@ -4256,15 +4400,15 @@ function createTeleportArea({
4256
4400
  const options = payload.options ?? {};
4257
4401
  const width = typeof options.width === "number" ? options.width : 10;
4258
4402
  const depth = typeof options.depth === "number" ? options.depth : 10;
4259
- const geometry = new THREE51__namespace.PlaneGeometry(width, depth);
4260
- const material = new THREE51__namespace.MeshBasicMaterial({
4403
+ const geometry = new THREE56__namespace.PlaneGeometry(width, depth);
4404
+ const material = new THREE56__namespace.MeshBasicMaterial({
4261
4405
  color: "#4da3ff",
4262
4406
  transparent: true,
4263
4407
  opacity: 0.18,
4264
- side: THREE51__namespace.DoubleSide,
4408
+ side: THREE56__namespace.DoubleSide,
4265
4409
  depthWrite: false
4266
4410
  });
4267
- const object = new THREE51__namespace.Mesh(geometry, material);
4411
+ const object = new THREE56__namespace.Mesh(geometry, material);
4268
4412
  const entityId = createBaseEntity(world, object, {
4269
4413
  label: payload.name ?? "Teleport Area",
4270
4414
  entityType: "teleportArea",
@@ -4292,7 +4436,7 @@ function createXRInteractor({
4292
4436
  scene,
4293
4437
  payload
4294
4438
  }) {
4295
- const root = new THREE51__namespace.Group();
4439
+ const root = new THREE56__namespace.Group();
4296
4440
  const options = payload.options ?? {};
4297
4441
  const entityId = createBaseEntity(world, root, {
4298
4442
  label: payload.name ?? "XR Interactor",
@@ -4305,7 +4449,7 @@ function createXRInteractor({
4305
4449
  return entityId;
4306
4450
  }
4307
4451
  for (const side of ["left", "right"]) {
4308
- const slot = new THREE51__namespace.Group();
4452
+ const slot = new THREE56__namespace.Group();
4309
4453
  const label = side === "left" ? "Left Interactor" : "Right Interactor";
4310
4454
  const slotEntityId = createBaseEntity(world, slot, {
4311
4455
  label,
@@ -4318,12 +4462,121 @@ function createXRInteractor({
4318
4462
  return entityId;
4319
4463
  }
4320
4464
  __name(createXRInteractor, "createXRInteractor");
4465
+ function createUIButton({
4466
+ world,
4467
+ scene,
4468
+ payload
4469
+ }) {
4470
+ const object = new THREE56__namespace.Group();
4471
+ const entityId = createBaseEntity(world, object, {
4472
+ label: payload.name ?? "UI Button",
4473
+ entityType: "uiButton",
4474
+ transformInput: payload.transform
4475
+ });
4476
+ const options = payload.options ?? {};
4477
+ world.addComponent(entityId, new UIButtonComponent(options));
4478
+ world.addComponent(
4479
+ entityId,
4480
+ new XRInteractableComponent({
4481
+ grabbable: false,
4482
+ pointable: true,
4483
+ hoverBehavior: "color"
4484
+ })
4485
+ );
4486
+ scene.add(object);
4487
+ return entityId;
4488
+ }
4489
+ __name(createUIButton, "createUIButton");
4490
+ function createUIPanel({
4491
+ world,
4492
+ scene,
4493
+ payload
4494
+ }) {
4495
+ const object = new THREE56__namespace.Group();
4496
+ const entityId = createBaseEntity(world, object, {
4497
+ label: payload.name ?? "UI Panel",
4498
+ entityType: "uiPanel",
4499
+ transformInput: payload.transform
4500
+ });
4501
+ const options = payload.options ?? {};
4502
+ world.addComponent(entityId, new UIPanelComponent(options));
4503
+ scene.add(object);
4504
+ return entityId;
4505
+ }
4506
+ __name(createUIPanel, "createUIPanel");
4507
+ function createUISlider({
4508
+ world,
4509
+ scene,
4510
+ payload
4511
+ }) {
4512
+ const object = new THREE56__namespace.Group();
4513
+ const entityId = createBaseEntity(world, object, {
4514
+ label: payload.name ?? "UI Slider",
4515
+ entityType: "uiSlider",
4516
+ transformInput: payload.transform
4517
+ });
4518
+ const options = payload.options ?? {};
4519
+ world.addComponent(entityId, new UISliderComponent(options));
4520
+ world.addComponent(
4521
+ entityId,
4522
+ new XRInteractableComponent({
4523
+ grabbable: false,
4524
+ pointable: true,
4525
+ hoverBehavior: "none"
4526
+ })
4527
+ );
4528
+ scene.add(object);
4529
+ return entityId;
4530
+ }
4531
+ __name(createUISlider, "createUISlider");
4532
+ function createUIText({
4533
+ world,
4534
+ scene,
4535
+ payload
4536
+ }) {
4537
+ const object = new THREE56__namespace.Group();
4538
+ const entityId = createBaseEntity(world, object, {
4539
+ label: payload.name ?? "UI Text",
4540
+ entityType: "uiText",
4541
+ transformInput: payload.transform
4542
+ });
4543
+ const options = payload.options ?? {};
4544
+ world.addComponent(entityId, new UITextComponent(options));
4545
+ scene.add(object);
4546
+ return entityId;
4547
+ }
4548
+ __name(createUIText, "createUIText");
4549
+ function createUIToggle({
4550
+ world,
4551
+ scene,
4552
+ payload
4553
+ }) {
4554
+ const object = new THREE56__namespace.Group();
4555
+ const entityId = createBaseEntity(world, object, {
4556
+ label: payload.name ?? "UI Toggle",
4557
+ entityType: "uiToggle",
4558
+ transformInput: payload.transform
4559
+ });
4560
+ const options = payload.options ?? {};
4561
+ world.addComponent(entityId, new UIToggleComponent(options));
4562
+ world.addComponent(
4563
+ entityId,
4564
+ new XRInteractableComponent({
4565
+ grabbable: false,
4566
+ pointable: true,
4567
+ hoverBehavior: "color"
4568
+ })
4569
+ );
4570
+ scene.add(object);
4571
+ return entityId;
4572
+ }
4573
+ __name(createUIToggle, "createUIToggle");
4321
4574
  function createXRRig({
4322
4575
  world,
4323
4576
  scene,
4324
4577
  payload
4325
4578
  }) {
4326
- const object = new THREE51__namespace.Group();
4579
+ const object = new THREE56__namespace.Group();
4327
4580
  const entityId = createBaseEntity(world, object, {
4328
4581
  label: payload.name ?? "XR Rig",
4329
4582
  entityType: "xrRig",
@@ -4336,7 +4589,7 @@ function createXRRig({
4336
4589
  }
4337
4590
  __name(createXRRig, "createXRRig");
4338
4591
  function createSprite({ world, scene, payload }) {
4339
- const anchor = new THREE51__namespace.Object3D();
4592
+ const anchor = new THREE56__namespace.Object3D();
4340
4593
  const imagePath = payload.path ?? "";
4341
4594
  const entityId = createBaseEntity(world, anchor, {
4342
4595
  label: payload.name ?? "Sprite",
@@ -4348,7 +4601,7 @@ function createSprite({ world, scene, payload }) {
4348
4601
  if (!imagePath) {
4349
4602
  return entityId;
4350
4603
  }
4351
- const loader = new THREE51__namespace.TextureLoader();
4604
+ const loader = new THREE56__namespace.TextureLoader();
4352
4605
  loader.load(
4353
4606
  imagePath,
4354
4607
  (texture) => {
@@ -4356,11 +4609,11 @@ function createSprite({ world, scene, payload }) {
4356
4609
  texture.dispose();
4357
4610
  return;
4358
4611
  }
4359
- const material = new THREE51__namespace.SpriteMaterial({
4612
+ const material = new THREE56__namespace.SpriteMaterial({
4360
4613
  map: texture,
4361
4614
  transparent: true
4362
4615
  });
4363
- const sprite = new THREE51__namespace.Sprite(material);
4616
+ const sprite = new THREE56__namespace.Sprite(material);
4364
4617
  sprite.userData.entityId = entityId;
4365
4618
  const textureImage = texture.image;
4366
4619
  spriteComponent.width = textureImage?.width ?? 1;
@@ -4378,9 +4631,9 @@ function createSprite({ world, scene, payload }) {
4378
4631
  }
4379
4632
  __name(createSprite, "createSprite");
4380
4633
  function createSphere({ world, scene, payload }) {
4381
- const geometry = new THREE51__namespace.SphereGeometry(0.5, 24, 24);
4382
- const material = new THREE51__namespace.MeshStandardMaterial({ color: "#ffffff" });
4383
- const mesh = new THREE51__namespace.Mesh(geometry, material);
4634
+ const geometry = new THREE56__namespace.SphereGeometry(0.5, 24, 24);
4635
+ const material = new THREE56__namespace.MeshStandardMaterial({ color: "#ffffff" });
4636
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
4384
4637
  const entityId = createBaseEntity(world, mesh, {
4385
4638
  label: payload.name ?? "Sphere",
4386
4639
  entityType: "sphere",
@@ -4400,10 +4653,10 @@ function createSphere({ world, scene, payload }) {
4400
4653
  __name(createSphere, "createSphere");
4401
4654
  function getOrCreateListener2(camera) {
4402
4655
  const existing = camera.userData.ecsAudioListener;
4403
- if (existing instanceof THREE51__namespace.AudioListener) {
4656
+ if (existing instanceof THREE56__namespace.AudioListener) {
4404
4657
  return existing;
4405
4658
  }
4406
- const listener = new THREE51__namespace.AudioListener();
4659
+ const listener = new THREE56__namespace.AudioListener();
4407
4660
  camera.add(listener);
4408
4661
  camera.userData.ecsAudioListener = listener;
4409
4662
  return listener;
@@ -4453,10 +4706,10 @@ function normalizeOptions(input) {
4453
4706
  }
4454
4707
  __name(normalizeOptions, "normalizeOptions");
4455
4708
  function createChromaMaterial(texture, options) {
4456
- return new THREE51__namespace.ShaderMaterial({
4709
+ return new THREE56__namespace.ShaderMaterial({
4457
4710
  uniforms: {
4458
4711
  tex: { value: texture },
4459
- keyColor: { value: new THREE51__namespace.Color(options.backgroundColor) },
4712
+ keyColor: { value: new THREE56__namespace.Color(options.backgroundColor) },
4460
4713
  similarity: { value: options.similarity },
4461
4714
  smoothness: { value: options.smoothness },
4462
4715
  spill: { value: options.spill }
@@ -4488,22 +4741,22 @@ function createChromaMaterial(texture, options) {
4488
4741
  }
4489
4742
  `,
4490
4743
  transparent: true,
4491
- side: THREE51__namespace.DoubleSide
4744
+ side: THREE56__namespace.DoubleSide
4492
4745
  });
4493
4746
  }
4494
4747
  __name(createChromaMaterial, "createChromaMaterial");
4495
4748
  function createStandardMaterial(texture) {
4496
- return new THREE51__namespace.MeshBasicMaterial({
4749
+ return new THREE56__namespace.MeshBasicMaterial({
4497
4750
  map: texture,
4498
4751
  transparent: true,
4499
- side: THREE51__namespace.DoubleSide
4752
+ side: THREE56__namespace.DoubleSide
4500
4753
  });
4501
4754
  }
4502
4755
  __name(createStandardMaterial, "createStandardMaterial");
4503
4756
  function createVideoMesh(entityId, texture, options) {
4504
- const geometry = new THREE51__namespace.PlaneGeometry(1, 1);
4757
+ const geometry = new THREE56__namespace.PlaneGeometry(1, 1);
4505
4758
  const material = options.isGreenScreen ? createChromaMaterial(texture, options) : createStandardMaterial(texture);
4506
- const mesh = new THREE51__namespace.Mesh(geometry, material);
4759
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
4507
4760
  mesh.userData.entityId = entityId;
4508
4761
  mesh.scale.set(options.width, options.height, 1);
4509
4762
  return { mesh, material };
@@ -4538,7 +4791,7 @@ function createVideo({
4538
4791
  payload,
4539
4792
  isEditor
4540
4793
  }) {
4541
- const anchor = new THREE51__namespace.Object3D();
4794
+ const anchor = new THREE56__namespace.Object3D();
4542
4795
  const path = payload.path ?? "";
4543
4796
  const options = normalizeOptions(payload.options);
4544
4797
  const entityId = createBaseEntity(world, anchor, {
@@ -4553,8 +4806,8 @@ function createVideo({
4553
4806
  return entityId;
4554
4807
  }
4555
4808
  const video = createVideoElement(path, options);
4556
- const texture = new THREE51__namespace.VideoTexture(video);
4557
- texture.colorSpace = THREE51__namespace.SRGBColorSpace;
4809
+ const texture = new THREE56__namespace.VideoTexture(video);
4810
+ texture.colorSpace = THREE56__namespace.SRGBColorSpace;
4558
4811
  texture.needsUpdate = true;
4559
4812
  const { mesh, material } = createVideoMesh(entityId, texture, options);
4560
4813
  anchor.add(mesh);
@@ -4570,7 +4823,7 @@ function createVideo({
4570
4823
  });
4571
4824
  if (!isEditor && options.positional) {
4572
4825
  const listener = getOrCreateListener2(camera);
4573
- const audioNode = new THREE51__namespace.PositionalAudio(listener);
4826
+ const audioNode = new THREE56__namespace.PositionalAudio(listener);
4574
4827
  audioNode.setVolume(options.volume);
4575
4828
  audioNode.setRefDistance(options.refDistance);
4576
4829
  audioNode.setDistanceModel(normalizeDistanceModel2(options.distanceModel));
@@ -4613,6 +4866,11 @@ function registerDefaultFactories(objectManagementSystem) {
4613
4866
  objectManagementSystem.register("xrRig", createXRRig);
4614
4867
  objectManagementSystem.register("xrInteractor", createXRInteractor);
4615
4868
  objectManagementSystem.register("teleportArea", createTeleportArea);
4869
+ objectManagementSystem.register("uiPanel", createUIPanel);
4870
+ objectManagementSystem.register("uiText", createUIText);
4871
+ objectManagementSystem.register("uiButton", createUIButton);
4872
+ objectManagementSystem.register("uiToggle", createUIToggle);
4873
+ objectManagementSystem.register("uiSlider", createUISlider);
4616
4874
  registerDefaultComponentFactories(objectManagementSystem);
4617
4875
  }
4618
4876
  __name(registerDefaultFactories, "registerDefaultFactories");
@@ -4763,6 +5021,11 @@ function buildComponentValueMaps(snapshot) {
4763
5021
  "XRHandPoseComponent"
4764
5022
  ),
4765
5023
  xrAnchorValues: readByComponent(snapshot.components, "XRAnchorComponent"),
5024
+ uiPanelValues: readByComponent(snapshot.components, "UIPanelComponent"),
5025
+ uiTextValues: readByComponent(snapshot.components, "UITextComponent"),
5026
+ uiButtonValues: readByComponent(snapshot.components, "UIButtonComponent"),
5027
+ uiToggleValues: readByComponent(snapshot.components, "UIToggleComponent"),
5028
+ uiSliderValues: readByComponent(snapshot.components, "UISliderComponent"),
4766
5029
  xrInteractorSideValues: readByComponent(
4767
5030
  snapshot.components,
4768
5031
  "XRInteractorSideComponent"
@@ -5084,6 +5347,49 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
5084
5347
  }
5085
5348
  }
5086
5349
  }
5350
+ {
5351
+ const restoreUi = /* @__PURE__ */ __name((map, Ctor, existing) => {
5352
+ const source = asRecord3(map.get(sourceEntityId));
5353
+ if (!source) return;
5354
+ let component = existing;
5355
+ if (!component) {
5356
+ component = new Ctor();
5357
+ world.addComponent(targetEntityId, component);
5358
+ }
5359
+ const target = component;
5360
+ for (const [key, value] of Object.entries(source)) {
5361
+ const current = target[key];
5362
+ if ((typeof value === "string" || typeof value === "number" || typeof value === "boolean") && typeof current === typeof value) {
5363
+ target[key] = value;
5364
+ }
5365
+ }
5366
+ }, "restoreUi");
5367
+ restoreUi(
5368
+ values.uiPanelValues,
5369
+ UIPanelComponent,
5370
+ world.getComponent(targetEntityId, UIPanelComponent)
5371
+ );
5372
+ restoreUi(
5373
+ values.uiTextValues,
5374
+ UITextComponent,
5375
+ world.getComponent(targetEntityId, UITextComponent)
5376
+ );
5377
+ restoreUi(
5378
+ values.uiButtonValues,
5379
+ UIButtonComponent,
5380
+ world.getComponent(targetEntityId, UIButtonComponent)
5381
+ );
5382
+ restoreUi(
5383
+ values.uiToggleValues,
5384
+ UIToggleComponent,
5385
+ world.getComponent(targetEntityId, UIToggleComponent)
5386
+ );
5387
+ restoreUi(
5388
+ values.uiSliderValues,
5389
+ UISliderComponent,
5390
+ world.getComponent(targetEntityId, UISliderComponent)
5391
+ );
5392
+ }
5087
5393
  {
5088
5394
  const sourceInteractor = asRecord3(
5089
5395
  values.xrInteractorValues.get(sourceEntityId)
@@ -6841,7 +7147,7 @@ function transitionToClip(runtime, nextClipId, crossFadeDuration) {
6841
7147
  }
6842
7148
  __name(transitionToClip, "transitionToClip");
6843
7149
  function applyActionState(action, component, paused, shouldLoop) {
6844
- const loopMode = shouldLoop ? THREE51__namespace.LoopRepeat : THREE51__namespace.LoopOnce;
7150
+ const loopMode = shouldLoop ? THREE56__namespace.LoopRepeat : THREE56__namespace.LoopOnce;
6845
7151
  const repetitions = shouldLoop ? Infinity : 1;
6846
7152
  action.setLoop(loopMode, repetitions);
6847
7153
  action.clampWhenFinished = !shouldLoop;
@@ -7014,7 +7320,7 @@ var _AnimationSystem = class _AnimationSystem {
7014
7320
  if (runtime) {
7015
7321
  stopAllActions(runtime);
7016
7322
  }
7017
- const mixer = new THREE51__namespace.AnimationMixer(root);
7323
+ const mixer = new THREE56__namespace.AnimationMixer(root);
7018
7324
  const entries2 = readRuntimeClipEntries(animation);
7019
7325
  const indexedActions = createActions(mixer, entries2);
7020
7326
  runtime = {
@@ -7140,11 +7446,11 @@ function normalizeOrthoBounds(component) {
7140
7446
  }
7141
7447
  __name(normalizeOrthoBounds, "normalizeOrthoBounds");
7142
7448
  function isPerspectiveCamera(camera) {
7143
- return camera instanceof THREE51__namespace.PerspectiveCamera;
7449
+ return camera instanceof THREE56__namespace.PerspectiveCamera;
7144
7450
  }
7145
7451
  __name(isPerspectiveCamera, "isPerspectiveCamera");
7146
7452
  function isOrthographicCamera(camera) {
7147
- return camera instanceof THREE51__namespace.OrthographicCamera;
7453
+ return camera instanceof THREE56__namespace.OrthographicCamera;
7148
7454
  }
7149
7455
  __name(isOrthographicCamera, "isOrthographicCamera");
7150
7456
  function readWindowAspect() {
@@ -7198,7 +7504,7 @@ var _CameraSyncSystem = class _CameraSyncSystem {
7198
7504
  continue;
7199
7505
  }
7200
7506
  const camera = objectRef.object;
7201
- if (!(camera instanceof THREE51__namespace.Camera)) {
7507
+ if (!(camera instanceof THREE56__namespace.Camera)) {
7202
7508
  continue;
7203
7509
  }
7204
7510
  if (xrCamera !== null && camera === xrCamera) {
@@ -7265,7 +7571,7 @@ var MIN_DENSITY = 0;
7265
7571
  function createGradientTexture(effect, colors) {
7266
7572
  if (typeof document === "undefined") {
7267
7573
  const pixel = new Uint8Array([30, 30, 30, 255]);
7268
- const fallback = new THREE51__namespace.DataTexture(pixel, 1, 1, THREE51__namespace.RGBAFormat);
7574
+ const fallback = new THREE56__namespace.DataTexture(pixel, 1, 1, THREE56__namespace.RGBAFormat);
7269
7575
  fallback.needsUpdate = true;
7270
7576
  return fallback;
7271
7577
  }
@@ -7274,7 +7580,7 @@ function createGradientTexture(effect, colors) {
7274
7580
  canvas.height = 512;
7275
7581
  const context = canvas.getContext("2d");
7276
7582
  if (!context) {
7277
- const fallback = new THREE51__namespace.CanvasTexture(canvas);
7583
+ const fallback = new THREE56__namespace.CanvasTexture(canvas);
7278
7584
  fallback.needsUpdate = true;
7279
7585
  return fallback;
7280
7586
  }
@@ -7302,15 +7608,15 @@ function createGradientTexture(effect, colors) {
7302
7608
  context.fillRect(0, 0, 512, 512);
7303
7609
  context.globalCompositeOperation = "source-over";
7304
7610
  }
7305
- const texture = new THREE51__namespace.CanvasTexture(canvas);
7306
- texture.colorSpace = THREE51__namespace.SRGBColorSpace;
7611
+ const texture = new THREE56__namespace.CanvasTexture(canvas);
7612
+ texture.colorSpace = THREE56__namespace.SRGBColorSpace;
7307
7613
  texture.needsUpdate = true;
7308
7614
  return texture;
7309
7615
  }
7310
7616
  __name(createGradientTexture, "createGradientTexture");
7311
7617
  var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
7312
7618
  constructor(scene) {
7313
- this.textureLoader = new THREE51__namespace.TextureLoader();
7619
+ this.textureLoader = new THREE56__namespace.TextureLoader();
7314
7620
  this.signature = null;
7315
7621
  this.activeTexture = null;
7316
7622
  this.pendingImageUrl = null;
@@ -7335,7 +7641,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
7335
7641
  if (skybox.type === "color") {
7336
7642
  this.disposeActiveTexture();
7337
7643
  this.pendingImageUrl = null;
7338
- this.scene.background = new THREE51__namespace.Color(skybox.color || "#1e1e1e");
7644
+ this.scene.background = new THREE56__namespace.Color(skybox.color || "#1e1e1e");
7339
7645
  return;
7340
7646
  }
7341
7647
  if (skybox.type === "gradient") {
@@ -7369,7 +7675,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
7369
7675
  texture.dispose();
7370
7676
  return;
7371
7677
  }
7372
- texture.colorSpace = THREE51__namespace.SRGBColorSpace;
7678
+ texture.colorSpace = THREE56__namespace.SRGBColorSpace;
7373
7679
  this.activeTexture = texture;
7374
7680
  this.scene.background = texture;
7375
7681
  },
@@ -7388,16 +7694,16 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
7388
7694
  this.scene.fog = null;
7389
7695
  return;
7390
7696
  }
7391
- const fogColor = new THREE51__namespace.Color(fog.color || "#1e1e1e");
7697
+ const fogColor = new THREE56__namespace.Color(fog.color || "#1e1e1e");
7392
7698
  if (fog.type === "linear") {
7393
7699
  const near = Number.isFinite(fog.linear.near) ? fog.linear.near : 1;
7394
7700
  const farCandidate = Number.isFinite(fog.linear.far) ? fog.linear.far : near + 10;
7395
7701
  const far = Math.max(near + MIN_FOG_DELTA, farCandidate);
7396
- this.scene.fog = new THREE51__namespace.Fog(fogColor, near, far);
7702
+ this.scene.fog = new THREE56__namespace.Fog(fogColor, near, far);
7397
7703
  return;
7398
7704
  }
7399
7705
  const density = Number.isFinite(fog.exponential.density) ? Math.max(MIN_DENSITY, fog.exponential.density) : 2e-3;
7400
- this.scene.fog = new THREE51__namespace.FogExp2(fogColor, density);
7706
+ this.scene.fog = new THREE56__namespace.FogExp2(fogColor, density);
7401
7707
  }
7402
7708
  update(world) {
7403
7709
  const passthrough = world.isXrPassthrough();
@@ -7491,10 +7797,10 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
7491
7797
  );
7492
7798
  }
7493
7799
  if (values.bloom.enabled) {
7494
- const size = this.renderer.getSize(new THREE51__namespace.Vector2());
7800
+ const size = this.renderer.getSize(new THREE56__namespace.Vector2());
7495
7801
  composer.addPass(
7496
7802
  new UnrealBloomPass_js.UnrealBloomPass(
7497
- new THREE51__namespace.Vector2(size.width, size.height),
7803
+ new THREE56__namespace.Vector2(size.width, size.height),
7498
7804
  values.bloom.intensity,
7499
7805
  values.bloom.radius,
7500
7806
  values.bloom.threshold
@@ -7539,7 +7845,7 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
7539
7845
  if (values.dotScreen.enabled) {
7540
7846
  composer.addPass(
7541
7847
  new DotScreenPass_js.DotScreenPass(
7542
- new THREE51__namespace.Vector2(0, 0),
7848
+ new THREE56__namespace.Vector2(0, 0),
7543
7849
  values.dotScreen.angle ?? values.dotScreen.dotScreenAngle,
7544
7850
  values.dotScreen.scale ?? values.dotScreen.dotScreenScale
7545
7851
  )
@@ -7627,7 +7933,7 @@ function computeColliderState(colliderValues, object) {
7627
7933
  if (!object) {
7628
7934
  return colliderValues;
7629
7935
  }
7630
- const scale = new THREE51__namespace.Vector3();
7936
+ const scale = new THREE56__namespace.Vector3();
7631
7937
  object.getWorldScale(scale);
7632
7938
  return {
7633
7939
  ...colliderValues,
@@ -7678,8 +7984,8 @@ function createColliderDesc(RAPIER, values, triMeshData) {
7678
7984
  values.offsetPosition.y,
7679
7985
  values.offsetPosition.z
7680
7986
  );
7681
- const offsetQuaternion = new THREE51__namespace.Quaternion().setFromEuler(
7682
- new THREE51__namespace.Euler(
7987
+ const offsetQuaternion = new THREE56__namespace.Quaternion().setFromEuler(
7988
+ new THREE56__namespace.Euler(
7683
7989
  values.offsetRotation.x,
7684
7990
  values.offsetRotation.y,
7685
7991
  values.offsetRotation.z
@@ -7694,17 +8000,17 @@ function collectTriMeshParts(object) {
7694
8000
  return null;
7695
8001
  }
7696
8002
  object.updateWorldMatrix(true, true);
7697
- const worldScale = new THREE51__namespace.Vector3();
8003
+ const worldScale = new THREE56__namespace.Vector3();
7698
8004
  object.getWorldScale(worldScale);
7699
8005
  const scale = {
7700
8006
  x: Math.abs(worldScale.x) || 1,
7701
8007
  y: Math.abs(worldScale.y) || 1,
7702
8008
  z: Math.abs(worldScale.z) || 1
7703
8009
  };
7704
- const worldToObject = new THREE51__namespace.Matrix4().copy(object.matrixWorld).invert();
8010
+ const worldToObject = new THREE56__namespace.Matrix4().copy(object.matrixWorld).invert();
7705
8011
  const parts = [];
7706
8012
  object.traverse((child) => {
7707
- if (!(child instanceof THREE51__namespace.Mesh) || !child.geometry) {
8013
+ if (!(child instanceof THREE56__namespace.Mesh) || !child.geometry) {
7708
8014
  return;
7709
8015
  }
7710
8016
  const geometry = child.geometry;
@@ -7721,7 +8027,7 @@ function collectTriMeshParts(object) {
7721
8027
  if (indexCount < 3) {
7722
8028
  return;
7723
8029
  }
7724
- const relativeMatrix = new THREE51__namespace.Matrix4().multiplyMatrices(
8030
+ const relativeMatrix = new THREE56__namespace.Matrix4().multiplyMatrices(
7725
8031
  worldToObject,
7726
8032
  child.matrixWorld
7727
8033
  );
@@ -7762,7 +8068,7 @@ function createTriMeshData(collected) {
7762
8068
  }
7763
8069
  const vertices = new Float32Array(vertexTotal * 3);
7764
8070
  const indices = new Uint32Array(indexTotal);
7765
- const point = new THREE51__namespace.Vector3();
8071
+ const point = new THREE56__namespace.Vector3();
7766
8072
  let vertexOffset = 0;
7767
8073
  let indexOffset = 0;
7768
8074
  for (const part of parts) {
@@ -7794,8 +8100,8 @@ function createTriMeshData(collected) {
7794
8100
  }
7795
8101
  __name(createTriMeshData, "createTriMeshData");
7796
8102
  function syncBodyFromTransform(body, transform) {
7797
- const quaternion = new THREE51__namespace.Quaternion().setFromEuler(
7798
- new THREE51__namespace.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
8103
+ const quaternion = new THREE56__namespace.Quaternion().setFromEuler(
8104
+ new THREE56__namespace.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
7799
8105
  );
7800
8106
  body.setTranslation(
7801
8107
  {
@@ -7822,8 +8128,8 @@ function syncTransformFromBody(transform, body) {
7822
8128
  transform.position.x = position.x;
7823
8129
  transform.position.y = position.y;
7824
8130
  transform.position.z = position.z;
7825
- const euler = new THREE51__namespace.Euler().setFromQuaternion(
7826
- new THREE51__namespace.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
8131
+ const euler = new THREE56__namespace.Euler().setFromQuaternion(
8132
+ new THREE56__namespace.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
7827
8133
  );
7828
8134
  transform.rotation.x = euler.x;
7829
8135
  transform.rotation.y = euler.y;
@@ -7936,8 +8242,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
7936
8242
  transform.position.y,
7937
8243
  transform.position.z
7938
8244
  );
7939
- const quaternion = new THREE51__namespace.Quaternion().setFromEuler(
7940
- new THREE51__namespace.Euler(
8245
+ const quaternion = new THREE56__namespace.Quaternion().setFromEuler(
8246
+ new THREE56__namespace.Euler(
7941
8247
  transform.rotation.x,
7942
8248
  transform.rotation.y,
7943
8249
  transform.rotation.z
@@ -8535,7 +8841,7 @@ function applyLightValues(lightComponent) {
8535
8841
  values.castShadow
8536
8842
  );
8537
8843
  }
8538
- if (light instanceof THREE51__namespace.PointLight) {
8844
+ if (light instanceof THREE56__namespace.PointLight) {
8539
8845
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
8540
8846
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
8541
8847
  }
@@ -8543,7 +8849,7 @@ function applyLightValues(lightComponent) {
8543
8849
  light.decay = Math.max(0, values.decay);
8544
8850
  }
8545
8851
  }
8546
- if (light instanceof THREE51__namespace.SpotLight) {
8852
+ if (light instanceof THREE56__namespace.SpotLight) {
8547
8853
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
8548
8854
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
8549
8855
  }
@@ -8557,7 +8863,7 @@ function applyLightValues(lightComponent) {
8557
8863
  light.penumbra = Math.max(0, Math.min(1, values.penumbra));
8558
8864
  }
8559
8865
  }
8560
- if (light instanceof THREE51__namespace.RectAreaLight) {
8866
+ if (light instanceof THREE56__namespace.RectAreaLight) {
8561
8867
  if (typeof values.width === "number" && Number.isFinite(values.width)) {
8562
8868
  light.width = Math.max(MIN_AREA_SIZE, values.width);
8563
8869
  }
@@ -8618,15 +8924,15 @@ function resolveMaterialPropertyKey(key) {
8618
8924
  __name(resolveMaterialPropertyKey, "resolveMaterialPropertyKey");
8619
8925
  function createMaterialByType(type) {
8620
8926
  if (type === "basic") {
8621
- return new THREE51__namespace.MeshBasicMaterial();
8927
+ return new THREE56__namespace.MeshBasicMaterial();
8622
8928
  }
8623
8929
  if (type === "phong") {
8624
- return new THREE51__namespace.MeshPhongMaterial();
8930
+ return new THREE56__namespace.MeshPhongMaterial();
8625
8931
  }
8626
8932
  if (type === "physical") {
8627
- return new THREE51__namespace.MeshPhysicalMaterial();
8933
+ return new THREE56__namespace.MeshPhysicalMaterial();
8628
8934
  }
8629
- return new THREE51__namespace.MeshStandardMaterial();
8935
+ return new THREE56__namespace.MeshStandardMaterial();
8630
8936
  }
8631
8937
  __name(createMaterialByType, "createMaterialByType");
8632
8938
  function replaceMaterialAcrossObject(object, previous, next) {
@@ -8685,12 +8991,12 @@ function applyTextureFiltering(material, filtering) {
8685
8991
  const target = material;
8686
8992
  for (const slot of materialTextureSlots) {
8687
8993
  const texture = target[slot];
8688
- if (!texture || !(texture instanceof THREE51__namespace.Texture)) {
8994
+ if (!texture || !(texture instanceof THREE56__namespace.Texture)) {
8689
8995
  continue;
8690
8996
  }
8691
8997
  const useMipmaps = texture.generateMipmaps !== false;
8692
- texture.magFilter = sharp ? THREE51__namespace.NearestFilter : THREE51__namespace.LinearFilter;
8693
- texture.minFilter = useMipmaps ? sharp ? THREE51__namespace.NearestMipmapNearestFilter : THREE51__namespace.LinearMipmapLinearFilter : sharp ? THREE51__namespace.NearestFilter : THREE51__namespace.LinearFilter;
8998
+ texture.magFilter = sharp ? THREE56__namespace.NearestFilter : THREE56__namespace.LinearFilter;
8999
+ texture.minFilter = useMipmaps ? sharp ? THREE56__namespace.NearestMipmapNearestFilter : THREE56__namespace.LinearMipmapLinearFilter : sharp ? THREE56__namespace.NearestFilter : THREE56__namespace.LinearFilter;
8694
9000
  texture.needsUpdate = true;
8695
9001
  }
8696
9002
  material.needsUpdate = true;
@@ -8700,12 +9006,12 @@ function setTextureMipmaps(material, enabled) {
8700
9006
  const target = material;
8701
9007
  for (const slot of materialTextureSlots) {
8702
9008
  const texture = target[slot];
8703
- if (!texture || !(texture instanceof THREE51__namespace.Texture)) {
9009
+ if (!texture || !(texture instanceof THREE56__namespace.Texture)) {
8704
9010
  continue;
8705
9011
  }
8706
9012
  texture.generateMipmaps = enabled;
8707
- const sharp = texture.magFilter === THREE51__namespace.NearestFilter;
8708
- texture.minFilter = enabled ? sharp ? THREE51__namespace.NearestMipmapNearestFilter : THREE51__namespace.LinearMipmapLinearFilter : sharp ? THREE51__namespace.NearestFilter : THREE51__namespace.LinearFilter;
9013
+ const sharp = texture.magFilter === THREE56__namespace.NearestFilter;
9014
+ texture.minFilter = enabled ? sharp ? THREE56__namespace.NearestMipmapNearestFilter : THREE56__namespace.LinearMipmapLinearFilter : sharp ? THREE56__namespace.NearestFilter : THREE56__namespace.LinearFilter;
8709
9015
  texture.needsUpdate = true;
8710
9016
  }
8711
9017
  material.needsUpdate = true;
@@ -8716,7 +9022,7 @@ function assignMaterialTexture(material, rawKey, url, textureCache) {
8716
9022
  const key = resolveMaterialPropertyKey(rawKey);
8717
9023
  let texture = textureCache.get(url);
8718
9024
  if (!texture) {
8719
- texture = new THREE51__namespace.TextureLoader().load(url);
9025
+ texture = new THREE56__namespace.TextureLoader().load(url);
8720
9026
  textureCache.set(url, texture);
8721
9027
  }
8722
9028
  target[key] = texture;
@@ -8754,7 +9060,7 @@ function applyMaterialState(material, value, textureCache) {
8754
9060
  }
8755
9061
  target.side = value.side;
8756
9062
  target.blending = value.blending;
8757
- target.premultipliedAlpha = value.blending === THREE51__namespace.MultiplyBlending || value.blending === THREE51__namespace.SubtractiveBlending;
9063
+ target.premultipliedAlpha = value.blending === THREE56__namespace.MultiplyBlending || value.blending === THREE56__namespace.SubtractiveBlending;
8758
9064
  target.depthTest = value.depthTest;
8759
9065
  target.depthWrite = value.depthWrite;
8760
9066
  target.wireframe = value.wireframe;
@@ -8915,12 +9221,12 @@ function resolvePrimaryMesh(object) {
8915
9221
  if (!object) {
8916
9222
  return null;
8917
9223
  }
8918
- if (object instanceof THREE51__namespace.Mesh) {
9224
+ if (object instanceof THREE56__namespace.Mesh) {
8919
9225
  return object;
8920
9226
  }
8921
9227
  let found = null;
8922
9228
  object.traverse((child) => {
8923
- if (!found && child instanceof THREE51__namespace.Mesh) {
9229
+ if (!found && child instanceof THREE56__namespace.Mesh) {
8924
9230
  found = child;
8925
9231
  }
8926
9232
  });
@@ -8941,22 +9247,22 @@ function applyGeometry(component) {
8941
9247
  switch (component.kind) {
8942
9248
  case "cube": {
8943
9249
  const params = sanitized;
8944
- nextGeometry = new THREE51__namespace.BoxGeometry(params.width, params.height, params.depth);
9250
+ nextGeometry = new THREE56__namespace.BoxGeometry(params.width, params.height, params.depth);
8945
9251
  break;
8946
9252
  }
8947
9253
  case "plane": {
8948
9254
  const params = sanitized;
8949
- nextGeometry = new THREE51__namespace.PlaneGeometry(params.width, params.height);
9255
+ nextGeometry = new THREE56__namespace.PlaneGeometry(params.width, params.height);
8950
9256
  break;
8951
9257
  }
8952
9258
  case "sphere": {
8953
9259
  const params = sanitized;
8954
- nextGeometry = new THREE51__namespace.SphereGeometry(params.radius, 24, 24);
9260
+ nextGeometry = new THREE56__namespace.SphereGeometry(params.radius, 24, 24);
8955
9261
  break;
8956
9262
  }
8957
9263
  case "cylinder": {
8958
9264
  const params = sanitized;
8959
- nextGeometry = new THREE51__namespace.CylinderGeometry(
9265
+ nextGeometry = new THREE56__namespace.CylinderGeometry(
8960
9266
  params.radius,
8961
9267
  params.radius,
8962
9268
  params.height,
@@ -8966,7 +9272,7 @@ function applyGeometry(component) {
8966
9272
  }
8967
9273
  case "capsule": {
8968
9274
  const params = sanitized;
8969
- nextGeometry = new THREE51__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
9275
+ nextGeometry = new THREE56__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
8970
9276
  break;
8971
9277
  }
8972
9278
  default:
@@ -8982,7 +9288,7 @@ function applyShadowFlags(component) {
8982
9288
  return;
8983
9289
  }
8984
9290
  component.object.traverse((child) => {
8985
- if (child instanceof THREE51__namespace.Mesh) {
9291
+ if (child instanceof THREE56__namespace.Mesh) {
8986
9292
  child.castShadow = component.castShadow;
8987
9293
  child.receiveShadow = component.receiveShadow;
8988
9294
  }
@@ -9065,35 +9371,35 @@ function buildGeometry(component) {
9065
9371
  switch (component.meshKind) {
9066
9372
  case "cube": {
9067
9373
  const params = p;
9068
- return new THREE51__namespace.BoxGeometry(params.width, params.height, params.depth);
9374
+ return new THREE56__namespace.BoxGeometry(params.width, params.height, params.depth);
9069
9375
  }
9070
9376
  case "plane": {
9071
9377
  const params = p;
9072
- return new THREE51__namespace.PlaneGeometry(params.width, params.height);
9378
+ return new THREE56__namespace.PlaneGeometry(params.width, params.height);
9073
9379
  }
9074
9380
  case "sphere": {
9075
9381
  const params = p;
9076
- return new THREE51__namespace.SphereGeometry(params.radius, 24, 24);
9382
+ return new THREE56__namespace.SphereGeometry(params.radius, 24, 24);
9077
9383
  }
9078
9384
  case "cylinder": {
9079
9385
  const params = p;
9080
- return new THREE51__namespace.CylinderGeometry(params.radius, params.radius, params.height, 20);
9386
+ return new THREE56__namespace.CylinderGeometry(params.radius, params.radius, params.height, 20);
9081
9387
  }
9082
9388
  case "capsule": {
9083
9389
  const params = p;
9084
- return new THREE51__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
9390
+ return new THREE56__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
9085
9391
  }
9086
9392
  default:
9087
- return new THREE51__namespace.BoxGeometry(1, 1, 1);
9393
+ return new THREE56__namespace.BoxGeometry(1, 1, 1);
9088
9394
  }
9089
9395
  }
9090
9396
  __name(buildGeometry, "buildGeometry");
9091
- var _position = new THREE51__namespace.Vector3();
9092
- var _quaternion = new THREE51__namespace.Quaternion();
9093
- var _euler = new THREE51__namespace.Euler();
9094
- var _scale = new THREE51__namespace.Vector3();
9095
- var _matrix = new THREE51__namespace.Matrix4();
9096
- var _modelMatrix = new THREE51__namespace.Matrix4();
9397
+ var _position = new THREE56__namespace.Vector3();
9398
+ var _quaternion = new THREE56__namespace.Quaternion();
9399
+ var _euler = new THREE56__namespace.Euler();
9400
+ var _scale = new THREE56__namespace.Vector3();
9401
+ var _matrix = new THREE56__namespace.Matrix4();
9402
+ var _modelMatrix = new THREE56__namespace.Matrix4();
9097
9403
  function instanceTransformFromComponent(transform, out) {
9098
9404
  _position.set(transform.position.x, transform.position.y, transform.position.z);
9099
9405
  _euler.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, "XYZ");
@@ -9246,7 +9552,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
9246
9552
  const sourceMaterial = Array.isArray(meshChild.material) ? meshChild.material.map((m) => m.clone()) : meshChild.material.clone();
9247
9553
  meshChild.updateWorldMatrix(true, false);
9248
9554
  const localMatrix = meshChild.matrixWorld.clone();
9249
- const instancedMesh2 = new THREE51__namespace.InstancedMesh(
9555
+ const instancedMesh2 = new THREE56__namespace.InstancedMesh(
9250
9556
  geometry2,
9251
9557
  Array.isArray(sourceMaterial) ? sourceMaterial : sourceMaterial,
9252
9558
  count2
@@ -9271,7 +9577,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
9271
9577
  this.loadingSignatures.delete(targetEntityId);
9272
9578
  return;
9273
9579
  }
9274
- const group = new THREE51__namespace.Group();
9580
+ const group = new THREE56__namespace.Group();
9275
9581
  for (const entry of entries) {
9276
9582
  group.add(entry.instancedMesh);
9277
9583
  }
@@ -9289,8 +9595,8 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
9289
9595
  }
9290
9596
  const count = instanceCount;
9291
9597
  const geometry = buildGeometry(comp);
9292
- const material = new THREE51__namespace.MeshStandardMaterial({ color: "#ffffff" });
9293
- const instancedMesh = new THREE51__namespace.InstancedMesh(geometry, material, count);
9598
+ const material = new THREE56__namespace.MeshStandardMaterial({ color: "#ffffff" });
9599
+ const instancedMesh = new THREE56__namespace.InstancedMesh(geometry, material, count);
9294
9600
  instancedMesh.castShadow = comp.castShadow;
9295
9601
  instancedMesh.receiveShadow = comp.receiveShadow;
9296
9602
  for (let i = 0; i < count; i++) {
@@ -9473,7 +9779,7 @@ var _Behaviour = class _Behaviour {
9473
9779
  if (!this.threeRef) {
9474
9780
  return null;
9475
9781
  }
9476
- const position = new THREE51__namespace.Vector3();
9782
+ const position = new THREE56__namespace.Vector3();
9477
9783
  this.threeRef.object.getWorldPosition(position);
9478
9784
  return position;
9479
9785
  }
@@ -9485,7 +9791,7 @@ var _Behaviour = class _Behaviour {
9485
9791
  if (!this.threeRef) {
9486
9792
  return null;
9487
9793
  }
9488
- const target = new THREE51__namespace.Vector3();
9794
+ const target = new THREE56__namespace.Vector3();
9489
9795
  this.threeRef.object.getWorldDirection(target);
9490
9796
  return target;
9491
9797
  }
@@ -9547,7 +9853,7 @@ var _Behaviour = class _Behaviour {
9547
9853
  if (!this.threeRef) {
9548
9854
  return;
9549
9855
  }
9550
- const rotation = new THREE51__namespace.Quaternion();
9856
+ const rotation = new THREE56__namespace.Quaternion();
9551
9857
  this.threeRef.object.getWorldQuaternion(rotation);
9552
9858
  rotation.slerp(targetRotation, alpha);
9553
9859
  this.transform.setRotation(rotation);
@@ -9564,7 +9870,7 @@ var _Behaviour = class _Behaviour {
9564
9870
  return;
9565
9871
  }
9566
9872
  const clone3 = this.threeRef.object.clone();
9567
- const rotation = new THREE51__namespace.Quaternion();
9873
+ const rotation = new THREE56__namespace.Quaternion();
9568
9874
  clone3.lookAt(targetPosition);
9569
9875
  clone3.getWorldQuaternion(rotation);
9570
9876
  this.transform.setRotation(rotation);
@@ -9729,6 +10035,23 @@ var _Behaviour = class _Behaviour {
9729
10035
  /** Called when this entity is released. Optional XR hook. */
9730
10036
  onRelease(input) {
9731
10037
  }
10038
+ /** A UI button on this entity (or a descendant) was clicked. Clicks fire on
10039
+ * RELEASE while still pointing at the widget — sliding off cancels. */
10040
+ onUiPress(_event) {
10041
+ }
10042
+ /** A pointer began hovering a UI widget on this entity (or a descendant). */
10043
+ onUiHoverStart(_event) {
10044
+ }
10045
+ /** The pointer left the widget. */
10046
+ onUiHoverEnd(_event) {
10047
+ }
10048
+ /** A UI toggle flipped; `event.value` is the new boolean. */
10049
+ onUiToggle(_event) {
10050
+ }
10051
+ /** A UI slider moved; `event.value` is the new number. Fires on real value
10052
+ * changes during the drag and once more on release. */
10053
+ onUiChange(_event) {
10054
+ }
9732
10055
  };
9733
10056
  __name(_Behaviour, "Behaviour");
9734
10057
  /**
@@ -9930,8 +10253,8 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
9930
10253
  }, "onPointerUp");
9931
10254
  this.moduleLoader = moduleLoader ?? defaultModuleLoader;
9932
10255
  this.selectionCtx = selectionContext ?? null;
9933
- this.raycaster = new THREE51__namespace.Raycaster();
9934
- this.ndcPointer = new THREE51__namespace.Vector2();
10256
+ this.raycaster = new THREE56__namespace.Raycaster();
10257
+ this.ndcPointer = new THREE56__namespace.Vector2();
9935
10258
  if (this.selectionCtx) {
9936
10259
  this.selectionCtx.domElement.addEventListener("pointerdown", this.onPointerDown);
9937
10260
  this.selectionCtx.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -10310,7 +10633,7 @@ function disposeBuiltObject(root) {
10310
10633
  for (const mat of materials) {
10311
10634
  if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
10312
10635
  for (const value of Object.values(mat)) {
10313
- if (value instanceof THREE51__namespace.Texture) value.dispose();
10636
+ if (value instanceof THREE56__namespace.Texture) value.dispose();
10314
10637
  }
10315
10638
  mat.dispose?.();
10316
10639
  }
@@ -10674,7 +10997,7 @@ var _AudioSystem = class _AudioSystem {
10674
10997
  if (!prev || prev.volume !== opts.volume) {
10675
10998
  sound.setVolume(opts.volume);
10676
10999
  }
10677
- if (opts.positional && sound instanceof THREE51__namespace.PositionalAudio) {
11000
+ if (opts.positional && sound instanceof THREE56__namespace.PositionalAudio) {
10678
11001
  if (!prev || prev.refDistance !== opts.refDistance) {
10679
11002
  sound.setRefDistance(opts.refDistance);
10680
11003
  }
@@ -10800,7 +11123,7 @@ var _VideoSystem = class _VideoSystem {
10800
11123
  component.object.scale.set(opts.width, opts.height, 1);
10801
11124
  }
10802
11125
  const material = component.material;
10803
- if (material instanceof THREE51__namespace.ShaderMaterial) {
11126
+ if (material instanceof THREE56__namespace.ShaderMaterial) {
10804
11127
  if (!prev || prev.backgroundColor !== opts.backgroundColor) {
10805
11128
  material.uniforms.keyColor.value.set(opts.backgroundColor);
10806
11129
  }
@@ -10814,7 +11137,7 @@ var _VideoSystem = class _VideoSystem {
10814
11137
  material.uniforms.spill.value = opts.spill;
10815
11138
  }
10816
11139
  }
10817
- if (opts.positional && component.audioNode instanceof THREE51__namespace.PositionalAudio) {
11140
+ if (opts.positional && component.audioNode instanceof THREE56__namespace.PositionalAudio) {
10818
11141
  const posAudio = component.audioNode;
10819
11142
  if (!prev || prev.refDistance !== opts.refDistance) {
10820
11143
  posAudio.setRefDistance(opts.refDistance);
@@ -10854,8 +11177,8 @@ function blankState(source) {
10854
11177
  handedness: handednessOf(source),
10855
11178
  kind: kindOf(source),
10856
11179
  connected: true,
10857
- rayOrigin: new THREE51__namespace.Vector3(),
10858
- rayDirection: new THREE51__namespace.Vector3(0, 0, -1),
11180
+ rayOrigin: new THREE56__namespace.Vector3(),
11181
+ rayDirection: new THREE56__namespace.Vector3(0, 0, -1),
10859
11182
  gripPosition: null,
10860
11183
  gripQuaternion: null,
10861
11184
  select: false,
@@ -10881,7 +11204,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
10881
11204
  }
10882
11205
  if (spaces.length === 0) return null;
10883
11206
  const target = existing ?? {};
10884
- const matrix = new THREE51__namespace.Matrix4();
11207
+ const matrix = new THREE56__namespace.Matrix4();
10885
11208
  const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
10886
11209
  const fillPoses = frame.fillPoses;
10887
11210
  if (typeof fillPoses === "function") {
@@ -10894,7 +11217,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
10894
11217
  if (filled) {
10895
11218
  for (let i = 0; i < names.length; i += 1) {
10896
11219
  matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
10897
- const vector = target[names[i]] ?? new THREE51__namespace.Vector3();
11220
+ const vector = target[names[i]] ?? new THREE56__namespace.Vector3();
10898
11221
  vector.setFromMatrixPosition(matrix);
10899
11222
  target[names[i]] = vector;
10900
11223
  }
@@ -10912,7 +11235,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
10912
11235
  continue;
10913
11236
  }
10914
11237
  matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
10915
- const vector = target[names[i]] ?? new THREE51__namespace.Vector3();
11238
+ const vector = target[names[i]] ?? new THREE56__namespace.Vector3();
10916
11239
  vector.setFromMatrixPosition(matrix);
10917
11240
  target[names[i]] = vector;
10918
11241
  anyTracked = true;
@@ -10924,7 +11247,7 @@ var _XRInputSystem = class _XRInputSystem {
10924
11247
  constructor() {
10925
11248
  this.records = /* @__PURE__ */ new Map();
10926
11249
  /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
10927
- this.referenceToWorld = new THREE51__namespace.Matrix4();
11250
+ this.referenceToWorld = new THREE56__namespace.Matrix4();
10928
11251
  this.session = null;
10929
11252
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
10930
11253
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -11022,19 +11345,19 @@ var _XRInputSystem = class _XRInputSystem {
11022
11345
  if (frame && referenceSpace && source.targetRaySpace) {
11023
11346
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
11024
11347
  if (pose) {
11025
- const m = new THREE51__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
11348
+ const m = new THREE56__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
11026
11349
  state.rayOrigin.setFromMatrixPosition(m);
11027
11350
  state.rayDirection.set(0, 0, -1).applyQuaternion(
11028
- new THREE51__namespace.Quaternion().setFromRotationMatrix(m)
11351
+ new THREE56__namespace.Quaternion().setFromRotationMatrix(m)
11029
11352
  ).normalize();
11030
11353
  }
11031
11354
  }
11032
11355
  if (frame && referenceSpace && source.gripSpace) {
11033
11356
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
11034
11357
  if (gripPose) {
11035
- const m = new THREE51__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
11036
- state.gripPosition ?? (state.gripPosition = new THREE51__namespace.Vector3());
11037
- state.gripQuaternion ?? (state.gripQuaternion = new THREE51__namespace.Quaternion());
11358
+ const m = new THREE56__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
11359
+ state.gripPosition ?? (state.gripPosition = new THREE56__namespace.Vector3());
11360
+ state.gripQuaternion ?? (state.gripQuaternion = new THREE56__namespace.Quaternion());
11038
11361
  state.gripPosition.setFromMatrixPosition(m);
11039
11362
  state.gripQuaternion.setFromRotationMatrix(m);
11040
11363
  }
@@ -11162,15 +11485,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
11162
11485
  const fresh = this.samples.filter(
11163
11486
  (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
11164
11487
  );
11165
- if (fresh.length < 2) return new THREE51__namespace.Vector3();
11166
- let best = new THREE51__namespace.Vector3();
11488
+ if (fresh.length < 2) return new THREE56__namespace.Vector3();
11489
+ let best = new THREE56__namespace.Vector3();
11167
11490
  let bestSpeed = 0;
11168
11491
  for (let i = 1; i < fresh.length; i += 1) {
11169
11492
  const previous = fresh[i - 1];
11170
11493
  const current = fresh[i];
11171
11494
  const dt = current.time - previous.time;
11172
11495
  if (dt <= 1e-5) continue;
11173
- const velocity = new THREE51__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
11496
+ const velocity = new THREE56__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
11174
11497
  const speed = velocity.length();
11175
11498
  if (speed > MAX_PLAUSIBLE_SPEED) continue;
11176
11499
  if (speed > bestSpeed) {
@@ -11187,15 +11510,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
11187
11510
  __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
11188
11511
  var ThrowVelocityTracker = _ThrowVelocityTracker;
11189
11512
  function angularVelocityFrom(previous, current, deltaTime) {
11190
- if (deltaTime <= 1e-5) return new THREE51__namespace.Vector3();
11513
+ if (deltaTime <= 1e-5) return new THREE56__namespace.Vector3();
11191
11514
  const delta = current.clone().multiply(previous.clone().invert());
11192
11515
  if (delta.w < 0) {
11193
11516
  delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
11194
11517
  }
11195
- const angle = 2 * Math.acos(THREE51__namespace.MathUtils.clamp(delta.w, -1, 1));
11518
+ const angle = 2 * Math.acos(THREE56__namespace.MathUtils.clamp(delta.w, -1, 1));
11196
11519
  const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
11197
- if (sinHalf < 1e-6 || angle < 1e-6) return new THREE51__namespace.Vector3();
11198
- return new THREE51__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
11520
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE56__namespace.Vector3();
11521
+ return new THREE56__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
11199
11522
  }
11200
11523
  __name(angularVelocityFrom, "angularVelocityFrom");
11201
11524
  var MIN_HAND_SEPARATION = 0.06;
@@ -11203,7 +11526,7 @@ var MIN_ANCHOR_SEPARATION = 1e-3;
11203
11526
  var SCALE_DEADZONE = 0.05;
11204
11527
  function solveTwoHandTransform(primary, secondary, basis, options) {
11205
11528
  const separation = primary.position.distanceTo(secondary.position);
11206
- const anchorDelta = new THREE51__namespace.Vector3().subVectors(
11529
+ const anchorDelta = new THREE56__namespace.Vector3().subVectors(
11207
11530
  basis.secondaryLocal,
11208
11531
  basis.primaryLocal
11209
11532
  );
@@ -11227,8 +11550,8 @@ function solveTwoHandTransform(primary, secondary, basis, options) {
11227
11550
  } else {
11228
11551
  const axisLocal = anchorDelta.clone().normalize();
11229
11552
  const currentAxis = axisLocal.clone().applyQuaternion(base).normalize();
11230
- const desiredAxis = new THREE51__namespace.Vector3().subVectors(secondary.position, primary.position).normalize();
11231
- const correction = new THREE51__namespace.Quaternion().setFromUnitVectors(
11553
+ const desiredAxis = new THREE56__namespace.Vector3().subVectors(secondary.position, primary.position).normalize();
11554
+ const correction = new THREE56__namespace.Quaternion().setFromUnitVectors(
11232
11555
  currentAxis,
11233
11556
  desiredAxis
11234
11557
  );
@@ -11243,11 +11566,11 @@ __name(solveTwoHandTransform, "solveTwoHandTransform");
11243
11566
  // core/systems/XRInteractionSystem.ts
11244
11567
  var RAYLESS_HOLD_DISTANCE = 0.5;
11245
11568
  function quatFromEulerVec(r) {
11246
- return new THREE51__namespace.Quaternion().setFromEuler(new THREE51__namespace.Euler(r.x, r.y, r.z));
11569
+ return new THREE56__namespace.Quaternion().setFromEuler(new THREE56__namespace.Euler(r.x, r.y, r.z));
11247
11570
  }
11248
11571
  __name(quatFromEulerVec, "quatFromEulerVec");
11249
11572
  function writeEuler(target, q) {
11250
- const euler = new THREE51__namespace.Euler().setFromQuaternion(q);
11573
+ const euler = new THREE56__namespace.Euler().setFromQuaternion(q);
11251
11574
  target.x = euler.x;
11252
11575
  target.y = euler.y;
11253
11576
  target.z = euler.z;
@@ -11255,10 +11578,11 @@ function writeEuler(target, q) {
11255
11578
  __name(writeEuler, "writeEuler");
11256
11579
  var _XRInteractionSystem = class _XRInteractionSystem {
11257
11580
  constructor(inputSystem) {
11258
- this.raycaster = new THREE51__namespace.Raycaster();
11581
+ this.raycaster = new THREE56__namespace.Raycaster();
11259
11582
  this.grabs = /* @__PURE__ */ new Map();
11260
11583
  this.hoveredEntityId = null;
11261
11584
  /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
11585
+ this.busyContributors = [];
11262
11586
  this.hovers = /* @__PURE__ */ new Map();
11263
11587
  /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
11264
11588
  this.elapsed = 0;
@@ -11267,6 +11591,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11267
11591
  update(world, deltaTime) {
11268
11592
  if (!world.isXrPresenting()) {
11269
11593
  this.releaseAll(world);
11594
+ this.hovers.clear();
11595
+ this.hoveredEntityId = null;
11270
11596
  return;
11271
11597
  }
11272
11598
  this.elapsed += Math.max(0, deltaTime);
@@ -11363,7 +11689,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11363
11689
  const hand = this.handPosition(input);
11364
11690
  const handQuaternion = this.handQuaternion(input);
11365
11691
  const objectQuaternion = quatFromEulerVec(transform.rotation);
11366
- record.offset = new THREE51__namespace.Vector3(
11692
+ record.offset = new THREE56__namespace.Vector3(
11367
11693
  transform.position.x - hand.x,
11368
11694
  transform.position.y - hand.y,
11369
11695
  transform.position.z - hand.z
@@ -11395,7 +11721,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11395
11721
  primary.record.twoHand = {
11396
11722
  primaryLocal,
11397
11723
  secondaryLocal,
11398
- initialScale: new THREE51__namespace.Vector3(
11724
+ initialScale: new THREE56__namespace.Vector3(
11399
11725
  transform.scale.x,
11400
11726
  transform.scale.y,
11401
11727
  transform.scale.z
@@ -11411,7 +11737,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11411
11737
  /** World point → the object's UNSCALED local space. */
11412
11738
  objectLocalPoint(transform, worldPoint) {
11413
11739
  const local = worldPoint.clone().sub(
11414
- new THREE51__namespace.Vector3(
11740
+ new THREE56__namespace.Vector3(
11415
11741
  transform.position.x,
11416
11742
  transform.position.y,
11417
11743
  transform.position.z
@@ -11469,7 +11795,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11469
11795
  );
11470
11796
  if (!interactable2) continue;
11471
11797
  if (interactable2.grabReach === "distance") continue;
11472
- const position = new THREE51__namespace.Vector3();
11798
+ const position = new THREE56__namespace.Vector3();
11473
11799
  entry.object.getWorldPosition(position);
11474
11800
  const distance = position.distanceTo(hand);
11475
11801
  if (distance <= interactable2.grabRadius && distance < nearestDistance) {
@@ -11541,13 +11867,13 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11541
11867
  this.holdersOf(entityId).map((holder) => holder.record.attachPointId).filter((id) => id !== null)
11542
11868
  );
11543
11869
  const hand = this.handPosition(input);
11544
- const objectPosition = new THREE51__namespace.Vector3(
11870
+ const objectPosition = new THREE56__namespace.Vector3(
11545
11871
  transform.position.x,
11546
11872
  transform.position.y,
11547
11873
  transform.position.z
11548
11874
  );
11549
11875
  const objectQuaternion = quatFromEulerVec(transform.rotation);
11550
- const scale = new THREE51__namespace.Vector3(
11876
+ const scale = new THREE56__namespace.Vector3(
11551
11877
  transform.scale.x,
11552
11878
  transform.scale.y,
11553
11879
  transform.scale.z
@@ -11558,7 +11884,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11558
11884
  if (taken.has(point.id)) continue;
11559
11885
  if (point.hand !== "any" && point.hand !== input.handedness) continue;
11560
11886
  if (point.role !== "any" && point.role !== role) continue;
11561
- const world3 = new THREE51__namespace.Vector3(
11887
+ const world3 = new THREE56__namespace.Vector3(
11562
11888
  point.position.x,
11563
11889
  point.position.y,
11564
11890
  point.position.z
@@ -11581,21 +11907,21 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11581
11907
  let offset;
11582
11908
  let remoteDistance = 0;
11583
11909
  if (remote) {
11584
- const position = new THREE51__namespace.Vector3(
11910
+ const position = new THREE56__namespace.Vector3(
11585
11911
  transform.position.x,
11586
11912
  transform.position.y,
11587
11913
  transform.position.z
11588
11914
  );
11589
11915
  remoteDistance = position.distanceTo(input.rayOrigin);
11590
- offset = new THREE51__namespace.Vector3();
11916
+ offset = new THREE56__namespace.Vector3();
11591
11917
  } else if (viaDistance) {
11592
- offset = new THREE51__namespace.Vector3(
11918
+ offset = new THREE56__namespace.Vector3(
11593
11919
  interactable.grabOffsetPosition.x,
11594
11920
  interactable.grabOffsetPosition.y,
11595
11921
  interactable.grabOffsetPosition.z
11596
11922
  );
11597
11923
  } else {
11598
- offset = new THREE51__namespace.Vector3(
11924
+ offset = new THREE56__namespace.Vector3(
11599
11925
  transform.position.x - hand.x,
11600
11926
  transform.position.y - hand.y,
11601
11927
  transform.position.z - hand.z
@@ -11622,7 +11948,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11622
11948
  entityId,
11623
11949
  role,
11624
11950
  attachPointId: attachPoint?.id ?? null,
11625
- attachLocal: attachPoint ? new THREE51__namespace.Vector3(
11951
+ attachLocal: attachPoint ? new THREE56__namespace.Vector3(
11626
11952
  attachPoint.position.x,
11627
11953
  attachPoint.position.y,
11628
11954
  attachPoint.position.z
@@ -11631,8 +11957,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11631
11957
  handRotationOffset,
11632
11958
  twoHand: null,
11633
11959
  offset,
11634
- rotationOffset: new THREE51__namespace.Quaternion().setFromEuler(
11635
- new THREE51__namespace.Euler(
11960
+ rotationOffset: new THREE56__namespace.Quaternion().setFromEuler(
11961
+ new THREE56__namespace.Euler(
11636
11962
  interactable.grabOffsetRotation.x,
11637
11963
  interactable.grabOffsetRotation.y,
11638
11964
  interactable.grabOffsetRotation.z
@@ -11643,7 +11969,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11643
11969
  restoreBodyType,
11644
11970
  tracker,
11645
11971
  lastHandQuaternion: handQuaternion.clone(),
11646
- angularVelocity: new THREE51__namespace.Vector3()
11972
+ angularVelocity: new THREE56__namespace.Vector3()
11647
11973
  });
11648
11974
  if (role === "primary") {
11649
11975
  this.dispatch(world, entityId, (b) => b.onGrab(input));
@@ -11716,14 +12042,14 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11716
12042
  const handQuaternion = this.handQuaternion(input);
11717
12043
  const objectQuaternion = held.attachQuaternion ? handQuaternion.clone().multiply(held.rotationOffset).multiply(held.attachQuaternion.clone().invert()) : handQuaternion.clone().multiply(held.handRotationOffset);
11718
12044
  if (held.remote) {
11719
- const target = new THREE51__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
12045
+ const target = new THREE56__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
11720
12046
  transform.position.x = target.x;
11721
12047
  transform.position.y = target.y;
11722
12048
  transform.position.z = target.z;
11723
12049
  writeEuler(transform.rotation, objectQuaternion);
11724
12050
  } else if (held.attachLocal) {
11725
12051
  const anchor = held.attachLocal.clone().multiply(
11726
- new THREE51__namespace.Vector3(
12052
+ new THREE56__namespace.Vector3(
11727
12053
  transform.scale.x,
11728
12054
  transform.scale.y,
11729
12055
  transform.scale.z
@@ -11741,7 +12067,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11741
12067
  writeEuler(transform.rotation, objectQuaternion);
11742
12068
  }
11743
12069
  held.tracker.record(
11744
- held.remote ? new THREE51__namespace.Vector3(
12070
+ held.remote ? new THREE56__namespace.Vector3(
11745
12071
  transform.position.x,
11746
12072
  transform.position.y,
11747
12073
  transform.position.z
@@ -11815,7 +12141,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11815
12141
  */
11816
12142
  handPosition(input) {
11817
12143
  if (input.gripPosition) return input.gripPosition;
11818
- return new THREE51__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
12144
+ return new THREE56__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
11819
12145
  }
11820
12146
  /**
11821
12147
  * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
@@ -11823,8 +12149,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11823
12149
  */
11824
12150
  handQuaternion(input) {
11825
12151
  if (input.gripQuaternion) return input.gripQuaternion.clone();
11826
- return new THREE51__namespace.Quaternion().setFromUnitVectors(
11827
- new THREE51__namespace.Vector3(0, 0, -1),
12152
+ return new THREE56__namespace.Quaternion().setFromUnitVectors(
12153
+ new THREE56__namespace.Vector3(0, 0, -1),
11828
12154
  input.rayDirection.clone().normalize()
11829
12155
  );
11830
12156
  }
@@ -11889,13 +12215,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
11889
12215
  */
11890
12216
  isSourceBusy(world, input) {
11891
12217
  if (this.grabs.has(input)) return true;
12218
+ for (const contributor of this.busyContributors) {
12219
+ try {
12220
+ if (contributor(world, input)) return true;
12221
+ } catch {
12222
+ }
12223
+ }
11892
12224
  const hover = this.hovers.get(input);
11893
12225
  if (!hover) return false;
11894
12226
  const interactable = world.getComponent(
11895
12227
  hover.entityId,
11896
12228
  XRInteractableComponent
11897
12229
  );
11898
- return Boolean(interactable?.enabled && interactable.grabbable);
12230
+ return Boolean(
12231
+ interactable?.enabled && (interactable.grabbable || interactable.pointable)
12232
+ );
12233
+ }
12234
+ /** OR-ed into isSourceBusy; see the comment there. */
12235
+ registerBusyContributor(contributor) {
12236
+ this.busyContributors.push(contributor);
12237
+ }
12238
+ /** Hover for a specific input source (handedness-free — Vision Pro's
12239
+ * transient pointers all report handedness "none"). */
12240
+ getHoverForInput(input) {
12241
+ return this.hovers.get(input) ?? null;
11899
12242
  }
11900
12243
  /** True when THIS hand is holding something. */
11901
12244
  isGrabbingWith(handedness) {
@@ -11944,7 +12287,7 @@ function sampleTeleportArc(origin, direction, out = [], options) {
11944
12287
  for (let i = 0; i < steps; i += 1) {
11945
12288
  const t = i / (steps - 1) * maxTime;
11946
12289
  out.push(
11947
- new THREE51__namespace.Vector3(
12290
+ new THREE56__namespace.Vector3(
11948
12291
  origin.x + velocity.x * t,
11949
12292
  origin.y + velocity.y * t - 0.5 * gravity * t * t,
11950
12293
  origin.z + velocity.z * t
@@ -11961,7 +12304,7 @@ function arcGroundCrossing(points, groundY) {
11961
12304
  if (previous.y >= groundY && current.y <= groundY) {
11962
12305
  const span = previous.y - current.y;
11963
12306
  const alpha = span > 1e-6 ? (previous.y - groundY) / span : 0;
11964
- return new THREE51__namespace.Vector3().lerpVectors(previous, current, alpha);
12307
+ return new THREE56__namespace.Vector3().lerpVectors(previous, current, alpha);
11965
12308
  }
11966
12309
  }
11967
12310
  return null;
@@ -12005,11 +12348,11 @@ __name(_TeleportAimGate, "TeleportAimGate");
12005
12348
  var TeleportAimGate = _TeleportAimGate;
12006
12349
 
12007
12350
  // core/systems/XRLocomotionSystem.ts
12008
- var UP = new THREE51__namespace.Vector3(0, 1, 0);
12351
+ var UP = new THREE56__namespace.Vector3(0, 1, 0);
12009
12352
  var AXIS_DEADZONE = 0.5;
12010
12353
  var _XRLocomotionSystem = class _XRLocomotionSystem {
12011
12354
  constructor(inputSystem) {
12012
- this.raycaster = new THREE51__namespace.Raycaster();
12355
+ this.raycaster = new THREE56__namespace.Raycaster();
12013
12356
  /** Per-source latch so one stick push yields exactly one snap turn. */
12014
12357
  this.turnLatched = /* @__PURE__ */ new WeakSet();
12015
12358
  /** Reused arc buffer — aiming runs every frame. */
@@ -12059,6 +12402,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12059
12402
  const rig = world.getComponent(rigEntityId, XRRigComponent);
12060
12403
  const rigTransform = world.getComponent(rigEntityId, TransformComponent);
12061
12404
  if (!rig || !rigTransform) return;
12405
+ if (world.isXrPassthrough()) return;
12062
12406
  const inputs = this.inputSystem.getInputs();
12063
12407
  if (inputs.length === 0) return;
12064
12408
  if (rig.snapTurn) {
@@ -12134,7 +12478,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12134
12478
  }
12135
12479
  if (this.turnLatched.has(source)) continue;
12136
12480
  this.turnLatched.add(source);
12137
- transform.rotation.y -= Math.sign(x) * THREE51__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
12481
+ transform.rotation.y -= Math.sign(x) * THREE56__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
12138
12482
  }
12139
12483
  }
12140
12484
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -12144,12 +12488,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12144
12488
  const y = input.axes[1] ?? 0;
12145
12489
  if (Math.abs(x) < 0.15 && Math.abs(y) < 0.15) continue;
12146
12490
  const camera = world.getCamera();
12147
- const forward = new THREE51__namespace.Vector3();
12491
+ const forward = new THREE56__namespace.Vector3();
12148
12492
  camera.getWorldDirection(forward);
12149
12493
  forward.y = 0;
12150
12494
  if (forward.lengthSq() < 1e-6) continue;
12151
12495
  forward.normalize();
12152
- const right = new THREE51__namespace.Vector3().crossVectors(forward, UP).normalize();
12496
+ const right = new THREE56__namespace.Vector3().crossVectors(forward, UP).normalize();
12153
12497
  const step = rig.moveSpeed * Math.max(0, deltaTime);
12154
12498
  transform.position.x += (forward.x * -y + right.x * x) * step;
12155
12499
  transform.position.z += (forward.z * -y + right.z * x) * step;
@@ -12230,7 +12574,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
12230
12574
  for (let i = 1; i < points.length; i += 1) {
12231
12575
  const from = points[i - 1];
12232
12576
  const to = points[i];
12233
- const delta = new THREE51__namespace.Vector3().subVectors(to, from);
12577
+ const delta = new THREE56__namespace.Vector3().subVectors(to, from);
12234
12578
  const distance = delta.length();
12235
12579
  if (distance < 1e-6) continue;
12236
12580
  this.raycaster.set(from, delta.divideScalar(distance));
@@ -12321,6 +12665,32 @@ var _XRSessionSystem = class _XRSessionSystem {
12321
12665
  }
12322
12666
  if (presenting) {
12323
12667
  this.dispatchInputEdges(world);
12668
+ this.muteDepthOcclusionColor(world);
12669
+ }
12670
+ }
12671
+ /**
12672
+ * Stop three's depth-occlusion pass from writing COLOR.
12673
+ *
12674
+ * With depth sensing granted, three draws a full-screen, head-locked mesh
12675
+ * every frame whose fragment shader assigns ONLY `gl_FragDepth` — that is
12676
+ * the whole occlusion mechanism — but its material never sets
12677
+ * `colorWrite: false`, so the colour output is UNDEFINED per GLSL. Desktop
12678
+ * drivers happen to zero it, which is why this ships unnoticed; Quest's
12679
+ * mobile GPU frequently leaves the last-computed temporary in the register —
12680
+ * the sampled DEPTH value — so the pass paints the depth camera's view over
12681
+ * the screen as faint head-locked contour bands. On device that reads as "a
12682
+ * large wireframe dome that follows my head".
12683
+ *
12684
+ * The mesh is created lazily inside three's render, so this is re-checked
12685
+ * every frame rather than hooked once; the write is idempotent and the
12686
+ * check is two property reads. Depth-only is the mesh's entire purpose —
12687
+ * occlusion is unaffected.
12688
+ */
12689
+ muteDepthOcclusionColor(world) {
12690
+ const xr = world.getRenderer().xr;
12691
+ const material = xr.getDepthSensingMesh?.()?.material;
12692
+ if (material && material.colorWrite !== false) {
12693
+ material.colorWrite = false;
12324
12694
  }
12325
12695
  }
12326
12696
  handleSessionStart(world) {
@@ -12473,23 +12843,27 @@ function planeColliderFromPolygon(polygon) {
12473
12843
  }
12474
12844
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
12475
12845
  function writeXrMeshGeometry(vertices, indices, existing) {
12476
- const geometry = existing ?? new THREE51__namespace.BufferGeometry();
12477
- geometry.setAttribute("position", new THREE51__namespace.BufferAttribute(vertices, 3));
12478
- geometry.setIndex(new THREE51__namespace.BufferAttribute(indices, 1));
12846
+ const geometry = existing ?? new THREE56__namespace.BufferGeometry();
12847
+ geometry.setAttribute(
12848
+ "position",
12849
+ new THREE56__namespace.BufferAttribute(vertices.slice(), 3)
12850
+ );
12851
+ geometry.setIndex(new THREE56__namespace.BufferAttribute(indices.slice(), 1));
12852
+ geometry.deleteAttribute("normal");
12479
12853
  geometry.computeBoundingSphere();
12480
12854
  geometry.computeVertexNormals();
12481
12855
  return geometry;
12482
12856
  }
12483
12857
  __name(writeXrMeshGeometry, "writeXrMeshGeometry");
12484
12858
  function decomposeXrPose(matrixElements, target) {
12485
- const matrix = new THREE51__namespace.Matrix4().fromArray(
12859
+ const matrix = new THREE56__namespace.Matrix4().fromArray(
12486
12860
  matrixElements
12487
12861
  );
12488
- const position = new THREE51__namespace.Vector3();
12489
- const quaternion = new THREE51__namespace.Quaternion();
12490
- const scale = new THREE51__namespace.Vector3();
12862
+ const position = new THREE56__namespace.Vector3();
12863
+ const quaternion = new THREE56__namespace.Quaternion();
12864
+ const scale = new THREE56__namespace.Vector3();
12491
12865
  matrix.decompose(position, quaternion, scale);
12492
- const euler = new THREE51__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
12866
+ const euler = new THREE56__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
12493
12867
  target.position.x = position.x;
12494
12868
  target.position.y = position.y;
12495
12869
  target.position.z = position.z;
@@ -12499,20 +12873,151 @@ function decomposeXrPose(matrixElements, target) {
12499
12873
  }
12500
12874
  __name(decomposeXrPose, "decomposeXrPose");
12501
12875
 
12876
+ // core/xr/poseStability.ts
12877
+ var GLUED_REF_SPEED = 0.12;
12878
+ var STILL_SPEED = 0.05;
12879
+ var GLUED_FRAMES_TO_FREEZE = 3;
12880
+ var STATIC_FRAMES_TO_RECOVER = 10;
12881
+ var SNAP_SPEED = 3;
12882
+ var dist = /* @__PURE__ */ __name((a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z), "dist");
12883
+ var _SurfacePoseGate = class _SurfacePoseGate {
12884
+ constructor() {
12885
+ this.prevRef = null;
12886
+ this.prevView = null;
12887
+ this.gluedStreak = 0;
12888
+ this.staticStreak = 0;
12889
+ this.suspect = false;
12890
+ this.deltas = 0;
12891
+ }
12892
+ /** True while poses are being rejected as head-glued. */
12893
+ get frozen() {
12894
+ return this.suspect;
12895
+ }
12896
+ /**
12897
+ * True once enough motion has been observed to classify the pose at all.
12898
+ * Surface CREATION waits for this: an entity spawned from a glued pose
12899
+ * materializes wherever the head happens to be — a rotated ghost copy of
12900
+ * the room — and freezing it there is barely better than letting it follow.
12901
+ */
12902
+ get warmedUp() {
12903
+ return this.deltas >= GLUED_FRAMES_TO_FREEZE;
12904
+ }
12905
+ /**
12906
+ * Feed this frame's surface position in reference space and in view
12907
+ * (viewer-relative) space. Returns true when the pose should be APPLIED,
12908
+ * false when it should be frozen out.
12909
+ *
12910
+ * `viewPos` may be null when the viewer pose is unavailable (older
12911
+ * runtimes) — the gate then passes everything through, degrading to the
12912
+ * flag-only behavior.
12913
+ */
12914
+ update(refPos, viewPos, deltaTime) {
12915
+ if (!viewPos || deltaTime <= 0) {
12916
+ this.prevRef = { ...refPos };
12917
+ this.prevView = viewPos ? { ...viewPos } : null;
12918
+ return !this.suspect;
12919
+ }
12920
+ const prevRef = this.prevRef;
12921
+ const prevView = this.prevView;
12922
+ this.prevRef = { ...refPos };
12923
+ this.prevView = { ...viewPos };
12924
+ if (!prevRef || !prevView) return !this.suspect;
12925
+ this.deltas += 1;
12926
+ const refSpeed = dist(refPos, prevRef) / deltaTime;
12927
+ const viewSpeed = dist(viewPos, prevView) / deltaTime;
12928
+ const glued = refSpeed > GLUED_REF_SPEED && viewSpeed < STILL_SPEED;
12929
+ const worldStatic = refSpeed < STILL_SPEED && viewSpeed > GLUED_REF_SPEED;
12930
+ if (this.suspect) {
12931
+ if (refSpeed > SNAP_SPEED && viewSpeed > SNAP_SPEED) {
12932
+ this.suspect = false;
12933
+ this.gluedStreak = 0;
12934
+ this.staticStreak = 0;
12935
+ return true;
12936
+ }
12937
+ this.staticStreak = worldStatic ? this.staticStreak + 1 : 0;
12938
+ if (this.staticStreak >= STATIC_FRAMES_TO_RECOVER) {
12939
+ this.suspect = false;
12940
+ this.gluedStreak = 0;
12941
+ this.staticStreak = 0;
12942
+ return true;
12943
+ }
12944
+ return false;
12945
+ }
12946
+ this.gluedStreak = glued ? this.gluedStreak + 1 : 0;
12947
+ if (this.gluedStreak >= GLUED_FRAMES_TO_FREEZE) {
12948
+ this.suspect = true;
12949
+ this.staticStreak = 0;
12950
+ return false;
12951
+ }
12952
+ return true;
12953
+ }
12954
+ };
12955
+ __name(_SurfacePoseGate, "SurfacePoseGate");
12956
+ var SurfacePoseGate = _SurfacePoseGate;
12957
+
12502
12958
  // core/systems/XREnvironmentSystem.ts
12503
12959
  var DEBUG_COLOR = 5088255;
12960
+ function debugColorFor(kind, semanticLabel) {
12961
+ switch (semanticLabel) {
12962
+ case "floor":
12963
+ return 5100666;
12964
+ // green — the surface everything should rest on
12965
+ case "ceiling":
12966
+ return 11693055;
12967
+ // purple
12968
+ case "wall":
12969
+ case "wall face":
12970
+ return DEBUG_COLOR;
12971
+ // blue, the family's base colour
12972
+ case "global mesh":
12973
+ return 9079442;
12974
+ // dim gray — raw scan, not an object
12975
+ default:
12976
+ return semanticLabel ? 16747594 : DEBUG_COLOR;
12977
+ }
12978
+ }
12979
+ __name(debugColorFor, "debugColorFor");
12980
+ var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
12981
+ var DEBUG_RENDER_ORDER = 900;
12504
12982
  var ROOM_CAPTURE_DELAY_SECONDS = 3;
12983
+ var SYNTHETIC_FLOOR_EXTENT = 50;
12984
+ var SYNTHETIC_FLOOR_PLANE = {
12985
+ planeSpace: {},
12986
+ polygon: [
12987
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
12988
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: -SYNTHETIC_FLOOR_EXTENT / 2 },
12989
+ { x: SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 },
12990
+ { x: -SYNTHETIC_FLOOR_EXTENT / 2, y: 0, z: SYNTHETIC_FLOOR_EXTENT / 2 }
12991
+ ],
12992
+ orientation: "horizontal",
12993
+ semanticLabel: "floor",
12994
+ lastChangedTime: 0
12995
+ };
12505
12996
  var _XREnvironmentSystem = class _XREnvironmentSystem {
12506
12997
  constructor() {
12507
12998
  /** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
12508
12999
  this.planes = /* @__PURE__ */ new Map();
12509
13000
  this.meshes = /* @__PURE__ */ new Map();
13001
+ /**
13002
+ * Surfaces observed but not yet SPAWNED — their gate is still proving the
13003
+ * pose is world-anchored. Creating from an unflagged head-glued pose (seen
13004
+ * on-device after a suspend/resume regenerated the scan identities) plants
13005
+ * a rotated ghost copy of the room wherever the head happens to be, which
13006
+ * the apply-gate then freezes there — wrong forever. So admission itself
13007
+ * is gated: a few frames of world-anchored behavior first.
13008
+ */
13009
+ this.pendingPlanes = /* @__PURE__ */ new Map();
13010
+ this.pendingMeshes = /* @__PURE__ */ new Map();
12510
13011
  this.presenting = false;
12511
13012
  this.debugVisible = false;
12512
13013
  /** Seconds presenting, used to decide the room has genuinely nothing in it. */
12513
13014
  this.elapsed = 0;
12514
13015
  /** `initiateRoomCapture` may be called only ONCE per session. */
12515
13016
  this.roomCaptureRequested = false;
13017
+ /** This frame's inverse viewer matrix, or null when unavailable. */
13018
+ this.viewerInverse = null;
13019
+ this.tmpViewerMatrix = new THREE56__namespace.Matrix4();
13020
+ this.tmpViewPos = new THREE56__namespace.Vector3();
12516
13021
  }
12517
13022
  update(world, deltaTime = 0) {
12518
13023
  const presenting = world.isXrPresenting();
@@ -12542,16 +13047,23 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12542
13047
  const frame = xr.getFrame?.();
12543
13048
  const referenceSpace = xr.getReferenceSpace();
12544
13049
  if (!frame || !referenceSpace) return;
13050
+ if (frame.detectedPlanes === void 0 && frame.detectedMeshes === void 0) {
13051
+ this.ensureSyntheticFloor(world);
13052
+ return;
13053
+ }
13054
+ this.removeSyntheticFloor(world);
12545
13055
  const meshesAvailable = Boolean(frame.detectedMeshes);
12546
13056
  const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
12547
13057
  const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
13058
+ const viewer = frame.getViewerPose?.(referenceSpace);
13059
+ this.viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
12548
13060
  if (usePlanes) {
12549
- this.syncPlanes(world, frame, referenceSpace);
13061
+ this.syncPlanes(world, frame, referenceSpace, deltaTime);
12550
13062
  } else if (this.planes.size > 0) {
12551
13063
  this.clearMap(world, this.planes);
12552
13064
  }
12553
13065
  if (useMesh) {
12554
- this.syncMeshes(world, frame, referenceSpace);
13066
+ this.syncMeshes(world, frame, referenceSpace, deltaTime);
12555
13067
  } else if (this.meshes.size > 0) {
12556
13068
  this.clearMap(world, this.meshes);
12557
13069
  }
@@ -12588,12 +13100,57 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12588
13100
  getDetectedCount() {
12589
13101
  return { planes: this.planes.size, meshes: this.meshes.size };
12590
13102
  }
13103
+ /**
13104
+ * A census of every surface currently spawned: what it is, where it sits and
13105
+ * roughly how big it is. Built for the template's post-session readout, so a
13106
+ * mystery shape on the headset can be looked up by its label instead of
13107
+ * guessed at — a small-radius entry at head height IS the mystery shape, and
13108
+ * a clean list means whatever was seen did not come from this system.
13109
+ */
13110
+ getDebugSnapshot(world) {
13111
+ const snapshot = [];
13112
+ for (const [plane, entry] of this.planes) {
13113
+ const transform = world.getComponent(entry.entityId, TransformComponent);
13114
+ const collider = planeColliderFromPolygon(plane.polygon);
13115
+ snapshot.push({
13116
+ kind: "plane",
13117
+ label: plane.semanticLabel ?? null,
13118
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
13119
+ radius: collider ? Math.hypot(collider.size.x, collider.size.z) / 2 : 0,
13120
+ emulated: entry.emulated
13121
+ });
13122
+ }
13123
+ for (const [mesh, entry] of this.meshes) {
13124
+ const transform = world.getComponent(entry.entityId, TransformComponent);
13125
+ snapshot.push({
13126
+ kind: "mesh",
13127
+ label: mesh.semanticLabel ?? null,
13128
+ position: transform ? { ...transform.position } : { x: 0, y: 0, z: 0 },
13129
+ radius: entry.geometry?.boundingSphere?.radius ?? 0,
13130
+ emulated: entry.emulated
13131
+ });
13132
+ }
13133
+ return snapshot;
13134
+ }
13135
+ /** Create the no-scan floor once; it lives in the ordinary plane map. */
13136
+ ensureSyntheticFloor(world) {
13137
+ if (this.planes.has(SYNTHETIC_FLOOR_PLANE)) return;
13138
+ const created = this.createPlaneEntity(world, SYNTHETIC_FLOOR_PLANE);
13139
+ if (created) this.planes.set(SYNTHETIC_FLOOR_PLANE, created);
13140
+ }
13141
+ /** Real scene data exists (or arrived) — the stand-in must go. */
13142
+ removeSyntheticFloor(world) {
13143
+ const entry = this.planes.get(SYNTHETIC_FLOOR_PLANE);
13144
+ if (!entry) return;
13145
+ this.destroyEntry(world, entry);
13146
+ this.planes.delete(SYNTHETIC_FLOOR_PLANE);
13147
+ }
12591
13148
  findRig(world) {
12592
13149
  const ids = world.query(XRRigComponent);
12593
13150
  if (ids.length === 0) return null;
12594
13151
  return world.getComponent(ids[0], XRRigComponent) ?? null;
12595
13152
  }
12596
- syncPlanes(world, frame, referenceSpace) {
13153
+ syncPlanes(world, frame, referenceSpace, deltaTime) {
12597
13154
  const detected = frame.detectedPlanes;
12598
13155
  if (!detected) return;
12599
13156
  for (const [plane, entry] of this.planes) {
@@ -12602,14 +13159,22 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12602
13159
  this.planes.delete(plane);
12603
13160
  }
12604
13161
  }
13162
+ for (const plane of this.pendingPlanes.keys()) {
13163
+ if (!detected.has(plane)) this.pendingPlanes.delete(plane);
13164
+ }
12605
13165
  for (const plane of detected) {
12606
13166
  const pose = frame.getPose(plane.planeSpace, referenceSpace);
12607
13167
  if (!pose) continue;
13168
+ const emulated = pose.emulatedPosition === true;
12608
13169
  let entry = this.planes.get(plane);
12609
13170
  const changed = plane.lastChangedTime ?? 0;
12610
13171
  if (!entry) {
13172
+ if (emulated) continue;
13173
+ const gate = this.admit(this.pendingPlanes, plane, pose, deltaTime);
13174
+ if (!gate) continue;
12611
13175
  const created = this.createPlaneEntity(world, plane);
12612
13176
  if (!created) continue;
13177
+ created.gate = gate;
12613
13178
  entry = created;
12614
13179
  this.planes.set(plane, entry);
12615
13180
  } else if (changed !== entry.lastChangedTime) {
@@ -12620,14 +13185,21 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12620
13185
  if (collider) {
12621
13186
  entry.geometry?.dispose();
12622
13187
  entry.geometry = null;
12623
- this.attachPlaneDebug(world, entry, collider);
13188
+ this.attachPlaneDebug(
13189
+ world,
13190
+ entry,
13191
+ collider,
13192
+ plane.semanticLabel ?? null
13193
+ );
12624
13194
  }
12625
13195
  }
12626
13196
  }
12627
- this.applyPose(world, entry.entityId, pose);
13197
+ const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
13198
+ entry.emulated = emulated || entry.gate.frozen;
13199
+ if (allowed) this.applyPose(world, entry.entityId, pose);
12628
13200
  }
12629
13201
  }
12630
- syncMeshes(world, frame, referenceSpace) {
13202
+ syncMeshes(world, frame, referenceSpace, deltaTime) {
12631
13203
  const detected = frame.detectedMeshes;
12632
13204
  if (!detected) return;
12633
13205
  for (const [mesh, entry] of this.meshes) {
@@ -12636,23 +13208,68 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12636
13208
  this.meshes.delete(mesh);
12637
13209
  }
12638
13210
  }
13211
+ for (const mesh of this.pendingMeshes.keys()) {
13212
+ if (!detected.has(mesh)) this.pendingMeshes.delete(mesh);
13213
+ }
12639
13214
  for (const mesh of detected) {
12640
13215
  const pose = frame.getPose(mesh.meshSpace, referenceSpace);
12641
13216
  if (!pose) continue;
13217
+ const emulated = pose.emulatedPosition === true;
12642
13218
  let entry = this.meshes.get(mesh);
12643
13219
  const changed = mesh.lastChangedTime ?? 0;
12644
13220
  if (!entry) {
13221
+ if (emulated) continue;
13222
+ const gate = this.admit(this.pendingMeshes, mesh, pose, deltaTime);
13223
+ if (!gate) continue;
12645
13224
  const created = this.createMeshEntity(world, mesh);
12646
13225
  if (!created) continue;
13226
+ created.gate = gate;
12647
13227
  entry = created;
12648
13228
  this.meshes.set(mesh, entry);
12649
13229
  } else if (changed !== entry.lastChangedTime) {
12650
13230
  entry.lastChangedTime = changed;
12651
13231
  writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
12652
13232
  }
12653
- this.applyPose(world, entry.entityId, pose);
13233
+ const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
13234
+ entry.emulated = emulated || entry.gate.frozen;
13235
+ if (allowed) this.applyPose(world, entry.entityId, pose);
12654
13236
  }
12655
13237
  }
13238
+ /**
13239
+ * Admission control for a NOT-yet-spawned surface. With a viewer pose
13240
+ * available, the surface must behave world-anchored for a few frames before
13241
+ * an entity is created for it; without one (older runtimes, tests) it is
13242
+ * admitted immediately — the gate can't classify anyway. Returns the
13243
+ * warmed-up gate (carried into the entry so its motion history survives),
13244
+ * or null while the surface must keep waiting.
13245
+ */
13246
+ admit(pending, key, pose, deltaTime) {
13247
+ if (!this.viewerInverse) return new SurfacePoseGate();
13248
+ let gate = pending.get(key);
13249
+ if (!gate) {
13250
+ gate = new SurfacePoseGate();
13251
+ pending.set(key, gate);
13252
+ }
13253
+ const allowed = this.gateAllows(gate, pose, deltaTime);
13254
+ if (!allowed || !gate.warmedUp) return null;
13255
+ pending.delete(key);
13256
+ return gate;
13257
+ }
13258
+ /**
13259
+ * Run a surface's pose through its head-glue gate: position in reference
13260
+ * space vs position relative to the viewer. Without a viewer pose the gate
13261
+ * passes everything, degrading to the flag-only behavior.
13262
+ */
13263
+ gateAllows(gate, pose, deltaTime) {
13264
+ const m = pose.transform.matrix;
13265
+ const refPos = { x: m[12], y: m[13], z: m[14] };
13266
+ let viewPos = null;
13267
+ if (this.viewerInverse) {
13268
+ const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(this.viewerInverse);
13269
+ viewPos = { x: v.x, y: v.y, z: v.z };
13270
+ }
13271
+ return gate.update(refPos, viewPos, deltaTime);
13272
+ }
12656
13273
  createPlaneEntity(world, plane) {
12657
13274
  const collider = planeColliderFromPolygon(plane.polygon);
12658
13275
  if (!collider) return null;
@@ -12670,9 +13287,13 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12670
13287
  entityId,
12671
13288
  lastChangedTime: plane.lastChangedTime ?? 0,
12672
13289
  geometry: null,
12673
- material: null
13290
+ material: null,
13291
+ emulated: false,
13292
+ gate: new SurfacePoseGate()
12674
13293
  };
12675
- if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
13294
+ if (this.debugVisible) {
13295
+ this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
13296
+ }
12676
13297
  return entry;
12677
13298
  }
12678
13299
  applyPlaneCollider(world, entityId, plane) {
@@ -12697,15 +13318,36 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12697
13318
  createMeshEntity(world, mesh) {
12698
13319
  if (!mesh.vertices?.length || !mesh.indices?.length) return null;
12699
13320
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
12700
- const material = new THREE51__namespace.MeshBasicMaterial({
12701
- color: DEBUG_COLOR,
12702
- wireframe: true
13321
+ const label = mesh.semanticLabel ?? null;
13322
+ const isGlobalMesh = label === "global mesh";
13323
+ const material = new THREE56__namespace.MeshBasicMaterial({
13324
+ color: debugColorFor("mesh", label),
13325
+ wireframe: true,
13326
+ // The global scan is a coarse triangle soup of the whole room; at full
13327
+ // strength it reads as phantom objects rather than as context.
13328
+ transparent: isGlobalMesh,
13329
+ opacity: isGlobalMesh ? GLOBAL_MESH_DEBUG_OPACITY : 1,
13330
+ // Depth-TESTED, deliberately. The overlay was X-ray for one release —
13331
+ // introduced on the theory that depth occlusion was carving it into a
13332
+ // head-locked dome — but that theory was falsified (the dome was the
13333
+ // depth mesh's unset colour output, plus emulated poses; both fixed).
13334
+ // X-ray had its own on-device failure: the global scan is DENSE, and a
13335
+ // dense wireframe at 0.35 opacity fuses into a translucent sheet — with
13336
+ // no depth test the walls' sheets drew through everything, stacked in
13337
+ // front of the user's eyes ("planes covering my face"). With the depth
13338
+ // buffer now holding CORRECT real-world depth, normal testing gives the
13339
+ // right picture: the near wall hides the far one. depthWrite stays off
13340
+ // so the overlay never occludes actual content.
13341
+ depthTest: true,
13342
+ depthWrite: false
12703
13343
  });
12704
- const root = new THREE51__namespace.Group();
12705
- const child = new THREE51__namespace.Mesh(geometry, material);
13344
+ const root = new THREE56__namespace.Group();
13345
+ const child = new THREE56__namespace.Mesh(geometry, material);
12706
13346
  child.visible = this.debugVisible;
13347
+ child.renderOrder = DEBUG_RENDER_ORDER;
12707
13348
  root.add(child);
12708
13349
  const entityId = world.createEntity();
13350
+ root.userData.entityId = entityId;
12709
13351
  world.addComponents(entityId, [
12710
13352
  new TransformComponent(),
12711
13353
  new Object3DRef(root),
@@ -12719,29 +13361,38 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12719
13361
  entityId,
12720
13362
  lastChangedTime: mesh.lastChangedTime ?? 0,
12721
13363
  geometry,
12722
- material
13364
+ material,
13365
+ emulated: false,
13366
+ gate: new SurfacePoseGate()
12723
13367
  };
12724
13368
  }
12725
13369
  /** A thin box so a detected plane can be SEEN, not just collided with. */
12726
- attachPlaneDebug(world, entry, collider) {
12727
- const geometry = new THREE51__namespace.BoxGeometry(
13370
+ attachPlaneDebug(world, entry, collider, semanticLabel) {
13371
+ const box = new THREE56__namespace.BoxGeometry(
12728
13372
  collider.size.x,
12729
13373
  collider.size.y,
12730
13374
  collider.size.z
12731
13375
  );
12732
- const material = new THREE51__namespace.MeshBasicMaterial({
12733
- color: DEBUG_COLOR,
12734
- wireframe: true
13376
+ const geometry = new THREE56__namespace.EdgesGeometry(box);
13377
+ box.dispose();
13378
+ const material = new THREE56__namespace.LineBasicMaterial({
13379
+ color: debugColorFor("plane", semanticLabel),
13380
+ // Same depth policy as the mesh overlay — see createMeshEntity.
13381
+ depthTest: true,
13382
+ depthWrite: false
12735
13383
  });
12736
- const root = new THREE51__namespace.Group();
12737
- const child = new THREE51__namespace.Mesh(geometry, material);
13384
+ const root = new THREE56__namespace.Group();
13385
+ const child = new THREE56__namespace.LineSegments(geometry, material);
13386
+ child.renderOrder = DEBUG_RENDER_ORDER;
12738
13387
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
12739
13388
  child.visible = this.debugVisible;
12740
13389
  root.add(child);
12741
13390
  entry.geometry = geometry;
12742
13391
  entry.material = material;
13392
+ root.userData.entityId = entry.entityId;
12743
13393
  const existing = world.getComponent(entry.entityId, Object3DRef);
12744
13394
  if (existing) {
13395
+ existing.object.removeFromParent();
12745
13396
  existing.object = root;
12746
13397
  } else {
12747
13398
  world.addComponent(entry.entityId, new Object3DRef(root));
@@ -12757,19 +13408,31 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12757
13408
  for (const [plane, entry] of this.planes) {
12758
13409
  if (entry.geometry) continue;
12759
13410
  const collider = planeColliderFromPolygon(plane.polygon);
12760
- if (collider) this.attachPlaneDebug(world, entry, collider);
13411
+ if (collider) {
13412
+ this.attachPlaneDebug(
13413
+ world,
13414
+ entry,
13415
+ collider,
13416
+ plane.semanticLabel ?? null
13417
+ );
13418
+ }
12761
13419
  }
12762
13420
  }
12763
13421
  for (const map of [this.planes, this.meshes]) {
12764
13422
  for (const entry of map.values()) {
12765
13423
  const ref = world.getComponent(entry.entityId, Object3DRef);
12766
13424
  ref?.object.traverse((child) => {
12767
- if (child.isMesh) child.visible = this.debugVisible;
13425
+ const drawable = child;
13426
+ if (drawable.isMesh || drawable.isLine) {
13427
+ child.visible = this.debugVisible;
13428
+ }
12768
13429
  });
12769
13430
  }
12770
13431
  }
12771
13432
  }
12772
13433
  destroyEntry(world, entry) {
13434
+ const ref = world.getComponent(entry.entityId, Object3DRef);
13435
+ ref?.object.removeFromParent();
12773
13436
  entry.geometry?.dispose();
12774
13437
  entry.material?.dispose();
12775
13438
  world.destroyEntity(entry.entityId);
@@ -12781,6 +13444,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
12781
13444
  clearAll(world) {
12782
13445
  this.clearMap(world, this.planes);
12783
13446
  this.clearMap(world, this.meshes);
13447
+ this.pendingPlanes.clear();
13448
+ this.pendingMeshes.clear();
12784
13449
  }
12785
13450
  dispose(world) {
12786
13451
  this.clearAll(world);
@@ -12792,6 +13457,918 @@ function normalizeOrientation(value) {
12792
13457
  return value === "horizontal" || value === "vertical" ? value : null;
12793
13458
  }
12794
13459
  __name(normalizeOrientation, "normalizeOrientation");
13460
+ var MOUSE_CLICK_TRAVEL_PX = 6;
13461
+ var PRESS_TINT_MIN_SECONDS = 0.1;
13462
+ var PRESS_TINT = 0.72;
13463
+ var HOVER_TINT = 0.9;
13464
+ var _UIInteractionSystem = class _UIInteractionSystem {
13465
+ /**
13466
+ * @param interaction The XR interaction system, or null in a world with no
13467
+ * XR (flat 3D experiences) — the mouse path still works via
13468
+ * `options.inputSystem`.
13469
+ */
13470
+ constructor(interaction, options) {
13471
+ this.xrPresses = /* @__PURE__ */ new Map();
13472
+ this.mousePress = null;
13473
+ this.mouseHover = null;
13474
+ /** entityId → seconds of pressed-tint left to display. */
13475
+ this.tintFloors = /* @__PURE__ */ new Map();
13476
+ this.tinted = /* @__PURE__ */ new Set();
13477
+ this.raycaster = new THREE56__namespace.Raycaster();
13478
+ this.interaction = interaction;
13479
+ this.inputSystem = options?.inputSystem ?? null;
13480
+ interaction?.registerBusyContributor(
13481
+ (_world, input) => this.xrPresses.has(input)
13482
+ );
13483
+ }
13484
+ update(world, deltaTime = 0) {
13485
+ if (world.isEditorMode()) return;
13486
+ if (world.isXrPresenting()) {
13487
+ this.updateXr(world);
13488
+ } else {
13489
+ this.updateMouse(world);
13490
+ }
13491
+ this.updateTints(world, deltaTime);
13492
+ }
13493
+ // ── XR path ────────────────────────────────────────────────────────────────
13494
+ updateXr(world) {
13495
+ const interaction = this.interaction;
13496
+ if (!interaction) return;
13497
+ const xrInput = interaction.inputSystem;
13498
+ const inputs = xrInput.getInputs();
13499
+ for (const input of inputs) {
13500
+ const hover = interaction.getHoverForInput(input);
13501
+ const hovered = hover ? this.widgetKind(world, hover.entityId) : null;
13502
+ if (input.selectStart && hover && hovered) {
13503
+ this.xrPresses.set(input, {
13504
+ entityId: hover.entityId,
13505
+ kind: hovered,
13506
+ travelPx: 0
13507
+ });
13508
+ this.beginPressTint(world, hover.entityId);
13509
+ }
13510
+ const press = this.xrPresses.get(input);
13511
+ if (press && press.kind === "slider") {
13512
+ this.dragSlider(world, press.entityId, input);
13513
+ }
13514
+ if (input.selectEnd || !input.connected) {
13515
+ if (press) {
13516
+ this.xrPresses.delete(input);
13517
+ const stillOn = hover?.entityId === press.entityId || // Vision Pro: the transient source is gone from the hover map on
13518
+ // its release frame — confirm against the last latched ray.
13519
+ this.rayHitsWidget(world, press.entityId, input);
13520
+ if (stillOn || press.kind === "slider") {
13521
+ this.fire(world, press, input);
13522
+ }
13523
+ this.endPressTint(press.entityId);
13524
+ }
13525
+ }
13526
+ }
13527
+ for (const [input] of this.xrPresses) {
13528
+ if (!inputs.includes(input)) this.xrPresses.delete(input);
13529
+ }
13530
+ }
13531
+ // ── Mouse path (flat preview) ──────────────────────────────────────────────
13532
+ updateMouse(world) {
13533
+ const input = this.inputSystem;
13534
+ if (!input) return;
13535
+ const hit = this.raycastMouse(world);
13536
+ const hovered = hit && this.widgetKind(world, hit) ? hit : null;
13537
+ if (hovered !== this.mouseHover) {
13538
+ if (this.mouseHover !== null) {
13539
+ this.setTint(world, this.mouseHover, 1);
13540
+ this.dispatchBubbling(
13541
+ world,
13542
+ this.mouseHover,
13543
+ (b, e) => b.onUiHoverEnd(e)
13544
+ );
13545
+ }
13546
+ if (hovered !== null) {
13547
+ this.setTint(world, hovered, HOVER_TINT);
13548
+ this.dispatchBubbling(world, hovered, (b, e) => b.onUiHoverStart(e));
13549
+ }
13550
+ this.mouseHover = hovered;
13551
+ }
13552
+ if (input.getMouseDown("left") && hovered) {
13553
+ const kind = this.widgetKind(world, hovered);
13554
+ if (kind) {
13555
+ this.mousePress = { entityId: hovered, kind, travelPx: 0 };
13556
+ this.beginPressTint(world, hovered);
13557
+ }
13558
+ }
13559
+ if (this.mousePress) {
13560
+ const delta = input.getMouseDelta();
13561
+ this.mousePress.travelPx += Math.hypot(delta.x, delta.y);
13562
+ if (this.mousePress.kind === "slider") {
13563
+ this.dragSliderMouse(world, this.mousePress.entityId);
13564
+ }
13565
+ }
13566
+ if (input.getMouseUp("left") && this.mousePress) {
13567
+ const press = this.mousePress;
13568
+ this.mousePress = null;
13569
+ const stillOn = hovered === press.entityId;
13570
+ const withinTravel = press.travelPx <= MOUSE_CLICK_TRAVEL_PX;
13571
+ if (press.kind === "slider" || stillOn && withinTravel) {
13572
+ this.fire(world, press, null);
13573
+ }
13574
+ this.endPressTint(press.entityId);
13575
+ }
13576
+ }
13577
+ raycastMouse(world) {
13578
+ const input = this.inputSystem;
13579
+ if (!input) return null;
13580
+ const dom = world.getRenderer().domElement;
13581
+ const rect = dom.getBoundingClientRect();
13582
+ let ndcX = 0;
13583
+ let ndcY = 0;
13584
+ if (input.isMousePointerLocked()) {
13585
+ ndcX = 0;
13586
+ ndcY = 0;
13587
+ } else {
13588
+ const pos = input.getMousePosition();
13589
+ ndcX = (pos.x - rect.left) / Math.max(1, rect.width) * 2 - 1;
13590
+ ndcY = -((pos.y - rect.top) / Math.max(1, rect.height) * 2 - 1);
13591
+ if (ndcX < -1 || ndcX > 1 || ndcY < -1 || ndcY > 1) return null;
13592
+ }
13593
+ this.raycaster.setFromCamera(new THREE56__namespace.Vector2(ndcX, ndcY), world.getCamera());
13594
+ const hits = world.raycast(
13595
+ this.raycaster.ray.origin,
13596
+ this.raycaster.ray.direction
13597
+ );
13598
+ for (const hit of hits) {
13599
+ if (typeof hit.entityId !== "number") continue;
13600
+ if (this.widgetKind(world, hit.entityId)) return hit.entityId;
13601
+ return null;
13602
+ }
13603
+ return null;
13604
+ }
13605
+ // ── Shared machinery ───────────────────────────────────────────────────────
13606
+ widgetKind(world, entityId) {
13607
+ if (world.getComponent(entityId, UIButtonComponent)) return "button";
13608
+ if (world.getComponent(entityId, UIToggleComponent)) return "toggle";
13609
+ if (world.getComponent(entityId, UISliderComponent)) return "slider";
13610
+ return null;
13611
+ }
13612
+ fire(world, press, input) {
13613
+ if (press.kind === "button") {
13614
+ this.dispatchBubbling(world, press.entityId, (b, e) => b.onUiPress(e), input);
13615
+ return;
13616
+ }
13617
+ if (press.kind === "toggle") {
13618
+ const toggle = world.getComponent(press.entityId, UIToggleComponent);
13619
+ if (!toggle) return;
13620
+ toggle.value = !toggle.value;
13621
+ this.dispatchBubbling(
13622
+ world,
13623
+ press.entityId,
13624
+ (b, e) => b.onUiToggle(e),
13625
+ input,
13626
+ toggle.value
13627
+ );
13628
+ return;
13629
+ }
13630
+ const slider = world.getComponent(press.entityId, UISliderComponent);
13631
+ if (!slider) return;
13632
+ this.dispatchBubbling(
13633
+ world,
13634
+ press.entityId,
13635
+ (b, e) => b.onUiChange(e),
13636
+ input,
13637
+ slider.value
13638
+ );
13639
+ }
13640
+ dragSlider(world, entityId, input) {
13641
+ const point = this.rayPointOnWidget(world, entityId, input);
13642
+ if (point) this.applySliderPoint(world, entityId, point, input);
13643
+ }
13644
+ dragSliderMouse(world, entityId) {
13645
+ const object = this.widgetObject(world, entityId);
13646
+ if (!object) return;
13647
+ const hits = this.raycaster.intersectObject(object, true);
13648
+ if (hits.length > 0) this.applySliderPoint(world, entityId, hits[0].point, null);
13649
+ }
13650
+ applySliderPoint(world, entityId, worldPoint, input) {
13651
+ const slider = world.getComponent(entityId, UISliderComponent);
13652
+ const object = this.widgetObject(world, entityId);
13653
+ if (!slider || !object) return;
13654
+ const local = object.worldToLocal(worldPoint.clone());
13655
+ const inset = slider.height / 2;
13656
+ const usable = Math.max(1e-3, slider.width - inset * 2);
13657
+ const t = Math.min(1, Math.max(0, local.x / usable + 0.5));
13658
+ const next = slider.min + t * (slider.max - slider.min);
13659
+ if (Math.abs(next - slider.value) < 1e-4) return;
13660
+ slider.value = next;
13661
+ this.dispatchBubbling(
13662
+ world,
13663
+ entityId,
13664
+ (b, e) => b.onUiChange(e),
13665
+ input,
13666
+ next
13667
+ );
13668
+ }
13669
+ widgetObject(world, entityId) {
13670
+ return world.getComponent(entityId, Object3DRef)?.object;
13671
+ }
13672
+ rayHitsWidget(world, entityId, input) {
13673
+ const object = this.widgetObject(world, entityId);
13674
+ if (!object) return false;
13675
+ this.raycaster.set(
13676
+ input.rayOrigin.clone(),
13677
+ input.rayDirection.clone().normalize()
13678
+ );
13679
+ return this.raycaster.intersectObject(object, true).length > 0;
13680
+ }
13681
+ rayPointOnWidget(world, entityId, input) {
13682
+ const object = this.widgetObject(world, entityId);
13683
+ if (!object) return null;
13684
+ this.raycaster.set(
13685
+ input.rayOrigin.clone(),
13686
+ input.rayDirection.clone().normalize()
13687
+ );
13688
+ const hits = this.raycaster.intersectObject(object, true);
13689
+ return hits.length > 0 ? hits[0].point : null;
13690
+ }
13691
+ /**
13692
+ * Dispatch to the widget's Behaviours, then BUBBLE up the parent chain so a
13693
+ * panel script hears its children. Bubbling (not a world broadcast) keeps
13694
+ * two spawned copies of the same menu prefab independent.
13695
+ */
13696
+ dispatchBubbling(world, entityId, visit, input = null, value) {
13697
+ const event = {
13698
+ targetEntityId: entityId,
13699
+ actionId: this.actionIdOf(world, entityId),
13700
+ input,
13701
+ ...value === void 0 ? {} : { value }
13702
+ };
13703
+ const visited = /* @__PURE__ */ new Set();
13704
+ let current = entityId;
13705
+ for (; ; ) {
13706
+ if (visited.has(current)) break;
13707
+ visited.add(current);
13708
+ const script = world.getComponent(current, ScriptComponent);
13709
+ const instances = script?.scriptInstances;
13710
+ if (instances) {
13711
+ for (const instance of instances.values()) {
13712
+ if (!(instance instanceof Behaviour)) continue;
13713
+ try {
13714
+ visit(instance, event);
13715
+ } catch (error) {
13716
+ console.error("[UIInteractionSystem] hook failed:", error);
13717
+ }
13718
+ }
13719
+ }
13720
+ const parentId = world.getComponent(current, ParentId)?.value;
13721
+ if (typeof parentId !== "number") break;
13722
+ current = parentId;
13723
+ }
13724
+ }
13725
+ actionIdOf(world, entityId) {
13726
+ const button = world.getComponent(entityId, UIButtonComponent);
13727
+ if (button?.actionId) return button.actionId;
13728
+ const toggle = world.getComponent(entityId, UIToggleComponent);
13729
+ if (toggle?.actionId) return toggle.actionId;
13730
+ const slider = world.getComponent(entityId, UISliderComponent);
13731
+ if (slider?.actionId) return slider.actionId;
13732
+ const name = world.getComponent(entityId, Name)?.value;
13733
+ return typeof name === "string" ? name : String(entityId);
13734
+ }
13735
+ // ── Pressed/hover tint ─────────────────────────────────────────────────────
13736
+ beginPressTint(world, entityId) {
13737
+ this.setTint(world, entityId, PRESS_TINT);
13738
+ this.tintFloors.set(entityId, PRESS_TINT_MIN_SECONDS);
13739
+ }
13740
+ endPressTint(entityId) {
13741
+ if (!this.tintFloors.has(entityId)) {
13742
+ this.tintFloors.set(entityId, 0);
13743
+ }
13744
+ }
13745
+ updateTints(world, deltaTime) {
13746
+ for (const [entityId, remaining] of this.tintFloors) {
13747
+ const next = remaining - deltaTime;
13748
+ const stillHeld = [...this.xrPresses.values()].some((p) => p.entityId === entityId) || this.mousePress?.entityId === entityId;
13749
+ if (stillHeld) continue;
13750
+ if (next <= 0) {
13751
+ this.tintFloors.delete(entityId);
13752
+ this.setTint(
13753
+ world,
13754
+ entityId,
13755
+ this.mouseHover === entityId ? HOVER_TINT : 1
13756
+ );
13757
+ } else {
13758
+ this.tintFloors.set(entityId, next);
13759
+ }
13760
+ }
13761
+ }
13762
+ setTint(world, entityId, scalar) {
13763
+ const object = this.widgetObject(world, entityId);
13764
+ if (!object) return;
13765
+ if (scalar === 1) this.tinted.delete(entityId);
13766
+ else this.tinted.add(entityId);
13767
+ object.traverse((child) => {
13768
+ const material = child.material;
13769
+ if (material?.color) material.color.setScalar(scalar);
13770
+ });
13771
+ }
13772
+ };
13773
+ __name(_UIInteractionSystem, "UIInteractionSystem");
13774
+ var UIInteractionSystem = _UIInteractionSystem;
13775
+
13776
+ // core/ui/uiCanvas.ts
13777
+ var DEFAULT_PIXELS_PER_METER = 1024;
13778
+ var MIN_PIXELS_PER_METER = 256;
13779
+ var MAX_PIXELS_PER_METER = 2048;
13780
+ var MAX_CANVAS_SIDE = 2048;
13781
+ function resolveCanvasSize(widthMeters, heightMeters, pixelDensity = DEFAULT_PIXELS_PER_METER) {
13782
+ const w = Math.max(0.01, widthMeters);
13783
+ const h = Math.max(0.01, heightMeters);
13784
+ const authored = Math.min(
13785
+ MAX_PIXELS_PER_METER,
13786
+ Math.max(MIN_PIXELS_PER_METER, pixelDensity)
13787
+ );
13788
+ const density = Math.min(authored, MAX_CANVAS_SIDE / Math.max(w, h));
13789
+ const even = /* @__PURE__ */ __name((v) => Math.max(2, 2 * Math.round(v * density / 2)), "even");
13790
+ return { pxWidth: even(w), pxHeight: even(h), density };
13791
+ }
13792
+ __name(resolveCanvasSize, "resolveCanvasSize");
13793
+ function wrapText(measure, text, maxWidthPx) {
13794
+ const lines = [];
13795
+ for (const paragraph of text.split("\n")) {
13796
+ const words = paragraph.split(/\s+/).filter((w) => w.length > 0);
13797
+ if (words.length === 0) {
13798
+ lines.push("");
13799
+ continue;
13800
+ }
13801
+ let line = "";
13802
+ for (const word of words) {
13803
+ const candidate = line ? `${line} ${word}` : word;
13804
+ if (measure(candidate) <= maxWidthPx) {
13805
+ line = candidate;
13806
+ continue;
13807
+ }
13808
+ if (line) {
13809
+ lines.push(line);
13810
+ line = "";
13811
+ }
13812
+ if (measure(word) <= maxWidthPx) {
13813
+ line = word;
13814
+ continue;
13815
+ }
13816
+ let chunk = "";
13817
+ for (const char of word) {
13818
+ const next = chunk + char;
13819
+ if (measure(next) <= maxWidthPx || chunk.length === 0) {
13820
+ chunk = next;
13821
+ } else {
13822
+ lines.push(chunk);
13823
+ chunk = char;
13824
+ }
13825
+ }
13826
+ line = chunk;
13827
+ }
13828
+ lines.push(line);
13829
+ }
13830
+ return lines;
13831
+ }
13832
+ __name(wrapText, "wrapText");
13833
+ var ELLIPSIS = "\u2026";
13834
+ function clampLines(measure, lines, maxLines, maxWidthPx) {
13835
+ if (maxLines <= 0) return [];
13836
+ if (lines.length <= maxLines) return lines;
13837
+ const kept = lines.slice(0, maxLines);
13838
+ let last = kept[maxLines - 1];
13839
+ while (last.length > 0 && measure(last + ELLIPSIS) > maxWidthPx) {
13840
+ last = last.slice(0, -1);
13841
+ }
13842
+ kept[maxLines - 1] = last + ELLIPSIS;
13843
+ return kept;
13844
+ }
13845
+ __name(clampLines, "clampLines");
13846
+ var UI_FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
13847
+ function tracePath(ctx, x, y, w, h, r) {
13848
+ const radius = Math.max(0, Math.min(r, w / 2, h / 2));
13849
+ ctx.beginPath();
13850
+ if (typeof ctx.roundRect === "function") {
13851
+ ctx.roundRect(x, y, w, h, radius);
13852
+ return;
13853
+ }
13854
+ ctx.moveTo(x + radius, y);
13855
+ ctx.arcTo(x + w, y, x + w, y + h, radius);
13856
+ ctx.arcTo(x + w, y + h, x, y + h, radius);
13857
+ ctx.arcTo(x, y + h, x, y, radius);
13858
+ ctx.arcTo(x, y, x + w, y, radius);
13859
+ ctx.closePath();
13860
+ }
13861
+ __name(tracePath, "tracePath");
13862
+ function withOpacity(ctx, color, opacity) {
13863
+ if (opacity >= 1) return color;
13864
+ ctx.save();
13865
+ ctx.fillStyle = color;
13866
+ const parsed = ctx.fillStyle;
13867
+ ctx.restore();
13868
+ if (parsed.startsWith("#") && (parsed.length === 7 || parsed.length === 4)) {
13869
+ const hex = parsed.length === 4 ? `#${parsed[1]}${parsed[1]}${parsed[2]}${parsed[2]}${parsed[3]}${parsed[3]}` : parsed;
13870
+ const r = Number.parseInt(hex.slice(1, 3), 16);
13871
+ const g = Number.parseInt(hex.slice(3, 5), 16);
13872
+ const b = Number.parseInt(hex.slice(5, 7), 16);
13873
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
13874
+ }
13875
+ return parsed;
13876
+ }
13877
+ __name(withOpacity, "withOpacity");
13878
+ function drawChrome(ctx, pxWidth, pxHeight, density, style) {
13879
+ ctx.clearRect(0, 0, pxWidth, pxHeight);
13880
+ const border = Math.max(0, style.borderWidthMeters * density);
13881
+ const radius = style.cornerRadiusMeters * density;
13882
+ tracePath(ctx, 0, 0, pxWidth, pxHeight, radius);
13883
+ ctx.fillStyle = withOpacity(
13884
+ ctx,
13885
+ style.backgroundColor,
13886
+ Math.max(0, Math.min(1, style.backgroundOpacity))
13887
+ );
13888
+ ctx.fill();
13889
+ if (border > 0) {
13890
+ tracePath(
13891
+ ctx,
13892
+ border / 2,
13893
+ border / 2,
13894
+ pxWidth - border,
13895
+ pxHeight - border,
13896
+ Math.max(0, radius - border / 2)
13897
+ );
13898
+ ctx.strokeStyle = style.borderColor;
13899
+ ctx.lineWidth = border;
13900
+ ctx.stroke();
13901
+ }
13902
+ }
13903
+ __name(drawChrome, "drawChrome");
13904
+ function drawTextBlock(ctx, pxWidth, pxHeight, density, text, style) {
13905
+ const fontPx = Math.max(4, style.fontSizeMeters * density);
13906
+ const paddingPx = style.paddingMeters * density;
13907
+ const contentW = Math.max(1, pxWidth - paddingPx * 2);
13908
+ const contentH = Math.max(1, pxHeight - paddingPx * 2);
13909
+ const lineHeightPx = fontPx * style.lineHeight;
13910
+ ctx.font = `${style.fontWeight} ${fontPx}px ${UI_FONT_STACK}`;
13911
+ const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width, "measure");
13912
+ const maxLines = Math.max(1, Math.floor(contentH / lineHeightPx));
13913
+ const lines = clampLines(
13914
+ measure,
13915
+ wrapText(measure, text, contentW),
13916
+ maxLines,
13917
+ contentW
13918
+ );
13919
+ const blockH = lines.length * lineHeightPx;
13920
+ const top = style.valign === "top" ? paddingPx : style.valign === "bottom" ? pxHeight - paddingPx - blockH : (pxHeight - blockH) / 2;
13921
+ const anchorX = style.align === "left" ? paddingPx : style.align === "right" ? pxWidth - paddingPx : pxWidth / 2;
13922
+ ctx.save();
13923
+ tracePath(ctx, 0, 0, pxWidth, pxHeight, 0);
13924
+ ctx.clip();
13925
+ ctx.fillStyle = style.color;
13926
+ ctx.textAlign = style.align;
13927
+ ctx.textBaseline = "middle";
13928
+ for (let i = 0; i < lines.length; i += 1) {
13929
+ ctx.fillText(lines[i], anchorX, top + (i + 0.5) * lineHeightPx);
13930
+ }
13931
+ ctx.restore();
13932
+ }
13933
+ __name(drawTextBlock, "drawTextBlock");
13934
+ function panelSignature(widthM, heightM, size, style) {
13935
+ return JSON.stringify([
13936
+ widthM,
13937
+ heightM,
13938
+ size.pxWidth,
13939
+ size.pxHeight,
13940
+ style.backgroundColor,
13941
+ style.backgroundOpacity,
13942
+ style.cornerRadiusMeters,
13943
+ style.borderWidthMeters,
13944
+ style.borderColor
13945
+ ]);
13946
+ }
13947
+ __name(panelSignature, "panelSignature");
13948
+ function textSignature(widthM, heightM, size, text, style) {
13949
+ return JSON.stringify([
13950
+ widthM,
13951
+ heightM,
13952
+ size.pxWidth,
13953
+ size.pxHeight,
13954
+ text,
13955
+ style.fontSizeMeters,
13956
+ style.fontWeight,
13957
+ style.color,
13958
+ style.align,
13959
+ style.valign,
13960
+ style.lineHeight,
13961
+ style.paddingMeters
13962
+ ]);
13963
+ }
13964
+ __name(textSignature, "textSignature");
13965
+ function buttonSignature(widthM, heightM, size, panel, label, text) {
13966
+ return JSON.stringify([
13967
+ panelSignature(widthM, heightM, size, panel),
13968
+ label,
13969
+ text.fontSizeMeters,
13970
+ text.fontWeight,
13971
+ text.color
13972
+ ]);
13973
+ }
13974
+ __name(buttonSignature, "buttonSignature");
13975
+ function trackSignature(widthM, heightM, size, panel, accentColor, knobColor) {
13976
+ return JSON.stringify([
13977
+ panelSignature(widthM, heightM, size, panel),
13978
+ accentColor,
13979
+ knobColor
13980
+ ]);
13981
+ }
13982
+ __name(trackSignature, "trackSignature");
13983
+ function drawRoundedBar(ctx, pxX, pxY, pxW, pxH, color) {
13984
+ tracePath(ctx, pxX, pxY, pxW, pxH, pxH / 2);
13985
+ ctx.fillStyle = color;
13986
+ ctx.fill();
13987
+ }
13988
+ __name(drawRoundedBar, "drawRoundedBar");
13989
+
13990
+ // core/systems/UIPanelSystem.ts
13991
+ var FACE_RENDER_ORDER = 10;
13992
+ var CHILD_Z = 2e-3;
13993
+ var _UIPanelSystem = class _UIPanelSystem {
13994
+ constructor(options) {
13995
+ this.entries = /* @__PURE__ */ new Map();
13996
+ this.createCanvas = options?.createCanvas ?? (() => document.createElement("canvas"));
13997
+ }
13998
+ update(world, deltaTime = 0) {
13999
+ const live = /* @__PURE__ */ new Set();
14000
+ for (const entityId of world.query(UIPanelComponent, Object3DRef)) {
14001
+ const panel = world.getComponent(entityId, UIPanelComponent);
14002
+ if (!panel) continue;
14003
+ live.add(entityId);
14004
+ this.syncPanel(world, entityId, panel);
14005
+ this.applyFollow(world, entityId, panel);
14006
+ }
14007
+ for (const entityId of world.query(UITextComponent, Object3DRef)) {
14008
+ const text = world.getComponent(entityId, UITextComponent);
14009
+ if (!text) continue;
14010
+ live.add(entityId);
14011
+ this.syncText(world, entityId, text);
14012
+ }
14013
+ for (const entityId of world.query(UIButtonComponent, Object3DRef)) {
14014
+ const button = world.getComponent(entityId, UIButtonComponent);
14015
+ if (!button) continue;
14016
+ live.add(entityId);
14017
+ this.syncButton(world, entityId, button);
14018
+ }
14019
+ for (const entityId of world.query(UIToggleComponent, Object3DRef)) {
14020
+ const toggle = world.getComponent(entityId, UIToggleComponent);
14021
+ if (!toggle) continue;
14022
+ live.add(entityId);
14023
+ this.syncToggle(world, entityId, toggle, deltaTime);
14024
+ }
14025
+ for (const entityId of world.query(UISliderComponent, Object3DRef)) {
14026
+ const slider = world.getComponent(entityId, UISliderComponent);
14027
+ if (!slider) continue;
14028
+ live.add(entityId);
14029
+ this.syncSlider(world, entityId, slider);
14030
+ }
14031
+ for (const [entityId, entry] of this.entries) {
14032
+ if (!live.has(entityId)) {
14033
+ this.destroyEntry(entry);
14034
+ this.entries.delete(entityId);
14035
+ }
14036
+ }
14037
+ }
14038
+ /** All GPU/canvas resources; call on world teardown. */
14039
+ dispose() {
14040
+ for (const entry of this.entries.values()) this.destroyEntry(entry);
14041
+ this.entries.clear();
14042
+ }
14043
+ // ── Widgets ────────────────────────────────────────────────────────────────
14044
+ syncPanel(world, entityId, panel) {
14045
+ const size = resolveCanvasSize(panel.width, panel.height, panel.pixelDensity);
14046
+ const style = {
14047
+ backgroundColor: panel.backgroundColor,
14048
+ backgroundOpacity: panel.backgroundOpacity,
14049
+ cornerRadiusMeters: panel.cornerRadius,
14050
+ borderWidthMeters: panel.borderWidth,
14051
+ borderColor: panel.borderColor
14052
+ };
14053
+ const signature = panelSignature(panel.width, panel.height, size, style);
14054
+ this.rebuildIfChanged(world, entityId, signature, panel.width, panel.height, size, (ctx) => {
14055
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, style);
14056
+ });
14057
+ }
14058
+ syncText(world, entityId, text) {
14059
+ const size = resolveCanvasSize(text.width, text.height);
14060
+ const style = {
14061
+ fontSizeMeters: text.fontSize,
14062
+ fontWeight: text.fontWeight,
14063
+ color: text.color,
14064
+ align: text.align,
14065
+ valign: text.valign,
14066
+ lineHeight: text.lineHeight,
14067
+ paddingMeters: text.padding
14068
+ };
14069
+ const signature = textSignature(text.width, text.height, size, text.text, style);
14070
+ this.rebuildIfChanged(world, entityId, signature, text.width, text.height, size, (ctx) => {
14071
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
14072
+ drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, text.text, style);
14073
+ });
14074
+ }
14075
+ syncButton(world, entityId, button) {
14076
+ const size = resolveCanvasSize(button.width, button.height);
14077
+ const chrome = {
14078
+ backgroundColor: button.backgroundColor,
14079
+ backgroundOpacity: button.backgroundOpacity,
14080
+ cornerRadiusMeters: button.cornerRadius,
14081
+ borderWidthMeters: button.borderWidth,
14082
+ borderColor: button.borderColor
14083
+ };
14084
+ const label = {
14085
+ fontSizeMeters: button.fontSize,
14086
+ fontWeight: button.fontWeight,
14087
+ color: button.textColor,
14088
+ align: "center",
14089
+ valign: "middle",
14090
+ lineHeight: 1.2,
14091
+ paddingMeters: 0.01
14092
+ };
14093
+ const signature = buttonSignature(
14094
+ button.width,
14095
+ button.height,
14096
+ size,
14097
+ chrome,
14098
+ button.label,
14099
+ label
14100
+ );
14101
+ this.rebuildIfChanged(world, entityId, signature, button.width, button.height, size, (ctx) => {
14102
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
14103
+ drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, button.label, label);
14104
+ });
14105
+ }
14106
+ syncToggle(world, entityId, toggle, deltaTime) {
14107
+ const size = resolveCanvasSize(toggle.width, toggle.height);
14108
+ const chrome = {
14109
+ backgroundColor: toggle.value ? toggle.accentColor : toggle.trackColor,
14110
+ backgroundOpacity: 1,
14111
+ cornerRadiusMeters: toggle.height / 2,
14112
+ borderWidthMeters: 0,
14113
+ borderColor: "#000000"
14114
+ };
14115
+ const signature = JSON.stringify([
14116
+ trackSignature(
14117
+ toggle.width,
14118
+ toggle.height,
14119
+ size,
14120
+ chrome,
14121
+ toggle.accentColor,
14122
+ toggle.knobColor
14123
+ ),
14124
+ toggle.value
14125
+ ]);
14126
+ const entry = this.rebuildIfChanged(
14127
+ world,
14128
+ entityId,
14129
+ signature,
14130
+ toggle.width,
14131
+ toggle.height,
14132
+ size,
14133
+ (ctx) => {
14134
+ drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
14135
+ }
14136
+ );
14137
+ if (!entry) return;
14138
+ if (!entry.knob) {
14139
+ entry.knob = this.makeDisc(
14140
+ toggle.height * 0.4,
14141
+ toggle.knobColor,
14142
+ entry.face.parent,
14143
+ CHILD_Z
14144
+ );
14145
+ }
14146
+ const inset = toggle.height / 2;
14147
+ const target = toggle.value ? toggle.width / 2 - inset : -(toggle.width / 2 - inset);
14148
+ const knob = entry.knob;
14149
+ const ease = Math.min(1, deltaTime * 14);
14150
+ knob.position.x += (target - knob.position.x) * (deltaTime > 0 ? ease : 1);
14151
+ knob.position.z = CHILD_Z;
14152
+ }
14153
+ syncSlider(world, entityId, slider) {
14154
+ const size = resolveCanvasSize(slider.width, slider.height);
14155
+ const trackThickness = slider.height * 0.32;
14156
+ const knobRadius = slider.height * 0.45;
14157
+ const usable = Math.max(1e-3, slider.width - slider.height);
14158
+ const chrome = {
14159
+ backgroundColor: slider.trackColor,
14160
+ backgroundOpacity: 1,
14161
+ cornerRadiusMeters: trackThickness / 2,
14162
+ borderWidthMeters: 0,
14163
+ borderColor: "#000000"
14164
+ };
14165
+ const signature = trackSignature(
14166
+ slider.width,
14167
+ slider.height,
14168
+ size,
14169
+ chrome,
14170
+ slider.accentColor,
14171
+ slider.knobColor
14172
+ );
14173
+ const entry = this.rebuildIfChanged(
14174
+ world,
14175
+ entityId,
14176
+ signature,
14177
+ slider.width,
14178
+ slider.height,
14179
+ size,
14180
+ (ctx) => {
14181
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
14182
+ const trackWidthM = usable + trackThickness;
14183
+ drawRoundedBar(
14184
+ ctx,
14185
+ (slider.width - trackWidthM) / 2 * size.density,
14186
+ (slider.height - trackThickness) / 2 * size.density,
14187
+ trackWidthM * size.density,
14188
+ trackThickness * size.density,
14189
+ slider.trackColor
14190
+ );
14191
+ }
14192
+ );
14193
+ if (!entry) return;
14194
+ if (!entry.fill) {
14195
+ entry.fill = this.makeQuad(
14196
+ 1,
14197
+ trackThickness,
14198
+ slider.accentColor,
14199
+ entry.face.parent,
14200
+ CHILD_Z / 2
14201
+ );
14202
+ entry.cap = this.makeDisc(
14203
+ trackThickness / 2,
14204
+ slider.accentColor,
14205
+ entry.face.parent,
14206
+ CHILD_Z / 2,
14207
+ Math.PI / 2,
14208
+ Math.PI
14209
+ );
14210
+ entry.cap.position.x = -usable / 2;
14211
+ }
14212
+ if (!entry.knob) {
14213
+ entry.knob = this.makeDisc(
14214
+ knobRadius,
14215
+ slider.knobColor,
14216
+ entry.face.parent,
14217
+ CHILD_Z
14218
+ );
14219
+ }
14220
+ const range = Math.max(1e-6, slider.max - slider.min);
14221
+ const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
14222
+ entry.knob.position.x = -usable / 2 + t * usable;
14223
+ entry.knob.position.z = CHILD_Z;
14224
+ entry.fill.scale.x = Math.max(1e-4, t * usable);
14225
+ entry.fill.position.x = -usable / 2 + t * usable / 2;
14226
+ entry.fill.position.z = CHILD_Z / 2;
14227
+ }
14228
+ // ── Machinery ──────────────────────────────────────────────────────────────
14229
+ rebuildIfChanged(world, entityId, signature, widthM, heightM, size, draw) {
14230
+ const root = world.getComponent(entityId, Object3DRef)?.object;
14231
+ if (!root) return null;
14232
+ let entry = this.entries.get(entityId);
14233
+ if (entry && entry.signature === signature) return entry;
14234
+ if (entry) this.disposeMovingParts(entry);
14235
+ if (!entry) {
14236
+ const canvas2 = this.createCanvas();
14237
+ const texture = new THREE56__namespace.CanvasTexture(
14238
+ canvas2
14239
+ );
14240
+ this.configureTexture(world, texture);
14241
+ const material = new THREE56__namespace.MeshBasicMaterial({
14242
+ map: texture,
14243
+ transparent: true,
14244
+ depthWrite: false,
14245
+ // UI colours are authored absolutes; scene tone mapping would shift
14246
+ // them (the classic "why is my white UI beige" bug).
14247
+ toneMapped: false,
14248
+ side: THREE56__namespace.DoubleSide,
14249
+ premultipliedAlpha: true
14250
+ });
14251
+ const face = new THREE56__namespace.Mesh(
14252
+ new THREE56__namespace.PlaneGeometry(widthM, heightM),
14253
+ material
14254
+ );
14255
+ face.renderOrder = FACE_RENDER_ORDER;
14256
+ root.add(face);
14257
+ entry = {
14258
+ signature: "",
14259
+ face,
14260
+ texture,
14261
+ material,
14262
+ canvas: canvas2,
14263
+ knob: null,
14264
+ fill: null,
14265
+ cap: null
14266
+ };
14267
+ this.entries.set(entityId, entry);
14268
+ }
14269
+ const { canvas } = entry;
14270
+ if (canvas.width !== size.pxWidth || canvas.height !== size.pxHeight) {
14271
+ canvas.width = size.pxWidth;
14272
+ canvas.height = size.pxHeight;
14273
+ }
14274
+ const params = entry.face.geometry.parameters;
14275
+ if (params.width !== widthM || params.height !== heightM) {
14276
+ entry.face.geometry.dispose();
14277
+ entry.face.geometry = new THREE56__namespace.PlaneGeometry(widthM, heightM);
14278
+ }
14279
+ const ctx = canvas.getContext("2d");
14280
+ if (ctx) draw(ctx);
14281
+ entry.texture.needsUpdate = true;
14282
+ entry.signature = signature;
14283
+ return entry;
14284
+ }
14285
+ configureTexture(world, texture) {
14286
+ texture.colorSpace = THREE56__namespace.SRGBColorSpace;
14287
+ texture.generateMipmaps = true;
14288
+ texture.minFilter = THREE56__namespace.LinearMipmapLinearFilter;
14289
+ texture.magFilter = THREE56__namespace.LinearFilter;
14290
+ texture.premultiplyAlpha = true;
14291
+ try {
14292
+ const max = world.getRenderer().capabilities?.getMaxAnisotropy?.();
14293
+ if (typeof max === "number") texture.anisotropy = Math.min(8, max);
14294
+ } catch {
14295
+ }
14296
+ }
14297
+ makeQuad(widthM, heightM, color, parent, z) {
14298
+ return this.addPart(
14299
+ new THREE56__namespace.PlaneGeometry(widthM, heightM),
14300
+ color,
14301
+ parent,
14302
+ z
14303
+ );
14304
+ }
14305
+ /** A filled disc (or arc segment, for the fill's rounded cap). */
14306
+ makeDisc(radiusM, color, parent, z, thetaStart = 0, thetaLength = Math.PI * 2) {
14307
+ return this.addPart(
14308
+ new THREE56__namespace.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
14309
+ color,
14310
+ parent,
14311
+ z
14312
+ );
14313
+ }
14314
+ addPart(geometry, color, parent, z) {
14315
+ const mesh = new THREE56__namespace.Mesh(
14316
+ geometry,
14317
+ new THREE56__namespace.MeshBasicMaterial({
14318
+ color,
14319
+ transparent: true,
14320
+ depthWrite: false,
14321
+ toneMapped: false,
14322
+ side: THREE56__namespace.DoubleSide
14323
+ })
14324
+ );
14325
+ mesh.renderOrder = FACE_RENDER_ORDER + 1;
14326
+ mesh.position.z = z;
14327
+ mesh.raycast = () => {
14328
+ };
14329
+ parent?.add(mesh);
14330
+ return mesh;
14331
+ }
14332
+ /** Remove + dispose knob/fill/cap so a sync pass rebuilds them fresh. */
14333
+ disposeMovingParts(entry) {
14334
+ for (const key of ["knob", "fill", "cap"]) {
14335
+ const mesh = entry[key];
14336
+ if (!mesh) continue;
14337
+ mesh.removeFromParent();
14338
+ mesh.geometry.dispose();
14339
+ mesh.material.dispose();
14340
+ entry[key] = null;
14341
+ }
14342
+ }
14343
+ /**
14344
+ * Billboard: yaw the ENTITY TRANSFORM toward the camera. Writing the
14345
+ * Object3D directly would be overwritten by `TransformSyncSystem` (which
14346
+ * runs after this system, before render) — the same reason locomotion
14347
+ * writes the rig transform. Skipped in the editor so the gizmo and the
14348
+ * authored rotation stay in charge while authoring.
14349
+ */
14350
+ applyFollow(world, entityId, panel) {
14351
+ if (panel.follow !== "billboard" || world.isEditorMode()) return;
14352
+ const transform = world.getComponent(entityId, TransformComponent);
14353
+ const object = world.getComponent(entityId, Object3DRef)?.object;
14354
+ if (!transform || !object) return;
14355
+ const camera = world.getCamera();
14356
+ const cam = new THREE56__namespace.Vector3();
14357
+ const self = new THREE56__namespace.Vector3();
14358
+ camera.getWorldPosition(cam);
14359
+ object.getWorldPosition(self);
14360
+ transform.rotation.y = Math.atan2(cam.x - self.x, cam.z - self.z);
14361
+ }
14362
+ destroyEntry(entry) {
14363
+ this.disposeMovingParts(entry);
14364
+ entry.face.removeFromParent();
14365
+ entry.face.geometry.dispose();
14366
+ entry.face.material.dispose();
14367
+ entry.texture.dispose();
14368
+ }
14369
+ };
14370
+ __name(_UIPanelSystem, "UIPanelSystem");
14371
+ var UIPanelSystem = _UIPanelSystem;
12795
14372
  var _XRHitTestSystem = class _XRHitTestSystem {
12796
14373
  constructor() {
12797
14374
  this.sources = /* @__PURE__ */ new Map();
@@ -12828,12 +14405,12 @@ var _XRHitTestSystem = class _XRHitTestSystem {
12828
14405
  if (!hits || hits.length === 0) continue;
12829
14406
  const pose = hits[0].getPose(referenceSpace);
12830
14407
  if (!pose) continue;
12831
- const matrix = new THREE51__namespace.Matrix4().fromArray(
14408
+ const matrix = new THREE56__namespace.Matrix4().fromArray(
12832
14409
  pose.transform.matrix
12833
14410
  );
12834
- const position = new THREE51__namespace.Vector3();
12835
- const quaternion = new THREE51__namespace.Quaternion();
12836
- matrix.decompose(position, quaternion, new THREE51__namespace.Vector3());
14411
+ const position = new THREE56__namespace.Vector3();
14412
+ const quaternion = new THREE56__namespace.Quaternion();
14413
+ matrix.decompose(position, quaternion, new THREE56__namespace.Vector3());
12837
14414
  this.results.set(key, { position, quaternion });
12838
14415
  }
12839
14416
  }
@@ -12904,9 +14481,13 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12904
14481
  this.anchors = /* @__PURE__ */ new Map();
12905
14482
  /** In-flight creations, so one entity is not anchored twice. */
12906
14483
  this.pending = /* @__PURE__ */ new Set();
14484
+ /** Per-anchor head-glue gates — see XREnvironmentSystem's TrackedEntry. */
14485
+ this.gates = /* @__PURE__ */ new Map();
12907
14486
  this.presenting = false;
14487
+ this.tmpViewerMatrix = new THREE56__namespace.Matrix4();
14488
+ this.tmpViewPos = new THREE56__namespace.Vector3();
12908
14489
  }
12909
- update(world) {
14490
+ update(world, deltaTime = 0) {
12910
14491
  const presenting = world.isXrPresenting();
12911
14492
  if (!presenting) {
12912
14493
  if (this.presenting) {
@@ -12926,8 +14507,11 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12926
14507
  if (!live.has(entityId)) {
12927
14508
  anchor.delete?.();
12928
14509
  this.anchors.delete(entityId);
14510
+ this.gates.delete(entityId);
12929
14511
  }
12930
14512
  }
14513
+ const viewer = frame.getViewerPose?.(referenceSpace);
14514
+ const viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
12931
14515
  for (const entityId of entityIds) {
12932
14516
  const component = world.getComponent(entityId, XRAnchorComponent);
12933
14517
  const transform = world.getComponent(entityId, TransformComponent);
@@ -12937,6 +14521,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12937
14521
  if (existing) {
12938
14522
  existing.delete?.();
12939
14523
  this.anchors.delete(entityId);
14524
+ this.gates.delete(entityId);
12940
14525
  }
12941
14526
  continue;
12942
14527
  }
@@ -12946,7 +14531,23 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12946
14531
  continue;
12947
14532
  }
12948
14533
  const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
12949
- if (pose) decomposeXrPose(pose.transform.matrix, transform);
14534
+ if (pose && pose.emulatedPosition !== true) {
14535
+ let gate = this.gates.get(entityId);
14536
+ if (!gate) {
14537
+ gate = new SurfacePoseGate();
14538
+ this.gates.set(entityId, gate);
14539
+ }
14540
+ const m = pose.transform.matrix;
14541
+ const refPos = { x: m[12], y: m[13], z: m[14] };
14542
+ let viewPos = null;
14543
+ if (viewerInverse) {
14544
+ const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(viewerInverse);
14545
+ viewPos = { x: v.x, y: v.y, z: v.z };
14546
+ }
14547
+ if (gate.update(refPos, viewPos, deltaTime)) {
14548
+ decomposeXrPose(pose.transform.matrix, transform);
14549
+ }
14550
+ }
12950
14551
  }
12951
14552
  }
12952
14553
  /** Whether this entity's pose is currently being driven by an anchor. */
@@ -12958,8 +14559,8 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12958
14559
  if (typeof frame.createAnchor !== "function") return;
12959
14560
  const RigidTransform = globalThis.XRRigidTransform;
12960
14561
  if (!RigidTransform) return;
12961
- const quaternion = new THREE51__namespace.Quaternion().setFromEuler(
12962
- new THREE51__namespace.Euler(
14562
+ const quaternion = new THREE56__namespace.Quaternion().setFromEuler(
14563
+ new THREE56__namespace.Euler(
12963
14564
  transform.rotation.x,
12964
14565
  transform.rotation.y,
12965
14566
  transform.rotation.z,
@@ -12990,6 +14591,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
12990
14591
  }).finally(() => this.pending.delete(entityId));
12991
14592
  }
12992
14593
  releaseAll() {
14594
+ this.gates.clear();
12993
14595
  for (const anchor of this.anchors.values()) anchor.delete?.();
12994
14596
  this.anchors.clear();
12995
14597
  this.pending.clear();
@@ -13004,9 +14606,9 @@ var XRAnchorSystem = _XRAnchorSystem;
13004
14606
  function buildGeometry2(area) {
13005
14607
  if (area.shape === "circle") {
13006
14608
  const radius = Math.max(5e-3, area.width / 2);
13007
- return new THREE51__namespace.CircleGeometry(radius, 48);
14609
+ return new THREE56__namespace.CircleGeometry(radius, 48);
13008
14610
  }
13009
- return new THREE51__namespace.PlaneGeometry(
14611
+ return new THREE56__namespace.PlaneGeometry(
13010
14612
  Math.max(0.01, area.width),
13011
14613
  Math.max(0.01, area.depth)
13012
14614
  );
@@ -13029,7 +14631,7 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
13029
14631
  for (const entityId of entityIds) {
13030
14632
  const area = world.getComponent(entityId, VRTeleportAreaComponent);
13031
14633
  const object = world.getComponent(entityId, Object3DRef)?.object;
13032
- if (!area || !(object instanceof THREE51__namespace.Mesh)) continue;
14634
+ if (!area || !(object instanceof THREE56__namespace.Mesh)) continue;
13033
14635
  const signature = signatureOf(area);
13034
14636
  if (this.signatures.get(entityId) === signature) continue;
13035
14637
  this.signatures.set(entityId, signature);
@@ -13041,18 +14643,18 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
13041
14643
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
13042
14644
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
13043
14645
  function createHandMaterial(preset, options) {
13044
- const color = new THREE51__namespace.Color(options.color);
14646
+ const color = new THREE56__namespace.Color(options.color);
13045
14647
  switch (preset) {
13046
14648
  case "ghost":
13047
- return new THREE51__namespace.MeshBasicMaterial({
14649
+ return new THREE56__namespace.MeshBasicMaterial({
13048
14650
  color,
13049
14651
  transparent: true,
13050
14652
  opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
13051
14653
  depthWrite: false,
13052
- side: THREE51__namespace.DoubleSide
14654
+ side: THREE56__namespace.DoubleSide
13053
14655
  });
13054
14656
  case "neon":
13055
- return new THREE51__namespace.MeshStandardMaterial({
14657
+ return new THREE56__namespace.MeshStandardMaterial({
13056
14658
  color,
13057
14659
  emissive: color,
13058
14660
  emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
@@ -13062,7 +14664,7 @@ function createHandMaterial(preset, options) {
13062
14664
  opacity: options.opacity
13063
14665
  });
13064
14666
  case "skeleton":
13065
- return new THREE51__namespace.MeshBasicMaterial({
14667
+ return new THREE56__namespace.MeshBasicMaterial({
13066
14668
  color,
13067
14669
  wireframe: true,
13068
14670
  transparent: true,
@@ -13070,7 +14672,7 @@ function createHandMaterial(preset, options) {
13070
14672
  });
13071
14673
  case "custom":
13072
14674
  case "default":
13073
- return new THREE51__namespace.MeshStandardMaterial({
14675
+ return new THREE56__namespace.MeshStandardMaterial({
13074
14676
  color,
13075
14677
  roughness: 0.85,
13076
14678
  metalness: 0,
@@ -13078,10 +14680,10 @@ function createHandMaterial(preset, options) {
13078
14680
  emissiveIntensity: Math.max(0, options.emissiveIntensity),
13079
14681
  transparent: options.opacity < 1,
13080
14682
  opacity: options.opacity,
13081
- side: THREE51__namespace.DoubleSide
14683
+ side: THREE56__namespace.DoubleSide
13082
14684
  });
13083
14685
  default: {
13084
- return new THREE51__namespace.MeshStandardMaterial({ color });
14686
+ return new THREE56__namespace.MeshStandardMaterial({ color });
13085
14687
  }
13086
14688
  }
13087
14689
  }
@@ -13106,9 +14708,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
13106
14708
  constructor(options) {
13107
14709
  this.controllerModelFactory = new XRControllerModelFactory_js.XRControllerModelFactory();
13108
14710
  this.handModelFactory = new XRHandModelFactory_js.XRHandModelFactory();
13109
- this.group = new THREE51__namespace.Group();
14711
+ this.group = new THREE56__namespace.Group();
13110
14712
  /** Reused so a per-frame colour compare allocates nothing. */
13111
- this.rayColorScratch = new THREE51__namespace.Color();
14713
+ this.rayColorScratch = new THREE56__namespace.Color();
13112
14714
  this.slots = /* @__PURE__ */ new Map();
13113
14715
  this.attachedTo = null;
13114
14716
  /** Last applied preset signature, so materials rebuild only on real change. */
@@ -13295,17 +14897,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
13295
14897
  * without pulling in a line-rendering extension.
13296
14898
  */
13297
14899
  createRay() {
13298
- const geometry = new THREE51__namespace.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
14900
+ const geometry = new THREE56__namespace.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
13299
14901
  geometry.rotateX(-Math.PI / 2);
13300
14902
  geometry.translate(0, 0, -0.5);
13301
- const material = new THREE51__namespace.MeshBasicMaterial({
14903
+ const material = new THREE56__namespace.MeshBasicMaterial({
13302
14904
  color: "#4da3ff",
13303
14905
  transparent: true,
13304
14906
  opacity: 0.75,
13305
14907
  depthTest: false,
13306
14908
  depthWrite: false
13307
14909
  });
13308
- const mesh = new THREE51__namespace.Mesh(geometry, material);
14910
+ const mesh = new THREE56__namespace.Mesh(geometry, material);
13309
14911
  mesh.name = "XRPointerRay";
13310
14912
  mesh.renderOrder = 999;
13311
14913
  mesh.visible = false;
@@ -13336,14 +14938,14 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
13336
14938
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
13337
14939
  var XRControllerVisualSystem = _XRControllerVisualSystem;
13338
14940
  var MARKER_RADIUS = 0.28;
13339
- var ACCENT = new THREE51__namespace.Color("#4da3ff");
13340
- var SEGMENT_AXIS = new THREE51__namespace.Vector3(0, 1, 0);
14941
+ var ACCENT = new THREE56__namespace.Color("#4da3ff");
14942
+ var SEGMENT_AXIS = new THREE56__namespace.Vector3(0, 1, 0);
13341
14943
  var HEAD_CLEARANCE = 0.25;
13342
14944
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
13343
14945
  constructor(locomotionSystem) {
13344
- this.group = new THREE51__namespace.Group();
14946
+ this.group = new THREE56__namespace.Group();
13345
14947
  /** Holds the segment chain that draws the arc. */
13346
- this.arc = new THREE51__namespace.Group();
14948
+ this.arc = new THREE56__namespace.Group();
13347
14949
  /**
13348
14950
  * Unit cylinder along +Y, reused by every segment.
13349
14951
  *
@@ -13352,14 +14954,14 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
13352
14954
  * scaled cylinders gives real thickness while rebuilding no geometry: each
13353
14955
  * frame only moves, rotates and scales meshes that already exist.
13354
14956
  */
13355
- this.segmentGeometry = new THREE51__namespace.CylinderGeometry(1, 1, 1, 6, 1);
14957
+ this.segmentGeometry = new THREE56__namespace.CylinderGeometry(1, 1, 1, 6, 1);
13356
14958
  this.segments = [];
13357
- this.marker = new THREE51__namespace.Group();
14959
+ this.marker = new THREE56__namespace.Group();
13358
14960
  this.attached = false;
13359
- this.scratchDirection = new THREE51__namespace.Vector3();
13360
- this.scratchHead = new THREE51__namespace.Vector3();
13361
- this.validColor = new THREE51__namespace.Color();
13362
- this.invalidColor = new THREE51__namespace.Color();
14961
+ this.scratchDirection = new THREE56__namespace.Vector3();
14962
+ this.scratchHead = new THREE56__namespace.Vector3();
14963
+ this.validColor = new THREE56__namespace.Color();
14964
+ this.invalidColor = new THREE56__namespace.Color();
13363
14965
  this.locomotionSystem = locomotionSystem;
13364
14966
  this.group.name = "XRTeleportVisuals";
13365
14967
  this.group.userData.ecsPreviewHidden = true;
@@ -13437,12 +15039,12 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
13437
15039
  /** Grow the segment pool to cover the current arc. */
13438
15040
  ensureSegments(count) {
13439
15041
  while (this.segments.length < count) {
13440
- const material = new THREE51__namespace.MeshBasicMaterial({
15042
+ const material = new THREE56__namespace.MeshBasicMaterial({
13441
15043
  transparent: true,
13442
15044
  depthTest: false,
13443
15045
  depthWrite: false
13444
15046
  });
13445
- const mesh = new THREE51__namespace.Mesh(this.segmentGeometry, material);
15047
+ const mesh = new THREE56__namespace.Mesh(this.segmentGeometry, material);
13446
15048
  mesh.frustumCulled = false;
13447
15049
  mesh.renderOrder = 998;
13448
15050
  mesh.raycast = () => {
@@ -13466,33 +15068,33 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
13466
15068
  return world.getComponent(entityId, XRRigComponent) ?? null;
13467
15069
  }
13468
15070
  createRing() {
13469
- const geometry = new THREE51__namespace.RingGeometry(
15071
+ const geometry = new THREE56__namespace.RingGeometry(
13470
15072
  MARKER_RADIUS * 0.72,
13471
15073
  MARKER_RADIUS,
13472
15074
  48
13473
15075
  );
13474
15076
  geometry.rotateX(-Math.PI / 2);
13475
- return new THREE51__namespace.Mesh(
15077
+ return new THREE56__namespace.Mesh(
13476
15078
  geometry,
13477
- new THREE51__namespace.MeshBasicMaterial({
15079
+ new THREE56__namespace.MeshBasicMaterial({
13478
15080
  color: ACCENT,
13479
15081
  transparent: true,
13480
15082
  opacity: 0.9,
13481
- side: THREE51__namespace.DoubleSide,
15083
+ side: THREE56__namespace.DoubleSide,
13482
15084
  depthTest: false
13483
15085
  })
13484
15086
  );
13485
15087
  }
13486
15088
  createDot() {
13487
- const geometry = new THREE51__namespace.CircleGeometry(MARKER_RADIUS * 0.16, 24);
15089
+ const geometry = new THREE56__namespace.CircleGeometry(MARKER_RADIUS * 0.16, 24);
13488
15090
  geometry.rotateX(-Math.PI / 2);
13489
- return new THREE51__namespace.Mesh(
15091
+ return new THREE56__namespace.Mesh(
13490
15092
  geometry,
13491
- new THREE51__namespace.MeshBasicMaterial({
15093
+ new THREE56__namespace.MeshBasicMaterial({
13492
15094
  color: ACCENT,
13493
15095
  transparent: true,
13494
15096
  opacity: 0.55,
13495
- side: THREE51__namespace.DoubleSide,
15097
+ side: THREE56__namespace.DoubleSide,
13496
15098
  depthTest: false
13497
15099
  })
13498
15100
  );
@@ -13603,7 +15205,7 @@ function boneDirections(joints, finger) {
13603
15205
  const from = joints[chain[i - 1]];
13604
15206
  const to = joints[chain[i]];
13605
15207
  if (!from || !to) return null;
13606
- const direction = new THREE51__namespace.Vector3().subVectors(to, from);
15208
+ const direction = new THREE56__namespace.Vector3().subVectors(to, from);
13607
15209
  if (direction.lengthSq() < 1e-12) return null;
13608
15210
  directions.push(direction.normalize());
13609
15211
  }
@@ -13676,8 +15278,8 @@ function computeSpread(joints, a, b) {
13676
15278
  const bBase = joints[METACARPAL_JOINTS[b]];
13677
15279
  const bTip = joints[TIP_JOINTS[b]];
13678
15280
  if (!aBase || !aTip || !bBase || !bTip) return null;
13679
- const dirA = new THREE51__namespace.Vector3().subVectors(aTip, aBase);
13680
- const dirB = new THREE51__namespace.Vector3().subVectors(bTip, bBase);
15281
+ const dirA = new THREE56__namespace.Vector3().subVectors(aTip, aBase);
15282
+ const dirB = new THREE56__namespace.Vector3().subVectors(bTip, bBase);
13681
15283
  if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
13682
15284
  return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
13683
15285
  }
@@ -13687,18 +15289,18 @@ function computePalmPose(joints, handedness) {
13687
15289
  const indexBase = joints["index-finger-metacarpal"];
13688
15290
  const pinkyBase = joints["pinky-finger-metacarpal"];
13689
15291
  if (!wrist || !indexBase || !pinkyBase) return null;
13690
- const u = new THREE51__namespace.Vector3().subVectors(indexBase, wrist);
13691
- const v = new THREE51__namespace.Vector3().subVectors(pinkyBase, wrist);
13692
- const normal = new THREE51__namespace.Vector3().crossVectors(u, v);
15292
+ const u = new THREE56__namespace.Vector3().subVectors(indexBase, wrist);
15293
+ const v = new THREE56__namespace.Vector3().subVectors(pinkyBase, wrist);
15294
+ const normal = new THREE56__namespace.Vector3().crossVectors(u, v);
13693
15295
  if (normal.lengthSq() < 1e-12) return null;
13694
15296
  normal.normalize();
13695
15297
  if (handedness === "left") normal.multiplyScalar(-1);
13696
- const right = new THREE51__namespace.Vector3().subVectors(indexBase, pinkyBase);
15298
+ const right = new THREE56__namespace.Vector3().subVectors(indexBase, pinkyBase);
13697
15299
  if (right.lengthSq() < 1e-12) return null;
13698
15300
  right.normalize();
13699
15301
  if (handedness === "left") right.multiplyScalar(-1);
13700
- const up = new THREE51__namespace.Vector3().crossVectors(normal, right).normalize();
13701
- const center = new THREE51__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
15302
+ const up = new THREE56__namespace.Vector3().crossVectors(normal, right).normalize();
15303
+ const center = new THREE56__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
13702
15304
  return {
13703
15305
  center,
13704
15306
  normal,
@@ -13729,7 +15331,7 @@ function computeHandShape(joints, handedness) {
13729
15331
  const thumbTip = joints["thumb-tip"];
13730
15332
  let thumbDirection = null;
13731
15333
  if (thumbBase && thumbTip) {
13732
- const direction = new THREE51__namespace.Vector3().subVectors(thumbTip, thumbBase);
15334
+ const direction = new THREE56__namespace.Vector3().subVectors(thumbTip, thumbBase);
13733
15335
  if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
13734
15336
  }
13735
15337
  const fingerDirections = {};
@@ -13740,7 +15342,7 @@ function computeHandShape(joints, handedness) {
13740
15342
  fingerDirections[finger] = null;
13741
15343
  continue;
13742
15344
  }
13743
- const direction = new THREE51__namespace.Vector3().subVectors(tip, base);
15345
+ const direction = new THREE56__namespace.Vector3().subVectors(tip, base);
13744
15346
  fingerDirections[finger] = direction.lengthSq() > 1e-12 ? direction.normalize() : null;
13745
15347
  }
13746
15348
  return {
@@ -13774,12 +15376,12 @@ function scoreAgainst(actual, target, tolerance) {
13774
15376
  __name(scoreAgainst, "scoreAgainst");
13775
15377
  var DEFAULT_DIRECTION_TOLERANCE = 45;
13776
15378
  var WORLD_DIRECTIONS = {
13777
- up: new THREE51__namespace.Vector3(0, 1, 0),
13778
- down: new THREE51__namespace.Vector3(0, -1, 0),
13779
- forward: new THREE51__namespace.Vector3(0, 0, -1),
13780
- back: new THREE51__namespace.Vector3(0, 0, 1),
13781
- left: new THREE51__namespace.Vector3(-1, 0, 0),
13782
- right: new THREE51__namespace.Vector3(1, 0, 0)
15379
+ up: new THREE56__namespace.Vector3(0, 1, 0),
15380
+ down: new THREE56__namespace.Vector3(0, -1, 0),
15381
+ forward: new THREE56__namespace.Vector3(0, 0, -1),
15382
+ back: new THREE56__namespace.Vector3(0, 0, 1),
15383
+ left: new THREE56__namespace.Vector3(-1, 0, 0),
15384
+ right: new THREE56__namespace.Vector3(1, 0, 0)
13783
15385
  };
13784
15386
  function axisVector(shape, axis) {
13785
15387
  if (axis === "thumb") return shape.thumbDirection;
@@ -13794,7 +15396,7 @@ function resolveDirection(direction, shape, context) {
13794
15396
  const head = context?.headPosition;
13795
15397
  const origin = shape.palm?.center;
13796
15398
  if (!head || !origin) return null;
13797
- const toHead = new THREE51__namespace.Vector3().subVectors(head, origin);
15399
+ const toHead = new THREE56__namespace.Vector3().subVectors(head, origin);
13798
15400
  if (toHead.lengthSq() < 1e-12) return null;
13799
15401
  toHead.normalize();
13800
15402
  return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
@@ -13806,8 +15408,8 @@ function scoreDirections(actual, accepted, shape, toleranceDegrees, context) {
13806
15408
  for (const direction of accepted) {
13807
15409
  const wanted = resolveDirection(direction, shape, context);
13808
15410
  if (!wanted) continue;
13809
- const angle = THREE51__namespace.MathUtils.radToDeg(
13810
- Math.acos(THREE51__namespace.MathUtils.clamp(actual.dot(wanted), -1, 1))
15411
+ const angle = THREE56__namespace.MathUtils.radToDeg(
15412
+ Math.acos(THREE56__namespace.MathUtils.clamp(actual.dot(wanted), -1, 1))
13811
15413
  );
13812
15414
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
13813
15415
  const score = clamp01(1 - normalizedError / MATCH_FALLOFF);
@@ -13872,8 +15474,8 @@ function matchHandPose(shape, definition, context) {
13872
15474
  if (!handAxis || !wanted) {
13873
15475
  consider(0, "orientation");
13874
15476
  } else {
13875
- const angle = THREE51__namespace.MathUtils.radToDeg(
13876
- Math.acos(THREE51__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
15477
+ const angle = THREE56__namespace.MathUtils.radToDeg(
15478
+ Math.acos(THREE56__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
13877
15479
  );
13878
15480
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
13879
15481
  consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
@@ -13951,7 +15553,7 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
13951
15553
  this.trackers = /* @__PURE__ */ new Map();
13952
15554
  this.shapes = /* @__PURE__ */ new Map();
13953
15555
  /** Reused so head-relative orientation checks allocate nothing per frame. */
13954
- this.headPosition = new THREE51__namespace.Vector3();
15556
+ this.headPosition = new THREE56__namespace.Vector3();
13955
15557
  this.elapsed = 0;
13956
15558
  this.warnedNoHands = false;
13957
15559
  this.noHandSeconds = 0;
@@ -14128,7 +15730,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
14128
15730
  const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
14129
15731
  this.records.set(entityId, {
14130
15732
  shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
14131
- baseScale: new THREE51__namespace.Vector3(
15733
+ baseScale: new THREE56__namespace.Vector3(
14132
15734
  transform.scale.x,
14133
15735
  transform.scale.y,
14134
15736
  transform.scale.z
@@ -14141,27 +15743,27 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
14141
15743
  addShell(world, entityId, interactable) {
14142
15744
  const object = world.getComponent(entityId, Object3DRef)?.object;
14143
15745
  if (!object) return null;
14144
- const material = new THREE51__namespace.MeshBasicMaterial({
14145
- color: new THREE51__namespace.Color(interactable.hoverColor),
15746
+ const material = new THREE56__namespace.MeshBasicMaterial({
15747
+ color: new THREE56__namespace.Color(interactable.hoverColor),
14146
15748
  // Back faces only, so the shell shows just outside the silhouette instead
14147
15749
  // of covering the object.
14148
- side: THREE51__namespace.BackSide,
15750
+ side: THREE56__namespace.BackSide,
14149
15751
  transparent: true,
14150
15752
  opacity: 0.85,
14151
15753
  depthWrite: false
14152
15754
  });
14153
- const group = new THREE51__namespace.Group();
15755
+ const group = new THREE56__namespace.Group();
14154
15756
  group.name = "XRHoverShell";
14155
15757
  group.userData[XR_HOVER_SHELL_FLAG] = true;
14156
15758
  group.renderOrder = 998;
14157
15759
  object.traverse((child) => {
14158
- if (!(child instanceof THREE51__namespace.Mesh) || !child.geometry) return;
15760
+ if (!(child instanceof THREE56__namespace.Mesh) || !child.geometry) return;
14159
15761
  if (child.userData[XR_HOVER_SHELL_FLAG]) return;
14160
- const clone3 = new THREE51__namespace.Mesh(child.geometry, material);
15762
+ const clone3 = new THREE56__namespace.Mesh(child.geometry, material);
14161
15763
  child.updateWorldMatrix(true, false);
14162
15764
  object.updateWorldMatrix(true, false);
14163
15765
  clone3.matrixAutoUpdate = false;
14164
- clone3.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE51__namespace.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
15766
+ clone3.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE56__namespace.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
14165
15767
  clone3.userData[XR_HOVER_SHELL_FLAG] = true;
14166
15768
  clone3.raycast = () => {
14167
15769
  };
@@ -14210,7 +15812,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
14210
15812
  if (!shell) return;
14211
15813
  shell.parent?.remove(shell);
14212
15814
  shell.traverse((child) => {
14213
- if (child instanceof THREE51__namespace.Mesh) {
15815
+ if (child instanceof THREE56__namespace.Mesh) {
14214
15816
  const material = child.material;
14215
15817
  if (Array.isArray(material)) {
14216
15818
  for (const entry of material) entry.dispose();
@@ -14580,6 +16182,7 @@ var _World = class _World {
14580
16182
  const lightSyncSystem = new LightSyncSystem();
14581
16183
  const materialSyncSystem = new MaterialSyncSystem();
14582
16184
  const vrTeleportAreaSystem = new VRTeleportAreaSystem();
16185
+ const uiPanelSystem = new UIPanelSystem();
14583
16186
  const physicsSyncSystem = new PhysicsSyncSystem({
14584
16187
  isEditor: isEditor ?? false
14585
16188
  });
@@ -14610,6 +16213,7 @@ var _World = class _World {
14610
16213
  this.addSystems([
14611
16214
  ...coreSystems,
14612
16215
  vrTeleportAreaSystem,
16216
+ uiPanelSystem,
14613
16217
  physicsSyncSystem,
14614
16218
  ...systems,
14615
16219
  objectVisibilitySystem,
@@ -14922,8 +16526,8 @@ var _World = class _World {
14922
16526
  * ```
14923
16527
  */
14924
16528
  raycast(origin, direction, near = 0, far = Infinity) {
14925
- const rayOrigin = new THREE51__namespace.Vector3(origin.x, origin.y, origin.z);
14926
- const rayDirection = new THREE51__namespace.Vector3(
16529
+ const rayOrigin = new THREE56__namespace.Vector3(origin.x, origin.y, origin.z);
16530
+ const rayDirection = new THREE56__namespace.Vector3(
14927
16531
  direction.x,
14928
16532
  direction.y,
14929
16533
  direction.z
@@ -14932,7 +16536,7 @@ var _World = class _World {
14932
16536
  return [];
14933
16537
  }
14934
16538
  rayDirection.normalize();
14935
- const raycaster = new THREE51__namespace.Raycaster(rayOrigin, rayDirection, near, far);
16539
+ const raycaster = new THREE56__namespace.Raycaster(rayOrigin, rayDirection, near, far);
14936
16540
  const intersections = raycaster.intersectObjects(
14937
16541
  this.scene.children,
14938
16542
  true
@@ -14966,14 +16570,14 @@ var _World = class _World {
14966
16570
  if (!sourceObject) {
14967
16571
  return [];
14968
16572
  }
14969
- const rayOrigin = new THREE51__namespace.Vector3();
14970
- const rayDirection = new THREE51__namespace.Vector3();
16573
+ const rayOrigin = new THREE56__namespace.Vector3();
16574
+ const rayDirection = new THREE56__namespace.Vector3();
14971
16575
  sourceObject.getWorldPosition(rayOrigin);
14972
16576
  sourceObject.getWorldDirection(rayDirection);
14973
16577
  if (rayDirection.lengthSq() === 0) {
14974
16578
  return [];
14975
16579
  }
14976
- const raycaster = new THREE51__namespace.Raycaster(rayOrigin, rayDirection, near, far);
16580
+ const raycaster = new THREE56__namespace.Raycaster(rayOrigin, rayDirection, near, far);
14977
16581
  const intersections = raycaster.intersectObjects(
14978
16582
  this.scene.children,
14979
16583
  true
@@ -15680,6 +17284,8 @@ exports.ScriptObjectSystem = ScriptObjectSystem;
15680
17284
  exports.TeleportAimGate = TeleportAimGate;
15681
17285
  exports.ThrowVelocityTracker = ThrowVelocityTracker;
15682
17286
  exports.TransformSyncSystem = TransformSyncSystem;
17287
+ exports.UIInteractionSystem = UIInteractionSystem;
17288
+ exports.UIPanelSystem = UIPanelSystem;
15683
17289
  exports.VELOCITY_SAMPLES = VELOCITY_SAMPLES;
15684
17290
  exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
15685
17291
  exports.VideoSystem = VideoSystem;
@@ -15746,6 +17352,11 @@ exports.createSelectableComponent = createSelectableComponent;
15746
17352
  exports.createSelectionTransformStateComponent = createSelectionTransformStateComponent;
15747
17353
  exports.createSpriteComponent = createSpriteComponent;
15748
17354
  exports.createTransformComponent = createTransformComponent;
17355
+ exports.createUIButtonComponent = createUIButtonComponent;
17356
+ exports.createUIPanelComponent = createUIPanelComponent;
17357
+ exports.createUISliderComponent = createUISliderComponent;
17358
+ exports.createUITextComponent = createUITextComponent;
17359
+ exports.createUIToggleComponent = createUIToggleComponent;
15749
17360
  exports.createVRTeleportAreaComponent = createVRTeleportAreaComponent;
15750
17361
  exports.createVideoComponent = createVideoComponent;
15751
17362
  exports.createXRAnchorComponent = createXRAnchorComponent;