@relu-interactives/spatial-ecs 0.8.0 → 0.10.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 (56) hide show
  1. package/dist/{System-8D-XBDKZ.d.cts → System-BldSHxZV.d.cts} +214 -81
  2. package/dist/{System-DPMAW30g.d.ts → System-CADhPyQs.d.ts} +214 -81
  3. package/dist/{XRHandPose-G0Dl-F0w.d.ts → XRHandPose-BNSwd1y9.d.ts} +145 -2
  4. package/dist/{XRHandPose-VQMT1WXj.d.cts → XRHandPose-D3Ab1Dfs.d.cts} +145 -2
  5. package/dist/{chunk-G4XJ53LS.js → chunk-JEPUCLZX.js} +3 -3
  6. package/dist/{chunk-G4XJ53LS.js.map → chunk-JEPUCLZX.js.map} +1 -1
  7. package/dist/{chunk-NNDUOQ7O.js → chunk-JNYPJB55.js} +250 -3
  8. package/dist/chunk-JNYPJB55.js.map +1 -0
  9. package/dist/{chunk-SGF73YTF.js → chunk-NXASMBDS.js} +4 -4
  10. package/dist/{chunk-SGF73YTF.js.map → chunk-NXASMBDS.js.map} +1 -1
  11. package/dist/{chunk-JIJQFW6P.js → chunk-TY3XCHOR.js} +1390 -485
  12. package/dist/chunk-TY3XCHOR.js.map +1 -0
  13. package/dist/{chunk-U727NTWQ.js → chunk-WWP46AEF.js} +26 -4
  14. package/dist/chunk-WWP46AEF.js.map +1 -0
  15. package/dist/components/index.cjs +151 -0
  16. package/dist/components/index.cjs.map +1 -1
  17. package/dist/components/index.d.cts +3 -3
  18. package/dist/components/index.d.ts +3 -3
  19. package/dist/components/index.js +1 -1
  20. package/dist/docs/guide/bone-attachments-and-ik.md +1 -1
  21. package/dist/docs/guide/multiplayer.md +65 -0
  22. package/dist/docs/guide/spatial-ui.md +83 -7
  23. package/dist/factories/index.cjs +151 -0
  24. package/dist/factories/index.cjs.map +1 -1
  25. package/dist/factories/index.d.cts +2 -2
  26. package/dist/factories/index.d.ts +2 -2
  27. package/dist/factories/index.js +3 -3
  28. package/dist/{index-C61n65PE.d.ts → index-6pZO_gV7.d.ts} +27 -2
  29. package/dist/{index-D9DjrUn3.d.cts → index-BXjwEdNq.d.cts} +6 -3
  30. package/dist/{index-C70N4BYG.d.cts → index-BdghvXKE.d.cts} +27 -2
  31. package/dist/{index-DppafVu4.d.ts → index-CVG-hREM.d.ts} +6 -3
  32. package/dist/index.cjs +1800 -626
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +8 -30
  35. package/dist/index.d.ts +8 -30
  36. package/dist/index.js +10 -10
  37. package/dist/network/index.cjs +858 -8
  38. package/dist/network/index.cjs.map +1 -1
  39. package/dist/network/index.d.cts +126 -5
  40. package/dist/network/index.d.ts +126 -5
  41. package/dist/network/index.js +683 -8
  42. package/dist/network/index.js.map +1 -1
  43. package/dist/systems/index.cjs +1702 -528
  44. package/dist/systems/index.cjs.map +1 -1
  45. package/dist/systems/index.d.cts +2 -2
  46. package/dist/systems/index.d.ts +2 -2
  47. package/dist/systems/index.js +4 -4
  48. package/dist/types.d.cts +1 -1
  49. package/dist/types.d.ts +1 -1
  50. package/dist/webgpu/index.d.ts +2 -2
  51. package/dist/worldSnapshotUtils.d.cts +1 -1
  52. package/dist/worldSnapshotUtils.d.ts +1 -1
  53. package/package.json +1 -1
  54. package/dist/chunk-JIJQFW6P.js.map +0 -1
  55. package/dist/chunk-NNDUOQ7O.js.map +0 -1
  56. package/dist/chunk-U727NTWQ.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { getRendererKindOf, readWorldRenderBackend, readWorldRendererKind } from './chunk-OHCNPPMX.js';
2
- import { resolveScriptCtor, Behaviour, normalizeScriptData, defaultModuleLoader, reportScriptError, serializeDataForStorage, toErrorMessage, readScriptPhase, withScriptPhase, mergeDataByCurrentDefaults, resolveInputData, resolvePrefabAssetUrl, reportBehaviourHookError } from './chunk-U727NTWQ.js';
3
- import { createGLTFLoader, SHARED_MODEL_RESOURCE_FLAG, applyTransform, findModelBone } from './chunk-G4XJ53LS.js';
4
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, normalizeEnvironmentLighting, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ScriptComponent, ScriptObjectComponent, AudioComponent, VideoComponent, XRInteractableComponent, XRRigComponent, VRTeleportAreaComponent, ParentId, XREnvironmentComponent, UIButtonComponent, UIToggleComponent, UISliderComponent, Name, UIPanelComponent, UITextComponent, XRAnchorComponent, IKComponent, XRInteractorComponent, XRInteractorSideComponent, handPoseDefinitionFor, getHandPosePreset, XRHandPoseComponent } from './chunk-NNDUOQ7O.js';
2
+ import { resolveScriptCtor, Behaviour, normalizeScriptData, defaultModuleLoader, reportScriptError, serializeDataForStorage, toErrorMessage, readScriptPhase, withScriptPhase, mergeDataByCurrentDefaults, resolveInputData, resolvePrefabAssetUrl, reportBehaviourHookError } from './chunk-WWP46AEF.js';
3
+ import { createGLTFLoader, SHARED_MODEL_RESOURCE_FLAG, applyTransform, findModelBone } from './chunk-JEPUCLZX.js';
4
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, normalizeEnvironmentLighting, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ScriptComponent, ScriptObjectComponent, AudioComponent, VideoComponent, UIPanelComponent, UITextComponent, UIButtonComponent, UIToggleComponent, UISliderComponent, ParentId, XRInteractableComponent, XRRigComponent, VRTeleportAreaComponent, XREnvironmentComponent, Name, resolveChrome, chromeBleed, resolveText, resolveToken, styleAssets, XRAnchorComponent, IKComponent, XRInteractorComponent, XRInteractorSideComponent, handPoseDefinitionFor, getHandPosePreset, XRHandPoseComponent } from './chunk-JNYPJB55.js';
5
5
  import { __name } from './chunk-XE4OXN2W.js';
6
- import * as THREE19 from 'three';
6
+ import * as THREE26 from 'three';
7
7
  import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
8
8
  import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
9
9
  import { XRHandModelFactory } from 'three/examples/jsm/webxr/XRHandModelFactory.js';
@@ -139,7 +139,7 @@ function transitionToClip(runtime, nextClipId, crossFadeDuration) {
139
139
  }
140
140
  __name(transitionToClip, "transitionToClip");
