@relu-interactives/spatial-ecs 0.3.9 → 0.3.11

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 (38) hide show
  1. package/dist/{XRHandPose-CxPz8VBp.d.ts → XRHandPose-B6I4y1lj.d.ts} +117 -14
  2. package/dist/{XRHandPose-VuSbe4iz.d.cts → XRHandPose-C-KJeYcJ.d.cts} +117 -14
  3. package/dist/{chunk-KIKIWCEB.js → chunk-FSHRPY65.js} +4 -4
  4. package/dist/{chunk-KIKIWCEB.js.map → chunk-FSHRPY65.js.map} +1 -1
  5. package/dist/{chunk-W5TSYV6Q.js → chunk-KLU2M43M.js} +836 -212
  6. package/dist/chunk-KLU2M43M.js.map +1 -0
  7. package/dist/{chunk-KVGGAL5V.js → chunk-TXEGYR76.js} +26 -5
  8. package/dist/chunk-TXEGYR76.js.map +1 -0
  9. package/dist/{chunk-L5BP727H.js → chunk-Y3AC3VZ2.js} +3 -3
  10. package/dist/{chunk-L5BP727H.js.map → chunk-Y3AC3VZ2.js.map} +1 -1
  11. package/dist/components/index.cjs +23 -2
  12. package/dist/components/index.cjs.map +1 -1
  13. package/dist/components/index.d.cts +2 -2
  14. package/dist/components/index.d.ts +2 -2
  15. package/dist/components/index.js +1 -1
  16. package/dist/docs/guide/vr-and-hand-poses.md +55 -3
  17. package/dist/factories/index.cjs +22 -2
  18. package/dist/factories/index.cjs.map +1 -1
  19. package/dist/factories/index.d.cts +1 -1
  20. package/dist/factories/index.d.ts +1 -1
  21. package/dist/factories/index.js +3 -3
  22. package/dist/{index-C7XQyrgE.d.ts → index-Bi_wIo1o.d.ts} +107 -1
  23. package/dist/{index-BxdXgqiI.d.ts → index-CEZp7qLL.d.ts} +9 -3
  24. package/dist/{index-BIQoaKAA.d.cts → index-DPRnnSI-.d.cts} +9 -3
  25. package/dist/{index-DyN50JZo.d.cts → index-jlgDou9W.d.cts} +107 -1
  26. package/dist/index.cjs +978 -331
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +14 -5
  29. package/dist/index.d.ts +14 -5
  30. package/dist/index.js +8 -8
  31. package/dist/systems/index.cjs +885 -239
  32. package/dist/systems/index.cjs.map +1 -1
  33. package/dist/systems/index.d.cts +1 -1
  34. package/dist/systems/index.d.ts +1 -1
  35. package/dist/systems/index.js +3 -3
  36. package/package.json +1 -1
  37. package/dist/chunk-KVGGAL5V.js.map +0 -1
  38. package/dist/chunk-W5TSYV6Q.js.map +0 -1
@@ -1,7 +1,7 @@
1
- import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-L5BP727H.js';
2
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-KVGGAL5V.js';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-Y3AC3VZ2.js';
2
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, XRRigComponent, VRTeleportAreaComponent, XRInteractableComponent, createAttachPoint, XRHandPoseComponent, createHandPoseEntry, XRInteractorComponent, XRInteractorSideComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance, handPoseDefinitionFor, getHandPosePreset } from './chunk-TXEGYR76.js';
3
3
  import { __name } from './chunk-OY7ZODZP.js';
4
- import * as THREE7 from 'three';
4
+ import * as THREE19 from 'three';
5
5
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
6
6
  import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
7
7
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
@@ -160,7 +160,7 @@ function transitionToClip(runtime, nextClipId, crossFadeDuration) {
160
160
  }
161
161
  __name(transitionToClip, "transitionToClip");
