@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,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var THREE14 = require('three');
3
+ var THREE26 = require('three');
4
4
  require('three/examples/jsm/utils/SkeletonUtils.js');
5
5
  var DRACOLoader_js = require('three/examples/jsm/loaders/DRACOLoader.js');
6
6
  var FBXLoader_js = require('three/examples/jsm/loaders/FBXLoader.js');
@@ -40,7 +40,7 @@ function _interopNamespace(e) {
40
40
  return Object.freeze(n);
41
41
  }
42
42
 
43
- var THREE14__namespace = /*#__PURE__*/_interopNamespace(THREE14);
43
+ var THREE26__namespace = /*#__PURE__*/_interopNamespace(THREE26);
44
44
 
45
45
  var __defProp = Object.defineProperty;
46
46
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -101,14 +101,14 @@ var _TransformComponent = class _TransformComponent extends Component {
101
101
  z: scale.z ?? 1
102
102
  };
103
103
  }
104
- toPositionVector3(target = new THREE14.Vector3()) {
104
+ toPositionVector3(target = new THREE26.Vector3()) {
105
105
  return target.set(this.position.x, this.position.y, this.position.z);
106
106
  }
107
- toRotationEuler(target = new THREE14.Euler(), order = "XYZ") {
107
+ toRotationEuler(target = new THREE26.Euler(), order = "XYZ") {
108
108
  return target.set(this.rotation.x, this.rotation.y, this.rotation.z, order);
109
109
  }
110
- toRotationQuaternion(target = new THREE14.Quaternion(), order = "XYZ") {
111
- const euler = new THREE14.Euler(
110
+ toRotationQuaternion(target = new THREE26.Quaternion(), order = "XYZ") {
111
+ const euler = new THREE26.Euler(
112
112
  this.rotation.x,
113
113
  this.rotation.y,
114
114
  this.rotation.z,
@@ -116,7 +116,7 @@ var _TransformComponent = class _TransformComponent extends Component {
116
116
  );
117
117
  return target.setFromEuler(euler);
118
118
  }
119
- toScaleVector3(target = new THREE14.Vector3()) {
119
+ toScaleVector3(target = new THREE26.Vector3()) {
120
120
  return target.set(this.scale.x, this.scale.y, this.scale.z);
121
121
  }
122
122
  setPosition(input) {
@@ -134,8 +134,8 @@ var _TransformComponent = class _TransformComponent extends Component {
134
134
  return this;
135
135
  }
136
136
  setRotation(input, order = "XYZ") {
137
- if (input instanceof THREE14.Quaternion) {
138
- const euler = new THREE14.Euler().setFromQuaternion(input, order);
137
+ if (input instanceof THREE26.Quaternion) {
138
+ const euler = new THREE26.Euler().setFromQuaternion(input, order);
139
139
  this.rotation.x = euler.x;
140
140
  this.rotation.y = euler.y;
141
141
  this.rotation.z = euler.z;
@@ -184,10 +184,10 @@ var _ObjectManagementSystem = class _ObjectManagementSystem {
184
184
  }
185
185
  getOrCreateAudioListener() {
186
186
  const existing = this.camera.userData.ecsAudioListener;
187
- if (existing instanceof THREE14__namespace.AudioListener) {
187
+ if (existing instanceof THREE26__namespace.AudioListener) {
188
188
  return existing;
189
189
  }
190
- const listener = new THREE14__namespace.AudioListener();
190
+ const listener = new THREE26__namespace.AudioListener();
191
191
  this.camera.add(listener);
192
192
  this.camera.userData.ecsAudioListener = listener;
193
193
  return listener;
@@ -261,7 +261,7 @@ var _ObjectManagementSystem = class _ObjectManagementSystem {
261
261
  }
262
262
  if (dispose && object3d) {
263
263
  object3d.traverse((child) => {
264
- if (child instanceof THREE14__namespace.Mesh) {
264
+ if (child instanceof THREE26__namespace.Mesh) {
265
265
  if (!child.geometry?.userData?.[SHARED_MODEL_RESOURCE_FLAG]) {
266
266
  child.geometry?.dispose();
267
267
  }
@@ -641,14 +641,14 @@ var _AnimationComponent = class _AnimationComponent extends Component {
641
641
  return;
642
642
  }
643
643
  this.importedClips = clips.map((entry) => {
644
- if (entry instanceof THREE14__namespace.AnimationClip) {
644
+ if (entry instanceof THREE26__namespace.AnimationClip) {
645
645
  return { clip: entry, sourceUrl: null };
646
646
  }
647
647
  if (!entry || typeof entry !== "object") {
648
648
  return null;
649
649
  }
650
650
  const candidate = entry;
651
- if (!(candidate.clip instanceof THREE14__namespace.AnimationClip)) {
651
+ if (!(candidate.clip instanceof THREE26__namespace.AnimationClip)) {
652
652
  return null;
653
653
  }
654
654
  return {
@@ -782,7 +782,7 @@ var DEFAULT_ORTHO_TOP = 5;
782
782
  var DEFAULT_ORTHO_BOTTOM = -5;
783
783
  var DEFAULT_ZOOM = 1;
784
784
  function inferProjection(camera) {
785
- if (camera instanceof THREE14__namespace.OrthographicCamera) {
785
+ if (camera instanceof THREE26__namespace.OrthographicCamera) {
786
786
  return "orthographic";
787
787
  }
788
788
  return "perspective";
@@ -803,7 +803,7 @@ function readCameraFar(camera) {
803
803
  }
804
804
  __name(readCameraFar, "readCameraFar");
805
805
  function inferFov(camera) {
806
- if (camera instanceof THREE14__namespace.PerspectiveCamera && Number.isFinite(camera.fov)) {
806
+ if (camera instanceof THREE26__namespace.PerspectiveCamera && Number.isFinite(camera.fov)) {
807
807
  return camera.fov;
808
808
  }
809
809
  return DEFAULT_FOV;
@@ -826,28 +826,28 @@ function inferFar(camera) {
826
826
  }
827
827
  __name(inferFar, "inferFar");
828
828
  function inferOrthoLeft(camera) {
829
- if (camera instanceof THREE14__namespace.OrthographicCamera && Number.isFinite(camera.left)) {
829
+ if (camera instanceof THREE26__namespace.OrthographicCamera && Number.isFinite(camera.left)) {
830
830
  return camera.left;
831
831
  }
832
832
  return DEFAULT_ORTHO_LEFT;
833
833
  }
834
834
  __name(inferOrthoLeft, "inferOrthoLeft");
835
835
  function inferOrthoRight(camera) {
836
- if (camera instanceof THREE14__namespace.OrthographicCamera && Number.isFinite(camera.right)) {
836
+ if (camera instanceof THREE26__namespace.OrthographicCamera && Number.isFinite(camera.right)) {
837
837
  return camera.right;
838
838
  }
839
839
  return DEFAULT_ORTHO_RIGHT;
840
840
  }
841
841
  __name(inferOrthoRight, "inferOrthoRight");
842
842
  function inferOrthoTop(camera) {
843
- if (camera instanceof THREE14__namespace.OrthographicCamera && Number.isFinite(camera.top)) {
843
+ if (camera instanceof THREE26__namespace.OrthographicCamera && Number.isFinite(camera.top)) {
844
844
  return camera.top;
845
845
  }
846
846
  return DEFAULT_ORTHO_TOP;
847
847
  }
848
848
  __name(inferOrthoTop, "inferOrthoTop");
849
849
  function inferOrthoBottom(camera) {
850
- if (camera instanceof THREE14__namespace.OrthographicCamera && Number.isFinite(camera.bottom)) {
850
+ if (camera instanceof THREE26__namespace.OrthographicCamera && Number.isFinite(camera.bottom)) {
851
851
  return camera.bottom;
852
852
  }
853
853
  return DEFAULT_ORTHO_BOTTOM;
@@ -863,7 +863,7 @@ __name(inferZoom, "inferZoom");
863
863
  var _CameraComponent = class _CameraComponent extends Component {
864
864
  constructor(payload = {}) {
865
865
  super();
866
- this.camera = payload.camera ?? new THREE14__namespace.PerspectiveCamera();
866
+ this.camera = payload.camera ?? new THREE26__namespace.PerspectiveCamera();
867
867
  this.type = payload.type ?? "3d";
868
868
  this.isDefault = typeof payload.isDefault === "boolean" ? payload.isDefault : false;
869
869
  this.projection = payload.projection ?? inferProjection(payload.camera);
@@ -1666,15 +1666,15 @@ function createValueState(light) {
1666
1666
  castShadow: light.castShadow ?? false,
1667
1667
  frustumCulling: light.frustumCulled
1668
1668
  };
1669
- if (light instanceof THREE14__namespace.PointLight) {
1669
+ if (light instanceof THREE26__namespace.PointLight) {
1670
1670
  base.distance = light.distance;
1671
1671
  base.decay = light.decay;
1672
- } else if (light instanceof THREE14__namespace.SpotLight) {
1672
+ } else if (light instanceof THREE26__namespace.SpotLight) {
1673
1673
  base.distance = light.distance;
1674
1674
  base.decay = light.decay;
1675
1675
  base.angle = light.angle;
1676
1676
  base.penumbra = light.penumbra;
1677
- } else if (light instanceof THREE14__namespace.RectAreaLight) {
1677
+ } else if (light instanceof THREE26__namespace.RectAreaLight) {
1678
1678
  base.width = light.width;
1679
1679
  base.height = light.height;
1680
1680
  }
@@ -1709,13 +1709,13 @@ function toHexColor2(value, fallback) {
1709
1709
  }
1710
1710
  __name(toHexColor2, "toHexColor");
1711
1711
  function getMaterialType(material) {
1712
- if (material instanceof THREE14__namespace.MeshPhysicalMaterial) {
1712
+ if (material instanceof THREE26__namespace.MeshPhysicalMaterial) {
1713
1713
  return "physical";
1714
1714
  }
1715
- if (material instanceof THREE14__namespace.MeshPhongMaterial) {
1715
+ if (material instanceof THREE26__namespace.MeshPhongMaterial) {
1716
1716
  return "phong";
1717
1717
  }
1718
- if (material instanceof THREE14__namespace.MeshBasicMaterial) {
1718
+ if (material instanceof THREE26__namespace.MeshBasicMaterial) {
1719
1719
  return "basic";
1720
1720
  }
1721
1721
  return "standard";
@@ -1750,8 +1750,8 @@ function createMaterialValue(material) {
1750
1750
  opacity: typeof materialRecord.opacity === "number" ? materialRecord.opacity : 1,
1751
1751
  emissive: toHexColor2(materialRecord.emissive, "#000000"),
1752
1752
  emissiveIntensity: typeof materialRecord.emissiveIntensity === "number" ? materialRecord.emissiveIntensity : 0,
1753
- side: typeof materialRecord.side === "number" ? materialRecord.side : THREE14__namespace.FrontSide,
1754
- blending: typeof materialRecord.blending === "number" ? materialRecord.blending : THREE14__namespace.NormalBlending,
1753
+ side: typeof materialRecord.side === "number" ? materialRecord.side : THREE26__namespace.FrontSide,
1754
+ blending: typeof materialRecord.blending === "number" ? materialRecord.blending : THREE26__namespace.NormalBlending,
1755
1755
  depthTest: materialRecord.depthTest !== false,
1756
1756
  depthWrite: materialRecord.depthWrite !== false,
1757
1757
  wireframe: Boolean(materialRecord.wireframe),
@@ -1783,7 +1783,7 @@ function createMaterialValue(material) {
1783
1783
  materialRecord.lightMap
1784
1784
  ),
1785
1785
  envMap: textureImageSrc(materialRecord.envMap),
1786
- textureFiltering: map?.magFilter === THREE14__namespace.NearestFilter ? "sharp" : "smooth",
1786
+ textureFiltering: map?.magFilter === THREE26__namespace.NearestFilter ? "sharp" : "smooth",
1787
1787
  useMipmaps: map?.generateMipmaps !== false
1788
1788
  };
1789
1789
  }
@@ -1798,8 +1798,8 @@ function defaultMaterialValue() {
1798
1798
  opacity: 1,
1799
1799
  emissive: "#000000",
1800
1800
  emissiveIntensity: 0,
1801
- side: THREE14__namespace.FrontSide,
1802
- blending: THREE14__namespace.NormalBlending,
1801
+ side: THREE26__namespace.FrontSide,
1802
+ blending: THREE26__namespace.NormalBlending,
1803
1803
  depthTest: true,
1804
1804
  depthWrite: true,
1805
1805
  wireframe: false,
@@ -2098,7 +2098,6 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
2098
2098
  this.enabled = options?.enabled ?? true;
2099
2099
  this.grabbable = options?.grabbable ?? true;
2100
2100
  this.pointable = options?.pointable ?? true;
2101
- this.grabMode = options?.grabMode ?? "kinematic";
2102
2101
  this.grabReach = options?.grabReach ?? "both";
2103
2102
  this.distanceBehavior = options?.distanceBehavior ?? "snap";
2104
2103
  this.grabGesture = options?.grabGesture ?? "any";
@@ -2113,13 +2112,34 @@ var _XRInteractableComponent = class _XRInteractableComponent extends Component
2113
2112
  y: 0,
2114
2113
  z: 0
2115
2114
  };
2115
+ this.grabHands = options?.grabHands ?? "handOff";
2116
+ this.twoHandRotation = options?.twoHandRotation ?? "align";
2117
+ this.allowTwoHandScale = options?.allowTwoHandScale ?? false;
2118
+ this.minScale = options?.minScale ?? 0.25;
2119
+ this.maxScale = options?.maxScale ?? 4;
2120
+ this.attachPoints = (options?.attachPoints ?? []).map(
2121
+ (point) => createAttachPoint(point)
2122
+ );
2116
2123
  this.throwOnRelease = options?.throwOnRelease ?? true;
2117
2124
  this.throwStrength = options?.throwStrength ?? 1;
2118
- this.highlightOnHover = options?.highlightOnHover ?? true;
2125
+ this.hoverBehavior = options?.hoverBehavior ?? "color";
2126
+ this.hoverColor = options?.hoverColor ?? "#4da3ff";
2127
+ this.hoverScale = options?.hoverScale ?? 1.06;
2119
2128
  }
2120
2129
  };
2121
2130
  __name(_XRInteractableComponent, "XRInteractableComponent");
2122
2131
  var XRInteractableComponent = _XRInteractableComponent;
2132
+ function createAttachPoint(partial) {
2133
+ return {
2134
+ id: partial.id,
2135
+ name: partial.name ?? partial.id,
2136
+ position: { x: 0, y: 0, z: 0, ...partial.position },
2137
+ rotation: { x: 0, y: 0, z: 0, ...partial.rotation },
2138
+ hand: partial.hand ?? "any",
2139
+ role: partial.role ?? "any"
2140
+ };
2141
+ }
2142
+ __name(createAttachPoint, "createAttachPoint");
2123
2143
 
2124
2144
  // core/components/XRRig.ts
2125
2145
  var _XRRigComponent = class _XRRigComponent extends Component {
@@ -2525,7 +2545,7 @@ function transitionToClip(runtime, nextClipId, crossFadeDuration) {
2525
2545
  }
2526
2546
  __name(transitionToClip, "transitionToClip");
2527
2547
  function applyActionState(action, component, paused, shouldLoop) {
2528
- const loopMode = shouldLoop ? THREE14__namespace.LoopRepeat : THREE14__namespace.LoopOnce;
2548
+ const loopMode = shouldLoop ? THREE26__namespace.LoopRepeat : THREE26__namespace.LoopOnce;
2529
2549
  const repetitions = shouldLoop ? Infinity : 1;
2530
2550
  action.setLoop(loopMode, repetitions);
2531
2551
  action.clampWhenFinished = !shouldLoop;
@@ -2698,7 +2718,7 @@ var _AnimationSystem = class _AnimationSystem {
2698
2718
  if (runtime) {
2699
2719
  stopAllActions(runtime);
2700
2720
  }
2701
- const mixer = new THREE14__namespace.AnimationMixer(root);
2721
+ const mixer = new THREE26__namespace.AnimationMixer(root);
2702
2722
  const entries2 = readRuntimeClipEntries(animation);
2703
2723
  const indexedActions = createActions(mixer, entries2);
2704
2724
  runtime = {
@@ -2824,11 +2844,11 @@ function normalizeOrthoBounds(component) {
2824
2844
  }
2825
2845
  __name(normalizeOrthoBounds, "normalizeOrthoBounds");
2826
2846
  function isPerspectiveCamera(camera) {
2827
- return camera instanceof THREE14__namespace.PerspectiveCamera;
2847
+ return camera instanceof THREE26__namespace.PerspectiveCamera;
2828
2848
  }
2829
2849
  __name(isPerspectiveCamera, "isPerspectiveCamera");
2830
2850
  function isOrthographicCamera(camera) {
2831
- return camera instanceof THREE14__namespace.OrthographicCamera;
2851
+ return camera instanceof THREE26__namespace.OrthographicCamera;
2832
2852
  }
2833
2853
  __name(isOrthographicCamera, "isOrthographicCamera");
2834
2854
  function readWindowAspect() {
@@ -2882,7 +2902,7 @@ var _CameraSyncSystem = class _CameraSyncSystem {
2882
2902
  continue;
2883
2903
  }
2884
2904
  const camera = objectRef.object;
2885
- if (!(camera instanceof THREE14__namespace.Camera)) {
2905
+ if (!(camera instanceof THREE26__namespace.Camera)) {
2886
2906
  continue;
2887
2907
  }
2888
2908
  if (xrCamera !== null && camera === xrCamera) {
@@ -2949,7 +2969,7 @@ var MIN_DENSITY = 0;
2949
2969
  function createGradientTexture(effect, colors) {
2950
2970
  if (typeof document === "undefined") {
2951
2971
  const pixel = new Uint8Array([30, 30, 30, 255]);
2952
- const fallback = new THREE14__namespace.DataTexture(pixel, 1, 1, THREE14__namespace.RGBAFormat);
2972
+ const fallback = new THREE26__namespace.DataTexture(pixel, 1, 1, THREE26__namespace.RGBAFormat);
2953
2973
  fallback.needsUpdate = true;
2954
2974
  return fallback;
2955
2975
  }
@@ -2958,7 +2978,7 @@ function createGradientTexture(effect, colors) {
2958
2978
  canvas.height = 512;
2959
2979
  const context = canvas.getContext("2d");
2960
2980
  if (!context) {
2961
- const fallback = new THREE14__namespace.CanvasTexture(canvas);
2981
+ const fallback = new THREE26__namespace.CanvasTexture(canvas);
2962
2982
  fallback.needsUpdate = true;
2963
2983
  return fallback;
2964
2984
  }
@@ -2986,15 +3006,15 @@ function createGradientTexture(effect, colors) {
2986
3006
  context.fillRect(0, 0, 512, 512);
2987
3007
  context.globalCompositeOperation = "source-over";
2988
3008
  }
2989
- const texture = new THREE14__namespace.CanvasTexture(canvas);
2990
- texture.colorSpace = THREE14__namespace.SRGBColorSpace;
3009
+ const texture = new THREE26__namespace.CanvasTexture(canvas);
3010
+ texture.colorSpace = THREE26__namespace.SRGBColorSpace;
2991
3011
  texture.needsUpdate = true;
2992
3012
  return texture;
2993
3013
  }
2994
3014
  __name(createGradientTexture, "createGradientTexture");
2995
3015
  var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
2996
3016
  constructor(scene) {
2997
- this.textureLoader = new THREE14__namespace.TextureLoader();
3017
+ this.textureLoader = new THREE26__namespace.TextureLoader();
2998
3018
  this.signature = null;
2999
3019
  this.activeTexture = null;
3000
3020
  this.pendingImageUrl = null;
@@ -3017,7 +3037,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
3017
3037
  if (skybox.type === "color") {
3018
3038
  this.disposeActiveTexture();
3019
3039
  this.pendingImageUrl = null;
3020
- this.scene.background = new THREE14__namespace.Color(skybox.color || "#1e1e1e");
3040
+ this.scene.background = new THREE26__namespace.Color(skybox.color || "#1e1e1e");
3021
3041
  return;
3022
3042
  }
3023
3043
  if (skybox.type === "gradient") {
@@ -3051,7 +3071,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
3051
3071
  texture.dispose();
3052
3072
  return;
3053
3073
  }
3054
- texture.colorSpace = THREE14__namespace.SRGBColorSpace;
3074
+ texture.colorSpace = THREE26__namespace.SRGBColorSpace;
3055
3075
  this.activeTexture = texture;
3056
3076
  this.scene.background = texture;
3057
3077
  },
@@ -3070,16 +3090,16 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
3070
3090
  this.scene.fog = null;
3071
3091
  return;
3072
3092
  }
3073
- const fogColor = new THREE14__namespace.Color(fog.color || "#1e1e1e");
3093
+ const fogColor = new THREE26__namespace.Color(fog.color || "#1e1e1e");
3074
3094
  if (fog.type === "linear") {
3075
3095
  const near = Number.isFinite(fog.linear.near) ? fog.linear.near : 1;
3076
3096
  const farCandidate = Number.isFinite(fog.linear.far) ? fog.linear.far : near + 10;
3077
3097
  const far = Math.max(near + MIN_FOG_DELTA, farCandidate);
3078
- this.scene.fog = new THREE14__namespace.Fog(fogColor, near, far);
3098
+ this.scene.fog = new THREE26__namespace.Fog(fogColor, near, far);
3079
3099
  return;
3080
3100
  }
3081
3101
  const density = Number.isFinite(fog.exponential.density) ? Math.max(MIN_DENSITY, fog.exponential.density) : 2e-3;
3082
- this.scene.fog = new THREE14__namespace.FogExp2(fogColor, density);
3102
+ this.scene.fog = new THREE26__namespace.FogExp2(fogColor, density);
3083
3103
  }
3084
3104
  update(world) {
3085
3105
  const entityIds = world.query(Environment);
@@ -3161,10 +3181,10 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
3161
3181
  );
3162
3182
  }
3163
3183
  if (values.bloom.enabled) {
3164
- const size = this.renderer.getSize(new THREE14__namespace.Vector2());
3184
+ const size = this.renderer.getSize(new THREE26__namespace.Vector2());
3165
3185
  composer.addPass(
3166
3186
  new UnrealBloomPass_js.UnrealBloomPass(
3167
- new THREE14__namespace.Vector2(size.width, size.height),
3187
+ new THREE26__namespace.Vector2(size.width, size.height),
3168
3188
  values.bloom.intensity,
3169
3189
  values.bloom.radius,
3170
3190
  values.bloom.threshold
@@ -3209,7 +3229,7 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
3209
3229
  if (values.dotScreen.enabled) {
3210
3230
  composer.addPass(
3211
3231
  new DotScreenPass_js.DotScreenPass(
3212
- new THREE14__namespace.Vector2(0, 0),
3232
+ new THREE26__namespace.Vector2(0, 0),
3213
3233
  values.dotScreen.angle ?? values.dotScreen.dotScreenAngle,
3214
3234
  values.dotScreen.scale ?? values.dotScreen.dotScreenScale
3215
3235
  )
@@ -3266,6 +3286,20 @@ var _PostprocessingSyncSystem = class _PostprocessingSyncSystem {
3266
3286
  __name(_PostprocessingSyncSystem, "PostprocessingSyncSystem");
3267
3287
  var PostprocessingSyncSystem = _PostprocessingSyncSystem;
3268
3288
  var MIN_SIZE = 1e-4;
3289
+ function toBodyType(RAPIER, bodyType) {
3290
+ switch (bodyType) {
3291
+ case "fixed":
3292
+ return RAPIER.RigidBodyType.Fixed;
3293
+ case "kinematicPosition":
3294
+ return RAPIER.RigidBodyType.KinematicPositionBased;
3295
+ case "kinematicVelocity":
3296
+ return RAPIER.RigidBodyType.KinematicVelocityBased;
3297
+ case "dynamic":
3298
+ default:
3299
+ return RAPIER.RigidBodyType.Dynamic;
3300
+ }
3301
+ }
3302
+ __name(toBodyType, "toBodyType");
3269
3303
  function createRigidBodyDesc(RAPIER, bodyType) {
3270
3304
  if (bodyType === "fixed") {
3271
3305
  return RAPIER.RigidBodyDesc.fixed();
@@ -3283,7 +3317,7 @@ function computeColliderState(colliderValues, object) {
3283
3317
  if (!object) {
3284
3318
  return colliderValues;
3285
3319
  }
3286
- const scale = new THREE14__namespace.Vector3();
3320
+ const scale = new THREE26__namespace.Vector3();
3287
3321
  object.getWorldScale(scale);
3288
3322
  return {
3289
3323
  ...colliderValues,
@@ -3334,8 +3368,8 @@ function createColliderDesc(RAPIER, values, triMeshData) {
3334
3368
  values.offsetPosition.y,
3335
3369
  values.offsetPosition.z
3336
3370
  );
3337
- const offsetQuaternion = new THREE14__namespace.Quaternion().setFromEuler(
3338
- new THREE14__namespace.Euler(
3371
+ const offsetQuaternion = new THREE26__namespace.Quaternion().setFromEuler(
3372
+ new THREE26__namespace.Euler(
3339
3373
  values.offsetRotation.x,
3340
3374
  values.offsetRotation.y,
3341
3375
  values.offsetRotation.z
@@ -3350,17 +3384,17 @@ function collectTriMeshParts(object) {
3350
3384
  return null;
3351
3385
  }
3352
3386
  object.updateWorldMatrix(true, true);
3353
- const worldScale = new THREE14__namespace.Vector3();
3387
+ const worldScale = new THREE26__namespace.Vector3();
3354
3388
  object.getWorldScale(worldScale);
3355
3389
  const scale = {
3356
3390
  x: Math.abs(worldScale.x) || 1,
3357
3391
  y: Math.abs(worldScale.y) || 1,
3358
3392
  z: Math.abs(worldScale.z) || 1
3359
3393
  };
3360
- const worldToObject = new THREE14__namespace.Matrix4().copy(object.matrixWorld).invert();
3394
+ const worldToObject = new THREE26__namespace.Matrix4().copy(object.matrixWorld).invert();
3361
3395
  const parts = [];
3362
3396
  object.traverse((child) => {
3363
- if (!(child instanceof THREE14__namespace.Mesh) || !child.geometry) {
3397
+ if (!(child instanceof THREE26__namespace.Mesh) || !child.geometry) {
3364
3398
  return;
3365
3399
  }
3366
3400
  const geometry = child.geometry;
@@ -3377,7 +3411,7 @@ function collectTriMeshParts(object) {
3377
3411
  if (indexCount < 3) {
3378
3412
  return;
3379
3413
  }
3380
- const relativeMatrix = new THREE14__namespace.Matrix4().multiplyMatrices(
3414
+ const relativeMatrix = new THREE26__namespace.Matrix4().multiplyMatrices(
3381
3415
  worldToObject,
3382
3416
  child.matrixWorld
3383
3417
  );
@@ -3418,7 +3452,7 @@ function createTriMeshData(collected) {
3418
3452
  }
3419
3453
  const vertices = new Float32Array(vertexTotal * 3);
3420
3454
  const indices = new Uint32Array(indexTotal);
3421
- const point = new THREE14__namespace.Vector3();
3455
+ const point = new THREE26__namespace.Vector3();
3422
3456
  let vertexOffset = 0;
3423
3457
  let indexOffset = 0;
3424
3458
  for (const part of parts) {
@@ -3450,8 +3484,8 @@ function createTriMeshData(collected) {
3450
3484
  }
3451
3485
  __name(createTriMeshData, "createTriMeshData");
3452
3486
  function syncBodyFromTransform(body, transform) {
3453
- const quaternion = new THREE14__namespace.Quaternion().setFromEuler(
3454
- new THREE14__namespace.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
3487
+ const quaternion = new THREE26__namespace.Quaternion().setFromEuler(
3488
+ new THREE26__namespace.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
3455
3489
  );
3456
3490
  body.setTranslation(
3457
3491
  {
@@ -3478,8 +3512,8 @@ function syncTransformFromBody(transform, body) {
3478
3512
  transform.position.x = position.x;
3479
3513
  transform.position.y = position.y;
3480
3514
  transform.position.z = position.z;
3481
- const euler = new THREE14__namespace.Euler().setFromQuaternion(
3482
- new THREE14__namespace.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
3515
+ const euler = new THREE26__namespace.Euler().setFromQuaternion(
3516
+ new THREE26__namespace.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
3483
3517
  );
3484
3518
  transform.rotation.x = euler.x;
3485
3519
  transform.rotation.y = euler.y;
@@ -3564,7 +3598,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
3564
3598
  setPhysicsLiveRef(physics, "collider", null);
3565
3599
  continue;
3566
3600
  }
3567
- const rigidBodySignature = JSON.stringify(physics.values.rigidbody);
3601
+ const { type: _desiredBodyType, ...rigidBodyRebuildValues } = physics.values.rigidbody;
3602
+ const rigidBodySignature = JSON.stringify(rigidBodyRebuildValues);
3568
3603
  const previousRigidBodySignature = this.rigidBodySignatures.get(entityId);
3569
3604
  const hasRigidBody = this.rigidBodyHandles.has(entityId);
3570
3605
  const shouldRecreateBody = !hasRigidBody || previousRigidBodySignature !== rigidBodySignature;
@@ -3591,8 +3626,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
3591
3626
  transform.position.y,
3592
3627
  transform.position.z
3593
3628
  );
3594
- const quaternion = new THREE14__namespace.Quaternion().setFromEuler(
3595
- new THREE14__namespace.Euler(
3629
+ const quaternion = new THREE26__namespace.Quaternion().setFromEuler(
3630
+ new THREE26__namespace.Euler(
3596
3631
  transform.rotation.x,
3597
3632
  transform.rotation.y,
3598
3633
  transform.rotation.z
@@ -3619,6 +3654,25 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
3619
3654
  continue;
3620
3655
  }
3621
3656
  setPhysicsLiveRef(physics, "rigidBody", body);
3657
+ const desiredBodyType = this.isEditorMode ? "fixed" : physics.values.rigidbody.type;
3658
+ const desiredRapierType = toBodyType(this.rapier, desiredBodyType);
3659
+ if (body.bodyType() !== desiredRapierType) {
3660
+ body.setBodyType(desiredRapierType, true);
3661
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
3662
+ body.setAdditionalMass(physics.values.rigidbody.mass, true);
3663
+ }
3664
+ }
3665
+ const pending = physics.pendingVelocity;
3666
+ if (pending) {
3667
+ setPhysicsLiveRef(physics, "pendingVelocity", null);
3668
+ if (!this.isEditorMode && desiredBodyType === "dynamic") {
3669
+ try {
3670
+ body.setLinvel(pending.linear, true);
3671
+ body.setAngvel(pending.angular, true);
3672
+ } catch {
3673
+ }
3674
+ }
3675
+ }
3622
3676
  if (this.isEditorMode || physics.values.rigidbody.type !== "dynamic") {
3623
3677
  syncBodyFromTransform(body, transform);
3624
3678
  }
@@ -4171,7 +4225,7 @@ function applyLightValues(lightComponent) {
4171
4225
  values.castShadow
4172
4226
  );
4173
4227
  }
4174
- if (light instanceof THREE14__namespace.PointLight) {
4228
+ if (light instanceof THREE26__namespace.PointLight) {
4175
4229
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
4176
4230
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
4177
4231
  }
@@ -4179,7 +4233,7 @@ function applyLightValues(lightComponent) {
4179
4233
  light.decay = Math.max(0, values.decay);
4180
4234
  }
4181
4235
  }
4182
- if (light instanceof THREE14__namespace.SpotLight) {
4236
+ if (light instanceof THREE26__namespace.SpotLight) {
4183
4237
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
4184
4238
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
4185
4239
  }
@@ -4193,7 +4247,7 @@ function applyLightValues(lightComponent) {
4193
4247
  light.penumbra = Math.max(0, Math.min(1, values.penumbra));
4194
4248
  }
4195
4249
  }
4196
- if (light instanceof THREE14__namespace.RectAreaLight) {
4250
+ if (light instanceof THREE26__namespace.RectAreaLight) {
4197
4251
  if (typeof values.width === "number" && Number.isFinite(values.width)) {
4198
4252
  light.width = Math.max(MIN_AREA_SIZE, values.width);
4199
4253
  }
@@ -4254,15 +4308,15 @@ function resolveMaterialPropertyKey(key) {
4254
4308
  __name(resolveMaterialPropertyKey, "resolveMaterialPropertyKey");
4255
4309
  function createMaterialByType(type) {
4256
4310
  if (type === "basic") {
4257
- return new THREE14__namespace.MeshBasicMaterial();
4311
+ return new THREE26__namespace.MeshBasicMaterial();
4258
4312
  }
4259
4313
  if (type === "phong") {
4260
- return new THREE14__namespace.MeshPhongMaterial();
4314
+ return new THREE26__namespace.MeshPhongMaterial();
4261
4315
  }
4262
4316
  if (type === "physical") {
4263
- return new THREE14__namespace.MeshPhysicalMaterial();
4317
+ return new THREE26__namespace.MeshPhysicalMaterial();
4264
4318
  }
4265
- return new THREE14__namespace.MeshStandardMaterial();
4319
+ return new THREE26__namespace.MeshStandardMaterial();
4266
4320
  }
4267
4321
  __name(createMaterialByType, "createMaterialByType");
4268
4322
  function replaceMaterialAcrossObject(object, previous, next) {
@@ -4321,12 +4375,12 @@ function applyTextureFiltering(material, filtering) {
4321
4375
  const target = material;
4322
4376
  for (const slot of materialTextureSlots) {
4323
4377
  const texture = target[slot];
4324
- if (!texture || !(texture instanceof THREE14__namespace.Texture)) {
4378
+ if (!texture || !(texture instanceof THREE26__namespace.Texture)) {
4325
4379
  continue;
4326
4380
  }
4327
4381
  const useMipmaps = texture.generateMipmaps !== false;
4328
- texture.magFilter = sharp ? THREE14__namespace.NearestFilter : THREE14__namespace.LinearFilter;
4329
- texture.minFilter = useMipmaps ? sharp ? THREE14__namespace.NearestMipmapNearestFilter : THREE14__namespace.LinearMipmapLinearFilter : sharp ? THREE14__namespace.NearestFilter : THREE14__namespace.LinearFilter;
4382
+ texture.magFilter = sharp ? THREE26__namespace.NearestFilter : THREE26__namespace.LinearFilter;
4383
+ texture.minFilter = useMipmaps ? sharp ? THREE26__namespace.NearestMipmapNearestFilter : THREE26__namespace.LinearMipmapLinearFilter : sharp ? THREE26__namespace.NearestFilter : THREE26__namespace.LinearFilter;
4330
4384
  texture.needsUpdate = true;
4331
4385
  }
4332
4386
  material.needsUpdate = true;
@@ -4336,12 +4390,12 @@ function setTextureMipmaps(material, enabled) {
4336
4390
  const target = material;
4337
4391
  for (const slot of materialTextureSlots) {
4338
4392
  const texture = target[slot];
4339
- if (!texture || !(texture instanceof THREE14__namespace.Texture)) {
4393
+ if (!texture || !(texture instanceof THREE26__namespace.Texture)) {
4340
4394
  continue;
4341
4395
  }
4342
4396
  texture.generateMipmaps = enabled;
4343
- const sharp = texture.magFilter === THREE14__namespace.NearestFilter;
4344
- texture.minFilter = enabled ? sharp ? THREE14__namespace.NearestMipmapNearestFilter : THREE14__namespace.LinearMipmapLinearFilter : sharp ? THREE14__namespace.NearestFilter : THREE14__namespace.LinearFilter;
4397
+ const sharp = texture.magFilter === THREE26__namespace.NearestFilter;
4398
+ texture.minFilter = enabled ? sharp ? THREE26__namespace.NearestMipmapNearestFilter : THREE26__namespace.LinearMipmapLinearFilter : sharp ? THREE26__namespace.NearestFilter : THREE26__namespace.LinearFilter;
4345
4399
  texture.needsUpdate = true;
4346
4400
  }
4347
4401
  material.needsUpdate = true;
@@ -4352,7 +4406,7 @@ function assignMaterialTexture(material, rawKey, url, textureCache) {
4352
4406
  const key = resolveMaterialPropertyKey(rawKey);
4353
4407
  let texture = textureCache.get(url);
4354
4408
  if (!texture) {
4355
- texture = new THREE14__namespace.TextureLoader().load(url);
4409
+ texture = new THREE26__namespace.TextureLoader().load(url);
4356
4410
  textureCache.set(url, texture);
4357
4411
  }
4358
4412
  target[key] = texture;
@@ -4390,7 +4444,7 @@ function applyMaterialState(material, value, textureCache) {
4390
4444
  }
4391
4445
  target.side = value.side;
4392
4446
  target.blending = value.blending;
4393
- target.premultipliedAlpha = value.blending === THREE14__namespace.MultiplyBlending || value.blending === THREE14__namespace.SubtractiveBlending;
4447
+ target.premultipliedAlpha = value.blending === THREE26__namespace.MultiplyBlending || value.blending === THREE26__namespace.SubtractiveBlending;
4394
4448
  target.depthTest = value.depthTest;
4395
4449
  target.depthWrite = value.depthWrite;
4396
4450
  target.wireframe = value.wireframe;
@@ -4551,12 +4605,12 @@ function resolvePrimaryMesh(object) {
4551
4605
  if (!object) {
4552
4606
  return null;
4553
4607
  }
4554
- if (object instanceof THREE14__namespace.Mesh) {
4608
+ if (object instanceof THREE26__namespace.Mesh) {
4555
4609
  return object;
4556
4610
  }
4557
4611
  let found = null;
4558
4612
  object.traverse((child) => {
4559
- if (!found && child instanceof THREE14__namespace.Mesh) {
4613
+ if (!found && child instanceof THREE26__namespace.Mesh) {
4560
4614
  found = child;
4561
4615
  }
4562
4616
  });
@@ -4577,22 +4631,22 @@ function applyGeometry(component) {
4577
4631
  switch (component.kind) {
4578
4632
  case "cube": {
4579
4633
  const params = sanitized;
4580
- nextGeometry = new THREE14__namespace.BoxGeometry(params.width, params.height, params.depth);
4634
+ nextGeometry = new THREE26__namespace.BoxGeometry(params.width, params.height, params.depth);
4581
4635
  break;
4582
4636
  }
4583
4637
  case "plane": {
4584
4638
  const params = sanitized;
4585
- nextGeometry = new THREE14__namespace.PlaneGeometry(params.width, params.height);
4639
+ nextGeometry = new THREE26__namespace.PlaneGeometry(params.width, params.height);
4586
4640
  break;
4587
4641
  }
4588
4642
  case "sphere": {
4589
4643
  const params = sanitized;
4590
- nextGeometry = new THREE14__namespace.SphereGeometry(params.radius, 24, 24);
4644
+ nextGeometry = new THREE26__namespace.SphereGeometry(params.radius, 24, 24);
4591
4645
  break;
4592
4646
  }
4593
4647
  case "cylinder": {
4594
4648
  const params = sanitized;
4595
- nextGeometry = new THREE14__namespace.CylinderGeometry(
4649
+ nextGeometry = new THREE26__namespace.CylinderGeometry(
4596
4650
  params.radius,
4597
4651
  params.radius,
4598
4652
  params.height,
@@ -4602,7 +4656,7 @@ function applyGeometry(component) {
4602
4656
  }
4603
4657
  case "capsule": {
4604
4658
  const params = sanitized;
4605
- nextGeometry = new THREE14__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
4659
+ nextGeometry = new THREE26__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
4606
4660
  break;
4607
4661
  }
4608
4662
  default:
@@ -4618,7 +4672,7 @@ function applyShadowFlags(component) {
4618
4672
  return;
4619
4673
  }
4620
4674
  component.object.traverse((child) => {
4621
- if (child instanceof THREE14__namespace.Mesh) {
4675
+ if (child instanceof THREE26__namespace.Mesh) {
4622
4676
  child.castShadow = component.castShadow;
4623
4677
  child.receiveShadow = component.receiveShadow;
4624
4678
  }
@@ -4701,35 +4755,35 @@ function buildGeometry(component) {
4701
4755
  switch (component.meshKind) {
4702
4756
  case "cube": {
4703
4757
  const params = p;
4704
- return new THREE14__namespace.BoxGeometry(params.width, params.height, params.depth);
4758
+ return new THREE26__namespace.BoxGeometry(params.width, params.height, params.depth);
4705
4759
  }
4706
4760
  case "plane": {
4707
4761
  const params = p;
4708
- return new THREE14__namespace.PlaneGeometry(params.width, params.height);
4762
+ return new THREE26__namespace.PlaneGeometry(params.width, params.height);
4709
4763
  }
4710
4764
  case "sphere": {
4711
4765
  const params = p;
4712
- return new THREE14__namespace.SphereGeometry(params.radius, 24, 24);
4766
+ return new THREE26__namespace.SphereGeometry(params.radius, 24, 24);
4713
4767
  }
4714
4768
  case "cylinder": {
4715
4769
  const params = p;
4716
- return new THREE14__namespace.CylinderGeometry(params.radius, params.radius, params.height, 20);
4770
+ return new THREE26__namespace.CylinderGeometry(params.radius, params.radius, params.height, 20);
4717
4771
  }
4718
4772
  case "capsule": {
4719
4773
  const params = p;
4720
- return new THREE14__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
4774
+ return new THREE26__namespace.CapsuleGeometry(params.radius, params.height, 4, 8);
4721
4775
  }
4722
4776
  default:
4723
- return new THREE14__namespace.BoxGeometry(1, 1, 1);
4777
+ return new THREE26__namespace.BoxGeometry(1, 1, 1);
4724
4778
  }
4725
4779
  }
4726
4780
  __name(buildGeometry, "buildGeometry");
4727
- var _position = new THREE14__namespace.Vector3();
4728
- var _quaternion = new THREE14__namespace.Quaternion();
4729
- var _euler = new THREE14__namespace.Euler();
4730
- var _scale = new THREE14__namespace.Vector3();
4731
- var _matrix = new THREE14__namespace.Matrix4();
4732
- var _modelMatrix = new THREE14__namespace.Matrix4();
4781
+ var _position = new THREE26__namespace.Vector3();
4782
+ var _quaternion = new THREE26__namespace.Quaternion();
4783
+ var _euler = new THREE26__namespace.Euler();
4784
+ var _scale = new THREE26__namespace.Vector3();
4785
+ var _matrix = new THREE26__namespace.Matrix4();
4786
+ var _modelMatrix = new THREE26__namespace.Matrix4();
4733
4787
  function instanceTransformFromComponent(transform, out) {
4734
4788
  _position.set(transform.position.x, transform.position.y, transform.position.z);
4735
4789
  _euler.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, "XYZ");
@@ -4882,7 +4936,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
4882
4936
  const sourceMaterial = Array.isArray(meshChild.material) ? meshChild.material.map((m) => m.clone()) : meshChild.material.clone();
4883
4937
  meshChild.updateWorldMatrix(true, false);
4884
4938
  const localMatrix = meshChild.matrixWorld.clone();
4885
- const instancedMesh2 = new THREE14__namespace.InstancedMesh(
4939
+ const instancedMesh2 = new THREE26__namespace.InstancedMesh(
4886
4940
  geometry2,
4887
4941
  Array.isArray(sourceMaterial) ? sourceMaterial : sourceMaterial,
4888
4942
  count2
@@ -4907,7 +4961,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
4907
4961
  this.loadingSignatures.delete(targetEntityId);
4908
4962
  return;
4909
4963
  }
4910
- const group = new THREE14__namespace.Group();
4964
+ const group = new THREE26__namespace.Group();
4911
4965
  for (const entry of entries) {
4912
4966
  group.add(entry.instancedMesh);
4913
4967
  }
@@ -4925,8 +4979,8 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
4925
4979
  }
4926
4980
  const count = instanceCount;
4927
4981
  const geometry = buildGeometry(comp);
4928
- const material = new THREE14__namespace.MeshStandardMaterial({ color: "#ffffff" });
4929
- const instancedMesh = new THREE14__namespace.InstancedMesh(geometry, material, count);
4982
+ const material = new THREE26__namespace.MeshStandardMaterial({ color: "#ffffff" });
4983
+ const instancedMesh = new THREE26__namespace.InstancedMesh(geometry, material, count);
4930
4984
  instancedMesh.castShadow = comp.castShadow;
4931
4985
  instancedMesh.receiveShadow = comp.receiveShadow;
4932
4986
  for (let i = 0; i < count; i++) {
@@ -5346,9 +5400,6 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
5346
5400
  if (typeof sourceInteractable.pointable === "boolean") {
5347
5401
  interactable.pointable = sourceInteractable.pointable;
5348
5402
  }
5349
- if (typeof sourceInteractable.grabMode === "string") {
5350
- interactable.grabMode = sourceInteractable.grabMode;
5351
- }
5352
5403
  if (typeof sourceInteractable.grabReach === "string") {
5353
5404
  interactable.grabReach = sourceInteractable.grabReach;
5354
5405
  }
@@ -5361,6 +5412,30 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
5361
5412
  if (typeof sourceInteractable.grabRadius === "number") {
5362
5413
  interactable.grabRadius = sourceInteractable.grabRadius;
5363
5414
  }
5415
+ if (typeof sourceInteractable.grabHands === "string") {
5416
+ interactable.grabHands = sourceInteractable.grabHands;
5417
+ }
5418
+ if (typeof sourceInteractable.twoHandRotation === "string") {
5419
+ interactable.twoHandRotation = sourceInteractable.twoHandRotation;
5420
+ }
5421
+ if (typeof sourceInteractable.allowTwoHandScale === "boolean") {
5422
+ interactable.allowTwoHandScale = sourceInteractable.allowTwoHandScale;
5423
+ }
5424
+ if (typeof sourceInteractable.minScale === "number") {
5425
+ interactable.minScale = sourceInteractable.minScale;
5426
+ }
5427
+ if (typeof sourceInteractable.maxScale === "number") {
5428
+ interactable.maxScale = sourceInteractable.maxScale;
5429
+ }
5430
+ if (Array.isArray(sourceInteractable.attachPoints)) {
5431
+ interactable.attachPoints = sourceInteractable.attachPoints.map((point) => asRecord3(point)).filter(
5432
+ (point) => point !== null && typeof point.id === "string"
5433
+ ).map(
5434
+ (point) => createAttachPoint(
5435
+ point
5436
+ )
5437
+ );
5438
+ }
5364
5439
  if (typeof sourceInteractable.throwOnRelease === "boolean") {
5365
5440
  interactable.throwOnRelease = sourceInteractable.throwOnRelease;
5366
5441
  }
@@ -5383,8 +5458,16 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
5383
5458
  z: typeof grabRot.z === "number" ? grabRot.z : 0
5384
5459
  };
5385
5460
  }
5386
- if (typeof sourceInteractable.highlightOnHover === "boolean") {
5387
- interactable.highlightOnHover = sourceInteractable.highlightOnHover;
5461
+ if (typeof sourceInteractable.hoverBehavior === "string") {
5462
+ interactable.hoverBehavior = sourceInteractable.hoverBehavior;
5463
+ } else if (sourceInteractable.highlightOnHover === false) {
5464
+ interactable.hoverBehavior = "none";
5465
+ }
5466
+ if (typeof sourceInteractable.hoverColor === "string") {
5467
+ interactable.hoverColor = sourceInteractable.hoverColor;
5468
+ }
5469
+ if (typeof sourceInteractable.hoverScale === "number") {
5470
+ interactable.hoverScale = sourceInteractable.hoverScale;
5388
5471
  }
5389
5472
  }
5390
5473
  }
@@ -6569,7 +6652,7 @@ var _Behaviour = class _Behaviour {
6569
6652
  if (!this.threeRef) {
6570
6653
  return null;
6571
6654
  }
6572
- const position = new THREE14__namespace.Vector3();
6655
+ const position = new THREE26__namespace.Vector3();
6573
6656
  this.threeRef.object.getWorldPosition(position);
6574
6657
  return position;
6575
6658
  }
@@ -6581,7 +6664,7 @@ var _Behaviour = class _Behaviour {
6581
6664
  if (!this.threeRef) {
6582
6665
  return null;
6583
6666
  }
6584
- const target = new THREE14__namespace.Vector3();
6667
+ const target = new THREE26__namespace.Vector3();
6585
6668
  this.threeRef.object.getWorldDirection(target);
6586
6669
  return target;
6587
6670
  }
@@ -6643,7 +6726,7 @@ var _Behaviour = class _Behaviour {
6643
6726
  if (!this.threeRef) {
6644
6727
  return;
6645
6728
  }
6646
- const rotation = new THREE14__namespace.Quaternion();
6729
+ const rotation = new THREE26__namespace.Quaternion();
6647
6730
  this.threeRef.object.getWorldQuaternion(rotation);
6648
6731
  rotation.slerp(targetRotation, alpha);
6649
6732
  this.transform.setRotation(rotation);
@@ -6660,7 +6743,7 @@ var _Behaviour = class _Behaviour {
6660
6743
  return;
6661
6744
  }
6662
6745
  const clone = this.threeRef.object.clone();
6663
- const rotation = new THREE14__namespace.Quaternion();
6746
+ const rotation = new THREE26__namespace.Quaternion();
6664
6747
  clone.lookAt(targetPosition);
6665
6748
  clone.getWorldQuaternion(rotation);
6666
6749
  this.transform.setRotation(rotation);
@@ -6798,6 +6881,17 @@ var _Behaviour = class _Behaviour {
6798
6881
  *
6799
6882
  * @param input - The source doing the grabbing.
6800
6883
  */
6884
+ /**
6885
+ * A SECOND hand joined a two-hand grab on this entity.
6886
+ *
6887
+ * The first hand anchors the object and keeps holding it; this fires for the
6888
+ * hand that came second — the foregrip on a rifle, say.
6889
+ */
6890
+ onSecondaryGrab(_input) {
6891
+ }
6892
+ /** The second hand let go, leaving the object in the first hand alone. */
6893
+ onSecondaryRelease(_input) {
6894
+ }
6801
6895
  /**
6802
6896
  * A hand pose declared on this entity started being held.
6803
6897
  *
@@ -7015,8 +7109,8 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
7015
7109
  }, "onPointerUp");
7016
7110
  this.moduleLoader = moduleLoader ?? defaultModuleLoader;
7017
7111
  this.selectionCtx = selectionContext ?? null;
7018
- this.raycaster = new THREE14__namespace.Raycaster();
7019
- this.ndcPointer = new THREE14__namespace.Vector2();
7112
+ this.raycaster = new THREE26__namespace.Raycaster();
7113
+ this.ndcPointer = new THREE26__namespace.Vector2();
7020
7114
  if (this.selectionCtx) {
7021
7115
  this.selectionCtx.domElement.addEventListener("pointerdown", this.onPointerDown);
7022
7116
  this.selectionCtx.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -7395,7 +7489,7 @@ function disposeBuiltObject(root) {
7395
7489
  for (const mat of materials) {
7396
7490
  if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
7397
7491
  for (const value of Object.values(mat)) {
7398
- if (value instanceof THREE14__namespace.Texture) value.dispose();
7492
+ if (value instanceof THREE26__namespace.Texture) value.dispose();
7399
7493
  }
7400
7494
  mat.dispose?.();
7401
7495
  }
@@ -7759,7 +7853,7 @@ var _AudioSystem = class _AudioSystem {
7759
7853
  if (!prev || prev.volume !== opts.volume) {
7760
7854
  sound.setVolume(opts.volume);
7761
7855
  }
7762
- if (opts.positional && sound instanceof THREE14__namespace.PositionalAudio) {
7856
+ if (opts.positional && sound instanceof THREE26__namespace.PositionalAudio) {
7763
7857
  if (!prev || prev.refDistance !== opts.refDistance) {
7764
7858
  sound.setRefDistance(opts.refDistance);
7765
7859
  }
@@ -7885,7 +7979,7 @@ var _VideoSystem = class _VideoSystem {
7885
7979
  component.object.scale.set(opts.width, opts.height, 1);
7886
7980
  }
7887
7981
  const material = component.material;
7888
- if (material instanceof THREE14__namespace.ShaderMaterial) {
7982
+ if (material instanceof THREE26__namespace.ShaderMaterial) {
7889
7983
  if (!prev || prev.backgroundColor !== opts.backgroundColor) {
7890
7984
  material.uniforms.keyColor.value.set(opts.backgroundColor);
7891
7985
  }
@@ -7899,7 +7993,7 @@ var _VideoSystem = class _VideoSystem {
7899
7993
  material.uniforms.spill.value = opts.spill;
7900
7994
  }
7901
7995
  }
7902
- if (opts.positional && component.audioNode instanceof THREE14__namespace.PositionalAudio) {
7996
+ if (opts.positional && component.audioNode instanceof THREE26__namespace.PositionalAudio) {
7903
7997
  const posAudio = component.audioNode;
7904
7998
  if (!prev || prev.refDistance !== opts.refDistance) {
7905
7999
  posAudio.setRefDistance(opts.refDistance);
@@ -7939,8 +8033,8 @@ function blankState(source) {
7939
8033
  handedness: handednessOf(source),
7940
8034
  kind: kindOf(source),
7941
8035
  connected: true,
7942
- rayOrigin: new THREE14__namespace.Vector3(),
7943
- rayDirection: new THREE14__namespace.Vector3(0, 0, -1),
8036
+ rayOrigin: new THREE26__namespace.Vector3(),
8037
+ rayDirection: new THREE26__namespace.Vector3(0, 0, -1),
7944
8038
  gripPosition: null,
7945
8039
  gripQuaternion: null,
7946
8040
  select: false,
@@ -7966,7 +8060,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
7966
8060
  }
7967
8061
  if (spaces.length === 0) return null;
7968
8062
  const target = existing ?? {};
7969
- const matrix = new THREE14__namespace.Matrix4();
8063
+ const matrix = new THREE26__namespace.Matrix4();
7970
8064
  const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
7971
8065
  const fillPoses = frame.fillPoses;
7972
8066
  if (typeof fillPoses === "function") {
@@ -7979,7 +8073,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
7979
8073
  if (filled) {
7980
8074
  for (let i = 0; i < names.length; i += 1) {
7981
8075
  matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
7982
- const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
8076
+ const vector = target[names[i]] ?? new THREE26__namespace.Vector3();
7983
8077
  vector.setFromMatrixPosition(matrix);
7984
8078
  target[names[i]] = vector;
7985
8079
  }
@@ -7997,7 +8091,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
7997
8091
  continue;
7998
8092
  }
7999
8093
  matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
8000
- const vector = target[names[i]] ?? new THREE14__namespace.Vector3();
8094
+ const vector = target[names[i]] ?? new THREE26__namespace.Vector3();
8001
8095
  vector.setFromMatrixPosition(matrix);
8002
8096
  target[names[i]] = vector;
8003
8097
  anyTracked = true;
@@ -8009,7 +8103,7 @@ var _XRInputSystem = class _XRInputSystem {
8009
8103
  constructor() {
8010
8104
  this.records = /* @__PURE__ */ new Map();
8011
8105
  /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
8012
- this.referenceToWorld = new THREE14__namespace.Matrix4();
8106
+ this.referenceToWorld = new THREE26__namespace.Matrix4();
8013
8107
  this.session = null;
8014
8108
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
8015
8109
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -8107,19 +8201,19 @@ var _XRInputSystem = class _XRInputSystem {
8107
8201
  if (frame && referenceSpace && source.targetRaySpace) {
8108
8202
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
8109
8203
  if (pose) {
8110
- const m = new THREE14__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
8204
+ const m = new THREE26__namespace.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
8111
8205
  state.rayOrigin.setFromMatrixPosition(m);
8112
8206
  state.rayDirection.set(0, 0, -1).applyQuaternion(
8113
- new THREE14__namespace.Quaternion().setFromRotationMatrix(m)
8207
+ new THREE26__namespace.Quaternion().setFromRotationMatrix(m)
8114
8208
  ).normalize();
8115
8209
  }
8116
8210
  }
8117
8211
  if (frame && referenceSpace && source.gripSpace) {
8118
8212
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
8119
8213
  if (gripPose) {
8120
- const m = new THREE14__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
8121
- state.gripPosition ?? (state.gripPosition = new THREE14__namespace.Vector3());
8122
- state.gripQuaternion ?? (state.gripQuaternion = new THREE14__namespace.Quaternion());
8214
+ const m = new THREE26__namespace.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
8215
+ state.gripPosition ?? (state.gripPosition = new THREE26__namespace.Vector3());
8216
+ state.gripQuaternion ?? (state.gripQuaternion = new THREE26__namespace.Quaternion());
8123
8217
  state.gripPosition.setFromMatrixPosition(m);
8124
8218
  state.gripQuaternion.setFromRotationMatrix(m);
8125
8219
  }
@@ -8228,15 +8322,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
8228
8322
  const fresh = this.samples.filter(
8229
8323
  (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
8230
8324
  );
8231
- if (fresh.length < 2) return new THREE14__namespace.Vector3();
8232
- let best = new THREE14__namespace.Vector3();
8325
+ if (fresh.length < 2) return new THREE26__namespace.Vector3();
8326
+ let best = new THREE26__namespace.Vector3();
8233
8327
  let bestSpeed = 0;
8234
8328
  for (let i = 1; i < fresh.length; i += 1) {
8235
8329
  const previous = fresh[i - 1];
8236
8330
  const current = fresh[i];
8237
8331
  const dt = current.time - previous.time;
8238
8332
  if (dt <= 1e-5) continue;
8239
- const velocity = new THREE14__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
8333
+ const velocity = new THREE26__namespace.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
8240
8334
  const speed = velocity.length();
8241
8335
  if (speed > MAX_PLAUSIBLE_SPEED) continue;
8242
8336
  if (speed > bestSpeed) {
@@ -8253,25 +8347,79 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
8253
8347
  __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
8254
8348
  var ThrowVelocityTracker = _ThrowVelocityTracker;
8255
8349
  function angularVelocityFrom(previous, current, deltaTime) {
8256
- if (deltaTime <= 1e-5) return new THREE14__namespace.Vector3();
8350
+ if (deltaTime <= 1e-5) return new THREE26__namespace.Vector3();
8257
8351
  const delta = current.clone().multiply(previous.clone().invert());
8258
8352
  if (delta.w < 0) {
8259
8353
  delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
8260
8354
  }
8261
- const angle = 2 * Math.acos(THREE14__namespace.MathUtils.clamp(delta.w, -1, 1));
8355
+ const angle = 2 * Math.acos(THREE26__namespace.MathUtils.clamp(delta.w, -1, 1));
8262
8356
  const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
8263
- if (sinHalf < 1e-6 || angle < 1e-6) return new THREE14__namespace.Vector3();
8264
- return new THREE14__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
8357
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE26__namespace.Vector3();
8358
+ return new THREE26__namespace.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
8265
8359
  }
8266
8360
  __name(angularVelocityFrom, "angularVelocityFrom");
8361
+ var MIN_HAND_SEPARATION = 0.06;
8362
+ var MIN_ANCHOR_SEPARATION = 1e-3;
8363
+ var SCALE_DEADZONE = 0.05;
8364
+ function solveTwoHandTransform(primary, secondary, basis, options) {
8365
+ const separation = primary.position.distanceTo(secondary.position);
8366
+ const anchorDelta = new THREE26__namespace.Vector3().subVectors(
8367
+ basis.secondaryLocal,
8368
+ basis.primaryLocal
8369
+ );
8370
+ const anchorLength = anchorDelta.length();
8371
+ let factor = 1;
8372
+ if (options.allowScale && basis.initialSeparation > MIN_ANCHOR_SEPARATION && separation > MIN_ANCHOR_SEPARATION) {
8373
+ factor = separation / basis.initialSeparation;
8374
+ if (Math.abs(factor - 1) <= SCALE_DEADZONE) {
8375
+ factor = 1;
8376
+ } else {
8377
+ factor += factor > 1 ? -SCALE_DEADZONE : SCALE_DEADZONE;
8378
+ }
8379
+ factor = Math.min(options.maxScale, Math.max(options.minScale, factor));
8380
+ }
8381
+ const scale = basis.initialScale.clone().multiplyScalar(factor);
8382
+ const degenerate = separation < MIN_HAND_SEPARATION || anchorLength < MIN_ANCHOR_SEPARATION;
8383
+ const base = primary.quaternion.clone().multiply(basis.primaryHandOffset);
8384
+ let quaternion;
8385
+ if (options.rotationMode === "primary" || degenerate) {
8386
+ quaternion = base;
8387
+ } else {
8388
+ const axisLocal = anchorDelta.clone().normalize();
8389
+ const currentAxis = axisLocal.clone().applyQuaternion(base).normalize();
8390
+ const desiredAxis = new THREE26__namespace.Vector3().subVectors(secondary.position, primary.position).normalize();
8391
+ const correction = new THREE26__namespace.Quaternion().setFromUnitVectors(
8392
+ currentAxis,
8393
+ desiredAxis
8394
+ );
8395
+ quaternion = correction.multiply(base);
8396
+ }
8397
+ const anchorOffset = basis.primaryLocal.clone().multiply(scale).applyQuaternion(quaternion);
8398
+ const position = primary.position.clone().sub(anchorOffset);
8399
+ return { position, quaternion, scale, degenerate };
8400
+ }
8401
+ __name(solveTwoHandTransform, "solveTwoHandTransform");
8267
8402
 
8268
8403
  // core/systems/XRInteractionSystem.ts
8269
8404
  var RAYLESS_HOLD_DISTANCE = 0.5;
8405
+ function quatFromEulerVec(r) {
8406
+ return new THREE26__namespace.Quaternion().setFromEuler(new THREE26__namespace.Euler(r.x, r.y, r.z));
8407
+ }
8408
+ __name(quatFromEulerVec, "quatFromEulerVec");
8409
+ function writeEuler(target, q) {
8410
+ const euler = new THREE26__namespace.Euler().setFromQuaternion(q);
8411
+ target.x = euler.x;
8412
+ target.y = euler.y;
8413
+ target.z = euler.z;
8414
+ }
8415
+ __name(writeEuler, "writeEuler");
8270
8416
  var _XRInteractionSystem = class _XRInteractionSystem {
8271
8417
  constructor(inputSystem) {
8272
- this.raycaster = new THREE14__namespace.Raycaster();
8418
+ this.raycaster = new THREE26__namespace.Raycaster();
8273
8419
  this.grabs = /* @__PURE__ */ new Map();
8274
8420
  this.hoveredEntityId = null;
8421
+ /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
8422
+ this.hovers = /* @__PURE__ */ new Map();
8275
8423
  /** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
8276
8424
  this.elapsed = 0;
8277
8425
  this.inputSystem = inputSystem;
@@ -8285,6 +8433,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8285
8433
  const inputs = this.inputSystem.getInputs();
8286
8434
  const interactables = this.collectInteractables(world);
8287
8435
  this.hoveredEntityId = null;
8436
+ this.hovers.clear();
8288
8437
  for (const input of inputs) {
8289
8438
  if (!input.connected) {
8290
8439
  this.release(world, input);
@@ -8292,7 +8441,6 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8292
8441
  }
8293
8442
  const held = this.grabs.get(input);
8294
8443
  if (held) {
8295
- this.moveHeld(world, input, held, deltaTime);
8296
8444
  if (this.wantsRelease(world, input, held)) {
8297
8445
  this.release(world, input);
8298
8446
  }
@@ -8303,6 +8451,10 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8303
8451
  if (this.hoveredEntityId === null) {
8304
8452
  this.hoveredEntityId = candidate.entityId;
8305
8453
  }
8454
+ this.hovers.set(input, {
8455
+ entityId: candidate.entityId,
8456
+ distance: candidate.distance
8457
+ });
8306
8458
  const interactable = world.getComponent(
8307
8459
  candidate.entityId,
8308
8460
  XRInteractableComponent
@@ -8313,9 +8465,122 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8313
8465
  /* starting */
8314
8466
  true
8315
8467
  )) {
8316
- this.grab(world, input, candidate.entityId, candidate.viaDistance);
8468
+ this.tryGrab(world, input, candidate.entityId, candidate.viaDistance);
8317
8469
  }
8318
8470
  }
8471
+ this.moveAllHeld(world, deltaTime);
8472
+ }
8473
+ /** Hands currently holding an entity. */
8474
+ holdersOf(entityId) {
8475
+ const holders = [];
8476
+ for (const [input, record] of this.grabs) {
8477
+ if (record.entityId === entityId) holders.push({ input, record });
8478
+ }
8479
+ return holders;
8480
+ }
8481
+ /**
8482
+ * Route a new grab: first hand, hand-off, or second hand.
8483
+ *
8484
+ * Which of those happens is the object's choice (`grabHands`), not the
8485
+ * hand's — a ball should pass between hands, a rifle should be held by both,
8486
+ * and a lever should stay with whoever grabbed it first.
8487
+ */
8488
+ tryGrab(world, input, entityId, viaDistance) {
8489
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8490
+ if (!interactable) return;
8491
+ const holders = this.holdersOf(entityId);
8492
+ if (holders.length === 0) {
8493
+ this.grab(world, input, entityId, viaDistance, "primary");
8494
+ return;
8495
+ }
8496
+ if (holders.some((holder) => holder.input === input)) return;
8497
+ if (interactable.grabHands === "single") return;
8498
+ if (interactable.grabHands === "handOff") {
8499
+ const previous = holders[0];
8500
+ this.grabs.delete(previous.input);
8501
+ const record = previous.record;
8502
+ record.role = "primary";
8503
+ record.twoHand = null;
8504
+ this.reanchor(world, input, record, interactable);
8505
+ this.grabs.set(input, record);
8506
+ this.dispatch(world, entityId, (b) => b.onGrab(input));
8507
+ return;
8508
+ }
8509
+ if (holders.length >= 2) return;
8510
+ this.grab(world, input, entityId, viaDistance, "secondary");
8511
+ this.beginTwoHand(world, entityId);
8512
+ }
8513
+ /**
8514
+ * Re-derive a record's hold from where the object is NOW.
8515
+ *
8516
+ * Used on hand-off and on promotion after the primary lets go. Without it the
8517
+ * object snaps to whatever offset the previous hand had, which reads as the
8518
+ * object jumping out of your grip at the moment you take it.
8519
+ */
8520
+ reanchor(world, input, record, interactable) {
8521
+ const transform = world.getComponent(record.entityId, TransformComponent);
8522
+ if (!transform) return;
8523
+ const hand = this.handPosition(input);
8524
+ const handQuaternion = this.handQuaternion(input);
8525
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
8526
+ record.offset = new THREE26__namespace.Vector3(
8527
+ transform.position.x - hand.x,
8528
+ transform.position.y - hand.y,
8529
+ transform.position.z - hand.z
8530
+ ).applyQuaternion(handQuaternion.clone().invert());
8531
+ record.handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
8532
+ record.remote = false;
8533
+ record.tracker.clear();
8534
+ record.tracker.record(hand, this.elapsed);
8535
+ record.lastHandQuaternion = handQuaternion.clone();
8536
+ if (record.attachLocal && interactable.attachPoints.length === 0) {
8537
+ record.attachLocal = null;
8538
+ record.attachQuaternion = null;
8539
+ record.attachPointId = null;
8540
+ }
8541
+ }
8542
+ /** Capture the two-hand basis once both hands are on the object. */
8543
+ beginTwoHand(world, entityId) {
8544
+ const holders = this.holdersOf(entityId);
8545
+ if (holders.length !== 2) return;
8546
+ const primary = holders.find((h) => h.record.role === "primary");
8547
+ const secondary = holders.find((h) => h.record.role === "secondary");
8548
+ if (!primary || !secondary) return;
8549
+ const transform = world.getComponent(entityId, TransformComponent);
8550
+ if (!transform) return;
8551
+ const primaryHand = this.handPosition(primary.input);
8552
+ const secondaryHand = this.handPosition(secondary.input);
8553
+ const primaryLocal = primary.record.attachLocal?.clone() ?? this.objectLocalPoint(transform, primaryHand);
8554
+ const secondaryLocal = secondary.record.attachLocal?.clone() ?? this.objectLocalPoint(transform, secondaryHand);
8555
+ primary.record.twoHand = {
8556
+ primaryLocal,
8557
+ secondaryLocal,
8558
+ initialScale: new THREE26__namespace.Vector3(
8559
+ transform.scale.x,
8560
+ transform.scale.y,
8561
+ transform.scale.z
8562
+ ),
8563
+ initialSeparation: primaryHand.distanceTo(secondaryHand),
8564
+ // How the object sits relative to the anchoring hand right now, so the
8565
+ // two-hand solve picks up exactly where the one-hand hold left off.
8566
+ primaryHandOffset: this.handQuaternion(primary.input).invert().multiply(quatFromEulerVec(transform.rotation)),
8567
+ initialQuaternion: quatFromEulerVec(transform.rotation)
8568
+ };
8569
+ this.dispatch(world, entityId, (b) => b.onSecondaryGrab(secondary.input));
8570
+ }
8571
+ /** World point → the object's UNSCALED local space. */
8572
+ objectLocalPoint(transform, worldPoint) {
8573
+ const local = worldPoint.clone().sub(
8574
+ new THREE26__namespace.Vector3(
8575
+ transform.position.x,
8576
+ transform.position.y,
8577
+ transform.position.z
8578
+ )
8579
+ ).applyQuaternion(quatFromEulerVec(transform.rotation).invert());
8580
+ local.x /= transform.scale.x || 1;
8581
+ local.y /= transform.scale.y || 1;
8582
+ local.z /= transform.scale.z || 1;
8583
+ return local;
8319
8584
  }
8320
8585
  /** Whether the grab gesture for this object is currently starting. */
8321
8586
  gestureMatches(input, interactable, starting) {
@@ -8364,7 +8629,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8364
8629
  );
8365
8630
  if (!interactable2) continue;
8366
8631
  if (interactable2.grabReach === "distance") continue;
8367
- const position = new THREE14__namespace.Vector3();
8632
+ const position = new THREE26__namespace.Vector3();
8368
8633
  entry.object.getWorldPosition(position);
8369
8634
  const distance = position.distanceTo(hand);
8370
8635
  if (distance <= interactable2.grabRadius && distance < nearestDistance) {
@@ -8373,13 +8638,22 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8373
8638
  }
8374
8639
  }
8375
8640
  if (nearestId !== null) {
8376
- return { entityId: nearestId, viaDistance: false };
8641
+ return {
8642
+ entityId: nearestId,
8643
+ viaDistance: false,
8644
+ distance: nearestDistance
8645
+ };
8377
8646
  }
8378
- const hitId = this.raycast(input, interactables);
8379
- if (hitId === null) return null;
8647
+ const hit = this.raycast(input, interactables);
8648
+ if (hit === null) return null;
8649
+ const hitId = hit.entityId;
8380
8650
  const interactable = world.getComponent(hitId, XRInteractableComponent);
8381
8651
  const viaDistanceAllowed = interactable?.grabReach === "distance" || interactable?.grabReach === "both";
8382
- return { entityId: hitId, viaDistance: viaDistanceAllowed };
8652
+ return {
8653
+ entityId: hitId,
8654
+ viaDistance: viaDistanceAllowed,
8655
+ distance: hit.distance
8656
+ };
8383
8657
  }
8384
8658
  collectInteractables(world) {
8385
8659
  const out = [];
@@ -8410,9 +8684,54 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8410
8684
  nearestId = entry.entityId;
8411
8685
  }
8412
8686
  }
8413
- return nearestId;
8687
+ return nearestId === null ? null : { entityId: nearestId, distance: nearestDistance };
8414
8688
  }
8415
- grab(world, input, entityId, viaDistance) {
8689
+ /**
8690
+ * Pick the authored hold this hand should take.
8691
+ *
8692
+ * Filters by hand and by role — that is what makes "the trigger hand goes on
8693
+ * the grip" hold regardless of which hand reaches first — then takes the
8694
+ * nearest remaining one so grabbing near the foregrip picks the foregrip.
8695
+ */
8696
+ chooseAttachPoint(world, input, entityId, interactable, role) {
8697
+ if (interactable.attachPoints.length === 0) return null;
8698
+ const transform = world.getComponent(entityId, TransformComponent);
8699
+ if (!transform) return null;
8700
+ const taken = new Set(
8701
+ this.holdersOf(entityId).map((holder) => holder.record.attachPointId).filter((id) => id !== null)
8702
+ );
8703
+ const hand = this.handPosition(input);
8704
+ const objectPosition = new THREE26__namespace.Vector3(
8705
+ transform.position.x,
8706
+ transform.position.y,
8707
+ transform.position.z
8708
+ );
8709
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
8710
+ const scale = new THREE26__namespace.Vector3(
8711
+ transform.scale.x,
8712
+ transform.scale.y,
8713
+ transform.scale.z
8714
+ );
8715
+ let best = null;
8716
+ let bestDistance = Number.POSITIVE_INFINITY;
8717
+ for (const point of interactable.attachPoints) {
8718
+ if (taken.has(point.id)) continue;
8719
+ if (point.hand !== "any" && point.hand !== input.handedness) continue;
8720
+ if (point.role !== "any" && point.role !== role) continue;
8721
+ const world3 = new THREE26__namespace.Vector3(
8722
+ point.position.x,
8723
+ point.position.y,
8724
+ point.position.z
8725
+ ).multiply(scale).applyQuaternion(objectQuaternion).add(objectPosition);
8726
+ const distance = world3.distanceTo(hand);
8727
+ if (distance < bestDistance) {
8728
+ bestDistance = distance;
8729
+ best = point;
8730
+ }
8731
+ }
8732
+ return best;
8733
+ }
8734
+ grab(world, input, entityId, viaDistance, role) {
8416
8735
  const transform = world.getComponent(entityId, TransformComponent);
8417
8736
  const interactable = world.getComponent(entityId, XRInteractableComponent);
8418
8737
  if (!transform || !interactable) return;
@@ -8422,25 +8741,25 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8422
8741
  let offset;
8423
8742
  let remoteDistance = 0;
8424
8743
  if (remote) {
8425
- const position = new THREE14__namespace.Vector3(
8744
+ const position = new THREE26__namespace.Vector3(
8426
8745
  transform.position.x,
8427
8746
  transform.position.y,
8428
8747
  transform.position.z
8429
8748
  );
8430
8749
  remoteDistance = position.distanceTo(input.rayOrigin);
8431
- offset = new THREE14__namespace.Vector3();
8750
+ offset = new THREE26__namespace.Vector3();
8432
8751
  } else if (viaDistance) {
8433
- offset = new THREE14__namespace.Vector3(
8752
+ offset = new THREE26__namespace.Vector3(
8434
8753
  interactable.grabOffsetPosition.x,
8435
8754
  interactable.grabOffsetPosition.y,
8436
8755
  interactable.grabOffsetPosition.z
8437
8756
  );
8438
8757
  } else {
8439
- offset = new THREE14__namespace.Vector3(
8758
+ offset = new THREE26__namespace.Vector3(
8440
8759
  transform.position.x - hand.x,
8441
8760
  transform.position.y - hand.y,
8442
8761
  transform.position.z - hand.z
8443
- );
8762
+ ).applyQuaternion(handQuaternion.clone().invert());
8444
8763
  }
8445
8764
  const physics = world.getComponent(entityId, PhysicsComponent);
8446
8765
  let restoreBodyType = null;
@@ -8450,11 +8769,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8450
8769
  }
8451
8770
  const tracker = new ThrowVelocityTracker();
8452
8771
  tracker.record(hand, this.elapsed);
8772
+ const attachPoint = this.chooseAttachPoint(
8773
+ world,
8774
+ input,
8775
+ entityId,
8776
+ interactable,
8777
+ role
8778
+ );
8779
+ const objectQuaternion = quatFromEulerVec(transform.rotation);
8780
+ const handRotationOffset = handQuaternion.clone().invert().multiply(objectQuaternion);
8453
8781
  this.grabs.set(input, {
8454
8782
  entityId,
8783
+ role,
8784
+ attachPointId: attachPoint?.id ?? null,
8785
+ attachLocal: attachPoint ? new THREE26__namespace.Vector3(
8786
+ attachPoint.position.x,
8787
+ attachPoint.position.y,
8788
+ attachPoint.position.z
8789
+ ) : null,
8790
+ attachQuaternion: attachPoint ? quatFromEulerVec(attachPoint.rotation) : null,
8791
+ handRotationOffset,
8792
+ twoHand: null,
8455
8793
  offset,
8456
- rotationOffset: new THREE14__namespace.Quaternion().setFromEuler(
8457
- new THREE14__namespace.Euler(
8794
+ rotationOffset: new THREE26__namespace.Quaternion().setFromEuler(
8795
+ new THREE26__namespace.Euler(
8458
8796
  interactable.grabOffsetRotation.x,
8459
8797
  interactable.grabOffsetRotation.y,
8460
8798
  interactable.grabOffsetRotation.z
@@ -8465,33 +8803,111 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8465
8803
  restoreBodyType,
8466
8804
  tracker,
8467
8805
  lastHandQuaternion: handQuaternion.clone(),
8468
- angularVelocity: new THREE14__namespace.Vector3()
8806
+ angularVelocity: new THREE26__namespace.Vector3()
8469
8807
  });
8470
- this.dispatch(world, entityId, (b) => b.onGrab(input));
8808
+ if (role === "primary") {
8809
+ this.dispatch(world, entityId, (b) => b.onGrab(input));
8810
+ }
8811
+ }
8812
+ /** Drive every held object, once each, after grabs and releases settle. */
8813
+ moveAllHeld(world, deltaTime) {
8814
+ const done = /* @__PURE__ */ new Set();
8815
+ for (const [input, record] of this.grabs) {
8816
+ if (done.has(record.entityId)) continue;
8817
+ done.add(record.entityId);
8818
+ const holders = this.holdersOf(record.entityId);
8819
+ if (holders.length >= 2) {
8820
+ this.moveTwoHand(world, record.entityId, holders, deltaTime);
8821
+ } else {
8822
+ this.moveHeld(world, input, record, deltaTime);
8823
+ }
8824
+ }
8825
+ }
8826
+ /** Solve and apply a two-hand hold. */
8827
+ moveTwoHand(world, entityId, holders, deltaTime) {
8828
+ const primary = holders.find((h) => h.record.role === "primary");
8829
+ const secondary = holders.find((h) => h.record.role === "secondary");
8830
+ const transform = world.getComponent(entityId, TransformComponent);
8831
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
8832
+ const basis = primary?.record.twoHand ?? null;
8833
+ if (!primary || !secondary || !transform || !interactable || !basis) {
8834
+ if (primary) {
8835
+ this.moveHeld(world, primary.input, primary.record, deltaTime);
8836
+ }
8837
+ return;
8838
+ }
8839
+ const primaryHand = this.handPosition(primary.input);
8840
+ const primaryQuaternion = this.handQuaternion(primary.input);
8841
+ const solved = solveTwoHandTransform(
8842
+ { position: primaryHand, quaternion: primaryQuaternion },
8843
+ {
8844
+ position: this.handPosition(secondary.input),
8845
+ quaternion: this.handQuaternion(secondary.input)
8846
+ },
8847
+ basis,
8848
+ {
8849
+ rotationMode: interactable.twoHandRotation,
8850
+ allowScale: interactable.allowTwoHandScale,
8851
+ minScale: interactable.minScale,
8852
+ maxScale: interactable.maxScale
8853
+ }
8854
+ );
8855
+ transform.position.x = solved.position.x;
8856
+ transform.position.y = solved.position.y;
8857
+ transform.position.z = solved.position.z;
8858
+ writeEuler(transform.rotation, solved.quaternion);
8859
+ if (interactable.allowTwoHandScale) {
8860
+ transform.scale.x = solved.scale.x;
8861
+ transform.scale.y = solved.scale.y;
8862
+ transform.scale.z = solved.scale.z;
8863
+ }
8864
+ primary.record.tracker.record(primaryHand, this.elapsed);
8865
+ primary.record.angularVelocity = angularVelocityFrom(
8866
+ primary.record.lastHandQuaternion,
8867
+ primaryQuaternion,
8868
+ deltaTime
8869
+ );
8870
+ primary.record.lastHandQuaternion = primaryQuaternion.clone();
8471
8871
  }
8472
8872
  moveHeld(world, input, held, deltaTime) {
8473
8873
  const transform = world.getComponent(held.entityId, TransformComponent);
8474
8874
  if (!transform) return;
8475
8875
  const hand = this.handPosition(input);
8876
+ const handQuaternion = this.handQuaternion(input);
8877
+ const objectQuaternion = held.attachQuaternion ? handQuaternion.clone().multiply(held.rotationOffset).multiply(held.attachQuaternion.clone().invert()) : handQuaternion.clone().multiply(held.handRotationOffset);
8476
8878
  if (held.remote) {
8477
- const target = new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
8879
+ const target = new THREE26__namespace.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
8478
8880
  transform.position.x = target.x;
8479
8881
  transform.position.y = target.y;
8480
8882
  transform.position.z = target.z;
8883
+ writeEuler(transform.rotation, objectQuaternion);
8884
+ } else if (held.attachLocal) {
8885
+ const anchor = held.attachLocal.clone().multiply(
8886
+ new THREE26__namespace.Vector3(
8887
+ transform.scale.x,
8888
+ transform.scale.y,
8889
+ transform.scale.z
8890
+ )
8891
+ ).applyQuaternion(objectQuaternion);
8892
+ transform.position.x = hand.x - anchor.x;
8893
+ transform.position.y = hand.y - anchor.y;
8894
+ transform.position.z = hand.z - anchor.z;
8895
+ writeEuler(transform.rotation, objectQuaternion);
8481
8896
  } else {
8482
- transform.position.x = hand.x + held.offset.x;
8483
- transform.position.y = hand.y + held.offset.y;
8484
- transform.position.z = hand.z + held.offset.z;
8897
+ const carried = held.offset.clone().applyQuaternion(handQuaternion);
8898
+ transform.position.x = hand.x + carried.x;
8899
+ transform.position.y = hand.y + carried.y;
8900
+ transform.position.z = hand.z + carried.z;
8901
+ writeEuler(transform.rotation, objectQuaternion);
8485
8902
  }
8486
8903
  held.tracker.record(
8487
- held.remote ? new THREE14__namespace.Vector3(
8904
+ held.remote ? new THREE26__namespace.Vector3(
8488
8905
  transform.position.x,
8489
8906
  transform.position.y,
8490
8907
  transform.position.z
8491
8908
  ) : hand,
8492
8909
  this.elapsed
8493
8910
  );
8494
- const handQuaternion = this.handQuaternion(input);
8495
8911
  held.angularVelocity = angularVelocityFrom(
8496
8912
  held.lastHandQuaternion,
8497
8913
  handQuaternion,
@@ -8507,22 +8923,41 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8507
8923
  held.entityId,
8508
8924
  XRInteractableComponent
8509
8925
  );
8926
+ const remaining = this.holdersOf(held.entityId);
8927
+ if (remaining.length > 0 && interactable) {
8928
+ if (held.role === "secondary") {
8929
+ const primary = remaining.find((h) => h.record.role === "primary");
8930
+ if (primary) {
8931
+ primary.record.twoHand = null;
8932
+ this.reanchor(world, primary.input, primary.record, interactable);
8933
+ }
8934
+ this.dispatch(
8935
+ world,
8936
+ held.entityId,
8937
+ (b) => b.onSecondaryRelease(input)
8938
+ );
8939
+ } else {
8940
+ const next = remaining[0];
8941
+ next.record.role = "primary";
8942
+ next.record.twoHand = null;
8943
+ next.record.restoreBodyType = held.restoreBodyType;
8944
+ this.reanchor(world, next.input, next.record, interactable);
8945
+ this.dispatch(world, held.entityId, (b) => b.onRelease(input));
8946
+ }
8947
+ held.tracker.clear();
8948
+ return;
8949
+ }
8510
8950
  const physics = world.getComponent(held.entityId, PhysicsComponent);
8511
8951
  if (held.restoreBodyType !== null && physics) {
8512
8952
  physics.values.rigidbody.type = held.restoreBodyType;
8513
- const body = physics.rigidBody;
8514
8953
  const dynamic = held.restoreBodyType === "dynamic";
8515
- if (body && dynamic && interactable?.throwOnRelease) {
8954
+ if (dynamic && interactable?.throwOnRelease) {
8516
8955
  const velocity = held.tracker.computeVelocity(this.elapsed).multiplyScalar(interactable.throwStrength);
8517
- try {
8518
- body.setLinvel(
8519
- { x: velocity.x, y: velocity.y, z: velocity.z },
8520
- true
8521
- );
8522
- const spin = held.angularVelocity;
8523
- body.setAngvel({ x: spin.x, y: spin.y, z: spin.z }, true);
8524
- } catch {
8525
- }
8956
+ const spin = held.angularVelocity;
8957
+ setPhysicsLiveRef(physics, "pendingVelocity", {
8958
+ linear: { x: velocity.x, y: velocity.y, z: velocity.z },
8959
+ angular: { x: spin.x, y: spin.y, z: spin.z }
8960
+ });
8526
8961
  }
8527
8962
  }
8528
8963
  held.tracker.clear();
@@ -8540,7 +8975,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8540
8975
  */
8541
8976
  handPosition(input) {
8542
8977
  if (input.gripPosition) return input.gripPosition;
8543
- return new THREE14__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
8978
+ return new THREE26__namespace.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
8544
8979
  }
8545
8980
  /**
8546
8981
  * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
@@ -8548,8 +8983,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8548
8983
  */
8549
8984
  handQuaternion(input) {
8550
8985
  if (input.gripQuaternion) return input.gripQuaternion.clone();
8551
- return new THREE14__namespace.Quaternion().setFromUnitVectors(
8552
- new THREE14__namespace.Vector3(0, 0, -1),
8986
+ return new THREE26__namespace.Quaternion().setFromUnitVectors(
8987
+ new THREE26__namespace.Vector3(0, 0, -1),
8553
8988
  input.rayDirection.clone().normalize()
8554
8989
  );
8555
8990
  }
@@ -8570,10 +9005,57 @@ var _XRInteractionSystem = class _XRInteractionSystem {
8570
9005
  getHoveredEntityId() {
8571
9006
  return this.hoveredEntityId;
8572
9007
  }
9008
+ /**
9009
+ * What this hand is pointing at, with the distance to it.
9010
+ *
9011
+ * The distance is what lets the pointer ray stop at the surface instead of
9012
+ * passing through the object.
9013
+ */
9014
+ getHoverFor(handedness) {
9015
+ for (const [input, hover] of this.hovers) {
9016
+ if (input.handedness === handedness) return hover;
9017
+ }
9018
+ return null;
9019
+ }
9020
+ /** Every entity under a pointer this frame — drives hover feedback. */
9021
+ getHoveredEntityIds() {
9022
+ const ids = /* @__PURE__ */ new Set();
9023
+ for (const hover of this.hovers.values()) ids.add(hover.entityId);
9024
+ return ids;
9025
+ }
9026
+ /** Entities currently held, so hover feedback can skip them. */
9027
+ getHeldEntityIds() {
9028
+ const ids = /* @__PURE__ */ new Set();
9029
+ for (const record of this.grabs.values()) ids.add(record.entityId);
9030
+ return ids;
9031
+ }
8573
9032
  /** True when any source is holding something. */
8574
9033
  isGrabbing() {
8575
9034
  return this.grabs.size > 0;
8576
9035
  }
9036
+ /**
9037
+ * How an entity is currently held.
9038
+ *
9039
+ * Exposed for scripts: a Behaviour can poll this in `onUpdate` to drive
9040
+ * two-hand-specific behaviour — showing a bipod when both hands are on a
9041
+ * weapon, reading the live hand separation to gate a gesture, or reacting to
9042
+ * the scale a pull-apart produced. Returns null when nothing holds it.
9043
+ */
9044
+ getGrabInfo(entityId) {
9045
+ const holders = this.holdersOf(entityId);
9046
+ if (holders.length === 0) return null;
9047
+ const primary = holders.find((h) => h.record.role === "primary");
9048
+ const secondary = holders.find((h) => h.record.role === "secondary");
9049
+ return {
9050
+ hands: holders.length,
9051
+ primaryHandedness: primary?.input.handedness ?? null,
9052
+ secondaryHandedness: secondary?.input.handedness ?? null,
9053
+ handSeparation: primary && secondary ? this.handPosition(primary.input).distanceTo(
9054
+ this.handPosition(secondary.input)
9055
+ ) : null,
9056
+ attachPointIds: holders.map((h) => h.record.attachPointId)
9057
+ };
9058
+ }
8577
9059
  };
8578
9060
  __name(_XRInteractionSystem, "XRInteractionSystem");
8579
9061
  var XRInteractionSystem = _XRInteractionSystem;
@@ -8591,7 +9073,7 @@ function sampleTeleportArc(origin, direction, out = [], options) {
8591
9073
  for (let i = 0; i < steps; i += 1) {
8592
9074
  const t = i / (steps - 1) * maxTime;
8593
9075
  out.push(
8594
- new THREE14__namespace.Vector3(
9076
+ new THREE26__namespace.Vector3(
8595
9077
  origin.x + velocity.x * t,
8596
9078
  origin.y + velocity.y * t - 0.5 * gravity * t * t,
8597
9079
  origin.z + velocity.z * t
@@ -8603,11 +9085,11 @@ function sampleTeleportArc(origin, direction, out = [], options) {
8603
9085
  __name(sampleTeleportArc, "sampleTeleportArc");
8604
9086
 
8605
9087
  // core/systems/XRLocomotionSystem.ts
8606
- var UP = new THREE14__namespace.Vector3(0, 1, 0);
9088
+ var UP = new THREE26__namespace.Vector3(0, 1, 0);
8607
9089
  var AXIS_DEADZONE = 0.5;
8608
9090
  var _XRLocomotionSystem = class _XRLocomotionSystem {
8609
9091
  constructor(inputSystem) {
8610
- this.raycaster = new THREE14__namespace.Raycaster();
9092
+ this.raycaster = new THREE26__namespace.Raycaster();
8611
9093
  /** Per-source latch so one stick push yields exactly one snap turn. */
8612
9094
  this.turnLatched = /* @__PURE__ */ new WeakSet();
8613
9095
  /** Reused arc buffer — aiming runs every frame. */
@@ -8716,7 +9198,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
8716
9198
  }
8717
9199
  if (this.turnLatched.has(source)) continue;
8718
9200
  this.turnLatched.add(source);
8719
- transform.rotation.y -= Math.sign(x) * THREE14__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
9201
+ transform.rotation.y -= Math.sign(x) * THREE26__namespace.MathUtils.degToRad(rig.snapTurnDegrees);
8720
9202
  }
8721
9203
  }
8722
9204
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -8726,12 +9208,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
8726
9208
  const y = input.axes[1] ?? 0;
8727
9209
  if (Math.abs(x) < 0.15 && Math.abs(y) < 0.15) continue;
8728
9210
  const camera = world.getCamera();
8729
- const forward = new THREE14__namespace.Vector3();
9211
+ const forward = new THREE26__namespace.Vector3();
8730
9212
  camera.getWorldDirection(forward);
8731
9213
  forward.y = 0;
8732
9214
  if (forward.lengthSq() < 1e-6) continue;
8733
9215
  forward.normalize();
8734
- const right = new THREE14__namespace.Vector3().crossVectors(forward, UP).normalize();
9216
+ const right = new THREE26__namespace.Vector3().crossVectors(forward, UP).normalize();
8735
9217
  const step = rig.moveSpeed * Math.max(0, deltaTime);
8736
9218
  transform.position.x += (forward.x * -y + right.x * x) * step;
8737
9219
  transform.position.z += (forward.z * -y + right.z * x) * step;
@@ -8796,7 +9278,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
8796
9278
  for (let i = 1; i < points.length; i += 1) {
8797
9279
  const from = points[i - 1];
8798
9280
  const to = points[i];
8799
- const delta = new THREE14__namespace.Vector3().subVectors(to, from);
9281
+ const delta = new THREE26__namespace.Vector3().subVectors(to, from);
8800
9282
  const distance = delta.length();
8801
9283
  if (distance < 1e-6) continue;
8802
9284
  this.raycaster.set(from, delta.divideScalar(distance));
@@ -9004,9 +9486,9 @@ var XRSessionSystem = _XRSessionSystem;
9004
9486
  function buildGeometry2(area) {
9005
9487
  if (area.shape === "circle") {
9006
9488
  const radius = Math.max(5e-3, area.width / 2);
9007
- return new THREE14__namespace.CircleGeometry(radius, 48);
9489
+ return new THREE26__namespace.CircleGeometry(radius, 48);
9008
9490
  }
9009
- return new THREE14__namespace.PlaneGeometry(
9491
+ return new THREE26__namespace.PlaneGeometry(
9010
9492
  Math.max(0.01, area.width),
9011
9493
  Math.max(0.01, area.depth)
9012
9494
  );
@@ -9029,7 +9511,7 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
9029
9511
  for (const entityId of entityIds) {
9030
9512
  const area = world.getComponent(entityId, VRTeleportAreaComponent);
9031
9513
  const object = world.getComponent(entityId, Object3DRef)?.object;
9032
- if (!area || !(object instanceof THREE14__namespace.Mesh)) continue;
9514
+ if (!area || !(object instanceof THREE26__namespace.Mesh)) continue;
9033
9515
  const signature = signatureOf(area);
9034
9516
  if (this.signatures.get(entityId) === signature) continue;
9035
9517
  this.signatures.set(entityId, signature);
@@ -9041,18 +9523,18 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
9041
9523
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
9042
9524
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
9043
9525
  function createHandMaterial(preset, options) {
9044
- const color = new THREE14__namespace.Color(options.color);
9526
+ const color = new THREE26__namespace.Color(options.color);
9045
9527
  switch (preset) {
9046
9528
  case "ghost":
9047
- return new THREE14__namespace.MeshBasicMaterial({
9529
+ return new THREE26__namespace.MeshBasicMaterial({
9048
9530
  color,
9049
9531
  transparent: true,
9050
9532
  opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
9051
9533
  depthWrite: false,
9052
- side: THREE14__namespace.DoubleSide
9534
+ side: THREE26__namespace.DoubleSide
9053
9535
  });
9054
9536
  case "neon":
9055
- return new THREE14__namespace.MeshStandardMaterial({
9537
+ return new THREE26__namespace.MeshStandardMaterial({
9056
9538
  color,
9057
9539
  emissive: color,
9058
9540
  emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
@@ -9062,7 +9544,7 @@ function createHandMaterial(preset, options) {
9062
9544
  opacity: options.opacity
9063
9545
  });
9064
9546
  case "skeleton":
9065
- return new THREE14__namespace.MeshBasicMaterial({
9547
+ return new THREE26__namespace.MeshBasicMaterial({
9066
9548
  color,
9067
9549
  wireframe: true,
9068
9550
  transparent: true,
@@ -9070,7 +9552,7 @@ function createHandMaterial(preset, options) {
9070
9552
  });
9071
9553
  case "custom":
9072
9554
  case "default":
9073
- return new THREE14__namespace.MeshStandardMaterial({
9555
+ return new THREE26__namespace.MeshStandardMaterial({
9074
9556
  color,
9075
9557
  roughness: 0.85,
9076
9558
  metalness: 0,
@@ -9078,10 +9560,10 @@ function createHandMaterial(preset, options) {
9078
9560
  emissiveIntensity: Math.max(0, options.emissiveIntensity),
9079
9561
  transparent: options.opacity < 1,
9080
9562
  opacity: options.opacity,
9081
- side: THREE14__namespace.DoubleSide
9563
+ side: THREE26__namespace.DoubleSide
9082
9564
  });
9083
9565
  default: {
9084
- return new THREE14__namespace.MeshStandardMaterial({ color });
9566
+ return new THREE26__namespace.MeshStandardMaterial({ color });
9085
9567
  }
9086
9568
  }
9087
9569
  }
@@ -9094,6 +9576,9 @@ function applyHandMaterial(root, material) {
9094
9576
  });
9095
9577
  }
9096
9578
  __name(applyHandMaterial, "applyHandMaterial");
9579
+
9580
+ // core/systems/XRControllerVisualSystem.ts
9581
+ var RAY_LENGTH = 5;
9097
9582
  var SLOTS = [
9098
9583
  { index: 0, side: "left" },
9099
9584
  { index: 1, side: "right" }
@@ -9102,7 +9587,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
9102
9587
  constructor(options) {
9103
9588
  this.controllerModelFactory = new XRControllerModelFactory_js.XRControllerModelFactory();
9104
9589
  this.handModelFactory = new XRHandModelFactory_js.XRHandModelFactory();
9105
- this.group = new THREE14__namespace.Group();
9590
+ this.group = new THREE26__namespace.Group();
9106
9591
  this.slots = /* @__PURE__ */ new Map();
9107
9592
  this.attachedTo = null;
9108
9593
  /** Last applied preset signature, so materials rebuild only on real change. */
@@ -9132,6 +9617,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
9132
9617
  visuals.controllerModel.visible = showControllers && hasController;
9133
9618
  visuals.handModel.visible = showHands && hasHand;
9134
9619
  visuals.ray.visible = this.shouldShowRay(interactor);
9620
+ this.fitRayToHover(visuals.ray, side);
9135
9621
  if (showHands && this.handMaterial) {
9136
9622
  applyHandMaterial(visuals.handModel, this.handMaterial);
9137
9623
  }
@@ -9212,6 +9698,18 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
9212
9698
  if (!this.interactionSystem) return true;
9213
9699
  return this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
9214
9700
  }
9701
+ /**
9702
+ * Stop the ray at whatever it is pointing at.
9703
+ *
9704
+ * A fixed-length ray drawn with depth testing off punches straight through the
9705
+ * object, so the user cannot tell where the pointer actually lands. Scaling it
9706
+ * to the hit distance turns it into a pointer that meets the surface.
9707
+ */
9708
+ fitRayToHover(ray, side) {
9709
+ const hover = this.interactionSystem?.getHoverFor(side) ?? null;
9710
+ const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
9711
+ ray.scale.z = length / RAY_LENGTH;
9712
+ }
9215
9713
  /** Lazily build per-slot visuals; the device reports profiles late. */
9216
9714
  ensureSlots(world) {
9217
9715
  const renderer = world.getRenderer();
@@ -9251,17 +9749,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
9251
9749
  this.attachedTo = target;
9252
9750
  }
9253
9751
  createRay() {
9254
- const geometry = new THREE14__namespace.BufferGeometry().setFromPoints([
9255
- new THREE14__namespace.Vector3(0, 0, 0),
9256
- new THREE14__namespace.Vector3(0, 0, -5)
9752
+ const geometry = new THREE26__namespace.BufferGeometry().setFromPoints([
9753
+ new THREE26__namespace.Vector3(0, 0, 0),
9754
+ new THREE26__namespace.Vector3(0, 0, -RAY_LENGTH)
9257
9755
  ]);
9258
- const material = new THREE14__namespace.LineBasicMaterial({
9756
+ const material = new THREE26__namespace.LineBasicMaterial({
9259
9757
  color: "#4da3ff",
9260
9758
  transparent: true,
9261
9759
  opacity: 0.6,
9262
9760
  depthTest: false
9263
9761
  });
9264
- const line = new THREE14__namespace.Line(geometry, material);
9762
+ const line = new THREE26__namespace.Line(geometry, material);
9265
9763
  line.name = "XRPointerRay";
9266
9764
  line.renderOrder = 999;
9267
9765
  line.visible = false;
@@ -9290,23 +9788,23 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
9290
9788
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
9291
9789
  var XRControllerVisualSystem = _XRControllerVisualSystem;
9292
9790
  var MARKER_RADIUS = 0.28;
9293
- var ACCENT = new THREE14__namespace.Color("#4da3ff");
9294
- var BLOCKED = new THREE14__namespace.Color("#ff5a5a");
9791
+ var ACCENT = new THREE26__namespace.Color("#4da3ff");
9792
+ var BLOCKED = new THREE26__namespace.Color("#ff5a5a");
9295
9793
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
9296
9794
  constructor(locomotionSystem) {
9297
- this.group = new THREE14__namespace.Group();
9298
- this.arcGeometry = new THREE14__namespace.BufferGeometry();
9299
- this.marker = new THREE14__namespace.Group();
9795
+ this.group = new THREE26__namespace.Group();
9796
+ this.arcGeometry = new THREE26__namespace.BufferGeometry();
9797
+ this.marker = new THREE26__namespace.Group();
9300
9798
  this.attached = false;
9301
9799
  this.locomotionSystem = locomotionSystem;
9302
9800
  this.group.name = "XRTeleportVisuals";
9303
9801
  this.group.userData.ecsPreviewHidden = true;
9304
- const arcMaterial = new THREE14__namespace.LineBasicMaterial({
9802
+ const arcMaterial = new THREE26__namespace.LineBasicMaterial({
9305
9803
  vertexColors: true,
9306
9804
  transparent: true,
9307
9805
  depthTest: false
9308
9806
  });
9309
- this.arc = new THREE14__namespace.Line(this.arcGeometry, arcMaterial);
9807
+ this.arc = new THREE26__namespace.Line(this.arcGeometry, arcMaterial);
9310
9808
  this.arc.frustumCulled = false;
9311
9809
  this.arc.renderOrder = 998;
9312
9810
  this.group.add(this.arc);
@@ -9354,39 +9852,39 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
9354
9852
  }
9355
9853
  this.arcGeometry.setAttribute(
9356
9854
  "position",
9357
- new THREE14__namespace.BufferAttribute(positions, 3)
9855
+ new THREE26__namespace.BufferAttribute(positions, 3)
9358
9856
  );
9359
- this.arcGeometry.setAttribute("color", new THREE14__namespace.BufferAttribute(colors, 3));
9857
+ this.arcGeometry.setAttribute("color", new THREE26__namespace.BufferAttribute(colors, 3));
9360
9858
  this.arcGeometry.computeBoundingSphere();
9361
9859
  }
9362
9860
  createRing() {
9363
- const geometry = new THREE14__namespace.RingGeometry(
9861
+ const geometry = new THREE26__namespace.RingGeometry(
9364
9862
  MARKER_RADIUS * 0.72,
9365
9863
  MARKER_RADIUS,
9366
9864
  48
9367
9865
  );
9368
9866
  geometry.rotateX(-Math.PI / 2);
9369
- return new THREE14__namespace.Mesh(
9867
+ return new THREE26__namespace.Mesh(
9370
9868
  geometry,
9371
- new THREE14__namespace.MeshBasicMaterial({
9869
+ new THREE26__namespace.MeshBasicMaterial({
9372
9870
  color: ACCENT,
9373
9871
  transparent: true,
9374
9872
  opacity: 0.9,
9375
- side: THREE14__namespace.DoubleSide,
9873
+ side: THREE26__namespace.DoubleSide,
9376
9874
  depthTest: false
9377
9875
  })
9378
9876
  );
9379
9877
  }
9380
9878
  createDot() {
9381
- const geometry = new THREE14__namespace.CircleGeometry(MARKER_RADIUS * 0.16, 24);
9879
+ const geometry = new THREE26__namespace.CircleGeometry(MARKER_RADIUS * 0.16, 24);
9382
9880
  geometry.rotateX(-Math.PI / 2);
9383
- return new THREE14__namespace.Mesh(
9881
+ return new THREE26__namespace.Mesh(
9384
9882
  geometry,
9385
- new THREE14__namespace.MeshBasicMaterial({
9883
+ new THREE26__namespace.MeshBasicMaterial({
9386
9884
  color: ACCENT,
9387
9885
  transparent: true,
9388
9886
  opacity: 0.55,
9389
- side: THREE14__namespace.DoubleSide,
9887
+ side: THREE26__namespace.DoubleSide,
9390
9888
  depthTest: false
9391
9889
  })
9392
9890
  );
@@ -9488,7 +9986,7 @@ function boneDirections(joints, finger) {
9488
9986
  const from = joints[chain[i - 1]];
9489
9987
  const to = joints[chain[i]];
9490
9988
  if (!from || !to) return null;
9491
- const direction = new THREE14__namespace.Vector3().subVectors(to, from);
9989
+ const direction = new THREE26__namespace.Vector3().subVectors(to, from);
9492
9990
  if (direction.lengthSq() < 1e-12) return null;
9493
9991
  directions.push(direction.normalize());
9494
9992
  }
@@ -9561,8 +10059,8 @@ function computeSpread(joints, a, b) {
9561
10059
  const bBase = joints[METACARPAL_JOINTS[b]];
9562
10060
  const bTip = joints[TIP_JOINTS[b]];
9563
10061
  if (!aBase || !aTip || !bBase || !bTip) return null;
9564
- const dirA = new THREE14__namespace.Vector3().subVectors(aTip, aBase);
9565
- const dirB = new THREE14__namespace.Vector3().subVectors(bTip, bBase);
10062
+ const dirA = new THREE26__namespace.Vector3().subVectors(aTip, aBase);
10063
+ const dirB = new THREE26__namespace.Vector3().subVectors(bTip, bBase);
9566
10064
  if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
9567
10065
  return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
9568
10066
  }
@@ -9572,18 +10070,18 @@ function computePalmPose(joints, handedness) {
9572
10070
  const indexBase = joints["index-finger-metacarpal"];
9573
10071
  const pinkyBase = joints["pinky-finger-metacarpal"];
9574
10072
  if (!wrist || !indexBase || !pinkyBase) return null;
9575
- const u = new THREE14__namespace.Vector3().subVectors(indexBase, wrist);
9576
- const v = new THREE14__namespace.Vector3().subVectors(pinkyBase, wrist);
9577
- const normal = new THREE14__namespace.Vector3().crossVectors(u, v);
10073
+ const u = new THREE26__namespace.Vector3().subVectors(indexBase, wrist);
10074
+ const v = new THREE26__namespace.Vector3().subVectors(pinkyBase, wrist);
10075
+ const normal = new THREE26__namespace.Vector3().crossVectors(u, v);
9578
10076
  if (normal.lengthSq() < 1e-12) return null;
9579
10077
  normal.normalize();
9580
10078
  if (handedness === "left") normal.multiplyScalar(-1);
9581
- const right = new THREE14__namespace.Vector3().subVectors(indexBase, pinkyBase);
10079
+ const right = new THREE26__namespace.Vector3().subVectors(indexBase, pinkyBase);
9582
10080
  if (right.lengthSq() < 1e-12) return null;
9583
10081
  right.normalize();
9584
10082
  if (handedness === "left") right.multiplyScalar(-1);
9585
- const up = new THREE14__namespace.Vector3().crossVectors(normal, right).normalize();
9586
- const center = new THREE14__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
10083
+ const up = new THREE26__namespace.Vector3().crossVectors(normal, right).normalize();
10084
+ const center = new THREE26__namespace.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
9587
10085
  return {
9588
10086
  center,
9589
10087
  normal,
@@ -9614,7 +10112,7 @@ function computeHandShape(joints, handedness) {
9614
10112
  const thumbTip = joints["thumb-tip"];
9615
10113
  let thumbDirection = null;
9616
10114
  if (thumbBase && thumbTip) {
9617
- const direction = new THREE14__namespace.Vector3().subVectors(thumbTip, thumbBase);
10115
+ const direction = new THREE26__namespace.Vector3().subVectors(thumbTip, thumbBase);
9618
10116
  if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
9619
10117
  }
9620
10118
  return {
@@ -9646,12 +10144,12 @@ function scoreAgainst(actual, target, tolerance) {
9646
10144
  }
9647
10145
  __name(scoreAgainst, "scoreAgainst");
9648
10146
  var WORLD_DIRECTIONS = {
9649
- up: new THREE14__namespace.Vector3(0, 1, 0),
9650
- down: new THREE14__namespace.Vector3(0, -1, 0),
9651
- forward: new THREE14__namespace.Vector3(0, 0, -1),
9652
- back: new THREE14__namespace.Vector3(0, 0, 1),
9653
- left: new THREE14__namespace.Vector3(-1, 0, 0),
9654
- right: new THREE14__namespace.Vector3(1, 0, 0)
10147
+ up: new THREE26__namespace.Vector3(0, 1, 0),
10148
+ down: new THREE26__namespace.Vector3(0, -1, 0),
10149
+ forward: new THREE26__namespace.Vector3(0, 0, -1),
10150
+ back: new THREE26__namespace.Vector3(0, 0, 1),
10151
+ left: new THREE26__namespace.Vector3(-1, 0, 0),
10152
+ right: new THREE26__namespace.Vector3(1, 0, 0)
9655
10153
  };
9656
10154
  function axisVector(shape, axis) {
9657
10155
  if (axis === "thumb") return shape.thumbDirection;
@@ -9666,7 +10164,7 @@ function resolveDirection(direction, shape, context) {
9666
10164
  const head = context?.headPosition;
9667
10165
  const origin = shape.palm?.center;
9668
10166
  if (!head || !origin) return null;
9669
- const toHead = new THREE14__namespace.Vector3().subVectors(head, origin);
10167
+ const toHead = new THREE26__namespace.Vector3().subVectors(head, origin);
9670
10168
  if (toHead.lengthSq() < 1e-12) return null;
9671
10169
  toHead.normalize();
9672
10170
  return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
@@ -9716,8 +10214,8 @@ function matchHandPose(shape, definition, context) {
9716
10214
  if (!handAxis || !wanted) {
9717
10215
  consider(0, "orientation");
9718
10216
  } else {
9719
- const angle = THREE14__namespace.MathUtils.radToDeg(
9720
- Math.acos(THREE14__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
10217
+ const angle = THREE26__namespace.MathUtils.radToDeg(
10218
+ Math.acos(THREE26__namespace.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
9721
10219
  );
9722
10220
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
9723
10221
  consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
@@ -9795,7 +10293,7 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9795
10293
  this.trackers = /* @__PURE__ */ new Map();
9796
10294
  this.shapes = /* @__PURE__ */ new Map();
9797
10295
  /** Reused so head-relative orientation checks allocate nothing per frame. */
9798
- this.headPosition = new THREE14__namespace.Vector3();
10296
+ this.headPosition = new THREE26__namespace.Vector3();
9799
10297
  this.elapsed = 0;
9800
10298
  this.inputSystem = inputSystem;
9801
10299
  }
@@ -9916,6 +10414,152 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9916
10414
  };
9917
10415
  __name(_XRHandPoseSystem, "XRHandPoseSystem");
9918
10416
  var XRHandPoseSystem = _XRHandPoseSystem;
10417
+ var XR_HOVER_SHELL_FLAG = "__xrHoverShell";
10418
+ var SHELL_INFLATE = 1.04;
10419
+ var SCALE_SMOOTH_SECONDS = 0.09;
10420
+ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
10421
+ constructor(interactionSystem) {
10422
+ this.records = /* @__PURE__ */ new Map();
10423
+ this.interactionSystem = interactionSystem;
10424
+ }
10425
+ update(world, deltaTime) {
10426
+ if (!world.isXrPresenting()) {
10427
+ this.clearAll(world);
10428
+ return;
10429
+ }
10430
+ const hovered = this.interactionSystem.getHoveredEntityIds();
10431
+ const held = this.interactionSystem.getHeldEntityIds();
10432
+ for (const entityId of hovered) {
10433
+ if (held.has(entityId)) continue;
10434
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
10435
+ if (!interactable?.enabled) continue;
10436
+ if (interactable.hoverBehavior === "none") continue;
10437
+ this.begin(world, entityId, interactable);
10438
+ }
10439
+ for (const [entityId, record] of this.records) {
10440
+ if (!hovered.has(entityId) || held.has(entityId)) {
10441
+ record.active = false;
10442
+ }
10443
+ }
10444
+ this.animate(world, deltaTime);
10445
+ }
10446
+ begin(world, entityId, interactable) {
10447
+ const existing = this.records.get(entityId);
10448
+ if (existing) {
10449
+ existing.active = true;
10450
+ return;
10451
+ }
10452
+ const transform = world.getComponent(entityId, TransformComponent);
10453
+ if (!transform) return;
10454
+ const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
10455
+ this.records.set(entityId, {
10456
+ shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
10457
+ baseScale: new THREE26__namespace.Vector3(
10458
+ transform.scale.x,
10459
+ transform.scale.y,
10460
+ transform.scale.z
10461
+ ),
10462
+ progress: 0,
10463
+ active: true
10464
+ });
10465
+ }
10466
+ /** Build an inflated back-face copy of the object's meshes. */
10467
+ addShell(world, entityId, interactable) {
10468
+ const object = world.getComponent(entityId, Object3DRef)?.object;
10469
+ if (!object) return null;
10470
+ const material = new THREE26__namespace.MeshBasicMaterial({
10471
+ color: new THREE26__namespace.Color(interactable.hoverColor),
10472
+ // Back faces only, so the shell shows just outside the silhouette instead
10473
+ // of covering the object.
10474
+ side: THREE26__namespace.BackSide,
10475
+ transparent: true,
10476
+ opacity: 0.85,
10477
+ depthWrite: false
10478
+ });
10479
+ const group = new THREE26__namespace.Group();
10480
+ group.name = "XRHoverShell";
10481
+ group.userData[XR_HOVER_SHELL_FLAG] = true;
10482
+ group.renderOrder = 998;
10483
+ object.traverse((child) => {
10484
+ if (!(child instanceof THREE26__namespace.Mesh) || !child.geometry) return;
10485
+ if (child.userData[XR_HOVER_SHELL_FLAG]) return;
10486
+ const clone = new THREE26__namespace.Mesh(child.geometry, material);
10487
+ child.updateWorldMatrix(true, false);
10488
+ object.updateWorldMatrix(true, false);
10489
+ clone.matrixAutoUpdate = false;
10490
+ clone.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE26__namespace.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
10491
+ clone.userData[XR_HOVER_SHELL_FLAG] = true;
10492
+ clone.raycast = () => {
10493
+ };
10494
+ group.add(clone);
10495
+ });
10496
+ if (group.children.length === 0) {
10497
+ material.dispose();
10498
+ return null;
10499
+ }
10500
+ object.add(group);
10501
+ return group;
10502
+ }
10503
+ animate(world, deltaTime) {
10504
+ const step = 1 - Math.exp(-Math.max(0, deltaTime) / SCALE_SMOOTH_SECONDS);
10505
+ for (const [entityId, record] of Array.from(this.records)) {
10506
+ const interactable = world.getComponent(entityId, XRInteractableComponent);
10507
+ const target = record.active ? 1 : 0;
10508
+ record.progress += (target - record.progress) * step;
10509
+ const wantsScale = interactable?.hoverBehavior === "scale" || interactable?.hoverBehavior === "both";
10510
+ if (wantsScale) {
10511
+ const transform = world.getComponent(entityId, TransformComponent);
10512
+ if (transform) {
10513
+ const factor = 1 + (interactable.hoverScale - 1) * Math.min(1, record.progress);
10514
+ transform.scale.x = record.baseScale.x * factor;
10515
+ transform.scale.y = record.baseScale.y * factor;
10516
+ transform.scale.z = record.baseScale.z * factor;
10517
+ }
10518
+ }
10519
+ if (!record.active && record.progress < 0.01) {
10520
+ this.finish(world, entityId, record);
10521
+ }
10522
+ }
10523
+ }
10524
+ /** Remove the effect and restore the authored scale exactly. */
10525
+ finish(world, entityId, record) {
10526
+ const transform = world.getComponent(entityId, TransformComponent);
10527
+ if (transform) {
10528
+ transform.scale.x = record.baseScale.x;
10529
+ transform.scale.y = record.baseScale.y;
10530
+ transform.scale.z = record.baseScale.z;
10531
+ }
10532
+ this.disposeShell(record.shell);
10533
+ this.records.delete(entityId);
10534
+ }
10535
+ disposeShell(shell) {
10536
+ if (!shell) return;
10537
+ shell.parent?.remove(shell);
10538
+ shell.traverse((child) => {
10539
+ if (child instanceof THREE26__namespace.Mesh) {
10540
+ const material = child.material;
10541
+ if (Array.isArray(material)) {
10542
+ for (const entry of material) entry.dispose();
10543
+ } else {
10544
+ material?.dispose();
10545
+ }
10546
+ }
10547
+ });
10548
+ }
10549
+ clearAll(world) {
10550
+ for (const [entityId, record] of Array.from(this.records)) {
10551
+ this.finish(world, entityId, record);
10552
+ }
10553
+ }
10554
+ dispose() {
10555
+ for (const record of this.records.values()) {
10556
+ this.disposeShell(record.shell);
10557
+ }
10558
+ this.records.clear();
10559
+ }
10560
+ };
10561
+ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
10562
+ var XRHoverVisualSystem = _XRHoverVisualSystem;
9919
10563
 
9920
10564
  exports.AnimationSystem = AnimationSystem;
9921
10565
  exports.AudioSystem = AudioSystem;
@@ -9940,10 +10584,12 @@ exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
9940
10584
  exports.VideoSystem = VideoSystem;
9941
10585
  exports.XRControllerVisualSystem = XRControllerVisualSystem;
9942
10586
  exports.XRHandPoseSystem = XRHandPoseSystem;
10587
+ exports.XRHoverVisualSystem = XRHoverVisualSystem;
9943
10588
  exports.XRInputSystem = XRInputSystem;
9944
10589
  exports.XRInteractionSystem = XRInteractionSystem;
9945
10590
  exports.XRLocomotionSystem = XRLocomotionSystem;
9946
10591
  exports.XRSessionSystem = XRSessionSystem;
9947
10592
  exports.XRTeleportVisualSystem = XRTeleportVisualSystem;
10593
+ exports.XR_HOVER_SHELL_FLAG = XR_HOVER_SHELL_FLAG;
9948
10594
  //# sourceMappingURL=index.cjs.map
9949
10595
  //# sourceMappingURL=index.cjs.map