141
141
  function applyActionState(action, component, paused, shouldLoop) {
142
- const loopMode = shouldLoop ? THREE19.LoopRepeat : THREE19.LoopOnce;
142
+ const loopMode = shouldLoop ? THREE26.LoopRepeat : THREE26.LoopOnce;
143
143
  const repetitions = shouldLoop ? Infinity : 1;
144
144
  action.setLoop(loopMode, repetitions);
145
145
  action.clampWhenFinished = !shouldLoop;
@@ -312,7 +312,7 @@ var _AnimationSystem = class _AnimationSystem {
312
312
  if (runtime) {
313
313
  stopAllActions(runtime);
314
314
  }
315
- const mixer = new THREE19.AnimationMixer(root);
315
+ const mixer = new THREE26.AnimationMixer(root);
316
316
  const entries2 = readRuntimeClipEntries(animation);
317
317
  const indexedActions = createActions(mixer, entries2);
318
318
  runtime = {
@@ -438,11 +438,11 @@ function normalizeOrthoBounds(component) {
438
438
  }
439
439
  __name(normalizeOrthoBounds, "normalizeOrthoBounds");
440
440
  function isPerspectiveCamera(camera) {
441
- return camera instanceof THREE19.PerspectiveCamera;
441
+ return camera instanceof THREE26.PerspectiveCamera;
442
442
  }
443
443
  __name(isPerspectiveCamera, "isPerspectiveCamera");
444
444
  function isOrthographicCamera(camera) {
445
- return camera instanceof THREE19.OrthographicCamera;
445
+ return camera instanceof THREE26.OrthographicCamera;
446
446
  }
447
447
  __name(isOrthographicCamera, "isOrthographicCamera");
448
448
  function readWindowAspect() {
@@ -496,7 +496,7 @@ var _CameraSyncSystem = class _CameraSyncSystem {
496
496
  continue;
497
497
  }
498
498
  const camera = objectRef.object;
499
- if (!(camera instanceof THREE19.Camera)) {
499
+ if (!(camera instanceof THREE26.Camera)) {
500
500
  continue;
501
501
  }
502
502
  if (xrCamera !== null && camera === xrCamera) {
@@ -561,8 +561,8 @@ var ObjectVisibilitySystem = _ObjectVisibilitySystem;
561
561
  function getUrlExtension(url) {
562
562
  const path = url.split(/[?#]/)[0] ?? "";
563
563
  const lastSegment = path.split("/").pop() ?? "";
564
- const dot = lastSegment.lastIndexOf(".");
565
- return dot >= 0 ? lastSegment.slice(dot + 1).toLowerCase() : "";
564
+ const dot2 = lastSegment.lastIndexOf(".");
565
+ return dot2 >= 0 ? lastSegment.slice(dot2 + 1).toLowerCase() : "";
566
566
  }
567
567
  __name(getUrlExtension, "getUrlExtension");
568
568
  async function loadEquirectTexture(url) {
@@ -575,10 +575,10 @@ async function loadEquirectTexture(url) {
575
575
  const { EXRLoader } = await import('three/examples/jsm/loaders/EXRLoader.js');
576
576
  texture = await new EXRLoader().loadAsync(url);
577
577
  } else {
578
- texture = await new THREE19.TextureLoader().loadAsync(url);
579
- texture.colorSpace = THREE19.SRGBColorSpace;
578
+ texture = await new THREE26.TextureLoader().loadAsync(url);
579
+ texture.colorSpace = THREE26.SRGBColorSpace;
580
580
  }
581
- texture.mapping = THREE19.EquirectangularReflectionMapping;
581
+ texture.mapping = THREE26.EquirectangularReflectionMapping;
582
582
  texture.needsUpdate = true;
583
583
  return texture;
584
584
  }
@@ -587,13 +587,13 @@ var rendererStates = /* @__PURE__ */ new WeakMap();
587
587
  function toThreeToneMapping(mode) {
588
588
  switch (mode) {
589
589
  case "aces":
590
- return THREE19.ACESFilmicToneMapping;
590
+ return THREE26.ACESFilmicToneMapping;
591
591
  case "agx":
592
- return THREE19.AgXToneMapping;
592
+ return THREE26.AgXToneMapping;
593
593
  case "neutral":
594
- return THREE19.NeutralToneMapping;
594
+ return THREE26.NeutralToneMapping;
595
595
  default:
596
- return THREE19.NoToneMapping;
596
+ return THREE26.NoToneMapping;
597
597
  }
598
598
  }
599
599
  __name(toThreeToneMapping, "toThreeToneMapping");
@@ -614,7 +614,7 @@ function setRendererToneMappingSource(renderer, source, request) {
614
614
  if (request === null) return;
615
615
  state = {
616
616
  baseline: {
617
- toneMapping: target.toneMapping ?? THREE19.NoToneMapping,
617
+ toneMapping: target.toneMapping ?? THREE26.NoToneMapping,
618
618
  exposure: typeof target.toneMappingExposure === "number" ? target.toneMappingExposure : 1
619
619
  },
620
620
  environment: null,
@@ -645,7 +645,7 @@ var MIN_DENSITY = 0;
645
645
  function createGradientTexture(effect, colors) {
646
646
  if (typeof document === "undefined") {
647
647
  const pixel = new Uint8Array([30, 30, 30, 255]);
648
- const fallback = new THREE19.DataTexture(pixel, 1, 1, THREE19.RGBAFormat);
648
+ const fallback = new THREE26.DataTexture(pixel, 1, 1, THREE26.RGBAFormat);
649
649
  fallback.needsUpdate = true;
650
650
  return fallback;
651
651
  }
@@ -654,7 +654,7 @@ function createGradientTexture(effect, colors) {
654
654
  canvas.height = 512;
655
655
  const context = canvas.getContext("2d");
656
656
  if (!context) {
657
- const fallback = new THREE19.CanvasTexture(canvas);
657
+ const fallback = new THREE26.CanvasTexture(canvas);
658
658
  fallback.needsUpdate = true;
659
659
  return fallback;
660
660
  }
@@ -682,8 +682,8 @@ function createGradientTexture(effect, colors) {
682
682
  context.fillRect(0, 0, 512, 512);
683
683
  context.globalCompositeOperation = "source-over";
684
684
  }
685
- const texture = new THREE19.CanvasTexture(canvas);
686
- texture.colorSpace = THREE19.SRGBColorSpace;
685
+ const texture = new THREE26.CanvasTexture(canvas);
686
+ texture.colorSpace = THREE26.SRGBColorSpace;
687
687
  texture.needsUpdate = true;
688
688
  return texture;
689
689
  }
@@ -700,7 +700,7 @@ function readRenderer(world) {
700
700
  __name(readRenderer, "readRenderer");
701
701
  var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
702
702
  constructor(scene) {
703
- this.textureLoader = new THREE19.TextureLoader();
703
+ this.textureLoader = new THREE26.TextureLoader();
704
704
  /** `true` once an Environment entity has been seen (drives the reset path). */
705
705
  this.hasEnvironment = false;
706
706
  this.skyboxSignature = null;
@@ -784,7 +784,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
784
784
  if (skybox.type === "color") {
785
785
  this.disposeActiveTexture();
786
786
  this.pendingImageUrl = null;
787
- this.setSkyboxBackground(new THREE19.Color(skybox.color || "#1e1e1e"));
787
+ this.setSkyboxBackground(new THREE26.Color(skybox.color || "#1e1e1e"));
788
788
  return;
789
789
  }
790
790
  if (skybox.type === "gradient") {
@@ -819,7 +819,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
819
819
  return;
820
820
  }
821
821
  if (!equirect) {
822
- texture.colorSpace = THREE19.SRGBColorSpace;
822
+ texture.colorSpace = THREE26.SRGBColorSpace;
823
823
  }
824
824
  this.activeTexture = texture;
825
825
  this.setSkyboxBackground(texture);
@@ -847,16 +847,16 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
847
847
  this.scene.fog = null;
848
848
  return;
849
849
  }
850
- const fogColor = new THREE19.Color(fog.color || "#1e1e1e");
850
+ const fogColor = new THREE26.Color(fog.color || "#1e1e1e");
851
851
  if (fog.type === "linear") {
852
852
  const near = Number.isFinite(fog.linear.near) ? fog.linear.near : 1;
853
853
  const farCandidate = Number.isFinite(fog.linear.far) ? fog.linear.far : near + 10;
854
854
  const far = Math.max(near + MIN_FOG_DELTA, farCandidate);
855
- this.scene.fog = new THREE19.Fog(fogColor, near, far);
855
+ this.scene.fog = new THREE26.Fog(fogColor, near, far);
856
856
  return;
857
857
  }
858
858
  const density = Number.isFinite(fog.exponential.density) ? Math.max(MIN_DENSITY, fog.exponential.density) : 2e-3;
859
- this.scene.fog = new THREE19.FogExp2(fogColor, density);
859
+ this.scene.fog = new THREE26.FogExp2(fogColor, density);
860
860
  }
861
861
  // ── Image-based lighting ────────────────────────────────────────────────
862
862
  disposeEnvResources() {
@@ -918,7 +918,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
918
918
  if (!renderer || getRendererKindOf(renderer) === "webgpu") return null;
919
919
  let generator = null;
920
920
  try {
921
- generator = new THREE19.PMREMGenerator(renderer);
921
+ generator = new THREE26.PMREMGenerator(renderer);
922
922
  return generator.fromEquirectangular(texture);
923
923
  } catch (error) {
924
924
  console.warn(
@@ -1213,7 +1213,7 @@ function computeColliderState(colliderValues, object) {
1213
1213
  if (!object) {
1214
1214
  return colliderValues;
1215
1215
  }
1216
- const scale = new THREE19.Vector3();
1216
+ const scale = new THREE26.Vector3();
1217
1217
  object.getWorldScale(scale);
1218
1218
  return {
1219
1219
  ...colliderValues,
@@ -1264,8 +1264,8 @@ function createColliderDesc(RAPIER, values, triMeshData) {
1264
1264
  values.offsetPosition.y,
1265
1265
  values.offsetPosition.z
1266
1266
  );
1267
- const offsetQuaternion = new THREE19.Quaternion().setFromEuler(
1268
- new THREE19.Euler(
1267
+ const offsetQuaternion = new THREE26.Quaternion().setFromEuler(
1268
+ new THREE26.Euler(
1269
1269
  values.offsetRotation.x,
1270
1270
  values.offsetRotation.y,
1271
1271
  values.offsetRotation.z
@@ -1280,17 +1280,17 @@ function collectTriMeshParts(object) {
1280
1280
  return null;
1281
1281
  }
1282
1282
  object.updateWorldMatrix(true, true);
1283
- const worldScale = new THREE19.Vector3();
1283
+ const worldScale = new THREE26.Vector3();
1284
1284
  object.getWorldScale(worldScale);
1285
1285
  const scale = {
1286
1286
  x: Math.abs(worldScale.x) || 1,
1287
1287
  y: Math.abs(worldScale.y) || 1,
1288
1288
  z: Math.abs(worldScale.z) || 1
1289
1289
  };
1290
- const worldToObject = new THREE19.Matrix4().copy(object.matrixWorld).invert();
1290
+ const worldToObject = new THREE26.Matrix4().copy(object.matrixWorld).invert();
1291
1291
  const parts = [];
1292
1292
  object.traverse((child) => {
1293
- if (!(child instanceof THREE19.Mesh) || !child.geometry) {
1293
+ if (!(child instanceof THREE26.Mesh) || !child.geometry) {
1294
1294
  return;
1295
1295
  }
1296
1296
  const geometry = child.geometry;
@@ -1307,7 +1307,7 @@ function collectTriMeshParts(object) {
1307
1307
  if (indexCount < 3) {
1308
1308
  return;
1309
1309
  }
1310
- const relativeMatrix = new THREE19.Matrix4().multiplyMatrices(
1310
+ const relativeMatrix = new THREE26.Matrix4().multiplyMatrices(
1311
1311
  worldToObject,
1312
1312
  child.matrixWorld
1313
1313
  );
@@ -1348,7 +1348,7 @@ function createTriMeshData(collected) {
1348
1348
  }
1349
1349
  const vertices = new Float32Array(vertexTotal * 3);
1350
1350
  const indices = new Uint32Array(indexTotal);
1351
- const point = new THREE19.Vector3();
1351
+ const point = new THREE26.Vector3();
1352
1352
  let vertexOffset = 0;
1353
1353
  let indexOffset = 0;
1354
1354
  for (const part of parts) {
@@ -1380,8 +1380,8 @@ function createTriMeshData(collected) {
1380
1380
  }
1381
1381
  __name(createTriMeshData, "createTriMeshData");
1382
1382
  function syncBodyFromTransform(body, transform) {
1383
- const quaternion = new THREE19.Quaternion().setFromEuler(
1384
- new THREE19.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1383
+ const quaternion = new THREE26.Quaternion().setFromEuler(
1384
+ new THREE26.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1385
1385
  );
1386
1386
  body.setTranslation(
1387
1387
  {
@@ -1408,8 +1408,8 @@ function syncTransformFromBody(transform, body) {
1408
1408
  transform.position.x = position.x;
1409
1409
  transform.position.y = position.y;
1410
1410
  transform.position.z = position.z;
1411
- const euler = new THREE19.Euler().setFromQuaternion(
1412
- new THREE19.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1411
+ const euler = new THREE26.Euler().setFromQuaternion(
1412
+ new THREE26.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1413
1413
  );
1414
1414
  transform.rotation.x = euler.x;
1415
1415
  transform.rotation.y = euler.y;
@@ -1522,8 +1522,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1522
1522
  transform.position.y,
1523
1523
  transform.position.z
1524
1524
  );
1525
- const quaternion = new THREE19.Quaternion().setFromEuler(
1526
- new THREE19.Euler(
1525
+ const quaternion = new THREE26.Quaternion().setFromEuler(
1526
+ new THREE26.Euler(
1527
1527
  transform.rotation.x,
1528
1528
  transform.rotation.y,
1529
1529
  transform.rotation.z
@@ -2254,7 +2254,7 @@ function applyLightValues(lightComponent, rendererKind) {
2254
2254
  values.castShadow
2255
2255
  );
2256
2256
  }
2257
- if (light instanceof THREE19.PointLight) {
2257
+ if (light instanceof THREE26.PointLight) {
2258
2258
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
2259
2259
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
2260
2260
  }
@@ -2262,7 +2262,7 @@ function applyLightValues(lightComponent, rendererKind) {
2262
2262
  light.decay = Math.max(0, values.decay);
2263
2263
  }
2264
2264
  }
2265
- if (light instanceof THREE19.SpotLight) {
2265
+ if (light instanceof THREE26.SpotLight) {
2266
2266
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
2267
2267
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
2268
2268
  }
@@ -2276,7 +2276,7 @@ function applyLightValues(lightComponent, rendererKind) {
2276
2276
  light.penumbra = Math.max(0, Math.min(1, values.penumbra));
2277
2277
  }
2278
2278
  }
2279
- if (light instanceof THREE19.RectAreaLight) {
2279
+ if (light instanceof THREE26.RectAreaLight) {
2280
2280
  if (typeof values.width === "number" && Number.isFinite(values.width)) {
2281
2281
  light.width = Math.max(MIN_AREA_SIZE, values.width);
2282
2282
  }
@@ -2340,15 +2340,15 @@ function resolveMaterialPropertyKey(key) {
2340
2340
  __name(resolveMaterialPropertyKey, "resolveMaterialPropertyKey");
2341
2341
  function createMaterialByType(type) {
2342
2342
  if (type === "basic") {
2343
- return new THREE19.MeshBasicMaterial();
2343
+ return new THREE26.MeshBasicMaterial();
2344
2344
  }
2345
2345
  if (type === "phong") {
2346
- return new THREE19.MeshPhongMaterial();
2346
+ return new THREE26.MeshPhongMaterial();
2347
2347
  }
2348
2348
  if (type === "physical") {
2349
- return new THREE19.MeshPhysicalMaterial();
2349
+ return new THREE26.MeshPhysicalMaterial();
2350
2350
  }
2351
- return new THREE19.MeshStandardMaterial();
2351
+ return new THREE26.MeshStandardMaterial();
2352
2352
  }
2353
2353
  __name(createMaterialByType, "createMaterialByType");
2354
2354
  function replaceMaterialAcrossObject(object, previous, next) {
@@ -2407,12 +2407,12 @@ function applyTextureFiltering(material, filtering) {
2407
2407
  const target = material;
2408
2408
  for (const slot of materialTextureSlots) {
2409
2409
  const texture = target[slot];
2410
- if (!texture || !(texture instanceof THREE19.Texture)) {
2410
+ if (!texture || !(texture instanceof THREE26.Texture)) {
2411
2411
  continue;
2412
2412
  }
2413
2413
  const useMipmaps = texture.generateMipmaps !== false;
2414
- texture.magFilter = sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2415
- texture.minFilter = useMipmaps ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2414
+ texture.magFilter = sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2415
+ texture.minFilter = useMipmaps ? sharp ? THREE26.NearestMipmapNearestFilter : THREE26.LinearMipmapLinearFilter : sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2416
2416
  texture.needsUpdate = true;
2417
2417
  }
2418
2418
  material.needsUpdate = true;
@@ -2422,12 +2422,12 @@ function setTextureMipmaps(material, enabled) {
2422
2422
  const target = material;
2423
2423
  for (const slot of materialTextureSlots) {
2424
2424
  const texture = target[slot];
2425
- if (!texture || !(texture instanceof THREE19.Texture)) {
2425
+ if (!texture || !(texture instanceof THREE26.Texture)) {
2426
2426
  continue;
2427
2427
  }
2428
2428
  texture.generateMipmaps = enabled;
2429
- const sharp = texture.magFilter === THREE19.NearestFilter;
2430
- texture.minFilter = enabled ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2429
+ const sharp = texture.magFilter === THREE26.NearestFilter;
2430
+ texture.minFilter = enabled ? sharp ? THREE26.NearestMipmapNearestFilter : THREE26.LinearMipmapLinearFilter : sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2431
2431
  texture.needsUpdate = true;
2432
2432
  }
2433
2433
  material.needsUpdate = true;
@@ -2438,7 +2438,7 @@ function assignMaterialTexture(material, rawKey, url, textureCache) {
2438
2438
  const key = resolveMaterialPropertyKey(rawKey);
2439
2439
  let texture = textureCache.get(url);
2440
2440
  if (!texture) {
2441
- texture = new THREE19.TextureLoader().load(url);
2441
+ texture = new THREE26.TextureLoader().load(url);
2442
2442
  textureCache.set(url, texture);
2443
2443
  }
2444
2444
  target[key] = texture;
@@ -2476,7 +2476,7 @@ function applyMaterialState(material, value, textureCache) {
2476
2476
  }
2477
2477
  target.side = value.side;
2478
2478
  target.blending = value.blending;
2479
- target.premultipliedAlpha = value.blending === THREE19.MultiplyBlending || value.blending === THREE19.SubtractiveBlending;
2479
+ target.premultipliedAlpha = value.blending === THREE26.MultiplyBlending || value.blending === THREE26.SubtractiveBlending;
2480
2480
  target.depthTest = value.depthTest;
2481
2481
  target.depthWrite = value.depthWrite;
2482
2482
  target.wireframe = value.wireframe;
@@ -2637,12 +2637,12 @@ function resolvePrimaryMesh(object) {
2637
2637
  if (!object) {
2638
2638
  return null;
2639
2639
  }
2640
- if (object instanceof THREE19.Mesh) {
2640
+ if (object instanceof THREE26.Mesh) {
2641
2641
  return object;
2642
2642
  }
2643
2643
  let found = null;
2644
2644
  object.traverse((child) => {
2645
- if (!found && child instanceof THREE19.Mesh) {
2645
+ if (!found && child instanceof THREE26.Mesh) {
2646
2646
  found = child;
2647
2647
  }
2648
2648
  });
@@ -2663,22 +2663,22 @@ function applyGeometry(component) {
2663
2663
  switch (component.kind) {
2664
2664
  case "cube": {
2665
2665
  const params = sanitized;
2666
- nextGeometry = new THREE19.BoxGeometry(params.width, params.height, params.depth);
2666
+ nextGeometry = new THREE26.BoxGeometry(params.width, params.height, params.depth);
2667
2667
  break;
2668
2668
  }
2669
2669
  case "plane": {
2670
2670
  const params = sanitized;
2671
- nextGeometry = new THREE19.PlaneGeometry(params.width, params.height);
2671
+ nextGeometry = new THREE26.PlaneGeometry(params.width, params.height);
2672
2672
  break;
2673
2673
  }
2674
2674
  case "sphere": {
2675
2675
  const params = sanitized;
2676
- nextGeometry = new THREE19.SphereGeometry(params.radius, 24, 24);
2676
+ nextGeometry = new THREE26.SphereGeometry(params.radius, 24, 24);
2677
2677
  break;
2678
2678
  }
2679
2679
  case "cylinder": {
2680
2680
  const params = sanitized;
2681
- nextGeometry = new THREE19.CylinderGeometry(
2681
+ nextGeometry = new THREE26.CylinderGeometry(
2682
2682
  params.radius,
2683
2683
  params.radius,
2684
2684
  params.height,
@@ -2688,7 +2688,7 @@ function applyGeometry(component) {
2688
2688
  }
2689
2689
  case "capsule": {
2690
2690
  const params = sanitized;
2691
- nextGeometry = new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2691
+ nextGeometry = new THREE26.CapsuleGeometry(params.radius, params.height, 4, 8);
2692
2692
  break;
2693
2693
  }
2694
2694
  default:
@@ -2704,7 +2704,7 @@ function applyShadowFlags(component) {
2704
2704
  return;
2705
2705
  }
2706
2706
  component.object.traverse((child) => {
2707
- if (child instanceof THREE19.Mesh) {
2707
+ if (child instanceof THREE26.Mesh) {
2708
2708
  child.castShadow = component.castShadow;
2709
2709
  child.receiveShadow = component.receiveShadow;
2710
2710
  }
@@ -2780,35 +2780,35 @@ function buildGeometry(component) {
2780
2780
  switch (component.meshKind) {
2781
2781
  case "cube": {
2782
2782
  const params = p;
2783
- return new THREE19.BoxGeometry(params.width, params.height, params.depth);
2783
+ return new THREE26.BoxGeometry(params.width, params.height, params.depth);
2784
2784
  }
2785
2785
  case "plane": {
2786
2786
  const params = p;
2787
- return new THREE19.PlaneGeometry(params.width, params.height);
2787
+ return new THREE26.PlaneGeometry(params.width, params.height);
2788
2788
  }
2789
2789
  case "sphere": {
2790
2790
  const params = p;
2791
- return new THREE19.SphereGeometry(params.radius, 24, 24);
2791
+ return new THREE26.SphereGeometry(params.radius, 24, 24);
2792
2792
  }
2793
2793
  case "cylinder": {
2794
2794
  const params = p;
2795
- return new THREE19.CylinderGeometry(params.radius, params.radius, params.height, 20);
2795
+ return new THREE26.CylinderGeometry(params.radius, params.radius, params.height, 20);
2796
2796
  }
2797
2797
  case "capsule": {
2798
2798
  const params = p;
2799
- return new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2799
+ return new THREE26.CapsuleGeometry(params.radius, params.height, 4, 8);
2800
2800
  }
2801
2801
  default:
2802
- return new THREE19.BoxGeometry(1, 1, 1);
2802
+ return new THREE26.BoxGeometry(1, 1, 1);
2803
2803
  }
2804
2804
  }
2805
2805
  __name(buildGeometry, "buildGeometry");
2806
- var _position = new THREE19.Vector3();
2807
- var _quaternion = new THREE19.Quaternion();
2808
- var _euler = new THREE19.Euler();
2809
- var _scale = new THREE19.Vector3();
2810
- var _matrix = new THREE19.Matrix4();
2811
- var _modelMatrix = new THREE19.Matrix4();
2806
+ var _position = new THREE26.Vector3();
2807
+ var _quaternion = new THREE26.Quaternion();
2808
+ var _euler = new THREE26.Euler();
2809
+ var _scale = new THREE26.Vector3();
2810
+ var _matrix = new THREE26.Matrix4();
2811
+ var _modelMatrix = new THREE26.Matrix4();
2812
2812
  function instanceTransformFromComponent(transform, out) {
2813
2813
  _position.set(transform.position.x, transform.position.y, transform.position.z);
2814
2814
  _euler.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, "XYZ");
@@ -2890,9 +2890,9 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2890
2890
  const instanceCount = comp.instanceEntityIds.length;
2891
2891
  const geometrySignature = comp.meshKind === "model" ? `model:${comp.modelPath}:${instanceCount}` : `${comp.meshKind}:${JSON.stringify(comp.meshParameters)}:${instanceCount}`;
2892
2892
  if (comp.instancedMesh === null) {
2893
- const tracked = this.trackedMeshes.get(entityId);
2894
- if (tracked) {
2895
- comp.instancedMesh = tracked;
2893
+ const tracked2 = this.trackedMeshes.get(entityId);
2894
+ if (tracked2) {
2895
+ comp.instancedMesh = tracked2;
2896
2896
  }
2897
2897
  }
2898
2898
  const pendingLoadSignature = this.loadingSignatures.get(entityId);
@@ -2961,7 +2961,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2961
2961
  const sourceMaterial = Array.isArray(meshChild.material) ? meshChild.material.map((m) => m.clone()) : meshChild.material.clone();
2962
2962
  meshChild.updateWorldMatrix(true, false);
2963
2963
  const localMatrix = meshChild.matrixWorld.clone();
2964
- const instancedMesh2 = new THREE19.InstancedMesh(
2964
+ const instancedMesh2 = new THREE26.InstancedMesh(
2965
2965
  geometry2,
2966
2966
  Array.isArray(sourceMaterial) ? sourceMaterial : sourceMaterial,
2967
2967
  count2
@@ -2986,7 +2986,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2986
2986
  this.loadingSignatures.delete(targetEntityId);
2987
2987
  return;
2988
2988
  }
2989
- const group = new THREE19.Group();
2989
+ const group = new THREE26.Group();
2990
2990
  for (const entry of entries) {
2991
2991
  group.add(entry.instancedMesh);
2992
2992
  }
@@ -3004,8 +3004,8 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
3004
3004
  }
3005
3005
  const count = instanceCount;
3006
3006
  const geometry = buildGeometry(comp);
3007
- const material = new THREE19.MeshStandardMaterial({ color: "#ffffff" });
3008
- const instancedMesh = new THREE19.InstancedMesh(geometry, material, count);
3007
+ const material = new THREE26.MeshStandardMaterial({ color: "#ffffff" });
3008
+ const instancedMesh = new THREE26.InstancedMesh(geometry, material, count);
3009
3009
  instancedMesh.castShadow = comp.castShadow;
3010
3010
  instancedMesh.receiveShadow = comp.receiveShadow;
3011
3011
  for (let i = 0; i < count; i++) {
@@ -3213,8 +3213,8 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
3213
3213
  const retryDelay = options?.errorRetryDelay;
3214
3214
  this.errorRetryDelay = typeof retryDelay === "number" && Number.isFinite(retryDelay) ? Math.max(0, retryDelay) : DEFAULT_SCRIPT_ERROR_RETRY_DELAY;
3215
3215
  this.selectionCtx = selectionContext ?? null;
3216
- this.raycaster = new THREE19.Raycaster();
3217
- this.ndcPointer = new THREE19.Vector2();
3216
+ this.raycaster = new THREE26.Raycaster();
3217
+ this.ndcPointer = new THREE26.Vector2();
3218
3218
  if (this.selectionCtx) {
3219
3219
  this.selectionCtx.domElement.addEventListener("pointerdown", this.onPointerDown);
3220
3220
  this.selectionCtx.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -3658,7 +3658,7 @@ function disposeBuiltObject(root) {
3658
3658
  for (const mat of materials) {
3659
3659
  if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
3660
3660
  for (const value of Object.values(mat)) {
3661
- if (value instanceof THREE19.Texture) value.dispose();
3661
+ if (value instanceof THREE26.Texture) value.dispose();
3662
3662
  }
3663
3663
  mat.dispose?.();
3664
3664
  }
@@ -4259,7 +4259,7 @@ var _AudioSystem = class _AudioSystem {
4259
4259
  if (!prev || prev.volume !== opts.volume) {
4260
4260
  sound.setVolume(opts.volume);
4261
4261
  }
4262
- if (opts.positional && sound instanceof THREE19.PositionalAudio) {
4262
+ if (opts.positional && sound instanceof THREE26.PositionalAudio) {
4263
4263
  if (!prev || prev.refDistance !== opts.refDistance) {
4264
4264
  sound.setRefDistance(opts.refDistance);
4265
4265
  }
@@ -4399,7 +4399,7 @@ var _VideoSystem = class _VideoSystem {
4399
4399
  spill: opts.spill
4400
4400
  });
4401
4401
  }
4402
- if (opts.positional && component.audioNode instanceof THREE19.PositionalAudio) {
4402
+ if (opts.positional && component.audioNode instanceof THREE26.PositionalAudio) {
4403
4403
  const posAudio = component.audioNode;
4404
4404
  if (!prev || prev.refDistance !== opts.refDistance) {
4405
4405
  posAudio.setRefDistance(opts.refDistance);
@@ -4439,8 +4439,8 @@ function blankState(source) {
4439
4439
  handedness: handednessOf(source),
4440
4440
  kind: kindOf(source),
4441
4441
  connected: true,
4442
- rayOrigin: new THREE19.Vector3(),
4443
- rayDirection: new THREE19.Vector3(0, 0, -1),
4442
+ rayOrigin: new THREE26.Vector3(),
4443
+ rayDirection: new THREE26.Vector3(0, 0, -1),
4444
4444
  gripPosition: null,
4445
4445
  gripQuaternion: null,
4446
4446
  select: false,
@@ -4466,7 +4466,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4466
4466
  }
4467
4467
  if (spaces.length === 0) return null;
4468
4468
  const target = existing ?? {};
4469
- const matrix = new THREE19.Matrix4();
4469
+ const matrix = new THREE26.Matrix4();
4470
4470
  const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
4471
4471
  const fillPoses = frame.fillPoses;
4472
4472
  if (typeof fillPoses === "function") {
@@ -4479,7 +4479,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4479
4479
  if (filled) {
4480
4480
  for (let i = 0; i < names.length; i += 1) {
4481
4481
  matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
4482
- const vector = target[names[i]] ?? new THREE19.Vector3();
4482
+ const vector = target[names[i]] ?? new THREE26.Vector3();
4483
4483
  vector.setFromMatrixPosition(matrix);
4484
4484
  target[names[i]] = vector;
4485
4485
  }
@@ -4497,7 +4497,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4497
4497
  continue;
4498
4498
  }
4499
4499
  matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
4500
- const vector = target[names[i]] ?? new THREE19.Vector3();
4500
+ const vector = target[names[i]] ?? new THREE26.Vector3();
4501
4501
  vector.setFromMatrixPosition(matrix);
4502
4502
  target[names[i]] = vector;
4503
4503
  anyTracked = true;
@@ -4509,7 +4509,7 @@ var _XRInputSystem = class _XRInputSystem {
4509
4509
  constructor() {
4510
4510
  this.records = /* @__PURE__ */ new Map();
4511
4511
  /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
4512
- this.referenceToWorld = new THREE19.Matrix4();
4512
+ this.referenceToWorld = new THREE26.Matrix4();
4513
4513
  this.session = null;
4514
4514
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
4515
4515
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -4607,19 +4607,19 @@ var _XRInputSystem = class _XRInputSystem {
4607
4607
  if (frame && referenceSpace && source.targetRaySpace) {
4608
4608
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
4609
4609
  if (pose) {
4610
- const m = new THREE19.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
4610
+ const m = new THREE26.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
4611
4611
  state.rayOrigin.setFromMatrixPosition(m);
4612
4612
  state.rayDirection.set(0, 0, -1).applyQuaternion(
4613
- new THREE19.Quaternion().setFromRotationMatrix(m)
4613
+ new THREE26.Quaternion().setFromRotationMatrix(m)
4614
4614
  ).normalize();
4615
4615
  }
4616
4616
  }
4617
4617
  if (frame && referenceSpace && source.gripSpace) {
4618
4618
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
4619
4619
  if (gripPose) {
4620
- const m = new THREE19.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
4621
- state.gripPosition ?? (state.gripPosition = new THREE19.Vector3());
4622
- state.gripQuaternion ?? (state.gripQuaternion = new THREE19.Quaternion());
4620
+ const m = new THREE26.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
4621
+ state.gripPosition ?? (state.gripPosition = new THREE26.Vector3());
4622
+ state.gripQuaternion ?? (state.gripQuaternion = new THREE26.Quaternion());
4623
4623
  state.gripPosition.setFromMatrixPosition(m);
4624
4624
  state.gripQuaternion.setFromRotationMatrix(m);
4625
4625
  }
@@ -4721,6 +4721,51 @@ var _XRInputSystem = class _XRInputSystem {
4721
4721
  };
4722
4722
  __name(_XRInputSystem, "XRInputSystem");
4723
4723
  var XRInputSystem = _XRInputSystem;
4724
+
4725
+ // core/ui/uiWidgetTree.ts
4726
+ var UI_PART_KEY = "__uiPart";
4727
+ function applyPartColor(mesh, tag) {
4728
+ const material = mesh.material;
4729
+ material.color.copy(tag.baseColor).multiplyScalar(tag.premultiplied ? tag.tint * tag.opacity : tag.tint);
4730
+ material.opacity = tag.opacity;
4731
+ }
4732
+ __name(applyPartColor, "applyPartColor");
4733
+ function isUiWidget(world, entityId) {
4734
+ return !!world.getComponent(entityId, UIPanelComponent) || !!world.getComponent(entityId, UITextComponent) || !!world.getComponent(entityId, UIButtonComponent) || !!world.getComponent(entityId, UIToggleComponent) || !!world.getComponent(entityId, UISliderComponent);
4735
+ }
4736
+ __name(isUiWidget, "isUiWidget");
4737
+ function isShownInScene(world, entityId) {
4738
+ let object = world.getComponent(entityId, Object3DRef)?.object ?? null;
4739
+ while (object) {
4740
+ if (!object.visible) return false;
4741
+ object = object.parent;
4742
+ }
4743
+ return true;
4744
+ }
4745
+ __name(isShownInScene, "isShownInScene");
4746
+ function nearestWidgetAncestor(world, entityId) {
4747
+ const visited = /* @__PURE__ */ new Set([entityId]);
4748
+ let current = entityId;
4749
+ for (; ; ) {
4750
+ const parentId = world.getComponent(current, ParentId)?.value;
4751
+ if (typeof parentId !== "number" || visited.has(parentId)) return null;
4752
+ if (isUiWidget(world, parentId)) return parentId;
4753
+ visited.add(parentId);
4754
+ current = parentId;
4755
+ }
4756
+ }
4757
+ __name(nearestWidgetAncestor, "nearestWidgetAncestor");
4758
+ function widgetTreeRoot(world, entityId) {
4759
+ const visited = /* @__PURE__ */ new Set([entityId]);
4760
+ let root = entityId;
4761
+ for (; ; ) {
4762
+ const parent = nearestWidgetAncestor(world, root);
4763
+ if (parent === null || visited.has(parent)) return root;
4764
+ visited.add(parent);
4765
+ root = parent;
4766
+ }
4767
+ }
4768
+ __name(widgetTreeRoot, "widgetTreeRoot");
4724
4769
  var VELOCITY_SAMPLES = 5;
4725
4770
  var MAX_SAMPLE_AGE_SECONDS = 0.2;
4726
4771
  var MAX_PLAUSIBLE_SPEED = 20;
@@ -4747,15 +4792,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
4747
4792
  const fresh = this.samples.filter(
4748
4793
  (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
4749
4794
  );
4750
- if (fresh.length < 2) return new THREE19.Vector3();
4751
- let best = new THREE19.Vector3();
4795
+ if (fresh.length < 2) return new THREE26.Vector3();
4796
+ let best = new THREE26.Vector3();
4752
4797
  let bestSpeed = 0;
4753
4798
  for (let i = 1; i < fresh.length; i += 1) {
4754
4799
  const previous = fresh[i - 1];
4755
4800
  const current = fresh[i];
4756
4801
  const dt = current.time - previous.time;
4757
4802
  if (dt <= 1e-5) continue;
4758
- const velocity = new THREE19.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
4803
+ const velocity = new THREE26.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
4759
4804
  const speed = velocity.length();
4760
4805
  if (speed > MAX_PLAUSIBLE_SPEED) continue;
4761
4806
  if (speed > bestSpeed) {
@@ -4772,15 +4817,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
4772
4817
  __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
4773
4818
  var ThrowVelocityTracker = _ThrowVelocityTracker;
4774
4819
  function angularVelocityFrom(previous, current, deltaTime) {
4775
- if (deltaTime <= 1e-5) return new THREE19.Vector3();
4820
+ if (deltaTime <= 1e-5) return new THREE26.Vector3();
4776
4821
  const delta = current.clone().multiply(previous.clone().invert());
4777
4822
  if (delta.w < 0) {
4778
4823
  delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
4779
4824
  }
4780
- const angle = 2 * Math.acos(THREE19.MathUtils.clamp(delta.w, -1, 1));
4825
+ const angle = 2 * Math.acos(THREE26.MathUtils.clamp(delta.w, -1, 1));
4781
4826
  const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
4782
- if (sinHalf < 1e-6 || angle < 1e-6) return new THREE19.Vector3();
4783
- return new THREE19.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
4827
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE26.Vector3();
4828
+ return new THREE26.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
4784
4829
  }
4785
4830
  __name(angularVelocityFrom, "angularVelocityFrom");
4786
4831
  var MIN_HAND_SEPARATION = 0.06;
@@ -4788,7 +4833,7 @@ var MIN_ANCHOR_SEPARATION = 1e-3;
4788
4833
  var SCALE_DEADZONE = 0.05;
4789
4834
  function solveTwoHandTransform(primary, secondary, basis, options) {
4790
4835
  const separation = primary.position.distanceTo(secondary.position);
4791
- const anchorDelta = new THREE19.Vector3().subVectors(
4836
+ const anchorDelta = new THREE26.Vector3().subVectors(
4792
4837
  basis.secondaryLocal,
4793
4838
  basis.primaryLocal
4794
4839
  );
@@ -4812,8 +4857,8 @@ function solveTwoHandTransform(primary, secondary, basis, options) {
4812
4857
  } else {
4813
4858
  const axisLocal = anchorDelta.clone().normalize();
4814
4859
  const currentAxis = axisLocal.clone().applyQuaternion(base).normalize();
4815
- const desiredAxis = new THREE19.Vector3().subVectors(secondary.position, primary.position).normalize();
4816
- const correction = new THREE19.Quaternion().setFromUnitVectors(
4860
+ const desiredAxis = new THREE26.Vector3().subVectors(secondary.position, primary.position).normalize();
4861
+ const correction = new THREE26.Quaternion().setFromUnitVectors(
4817
4862
  currentAxis,
4818
4863
  desiredAxis
4819
4864
  );
@@ -4828,11 +4873,11 @@ __name(solveTwoHandTransform, "solveTwoHandTransform");
4828
4873
  // core/systems/XRInteractionSystem.ts
4829
4874
  var RAYLESS_HOLD_DISTANCE = 0.5;
4830
4875
  function quatFromEulerVec(r) {
4831
- return new THREE19.Quaternion().setFromEuler(new THREE19.Euler(r.x, r.y, r.z));
4876
+ return new THREE26.Quaternion().setFromEuler(new THREE26.Euler(r.x, r.y, r.z));
4832
4877
  }
4833
4878
  __name(quatFromEulerVec, "quatFromEulerVec");
4834
4879
  function writeEuler(target, q) {
4835
- const euler = new THREE19.Euler().setFromQuaternion(q);
4880
+ const euler = new THREE26.Euler().setFromQuaternion(q);
4836
4881
  target.x = euler.x;
4837
4882
  target.y = euler.y;
4838
4883
  target.z = euler.z;
@@ -4840,7 +4885,7 @@ function writeEuler(target, q) {
4840
4885
  __name(writeEuler, "writeEuler");
4841
4886
  var _XRInteractionSystem = class _XRInteractionSystem {
4842
4887
  constructor(inputSystem) {
4843
- this.raycaster = new THREE19.Raycaster();
4888
+ this.raycaster = new THREE26.Raycaster();
4844
4889
  this.grabs = /* @__PURE__ */ new Map();
4845
4890
  this.hoveredEntityId = null;
4846
4891
  /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
@@ -4951,7 +4996,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
4951
4996
  const hand = this.handPosition(input);
4952
4997
  const handQuaternion = this.handQuaternion(input);
4953
4998
  const objectQuaternion = quatFromEulerVec(transform.rotation);
4954
- record.offset = new THREE19.Vector3(
4999
+ record.offset = new THREE26.Vector3(
4955
5000
  transform.position.x - hand.x,
4956
5001
  transform.position.y - hand.y,
4957
5002
  transform.position.z - hand.z
@@ -4983,7 +5028,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
4983
5028
  primary.record.twoHand = {
4984
5029
  primaryLocal,
4985
5030
  secondaryLocal,
4986
- initialScale: new THREE19.Vector3(
5031
+ initialScale: new THREE26.Vector3(
4987
5032
  transform.scale.x,
4988
5033
  transform.scale.y,
4989
5034
  transform.scale.z
@@ -5004,7 +5049,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5004
5049
  /** World point → the object's UNSCALED local space. */
5005
5050
  objectLocalPoint(transform, worldPoint) {
5006
5051
  const local = worldPoint.clone().sub(
5007
- new THREE19.Vector3(
5052
+ new THREE26.Vector3(
5008
5053
  transform.position.x,
5009
5054
  transform.position.y,
5010
5055
  transform.position.z
@@ -5062,7 +5107,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5062
5107
  );
5063
5108
  if (!interactable2) continue;
5064
5109
  if (interactable2.grabReach === "distance") continue;
5065
- const position = new THREE19.Vector3();
5110
+ const position = new THREE26.Vector3();
5066
5111
  entry.object.getWorldPosition(position);
5067
5112
  const distance = position.distanceTo(hand);
5068
5113
  if (distance <= interactable2.grabRadius && distance < nearestDistance) {
@@ -5094,6 +5139,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5094
5139
  const interactable = world.getComponent(entityId, XRInteractableComponent);
5095
5140
  if (!interactable?.enabled) continue;
5096
5141
  if (!world.isEntityVisible(entityId)) continue;
5142
+ if (!isShownInScene(world, entityId)) continue;
5097
5143
  const object = world.getComponent(entityId, Object3DRef)?.object;
5098
5144
  if (object) {
5099
5145
  out.push({ entityId, object, pointable: interactable.pointable });
@@ -5134,13 +5180,13 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5134
5180
  this.holdersOf(entityId).map((holder) => holder.record.attachPointId).filter((id) => id !== null)
5135
5181
  );
5136
5182
  const hand = this.handPosition(input);
5137
- const objectPosition = new THREE19.Vector3(
5183
+ const objectPosition = new THREE26.Vector3(
5138
5184
  transform.position.x,
5139
5185
  transform.position.y,
5140
5186
  transform.position.z
5141
5187
  );
5142
5188
  const objectQuaternion = quatFromEulerVec(transform.rotation);
5143
- const scale = new THREE19.Vector3(
5189
+ const scale = new THREE26.Vector3(
5144
5190
  transform.scale.x,
5145
5191
  transform.scale.y,
5146
5192
  transform.scale.z
@@ -5151,7 +5197,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5151
5197
  if (taken.has(point.id)) continue;
5152
5198
  if (point.hand !== "any" && point.hand !== input.handedness) continue;
5153
5199
  if (point.role !== "any" && point.role !== role) continue;
5154
- const world3 = new THREE19.Vector3(
5200
+ const world3 = new THREE26.Vector3(
5155
5201
  point.position.x,
5156
5202
  point.position.y,
5157
5203
  point.position.z
@@ -5174,21 +5220,21 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5174
5220
  let offset;
5175
5221
  let remoteDistance = 0;
5176
5222
  if (remote) {
5177
- const position = new THREE19.Vector3(
5223
+ const position = new THREE26.Vector3(
5178
5224
  transform.position.x,
5179
5225
  transform.position.y,
5180
5226
  transform.position.z
5181
5227
  );
5182
5228
  remoteDistance = position.distanceTo(input.rayOrigin);
5183
- offset = new THREE19.Vector3();
5229
+ offset = new THREE26.Vector3();
5184
5230
  } else if (viaDistance) {
5185
- offset = new THREE19.Vector3(
5231
+ offset = new THREE26.Vector3(
5186
5232
  interactable.grabOffsetPosition.x,
5187
5233
  interactable.grabOffsetPosition.y,
5188
5234
  interactable.grabOffsetPosition.z
5189
5235
  );
5190
5236
  } else {
5191
- offset = new THREE19.Vector3(
5237
+ offset = new THREE26.Vector3(
5192
5238
  transform.position.x - hand.x,
5193
5239
  transform.position.y - hand.y,
5194
5240
  transform.position.z - hand.z
@@ -5215,7 +5261,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5215
5261
  entityId,
5216
5262
  role,
5217
5263
  attachPointId: attachPoint?.id ?? null,
5218
- attachLocal: attachPoint ? new THREE19.Vector3(
5264
+ attachLocal: attachPoint ? new THREE26.Vector3(
5219
5265
  attachPoint.position.x,
5220
5266
  attachPoint.position.y,
5221
5267
  attachPoint.position.z
@@ -5224,8 +5270,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5224
5270
  handRotationOffset,
5225
5271
  twoHand: null,
5226
5272
  offset,
5227
- rotationOffset: new THREE19.Quaternion().setFromEuler(
5228
- new THREE19.Euler(
5273
+ rotationOffset: new THREE26.Quaternion().setFromEuler(
5274
+ new THREE26.Euler(
5229
5275
  interactable.grabOffsetRotation.x,
5230
5276
  interactable.grabOffsetRotation.y,
5231
5277
  interactable.grabOffsetRotation.z
@@ -5236,7 +5282,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5236
5282
  restoreBodyType,
5237
5283
  tracker,
5238
5284
  lastHandQuaternion: handQuaternion.clone(),
5239
- angularVelocity: new THREE19.Vector3()
5285
+ angularVelocity: new THREE26.Vector3()
5240
5286
  });
5241
5287
  if (role === "primary") {
5242
5288
  this.dispatch(world, entityId, "onGrab", (b) => b.onGrab(input));
@@ -5309,14 +5355,14 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5309
5355
  const handQuaternion = this.handQuaternion(input);
5310
5356
  const objectQuaternion = held.attachQuaternion ? handQuaternion.clone().multiply(held.rotationOffset).multiply(held.attachQuaternion.clone().invert()) : handQuaternion.clone().multiply(held.handRotationOffset);
5311
5357
  if (held.remote) {
5312
- const target = new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
5358
+ const target = new THREE26.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
5313
5359
  transform.position.x = target.x;
5314
5360
  transform.position.y = target.y;
5315
5361
  transform.position.z = target.z;
5316
5362
  writeEuler(transform.rotation, objectQuaternion);
5317
5363
  } else if (held.attachLocal) {
5318
5364
  const anchor = held.attachLocal.clone().multiply(
5319
- new THREE19.Vector3(
5365
+ new THREE26.Vector3(
5320
5366
  transform.scale.x,
5321
5367
  transform.scale.y,
5322
5368
  transform.scale.z
@@ -5334,7 +5380,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5334
5380
  writeEuler(transform.rotation, objectQuaternion);
5335
5381
  }
5336
5382
  held.tracker.record(
5337
- held.remote ? new THREE19.Vector3(
5383
+ held.remote ? new THREE26.Vector3(
5338
5384
  transform.position.x,
5339
5385
  transform.position.y,
5340
5386
  transform.position.z
@@ -5409,7 +5455,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5409
5455
  */
5410
5456
  handPosition(input) {
5411
5457
  if (input.gripPosition) return input.gripPosition;
5412
- return new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
5458
+ return new THREE26.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
5413
5459
  }
5414
5460
  /**
5415
5461
  * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
@@ -5417,8 +5463,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5417
5463
  */
5418
5464
  handQuaternion(input) {
5419
5465
  if (input.gripQuaternion) return input.gripQuaternion.clone();
5420
- return new THREE19.Quaternion().setFromUnitVectors(
5421
- new THREE19.Vector3(0, 0, -1),
5466
+ return new THREE26.Quaternion().setFromUnitVectors(
5467
+ new THREE26.Vector3(0, 0, -1),
5422
5468
  input.rayDirection.clone().normalize()
5423
5469
  );
5424
5470
  }
@@ -5555,7 +5601,7 @@ function sampleTeleportArc(origin, direction, out = [], options) {
5555
5601
  for (let i = 0; i < steps; i += 1) {
5556
5602
  const t = i / (steps - 1) * maxTime;
5557
5603
  out.push(
5558
- new THREE19.Vector3(
5604
+ new THREE26.Vector3(
5559
5605
  origin.x + velocity.x * t,
5560
5606
  origin.y + velocity.y * t - 0.5 * gravity * t * t,
5561
5607
  origin.z + velocity.z * t
@@ -5572,7 +5618,7 @@ function arcGroundCrossing(points, groundY) {
5572
5618
  if (previous.y >= groundY && current.y <= groundY) {
5573
5619
  const span = previous.y - current.y;
5574
5620
  const alpha = span > 1e-6 ? (previous.y - groundY) / span : 0;
5575
- return new THREE19.Vector3().lerpVectors(previous, current, alpha);
5621
+ return new THREE26.Vector3().lerpVectors(previous, current, alpha);
5576
5622
  }
5577
5623
  }
5578
5624
  return null;
@@ -5616,11 +5662,11 @@ __name(_TeleportAimGate, "TeleportAimGate");
5616
5662
  var TeleportAimGate = _TeleportAimGate;
5617
5663
 
5618
5664
  // core/systems/XRLocomotionSystem.ts
5619
- var UP = new THREE19.Vector3(0, 1, 0);
5665
+ var UP = new THREE26.Vector3(0, 1, 0);
5620
5666
  var AXIS_DEADZONE = 0.5;
5621
5667
  var _XRLocomotionSystem = class _XRLocomotionSystem {
5622
5668
  constructor(inputSystem) {
5623
- this.raycaster = new THREE19.Raycaster();
5669
+ this.raycaster = new THREE26.Raycaster();
5624
5670
  /** Per-source latch so one stick push yields exactly one snap turn. */
5625
5671
  this.turnLatched = /* @__PURE__ */ new WeakSet();
5626
5672
  /** Reused arc buffer — aiming runs every frame. */
@@ -5746,7 +5792,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5746
5792
  }
5747
5793
  if (this.turnLatched.has(source)) continue;
5748
5794
  this.turnLatched.add(source);
5749
- transform.rotation.y -= Math.sign(x) * THREE19.MathUtils.degToRad(rig.snapTurnDegrees);
5795
+ transform.rotation.y -= Math.sign(x) * THREE26.MathUtils.degToRad(rig.snapTurnDegrees);
5750
5796
  }
5751
5797
  }
5752
5798
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -5756,12 +5802,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5756
5802
  const y = input.axes[1] ?? 0;
5757
5803
  if (Math.abs(x) < 0.15 && Math.abs(y) < 0.15) continue;
5758
5804
  const camera = world.getCamera();
5759
- const forward = new THREE19.Vector3();
5805
+ const forward = new THREE26.Vector3();
5760
5806
  camera.getWorldDirection(forward);
5761
5807
  forward.y = 0;
5762
5808
  if (forward.lengthSq() < 1e-6) continue;
5763
5809
  forward.normalize();
5764
- const right = new THREE19.Vector3().crossVectors(forward, UP).normalize();
5810
+ const right = new THREE26.Vector3().crossVectors(forward, UP).normalize();
5765
5811
  const step = rig.moveSpeed * Math.max(0, deltaTime);
5766
5812
  transform.position.x += (forward.x * -y + right.x * x) * step;
5767
5813
  transform.position.z += (forward.z * -y + right.z * x) * step;
@@ -5842,7 +5888,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5842
5888
  for (let i = 1; i < points.length; i += 1) {
5843
5889
  const from = points[i - 1];
5844
5890
  const to = points[i];
5845
- const delta = new THREE19.Vector3().subVectors(to, from);
5891
+ const delta = new THREE26.Vector3().subVectors(to, from);
5846
5892
  const distance = delta.length();
5847
5893
  if (distance < 1e-6) continue;
5848
5894
  this.raycaster.set(from, delta.divideScalar(distance));
@@ -6115,12 +6161,12 @@ function planeColliderFromPolygon(polygon) {
6115
6161
  }
6116
6162
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
6117
6163
  function writeXrMeshGeometry(vertices, indices, existing) {
6118
- const geometry = existing ?? new THREE19.BufferGeometry();
6164
+ const geometry = existing ?? new THREE26.BufferGeometry();
6119
6165
  geometry.setAttribute(
6120
6166
  "position",
6121
- new THREE19.BufferAttribute(vertices.slice(), 3)
6167
+ new THREE26.BufferAttribute(vertices.slice(), 3)
6122
6168
  );
6123
- geometry.setIndex(new THREE19.BufferAttribute(indices.slice(), 1));
6169
+ geometry.setIndex(new THREE26.BufferAttribute(indices.slice(), 1));
6124
6170
  geometry.deleteAttribute("normal");
6125
6171
  geometry.computeBoundingSphere();
6126
6172
  geometry.computeVertexNormals();
@@ -6128,14 +6174,14 @@ function writeXrMeshGeometry(vertices, indices, existing) {
6128
6174
  }
6129
6175
  __name(writeXrMeshGeometry, "writeXrMeshGeometry");
6130
6176
  function decomposeXrPose(matrixElements, target) {
6131
- const matrix = new THREE19.Matrix4().fromArray(
6177
+ const matrix = new THREE26.Matrix4().fromArray(
6132
6178
  matrixElements
6133
6179
  );
6134
- const position = new THREE19.Vector3();
6135
- const quaternion = new THREE19.Quaternion();
6136
- const scale = new THREE19.Vector3();
6180
+ const position = new THREE26.Vector3();
6181
+ const quaternion = new THREE26.Quaternion();
6182
+ const scale = new THREE26.Vector3();
6137
6183
  matrix.decompose(position, quaternion, scale);
6138
- const euler = new THREE19.Euler().setFromQuaternion(quaternion, "XYZ");
6184
+ const euler = new THREE26.Euler().setFromQuaternion(quaternion, "XYZ");
6139
6185
  target.position.x = position.x;
6140
6186
  target.position.y = position.y;
6141
6187
  target.position.z = position.z;
@@ -6297,8 +6343,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6297
6343
  this.viewerPos = null;
6298
6344
  /** The ONE global room mesh currently allowed to spawn. */
6299
6345
  this.electedGlobalMesh = null;
6300
- this.tmpViewerMatrix = new THREE19.Matrix4();
6301
- this.tmpViewPos = new THREE19.Vector3();
6346
+ this.tmpViewerMatrix = new THREE26.Matrix4();
6347
+ this.tmpViewPos = new THREE26.Vector3();
6302
6348
  }
6303
6349
  update(world, deltaTime = 0) {
6304
6350
  const presenting = world.isXrPresenting();
@@ -6657,7 +6703,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6657
6703
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
6658
6704
  const label = mesh.semanticLabel ?? null;
6659
6705
  const isGlobalMesh = label === "global mesh";
6660
- const material = new THREE19.MeshBasicMaterial({
6706
+ const material = new THREE26.MeshBasicMaterial({
6661
6707
  color: debugColorFor("mesh", label),
6662
6708
  wireframe: true,
6663
6709
  // The global scan is a coarse triangle soup of the whole room; at full
@@ -6678,8 +6724,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6678
6724
  depthTest: true,
6679
6725
  depthWrite: false
6680
6726
  });
6681
- const root = new THREE19.Group();
6682
- const child = new THREE19.Mesh(geometry, material);
6727
+ const root = new THREE26.Group();
6728
+ const child = new THREE26.Mesh(geometry, material);
6683
6729
  child.visible = this.debugVisible;
6684
6730
  child.renderOrder = DEBUG_RENDER_ORDER;
6685
6731
  root.add(child);
@@ -6705,21 +6751,21 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6705
6751
  }
6706
6752
  /** A thin box so a detected plane can be SEEN, not just collided with. */
6707
6753
  attachPlaneDebug(world, entry, collider, semanticLabel) {
6708
- const box = new THREE19.BoxGeometry(
6754
+ const box = new THREE26.BoxGeometry(
6709
6755
  collider.size.x,
6710
6756
  collider.size.y,
6711
6757
  collider.size.z
6712
6758
  );
6713
- const geometry = new THREE19.EdgesGeometry(box);
6759
+ const geometry = new THREE26.EdgesGeometry(box);
6714
6760
  box.dispose();
6715
- const material = new THREE19.LineBasicMaterial({
6761
+ const material = new THREE26.LineBasicMaterial({
6716
6762
  color: debugColorFor("plane", semanticLabel),
6717
6763
  // Same depth policy as the mesh overlay — see createMeshEntity.
6718
6764
  depthTest: true,
6719
6765
  depthWrite: false
6720
6766
  });
6721
- const root = new THREE19.Group();
6722
- const child = new THREE19.LineSegments(geometry, material);
6767
+ const root = new THREE26.Group();
6768
+ const child = new THREE26.LineSegments(geometry, material);
6723
6769
  child.renderOrder = DEBUG_RENDER_ORDER;
6724
6770
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
6725
6771
  child.visible = this.debugVisible;
@@ -6795,6 +6841,14 @@ function normalizeOrientation(value) {
6795
6841
  return value === "horizontal" || value === "vertical" ? value : null;
6796
6842
  }
6797
6843
  __name(normalizeOrientation, "normalizeOrientation");
6844
+ var isDisplayOnlySlider = /* @__PURE__ */ __name((slider) => slider.readOnly === true || slider.variant !== void 0 && slider.variant !== "slider", "isDisplayOnlySlider");
6845
+ function snapToStep(value, slider) {
6846
+ const { step, min, max } = slider;
6847
+ if (!step || step <= 0) return value;
6848
+ const snapped = min + Math.round((value - min) / step) * step;
6849
+ return Math.min(max, Math.max(min, snapped));
6850
+ }
6851
+ __name(snapToStep, "snapToStep");
6798
6852
  var MOUSE_CLICK_TRAVEL_PX = 6;
6799
6853
  var PRESS_TINT_MIN_SECONDS = 0.1;
6800
6854
  var PRESS_TINT = 0.72;
@@ -6808,11 +6862,12 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6808
6862
  constructor(interaction, options) {
6809
6863
  this.xrPresses = /* @__PURE__ */ new Map();
6810
6864
  this.mousePress = null;
6811
- this.mouseHover = null;
6865
+ /** What each pointer is on. Two hands can rest on one widget. */
6866
+ this.hovers = /* @__PURE__ */ new Map();
6812
6867
  /** entityId → seconds of pressed-tint left to display. */
6813
6868
  this.tintFloors = /* @__PURE__ */ new Map();
6814
6869
  this.tinted = /* @__PURE__ */ new Set();
6815
- this.raycaster = new THREE19.Raycaster();
6870
+ this.raycaster = new THREE26.Raycaster();
6816
6871
  this.interaction = interaction;
6817
6872
  this.inputSystem = options?.inputSystem ?? null;
6818
6873
  interaction?.registerBusyContributor(
@@ -6822,8 +6877,10 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6822
6877
  update(world, deltaTime = 0) {
6823
6878
  if (world.isEditorMode()) return;
6824
6879
  if (world.isXrPresenting()) {
6880
+ this.setHover(world, "mouse", null, null);
6825
6881
  this.updateXr(world);
6826
6882
  } else {
6883
+ this.dropGoneXrHovers(world, []);
6827
6884
  this.updateMouse(world);
6828
6885
  }
6829
6886
  this.updateTints(world, deltaTime);
@@ -6837,6 +6894,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6837
6894
  for (const input of inputs) {
6838
6895
  const hover = interaction.getHoverForInput(input);
6839
6896
  const hovered = hover ? this.widgetKind(world, hover.entityId) : null;
6897
+ this.setHover(world, input, hover && hovered ? hover.entityId : null, input);
6840
6898
  if (input.selectStart && hover && hovered) {
6841
6899
  this.xrPresses.set(input, {
6842
6900
  entityId: hover.entityId,
@@ -6865,6 +6923,15 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6865
6923
  for (const [input] of this.xrPresses) {
6866
6924
  if (!inputs.includes(input)) this.xrPresses.delete(input);
6867
6925
  }
6926
+ this.dropGoneXrHovers(world, inputs);
6927
+ }
6928
+ /** A transient source leaves with no select edge to clear its hover. */
6929
+ dropGoneXrHovers(world, inputs) {
6930
+ for (const source of [...this.hovers.keys()]) {
6931
+ if (source !== "mouse" && !inputs.includes(source)) {
6932
+ this.setHover(world, source, null, source);
6933
+ }
6934
+ }
6868
6935
  }
6869
6936
  // ── Mouse path (flat preview) ──────────────────────────────────────────────
6870
6937
  updateMouse(world) {
@@ -6872,22 +6939,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6872
6939
  if (!input) return;
6873
6940
  const hit = this.raycastMouse(world);
6874
6941
  const hovered = hit && this.widgetKind(world, hit) ? hit : null;
6875
- if (hovered !== this.mouseHover) {
6876
- if (this.mouseHover !== null) {
6877
- this.setTint(world, this.mouseHover, 1);
6878
- this.dispatchBubbling(
6879
- world,
6880
- this.mouseHover,
6881
- "onUiHoverEnd",
6882
- (b, e) => b.onUiHoverEnd(e)
6883
- );
6884
- }
6885
- if (hovered !== null) {
6886
- this.setTint(world, hovered, HOVER_TINT);
6887
- this.dispatchBubbling(world, hovered, "onUiHoverStart", (b, e) => b.onUiHoverStart(e));
6888
- }
6889
- this.mouseHover = hovered;
6890
- }
6942
+ this.setHover(world, "mouse", hovered, null);
6891
6943
  if (input.getMouseDown("left") && hovered) {
6892
6944
  const kind = this.widgetKind(world, hovered);
6893
6945
  if (kind) {
@@ -6895,12 +6947,12 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6895
6947
  this.beginPressTint(world, hovered);
6896
6948
  }
6897
6949
  }
6898
- if (this.mousePress) {
6950
+ if (this.mousePress && !input.getMouseDown("left")) {
6899
6951
  const delta = input.getMouseDelta();
6900
6952
  this.mousePress.travelPx += Math.hypot(delta.x, delta.y);
6901
- if (this.mousePress.kind === "slider") {
6902
- this.dragSliderMouse(world, this.mousePress.entityId);
6903
- }
6953
+ }
6954
+ if (this.mousePress?.kind === "slider") {
6955
+ this.dragSliderMouse(world, this.mousePress.entityId);
6904
6956
  }
6905
6957
  if (input.getMouseUp("left") && this.mousePress) {
6906
6958
  const press = this.mousePress;
@@ -6929,15 +6981,24 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6929
6981
  ndcY = -((pos.y - rect.top) / Math.max(1, rect.height) * 2 - 1);
6930
6982
  if (ndcX < -1 || ndcX > 1 || ndcY < -1 || ndcY > 1) return null;
6931
6983
  }
6932
- this.raycaster.setFromCamera(new THREE19.Vector2(ndcX, ndcY), world.getCamera());
6984
+ this.raycaster.setFromCamera(new THREE26.Vector2(ndcX, ndcY), world.getCamera());
6933
6985
  const hits = world.raycast(
6934
6986
  this.raycaster.ray.origin,
6935
6987
  this.raycaster.ray.direction
6936
6988
  );
6989
+ let surfaceTree = null;
6937
6990
  for (const hit of hits) {
6938
- if (typeof hit.entityId !== "number") continue;
6939
- if (this.widgetKind(world, hit.entityId)) return hit.entityId;
6940
- return null;
6991
+ const entityId = hit.entityId;
6992
+ if (typeof entityId !== "number") continue;
6993
+ if (!isShownInScene(world, entityId)) continue;
6994
+ if (this.widgetKind(world, entityId)) {
6995
+ const sameSurface = surfaceTree === null || widgetTreeRoot(world, entityId) === surfaceTree;
6996
+ return sameSurface ? entityId : null;
6997
+ }
6998
+ if (!isUiWidget(world, entityId)) return null;
6999
+ const tree = widgetTreeRoot(world, entityId);
7000
+ if (surfaceTree !== null && tree !== surfaceTree) return null;
7001
+ surfaceTree = tree;
6941
7002
  }
6942
7003
  return null;
6943
7004
  }
@@ -6945,7 +7006,8 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6945
7006
  widgetKind(world, entityId) {
6946
7007
  if (world.getComponent(entityId, UIButtonComponent)) return "button";
6947
7008
  if (world.getComponent(entityId, UIToggleComponent)) return "toggle";
6948
- if (world.getComponent(entityId, UISliderComponent)) return "slider";
7009
+ const slider = world.getComponent(entityId, UISliderComponent);
7010
+ if (slider && !isDisplayOnlySlider(slider)) return "slider";
6949
7011
  return null;
6950
7012
  }
6951
7013
  fire(world, press, input) {
@@ -6996,7 +7058,10 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6996
7058
  const inset = slider.height / 2;
6997
7059
  const usable = Math.max(1e-3, slider.width - inset * 2);
6998
7060
  const t = Math.min(1, Math.max(0, local.x / usable + 0.5));
6999
- const next = slider.min + t * (slider.max - slider.min);
7061
+ const next = snapToStep(
7062
+ slider.min + t * (slider.max - slider.min),
7063
+ slider
7064
+ );
7000
7065
  if (Math.abs(next - slider.value) < 1e-4) return;
7001
7066
  slider.value = next;
7002
7067
  this.dispatchBubbling(
@@ -7074,6 +7139,43 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7074
7139
  const name = world.getComponent(entityId, Name)?.value;
7075
7140
  return typeof name === "string" ? name : String(entityId);
7076
7141
  }
7142
+ // ── Hover ──────────────────────────────────────────────────────────────────
7143
+ setHover(world, source, next, input) {
7144
+ const previous = this.hovers.get(source) ?? null;
7145
+ if (previous === next) return;
7146
+ if (next === null) this.hovers.delete(source);
7147
+ else this.hovers.set(source, next);
7148
+ if (previous !== null && !this.isHovered(previous)) {
7149
+ if (!this.tintFloors.has(previous)) this.setTint(world, previous, 1);
7150
+ this.dispatchBubbling(
7151
+ world,
7152
+ previous,
7153
+ "onUiHoverEnd",
7154
+ (b, e) => b.onUiHoverEnd(e),
7155
+ input
7156
+ );
7157
+ }
7158
+ if (next !== null && this.hoverCount(next) === 1) {
7159
+ if (!this.tintFloors.has(next)) this.setTint(world, next, HOVER_TINT);
7160
+ this.dispatchBubbling(
7161
+ world,
7162
+ next,
7163
+ "onUiHoverStart",
7164
+ (b, e) => b.onUiHoverStart(e),
7165
+ input
7166
+ );
7167
+ }
7168
+ }
7169
+ hoverCount(entityId) {
7170
+ let count = 0;
7171
+ for (const hovered of this.hovers.values()) {
7172
+ if (hovered === entityId) count += 1;
7173
+ }
7174
+ return count;
7175
+ }
7176
+ isHovered(entityId) {
7177
+ return this.hoverCount(entityId) > 0;
7178
+ }
7077
7179
  // ── Pressed/hover tint ─────────────────────────────────────────────────────
7078
7180
  beginPressTint(world, entityId) {
7079
7181
  this.setTint(world, entityId, PRESS_TINT);
@@ -7091,11 +7193,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7091
7193
  if (stillHeld) continue;
7092
7194
  if (next <= 0) {
7093
7195
  this.tintFloors.delete(entityId);
7094
- this.setTint(
7095
- world,
7096
- entityId,
7097
- this.mouseHover === entityId ? HOVER_TINT : 1
7098
- );
7196
+ this.setTint(world, entityId, this.isHovered(entityId) ? HOVER_TINT : 1);
7099
7197
  } else {
7100
7198
  this.tintFloors.set(entityId, next);
7101
7199
  }
@@ -7106,14 +7204,101 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7106
7204
  if (!object) return;
7107
7205
  if (scalar === 1) this.tinted.delete(entityId);
7108
7206
  else this.tinted.add(entityId);
7109
- object.traverse((child) => {
7110
- const material = child.material;
7111
- if (material?.color) material.color.setScalar(scalar);
7112
- });
7207
+ for (const child of object.children) {
7208
+ const tag = child.userData[UI_PART_KEY];
7209
+ if (!tag) continue;
7210
+ tag.tint = scalar;
7211
+ applyPartColor(child, tag);
7212
+ }
7113
7213
  }
7114
7214
  };
7115
7215
  __name(_UIInteractionSystem, "UIInteractionSystem");
7116
7216
  var UIInteractionSystem = _UIInteractionSystem;
7217
+ var fonts = /* @__PURE__ */ new Map();
7218
+ var images = /* @__PURE__ */ new Map();
7219
+ function hash(text) {
7220
+ let value = 5381;
7221
+ for (let i = 0; i < text.length; i += 1) {
7222
+ value = (value * 33 ^ text.charCodeAt(i)) >>> 0;
7223
+ }
7224
+ return value.toString(36);
7225
+ }
7226
+ __name(hash, "hash");
7227
+ function tracked(url, work) {
7228
+ const manager = THREE26.DefaultLoadingManager;
7229
+ manager.itemStart(url);
7230
+ return work.catch((error) => {
7231
+ manager.itemError(url);
7232
+ throw error;
7233
+ }).finally(() => manager.itemEnd(url));
7234
+ }
7235
+ __name(tracked, "tracked");
7236
+ function uiFontFamily(src) {
7237
+ if (typeof FontFace === "undefined" || typeof document === "undefined") {
7238
+ return null;
7239
+ }
7240
+ const known = fonts.get(src);
7241
+ if (known) return known.value;
7242
+ const entry = { value: null, settled: false };
7243
+ fonts.set(src, entry);
7244
+ const url = resolvePrefabAssetUrl(src);
7245
+ const family = `relu-ui-${hash(url)}`;
7246
+ tracked(
7247
+ url,
7248
+ (async () => {
7249
+ const response = await fetch(url);
7250
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
7251
+ const buffer = await response.arrayBuffer();
7252
+ const face = new FontFace(family, buffer, { weight: "100 900" });
7253
+ await face.load();
7254
+ document.fonts.add(face);
7255
+ })()
7256
+ ).then(() => {
7257
+ entry.value = family;
7258
+ }).catch((error) => {
7259
+ console.warn(`[spatial-ecs] Couldn't load UI font ${src}.`, error);
7260
+ }).finally(() => {
7261
+ entry.settled = true;
7262
+ });
7263
+ return null;
7264
+ }
7265
+ __name(uiFontFamily, "uiFontFamily");
7266
+ function uiImage(src) {
7267
+ if (typeof Image === "undefined" || typeof fetch === "undefined") {
7268
+ return null;
7269
+ }
7270
+ const known = images.get(src);
7271
+ if (known) return known.value;
7272
+ const entry = { value: null, settled: false };
7273
+ images.set(src, entry);
7274
+ const url = resolvePrefabAssetUrl(src);
7275
+ tracked(
7276
+ url,
7277
+ (async () => {
7278
+ const response = await fetch(url);
7279
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
7280
+ const blob = await response.blob();
7281
+ const typed = /\.svg(\?|#|$)/i.test(url) ? new Blob([blob], { type: "image/svg+xml" }) : blob;
7282
+ const image = new Image();
7283
+ const objectUrl = URL.createObjectURL(typed);
7284
+ try {
7285
+ image.src = objectUrl;
7286
+ await image.decode();
7287
+ return image;
7288
+ } finally {
7289
+ URL.revokeObjectURL(objectUrl);
7290
+ }
7291
+ })()
7292
+ ).then((image) => {
7293
+ entry.value = image;
7294
+ }).catch((error) => {
7295
+ console.warn(`[spatial-ecs] Couldn't load UI image ${src}.`, error);
7296
+ }).finally(() => {
7297
+ entry.settled = true;
7298
+ });
7299
+ return null;
7300
+ }
7301
+ __name(uiImage, "uiImage");
7117
7302
 
7118
7303
  // core/ui/uiCanvas.ts
7119
7304
  var DEFAULT_PIXELS_PER_METER = 1024;
@@ -7186,18 +7371,21 @@ function clampLines(measure, lines, maxLines, maxWidthPx) {
7186
7371
  }
7187
7372
  __name(clampLines, "clampLines");
7188
7373
  var UI_FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
7189
- function tracePath(ctx, x, y, w, h, r) {
7190
- const radius = Math.max(0, Math.min(r, w / 2, h / 2));
7374
+ var NO_ASSETS = { image: /* @__PURE__ */ __name(() => null, "image") };
7375
+ function tracePath(ctx, x, y, w, h, radii) {
7376
+ const limit = Math.min(w / 2, h / 2);
7377
+ const fit = /* @__PURE__ */ __name((r) => Math.max(0, Math.min(r, limit)), "fit");
7378
+ const [tl, tr, br, bl] = (Array.isArray(radii) ? radii : [radii, radii, radii, radii]).map(fit);
7191
7379
  ctx.beginPath();
7192
7380
  if (typeof ctx.roundRect === "function") {
7193
- ctx.roundRect(x, y, w, h, radius);
7381
+ ctx.roundRect(x, y, w, h, [tl, tr, br, bl]);
7194
7382
  return;
7195
7383
  }
7196
- ctx.moveTo(x + radius, y);
7197
- ctx.arcTo(x + w, y, x + w, y + h, radius);
7198
- ctx.arcTo(x + w, y + h, x, y + h, radius);
7199
- ctx.arcTo(x, y + h, x, y, radius);
7200
- ctx.arcTo(x, y, x + w, y, radius);
7384
+ ctx.moveTo(x + tl, y);
7385
+ ctx.arcTo(x + w, y, x + w, y + h, tr);
7386
+ ctx.arcTo(x + w, y + h, x, y + h, br);
7387
+ ctx.arcTo(x, y + h, x, y, bl);
7388
+ ctx.arcTo(x, y, x + w, y, tl);
7201
7389
  ctx.closePath();
7202
7390
  }
7203
7391
  __name(tracePath, "tracePath");
@@ -7217,108 +7405,244 @@ function withOpacity(ctx, color, opacity) {
7217
7405
  return parsed;
7218
7406
  }
7219
7407
  __name(withOpacity, "withOpacity");
7220
- function drawChrome(ctx, pxWidth, pxHeight, density, style) {
7221
- ctx.clearRect(0, 0, pxWidth, pxHeight);
7222
- const border = Math.max(0, style.borderWidthMeters * density);
7223
- const radius = style.cornerRadiusMeters * density;
7224
- tracePath(ctx, 0, 0, pxWidth, pxHeight, radius);
7225
- ctx.fillStyle = withOpacity(
7226
- ctx,
7227
- style.backgroundColor,
7228
- Math.max(0, Math.min(1, style.backgroundOpacity))
7229
- );
7408
+ function fillStyleFor(ctx, paint, box, opacity) {
7409
+ if (paint.kind === "solid") return withOpacity(ctx, paint.color, opacity);
7410
+ const cx = box.x + box.w / 2;
7411
+ const cy = box.y + box.h / 2;
7412
+ let gradient;
7413
+ if (paint.kind === "linear") {
7414
+ const radians = paint.angle * Math.PI / 180;
7415
+ const dx = Math.sin(radians);
7416
+ const dy = -Math.cos(radians);
7417
+ const half = (Math.abs(box.w * dx) + Math.abs(box.h * dy)) / 2;
7418
+ gradient = ctx.createLinearGradient(
7419
+ cx - dx * half,
7420
+ cy - dy * half,
7421
+ cx + dx * half,
7422
+ cy + dy * half
7423
+ );
7424
+ } else {
7425
+ gradient = ctx.createRadialGradient(
7426
+ cx,
7427
+ cy,
7428
+ 0,
7429
+ cx,
7430
+ cy,
7431
+ Math.max(box.w, box.h) / 2
7432
+ );
7433
+ }
7434
+ for (const stop of paint.stops) {
7435
+ gradient.addColorStop(stop.at, withOpacity(ctx, stop.color, opacity));
7436
+ }
7437
+ return gradient;
7438
+ }
7439
+ __name(fillStyleFor, "fillStyleFor");
7440
+ function drawImagePaint(ctx, paint, box, assets) {
7441
+ const image = assets.image(paint.src);
7442
+ if (!image) return;
7443
+ const sourceW = image.naturalWidth || box.w;
7444
+ const sourceH = image.naturalHeight || box.h;
7445
+ const scale = paint.fit === "fill" ? 1 : paint.fit === "cover" ? Math.max(box.w / sourceW, box.h / sourceH) : Math.min(box.w / sourceW, box.h / sourceH);
7446
+ const w = paint.fit === "fill" ? box.w : sourceW * scale;
7447
+ const h = paint.fit === "fill" ? box.h : sourceH * scale;
7448
+ const x = box.x + (box.w - w) / 2;
7449
+ const y = box.y + (box.h - h) / 2;
7450
+ if (!paint.tint) {
7451
+ ctx.drawImage(image, x, y, w, h);
7452
+ return;
7453
+ }
7454
+ const scratch = assets.scratch?.(Math.ceil(w), Math.ceil(h));
7455
+ if (!scratch) {
7456
+ ctx.drawImage(image, x, y, w, h);
7457
+ return;
7458
+ }
7459
+ scratch.drawImage(image, 0, 0, w, h);
7460
+ scratch.globalCompositeOperation = "source-in";
7461
+ scratch.fillStyle = paint.tint;
7462
+ scratch.fillRect(0, 0, w, h);
7463
+ ctx.drawImage(scratch.canvas, x, y, w, h);
7464
+ }
7465
+ __name(drawImagePaint, "drawImagePaint");
7466
+ function pxRadii(radius, density) {
7467
+ return radius.map((r) => r * density);
7468
+ }
7469
+ __name(pxRadii, "pxRadii");
7470
+ function drawBoxShadow(ctx, box, radii, shadow, density) {
7471
+ ctx.save();
7472
+ ctx.shadowColor = shadow.color;
7473
+ ctx.shadowBlur = shadow.blur * density;
7474
+ ctx.shadowOffsetX = shadow.x * density;
7475
+ ctx.shadowOffsetY = -shadow.y * density;
7476
+ ctx.fillStyle = "#000000";
7477
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7230
7478
  ctx.fill();
7231
- if (border > 0) {
7479
+ ctx.restore();
7480
+ ctx.save();
7481
+ ctx.globalCompositeOperation = "destination-out";
7482
+ ctx.fillStyle = "#000000";
7483
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7484
+ ctx.fill();
7485
+ ctx.restore();
7486
+ }
7487
+ __name(drawBoxShadow, "drawBoxShadow");
7488
+ function drawChrome(ctx, box, density, chrome, assets = NO_ASSETS) {
7489
+ const radii = pxRadii(chrome.radius, density);
7490
+ for (const shadow of chrome.shadows) {
7491
+ drawBoxShadow(ctx, box, radii, shadow, density);
7492
+ }
7493
+ const opacity = Math.max(0, Math.min(1, chrome.fillOpacity));
7494
+ if (chrome.fill?.kind === "image") {
7495
+ ctx.save();
7496
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7497
+ ctx.clip();
7498
+ ctx.globalAlpha = opacity;
7499
+ drawImagePaint(ctx, chrome.fill, box, assets);
7500
+ ctx.restore();
7501
+ } else if (chrome.fill) {
7502
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7503
+ ctx.fillStyle = fillStyleFor(ctx, chrome.fill, box, opacity);
7504
+ ctx.fill();
7505
+ }
7506
+ if (chrome.edge) {
7507
+ const width = chrome.edge.width * density;
7508
+ ctx.save();
7509
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7510
+ ctx.clip();
7511
+ const fade = ctx.createLinearGradient(0, box.y, 0, box.y + box.h / 2);
7512
+ fade.addColorStop(0, chrome.edge.color);
7513
+ fade.addColorStop(1, withOpacity(ctx, chrome.edge.color, 0));
7514
+ ctx.strokeStyle = fade;
7515
+ ctx.lineWidth = width * 2;
7516
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7517
+ ctx.stroke();
7518
+ ctx.restore();
7519
+ }
7520
+ if (chrome.border) {
7521
+ const border = chrome.border.width * density;
7232
7522
  tracePath(
7233
7523
  ctx,
7234
- border / 2,
7235
- border / 2,
7236
- pxWidth - border,
7237
- pxHeight - border,
7238
- Math.max(0, radius - border / 2)
7524
+ box.x + border / 2,
7525
+ box.y + border / 2,
7526
+ box.w - border,
7527
+ box.h - border,
7528
+ radii.map((r) => Math.max(0, r - border / 2))
7239
7529
  );
7240
- ctx.strokeStyle = style.borderColor;
7530
+ ctx.strokeStyle = chrome.border.color;
7241
7531
  ctx.lineWidth = border;
7242
7532
  ctx.stroke();
7243
7533
  }
7244
7534
  }
7245
7535
  __name(drawChrome, "drawChrome");
7246
- function drawTextBlock(ctx, pxWidth, pxHeight, density, text, style) {
7247
- const fontPx = Math.max(4, style.fontSizeMeters * density);
7248
- const paddingPx = style.paddingMeters * density;
7249
- const contentW = Math.max(1, pxWidth - paddingPx * 2);
7250
- const contentH = Math.max(1, pxHeight - paddingPx * 2);
7536
+ var applyTransform2 = /* @__PURE__ */ __name((text, transform) => transform === "upper" ? text.toUpperCase() : transform === "lower" ? text.toLowerCase() : text, "applyTransform");
7537
+ function fillTracked(ctx, line, x, y, trackingPx, width) {
7538
+ let cursor = ctx.textAlign === "center" ? x - width / 2 : ctx.textAlign === "right" ? x - width : x;
7539
+ const align = ctx.textAlign;
7540
+ ctx.textAlign = "left";
7541
+ for (const glyph of line) {
7542
+ ctx.fillText(glyph, cursor, y);
7543
+ cursor += ctx.measureText(glyph).width + trackingPx;
7544
+ }
7545
+ ctx.textAlign = align;
7546
+ }
7547
+ __name(fillTracked, "fillTracked");
7548
+ function drawTextBlock(ctx, box, density, text, style, family = null, assets = NO_ASSETS) {
7549
+ const fontPx = Math.max(4, style.fontSize * density);
7550
+ const paddingPx = style.padding * density;
7551
+ const contentW = Math.max(1, box.w - paddingPx * 2);
7552
+ const contentH = Math.max(1, box.h - paddingPx * 2);
7251
7553
  const lineHeightPx = fontPx * style.lineHeight;
7252
- ctx.font = `${style.fontWeight} ${fontPx}px ${UI_FONT_STACK}`;
7253
- const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width, "measure");
7254
- const maxLines = Math.max(1, Math.floor(contentH / lineHeightPx));
7554
+ const trackingPx = style.tracking * density;
7555
+ const stack = family ? `"${family}", ${UI_FONT_STACK}` : UI_FONT_STACK;
7556
+ ctx.font = `${style.fontWeight} ${fontPx}px ${stack}`;
7557
+ const nativeTracking = trackingPx !== 0 && "letterSpacing" in ctx;
7558
+ if (nativeTracking) {
7559
+ ctx.letterSpacing = `${trackingPx}px`;
7560
+ }
7561
+ const manualTracking = trackingPx !== 0 && !nativeTracking;
7562
+ const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width + (manualTracking ? trackingPx * s.length : 0), "measure");
7563
+ const fits = Math.max(1, Math.floor(contentH / lineHeightPx));
7564
+ const maxLines = style.maxLines ? Math.min(fits, style.maxLines) : fits;
7255
7565
  const lines = clampLines(
7256
7566
  measure,
7257
- wrapText(measure, text, contentW),
7567
+ wrapText(measure, applyTransform2(text, style.transform), contentW),
7258
7568
  maxLines,
7259
7569
  contentW
7260
7570
  );
7261
7571
  const blockH = lines.length * lineHeightPx;
7262
- const top = style.valign === "top" ? paddingPx : style.valign === "bottom" ? pxHeight - paddingPx - blockH : (pxHeight - blockH) / 2;
7263
- const anchorX = style.align === "left" ? paddingPx : style.align === "right" ? pxWidth - paddingPx : pxWidth / 2;
7572
+ const top = style.valign === "top" ? box.y + paddingPx : style.valign === "bottom" ? box.y + box.h - paddingPx - blockH : box.y + (box.h - blockH) / 2;
7573
+ const anchorX = style.align === "left" ? box.x + paddingPx : style.align === "right" ? box.x + box.w - paddingPx : box.x + box.w / 2;
7264
7574
  ctx.save();
7265
- tracePath(ctx, 0, 0, pxWidth, pxHeight, 0);
7266
- ctx.clip();
7267
- ctx.fillStyle = style.color;
7575
+ if (style.paint.kind === "image") {
7576
+ ctx.fillStyle = "#ffffff";
7577
+ } else {
7578
+ ctx.fillStyle = fillStyleFor(ctx, style.paint, box, 1);
7579
+ }
7580
+ if (style.shadow) {
7581
+ ctx.shadowColor = style.shadow.color;
7582
+ ctx.shadowBlur = style.shadow.blur * density;
7583
+ ctx.shadowOffsetX = style.shadow.x * density;
7584
+ ctx.shadowOffsetY = -style.shadow.y * density;
7585
+ }
7268
7586
  ctx.textAlign = style.align;
7269
7587
  ctx.textBaseline = "middle";
7270
7588
  for (let i = 0; i < lines.length; i += 1) {
7271
- ctx.fillText(lines[i], anchorX, top + (i + 0.5) * lineHeightPx);
7589
+ const y = top + (i + 0.5) * lineHeightPx;
7590
+ if (manualTracking) {
7591
+ fillTracked(ctx, lines[i], anchorX, y, trackingPx, measure(lines[i]));
7592
+ } else {
7593
+ ctx.fillText(lines[i], anchorX, y);
7594
+ }
7272
7595
  }
7273
7596
  ctx.restore();
7597
+ if (style.paint.kind === "image") {
7598
+ ctx.save();
7599
+ ctx.globalCompositeOperation = "source-atop";
7600
+ drawImagePaint(ctx, style.paint, box, assets);
7601
+ ctx.restore();
7602
+ }
7603
+ if (nativeTracking) {
7604
+ ctx.letterSpacing = "0px";
7605
+ }
7274
7606
  }
7275
7607
  __name(drawTextBlock, "drawTextBlock");
7276
- function panelSignature(widthM, heightM, size, style) {
7608
+ function panelSignature(widthM, heightM, size, chrome, loaded = null) {
7277
7609
  return JSON.stringify([
7278
7610
  widthM,
7279
7611
  heightM,
7280
7612
  size.pxWidth,
7281
7613
  size.pxHeight,
7282
- style.backgroundColor,
7283
- style.backgroundOpacity,
7284
- style.cornerRadiusMeters,
7285
- style.borderWidthMeters,
7286
- style.borderColor
7614
+ chrome,
7615
+ loaded
7287
7616
  ]);
7288
7617
  }
7289
7618
  __name(panelSignature, "panelSignature");
7290
- function textSignature(widthM, heightM, size, text, style) {
7619
+ function textSignature(widthM, heightM, size, text, style, family = null) {
7291
7620
  return JSON.stringify([
7292
7621
  widthM,
7293
7622
  heightM,
7294
7623
  size.pxWidth,
7295
7624
  size.pxHeight,
7296
7625
  text,
7297
- style.fontSizeMeters,
7298
- style.fontWeight,
7299
- style.color,
7300
- style.align,
7301
- style.valign,
7302
- style.lineHeight,
7303
- style.paddingMeters
7626
+ style,
7627
+ family
7304
7628
  ]);
7305
7629
  }
7306
7630
  __name(textSignature, "textSignature");
7307
- function buttonSignature(widthM, heightM, size, panel, label, text) {
7631
+ function buttonSignature(widthM, heightM, size, chrome, label, text, family = null, loaded = null) {
7308
7632
  return JSON.stringify([
7309
- panelSignature(widthM, heightM, size, panel),
7633
+ panelSignature(widthM, heightM, size, chrome, loaded),
7310
7634
  label,
7311
- text.fontSizeMeters,
7312
- text.fontWeight,
7313
- text.color
7635
+ text,
7636
+ family
7314
7637
  ]);
7315
7638
  }
7316
7639
  __name(buttonSignature, "buttonSignature");
7317
- function trackSignature(widthM, heightM, size, panel, accentColor, knobColor) {
7640
+ function trackSignature(widthM, heightM, size, chrome, accentColor, knobColor, variant = null) {
7318
7641
  return JSON.stringify([
7319
- panelSignature(widthM, heightM, size, panel),
7642
+ panelSignature(widthM, heightM, size, chrome),
7320
7643
  accentColor,
7321
- knobColor
7644
+ knobColor,
7645
+ variant
7322
7646
  ]);
7323
7647
  }
7324
7648
  __name(trackSignature, "trackSignature");
@@ -7328,17 +7652,226 @@ function drawRoundedBar(ctx, pxX, pxY, pxW, pxH, color) {
7328
7652
  ctx.fill();
7329
7653
  }
7330
7654
  __name(drawRoundedBar, "drawRoundedBar");
7655
+ function drawCheckMark(ctx, box, color) {
7656
+ ctx.beginPath();
7657
+ ctx.moveTo(box.x + box.w * 0.24, box.y + box.h * 0.54);
7658
+ ctx.lineTo(box.x + box.w * 0.43, box.y + box.h * 0.72);
7659
+ ctx.lineTo(box.x + box.w * 0.77, box.y + box.h * 0.3);
7660
+ ctx.strokeStyle = color;
7661
+ ctx.lineWidth = box.h * 0.13;
7662
+ ctx.lineCap = "round";
7663
+ ctx.lineJoin = "round";
7664
+ ctx.stroke();
7665
+ }
7666
+ __name(drawCheckMark, "drawCheckMark");
7667
+ function drawDot(ctx, cx, cy, radius, color) {
7668
+ ctx.beginPath();
7669
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
7670
+ ctx.fillStyle = color;
7671
+ ctx.fill();
7672
+ }
7673
+ __name(drawDot, "drawDot");
7674
+ function drawRing(ctx, box, t, trackColor, accentColor) {
7675
+ const thickness = Math.min(box.w, box.h) * 0.14;
7676
+ const radius = Math.min(box.w, box.h) / 2 - thickness / 2;
7677
+ const cx = box.x + box.w / 2;
7678
+ const cy = box.y + box.h / 2;
7679
+ ctx.lineWidth = thickness;
7680
+ ctx.lineCap = "round";
7681
+ ctx.beginPath();
7682
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
7683
+ ctx.strokeStyle = trackColor;
7684
+ ctx.stroke();
7685
+ if (t <= 0) return;
7686
+ const start = -Math.PI / 2;
7687
+ ctx.beginPath();
7688
+ ctx.arc(cx, cy, radius, start, start + t * Math.PI * 2);
7689
+ ctx.strokeStyle = accentColor;
7690
+ ctx.stroke();
7691
+ }
7692
+ __name(drawRing, "drawRing");
7693
+ function drawIconLabel(ctx, box, density, label, style, family, icon, position, assets) {
7694
+ const fontPx = style.fontSize * density;
7695
+ const iconPx = fontPx * 1.3;
7696
+ const gapPx = label ? fontPx * 0.5 : 0;
7697
+ const stack = family ? `"${family}", ${UI_FONT_STACK}` : UI_FONT_STACK;
7698
+ ctx.font = `${style.fontWeight} ${fontPx}px ${stack}`;
7699
+ const labelPx = label ? ctx.measureText(label).width : 0;
7700
+ const groupPx = iconPx + gapPx + labelPx;
7701
+ const startX = box.x + (box.w - groupPx) / 2;
7702
+ const iconX = position === "left" ? startX : startX + labelPx + gapPx;
7703
+ const labelX = position === "left" ? startX + iconPx + gapPx : startX;
7704
+ drawImagePaint(
7705
+ ctx,
7706
+ { src: icon, fit: "contain", tint: style.color },
7707
+ { x: iconX, y: box.y + (box.h - iconPx) / 2, w: iconPx, h: iconPx },
7708
+ assets
7709
+ );
7710
+ if (label) {
7711
+ drawTextBlock(
7712
+ ctx,
7713
+ { x: labelX, y: box.y, w: labelPx + 2, h: box.h },
7714
+ density,
7715
+ label,
7716
+ { ...style, align: "left", padding: 0 },
7717
+ family,
7718
+ assets
7719
+ );
7720
+ }
7721
+ }
7722
+ __name(drawIconLabel, "drawIconLabel");
7723
+
7724
+ // core/ui/uiPaintOrder.ts
7725
+ var BASE_RENDER_ORDER = 10;
7726
+ var MAX_RENDER_ORDER = 890;
7727
+ var MAX_TREE_RANK = MAX_RENDER_ORDER - BASE_RENDER_ORDER - 1;
7728
+ var SLOT_STEP = 1e-4;
7729
+ var SLOTS_PER_WIDGET = 3;
7730
+ var MAX_WIDGET_INDEX = Math.floor(1 / SLOT_STEP / SLOTS_PER_WIDGET) - 1;
7731
+ var PLANE_EPSILON_METERS = 1e-3;
7732
+ var PARALLEL_DOT = 1 - 1e-4;
7733
+ var dot = /* @__PURE__ */ __name((a, b) => a.x * b.x + a.y * b.y + a.z * b.z, "dot");
7734
+ var length = /* @__PURE__ */ __name((a) => Math.sqrt(dot(a, a)), "length");
7735
+ var subtract = /* @__PURE__ */ __name((a, b) => ({
7736
+ x: a.x - b.x,
7737
+ y: a.y - b.y,
7738
+ z: a.z - b.z
7739
+ }), "subtract");
7740
+ function unit(a) {
7741
+ const len = length(a);
7742
+ if (len < 1e-9) return { x: 0, y: 0, z: 0 };
7743
+ return { x: a.x / len, y: a.y / len, z: a.z / len };
7744
+ }
7745
+ __name(unit, "unit");
7746
+ function normalOf(rect) {
7747
+ const { halfX: a, halfY: b } = rect;
7748
+ return unit({
7749
+ x: a.y * b.z - a.z * b.y,
7750
+ y: a.z * b.x - a.x * b.z,
7751
+ z: a.x * b.y - a.y * b.x
7752
+ });
7753
+ }
7754
+ __name(normalOf, "normalOf");
7755
+ var planeDistance = /* @__PURE__ */ __name((rect, normal, eye) => Math.abs(dot(subtract(eye, rect.center), normal)), "planeDistance");
7756
+ function closestPointDistance(rect, eye) {
7757
+ const toEye = subtract(eye, rect.center);
7758
+ const lenX = length(rect.halfX);
7759
+ const lenY = length(rect.halfY);
7760
+ const axisX = unit(rect.halfX);
7761
+ const axisY = unit(rect.halfY);
7762
+ const s = Math.max(-lenX, Math.min(lenX, dot(toEye, axisX)));
7763
+ const t = Math.max(-lenY, Math.min(lenY, dot(toEye, axisY)));
7764
+ return length({
7765
+ x: toEye.x - axisX.x * s - axisY.x * t,
7766
+ y: toEye.y - axisX.y * s - axisY.y * t,
7767
+ z: toEye.z - axisX.z * s - axisY.z * t
7768
+ });
7769
+ }
7770
+ __name(closestPointDistance, "closestPointDistance");
7771
+ var areaOf = /* @__PURE__ */ __name((rect) => length(rect.halfX) * length(rect.halfY), "areaOf");
7772
+ function comparePaintRects(a, b, eye) {
7773
+ const normalA = normalOf(a);
7774
+ const normalB = normalOf(b);
7775
+ const parallel = Math.abs(dot(normalA, normalB)) >= PARALLEL_DOT;
7776
+ const distanceA = parallel ? planeDistance(a, normalA, eye) : closestPointDistance(a, eye);
7777
+ const distanceB = parallel ? planeDistance(b, normalB, eye) : closestPointDistance(b, eye);
7778
+ if (Math.abs(distanceA - distanceB) > PLANE_EPSILON_METERS) {
7779
+ return distanceB - distanceA;
7780
+ }
7781
+ const areaA = areaOf(a);
7782
+ const areaB = areaOf(b);
7783
+ if (areaA !== areaB) return areaB - areaA;
7784
+ return a.id - b.id;
7785
+ }
7786
+ __name(comparePaintRects, "comparePaintRects");
7787
+ function buildPaintTrees(nodes) {
7788
+ const known = new Set(nodes.map((node) => node.id));
7789
+ const children = /* @__PURE__ */ new Map();
7790
+ const roots = [];
7791
+ for (const node of nodes) {
7792
+ if (node.parent !== null && known.has(node.parent)) {
7793
+ const siblings = children.get(node.parent);
7794
+ if (siblings) siblings.push(node.id);
7795
+ else children.set(node.parent, [node.id]);
7796
+ } else {
7797
+ roots.push(node.id);
7798
+ }
7799
+ }
7800
+ const trees = [];
7801
+ const visited = /* @__PURE__ */ new Set();
7802
+ const walk = /* @__PURE__ */ __name((id, order) => {
7803
+ if (visited.has(id)) return;
7804
+ visited.add(id);
7805
+ order.push(id);
7806
+ const kids = children.get(id);
7807
+ if (!kids) return;
7808
+ for (const kid of [...kids].sort((a, b) => a - b)) walk(kid, order);
7809
+ }, "walk");
7810
+ for (const rootId of [...roots].sort((a, b) => a - b)) {
7811
+ const order = [];
7812
+ walk(rootId, order);
7813
+ trees.push({ rootId, order });
7814
+ }
7815
+ return trees;
7816
+ }
7817
+ __name(buildPaintTrees, "buildPaintTrees");
7818
+ function paintSlots(treeRank, widgetIndex) {
7819
+ const base = BASE_RENDER_ORDER + Math.min(treeRank, MAX_TREE_RANK);
7820
+ const index = Math.min(widgetIndex, MAX_WIDGET_INDEX);
7821
+ const slot = /* @__PURE__ */ __name((offset) => base + (index * SLOTS_PER_WIDGET + offset) * SLOT_STEP, "slot");
7822
+ return { face: slot(0), parts: slot(1), descendants: slot(2) };
7823
+ }
7824
+ __name(paintSlots, "paintSlots");
7825
+ function assignPaintOrder(trees, rects, eye) {
7826
+ const sorted = trees.filter((tree) => rects.has(tree.rootId)).sort(
7827
+ (a, b) => comparePaintRects(
7828
+ rects.get(a.rootId),
7829
+ rects.get(b.rootId),
7830
+ eye
7831
+ )
7832
+ );
7833
+ const slots = /* @__PURE__ */ new Map();
7834
+ sorted.forEach((tree, rank) => {
7835
+ tree.order.forEach((id, index) => {
7836
+ slots.set(id, paintSlots(rank, index));
7837
+ });
7838
+ });
7839
+ return slots;
7840
+ }
7841
+ __name(assignPaintOrder, "assignPaintOrder");
7331
7842
 
7332
7843
  // core/systems/UIPanelSystem.ts
7333
- var FACE_RENDER_ORDER = 10;
7334
7844
  var CHILD_Z = 2e-3;
7845
+ var DESCENDANT_WALK_INTERVAL = 30;
7846
+ var METER_STEPS = 200;
7847
+ var CLEAR_CHROME = {
7848
+ backgroundColor: "#000000",
7849
+ backgroundOpacity: 0,
7850
+ cornerRadius: 0,
7851
+ borderWidth: 0,
7852
+ borderColor: "#000000"
7853
+ };
7854
+ var PAINT_STAMP_KEY = "__uiPaintStamp";
7335
7855
  var _UIPanelSystem = class _UIPanelSystem {
7336
7856
  constructor(options) {
7337
7857
  this.entries = /* @__PURE__ */ new Map();
7858
+ this.framesUntilDescendantWalk = 0;
7859
+ /** Whether any panel defines tokens, so plain scenes skip the parent walk. */
7860
+ this.anyTokens = false;
7861
+ this.drawAssets = {
7862
+ image: /* @__PURE__ */ __name((src) => uiImage(src), "image"),
7863
+ scratch: /* @__PURE__ */ __name((width, height) => {
7864
+ const canvas = this.createCanvas();
7865
+ canvas.width = width;
7866
+ canvas.height = height;
7867
+ return canvas.getContext("2d");
7868
+ }, "scratch")
7869
+ };
7338
7870
  this.createCanvas = options?.createCanvas ?? (() => document.createElement("canvas"));
7339
7871
  }
7340
7872
  update(world, deltaTime = 0) {
7341
7873
  const live = /* @__PURE__ */ new Set();
7874
+ this.anyTokens = world.query(UIPanelComponent).some((id) => world.getComponent(id, UIPanelComponent)?.tokens);
7342
7875
  for (const entityId of world.query(UIPanelComponent, Object3DRef)) {
7343
7876
  const panel = world.getComponent(entityId, UIPanelComponent);
7344
7877
  if (!panel) continue;
@@ -7376,6 +7909,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7376
7909
  this.entries.delete(entityId);
7377
7910
  }
7378
7911
  }
7912
+ this.applyPaintOrder(world);
7379
7913
  }
7380
7914
  /** All GPU/canvas resources; call on world teardown. */
7381
7915
  dispose() {
@@ -7384,84 +7918,188 @@ var _UIPanelSystem = class _UIPanelSystem {
7384
7918
  }
7385
7919
  // ── Widgets ────────────────────────────────────────────────────────────────
7386
7920
  syncPanel(world, entityId, panel) {
7387
- const size = resolveCanvasSize(panel.width, panel.height, panel.pixelDensity);
7388
- const style = {
7389
- backgroundColor: panel.backgroundColor,
7390
- backgroundOpacity: panel.backgroundOpacity,
7391
- cornerRadiusMeters: panel.cornerRadius,
7392
- borderWidthMeters: panel.borderWidth,
7393
- borderColor: panel.borderColor
7394
- };
7395
- const signature = panelSignature(panel.width, panel.height, size, style);
7396
- this.rebuildIfChanged(world, entityId, signature, panel.width, panel.height, size, (ctx) => {
7397
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, style);
7398
- });
7921
+ const chrome = resolveChrome(
7922
+ panel,
7923
+ panel.style,
7924
+ this.tokensFor(world, entityId)
7925
+ );
7926
+ const bleed = chromeBleed(chrome);
7927
+ const size = resolveCanvasSize(
7928
+ panel.width + bleed * 2,
7929
+ panel.height + bleed * 2,
7930
+ panel.pixelDensity
7931
+ );
7932
+ const signature = panelSignature(
7933
+ panel.width,
7934
+ panel.height,
7935
+ size,
7936
+ chrome,
7937
+ imagesLoaded(chrome)
7938
+ );
7939
+ this.rebuildIfChanged(
7940
+ world,
7941
+ entityId,
7942
+ signature,
7943
+ panel,
7944
+ bleed,
7945
+ size,
7946
+ (ctx) => {
7947
+ drawChrome(
7948
+ ctx,
7949
+ layoutBox(panel, bleed, size.density),
7950
+ size.density,
7951
+ chrome,
7952
+ this.drawAssets
7953
+ );
7954
+ },
7955
+ THREE26.DoubleSide
7956
+ );
7399
7957
  }
7400
7958
  syncText(world, entityId, text) {
7401
- const size = resolveCanvasSize(text.width, text.height);
7402
- const style = {
7403
- fontSizeMeters: text.fontSize,
7404
- fontWeight: text.fontWeight,
7405
- color: text.color,
7406
- align: text.align,
7407
- valign: text.valign,
7408
- lineHeight: text.lineHeight,
7409
- paddingMeters: text.padding
7410
- };
7411
- const signature = textSignature(text.width, text.height, size, text.text, style);
7412
- this.rebuildIfChanged(world, entityId, signature, text.width, text.height, size, (ctx) => {
7413
- ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
7414
- drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, text.text, style);
7415
- });
7959
+ const tokens = this.tokensFor(world, entityId);
7960
+ const chrome = text.style ? resolveChrome(CLEAR_CHROME, text.style, tokens) : null;
7961
+ const bleed = chrome ? chromeBleed(chrome) : 0;
7962
+ const size = resolveCanvasSize(
7963
+ text.width + bleed * 2,
7964
+ text.height + bleed * 2,
7965
+ text.pixelDensity
7966
+ );
7967
+ const style = resolveText(text, text.style, tokens);
7968
+ const family = familyOf(style.font);
7969
+ const signature = JSON.stringify([
7970
+ textSignature(text.width, text.height, size, text.text, style, family),
7971
+ chrome,
7972
+ chrome ? imagesLoaded(chrome) : null
7973
+ ]);
7974
+ this.rebuildIfChanged(
7975
+ world,
7976
+ entityId,
7977
+ signature,
7978
+ text,
7979
+ bleed,
7980
+ size,
7981
+ (ctx) => {
7982
+ const box = layoutBox(text, bleed, size.density);
7983
+ if (chrome) {
7984
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
7985
+ }
7986
+ drawTextBlock(
7987
+ ctx,
7988
+ box,
7989
+ size.density,
7990
+ text.text,
7991
+ style,
7992
+ family,
7993
+ this.drawAssets
7994
+ );
7995
+ }
7996
+ );
7416
7997
  }
7417
7998
  syncButton(world, entityId, button) {
7418
- const size = resolveCanvasSize(button.width, button.height);
7419
- const chrome = {
7420
- backgroundColor: button.backgroundColor,
7421
- backgroundOpacity: button.backgroundOpacity,
7422
- cornerRadiusMeters: button.cornerRadius,
7423
- borderWidthMeters: button.borderWidth,
7424
- borderColor: button.borderColor
7425
- };
7426
- const label = {
7427
- fontSizeMeters: button.fontSize,
7428
- fontWeight: button.fontWeight,
7429
- color: button.textColor,
7430
- align: "center",
7431
- valign: "middle",
7432
- lineHeight: 1.2,
7433
- paddingMeters: 0.01
7434
- };
7999
+ const tokens = this.tokensFor(world, entityId);
8000
+ const chrome = resolveChrome(button, button.style, tokens);
8001
+ const bleed = chromeBleed(chrome);
8002
+ const size = resolveCanvasSize(
8003
+ button.width + bleed * 2,
8004
+ button.height + bleed * 2,
8005
+ button.pixelDensity
8006
+ );
8007
+ const label = resolveText(
8008
+ {
8009
+ fontSize: button.fontSize,
8010
+ fontWeight: button.fontWeight,
8011
+ color: button.textColor,
8012
+ align: "center",
8013
+ valign: "middle",
8014
+ lineHeight: 1.2,
8015
+ padding: 0.01
8016
+ },
8017
+ button.style,
8018
+ tokens
8019
+ );
8020
+ const family = familyOf(label.font);
8021
+ const icon = button.icon ? resolveToken(button.icon, tokens) : null;
7435
8022
  const signature = buttonSignature(
7436
8023
  button.width,
7437
8024
  button.height,
7438
8025
  size,
7439
8026
  chrome,
7440
8027
  button.label,
7441
- label
8028
+ label,
8029
+ family,
8030
+ [imagesLoaded(chrome), icon, icon ? !!uiImage(icon) : false]
8031
+ );
8032
+ this.rebuildIfChanged(
8033
+ world,
8034
+ entityId,
8035
+ signature,
8036
+ button,
8037
+ bleed,
8038
+ size,
8039
+ (ctx) => {
8040
+ const box = layoutBox(button, bleed, size.density);
8041
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
8042
+ if (icon) {
8043
+ drawIconLabel(
8044
+ ctx,
8045
+ box,
8046
+ size.density,
8047
+ button.label,
8048
+ label,
8049
+ family,
8050
+ icon,
8051
+ button.iconPosition ?? "left",
8052
+ this.drawAssets
8053
+ );
8054
+ } else {
8055
+ drawTextBlock(
8056
+ ctx,
8057
+ box,
8058
+ size.density,
8059
+ button.label,
8060
+ label,
8061
+ family,
8062
+ this.drawAssets
8063
+ );
8064
+ }
8065
+ }
7442
8066
  );
7443
- this.rebuildIfChanged(world, entityId, signature, button.width, button.height, size, (ctx) => {
7444
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
7445
- drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, button.label, label);
7446
- });
7447
8067
  }
7448
8068
  syncToggle(world, entityId, toggle, deltaTime) {
7449
- const size = resolveCanvasSize(toggle.width, toggle.height);
7450
- const chrome = {
7451
- backgroundColor: toggle.value ? toggle.accentColor : toggle.trackColor,
7452
- backgroundOpacity: 1,
7453
- cornerRadiusMeters: toggle.height / 2,
7454
- borderWidthMeters: 0,
7455
- borderColor: "#000000"
7456
- };
8069
+ const tokens = this.tokensFor(world, entityId);
8070
+ const accent = resolveToken(toggle.accentColor, tokens);
8071
+ const track = resolveToken(toggle.trackColor, tokens);
8072
+ const knobColor = resolveToken(toggle.knobColor, tokens);
8073
+ const size = resolveCanvasSize(
8074
+ toggle.width,
8075
+ toggle.height,
8076
+ toggle.pixelDensity
8077
+ );
8078
+ const variant = toggle.variant ?? "switch";
8079
+ if (variant !== "switch") {
8080
+ this.syncMarkToggle(world, entityId, toggle, accent, track, knobColor);
8081
+ return;
8082
+ }
8083
+ const chrome = resolveChrome(
8084
+ {
8085
+ backgroundColor: toggle.value ? accent : track,
8086
+ backgroundOpacity: 1,
8087
+ cornerRadius: toggle.height / 2,
8088
+ borderWidth: 0,
8089
+ borderColor: "#000000"
8090
+ },
8091
+ toggle.style,
8092
+ tokens
8093
+ );
7457
8094
  const signature = JSON.stringify([
7458
8095
  trackSignature(
7459
8096
  toggle.width,
7460
8097
  toggle.height,
7461
8098
  size,
7462
8099
  chrome,
7463
- toggle.accentColor,
7464
- toggle.knobColor
8100
+ accent,
8101
+ knobColor,
8102
+ variant
7465
8103
  ),
7466
8104
  toggle.value
7467
8105
  ]);
@@ -7469,18 +8107,24 @@ var _UIPanelSystem = class _UIPanelSystem {
7469
8107
  world,
7470
8108
  entityId,
7471
8109
  signature,
7472
- toggle.width,
7473
- toggle.height,
8110
+ toggle,
8111
+ 0,
7474
8112
  size,
7475
8113
  (ctx) => {
7476
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
8114
+ drawChrome(
8115
+ ctx,
8116
+ layoutBox(toggle, 0, size.density),
8117
+ size.density,
8118
+ chrome,
8119
+ this.drawAssets
8120
+ );
7477
8121
  }
7478
8122
  );
7479
8123
  if (!entry) return;
7480
8124
  if (!entry.knob) {
7481
8125
  entry.knob = this.makeDisc(
7482
8126
  toggle.height * 0.4,
7483
- toggle.knobColor,
8127
+ knobColor,
7484
8128
  entry.face.parent,
7485
8129
  CHILD_Z
7486
8130
  );
@@ -7492,35 +8136,95 @@ var _UIPanelSystem = class _UIPanelSystem {
7492
8136
  knob.position.x += (target - knob.position.x) * (deltaTime > 0 ? ease : 1);
7493
8137
  knob.position.z = CHILD_Z;
7494
8138
  }
8139
+ /** Checkbox and radio: a box or ring at the left of the widget's area. */
8140
+ syncMarkToggle(world, entityId, toggle, accent, track, markColor) {
8141
+ const tokens = this.tokensFor(world, entityId);
8142
+ const radio = toggle.variant === "radio";
8143
+ const size = resolveCanvasSize(
8144
+ toggle.width,
8145
+ toggle.height,
8146
+ toggle.pixelDensity
8147
+ );
8148
+ const side = toggle.height;
8149
+ const chrome = resolveChrome(
8150
+ {
8151
+ backgroundColor: toggle.value ? accent : track,
8152
+ backgroundOpacity: 1,
8153
+ cornerRadius: radio ? side / 2 : side * 0.2,
8154
+ borderWidth: 0,
8155
+ borderColor: "#000000"
8156
+ },
8157
+ toggle.style,
8158
+ tokens
8159
+ );
8160
+ const signature = JSON.stringify([
8161
+ trackSignature(
8162
+ toggle.width,
8163
+ toggle.height,
8164
+ size,
8165
+ chrome,
8166
+ accent,
8167
+ markColor,
8168
+ toggle.variant ?? null
8169
+ ),
8170
+ toggle.value
8171
+ ]);
8172
+ this.rebuildIfChanged(world, entityId, signature, toggle, 0, size, (ctx) => {
8173
+ const px = side * size.density;
8174
+ const box = { x: 0, y: 0, w: px, h: px };
8175
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
8176
+ if (!toggle.value) return;
8177
+ if (radio) drawDot(ctx, px / 2, px / 2, px * 0.24, markColor);
8178
+ else drawCheckMark(ctx, box, markColor);
8179
+ });
8180
+ }
7495
8181
  syncSlider(world, entityId, slider) {
7496
- const size = resolveCanvasSize(slider.width, slider.height);
8182
+ const tokens = this.tokensFor(world, entityId);
8183
+ const accent = resolveToken(slider.accentColor, tokens);
8184
+ const track = resolveToken(slider.trackColor, tokens);
8185
+ const knobColor = resolveToken(slider.knobColor, tokens);
8186
+ const size = resolveCanvasSize(
8187
+ slider.width,
8188
+ slider.height,
8189
+ slider.pixelDensity
8190
+ );
8191
+ const range = Math.max(1e-6, slider.max - slider.min);
8192
+ const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
8193
+ if (slider.variant === "progress" || slider.variant === "ring") {
8194
+ this.syncMeter(world, entityId, slider, t, accent, track, knobColor);
8195
+ return;
8196
+ }
7497
8197
  const trackThickness = slider.height * 0.32;
7498
8198
  const knobRadius = slider.height * 0.45;
7499
8199
  const usable = Math.max(1e-3, slider.width - slider.height);
7500
- const chrome = {
7501
- backgroundColor: slider.trackColor,
7502
- backgroundOpacity: 1,
7503
- cornerRadiusMeters: trackThickness / 2,
7504
- borderWidthMeters: 0,
7505
- borderColor: "#000000"
7506
- };
8200
+ const chrome = resolveChrome(
8201
+ {
8202
+ backgroundColor: track,
8203
+ backgroundOpacity: 1,
8204
+ cornerRadius: trackThickness / 2,
8205
+ borderWidth: 0,
8206
+ borderColor: "#000000"
8207
+ },
8208
+ void 0,
8209
+ null
8210
+ );
7507
8211
  const signature = trackSignature(
7508
8212
  slider.width,
7509
8213
  slider.height,
7510
8214
  size,
7511
8215
  chrome,
7512
- slider.accentColor,
7513
- slider.knobColor
8216
+ accent,
8217
+ knobColor,
8218
+ slider.variant ?? null
7514
8219
  );
7515
8220
  const entry = this.rebuildIfChanged(
7516
8221
  world,
7517
8222
  entityId,
7518
8223
  signature,
7519
- slider.width,
7520
- slider.height,
8224
+ slider,
8225
+ 0,
7521
8226
  size,
7522
8227
  (ctx) => {
7523
- ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
7524
8228
  const trackWidthM = usable + trackThickness;
7525
8229
  drawRoundedBar(
7526
8230
  ctx,
@@ -7528,7 +8232,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7528
8232
  (slider.height - trackThickness) / 2 * size.density,
7529
8233
  trackWidthM * size.density,
7530
8234
  trackThickness * size.density,
7531
- slider.trackColor
8235
+ track
7532
8236
  );
7533
8237
  }
7534
8238
  );
@@ -7537,13 +8241,13 @@ var _UIPanelSystem = class _UIPanelSystem {
7537
8241
  entry.fill = this.makeQuad(
7538
8242
  1,
7539
8243
  trackThickness,
7540
- slider.accentColor,
8244
+ accent,
7541
8245
  entry.face.parent,
7542
8246
  CHILD_Z / 2
7543
8247
  );
7544
8248
  entry.cap = this.makeDisc(
7545
8249
  trackThickness / 2,
7546
- slider.accentColor,
8250
+ accent,
7547
8251
  entry.face.parent,
7548
8252
  CHILD_Z / 2,
7549
8253
  Math.PI / 2,
@@ -7554,51 +8258,124 @@ var _UIPanelSystem = class _UIPanelSystem {
7554
8258
  if (!entry.knob) {
7555
8259
  entry.knob = this.makeDisc(
7556
8260
  knobRadius,
7557
- slider.knobColor,
8261
+ knobColor,
7558
8262
  entry.face.parent,
7559
8263
  CHILD_Z
7560
8264
  );
7561
8265
  }
7562
- const range = Math.max(1e-6, slider.max - slider.min);
7563
- const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
7564
8266
  entry.knob.position.x = -usable / 2 + t * usable;
7565
8267
  entry.knob.position.z = CHILD_Z;
7566
8268
  entry.fill.scale.x = Math.max(1e-4, t * usable);
7567
8269
  entry.fill.position.x = -usable / 2 + t * usable / 2;
7568
8270
  entry.fill.position.z = CHILD_Z / 2;
7569
8271
  }
8272
+ /**
8273
+ * Progress bars and rings. Unlike a slider these change rarely and have no
8274
+ * drag to keep smooth, so the value is drawn, in steps of half a percent.
8275
+ */
8276
+ syncMeter(world, entityId, slider, t, accent, track, knobColor) {
8277
+ const size = resolveCanvasSize(
8278
+ slider.width,
8279
+ slider.height,
8280
+ slider.pixelDensity
8281
+ );
8282
+ const shown = Math.round(t * METER_STEPS) / METER_STEPS;
8283
+ const signature = JSON.stringify([
8284
+ slider.width,
8285
+ slider.height,
8286
+ size,
8287
+ slider.variant,
8288
+ accent,
8289
+ track,
8290
+ knobColor,
8291
+ shown
8292
+ ]);
8293
+ this.rebuildIfChanged(world, entityId, signature, slider, 0, size, (ctx) => {
8294
+ const box = {
8295
+ x: 0,
8296
+ y: 0,
8297
+ w: size.pxWidth,
8298
+ h: size.pxHeight
8299
+ };
8300
+ if (slider.variant === "ring") {
8301
+ drawRing(ctx, box, shown, track, accent);
8302
+ return;
8303
+ }
8304
+ drawRoundedBar(ctx, 0, 0, box.w, box.h, track);
8305
+ if (shown <= 0) return;
8306
+ const fill = Math.max(box.h, box.w * shown);
8307
+ drawRoundedBar(ctx, 0, 0, fill, box.h, accent);
8308
+ });
8309
+ }
8310
+ /** The tokens in scope for a widget, nearest panel first. */
8311
+ tokensFor(world, entityId) {
8312
+ if (!this.anyTokens) return null;
8313
+ let merged = null;
8314
+ const visited = /* @__PURE__ */ new Set();
8315
+ let current = entityId;
8316
+ while (current !== null && !visited.has(current)) {
8317
+ visited.add(current);
8318
+ const tokens = world.getComponent(current, UIPanelComponent)?.tokens;
8319
+ if (tokens) merged = { ...tokens, ...merged ?? {} };
8320
+ const parent = world.getComponent(current, ParentId)?.value;
8321
+ current = typeof parent === "number" ? parent : null;
8322
+ }
8323
+ return merged;
8324
+ }
7570
8325
  // ── Machinery ──────────────────────────────────────────────────────────────
7571
- rebuildIfChanged(world, entityId, signature, widthM, heightM, size, draw) {
8326
+ rebuildIfChanged(world, entityId, signature, widget, bleed, size, draw, side = THREE26.FrontSide) {
7572
8327
  const root = world.getComponent(entityId, Object3DRef)?.object;
7573
8328
  if (!root) return null;
8329
+ const widthM = widget.width;
8330
+ const heightM = widget.height;
8331
+ const faceWidth = widthM + bleed * 2;
8332
+ const faceHeight = heightM + bleed * 2;
7574
8333
  let entry = this.entries.get(entityId);
8334
+ if (entry) entry.opacity = widget.style?.opacity ?? 1;
7575
8335
  if (entry && entry.signature === signature) return entry;
7576
8336
  if (entry) this.disposeMovingParts(entry);
7577
8337
  if (!entry) {
7578
8338
  const canvas2 = this.createCanvas();
7579
- const texture = new THREE19.CanvasTexture(
8339
+ const texture = new THREE26.CanvasTexture(
7580
8340
  canvas2
7581
8341
  );
7582
8342
  this.configureTexture(world, texture);
7583
- const material = new THREE19.MeshBasicMaterial({
8343
+ const material = new THREE26.MeshBasicMaterial({
7584
8344
  map: texture,
7585
8345
  transparent: true,
7586
8346
  depthWrite: false,
7587
8347
  // UI colours are authored absolutes; scene tone mapping would shift
7588
8348
  // them (the classic "why is my white UI beige" bug).
7589
8349
  toneMapped: false,
7590
- side: THREE19.DoubleSide,
7591
- premultipliedAlpha: true
8350
+ side,
8351
+ premultipliedAlpha: false,
8352
+ blending: THREE26.CustomBlending,
8353
+ blendSrc: THREE26.OneFactor,
8354
+ blendDst: THREE26.OneMinusSrcAlphaFactor,
8355
+ blendSrcAlpha: THREE26.OneFactor,
8356
+ blendDstAlpha: THREE26.OneMinusSrcAlphaFactor
7592
8357
  });
7593
- const face = new THREE19.Mesh(
7594
- new THREE19.PlaneGeometry(widthM, heightM),
8358
+ const face = new THREE26.Mesh(
8359
+ new THREE26.PlaneGeometry(faceWidth, faceHeight),
7595
8360
  material
7596
8361
  );
7597
- face.renderOrder = FACE_RENDER_ORDER;
8362
+ tagPart(face, 16777215, true);
8363
+ face.raycast = () => {
8364
+ };
7598
8365
  root.add(face);
8366
+ const hit = new THREE26.Mesh(
8367
+ new THREE26.PlaneGeometry(widthM, heightM),
8368
+ new THREE26.MeshBasicMaterial({ visible: false, side })
8369
+ );
8370
+ root.add(hit);
7599
8371
  entry = {
7600
8372
  signature: "",
8373
+ root,
8374
+ width: widthM,
8375
+ height: heightM,
8376
+ opacity: widget.style?.opacity ?? 1,
7601
8377
  face,
8378
+ hit,
7602
8379
  texture,
7603
8380
  material,
7604
8381
  canvas: canvas2,
@@ -7612,23 +8389,26 @@ var _UIPanelSystem = class _UIPanelSystem {
7612
8389
  if (canvas.width !== size.pxWidth || canvas.height !== size.pxHeight) {
7613
8390
  canvas.width = size.pxWidth;
7614
8391
  canvas.height = size.pxHeight;
8392
+ if (entry.signature) entry.texture.dispose();
7615
8393
  }
7616
- const params = entry.face.geometry.parameters;
7617
- if (params.width !== widthM || params.height !== heightM) {
7618
- entry.face.geometry.dispose();
7619
- entry.face.geometry = new THREE19.PlaneGeometry(widthM, heightM);
7620
- }
8394
+ entry.width = widthM;
8395
+ entry.height = heightM;
8396
+ resizePlane(entry.face, faceWidth, faceHeight);
8397
+ resizePlane(entry.hit, widthM, heightM);
7621
8398
  const ctx = canvas.getContext("2d");
7622
- if (ctx) draw(ctx);
8399
+ if (ctx) {
8400
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
8401
+ draw(ctx);
8402
+ }
7623
8403
  entry.texture.needsUpdate = true;
7624
8404
  entry.signature = signature;
7625
8405
  return entry;
7626
8406
  }
7627
8407
  configureTexture(world, texture) {
7628
- texture.colorSpace = THREE19.SRGBColorSpace;
8408
+ texture.colorSpace = THREE26.SRGBColorSpace;
7629
8409
  texture.generateMipmaps = true;
7630
- texture.minFilter = THREE19.LinearMipmapLinearFilter;
7631
- texture.magFilter = THREE19.LinearFilter;
8410
+ texture.minFilter = THREE26.LinearMipmapLinearFilter;
8411
+ texture.magFilter = THREE26.LinearFilter;
7632
8412
  texture.premultiplyAlpha = true;
7633
8413
  try {
7634
8414
  const renderer = world.getRenderer();
@@ -7639,7 +8419,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7639
8419
  }
7640
8420
  makeQuad(widthM, heightM, color, parent, z) {
7641
8421
  return this.addPart(
7642
- new THREE19.PlaneGeometry(widthM, heightM),
8422
+ new THREE26.PlaneGeometry(widthM, heightM),
7643
8423
  color,
7644
8424
  parent,
7645
8425
  z
@@ -7648,24 +8428,23 @@ var _UIPanelSystem = class _UIPanelSystem {
7648
8428
  /** A filled disc (or arc segment, for the fill's rounded cap). */
7649
8429
  makeDisc(radiusM, color, parent, z, thetaStart = 0, thetaLength = Math.PI * 2) {
7650
8430
  return this.addPart(
7651
- new THREE19.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
8431
+ new THREE26.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
7652
8432
  color,
7653
8433
  parent,
7654
8434
  z
7655
8435
  );
7656
8436
  }
7657
8437
  addPart(geometry, color, parent, z) {
7658
- const mesh = new THREE19.Mesh(
8438
+ const mesh = new THREE26.Mesh(
7659
8439
  geometry,
7660
- new THREE19.MeshBasicMaterial({
8440
+ new THREE26.MeshBasicMaterial({
7661
8441
  color,
7662
8442
  transparent: true,
7663
8443
  depthWrite: false,
7664
- toneMapped: false,
7665
- side: THREE19.DoubleSide
8444
+ toneMapped: false
7666
8445
  })
7667
8446
  );
7668
- mesh.renderOrder = FACE_RENDER_ORDER + 1;
8447
+ tagPart(mesh, color);
7669
8448
  mesh.position.z = z;
7670
8449
  mesh.raycast = () => {
7671
8450
  };
@@ -7696,22 +8475,144 @@ var _UIPanelSystem = class _UIPanelSystem {
7696
8475
  const object = world.getComponent(entityId, Object3DRef)?.object;
7697
8476
  if (!transform || !object) return;
7698
8477
  const camera = world.getCamera();
7699
- const cam = new THREE19.Vector3();
7700
- const self = new THREE19.Vector3();
8478
+ const cam = new THREE26.Vector3();
8479
+ const self = new THREE26.Vector3();
7701
8480
  camera.getWorldPosition(cam);
7702
8481
  object.getWorldPosition(self);
7703
8482
  transform.rotation.y = Math.atan2(cam.x - self.x, cam.z - self.z);
7704
8483
  }
8484
+ /**
8485
+ * Stamp every widget's meshes with this frame's paint order. Written each
8486
+ * frame because the eye moves: the order between trees can change at any
8487
+ * time, and a plain property write costs less than checking it.
8488
+ */
8489
+ applyPaintOrder(world) {
8490
+ if (this.entries.size === 0) return;
8491
+ const camera = world.getCamera();
8492
+ camera.getWorldPosition(eyeScratch);
8493
+ const nodes = [...this.entries.keys()].map((id) => ({
8494
+ id,
8495
+ parent: nearestWidgetAncestor(world, id)
8496
+ }));
8497
+ const trees = buildPaintTrees(nodes);
8498
+ const rects = /* @__PURE__ */ new Map();
8499
+ for (const tree of trees) {
8500
+ const entry = this.entries.get(tree.rootId);
8501
+ if (entry) rects.set(tree.rootId, rootRect(tree.rootId, entry));
8502
+ }
8503
+ const slots = assignPaintOrder(trees, rects, eyeScratch);
8504
+ this.framesUntilDescendantWalk -= 1;
8505
+ const walkDescendants = this.framesUntilDescendantWalk <= 0;
8506
+ if (walkDescendants) {
8507
+ this.framesUntilDescendantWalk = DESCENDANT_WALK_INTERVAL;
8508
+ }
8509
+ const widgetRoots = /* @__PURE__ */ new Set();
8510
+ if (walkDescendants) {
8511
+ for (const entry of this.entries.values()) widgetRoots.add(entry.root);
8512
+ }
8513
+ for (const [entityId, entry] of this.entries) {
8514
+ const slot = slots.get(entityId);
8515
+ if (!slot) continue;
8516
+ entry.face.renderOrder = slot.face;
8517
+ for (const part of [entry.knob, entry.fill, entry.cap]) {
8518
+ if (part) part.renderOrder = slot.parts;
8519
+ }
8520
+ if (walkDescendants) stampDescendants(entry.root, slot, widgetRoots);
8521
+ }
8522
+ const effective = /* @__PURE__ */ new Map();
8523
+ const parentOf = new Map(nodes.map((node) => [node.id, node.parent]));
8524
+ for (const tree of trees) {
8525
+ for (const id of tree.order) {
8526
+ const entry = this.entries.get(id);
8527
+ if (!entry) continue;
8528
+ const parent = parentOf.get(id);
8529
+ const inherited = parent != null ? effective.get(parent) ?? 1 : 1;
8530
+ const opacity = entry.opacity * inherited;
8531
+ effective.set(id, opacity);
8532
+ for (const mesh of [entry.face, entry.knob, entry.fill, entry.cap]) {
8533
+ if (!mesh) continue;
8534
+ const tag = mesh.userData[UI_PART_KEY];
8535
+ if (tag.opacity === opacity) continue;
8536
+ tag.opacity = opacity;
8537
+ applyPartColor(mesh, tag);
8538
+ }
8539
+ }
8540
+ }
8541
+ }
7705
8542
  destroyEntry(entry) {
7706
8543
  this.disposeMovingParts(entry);
7707
8544
  entry.face.removeFromParent();
7708
8545
  entry.face.geometry.dispose();
7709
8546
  entry.face.material.dispose();
8547
+ entry.hit.removeFromParent();
8548
+ entry.hit.geometry.dispose();
8549
+ entry.hit.material.dispose();
7710
8550
  entry.texture.dispose();
7711
8551
  }
7712
8552
  };
7713
8553
  __name(_UIPanelSystem, "UIPanelSystem");
7714
8554
  var UIPanelSystem = _UIPanelSystem;
8555
+ var eyeScratch = new THREE26.Vector3();
8556
+ function tagPart(mesh, color, premultiplied = false) {
8557
+ const tag = {
8558
+ baseColor: new THREE26.Color(color),
8559
+ tint: 1,
8560
+ opacity: 1,
8561
+ premultiplied
8562
+ };
8563
+ mesh.userData[UI_PART_KEY] = tag;
8564
+ }
8565
+ __name(tagPart, "tagPart");
8566
+ function resizePlane(mesh, width, height) {
8567
+ const params = mesh.geometry.parameters;
8568
+ if (params.width === width && params.height === height) return;
8569
+ mesh.geometry.dispose();
8570
+ mesh.geometry = new THREE26.PlaneGeometry(width, height);
8571
+ }
8572
+ __name(resizePlane, "resizePlane");
8573
+ function layoutBox(widget, bleed, density) {
8574
+ return {
8575
+ x: bleed * density,
8576
+ y: bleed * density,
8577
+ w: widget.width * density,
8578
+ h: widget.height * density
8579
+ };
8580
+ }
8581
+ __name(layoutBox, "layoutBox");
8582
+ function familyOf(font) {
8583
+ return font ? uiFontFamily(font) : null;
8584
+ }
8585
+ __name(familyOf, "familyOf");
8586
+ function imagesLoaded(chrome) {
8587
+ return styleAssets(chrome, null).images.map((src) => !!uiImage(src));
8588
+ }
8589
+ __name(imagesLoaded, "imagesLoaded");
8590
+ function rootRect(id, entry) {
8591
+ const e = entry.root.matrixWorld.elements;
8592
+ const halfW = entry.width / 2;
8593
+ const halfH = entry.height / 2;
8594
+ return {
8595
+ id,
8596
+ center: { x: e[12], y: e[13], z: e[14] },
8597
+ halfX: { x: e[0] * halfW, y: e[1] * halfW, z: e[2] * halfW },
8598
+ halfY: { x: e[4] * halfH, y: e[5] * halfH, z: e[6] * halfH }
8599
+ };
8600
+ }
8601
+ __name(rootRect, "rootRect");
8602
+ function stampDescendants(root, slot, widgetRoots) {
8603
+ for (const child of root.children) {
8604
+ if (widgetRoots.has(child) || child.userData[UI_PART_KEY]) continue;
8605
+ const material = child.material;
8606
+ const transparent = Array.isArray(material) ? material.some((m) => m.transparent) : material?.transparent === true;
8607
+ const untouched = child.renderOrder === 0 || child.userData[PAINT_STAMP_KEY] === child.renderOrder;
8608
+ if (transparent && untouched) {
8609
+ child.renderOrder = slot.descendants;
8610
+ child.userData[PAINT_STAMP_KEY] = slot.descendants;
8611
+ }
8612
+ stampDescendants(child, slot, widgetRoots);
8613
+ }
8614
+ }
8615
+ __name(stampDescendants, "stampDescendants");
7715
8616
  var _XRHitTestSystem = class _XRHitTestSystem {
7716
8617
  constructor() {
7717
8618
  this.sources = /* @__PURE__ */ new Map();
@@ -7748,12 +8649,12 @@ var _XRHitTestSystem = class _XRHitTestSystem {
7748
8649
  if (!hits || hits.length === 0) continue;
7749
8650
  const pose = hits[0].getPose(referenceSpace);
7750
8651
  if (!pose) continue;
7751
- const matrix = new THREE19.Matrix4().fromArray(
8652
+ const matrix = new THREE26.Matrix4().fromArray(
7752
8653
  pose.transform.matrix
7753
8654
  );
7754
- const position = new THREE19.Vector3();
7755
- const quaternion = new THREE19.Quaternion();
7756
- matrix.decompose(position, quaternion, new THREE19.Vector3());
8655
+ const position = new THREE26.Vector3();
8656
+ const quaternion = new THREE26.Quaternion();
8657
+ matrix.decompose(position, quaternion, new THREE26.Vector3());
7757
8658
  this.results.set(key, { position, quaternion });
7758
8659
  }
7759
8660
  }
@@ -7827,8 +8728,8 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7827
8728
  /** Per-anchor head-glue gates — see XREnvironmentSystem's TrackedEntry. */
7828
8729
  this.gates = /* @__PURE__ */ new Map();
7829
8730
  this.presenting = false;
7830
- this.tmpViewerMatrix = new THREE19.Matrix4();
7831
- this.tmpViewPos = new THREE19.Vector3();
8731
+ this.tmpViewerMatrix = new THREE26.Matrix4();
8732
+ this.tmpViewPos = new THREE26.Vector3();
7832
8733
  }
7833
8734
  update(world, deltaTime = 0) {
7834
8735
  const presenting = world.isXrPresenting();
@@ -7902,8 +8803,8 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7902
8803
  if (typeof frame.createAnchor !== "function") return;
7903
8804
  const RigidTransform = globalThis.XRRigidTransform;
7904
8805
  if (!RigidTransform) return;
7905
- const quaternion = new THREE19.Quaternion().setFromEuler(
7906
- new THREE19.Euler(
8806
+ const quaternion = new THREE26.Quaternion().setFromEuler(
8807
+ new THREE26.Euler(
7907
8808
  transform.rotation.x,
7908
8809
  transform.rotation.y,
7909
8810
  transform.rotation.z,
@@ -7946,13 +8847,13 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7946
8847
  };
7947
8848
  __name(_XRAnchorSystem, "XRAnchorSystem");
7948
8849
  var XRAnchorSystem = _XRAnchorSystem;
7949
- var _q = new THREE19.Quaternion();
7950
- var _q2 = new THREE19.Quaternion();
7951
- var _s = new THREE19.Vector3();
7952
- var _e = new THREE19.Vector3();
7953
- var _w = new THREE19.Vector3();
7954
- var _v = new THREE19.Vector3();
7955
- var _p = new THREE19.Vector3();
8850
+ var _q = new THREE26.Quaternion();
8851
+ var _q2 = new THREE26.Quaternion();
8852
+ var _s = new THREE26.Vector3();
8853
+ var _e = new THREE26.Vector3();
8854
+ var _w = new THREE26.Vector3();
8855
+ var _v = new THREE26.Vector3();
8856
+ var _p = new THREE26.Vector3();
7956
8857
  function setWorldQuaternion(o, q) {
7957
8858
  if (o.parent) {
7958
8859
  o.parent.getWorldQuaternion(_q2).invert();
@@ -7977,31 +8878,31 @@ function twoBoneIK(upper, lower, end, target, pole) {
7977
8878
  if (a < 1e-6 || b < 1e-6) return target.distanceTo(_w);
7978
8879
  const toT = _v.subVectors(target, _s);
7979
8880
  const want = toT.length();
7980
- const c = THREE19.MathUtils.clamp(want, Math.abs(a - b) + 1e-4, a + b - 1e-4);
8881
+ const c = THREE26.MathUtils.clamp(want, Math.abs(a - b) + 1e-4, a + b - 1e-4);
7981
8882
  const dir = toT.divideScalar(want || 1).clone();
7982
8883
  const pv = _p.subVectors(pole, _s);
7983
8884
  pv.addScaledVector(dir, -pv.dot(dir));
7984
8885
  if (pv.lengthSq() < 1e-8) pv.set(0, -1, 0).addScaledVector(dir, dir.y);
7985
8886
  pv.normalize();
7986
- const cosA = THREE19.MathUtils.clamp((a * a + c * c - b * b) / (2 * a * c), -1, 1);
8887
+ const cosA = THREE26.MathUtils.clamp((a * a + c * c - b * b) / (2 * a * c), -1, 1);
7987
8888
  const joint = _s.clone().addScaledVector(dir, a * cosA).addScaledVector(pv, a * Math.sqrt(1 - cosA * cosA));
7988
- turn(upper, new THREE19.Quaternion().setFromUnitVectors(_e.clone().sub(_s).normalize(), joint.clone().sub(_s).normalize()));
8889
+ turn(upper, new THREE26.Quaternion().setFromUnitVectors(_e.clone().sub(_s).normalize(), joint.clone().sub(_s).normalize()));
7989
8890
  lower.getWorldPosition(_e);
7990
8891
  end.getWorldPosition(_w);
7991
8892
  const reach = _s.clone().addScaledVector(dir, c);
7992
- turn(lower, new THREE19.Quaternion().setFromUnitVectors(_w.clone().sub(_e).normalize(), reach.clone().sub(_e).normalize()));
8893
+ turn(lower, new THREE26.Quaternion().setFromUnitVectors(_w.clone().sub(_e).normalize(), reach.clone().sub(_e).normalize()));
7993
8894
  return want - c;
7994
8895
  }
7995
8896
  __name(twoBoneIK, "twoBoneIK");
7996
- function gripQuaternion(axis, back, forearm, out = new THREE19.Quaternion()) {
8897
+ function gripQuaternion(axis, back, forearm, out = new THREE26.Quaternion()) {
7997
8898
  let X = axis.clone();
7998
8899
  const Z = back.clone().addScaledVector(X, -back.dot(X)).normalize().negate();
7999
- let Y = new THREE19.Vector3().crossVectors(Z, X);
8900
+ let Y = new THREE26.Vector3().crossVectors(Z, X);
8000
8901
  if (Y.dot(forearm) < 0) {
8001
8902
  X = X.negate();
8002
8903
  Y = Y.negate();
8003
8904
  }
8004
- return out.setFromRotationMatrix(new THREE19.Matrix4().makeBasis(X, Y, Z));
8905
+ return out.setFromRotationMatrix(new THREE26.Matrix4().makeBasis(X, Y, Z));
8005
8906
  }
8006
8907
  __name(gripQuaternion, "gripQuaternion");
8007
8908
 
@@ -8012,18 +8913,20 @@ var LIMB_BONES = {
8012
8913
  leftFoot: ["leftUpperLeg", "leftLowerLeg", "leftFoot"],
8013
8914
  rightFoot: ["rightUpperLeg", "rightLowerLeg", "rightFoot"]
8014
8915
  };
8015
- var _t = new THREE19.Vector3();
8016
- var _p2 = new THREE19.Vector3();
8017
- var _q3 = new THREE19.Quaternion();
8018
- var _pq = new THREE19.Quaternion();
8916
+ var _t = new THREE26.Vector3();
8917
+ var _p2 = new THREE26.Vector3();
8918
+ var _q3 = new THREE26.Quaternion();
8919
+ var _pq = new THREE26.Quaternion();
8019
8920
  var _IKSystem = class _IKSystem {
8020
8921
  constructor() {
8021
8922
  this.runtimes = /* @__PURE__ */ new Map();
8022
- this.reach = /* @__PURE__ */ new Map();
8023
8923
  }
8024
- /** How far a limb's end stayed from its target last frame, in metres (0 = reached). */
8924
+ /**
8925
+ * How far a limb's end stayed from its target last frame, in metres
8926
+ * (0 = reached), or null if the limb's chain didn't run.
8927
+ */
8025
8928
  getReachError(entityId, limb) {
8026
- return this.reach.get(`${entityId}:${limb}`) ?? null;
8929
+ return this.runtimes.get(entityId)?.reach.get(limb) ?? null;
8027
8930
  }
8028
8931
  restore(runtime) {
8029
8932
  for (const [bone, w] of runtime.written) {
@@ -8049,13 +8952,14 @@ var _IKSystem = class _IKSystem {
8049
8952
  let runtime = this.runtimes.get(id);
8050
8953
  if (!runtime || runtime.rootUuid !== root.uuid) {
8051
8954
  if (runtime) this.restore(runtime);
8052
- runtime = { rootUuid: root.uuid, limbs: /* @__PURE__ */ new Map(), written: /* @__PURE__ */ new Map() };
8955
+ runtime = { rootUuid: root.uuid, limbs: /* @__PURE__ */ new Map(), written: /* @__PURE__ */ new Map(), reach: /* @__PURE__ */ new Map() };
8053
8956
  this.runtimes.set(id, runtime);
8054
8957
  }
8055
8958
  for (const [bone, w] of runtime.written) {
8056
8959
  if (bone.quaternion.equals(w.written)) bone.quaternion.copy(w.before);
8057
8960
  }
8058
8961
  runtime.written.clear();
8962
+ runtime.reach.clear();
8059
8963
  if (!ik.enabled) continue;
8060
8964
  const active = ik.chains.filter((c) => c.enabled && c.weight > 0 && typeof c.target === "number");
8061
8965
  if (active.length === 0) continue;
@@ -8076,7 +8980,7 @@ var _IKSystem = class _IKSystem {
8076
8980
  const [upper, lower, end] = limb.bones;
8077
8981
  for (const bone of [upper, lower, end]) {
8078
8982
  if (!runtime.written.has(bone)) {
8079
- runtime.written.set(bone, { before: bone.quaternion.clone(), written: new THREE19.Quaternion() });
8983
+ runtime.written.set(bone, { before: bone.quaternion.clone(), written: new THREE26.Quaternion() });
8080
8984
  }
8081
8985
  }
8082
8986
  const animated = [upper.quaternion.clone(), lower.quaternion.clone(), end.quaternion.clone()];
@@ -8085,7 +8989,7 @@ var _IKSystem = class _IKSystem {
8085
8989
  if (poleObject) poleObject.getWorldPosition(_p2);
8086
8990
  else lower.getWorldPosition(_p2);
8087
8991
  const miss = twoBoneIK(upper, lower, end, _t, _p2);
8088
- this.reach.set(`${id}:${chain.limb}`, Math.max(0, miss));
8992
+ runtime.reach.set(chain.limb, Math.max(0, miss));
8089
8993
  if (chain.matchRotation) {
8090
8994
  target.getWorldQuaternion(_q3);
8091
8995
  end.parent?.getWorldQuaternion(_pq);
@@ -8114,9 +9018,9 @@ var IKSystem = _IKSystem;
8114
9018
  function buildGeometry2(area) {
8115
9019
  if (area.shape === "circle") {
8116
9020
  const radius = Math.max(5e-3, area.width / 2);
8117
- return new THREE19.CircleGeometry(radius, 48);
9021
+ return new THREE26.CircleGeometry(radius, 48);
8118
9022
  }
8119
- return new THREE19.PlaneGeometry(
9023
+ return new THREE26.PlaneGeometry(
8120
9024
  Math.max(0.01, area.width),
8121
9025
  Math.max(0.01, area.depth)
8122
9026
  );
@@ -8133,13 +9037,13 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
8133
9037
  update(world) {
8134
9038
  const entityIds = world.query(VRTeleportAreaComponent, Object3DRef);
8135
9039
  const alive = new Set(entityIds);
8136
- for (const tracked of this.signatures.keys()) {
8137
- if (!alive.has(tracked)) this.signatures.delete(tracked);
9040
+ for (const tracked2 of this.signatures.keys()) {
9041
+ if (!alive.has(tracked2)) this.signatures.delete(tracked2);
8138
9042
  }
8139
9043
  for (const entityId of entityIds) {
8140
9044
  const area = world.getComponent(entityId, VRTeleportAreaComponent);
8141
9045
  const object = world.getComponent(entityId, Object3DRef)?.object;
8142
- if (!area || !(object instanceof THREE19.Mesh)) continue;
9046
+ if (!area || !(object instanceof THREE26.Mesh)) continue;
8143
9047
  const signature = signatureOf(area);
8144
9048
  if (this.signatures.get(entityId) === signature) continue;
8145
9049
  this.signatures.set(entityId, signature);
@@ -8151,18 +9055,18 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
8151
9055
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
8152
9056
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
8153
9057
  function createHandMaterial(preset, options) {
8154
- const color = new THREE19.Color(options.color);
9058
+ const color = new THREE26.Color(options.color);
8155
9059
  switch (preset) {
8156
9060
  case "ghost":
8157
- return new THREE19.MeshBasicMaterial({
9061
+ return new THREE26.MeshBasicMaterial({
8158
9062
  color,
8159
9063
  transparent: true,
8160
9064
  opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
8161
9065
  depthWrite: false,
8162
- side: THREE19.DoubleSide
9066
+ side: THREE26.DoubleSide
8163
9067
  });
8164
9068
  case "neon":
8165
- return new THREE19.MeshStandardMaterial({
9069
+ return new THREE26.MeshStandardMaterial({
8166
9070
  color,
8167
9071
  emissive: color,
8168
9072
  emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
@@ -8172,7 +9076,7 @@ function createHandMaterial(preset, options) {
8172
9076
  opacity: options.opacity
8173
9077
  });
8174
9078
  case "skeleton":
8175
- return new THREE19.MeshBasicMaterial({
9079
+ return new THREE26.MeshBasicMaterial({
8176
9080
  color,
8177
9081
  wireframe: true,
8178
9082
  transparent: true,
@@ -8180,7 +9084,7 @@ function createHandMaterial(preset, options) {
8180
9084
  });
8181
9085
  case "custom":
8182
9086
  case "default":
8183
- return new THREE19.MeshStandardMaterial({
9087
+ return new THREE26.MeshStandardMaterial({
8184
9088
  color,
8185
9089
  roughness: 0.85,
8186
9090
  metalness: 0,
@@ -8188,10 +9092,10 @@ function createHandMaterial(preset, options) {
8188
9092
  emissiveIntensity: Math.max(0, options.emissiveIntensity),
8189
9093
  transparent: options.opacity < 1,
8190
9094
  opacity: options.opacity,
8191
- side: THREE19.DoubleSide
9095
+ side: THREE26.DoubleSide
8192
9096
  });
8193
9097
  default: {
8194
- return new THREE19.MeshStandardMaterial({ color });
9098
+ return new THREE26.MeshStandardMaterial({ color });
8195
9099
  }
8196
9100
  }
8197
9101
  }
@@ -8216,9 +9120,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8216
9120
  constructor(options) {
8217
9121
  this.controllerModelFactory = new XRControllerModelFactory();
8218
9122
  this.handModelFactory = new XRHandModelFactory();
8219
- this.group = new THREE19.Group();
9123
+ this.group = new THREE26.Group();
8220
9124
  /** Reused so a per-frame colour compare allocates nothing. */
8221
- this.rayColorScratch = new THREE19.Color();
9125
+ this.rayColorScratch = new THREE26.Color();
8222
9126
  this.slots = /* @__PURE__ */ new Map();
8223
9127
  this.attachedTo = null;
8224
9128
  /** Last applied preset signature, so materials rebuild only on real change. */
@@ -8355,8 +9259,8 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8355
9259
  */
8356
9260
  fitRayToHover(ray, side) {
8357
9261
  const hover = this.interactionSystem?.getHoverFor(side) ?? null;
8358
- const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
8359
- ray.scale.z = length;
9262
+ const length2 = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
9263
+ ray.scale.z = length2;
8360
9264
  }
8361
9265
  /** Lazily build per-slot visuals; the device reports profiles late. */
8362
9266
  ensureSlots(world) {
@@ -8405,17 +9309,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8405
9309
  * without pulling in a line-rendering extension.
8406
9310
  */
8407
9311
  createRay() {
8408
- const geometry = new THREE19.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
9312
+ const geometry = new THREE26.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
8409
9313
  geometry.rotateX(-Math.PI / 2);
8410
9314
  geometry.translate(0, 0, -0.5);
8411
- const material = new THREE19.MeshBasicMaterial({
9315
+ const material = new THREE26.MeshBasicMaterial({
8412
9316
  color: "#4da3ff",
8413
9317
  transparent: true,
8414
9318
  opacity: 0.75,
8415
9319
  depthTest: false,
8416
9320
  depthWrite: false
8417
9321
  });
8418
- const mesh = new THREE19.Mesh(geometry, material);
9322
+ const mesh = new THREE26.Mesh(geometry, material);
8419
9323
  mesh.name = "XRPointerRay";
8420
9324
  mesh.renderOrder = 999;
8421
9325
  mesh.visible = false;
@@ -8446,14 +9350,14 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8446
9350
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
8447
9351
  var XRControllerVisualSystem = _XRControllerVisualSystem;
8448
9352
  var MARKER_RADIUS = 0.28;
8449
- var ACCENT = new THREE19.Color("#4da3ff");
8450
- var SEGMENT_AXIS = new THREE19.Vector3(0, 1, 0);
9353
+ var ACCENT = new THREE26.Color("#4da3ff");
9354
+ var SEGMENT_AXIS = new THREE26.Vector3(0, 1, 0);
8451
9355
  var HEAD_CLEARANCE = 0.25;
8452
9356
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8453
9357
  constructor(locomotionSystem) {
8454
- this.group = new THREE19.Group();
9358
+ this.group = new THREE26.Group();
8455
9359
  /** Holds the segment chain that draws the arc. */
8456
- this.arc = new THREE19.Group();
9360
+ this.arc = new THREE26.Group();
8457
9361
  /**
8458
9362
  * Unit cylinder along +Y, reused by every segment.
8459
9363
  *
@@ -8462,14 +9366,14 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8462
9366
  * scaled cylinders gives real thickness while rebuilding no geometry: each
8463
9367
  * frame only moves, rotates and scales meshes that already exist.
8464
9368
  */
8465
- this.segmentGeometry = new THREE19.CylinderGeometry(1, 1, 1, 6, 1);
9369
+ this.segmentGeometry = new THREE26.CylinderGeometry(1, 1, 1, 6, 1);
8466
9370
  this.segments = [];
8467
- this.marker = new THREE19.Group();
9371
+ this.marker = new THREE26.Group();
8468
9372
  this.attached = false;
8469
- this.scratchDirection = new THREE19.Vector3();
8470
- this.scratchHead = new THREE19.Vector3();
8471
- this.validColor = new THREE19.Color();
8472
- this.invalidColor = new THREE19.Color();
9373
+ this.scratchDirection = new THREE26.Vector3();
9374
+ this.scratchHead = new THREE26.Vector3();
9375
+ this.validColor = new THREE26.Color();
9376
+ this.invalidColor = new THREE26.Color();
8473
9377
  this.locomotionSystem = locomotionSystem;
8474
9378
  this.group.name = "XRTeleportVisuals";
8475
9379
  this.group.userData.ecsPreviewHidden = true;
@@ -8527,8 +9431,8 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8527
9431
  const from = curve[i];
8528
9432
  const to = curve[i + 1];
8529
9433
  this.scratchDirection.subVectors(to, from);
8530
- const length = this.scratchDirection.length();
8531
- if (length < 1e-6) {
9434
+ const length2 = this.scratchDirection.length();
9435
+ if (length2 < 1e-6) {
8532
9436
  segment.visible = false;
8533
9437
  continue;
8534
9438
  }
@@ -8536,9 +9440,9 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8536
9440
  segment.position.copy(from).addScaledVector(this.scratchDirection, 0.5);
8537
9441
  segment.quaternion.setFromUnitVectors(
8538
9442
  SEGMENT_AXIS,
8539
- this.scratchDirection.divideScalar(length)
9443
+ this.scratchDirection.divideScalar(length2)
8540
9444
  );
8541
- segment.scale.set(radius, length, radius);
9445
+ segment.scale.set(radius, length2, radius);
8542
9446
  const t = needed > 1 ? i / (needed - 1) : 0;
8543
9447
  const fade = 1 - t * 0.75;
8544
9448
  segment.material.color.copy(tint).multiplyScalar(fade);
@@ -8547,12 +9451,12 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8547
9451
  /** Grow the segment pool to cover the current arc. */
8548
9452
  ensureSegments(count) {
8549
9453
  while (this.segments.length < count) {
8550
- const material = new THREE19.MeshBasicMaterial({
9454
+ const material = new THREE26.MeshBasicMaterial({
8551
9455
  transparent: true,
8552
9456
  depthTest: false,
8553
9457
  depthWrite: false
8554
9458
  });
8555
- const mesh = new THREE19.Mesh(this.segmentGeometry, material);
9459
+ const mesh = new THREE26.Mesh(this.segmentGeometry, material);
8556
9460
  mesh.frustumCulled = false;
8557
9461
  mesh.renderOrder = 998;
8558
9462
  mesh.raycast = () => {
@@ -8576,33 +9480,33 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8576
9480
  return world.getComponent(entityId, XRRigComponent) ?? null;
8577
9481
  }
8578
9482
  createRing() {
8579
- const geometry = new THREE19.RingGeometry(
9483
+ const geometry = new THREE26.RingGeometry(
8580
9484
  MARKER_RADIUS * 0.72,
8581
9485
  MARKER_RADIUS,
8582
9486
  48
8583
9487
  );
8584
9488
  geometry.rotateX(-Math.PI / 2);
8585
- return new THREE19.Mesh(
9489
+ return new THREE26.Mesh(
8586
9490
  geometry,
8587
- new THREE19.MeshBasicMaterial({
9491
+ new THREE26.MeshBasicMaterial({
8588
9492
  color: ACCENT,
8589
9493
  transparent: true,
8590
9494
  opacity: 0.9,
8591
- side: THREE19.DoubleSide,
9495
+ side: THREE26.DoubleSide,
8592
9496
  depthTest: false
8593
9497
  })
8594
9498
  );
8595
9499
  }
8596
9500
  createDot() {
8597
- const geometry = new THREE19.CircleGeometry(MARKER_RADIUS * 0.16, 24);
9501
+ const geometry = new THREE26.CircleGeometry(MARKER_RADIUS * 0.16, 24);
8598
9502
  geometry.rotateX(-Math.PI / 2);
8599
- return new THREE19.Mesh(
9503
+ return new THREE26.Mesh(
8600
9504
  geometry,
8601
- new THREE19.MeshBasicMaterial({
9505
+ new THREE26.MeshBasicMaterial({
8602
9506
  color: ACCENT,
8603
9507
  transparent: true,
8604
9508
  opacity: 0.55,
8605
- side: THREE19.DoubleSide,
9509
+ side: THREE26.DoubleSide,
8606
9510
  depthTest: false
8607
9511
  })
8608
9512
  );
@@ -8713,7 +9617,7 @@ function boneDirections(joints, finger) {
8713
9617
  const from = joints[chain[i - 1]];
8714
9618
  const to = joints[chain[i]];
8715
9619
  if (!from || !to) return null;
8716
- const direction = new THREE19.Vector3().subVectors(to, from);
9620
+ const direction = new THREE26.Vector3().subVectors(to, from);
8717
9621
  if (direction.lengthSq() < 1e-12) return null;
8718
9622
  directions.push(direction.normalize());
8719
9623
  }
@@ -8786,8 +9690,8 @@ function computeSpread(joints, a, b) {
8786
9690
  const bBase = joints[METACARPAL_JOINTS[b]];
8787
9691
  const bTip = joints[TIP_JOINTS[b]];
8788
9692
  if (!aBase || !aTip || !bBase || !bTip) return null;
8789
- const dirA = new THREE19.Vector3().subVectors(aTip, aBase);
8790
- const dirB = new THREE19.Vector3().subVectors(bTip, bBase);
9693
+ const dirA = new THREE26.Vector3().subVectors(aTip, aBase);
9694
+ const dirB = new THREE26.Vector3().subVectors(bTip, bBase);
8791
9695
  if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
8792
9696
  return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
8793
9697
  }
@@ -8797,18 +9701,18 @@ function computePalmPose(joints, handedness) {
8797
9701
  const indexBase = joints["index-finger-metacarpal"];
8798
9702
  const pinkyBase = joints["pinky-finger-metacarpal"];
8799
9703
  if (!wrist || !indexBase || !pinkyBase) return null;
8800
- const u = new THREE19.Vector3().subVectors(indexBase, wrist);
8801
- const v = new THREE19.Vector3().subVectors(pinkyBase, wrist);
8802
- const normal = new THREE19.Vector3().crossVectors(u, v);
9704
+ const u = new THREE26.Vector3().subVectors(indexBase, wrist);
9705
+ const v = new THREE26.Vector3().subVectors(pinkyBase, wrist);
9706
+ const normal = new THREE26.Vector3().crossVectors(u, v);
8803
9707
  if (normal.lengthSq() < 1e-12) return null;
8804
9708
  normal.normalize();
8805
9709
  if (handedness === "left") normal.multiplyScalar(-1);
8806
- const right = new THREE19.Vector3().subVectors(indexBase, pinkyBase);
9710
+ const right = new THREE26.Vector3().subVectors(indexBase, pinkyBase);
8807
9711
  if (right.lengthSq() < 1e-12) return null;
8808
9712
  right.normalize();
8809
9713
  if (handedness === "left") right.multiplyScalar(-1);
8810
- const up = new THREE19.Vector3().crossVectors(normal, right).normalize();
8811
- const center = new THREE19.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
9714
+ const up = new THREE26.Vector3().crossVectors(normal, right).normalize();
9715
+ const center = new THREE26.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
8812
9716
  return {
8813
9717
  center,
8814
9718
  normal,
@@ -8820,17 +9724,17 @@ function computePalmPose(joints, handedness) {
8820
9724
  __name(computePalmPose, "computePalmPose");
8821
9725
  function computeHandShape(joints, handedness) {
8822
9726
  const fingers = {};
8823
- let tracked = 0;
9727
+ let tracked2 = 0;
8824
9728
  for (const finger of XR_FINGERS) {
8825
9729
  const curl = computeFingerCurl(joints, finger);
8826
9730
  if (curl) {
8827
9731
  fingers[finger] = curl;
8828
- tracked += 1;
9732
+ tracked2 += 1;
8829
9733
  } else {
8830
9734
  fingers[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
8831
9735
  }
8832
9736
  }
8833
- if (tracked === 0) return null;
9737
+ if (tracked2 === 0) return null;
8834
9738
  const pinch = {};
8835
9739
  for (const finger of PINCH_FINGERS) {
8836
9740
  pinch[finger] = computePinch(joints, finger) ?? 0;
@@ -8839,7 +9743,7 @@ function computeHandShape(joints, handedness) {
8839
9743
  const thumbTip = joints["thumb-tip"];
8840
9744
  let thumbDirection = null;
8841
9745
  if (thumbBase && thumbTip) {
8842
- const direction = new THREE19.Vector3().subVectors(thumbTip, thumbBase);
9746
+ const direction = new THREE26.Vector3().subVectors(thumbTip, thumbBase);
8843
9747
  if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
8844
9748
  }
8845
9749
  const fingerDirections = {};
@@ -8850,7 +9754,7 @@ function computeHandShape(joints, handedness) {
8850
9754
  fingerDirections[finger] = null;
8851
9755
  continue;
8852
9756
  }
8853
- const direction = new THREE19.Vector3().subVectors(tip, base);
9757
+ const direction = new THREE26.Vector3().subVectors(tip, base);
8854
9758
  fingerDirections[finger] = direction.lengthSq() > 1e-12 ? direction.normalize() : null;
8855
9759
  }
8856
9760
  return {
@@ -8884,12 +9788,12 @@ function scoreAgainst(actual, target, tolerance) {
8884
9788
  __name(scoreAgainst, "scoreAgainst");
8885
9789
  var DEFAULT_DIRECTION_TOLERANCE = 45;
8886
9790
  var WORLD_DIRECTIONS = {
8887
- up: new THREE19.Vector3(0, 1, 0),
8888
- down: new THREE19.Vector3(0, -1, 0),
8889
- forward: new THREE19.Vector3(0, 0, -1),
8890
- back: new THREE19.Vector3(0, 0, 1),
8891
- left: new THREE19.Vector3(-1, 0, 0),
8892
- right: new THREE19.Vector3(1, 0, 0)
9791
+ up: new THREE26.Vector3(0, 1, 0),
9792
+ down: new THREE26.Vector3(0, -1, 0),
9793
+ forward: new THREE26.Vector3(0, 0, -1),
9794
+ back: new THREE26.Vector3(0, 0, 1),
9795
+ left: new THREE26.Vector3(-1, 0, 0),
9796
+ right: new THREE26.Vector3(1, 0, 0)
8893
9797
  };
8894
9798
  function axisVector(shape, axis) {
8895
9799
  if (axis === "thumb") return shape.thumbDirection;
@@ -8904,7 +9808,7 @@ function resolveDirection(direction, shape, context) {
8904
9808
  const head = context?.headPosition;
8905
9809
  const origin = shape.palm?.center;
8906
9810
  if (!head || !origin) return null;
8907
- const toHead = new THREE19.Vector3().subVectors(head, origin);
9811
+ const toHead = new THREE26.Vector3().subVectors(head, origin);
8908
9812
  if (toHead.lengthSq() < 1e-12) return null;
8909
9813
  toHead.normalize();
8910
9814
  return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
@@ -8916,8 +9820,8 @@ function scoreDirections(actual, accepted, shape, toleranceDegrees, context) {
8916
9820
  for (const direction of accepted) {
8917
9821
  const wanted = resolveDirection(direction, shape, context);
8918
9822
  if (!wanted) continue;
8919
- const angle = THREE19.MathUtils.radToDeg(
8920
- Math.acos(THREE19.MathUtils.clamp(actual.dot(wanted), -1, 1))
9823
+ const angle = THREE26.MathUtils.radToDeg(
9824
+ Math.acos(THREE26.MathUtils.clamp(actual.dot(wanted), -1, 1))
8921
9825
  );
8922
9826
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
8923
9827
  const score = clamp01(1 - normalizedError / MATCH_FALLOFF);
@@ -8982,8 +9886,8 @@ function matchHandPose(shape, definition, context) {
8982
9886
  if (!handAxis || !wanted) {
8983
9887
  consider(0, "orientation");
8984
9888
  } else {
8985
- const angle = THREE19.MathUtils.radToDeg(
8986
- Math.acos(THREE19.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
9889
+ const angle = THREE26.MathUtils.radToDeg(
9890
+ Math.acos(THREE26.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
8987
9891
  );
8988
9892
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
8989
9893
  consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
@@ -9061,7 +9965,7 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9061
9965
  this.trackers = /* @__PURE__ */ new Map();
9062
9966
  this.shapes = /* @__PURE__ */ new Map();
9063
9967
  /** Reused so head-relative orientation checks allocate nothing per frame. */
9064
- this.headPosition = new THREE19.Vector3();
9968
+ this.headPosition = new THREE26.Vector3();
9065
9969
  this.elapsed = 0;
9066
9970
  this.warnedNoHands = false;
9067
9971
  this.noHandSeconds = 0;
@@ -9077,8 +9981,8 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9077
9981
  const entries = this.collectEntries(world);
9078
9982
  if (entries.length === 0) return;
9079
9983
  if (!this.warnedNoHands) {
9080
- const tracked = this.shapes.get("left") !== null || this.shapes.get("right") !== null;
9081
- if (tracked) {
9984
+ const tracked2 = this.shapes.get("left") !== null || this.shapes.get("right") !== null;
9985
+ if (tracked2) {
9082
9986
  this.warnedNoHands = true;
9083
9987
  } else {
9084
9988
  this.noHandSeconds += deltaTime;
@@ -9224,6 +10128,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9224
10128
  const interactable = world.getComponent(entityId, XRInteractableComponent);
9225
10129
  if (!interactable?.enabled) continue;
9226
10130
  if (interactable.hoverBehavior === "none") continue;
10131
+ if (isUiWidget(world, entityId)) continue;
9227
10132
  this.begin(world, entityId, interactable);
9228
10133
  }
9229
10134
  for (const [entityId, record] of this.records) {
@@ -9244,7 +10149,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9244
10149
  const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
9245
10150
  this.records.set(entityId, {
9246
10151
  shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
9247
- baseScale: new THREE19.Vector3(
10152
+ baseScale: new THREE26.Vector3(
9248
10153
  transform.scale.x,
9249
10154
  transform.scale.y,
9250
10155
  transform.scale.z
@@ -9257,27 +10162,27 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9257
10162
  addShell(world, entityId, interactable) {
9258
10163
  const object = world.getComponent(entityId, Object3DRef)?.object;
9259
10164
  if (!object) return null;
9260
- const material = new THREE19.MeshBasicMaterial({
9261
- color: new THREE19.Color(interactable.hoverColor),
10165
+ const material = new THREE26.MeshBasicMaterial({
10166
+ color: new THREE26.Color(interactable.hoverColor),
9262
10167
  // Back faces only, so the shell shows just outside the silhouette instead
9263
10168
  // of covering the object.
9264
- side: THREE19.BackSide,
10169
+ side: THREE26.BackSide,
9265
10170
  transparent: true,
9266
10171
  opacity: 0.85,
9267
10172
  depthWrite: false
9268
10173
  });
9269
- const group = new THREE19.Group();
10174
+ const group = new THREE26.Group();
9270
10175
  group.name = "XRHoverShell";
9271
10176
  group.userData[XR_HOVER_SHELL_FLAG] = true;
9272
10177
  group.renderOrder = 998;
9273
10178
  object.traverse((child) => {
9274
- if (!(child instanceof THREE19.Mesh) || !child.geometry) return;
10179
+ if (!(child instanceof THREE26.Mesh) || !child.geometry) return;
9275
10180
  if (child.userData[XR_HOVER_SHELL_FLAG]) return;
9276
- const clone = new THREE19.Mesh(child.geometry, material);
10181
+ const clone = new THREE26.Mesh(child.geometry, material);
9277
10182
  child.updateWorldMatrix(true, false);
9278
10183
  object.updateWorldMatrix(true, false);
9279
10184
  clone.matrixAutoUpdate = false;
9280
- clone.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE19.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
10185
+ clone.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE26.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
9281
10186
  clone.userData[XR_HOVER_SHELL_FLAG] = true;
9282
10187
  clone.raycast = () => {
9283
10188
  };
@@ -9326,7 +10231,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9326
10231
  if (!shell) return;
9327
10232
  shell.parent?.remove(shell);
9328
10233
  shell.traverse((child) => {
9329
- if (child instanceof THREE19.Mesh) {
10234
+ if (child instanceof THREE26.Mesh) {
9330
10235
  const material = child.material;
9331
10236
  if (Array.isArray(material)) {
9332
10237
  for (const entry of material) entry.dispose();
@@ -9352,5 +10257,5 @@ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9352
10257
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9353
10258
 
9354
10259
  export { ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioMixer, AudioSystem, CURLED_COS, CameraSyncSystem, DEFAULT_DIRECTION_TOLERANCE, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, DEFAULT_SCRIPT_ERROR_RETRY_DELAY, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HandPoseTracker, IKSystem, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NO_XR_CAPABILITIES, ObjectVisibilitySystem, PINCH_FINGERS, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, SlotChooser, TeleportAimGate, ThrowVelocityTracker, TransformSyncSystem, UIInteractionSystem, UIPanelSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRAnchorSystem, XRControllerVisualSystem, XREnvironmentSystem, XRHandPoseSystem, XRHitTestSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, computeFingerCurl, computeHandShape, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, estimateHandScale, gripQuaternion, matchHandPose, readXrCapabilities, sampleTeleportArc, setWorldQuaternion, soundBankFiles, twoBoneIK };
9355
- //# sourceMappingURL=chunk-JIJQFW6P.js.map
9356
- //# sourceMappingURL=chunk-JIJQFW6P.js.map
10260
+ //# sourceMappingURL=chunk-TY3XCHOR.js.map
10261
+ //# sourceMappingURL=chunk-TY3XCHOR.js.map