162
162
  function applyActionState(action, component, paused, shouldLoop) {
163
- const loopMode = shouldLoop ? THREE7.LoopRepeat : THREE7.LoopOnce;
163
+ const loopMode = shouldLoop ? THREE19.LoopRepeat : THREE19.LoopOnce;
164
164
  const repetitions = shouldLoop ? Infinity : 1;
165
165
  action.setLoop(loopMode, repetitions);
166
166
  action.clampWhenFinished = !shouldLoop;
@@ -333,7 +333,7 @@ var _AnimationSystem = class _AnimationSystem {
333
333
  if (runtime) {
334
334
  stopAllActions(runtime);
335
335
  }
336
- const mixer = new THREE7.AnimationMixer(root);
336
+ const mixer = new THREE19.AnimationMixer(root);
337
337
  const entries2 = readRuntimeClipEntries(animation);
338
338
  const indexedActions = createActions(mixer, entries2);
339
339
  runtime = {
@@ -459,11 +459,11 @@ function normalizeOrthoBounds(component) {
459
459
  }
460
460
  __name(normalizeOrthoBounds, "normalizeOrthoBounds");
461
461
  function isPerspectiveCamera(camera) {
462
- return camera instanceof THREE7.PerspectiveCamera;
462
+ return camera instanceof THREE19.PerspectiveCamera;
463
463
  }
464
464
  __name(isPerspectiveCamera, "isPerspectiveCamera");
465
465
  function isOrthographicCamera(camera) {
466
- return camera instanceof THREE7.OrthographicCamera;
466
+ return camera instanceof THREE19.OrthographicCamera;
467
467
  }
468
468
  __name(isOrthographicCamera, "isOrthographicCamera");
469
469
  function readWindowAspect() {
@@ -517,7 +517,7 @@ var _CameraSyncSystem = class _CameraSyncSystem {
517
517
  continue;
518
518
  }
519
519
  const camera = objectRef.object;
520
- if (!(camera instanceof THREE7.Camera)) {
520
+ if (!(camera instanceof THREE19.Camera)) {
521
521
  continue;
522
522
  }
523
523
  if (xrCamera !== null && camera === xrCamera) {
@@ -584,7 +584,7 @@ var MIN_DENSITY = 0;
584
584
  function createGradientTexture(effect, colors) {
585
585
  if (typeof document === "undefined") {
586
586
  const pixel = new Uint8Array([30, 30, 30, 255]);
587
- const fallback = new THREE7.DataTexture(pixel, 1, 1, THREE7.RGBAFormat);
587
+ const fallback = new THREE19.DataTexture(pixel, 1, 1, THREE19.RGBAFormat);
588
588
  fallback.needsUpdate = true;
589
589
  return fallback;
590
590
  }
@@ -593,7 +593,7 @@ function createGradientTexture(effect, colors) {
593
593
  canvas.height = 512;
594
594
  const context = canvas.getContext("2d");
595
595
  if (!context) {
596
- const fallback = new THREE7.CanvasTexture(canvas);
596
+ const fallback = new THREE19.CanvasTexture(canvas);
597
597
  fallback.needsUpdate = true;
598
598
  return fallback;
599
599
  }
@@ -621,15 +621,15 @@ function createGradientTexture(effect, colors) {
621
621
  context.fillRect(0, 0, 512, 512);
622
622
  context.globalCompositeOperation = "source-over";
623
623
  }
624
- const texture = new THREE7.CanvasTexture(canvas);
625
- texture.colorSpace = THREE7.SRGBColorSpace;
624
+ const texture = new THREE19.CanvasTexture(canvas);
625
+ texture.colorSpace = THREE19.SRGBColorSpace;
626
626
  texture.needsUpdate = true;
627
627
  return texture;
628
628
  }
629
629
  __name(createGradientTexture, "createGradientTexture");
630
630
  var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
631
631
  constructor(scene) {
632
- this.textureLoader = new THREE7.TextureLoader();
632
+ this.textureLoader = new THREE19.TextureLoader();
633
633
  this.signature = null;
634
634
  this.activeTexture = null;
635
635
  this.pendingImageUrl = null;
@@ -652,7 +652,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
652
652
  if (skybox.type === "color") {
653
653
  this.disposeActiveTexture();
654
654
  this.pendingImageUrl = null;
655
- this.scene.background = new THREE7.Color(skybox.color || "#1e1e1e");
655
+ this.scene.background = new THREE19.Color(skybox.color || "#1e1e1e");
656
656
  return;
657
657
  }
658
658
  if (skybox.type === "gradient") {
@@ -686,7 +686,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
686
686
  texture.dispose();
687
687
  return;
688
688
  }
689
- texture.colorSpace = THREE7.SRGBColorSpace;
689
+ texture.colorSpace = THREE19.SRGBColorSpace;
690
690
  this.activeTexture = texture;
691
691
  this.scene.background = texture;
692
692
  },
@@ -705,16 +705,16 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
705
705
  this.scene.fog = null;
706
706
  return;
707
707
  }
708
- const fogColor = new THREE7.Color(fog.color || "#1e1e1e");
708
+ const fogColor = new THREE19.Color(fog.color || "#1e1e1e");
709
709
  if (fog.type === "linear") {
710
710
  const near = Number.isFinite(fog.linear.near) ? fog.linear.near : 1;
711
711
  const farCandidate = Number.isFinite(fog.linear.far) ? fog.linear.far : near + 10;
712
712
  const far = Math.max(near + MIN_FOG_DELTA, farCandidate);
713
- this.scene.fog = new THREE7.Fog(fogColor, near, far);
713
+ this.scene.fog = new THREE19.Fog(fogColor, near, far);
714
714
  return;
715
715
  }
716
716
  const density = Number.isFinite(fog.exponential.density) ? Math.max(MIN_DENSITY, fog.exponential.density) : 2e-3;
717
- this.scene.fog = new THREE7.FogExp2(fogColor, density);
717
+ this.scene.fog = new THREE19.FogExp2(fogColor, density);
718
718
  }
719
719
  update(world) {
720
720
  const entityIds = world.query(Environment);
@@ -796,10 +796,10 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
796
796
  );
797
797
  }
798
798
  if (values.bloom.enabled) {
799
- const size = this.renderer.getSize(new THREE7.Vector2());
799
+ const size = this.renderer.getSize(new THREE19.Vector2());
800
800
  composer.addPass(
801
801
  new UnrealBloomPass(
802
- new THREE7.Vector2(size.width, size.height),
802
+ new THREE19.Vector2(size.width, size.height),
803
803
  values.bloom.intensity,
804
804
  values.bloom.radius,
805
805
  values.bloom.threshold
@@ -844,7 +844,7 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
844
844
  if (values.dotScreen.enabled) {
845
845
  composer.addPass(
846
846
  new DotScreenPass(
847
- new THREE7.Vector2(0, 0),
847
+ new THREE19.Vector2(0, 0),
848
848
  values.dotScreen.angle ?? values.dotScreen.dotScreenAngle,
849
849
  values.dotScreen.scale ?? values.dotScreen.dotScreenScale
850
850
  )
@@ -901,6 +901,20 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
901
901
  __name(_PostprocessingSyncSystem, "PostprocessingSyncSystem");
902
902
  var PostprocessingSyncSystem = _PostprocessingSyncSystem;
903
903
  var MIN_SIZE = 1e-4;
904
+ function toBodyType(RAPIER, bodyType) {
905
+ switch (bodyType) {
906
+ case "fixed":
907
+ return RAPIER.RigidBodyType.Fixed;
908
+ case "kinematicPosition":
909
+ return RAPIER.RigidBodyType.KinematicPositionBased;
910
+ case "kinematicVelocity":
911
+ return RAPIER.RigidBodyType.KinematicVelocityBased;
912
+ case "dynamic":
913
+ default:
914
+ return RAPIER.RigidBodyType.Dynamic;
915
+ }
916
+ }
917
+ __name(toBodyType, "toBodyType");
904
918
  function createRigidBodyDesc(RAPIER, bodyType) {
905
919
  if (bodyType === "fixed") {
906
920
  return RAPIER.RigidBodyDesc.fixed();
@@ -918,7 +932,7 @@ function computeColliderState(colliderValues, object) {
918
932
  if (!object) {
919
933
  return colliderValues;
920
934
  }
921
- const scale = new THREE7.Vector3();
935
+ const scale = new THREE19.Vector3();
922
936
  object.getWorldScale(scale);
923
937
  return {
924
938
  ...colliderValues,
@@ -969,8 +983,8 @@ function createColliderDesc(RAPIER, values, triMeshData) {
969
983
  values.offsetPosition.y,
970
984
  values.offsetPosition.z
971
985
  );
972
- const offsetQuaternion = new THREE7.Quaternion().setFromEuler(
973
- new THREE7.Euler(
986
+ const offsetQuaternion = new THREE19.Quaternion().setFromEuler(
987
+ new THREE19.Euler(
974
988
  values.offsetRotation.x,
975
989
  values.offsetRotation.y,
976
990
  values.offsetRotation.z
@@ -985,17 +999,17 @@ function collectTriMeshParts(object) {
985
999
  return null;
986
1000
  }
987
1001
  object.updateWorldMatrix(true, true);
988
- const worldScale = new THREE7.Vector3();
1002
+ const worldScale = new THREE19.Vector3();
989
1003
  object.getWorldScale(worldScale);
990
1004
  const scale = {
991
1005
  x: Math.abs(worldScale.x) || 1,
992
1006
  y: Math.abs(worldScale.y) || 1,
993
1007
  z: Math.abs(worldScale.z) || 1
994
1008
  };
995
- const worldToObject = new THREE7.Matrix4().copy(object.matrixWorld).invert();
1009
+ const worldToObject = new THREE19.Matrix4().copy(object.matrixWorld).invert();
996
1010
  const parts = [];
997
1011
  object.traverse((child) => {
998
- if (!(child instanceof THREE7.Mesh) || !child.geometry) {
1012
+ if (!(child instanceof THREE19.Mesh) || !child.geometry) {
999
1013
  return;
1000
1014
  }
1001
1015
  const geometry = child.geometry;
@@ -1012,7 +1026,7 @@ function collectTriMeshParts(object) {
1012
1026
  if (indexCount < 3) {
1013
1027
  return;
1014
1028
  }
1015
- const relativeMatrix = new THREE7.Matrix4().multiplyMatrices(
1029
+ const relativeMatrix = new THREE19.Matrix4().multiplyMatrices(
1016
1030
  worldToObject,
1017
1031
  child.matrixWorld
1018
1032
  );
@@ -1053,7 +1067,7 @@ function createTriMeshData(collected) {
1053
1067
  }
1054
1068
  const vertices = new Float32Array(vertexTotal * 3);
1055
1069
  const indices = new Uint32Array(indexTotal);
1056
- const point = new THREE7.Vector3();
1070
+ const point = new THREE19.Vector3();
1057
1071
  let vertexOffset = 0;
1058
1072
  let indexOffset = 0;
1059
1073
  for (const part of parts) {
@@ -1085,8 +1099,8 @@ function createTriMeshData(collected) {
1085
1099
  }
1086
1100
  __name(createTriMeshData, "createTriMeshData");
1087
1101
  function syncBodyFromTransform(body, transform) {
1088
- const quaternion = new THREE7.Quaternion().setFromEuler(
1089
- new THREE7.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1102
+ const quaternion = new THREE19.Quaternion().setFromEuler(
1103
+ new THREE19.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1090
1104
  );
1091
1105
  body.setTranslation(
1092
1106
  {
@@ -1113,8 +1127,8 @@ function syncTransformFromBody(transform, body) {
1113
1127
  transform.position.x = position.x;
1114
1128
  transform.position.y = position.y;
1115
1129
  transform.position.z = position.z;
1116
- const euler = new THREE7.Euler().setFromQuaternion(
1117
- new THREE7.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1130
+ const euler = new THREE19.Euler().setFromQuaternion(
1131
+ new THREE19.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1118
1132
  );
1119
1133
  transform.rotation.x = euler.x;
1120
1134
  transform.rotation.y = euler.y;
@@ -1199,7 +1213,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1199
1213
  setPhysicsLiveRef(physics, "collider", null);
1200
1214
  continue;
1201
1215
  }
1202
- const rigidBodySignature = JSON.stringify(physics.values.rigidbody);
1216
+ const { type: _desiredBodyType, ...rigidBodyRebuildValues } = physics.values.rigidbody;
1217
+ const rigidBodySignature = JSON.stringify(rigidBodyRebuildValues);
1203
1218
  const previousRigidBodySignature = this.rigidBodySignatures.get(entityId);
1204
1219
  const hasRigidBody = this.rigidBodyHandles.has(entityId);
1205
1220
  const shouldRecreateBody = !hasRigidBody || previousRigidBodySignature !== rigidBodySignature;
@@ -1226,8 +1241,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1226
1241
  transform.position.y,
1227
1242
  transform.position.z
1228
1243
  );
1229
- const quaternion = new THREE7.Quaternion().setFromEuler(
1230
- new THREE7.Euler(
1244
+ const quaternion = new THREE19.Quaternion().setFromEuler(
1245
+ new THREE19.Euler(
1231
1246
  transform.rotation.x,
1232
1247
  transform.rotation.y,
1233
1248
  transform.rotation.z
@@ -1254,6 +1269,25 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1254
1269
  continue;
1255
1270
  }
1256
1271
  setPhysicsLiveRef(physics, "rigidBody", body);
1272
+ const desiredBodyType = this.isEditorMode ? "fixed" : physics.values.rigidbody.type;
1273
+ const desiredRapierType = toBodyType(this.rapier, desiredBodyType);
1274
+ if (body.bodyType() !== desiredRapierType) {
1275
+ body.setBodyType(desiredRapierType, true);
1276
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
1277
+ body.setAdditionalMass(physics.values.rigidbody.mass, true);
1278
+ }
1279
+ }
1280
+ const pending = physics.pendingVelocity;
1281
+ if (pending) {
1282
+ setPhysicsLiveRef(physics, "pendingVelocity", null);
1283
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
1284
+ try {
1285
+ body.setLinvel(pending.linear, true);
1286
+ body.setAngvel(pending.angular, true);
1287
+ } catch {
1288
+ }
1289
+ }
1290
+ }
1257
1291
  if (this.isEditorMode || physics.values.rigidbody.type !== "dynamic") {
1258
1292
  syncBodyFromTransform(body, transform);
1259
1293
  }
@@ -1806,7 +1840,7 @@ function applyLightValues(lightComponent) {
1806
1840
  values.castShadow
1807
1841
  );
1808
1842
  }
1809
- if (light instanceof THREE7.PointLight) {
1843
+ if (light instanceof THREE19.PointLight) {
1810
1844
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
1811
1845
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
1812
1846
  }
@@ -1814,7 +1848,7 @@ function applyLightValues(lightComponent) {
1814
1848
  light.decay = Math.max(0, values.decay);
1815
1849
  }
1816
1850
  }
1817
- if (light instanceof THREE7.SpotLight) {
1851
+ if (light instanceof THREE19.SpotLight) {
1818
1852
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
1819
1853
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
1820
1854
  }
@@ -1828,7 +1862,7 @@ function applyLightValues(lightComponent) {
1828
1862
  light.penumbra = Math.max(0, Math.min(1, values.penumbra));
1829
1863
  }
1830
1864
  }
1831
- if (light instanceof THREE7.RectAreaLight) {
1865
+ if (light instanceof THREE19.RectAreaLight) {
1832
1866
  if (typeof values.width === "number" && Number.isFinite(values.width)) {
1833
1867
  light.width = Math.max(MIN_AREA_SIZE, values.width);
1834
1868
  }
@@ -1889,15 +1923,15 @@ function resolveMaterialPropertyKey(key) {
1889
1923
  __name(resolveMaterialPropertyKey, "resolveMaterialPropertyKey");
1890
1924
  function createMaterialByType(type) {
1891
1925
  if (type === "basic") {
1892
- return new THREE7.MeshBasicMaterial();
1926
+ return new THREE19.MeshBasicMaterial();
1893
1927
  }
1894
1928
  if (type === "phong") {
1895
- return new THREE7.MeshPhongMaterial();
1929
+ return new THREE19.MeshPhongMaterial();
1896
1930
  }
1897
1931
  if (type === "physical") {
1898
- return new THREE7.MeshPhysicalMaterial();
1932
+ return new THREE19.MeshPhysicalMaterial();
1899
1933
  }
1900
- return new THREE7.MeshStandardMaterial();
1934
+ return new THREE19.MeshStandardMaterial();
1901
1935
  }
1902
1936
  __name(createMaterialByType, "createMaterialByType");
1903
1937
  function replaceMaterialAcrossObject(object, previous, next) {
@@ -1956,12 +1990,12 @@ function applyTextureFiltering(material, filtering) {
1956
1990
  const target = material;
1957
1991
  for (const slot of materialTextureSlots) {
1958
1992
  const texture = target[slot];
1959
- if (!texture || !(texture instanceof THREE7.Texture)) {
1993
+ if (!texture || !(texture instanceof THREE19.Texture)) {
1960
1994
  continue;
1961
1995
  }
1962
1996
  const useMipmaps = texture.generateMipmaps !== false;
1963
- texture.magFilter = sharp ? THREE7.NearestFilter : THREE7.LinearFilter;
1964
- texture.minFilter = useMipmaps ? sharp ? THREE7.NearestMipmapNearestFilter : THREE7.LinearMipmapLinearFilter : sharp ? THREE7.NearestFilter : THREE7.LinearFilter;
1997
+ texture.magFilter = sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
1998
+ texture.minFilter = useMipmaps ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
1965
1999
  texture.needsUpdate = true;
1966
2000
  }
1967
2001
  material.needsUpdate = true;
@@ -1971,12 +2005,12 @@ function setTextureMipmaps(material, enabled) {
1971
2005
  const target = material;
1972
2006
  for (const slot of materialTextureSlots) {
1973
2007
  const texture = target[slot];
1974
- if (!texture || !(texture instanceof THREE7.Texture)) {
2008
+ if (!texture || !(texture instanceof THREE19.Texture)) {
1975
2009
  continue;
1976
2010
  }
1977
2011
  texture.generateMipmaps = enabled;
1978
- const sharp = texture.magFilter === THREE7.NearestFilter;
1979
- texture.minFilter = enabled ? sharp ? THREE7.NearestMipmapNearestFilter : THREE7.LinearMipmapLinearFilter : sharp ? THREE7.NearestFilter : THREE7.LinearFilter;
2012
+ const sharp = texture.magFilter === THREE19.NearestFilter;
2013
+ texture.minFilter = enabled ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
1980
2014
  texture.needsUpdate = true;
1981
2015
  }
1982
2016
  material.needsUpdate = true;
@@ -1987,7 +2021,7 @@ function assignMaterialTexture(material, rawKey, url, textureCache) {
1987
2021
  const key = resolveMaterialPropertyKey(rawKey);
1988
2022
  let texture = textureCache.get(url);
1989
2023
  if (!texture) {
1990
- texture = new THREE7.TextureLoader().load(url);
2024
+ texture = new THREE19.TextureLoader().load(url);
1991
2025
  textureCache.set(url, texture);
1992
2026
  }
1993
2027
  target[key] = texture;
@@ -2025,7 +2059,7 @@ function applyMaterialState(material, value, textureCache) {
2025
2059
  }
2026
2060
  target.side = value.side;
2027
2061
  target.blending = value.blending;
2028
- target.premultipliedAlpha = value.blending === THREE7.MultiplyBlending || value.blending === THREE7.SubtractiveBlending;
2062
+ target.premultipliedAlpha = value.blending === THREE19.MultiplyBlending || value.blending === THREE19.SubtractiveBlending;
2029
2063
  target.depthTest = value.depthTest;
2030
2064
  target.depthWrite = value.depthWrite;
2031
2065
  target.wireframe = value.wireframe;
@@ -2186,12 +2220,12 @@ function resolvePrimaryMesh(object) {
2186
2220
  if (!object) {
2187
2221
  return null;
2188
2222
  }
2189
- if (object instanceof THREE7.Mesh) {
2223
+ if (object instanceof THREE19.Mesh) {
2190
2224
  return object;
2191
2225
  }
2192
2226
  let found = null;
2193
2227
  object.traverse((child) => {
2194
- if (!found && child instanceof THREE7.Mesh) {
2228
+ if (!found && child instanceof THREE19.Mesh) {
2195
2229
  found = child;
2196
2230
  }
2197
2231
  });
@@ -2212,22 +2246,22 @@ function applyGeometry(component) {
2212
2246
  switch (component.kind) {
2213
2247
  case "cube": {
2214
2248
  const params = sanitized;
2215
- nextGeometry = new THREE7.BoxGeometry(params.width, params.height, params.depth);
2249
+ nextGeometry = new THREE19.BoxGeometry(params.width, params.height, params.depth);
2216
2250
  break;
2217
2251
  }
2218
2252
  case "plane": {
2219
2253
  const params = sanitized;
2220
- nextGeometry = new THREE7.PlaneGeometry(params.width, params.height);
2254
+ nextGeometry = new THREE19.PlaneGeometry(params.width, params.height);
2221
2255
  break;
2222
2256
  }
2223
2257
  case "sphere": {
2224
2258
  const params = sanitized;
2225
- nextGeometry = new THREE7.SphereGeometry(params.radius, 24, 24);
2259
+ nextGeometry = new THREE19.SphereGeometry(params.radius, 24, 24);
2226
2260
  break;
2227
2261
  }
2228
2262
  case "cylinder": {
2229
2263
  const params = sanitized;
2230
- nextGeometry = new THREE7.CylinderGeometry(
2264
+ nextGeometry = new THREE19.CylinderGeometry(
2231
2265
  params.radius,
2232
2266
  params.radius,
2233
2267
  params.height,
@@ -2237,7 +2271,7 @@ function applyGeometry(component) {
2237
2271
  }
2238
2272
  case "capsule": {
2239
2273
  const params = sanitized;
2240
- nextGeometry = new THREE7.CapsuleGeometry(params.radius, params.height, 4, 8);
2274
+ nextGeometry = new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2241
2275
  break;
2242
2276
  }
2243
2277
  default:
@@ -2253,7 +2287,7 @@ function applyShadowFlags(component) {
2253
2287
  return;
2254
2288
  }
2255
2289
  component.object.traverse((child) => {
2256
- if (child instanceof THREE7.Mesh) {
2290
+ if (child instanceof THREE19.Mesh) {
2257
2291
  child.castShadow = component.castShadow;
2258
2292
  child.receiveShadow = component.receiveShadow;
2259
2293
  }
@@ -2336,35 +2370,35 @@ function buildGeometry(component) {
2336
2370
  switch (component.meshKind) {
2337
2371
  case "cube": {
2338
2372
  const params = p;
2339
- return new THREE7.BoxGeometry(params.width, params.height, params.depth);
2373
+ return new THREE19.BoxGeometry(params.width, params.height, params.depth);
2340
2374
  }
2341
2375
  case "plane": {
2342
2376
  const params = p;
2343
- return new THREE7.PlaneGeometry(params.width, params.height);
2377
+ return new THREE19.PlaneGeometry(params.width, params.height);
2344
2378
  }
2345
2379
  case "sphere": {
2346
2380
  const params = p;
2347
- return new THREE7.SphereGeometry(params.radius, 24, 24);
2381
+ return new THREE19.SphereGeometry(params.radius, 24, 24);
2348
2382
  }
2349
2383
  case "cylinder": {
2350
2384
  const params = p;
2351
- return new THREE7.CylinderGeometry(params.radius, params.radius, params.height, 20);
2385
+ return new THREE19.CylinderGeometry(params.radius, params.radius, params.height, 20);
2352
2386
  }
2353
2387
  case "capsule": {
2354
2388
  const params = p;
2355
- return new THREE7.CapsuleGeometry(params.radius, params.height, 4, 8);
2389
+ return new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2356
2390
  }
2357
2391
  default:
2358
- return new THREE7.BoxGeometry(1, 1, 1);
2392
+ return new THREE19.BoxGeometry(1, 1, 1);
2359
2393
  }
2360
2394
  }
2361
2395
  __name(buildGeometry, "buildGeometry");
2362
- var _position = new THREE7.Vector3();
2363
- var _quaternion = new THREE7.Quaternion();
2364
- var _euler = new THREE7.Euler();
2365
- var _scale = new THREE7.Vector3();
2366
- var _matrix = new THREE7.Matrix4();
2367
- var _modelMatrix = new THREE7.Matrix4();
2396
+ var _position = new THREE19.Vector3();
2397
+ var _quaternion = new THREE19.Quaternion();
2398
+ var _euler = new THREE19.Euler();
2399
+ var _scale = new THREE19.Vector3();
2400
+ var _matrix = new THREE19.Matrix4();
2401
+ var _modelMatrix = new THREE19.Matrix4();
2368
2402
  function instanceTransformFromComponent(transform, out) {
2369
2403
  _position.set(transform.position.x, transform.position.y, transform.position.z);
2370
2404
  _euler.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, "XYZ");
@@ -2517,7 +2551,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2517
2551
  const sourceMaterial = Array.isArray(meshChild.material) ? meshChild.material.map((m) => m.clone()) : meshChild.material.clone();
2518
2552
  meshChild.updateWorldMatrix(true, false);
2519
2553
  const localMatrix = meshChild.matrixWorld.clone();
2520
- const instancedMesh2 = new THREE7.InstancedMesh(
2554
+ const instancedMesh2 = new THREE19.InstancedMesh(
2521
2555
  geometry2,
2522
2556
  Array.isArray(sourceMaterial) ? sourceMaterial : sourceMaterial,
2523
2557
  count2
@@ -2542,7 +2576,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2542
2576
  this.loadingSignatures.delete(targetEntityId);
2543
2577
  return;
2544
2578
  }
2545
- const group = new THREE7.Group();
2579
+ const group = new THREE19.Group();
2546
2580
  for (const entry of entries) {
2547
2581
  group.add(entry.instancedMesh);
2548
2582
  }
@@ -2560,8 +2594,8 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2560
2594
  }
2561
2595
  const count = instanceCount;
2562
2596
  const geometry = buildGeometry(comp);
2563
- const material = new THREE7.MeshStandardMaterial({ color: "#ffffff" });
2564
- const instancedMesh = new THREE7.InstancedMesh(geometry, material, count);
2597
+ const material = new THREE19.MeshStandardMaterial({ color: "#ffffff" });
2598
+ const instancedMesh = new THREE19.InstancedMesh(geometry, material, count);
2565
2599
  instancedMesh.castShadow = comp.castShadow;
2566
2600
  instancedMesh.receiveShadow = comp.receiveShadow;
2567
2601
  for (let i = 0; i < count; i++) {
@@ -2981,9 +3015,6 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2981
3015
  if (typeof sourceInteractable.pointable === "boolean") {
2982
3016
  interactable.pointable = sourceInteractable.pointable;
2983
3017
  }
2984
- if (typeof sourceInteractable.grabMode === "string") {
2985
- interactable.grabMode = sourceInteractable.grabMode;
2986
- }
2987
3018
  if (typeof sourceInteractable.grabReach === "string") {
2988
3019
  interactable.grabReach = sourceInteractable.grabReach;
2989
3020
  }
@@ -2996,6 +3027,30 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2996
3027
  if (typeof sourceInteractable.grabRadius === "number") {
2997
3028
  interactable.grabRadius = sourceInteractable.grabRadius;
2998
3029
  }
3030
+ if (typeof sourceInteractable.grabHands === "string") {
3031
+ interactable.grabHands = sourceInteractable.grabHands;
3032
+ }
3033
+ if (typeof sourceInteractable.twoHandRotation === "string") {
3034
+ interactable.twoHandRotation = sourceInteractable.twoHandRotation;
3035
+ }
3036
+ if (typeof sourceInteractable.allowTwoHandScale === "boolean") {
3037
+ interactable.allowTwoHandScale = sourceInteractable.allowTwoHandScale;
3038
+ }
3039
+ if (typeof sourceInteractable.minScale === "number") {
3040
+ interactable.minScale = sourceInteractable.minScale;
3041
+ }
3042
+ if (typeof sourceInteractable.maxScale === "number") {
3043
+ interactable.maxScale = sourceInteractable.maxScale;
3044
+ }
3045
+ if (Array.isArray(sourceInteractable.attachPoints)) {
3046
+ interactable.attachPoints = sourceInteractable.attachPoints.map((point) => asRecord(point)).filter(
3047
+ (point) => point !== null && typeof point.id === "string"
3048
+ ).map(
3049
+ (point) => createAttachPoint(
3050
+ point
3051
+ )
3052
+ );
3053
+ }
2999
3054
  if (typeof sourceInteractable.throwOnRelease === "boolean") {
3000
3055
  interactable.throwOnRelease = sourceInteractable.throwOnRelease;
3001
3056
  }
@@ -3018,8 +3073,16 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
3018
3073
  z: typeof grabRot.z === "number" ? grabRot.z : 0
3019
3074
  };
3020
3075
  }
3021
- if (typeof sourceInteractable.highlightOnHover === "boolean") {
3022
- interactable.highlightOnHover = sourceInteractable.highlightOnHover;
3076
+ if (typeof sourceInteractable.hoverBehavior === "string") {
3077
+ interactable.hoverBehavior = sourceInteractable.hoverBehavior;
3078
+ } else if (sourceInteractable.highlightOnHover === false) {
3079
+ interactable.hoverBehavior = "none";
3080
+ }
3081
+ if (typeof sourceInteractable.hoverColor === "string") {
3082
+ interactable.hoverColor = sourceInteractable.hoverColor;
3083
+ }
3084
+ if (typeof sourceInteractable.hoverScale === "number") {
3085
+ interactable.hoverScale = sourceInteractable.hoverScale;
3023
3086
  }
3024
3087
  }
3025
3088
  }
@@ -4747,7 +4810,7 @@ var _Behaviour = class _Behaviour {
4747
4810
  if (!this.threeRef) {
4748
4811
  return null;
4749
4812
  }
4750
- const position = new THREE7.Vector3();
4813
+ const position = new THREE19.Vector3();
4751
4814
  this.threeRef.object.getWorldPosition(position);
4752
4815
  return position;
4753
4816
  }
@@ -4759,7 +4822,7 @@ var _Behaviour = class _Behaviour {
4759
4822
  if (!this.threeRef) {
4760
4823
  return null;
4761
4824
  }
4762
- const target = new THREE7.Vector3();
4825
+ const target = new THREE19.Vector3();
4763
4826
  this.threeRef.object.getWorldDirection(target);
4764
4827
  return target;
4765
4828
  }
@@ -4821,7 +4884,7 @@ var _Behaviour = class _Behaviour {
4821
4884
  if (!this.threeRef) {
4822
4885
  return;
4823
4886
  }
4824
- const rotation = new THREE7.Quaternion();
4887
+ const rotation = new THREE19.Quaternion();
4825
4888
  this.threeRef.object.getWorldQuaternion(rotation);
4826
4889
  rotation.slerp(targetRotation, alpha);
4827
4890
  this.transform.setRotation(rotation);
@@ -4838,7 +4901,7 @@ var _Behaviour = class _Behaviour {
4838
4901
  return;
4839
4902
  }
4840
4903
  const clone2 = this.threeRef.object.clone();
4841
- const rotation = new THREE7.Quaternion();
4904
+ const rotation = new THREE19.Quaternion();
4842
4905
  clone2.lookAt(targetPosition);
4843
4906
  clone2.getWorldQuaternion(rotation);
4844
4907
  this.transform.setRotation(rotation);
@@ -4976,6 +5039,17 @@ var _Behaviour = class _Behaviour {
4976
5039
  *
4977
5040
  * @param input - The source doing the grabbing.
4978
5041
  */
5042
+ /**
5043
+ * A SECOND hand joined a two-hand grab on this entity.
5044
+ *
5045
+ * The first hand anchors the object and keeps holding it; this fires for the
5046
+ * hand that came second — the foregrip on a rifle, say.
5047
+ */
5048
+ onSecondaryGrab(_input) {
5049
+ }
5050
+ /** The second hand let go, leaving the object in the first hand alone. */
5051
+ onSecondaryRelease(_input) {
5052
+ }
4979
5053
  /**
4980
5054
  * A hand pose declared on this entity started being held.
4981
5055
  *
@@ -5193,8 +5267,8 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
5193
5267
  }, "onPointerUp");
5194
5268
  this.moduleLoader = moduleLoader ?? defaultModuleLoader;
5195
5269
  this.selectionCtx = selectionContext ?? null;
5196
- this.raycaster = new THREE7.Raycaster();
5197
- this.ndcPointer = new THREE7.Vector2();
5270
+ this.raycaster = new THREE19.Raycaster();
5271
+ this.ndcPointer = new THREE19.Vector2();
5198
5272
  if (this.selectionCtx) {
5199
5273
  this.selectionCtx.domElement.addEventListener("pointerdown", this.onPointerDown);
5200
5274
  this.selectionCtx.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -5573,7 +5647,7 @@ function disposeBuiltObject(root) {
5573
5647
  for (const mat of materials) {
5574
5648
  if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
5575
5649
  for (const value of Object.values(mat)) {
5576
- if (value instanceof THREE7.Texture) value.dispose();
5650
+ if (value instanceof THREE19.Texture) value.dispose();
5577
5651
  }
5578
5652
  mat.dispose?.();
5579
5653
  }
@@ -5937,7 +6011,7 @@ var _AudioSystem = class _AudioSystem {
5937
6011
  if (!prev || prev.volume !== opts.volume) {
5938
6012
  sound.setVolume(opts.volume);
5939
6013
  }
5940
- if (opts.positional && sound instanceof THREE7.PositionalAudio) {
6014
+ if (opts.positional && sound instanceof THREE19.PositionalAudio) {
5941
6015
  if (!prev || prev.refDistance !== opts.refDistance) {
5942
6016
  sound.setRefDistance(opts.refDistance);
5943
6017
  }
@@ -6063,7 +6137,7 @@ var _VideoSystem = class _VideoSystem {
6063
6137
  component.object.scale.set(opts.width, opts.height, 1);
6064
6138
  }
6065
6139
  const material = component.material;
6066
- if (material instanceof THREE7.ShaderMaterial) {
6140
+ if (material instanceof THREE19.ShaderMaterial) {
6067
6141
  if (!prev || prev.backgroundColor !== opts.backgroundColor) {
6068
6142
  material.uniforms.keyColor.value.set(opts.backgroundColor);
6069
6143
  }
@@ -6077,7 +6151,7 @@ var _VideoSystem = class _VideoSystem {
6077
6151
  material.uniforms.spill.value = opts.spill;
6078
6152
  }
6079
6153
  }
6080
- if (opts.positional && component.audioNode instanceof THREE7.PositionalAudio) {
6154
+ if (opts.positional && component.audioNode instanceof THREE19.PositionalAudio) {
6081
6155
  const posAudio = component.audioNode;
6082
6156
  if (!prev || prev.refDistance !== opts.refDistance) {
6083
6157
  posAudio.setRefDistance(opts.refDistance);
@@ -6117,8 +6191,8 @@ function blankState(source) {
6117
6191
  handedness: handednessOf(source),
6118
6192
  kind: kindOf(source),
6119
6193
  connected: true,
6120
- rayOrigin: new THREE7.Vector3(),
6121
- rayDirection: new THREE7.Vector3(0, 0, -1),
6194
+ rayOrigin: new THREE19.Vector3(),
6195
+ rayDirection: new THREE19.Vector3(0, 0, -1),
6122
6196
  gripPosition: null,
6123
6197
  gripQuaternion: null,
6124
6198
  select: false,
@@ -6144,7 +6218,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
6144
6218
  }
6145
6219
  if (spaces.length === 0) return null;
6146
6220
  const target = existing ?? {};
6147
- const matrix = new THREE7.Matrix4();
6221
+ const matrix = new THREE19.Matrix4();
6148
6222
  const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
6149
6223
  const fillPoses = frame.fillPoses;
6150
6224
  if (typeof fillPoses === "function") {
@@ -6157,7 +6231,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
6157
6231
  if (filled) {
6158
6232
  for (let i = 0; i < names.length; i += 1) {
6159
6233
  matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
6160
- const vector = target[names[i]] ?? new THREE7.Vector3();
6234
+ const vector = target[names[i]] ?? new THREE19.Vector3();
6161
6235
  vector.setFromMatrixPosition(matrix);
6162
6236
  target[names[i]] = vector;
6163
6237
  }
@@ -6175,7 +6249,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
6175
6249
  continue;
6176
6250
  }
6177
6251
  matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
6178
- const vector = target[names[i]] ?? new THREE7.Vector3();
6252
+ const vector = target[names[i]] ?? new THREE19.Vector3();
6179
6253
  vector.setFromMatrixPosition(matrix);
6180
6254
  target[names[i]] = vector;
6181
6255
  anyTracked = true;
@@ -6187,7 +6261,7 @@ var _XRInputSystem = class _XRInputSystem {
6187
6261
  constructor() {
6188
6262
  this.records = /* @__PURE__ */ new Map();
6189
6263
  /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
6190
- this.referenceToWorld = new THREE7.Matrix4();
6264
+ this.referenceToWorld = new THREE19.Matrix4();
6191
6265
  this.session = null;
6192
6266
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
6193
6267
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -6285,19 +6359,19 @@ var _XRInputSystem = class _XRInputSystem {
6285
6359
  if (frame && referenceSpace && source.targetRaySpace) {
6286
6360
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
6287
6361
  if (pose) {
6288
- const m = new THREE7.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
6362
+ const m = new THREE19.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
6289
6363
  state.rayOrigin.setFromMatrixPosition(m);
6290
6364
  state.rayDirection.set(0, 0, -1).applyQuaternion(
6291
- new THREE7.Quaternion().setFromRotationMatrix(m)
6365
+ new THREE19.Quaternion().setFromRotationMatrix(m)
6292
6366
  ).normalize();
6293
6367
  }
6294
6368
  }
6295
6369
  if (frame && referenceSpace && source.gripSpace) {
6296
6370
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
6297
6371
  if (gripPose) {
6298
- const m = new THREE7.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
6299
- state.gripPosition ?? (state.gripPosition = new THREE7.Vector3());
6300
- state.gripQuaternion ?? (state.gripQuaternion = new THREE7.Quaternion());
6372
+ const m = new THREE19.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
6373
+ state.gripPosition ?? (state.gripPosition = new THREE19.Vector3());
6374
+ state.gripQuaternion ?? (state.gripQuaternion = new THREE19.Quaternion());
6301
6375
  state.gripPosition.setFromMatrixPosition(m);
6302
6376
  state.gripQuaternion.setFromRotationMatrix(m);
6303
6377
  }
@@ -6406,15 +6480,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
6406
6480
  const fresh = this.samples.filter(
6407
6481
  (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
6408
6482
  );
6409
- if (fresh.length < 2) return new THREE7.Vector3();
6410
- let best = new THREE7.Vector3();
6483
+ if (fresh.length < 2) return new THREE19.Vector3();
6484
+ let best = new THREE19.Vector3();
6411
6485
  let bestSpeed = 0;
6412
6486
  for (let i = 1; i < fresh.length; i += 1) {
6413
6487
  const previous = fresh[i - 1];
6414
6488
  const current = fresh[i];
6415
6489
  const dt = current.time - previous.time;
6416
6490
  if (dt <= 1e-5) continue;
6417
- const velocity = new THREE7.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
6491
+ const velocity = new THREE19.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
6418
6492
  const speed = velocity.length();
6419
6493
  if (speed > MAX_PLAUSIBLE_SPEED) continue;
6420
6494
  if (speed > bestSpeed) {
@@ -6431,25 +6505,79 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
6431
6505
  __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
6432
6506
  var ThrowVelocityTracker = _ThrowVelocityTracker;
6433
6507
  function angularVelocityFrom(previous, current, deltaTime) {
6434
- if (deltaTime <= 1e-5) return new THREE7.Vector3();
6508
+ if (deltaTime <= 1e-5) return new THREE19.Vector3();
6435
6509
  const delta = current.clone().multiply(previous.clone().invert());
6436
6510
  if (delta.w < 0) {
6437
6511
  delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
6438
6512
  }
6439
- const angle = 2 * Math.acos(THREE7.MathUtils.clamp(delta.w, -1, 1));
6513
+ const angle = 2 * Math.acos(THREE19.MathUtils.clamp(delta.w, -1, 1));
6440
6514
  const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
6441
- if (sinHalf < 1e-6 || angle < 1e-6) return new THREE7.Vector3();
6442
- return new THREE7.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
6515
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE19.Vector3();
6516
+ return new THREE19.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
6443
6517
  }
6444
6518
  __name(angularVelocityFrom, "angularVelocityFrom");
6519
+ var MIN_HAND_SEPARATION = 0.06;
6520
+ var MIN_ANCHOR_SEPARATION = 1e-3;
6521
+ var SCALE_DEADZONE = 0.05;
6522
+ function solveTwoHandTransform(primary, secondary, basis, options) {
6523
+ const separation = primary.position.distanceTo(secondary.position);
6524
+ const anchorDelta = new THREE19.Vector3().subVectors(
6525
+ basis.secondaryLocal,
6526
+ basis.primaryLocal
6527
+ );
6528
+ const anchorLength = anchorDelta.length();
6529
+ let factor = 1;
6530
+ if (options.allowScale && basis.initialSeparation > MIN_ANCHOR_SEPARATION && separation > MIN_ANCHOR_SEPARATION) {
6531
+ factor = separation / basis.initialSeparation;
6532
+ if (Math.abs(factor - 1) <= SCALE_DEADZONE) {
6533
+ factor = 1;
6534
+ } else {
6535
+ factor += factor > 1 ? -SCALE_DEADZONE : SCALE_DEADZONE;
6536
+ }
6537
+ factor = Math.min(options.maxScale, Math.max(options.minScale, factor));
6538
+ }
6539
+ const scale = basis.initialScale.clone().multiplyScalar(factor);
6540
+ const degenerate = separation < MIN_HAND_SEPARATION || anchorLength < MIN_ANCHOR_SEPARATION;
6541
+ const base = primary.quaternion.clone().multiply(basis.primaryHandOffset);
6542
+ let quaternion;
6543
+ if (options.rotationMode === "primary" || degenerate) {
6544
+ quaternion = base;
6545
+ } else {
6546
+ const axisLocal = anchorDelta.clone().normalize();
6547
+ const currentAxis = axisLocal.clone().applyQuaternion(base).normalize();
6548
+ const desiredAxis = new THREE19.Vector3().subVectors(secondary.position, primary.position).normalize();
6549
+ const correction = new THREE19.Quaternion().setFromUnitVectors(
6550
+ currentAxis,
6551
+ desiredAxis
6552
+ );
6553
+ quaternion = correction.multiply(base);
6554
+ }
6555
+ const anchorOffset = basis.primaryLocal.clone().multiply(scale).applyQuaternion(quaternion);
6556
+ const position = primary.position.clone().sub(anchorOffset);
6557
+ return { position, quaternion, scale, degenerate };
6558
+ }
6559
+ __name(solveTwoHandTransform, "solveTwoHandTransform");
6445
6560
 
6446
6561
  // core/systems/XRInteractionSystem.ts
6447
6562
  var RAYLESS_HOLD_DISTANCE = 0.5;
6563
+ function quatFromEulerVec(r) {
6564
+ return new THREE19.Quaternion().setFromEuler(new THREE19.Euler(r.x, r.y, r.z));
6565
+ }
6566
+ __name(quatFromEulerVec, "quatFromEulerVec");
6567
+ function writeEuler(target, q) {
6568
+ const euler = new THREE19.Euler().setFromQuaternion(q);
6569
+ target.x = euler.x;
6570
+ target.y = euler.y;
6571
+ target.z = euler.z;
6572
+ }
6573
+ __name(writeEuler, "writeEuler");
6448
6574
  var _XRInteractionSystem = class _XRInteractionSystem {
6449
6575
  constructor(inputSystem) {
6450
- this.raycaster = new THREE7.Raycaster();
6576
+ this.raycaster = new THREE19.Raycaster();
6451
6577
  this.grabs = /* @__PURE__ */ new Map();
6452
6578
  this.hoveredEntityId = null;
6579
+ /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
6580
+ this.hovers = /* @__PURE__ */ new Map();
6453
6581
  /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
6454
6582
  this.elapsed = 0;
6455
6583
  this.inputSystem = inputSystem;
@@ -6463,6 +6591,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6463
6591
  const inputs = this.inputSystem.getInputs();
6464
6592
  const interactables = this.collectInteractables(world);
6465
6593
  this.hoveredEntityId = null;
6594
+ this.hovers.clear();
6466
6595
  for (const input of inputs) {
6467
6596
  if (!input.connected) {
6468
6597
  this.release(world, input);
@@ -6470,7 +6599,6 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6470
6599
  }
6471
6600
  const held = this.grabs.get(input);
6472
6601
  if (held) {
6473
- this.moveHeld(world, input, held, deltaTime);
6474
6602
  if (this.wantsRelease(world, input, held)) {
6475
6603
  this.release(world, input);
6476
6604
  }
@@ -6481,6 +6609,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6481
6609
  if (this.hoveredEntityId === null) {
6482
6610
  this.hoveredEntityId = candidate.entityId;
6483
6611
  }
6612
+ this.hovers.set(input, {
6613
+ entityId: candidate.entityId,
6614
+ distance: candidate.distance
6615
+ });
6484
6616
  const interactable = world.getComponent(
6485
6617
  candidate.entityId,
6486
6618
  XRInteractableComponent
@@ -6491,9 +6623,122 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6491
6623
  /* starting */
6492
6624
  true
6493
6625
  )) {
6494
- this.grab(world, input, candidate.entityId, candidate.viaDistance);
6626
+ this.tryGrab(world, input, candidate.entityId, candidate.viaDistance);
6495
6627
  }
6496
6628
  }
6629
+ this.moveAllHeld(world, deltaTime);
6630
+ }
6631
+ /** Hands currently holding an entity. */
6632
+ holdersOf(entityId) {
6633
+ const holders = [];
6634
+ for (const [input, record] of this.grabs) {
6635
+ if (record.entityId === entityId) holders.push({ input, record });
6636
+ }
6637
+ return holders;
6638
+ }
6639
+ /**
6640
+ * Route a new grab: first hand, hand-off, or second hand.
6641
+ *
6642
+ * Which of those happens is the object's choice (`grabHands`), not the
6643
+ * hand's — a ball should pass between hands, a rifle should be held by both,
6644
+ * and a lever should stay with whoever grabbed it first.
6645
+ */
6646
+ tryGrab(world, input, entityId, viaDistance) {
6647
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
6648
+ if (!interactable) return;
6649
+ const holders = this.holdersOf(entityId);
6650
+ if (holders.length === 0) {
6651
+ this.grab(world, input, entityId, viaDistance, "primary");
6652
+ return;
6653
+ }
6654
+ if (holders.some((holder) => holder.input === input)) return;
6655
+ if (interactable.grabHands === "single") return;
6656
+ if (interactable.grabHands === "handOff") {
6657
+ const previous = holders[0];
6658
+ this.grabs.delete(previous.input);
6659
+ const record = previous.record;
6660
+ record.role = "primary";
6661
+ record.twoHand = null;
6662
+ this.reanchor(world, input, record, interactable);
6663
+ this.grabs.set(input, record);
6664
+ this.dispatch(world, entityId, (b) => b.onGrab(input));
6665
+ return;
6666
+ }
6667
+ if (holders.length >= 2) return;
6668
+ this.grab(world, input, entityId, viaDistance, "secondary");
6669
+ this.beginTwoHand(world, entityId);
6670
+ }
6671
+ /**
6672
+ * Re-derive a record's hold from where the object is NOW.
6673
+ *
6674
+ * Used on hand-off and on promotion after the primary lets go. Without it the
6675
+ * object snaps to whatever offset the previous hand had, which reads as the
6676
+ * object jumping out of your grip at the moment you take it.
6677
+ */
6678
+ reanchor(world, input, record, interactable) {
6679
+ const transform = world.getComponent(record.entityId, TransformComponent);
6680
+ if (!transform) return;
6681
+ const hand = this.handPosition(input);
6682
+ const handQuaternion = this.handQuaternion(input);
6683
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
6684
+ record.offset = new THREE19.Vector3(
6685
+ transform.position.x - hand.x,
6686
+ transform.position.y - hand.y,
6687
+ transform.position.z - hand.z
6688
+ ).applyQuaternion(handQuaternion.clone().invert());
6689
+ record.handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
6690
+ record.remote = false;
6691
+ record.tracker.clear();
6692
+ record.tracker.record(hand, this.elapsed);
6693
+ record.lastHandQuaternion = handQuaternion.clone();
6694
+ if (record.attachLocal && interactable.attachPoints.length === 0) {
6695
+ record.attachLocal = null;
6696
+ record.attachQuaternion = null;
6697
+ record.attachPointId = null;
6698
+ }
6699
+ }
6700
+ /** Capture the two-hand basis once both hands are on the object. */
6701
+ beginTwoHand(world, entityId) {
6702
+ const holders = this.holdersOf(entityId);
6703
+ if (holders.length !== 2) return;
6704
+ const primary = holders.find((h) => h.record.role === "primary");
6705
+ const secondary = holders.find((h) => h.record.role === "secondary");
6706
+ if (!primary || !secondary) return;
6707
+ const transform = world.getComponent(entityId, TransformComponent);
6708
+ if (!transform) return;
6709
+ const primaryHand = this.handPosition(primary.input);
6710
+ const secondaryHand = this.handPosition(secondary.input);
6711
+ const primaryLocal = primary.record.attachLocal?.clone() ?? this.objectLocalPoint(transform, primaryHand);
6712
+ const secondaryLocal = secondary.record.attachLocal?.clone() ?? this.objectLocalPoint(transform, secondaryHand);
6713
+ primary.record.twoHand = {
6714
+ primaryLocal,
6715
+ secondaryLocal,
6716
+ initialScale: new THREE19.Vector3(
6717
+ transform.scale.x,
6718
+ transform.scale.y,
6719
+ transform.scale.z
6720
+ ),
6721
+ initialSeparation: primaryHand.distanceTo(secondaryHand),
6722
+ // How the object sits relative to the anchoring hand right now, so the
6723
+ // two-hand solve picks up exactly where the one-hand hold left off.
6724
+ primaryHandOffset: this.handQuaternion(primary.input).invert().multiply(quatFromEulerVec(transform.rotation)),
6725
+ initialQuaternion: quatFromEulerVec(transform.rotation)
6726
+ };
6727
+ this.dispatch(world, entityId, (b) => b.onSecondaryGrab(secondary.input));
6728
+ }
6729
+ /** World point → the object's UNSCALED local space. */
6730
+ objectLocalPoint(transform, worldPoint) {
6731
+ const local = worldPoint.clone().sub(
6732
+ new THREE19.Vector3(
6733
+ transform.position.x,
6734
+ transform.position.y,
6735
+ transform.position.z
6736
+ )
6737
+ ).applyQuaternion(quatFromEulerVec(transform.rotation).invert());
6738
+ local.x /= transform.scale.x || 1;
6739
+ local.y /= transform.scale.y || 1;
6740
+ local.z /= transform.scale.z || 1;
6741
+ return local;
6497
6742
  }
6498
6743
  /** Whether the grab gesture for this object is currently starting. */
6499
6744
  gestureMatches(input, interactable, starting) {
@@ -6542,7 +6787,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6542
6787
  );
6543
6788
  if (!interactable2) continue;
6544
6789
  if (interactable2.grabReach === "distance") continue;
6545
- const position = new THREE7.Vector3();
6790
+ const position = new THREE19.Vector3();
6546
6791
  entry.object.getWorldPosition(position);
6547
6792
  const distance = position.distanceTo(hand);
6548
6793
  if (distance <= interactable2.grabRadius && distance < nearestDistance) {
@@ -6551,13 +6796,22 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6551
6796
  }
6552
6797
  }
6553
6798
  if (nearestId !== null) {
6554
- return { entityId: nearestId, viaDistance: false };
6799
+ return {
6800
+ entityId: nearestId,
6801
+ viaDistance: false,
6802
+ distance: nearestDistance
6803
+ };
6555
6804
  }
6556
- const hitId = this.raycast(input, interactables);
6557
- if (hitId === null) return null;
6805
+ const hit = this.raycast(input, interactables);
6806
+ if (hit === null) return null;
6807
+ const hitId = hit.entityId;
6558
6808
  const interactable = world.getComponent(hitId, XRInteractableComponent);
6559
6809
  const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
6560
- return { entityId: hitId, viaDistance: viaDistanceAllowed };
6810
+ return {
6811
+ entityId: hitId,
6812
+ viaDistance: viaDistanceAllowed,
6813
+ distance: hit.distance
6814
+ };
6561
6815
  }
6562
6816
  collectInteractables(world) {
6563
6817
  const out = [];
@@ -6588,9 +6842,54 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6588
6842
  nearestId = entry.entityId;
6589
6843
  }
6590
6844
  }
6591
- return nearestId;
6845
+ return nearestId === null ? null : { entityId: nearestId, distance: nearestDistance };
6846
+ }
6847
+ /**
6848
+ * Pick the authored hold this hand should take.
6849
+ *
6850
+ * Filters by hand and by role — that is what makes "the trigger hand goes on
6851
+ * the grip" hold regardless of which hand reaches first — then takes the
6852
+ * nearest remaining one so grabbing near the foregrip picks the foregrip.
6853
+ */
6854
+ chooseAttachPoint(world, input, entityId, interactable, role) {
6855
+ if (interactable.attachPoints.length === 0) return null;
6856
+ const transform = world.getComponent(entityId, TransformComponent);
6857
+ if (!transform) return null;
6858
+ const taken = new Set(
6859
+ this.holdersOf(entityId).map((holder) => holder.record.attachPointId).filter((id) => id !== null)
6860
+ );
6861
+ const hand = this.handPosition(input);
6862
+ const objectPosition = new THREE19.Vector3(
6863
+ transform.position.x,
6864
+ transform.position.y,
6865
+ transform.position.z
6866
+ );
6867
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
6868
+ const scale = new THREE19.Vector3(
6869
+ transform.scale.x,
6870
+ transform.scale.y,
6871
+ transform.scale.z
6872
+ );
6873
+ let best = null;
6874
+ let bestDistance = Number.POSITIVE_INFINITY;
6875
+ for (const point of interactable.attachPoints) {
6876
+ if (taken.has(point.id)) continue;
6877
+ if (point.hand !== "any" && point.hand !== input.handedness) continue;
6878
+ if (point.role !== "any" && point.role !== role) continue;
6879
+ const world3 = new THREE19.Vector3(
6880
+ point.position.x,
6881
+ point.position.y,
6882
+ point.position.z
6883
+ ).multiply(scale).applyQuaternion(objectQuaternion).add(objectPosition);
6884
+ const distance = world3.distanceTo(hand);
6885
+ if (distance < bestDistance) {
6886
+ bestDistance = distance;
6887
+ best = point;
6888
+ }
6889
+ }
6890
+ return best;
6592
6891
  }
6593
- grab(world, input, entityId, viaDistance) {
6892
+ grab(world, input, entityId, viaDistance, role) {
6594
6893
  const transform = world.getComponent(entityId, TransformComponent);
6595
6894
  const interactable = world.getComponent(entityId, XRInteractableComponent);
6596
6895
  if (!transform || !interactable) return;
@@ -6600,25 +6899,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6600
6899
  let offset;
6601
6900
  let remoteDistance = 0;
6602
6901
  if (remote) {
6603
- const position = new THREE7.Vector3(
6902
+ const position = new THREE19.Vector3(
6604
6903
  transform.position.x,
6605
6904
  transform.position.y,
6606
6905
  transform.position.z
6607
6906
  );
6608
6907
  remoteDistance = position.distanceTo(input.rayOrigin);
6609
- offset = new THREE7.Vector3();
6908
+ offset = new THREE19.Vector3();
6610
6909
  } else if (viaDistance) {
6611
- offset = new THREE7.Vector3(
6910
+ offset = new THREE19.Vector3(
6612
6911
  interactable.grabOffsetPosition.x,
6613
6912
  interactable.grabOffsetPosition.y,
6614
6913
  interactable.grabOffsetPosition.z
6615
6914
  );
6616
6915
  } else {
6617
- offset = new THREE7.Vector3(
6916
+ offset = new THREE19.Vector3(
6618
6917
  transform.position.x - hand.x,
6619
6918
  transform.position.y - hand.y,
6620
6919
  transform.position.z - hand.z
6621
- );
6920
+ ).applyQuaternion(handQuaternion.clone().invert());
6622
6921
  }
6623
6922
  const physics = world.getComponent(entityId, PhysicsComponent);
6624
6923
  let restoreBodyType = null;
@@ -6628,11 +6927,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6628
6927
  }
6629
6928
  const tracker = new ThrowVelocityTracker();
6630
6929
  tracker.record(hand, this.elapsed);
6930
+ const attachPoint = this.chooseAttachPoint(
6931
+ world,
6932
+ input,
6933
+ entityId,
6934
+ interactable,
6935
+ role
6936
+ );
6937
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
6938
+ const handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
6631
6939
  this.grabs.set(input, {
6632
6940
  entityId,
6941
+ role,
6942
+ attachPointId: attachPoint?.id ?? null,
6943
+ attachLocal: attachPoint ? new THREE19.Vector3(
6944
+ attachPoint.position.x,
6945
+ attachPoint.position.y,
6946
+ attachPoint.position.z
6947
+ ) : null,
6948
+ attachQuaternion: attachPoint ? quatFromEulerVec(attachPoint.rotation) : null,
6949
+ handRotationOffset,
6950
+ twoHand: null,
6633
6951
  offset,
6634
- rotationOffset: new THREE7.Quaternion().setFromEuler(
6635
- new THREE7.Euler(
6952
+ rotationOffset: new THREE19.Quaternion().setFromEuler(
6953
+ new THREE19.Euler(
6636
6954
  interactable.grabOffsetRotation.x,
6637
6955
  interactable.grabOffsetRotation.y,
6638
6956
  interactable.grabOffsetRotation.z
@@ -6643,33 +6961,111 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6643
6961
  restoreBodyType,
6644
6962
  tracker,
6645
6963
  lastHandQuaternion: handQuaternion.clone(),
6646
- angularVelocity: new THREE7.Vector3()
6964
+ angularVelocity: new THREE19.Vector3()
6647
6965
  });
6648
- this.dispatch(world, entityId, (b) => b.onGrab(input));
6966
+ if (role === "primary") {
6967
+ this.dispatch(world, entityId, (b) => b.onGrab(input));
6968
+ }
6969
+ }
6970
+ /** Drive every held object, once each, after grabs and releases settle. */
6971
+ moveAllHeld(world, deltaTime) {
6972
+ const done = /* @__PURE__ */ new Set();
6973
+ for (const [input, record] of this.grabs) {
6974
+ if (done.has(record.entityId)) continue;
6975
+ done.add(record.entityId);
6976
+ const holders = this.holdersOf(record.entityId);
6977
+ if (holders.length >= 2) {
6978
+ this.moveTwoHand(world, record.entityId, holders, deltaTime);
6979
+ } else {
6980
+ this.moveHeld(world, input, record, deltaTime);
6981
+ }
6982
+ }
6983
+ }
6984
+ /** Solve and apply a two-hand hold. */
6985
+ moveTwoHand(world, entityId, holders, deltaTime) {
6986
+ const primary = holders.find((h) => h.record.role === "primary");
6987
+ const secondary = holders.find((h) => h.record.role === "secondary");
6988
+ const transform = world.getComponent(entityId, TransformComponent);
6989
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
6990
+ const basis = primary?.record.twoHand ?? null;
6991
+ if (!primary || !secondary || !transform || !interactable || !basis) {
6992
+ if (primary) {
6993
+ this.moveHeld(world, primary.input, primary.record, deltaTime);
6994
+ }
6995
+ return;
6996
+ }
6997
+ const primaryHand = this.handPosition(primary.input);
6998
+ const primaryQuaternion = this.handQuaternion(primary.input);
6999
+ const solved = solveTwoHandTransform(
7000
+ { position: primaryHand, quaternion: primaryQuaternion },
7001
+ {
7002
+ position: this.handPosition(secondary.input),
7003
+ quaternion: this.handQuaternion(secondary.input)
7004
+ },
7005
+ basis,
7006
+ {
7007
+ rotationMode: interactable.twoHandRotation,
7008
+ allowScale: interactable.allowTwoHandScale,
7009
+ minScale: interactable.minScale,
7010
+ maxScale: interactable.maxScale
7011
+ }
7012
+ );
7013
+ transform.position.x = solved.position.x;
7014
+ transform.position.y = solved.position.y;
7015
+ transform.position.z = solved.position.z;
7016
+ writeEuler(transform.rotation, solved.quaternion);
7017
+ if (interactable.allowTwoHandScale) {
7018
+ transform.scale.x = solved.scale.x;
7019
+ transform.scale.y = solved.scale.y;
7020
+ transform.scale.z = solved.scale.z;
7021
+ }
7022
+ primary.record.tracker.record(primaryHand, this.elapsed);
7023
+ primary.record.angularVelocity = angularVelocityFrom(
7024
+ primary.record.lastHandQuaternion,
7025
+ primaryQuaternion,
7026
+ deltaTime
7027
+ );
7028
+ primary.record.lastHandQuaternion = primaryQuaternion.clone();
6649
7029
  }
6650
7030
  moveHeld(world, input, held, deltaTime) {
6651
7031
  const transform = world.getComponent(held.entityId, TransformComponent);
6652
7032
  if (!transform) return;
6653
7033
  const hand = this.handPosition(input);
7034
+ const handQuaternion = this.handQuaternion(input);
7035
+ const objectQuaternion = held.attachQuaternion ? handQuaternion.clone().multiply(held.rotationOffset).multiply(held.attachQuaternion.clone().invert()) : handQuaternion.clone().multiply(held.handRotationOffset);
6654
7036
  if (held.remote) {
6655
- const target = new THREE7.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
7037
+ const target = new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
6656
7038
  transform.position.x = target.x;
6657
7039
  transform.position.y = target.y;
6658
7040
  transform.position.z = target.z;
7041
+ writeEuler(transform.rotation, objectQuaternion);
7042
+ } else if (held.attachLocal) {
7043
+ const anchor = held.attachLocal.clone().multiply(
7044
+ new THREE19.Vector3(
7045
+ transform.scale.x,
7046
+ transform.scale.y,
7047
+ transform.scale.z
7048
+ )
7049
+ ).applyQuaternion(objectQuaternion);
7050
+ transform.position.x = hand.x - anchor.x;
7051
+ transform.position.y = hand.y - anchor.y;
7052
+ transform.position.z = hand.z - anchor.z;
7053
+ writeEuler(transform.rotation, objectQuaternion);
6659
7054
  } else {
6660
- transform.position.x = hand.x + held.offset.x;
6661
- transform.position.y = hand.y + held.offset.y;
6662
- transform.position.z = hand.z + held.offset.z;
7055
+ const carried = held.offset.clone().applyQuaternion(handQuaternion);
7056
+ transform.position.x = hand.x + carried.x;
7057
+ transform.position.y = hand.y + carried.y;
7058
+ transform.position.z = hand.z + carried.z;
7059
+ writeEuler(transform.rotation, objectQuaternion);
6663
7060
  }
6664
7061
  held.tracker.record(
6665
- held.remote ? new THREE7.Vector3(
7062
+ held.remote ? new THREE19.Vector3(
6666
7063
  transform.position.x,
6667
7064
  transform.position.y,
6668
7065
  transform.position.z
6669
7066
  ) : hand,
6670
7067
  this.elapsed
6671
7068
  );
6672
- const handQuaternion = this.handQuaternion(input);
6673
7069
  held.angularVelocity = angularVelocityFrom(
6674
7070
  held.lastHandQuaternion,
6675
7071
  handQuaternion,
@@ -6685,22 +7081,41 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6685
7081
  held.entityId,
6686
7082
  XRInteractableComponent
6687
7083
  );
7084
+ const remaining = this.holdersOf(held.entityId);
7085
+ if (remaining.length > 0 && interactable) {
7086
+ if (held.role === "secondary") {
7087
+ const primary = remaining.find((h) => h.record.role === "primary");
7088
+ if (primary) {
7089
+ primary.record.twoHand = null;
7090
+ this.reanchor(world, primary.input, primary.record, interactable);
7091
+ }
7092
+ this.dispatch(
7093
+ world,
7094
+ held.entityId,
7095
+ (b) => b.onSecondaryRelease(input)
7096
+ );
7097
+ } else {
7098
+ const next = remaining[0];
7099
+ next.record.role = "primary";
7100
+ next.record.twoHand = null;
7101
+ next.record.restoreBodyType = held.restoreBodyType;
7102
+ this.reanchor(world, next.input, next.record, interactable);
7103
+ this.dispatch(world, held.entityId, (b) => b.onRelease(input));
7104
+ }
7105
+ held.tracker.clear();
7106
+ return;
7107
+ }
6688
7108
  const physics = world.getComponent(held.entityId, PhysicsComponent);
6689
7109
  if (held.restoreBodyType !== null && physics) {
6690
7110
  physics.values.rigidbody.type = held.restoreBodyType;
6691
- const body = physics.rigidBody;
6692
7111
  const dynamic = held.restoreBodyType === "dynamic";
6693
- if (body && dynamic && interactable?.throwOnRelease) {
7112
+ if (dynamic && interactable?.throwOnRelease) {
6694
7113
  const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
6695
- try {
6696
- body.setLinvel(
6697
- { x: velocity.x, y: velocity.y, z: velocity.z },
6698
- true
6699
- );
6700
- const spin = held.angularVelocity;
6701
- body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
6702
- } catch {
6703
- }
7114
+ const spin = held.angularVelocity;
7115
+ setPhysicsLiveRef(physics, "pendingVelocity", {
7116
+ linear: { x: velocity.x, y: velocity.y, z: velocity.z },
7117
+ angular: { x: spin.x, y: spin.y, z: spin.z }
7118
+ });
6704
7119
  }
6705
7120
  }
6706
7121
  held.tracker.clear();
@@ -6718,7 +7133,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6718
7133
  */
6719
7134
  handPosition(input) {
6720
7135
  if (input.gripPosition) return input.gripPosition;
6721
- return new THREE7.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
7136
+ return new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
6722
7137
  }
6723
7138
  /**
6724
7139
  * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
@@ -6726,8 +7141,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6726
7141
  */
6727
7142
  handQuaternion(input) {
6728
7143
  if (input.gripQuaternion) return input.gripQuaternion.clone();
6729
- return new THREE7.Quaternion().setFromUnitVectors(
6730
- new THREE7.Vector3(0, 0, -1),
7144
+ return new THREE19.Quaternion().setFromUnitVectors(
7145
+ new THREE19.Vector3(0, 0, -1),
6731
7146
  input.rayDirection.clone().normalize()
6732
7147
  );
6733
7148
  }
@@ -6748,10 +7163,57 @@ var _XRInteractionSystem = class _XRInteractionSystem {
6748
7163
  getHoveredEntityId() {
6749
7164
  return this.hoveredEntityId;
6750
7165
  }
7166
+ /**
7167
+ * What this hand is pointing at, with the distance to it.
7168
+ *
7169
+ * The distance is what lets the pointer ray stop at the surface instead of
7170
+ * passing through the object.
7171
+ */
7172
+ getHoverFor(handedness) {
7173
+ for (const [input, hover] of this.hovers) {
7174
+ if (input.handedness === handedness) return hover;
7175
+ }
7176
+ return null;
7177
+ }
7178
+ /** Every entity under a pointer this frame — drives hover feedback. */
7179
+ getHoveredEntityIds() {
7180
+ const ids = /* @__PURE__ */ new Set();
7181
+ for (const hover of this.hovers.values()) ids.add(hover.entityId);
7182
+ return ids;
7183
+ }
7184
+ /** Entities currently held, so hover feedback can skip them. */
7185
+ getHeldEntityIds() {
7186
+ const ids = /* @__PURE__ */ new Set();
7187
+ for (const record of this.grabs.values()) ids.add(record.entityId);
7188
+ return ids;
7189
+ }
6751
7190
  /** True when any source is holding something. */
6752
7191
  isGrabbing() {
6753
7192
  return this.grabs.size > 0;
6754
7193
  }
7194
+ /**
7195
+ * How an entity is currently held.
7196
+ *
7197
+ * Exposed for scripts: a Behaviour can poll this in `onUpdate` to drive
7198
+ * two-hand-specific behaviour — showing a bipod when both hands are on a
7199
+ * weapon, reading the live hand separation to gate a gesture, or reacting to
7200
+ * the scale a pull-apart produced. Returns null when nothing holds it.
7201
+ */
7202
+ getGrabInfo(entityId) {
7203
+ const holders = this.holdersOf(entityId);
7204
+ if (holders.length === 0) return null;
7205
+ const primary = holders.find((h) => h.record.role === "primary");
7206
+ const secondary = holders.find((h) => h.record.role === "secondary");
7207
+ return {
7208
+ hands: holders.length,
7209
+ primaryHandedness: primary?.input.handedness ?? null,
7210
+ secondaryHandedness: secondary?.input.handedness ?? null,
7211
+ handSeparation: primary && secondary ? this.handPosition(primary.input).distanceTo(
7212
+ this.handPosition(secondary.input)
7213
+ ) : null,
7214
+ attachPointIds: holders.map((h) => h.record.attachPointId)
7215
+ };
7216
+ }
6755
7217
  };
6756
7218
  __name(_XRInteractionSystem, "XRInteractionSystem");
6757
7219
  var XRInteractionSystem = _XRInteractionSystem;
@@ -6769,7 +7231,7 @@ function sampleTeleportArc(origin, direction, out = [], options) {
6769
7231
  for (let i = 0; i < steps; i += 1) {
6770
7232
  const t = i / (steps - 1) * maxTime;
6771
7233
  out.push(
6772
- new THREE7.Vector3(
7234
+ new THREE19.Vector3(
6773
7235
  origin.x + velocity.x * t,
6774
7236
  origin.y + velocity.y * t - 0.5 * gravity * t * t,
6775
7237
  origin.z + velocity.z * t
@@ -6786,7 +7248,7 @@ function arcGroundCrossing(points, groundY) {
6786
7248
  if (previous.y >= groundY && current.y <= groundY) {
6787
7249
  const span = previous.y - current.y;
6788
7250
  const alpha = span > 1e-6 ? (previous.y - groundY) / span : 0;
6789
- return new THREE7.Vector3().lerpVectors(previous, current, alpha);
7251
+ return new THREE19.Vector3().lerpVectors(previous, current, alpha);
6790
7252
  }
6791
7253
  }
6792
7254
  return null;
@@ -6794,11 +7256,11 @@ function arcGroundCrossing(points, groundY) {
6794
7256
  __name(arcGroundCrossing, "arcGroundCrossing");
6795
7257
 
6796
7258
  // core/systems/XRLocomotionSystem.ts
6797
- var UP = new THREE7.Vector3(0, 1, 0);
7259
+ var UP = new THREE19.Vector3(0, 1, 0);
6798
7260
  var AXIS_DEADZONE = 0.5;
6799
7261
  var _XRLocomotionSystem = class _XRLocomotionSystem {
6800
7262
  constructor(inputSystem) {
6801
- this.raycaster = new THREE7.Raycaster();
7263
+ this.raycaster = new THREE19.Raycaster();
6802
7264
  /** Per-source latch so one stick push yields exactly one snap turn. */
6803
7265
  this.turnLatched = /* @__PURE__ */ new WeakSet();
6804
7266
  /** Reused arc buffer — aiming runs every frame. */
@@ -6907,7 +7369,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6907
7369
  }
6908
7370
  if (this.turnLatched.has(source)) continue;
6909
7371
  this.turnLatched.add(source);
6910
- transform.rotation.y -= Math.sign(x) * THREE7.MathUtils.degToRad(rig.snapTurnDegrees);
7372
+ transform.rotation.y -= Math.sign(x) * THREE19.MathUtils.degToRad(rig.snapTurnDegrees);
6911
7373
  }
6912
7374
  }
6913
7375
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -6917,12 +7379,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6917
7379
  const y = input.axes[1] ?? 0;
6918
7380
  if (Math.abs(x) < 0.15 && Math.abs(y) < 0.15) continue;
6919
7381
  const camera = world.getCamera();
6920
- const forward = new THREE7.Vector3();
7382
+ const forward = new THREE19.Vector3();
6921
7383
  camera.getWorldDirection(forward);
6922
7384
  forward.y = 0;
6923
7385
  if (forward.lengthSq() < 1e-6) continue;
6924
7386
  forward.normalize();
6925
- const right = new THREE7.Vector3().crossVectors(forward, UP).normalize();
7387
+ const right = new THREE19.Vector3().crossVectors(forward, UP).normalize();
6926
7388
  const step = rig.moveSpeed * Math.max(0, deltaTime);
6927
7389
  transform.position.x += (forward.x * -y + right.x * x) * step;
6928
7390
  transform.position.z += (forward.z * -y + right.z * x) * step;
@@ -6987,7 +7449,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
6987
7449
  for (let i = 1; i < points.length; i += 1) {
6988
7450
  const from = points[i - 1];
6989
7451
  const to = points[i];
6990
- const delta = new THREE7.Vector3().subVectors(to, from);
7452
+ const delta = new THREE19.Vector3().subVectors(to, from);
6991
7453
  const distance = delta.length();
6992
7454
  if (distance < 1e-6) continue;
6993
7455
  this.raycaster.set(from, delta.divideScalar(distance));
@@ -7195,9 +7657,9 @@ var XRSessionSystem = _XRSessionSystem;
7195
7657
  function buildGeometry2(area) {
7196
7658
  if (area.shape === "circle") {
7197
7659
  const radius = Math.max(5e-3, area.width / 2);
7198
- return new THREE7.CircleGeometry(radius, 48);
7660
+ return new THREE19.CircleGeometry(radius, 48);
7199
7661
  }
7200
- return new THREE7.PlaneGeometry(
7662
+ return new THREE19.PlaneGeometry(
7201
7663
  Math.max(0.01, area.width),
7202
7664
  Math.max(0.01, area.depth)
7203
7665
  );
@@ -7220,7 +7682,7 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
7220
7682
  for (const entityId of entityIds) {
7221
7683
  const area = world.getComponent(entityId, VRTeleportAreaComponent);
7222
7684
  const object = world.getComponent(entityId, Object3DRef)?.object;
7223
- if (!area || !(object instanceof THREE7.Mesh)) continue;
7685
+ if (!area || !(object instanceof THREE19.Mesh)) continue;
7224
7686
  const signature = signatureOf(area);
7225
7687
  if (this.signatures.get(entityId) === signature) continue;
7226
7688
  this.signatures.set(entityId, signature);
@@ -7232,18 +7694,18 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
7232
7694
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
7233
7695
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
7234
7696
  function createHandMaterial(preset, options) {
7235
- const color = new THREE7.Color(options.color);
7697
+ const color = new THREE19.Color(options.color);
7236
7698
  switch (preset) {
7237
7699
  case "ghost":
7238
- return new THREE7.MeshBasicMaterial({
7700
+ return new THREE19.MeshBasicMaterial({
7239
7701
  color,
7240
7702
  transparent: true,
7241
7703
  opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
7242
7704
  depthWrite: false,
7243
- side: THREE7.DoubleSide
7705
+ side: THREE19.DoubleSide
7244
7706
  });
7245
7707
  case "neon":
7246
- return new THREE7.MeshStandardMaterial({
7708
+ return new THREE19.MeshStandardMaterial({
7247
7709
  color,
7248
7710
  emissive: color,
7249
7711
  emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
@@ -7253,7 +7715,7 @@ function createHandMaterial(preset, options) {
7253
7715
  opacity: options.opacity
7254
7716
  });
7255
7717
  case "skeleton":
7256
- return new THREE7.MeshBasicMaterial({
7718
+ return new THREE19.MeshBasicMaterial({
7257
7719
  color,
7258
7720
  wireframe: true,
7259
7721
  transparent: true,
@@ -7261,7 +7723,7 @@ function createHandMaterial(preset, options) {
7261
7723
  });
7262
7724
  case "custom":
7263
7725
  case "default":
7264
- return new THREE7.MeshStandardMaterial({
7726
+ return new THREE19.MeshStandardMaterial({
7265
7727
  color,
7266
7728
  roughness: 0.85,
7267
7729
  metalness: 0,
@@ -7269,10 +7731,10 @@ function createHandMaterial(preset, options) {
7269
7731
  emissiveIntensity: Math.max(0, options.emissiveIntensity),
7270
7732
  transparent: options.opacity < 1,
7271
7733
  opacity: options.opacity,
7272
- side: THREE7.DoubleSide
7734
+ side: THREE19.DoubleSide
7273
7735
  });
7274
7736
  default: {
7275
- return new THREE7.MeshStandardMaterial({ color });
7737
+ return new THREE19.MeshStandardMaterial({ color });
7276
7738
  }
7277
7739
  }
7278
7740
  }
@@ -7285,6 +7747,9 @@ function applyHandMaterial(root, material) {
7285
7747
  });
7286
7748
  }
7287
7749
  __name(applyHandMaterial, "applyHandMaterial");
7750
+
7751
+ // core/systems/XRControllerVisualSystem.ts
7752
+ var RAY_LENGTH = 5;
7288
7753
  var SLOTS = [
7289
7754
  { index: 0, side: "left" },
7290
7755
  { index: 1, side: "right" }
@@ -7293,7 +7758,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7293
7758
  constructor(options) {
7294
7759
  this.controllerModelFactory = new XRControllerModelFactory();
7295
7760
  this.handModelFactory = new XRHandModelFactory();
7296
- this.group = new THREE7.Group();
7761
+ this.group = new THREE19.Group();
7297
7762
  this.slots = /* @__PURE__ */ new Map();
7298
7763
  this.attachedTo = null;
7299
7764
  /** Last applied preset signature, so materials rebuild only on real change. */
@@ -7323,6 +7788,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7323
7788
  visuals.controllerModel.visible = showControllers && hasController;
7324
7789
  visuals.handModel.visible = showHands && hasHand;
7325
7790
  visuals.ray.visible = this.shouldShowRay(interactor);
7791
+ this.fitRayToHover(visuals.ray, side);
7326
7792
  if (showHands && this.handMaterial) {
7327
7793
  applyHandMaterial(visuals.handModel, this.handMaterial);
7328
7794
  }
@@ -7403,6 +7869,18 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7403
7869
  if (!this.interactionSystem) return true;
7404
7870
  return this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
7405
7871
  }
7872
+ /**
7873
+ * Stop the ray at whatever it is pointing at.
7874
+ *
7875
+ * A fixed-length ray drawn with depth testing off punches straight through the
7876
+ * object, so the user cannot tell where the pointer actually lands. Scaling it
7877
+ * to the hit distance turns it into a pointer that meets the surface.
7878
+ */
7879
+ fitRayToHover(ray, side) {
7880
+ const hover = this.interactionSystem?.getHoverFor(side) ?? null;
7881
+ const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
7882
+ ray.scale.z = length / RAY_LENGTH;
7883
+ }
7406
7884
  /** Lazily build per-slot visuals; the device reports profiles late. */
7407
7885
  ensureSlots(world) {
7408
7886
  const renderer = world.getRenderer();
@@ -7442,17 +7920,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7442
7920
  this.attachedTo = target;
7443
7921
  }
7444
7922
  createRay() {
7445
- const geometry = new THREE7.BufferGeometry().setFromPoints([
7446
- new THREE7.Vector3(0, 0, 0),
7447
- new THREE7.Vector3(0, 0, -5)
7923
+ const geometry = new THREE19.BufferGeometry().setFromPoints([
7924
+ new THREE19.Vector3(0, 0, 0),
7925
+ new THREE19.Vector3(0, 0, -RAY_LENGTH)
7448
7926
  ]);
7449
- const material = new THREE7.LineBasicMaterial({
7927
+ const material = new THREE19.LineBasicMaterial({
7450
7928
  color: "#4da3ff",
7451
7929
  transparent: true,
7452
7930
  opacity: 0.6,
7453
7931
  depthTest: false
7454
7932
  });
7455
- const line = new THREE7.Line(geometry, material);
7933
+ const line = new THREE19.Line(geometry, material);
7456
7934
  line.name = "XRPointerRay";
7457
7935
  line.renderOrder = 999;
7458
7936
  line.visible = false;
@@ -7481,23 +7959,23 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
7481
7959
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
7482
7960
  var XRControllerVisualSystem = _XRControllerVisualSystem;
7483
7961
  var MARKER_RADIUS = 0.28;
7484
- var ACCENT = new THREE7.Color("#4da3ff");
7485
- var BLOCKED = new THREE7.Color("#ff5a5a");
7962
+ var ACCENT = new THREE19.Color("#4da3ff");
7963
+ var BLOCKED = new THREE19.Color("#ff5a5a");
7486
7964
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
7487
7965
  constructor(locomotionSystem) {
7488
- this.group = new THREE7.Group();
7489
- this.arcGeometry = new THREE7.BufferGeometry();
7490
- this.marker = new THREE7.Group();
7966
+ this.group = new THREE19.Group();
7967
+ this.arcGeometry = new THREE19.BufferGeometry();
7968
+ this.marker = new THREE19.Group();
7491
7969
  this.attached = false;
7492
7970
  this.locomotionSystem = locomotionSystem;
7493
7971
  this.group.name = "XRTeleportVisuals";
7494
7972
  this.group.userData.ecsPreviewHidden = true;
7495
- const arcMaterial = new THREE7.LineBasicMaterial({
7973
+ const arcMaterial = new THREE19.LineBasicMaterial({
7496
7974
  vertexColors: true,
7497
7975
  transparent: true,
7498
7976
  depthTest: false
7499
7977
  });
7500
- this.arc = new THREE7.Line(this.arcGeometry, arcMaterial);
7978
+ this.arc = new THREE19.Line(this.arcGeometry, arcMaterial);
7501
7979
  this.arc.frustumCulled = false;
7502
7980
  this.arc.renderOrder = 998;
7503
7981
  this.group.add(this.arc);
@@ -7545,39 +8023,39 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
7545
8023
  }
7546
8024
  this.arcGeometry.setAttribute(
7547
8025
  "position",
7548
- new THREE7.BufferAttribute(positions, 3)
8026
+ new THREE19.BufferAttribute(positions, 3)
7549
8027
  );
7550
- this.arcGeometry.setAttribute("color", new THREE7.BufferAttribute(colors, 3));
8028
+ this.arcGeometry.setAttribute("color", new THREE19.BufferAttribute(colors, 3));
7551
8029
  this.arcGeometry.computeBoundingSphere();
7552
8030
  }
7553
8031
  createRing() {
7554
- const geometry = new THREE7.RingGeometry(
8032
+ const geometry = new THREE19.RingGeometry(
7555
8033
  MARKER_RADIUS * 0.72,
7556
8034
  MARKER_RADIUS,
7557
8035
  48
7558
8036
  );
7559
8037
  geometry.rotateX(-Math.PI / 2);
7560
- return new THREE7.Mesh(
8038
+ return new THREE19.Mesh(
7561
8039
  geometry,
7562
- new THREE7.MeshBasicMaterial({
8040
+ new THREE19.MeshBasicMaterial({
7563
8041
  color: ACCENT,
7564
8042
  transparent: true,
7565
8043
  opacity: 0.9,
7566
- side: THREE7.DoubleSide,
8044
+ side: THREE19.DoubleSide,
7567
8045
  depthTest: false
7568
8046
  })
7569
8047
  );
7570
8048
  }
7571
8049
  createDot() {
7572
- const geometry = new THREE7.CircleGeometry(MARKER_RADIUS * 0.16, 24);
8050
+ const geometry = new THREE19.CircleGeometry(MARKER_RADIUS * 0.16, 24);
7573
8051
  geometry.rotateX(-Math.PI / 2);
7574
- return new THREE7.Mesh(
8052
+ return new THREE19.Mesh(
7575
8053
  geometry,
7576
- new THREE7.MeshBasicMaterial({
8054
+ new THREE19.MeshBasicMaterial({
7577
8055
  color: ACCENT,
7578
8056
  transparent: true,
7579
8057
  opacity: 0.55,
7580
- side: THREE7.DoubleSide,
8058
+ side: THREE19.DoubleSide,
7581
8059
  depthTest: false
7582
8060
  })
7583
8061
  );
@@ -7679,7 +8157,7 @@ function boneDirections(joints, finger) {
7679
8157
  const from = joints[chain[i - 1]];
7680
8158
  const to = joints[chain[i]];
7681
8159
  if (!from || !to) return null;
7682
- const direction = new THREE7.Vector3().subVectors(to, from);
8160
+ const direction = new THREE19.Vector3().subVectors(to, from);
7683
8161
  if (direction.lengthSq() < 1e-12) return null;
7684
8162
  directions.push(direction.normalize());
7685
8163
  }
@@ -7752,8 +8230,8 @@ function computeSpread(joints, a, b) {
7752
8230
  const bBase = joints[METACARPAL_JOINTS[b]];
7753
8231
  const bTip = joints[TIP_JOINTS[b]];
7754
8232
  if (!aBase || !aTip || !bBase || !bTip) return null;
7755
- const dirA = new THREE7.Vector3().subVectors(aTip, aBase);
7756
- const dirB = new THREE7.Vector3().subVectors(bTip, bBase);
8233
+ const dirA = new THREE19.Vector3().subVectors(aTip, aBase);
8234
+ const dirB = new THREE19.Vector3().subVectors(bTip, bBase);
7757
8235
  if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
7758
8236
  return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
7759
8237
  }
@@ -7763,18 +8241,18 @@ function computePalmPose(joints, handedness) {
7763
8241
  const indexBase = joints["index-finger-metacarpal"];
7764
8242
  const pinkyBase = joints["pinky-finger-metacarpal"];
7765
8243
  if (!wrist || !indexBase || !pinkyBase) return null;
7766
- const u = new THREE7.Vector3().subVectors(indexBase, wrist);
7767
- const v = new THREE7.Vector3().subVectors(pinkyBase, wrist);
7768
- const normal = new THREE7.Vector3().crossVectors(u, v);
8244
+ const u = new THREE19.Vector3().subVectors(indexBase, wrist);
8245
+ const v = new THREE19.Vector3().subVectors(pinkyBase, wrist);
8246
+ const normal = new THREE19.Vector3().crossVectors(u, v);
7769
8247
  if (normal.lengthSq() < 1e-12) return null;
7770
8248
  normal.normalize();
7771
8249
  if (handedness === "left") normal.multiplyScalar(-1);
7772
- const right = new THREE7.Vector3().subVectors(indexBase, pinkyBase);
8250
+ const right = new THREE19.Vector3().subVectors(indexBase, pinkyBase);
7773
8251
  if (right.lengthSq() < 1e-12) return null;
7774
8252
  right.normalize();
7775
8253
  if (handedness === "left") right.multiplyScalar(-1);
7776
- const up = new THREE7.Vector3().crossVectors(normal, right).normalize();
7777
- const center = new THREE7.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
8254
+ const up = new THREE19.Vector3().crossVectors(normal, right).normalize();
8255
+ const center = new THREE19.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
7778
8256
  return {
7779
8257
  center,
7780
8258
  normal,
@@ -7805,7 +8283,7 @@ function computeHandShape(joints, handedness) {
7805
8283
  const thumbTip = joints["thumb-tip"];
7806
8284
  let thumbDirection = null;
7807
8285
  if (thumbBase && thumbTip) {
7808
- const direction = new THREE7.Vector3().subVectors(thumbTip, thumbBase);
8286
+ const direction = new THREE19.Vector3().subVectors(thumbTip, thumbBase);
7809
8287
  if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
7810
8288
  }
7811
8289
  return {
@@ -7837,12 +8315,12 @@ function scoreAgainst(actual, target, tolerance) {
7837
8315
  }
7838
8316
  __name(scoreAgainst, "scoreAgainst");
7839
8317
  var WORLD_DIRECTIONS = {
7840
- up: new THREE7.Vector3(0, 1, 0),
7841
- down: new THREE7.Vector3(0, -1, 0),
7842
- forward: new THREE7.Vector3(0, 0, -1),
7843
- back: new THREE7.Vector3(0, 0, 1),
7844
- left: new THREE7.Vector3(-1, 0, 0),
7845
- right: new THREE7.Vector3(1, 0, 0)
8318
+ up: new THREE19.Vector3(0, 1, 0),
8319
+ down: new THREE19.Vector3(0, -1, 0),
8320
+ forward: new THREE19.Vector3(0, 0, -1),
8321
+ back: new THREE19.Vector3(0, 0, 1),
8322
+ left: new THREE19.Vector3(-1, 0, 0),
8323
+ right: new THREE19.Vector3(1, 0, 0)
7846
8324
  };
7847
8325
  function axisVector(shape, axis) {
7848
8326
  if (axis === "thumb") return shape.thumbDirection;
@@ -7857,7 +8335,7 @@ function resolveDirection(direction, shape, context) {
7857
8335
  const head = context?.headPosition;
7858
8336
  const origin = shape.palm?.center;
7859
8337
  if (!head || !origin) return null;
7860
- const toHead = new THREE7.Vector3().subVectors(head, origin);
8338
+ const toHead = new THREE19.Vector3().subVectors(head, origin);
7861
8339
  if (toHead.lengthSq() < 1e-12) return null;
7862
8340
  toHead.normalize();
7863
8341
  return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
@@ -7907,8 +8385,8 @@ function matchHandPose(shape, definition, context) {
7907
8385
  if (!handAxis || !wanted) {
7908
8386
  consider(0, "orientation");
7909
8387
  } else {
7910
- const angle = THREE7.MathUtils.radToDeg(
7911
- Math.acos(THREE7.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
8388
+ const angle = THREE19.MathUtils.radToDeg(
8389
+ Math.acos(THREE19.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
7912
8390
  );
7913
8391
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
7914
8392
  consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
@@ -7986,7 +8464,7 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
7986
8464
  this.trackers = /* @__PURE__ */ new Map();
7987
8465
  this.shapes = /* @__PURE__ */ new Map();
7988
8466
  /** Reused so head-relative orientation checks allocate nothing per frame. */
7989
- this.headPosition = new THREE7.Vector3();
8467
+ this.headPosition = new THREE19.Vector3();
7990
8468
  this.elapsed = 0;
7991
8469
  this.inputSystem = inputSystem;
7992
8470
  }
@@ -8107,7 +8585,153 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
8107
8585
  };
8108
8586
  __name(_XRHandPoseSystem, "XRHandPoseSystem");
8109
8587
  var XRHandPoseSystem = _XRHandPoseSystem;
8588
+ var XR_HOVER_SHELL_FLAG = "__xrHoverShell";
8589
+ var SHELL_INFLATE = 1.04;
8590
+ var SCALE_SMOOTH_SECONDS = 0.09;
8591
+ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
8592
+ constructor(interactionSystem) {
8593
+ this.records = /* @__PURE__ */ new Map();
8594
+ this.interactionSystem = interactionSystem;
8595
+ }
8596
+ update(world, deltaTime) {
8597
+ if (!world.isXrPresenting()) {
8598
+ this.clearAll(world);
8599
+ return;
8600
+ }
8601
+ const hovered = this.interactionSystem.getHoveredEntityIds();
8602
+ const held = this.interactionSystem.getHeldEntityIds();
8603
+ for (const entityId of hovered) {
8604
+ if (held.has(entityId)) continue;
8605
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8606
+ if (!interactable?.enabled) continue;
8607
+ if (interactable.hoverBehavior === "none") continue;
8608
+ this.begin(world, entityId, interactable);
8609
+ }
8610
+ for (const [entityId, record] of this.records) {
8611
+ if (!hovered.has(entityId) || held.has(entityId)) {
8612
+ record.active = false;
8613
+ }
8614
+ }
8615
+ this.animate(world, deltaTime);
8616
+ }
8617
+ begin(world, entityId, interactable) {
8618
+ const existing = this.records.get(entityId);
8619
+ if (existing) {
8620
+ existing.active = true;
8621
+ return;
8622
+ }
8623
+ const transform = world.getComponent(entityId, TransformComponent);
8624
+ if (!transform) return;
8625
+ const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
8626
+ this.records.set(entityId, {
8627
+ shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
8628
+ baseScale: new THREE19.Vector3(
8629
+ transform.scale.x,
8630
+ transform.scale.y,
8631
+ transform.scale.z
8632
+ ),
8633
+ progress: 0,
8634
+ active: true
8635
+ });
8636
+ }
8637
+ /** Build an inflated back-face copy of the object's meshes. */
8638
+ addShell(world, entityId, interactable) {
8639
+ const object = world.getComponent(entityId, Object3DRef)?.object;
8640
+ if (!object) return null;
8641
+ const material = new THREE19.MeshBasicMaterial({
8642
+ color: new THREE19.Color(interactable.hoverColor),
8643
+ // Back faces only, so the shell shows just outside the silhouette instead
8644
+ // of covering the object.
8645
+ side: THREE19.BackSide,
8646
+ transparent: true,
8647
+ opacity: 0.85,
8648
+ depthWrite: false
8649
+ });
8650
+ const group = new THREE19.Group();
8651
+ group.name = "XRHoverShell";
8652
+ group.userData[XR_HOVER_SHELL_FLAG] = true;
8653
+ group.renderOrder = 998;
8654
+ object.traverse((child) => {
8655
+ if (!(child instanceof THREE19.Mesh) || !child.geometry) return;
8656
+ if (child.userData[XR_HOVER_SHELL_FLAG]) return;
8657
+ const clone2 = new THREE19.Mesh(child.geometry, material);
8658
+ child.updateWorldMatrix(true, false);
8659
+ object.updateWorldMatrix(true, false);
8660
+ clone2.matrixAutoUpdate = false;
8661
+ clone2.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE19.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
8662
+ clone2.userData[XR_HOVER_SHELL_FLAG] = true;
8663
+ clone2.raycast = () => {
8664
+ };
8665
+ group.add(clone2);
8666
+ });
8667
+ if (group.children.length === 0) {
8668
+ material.dispose();
8669
+ return null;
8670
+ }
8671
+ object.add(group);
8672
+ return group;
8673
+ }
8674
+ animate(world, deltaTime) {
8675
+ const step = 1 - Math.exp(-Math.max(0, deltaTime) / SCALE_SMOOTH_SECONDS);
8676
+ for (const [entityId, record] of Array.from(this.records)) {
8677
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8678
+ const target = record.active ? 1 : 0;
8679
+ record.progress += (target - record.progress) * step;
8680
+ const wantsScale = interactable?.hoverBehavior === "scale" || interactable?.hoverBehavior === "both";
8681
+ if (wantsScale) {
8682
+ const transform = world.getComponent(entityId, TransformComponent);
8683
+ if (transform) {
8684
+ const factor = 1 + (interactable.hoverScale - 1) * Math.min(1, record.progress);
8685
+ transform.scale.x = record.baseScale.x * factor;
8686
+ transform.scale.y = record.baseScale.y * factor;
8687
+ transform.scale.z = record.baseScale.z * factor;
8688
+ }
8689
+ }
8690
+ if (!record.active && record.progress < 0.01) {
8691
+ this.finish(world, entityId, record);
8692
+ }
8693
+ }
8694
+ }
8695
+ /** Remove the effect and restore the authored scale exactly. */
8696
+ finish(world, entityId, record) {
8697
+ const transform = world.getComponent(entityId, TransformComponent);
8698
+ if (transform) {
8699
+ transform.scale.x = record.baseScale.x;
8700
+ transform.scale.y = record.baseScale.y;
8701
+ transform.scale.z = record.baseScale.z;
8702
+ }
8703
+ this.disposeShell(record.shell);
8704
+ this.records.delete(entityId);
8705
+ }
8706
+ disposeShell(shell) {
8707
+ if (!shell) return;
8708
+ shell.parent?.remove(shell);
8709
+ shell.traverse((child) => {
8710
+ if (child instanceof THREE19.Mesh) {
8711
+ const material = child.material;
8712
+ if (Array.isArray(material)) {
8713
+ for (const entry of material) entry.dispose();
8714
+ } else {
8715
+ material?.dispose();
8716
+ }
8717
+ }
8718
+ });
8719
+ }
8720
+ clearAll(world) {
8721
+ for (const [entityId, record] of Array.from(this.records)) {
8722
+ this.finish(world, entityId, record);
8723
+ }
8724
+ }
8725
+ dispose() {
8726
+ for (const record of this.records.values()) {
8727
+ this.disposeShell(record.shell);
8728
+ }
8729
+ this.records.clear();
8730
+ }
8731
+ };
8732
+ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
8733
+ var XRHoverVisualSystem = _XRHoverVisualSystem;
8110
8734
 
8111
- export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRHandPoseSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
8112
- //# sourceMappingURL=chunk-W5TSYV6Q.js.map
8113
- //# sourceMappingURL=chunk-W5TSYV6Q.js.map
8735
+ export { ADDED_NODE_MARKER_COMPONENT, ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioSystem, Behaviour, CURLED_COS, CameraSyncSystem, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HYDRATABLE_ENTITY_KINDS, HandPoseTracker, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, NO_XR_CAPABILITIES, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PINCH_FINGERS, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, ThrowVelocityTracker, TransformSyncSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRControllerVisualSystem, XRHandPoseSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeFingerCurl, computeHandShape, computeInstanceOverrides, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, decanonicalizeComponentValue, despawnPrefab, encodeOverride, estimateHandScale, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, matchHandPose, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, readXrCapabilities, restoreComponentState, restorePostCreateComponents, sampleTeleportArc, setPrefabAssetUrlResolver, spawnPrefab };
8736
+ //# sourceMappingURL=chunk-KLU2M43M.js.map
8737
+ //# sourceMappingURL=chunk-KLU2M43M.js.map