@react-three/drei 11.0.0-alpha.3 → 11.0.0-alpha.5

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.
package/core/index.mjs CHANGED
@@ -2,11 +2,10 @@ import * as React from 'react';
2
2
  import { forwardRef, useImperativeHandle, useMemo, useEffect, useCallback, useRef, useLayoutEffect, useState, Fragment, createContext, useContext } from 'react';
3
3
  import { useThree, useFrame, extend, createPortal, useInstanceHandle, context as context$9, useLoader, applyProps, addEffect, addAfterEffect } from '@react-three/fiber';
4
4
  import * as THREE from 'three';
5
- import { REVISION, WebGLRenderTarget, Frustum, Matrix4, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, Quaternion, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, Object3D, CanvasTexture, DoubleSide, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DepthTexture, DepthFormat, UnsignedShortType, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, Camera, CubeReflectionMapping, EquirectangularReflectionMapping, Scene, RepeatWrapping } from 'three';
5
+ import { REVISION, WebGLRenderTarget, Frustum, Matrix4, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, Object3D, CanvasTexture, DoubleSide, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DepthTexture, DepthFormat, UnsignedShortType, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, Camera, CubeReflectionMapping, EquirectangularReflectionMapping, Scene, RepeatWrapping } from 'three';
6
6
  import { ArcballControls as ArcballControls$1 } from 'three/examples/jsm/controls/ArcballControls.js';
7
- import { DeviceOrientationControls as DeviceOrientationControls$1 } from 'three-stdlib';
8
7
  import { useGesture } from '@use-gesture/react';
9
- import { FirstPersonControls as FirstPersonControls$1 } from 'three/examples/jsm/controls/FirstPersonControls';
8
+ import { FirstPersonControls as FirstPersonControls$1 } from 'three/examples/jsm/controls/FirstPersonControls.js';
10
9
  import { FlyControls as FlyControls$1 } from 'three/examples/jsm/controls/FlyControls.js';
11
10
  import { create as create$1 } from 'zustand';
12
11
  import { subscribeWithSelector } from 'zustand/middleware';
@@ -22,35 +21,35 @@ import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js';
22
21
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
23
22
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
24
23
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
25
- import { Flow } from 'three/examples/jsm/modifiers/CurveModifier';
24
+ import { Flow } from 'three/examples/jsm/modifiers/CurveModifier.js';
26
25
  import { DecalGeometry } from 'three/examples/jsm/geometries/DecalGeometry.js';
27
- import { toCreasedNormals, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils';
28
- import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
29
- import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
26
+ import { toCreasedNormals, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
27
+ import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
28
+ import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
30
29
  import { suspend, preload, clear as clear$1 } from 'suspend-react';
31
- import { TransformControls as TransformControls$1 } from 'three/examples/jsm/controls/TransformControls';
32
- import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';
30
+ import { TransformControls as TransformControls$1 } from 'three/examples/jsm/controls/TransformControls.js';
31
+ import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
33
32
  import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
34
- import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader';
33
+ import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js';
35
34
  import { Events } from 'hls.js';
36
- import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
37
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
38
- import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
35
+ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
36
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
37
+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
39
38
  import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
40
- import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader';
39
+ import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
41
40
  import { getGPUTier } from 'detect-gpu';
42
41
  import StatsImpl from 'stats.js';
43
42
  import Stats$1 from 'stats-gl';
44
43
  import tunnel from 'tunnel-rat';
45
- import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox';
46
- import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
47
- import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader';
44
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
45
+ import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
46
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
48
47
  import { HDRJPGLoader, GainMapLoader } from '@monogrid/gainmap-js';
49
48
  import { Sky as Sky$1 } from 'three/examples/jsm/objects/Sky.js';
50
49
  import { SkyMesh } from 'three/examples/jsm/objects/SkyMesh.js';
51
- import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader';
52
- import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader';
53
- import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox';
50
+ import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
51
+ import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
52
+ import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox.js';
54
53
  import { shallow } from 'zustand/shallow';
55
54
 
56
55
  const ContactShadows = /* @__PURE__ */ React.forwardRef(
@@ -180,6 +179,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
180
179
  });
181
180
  }
182
181
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
182
+ Object.assign(this, parameters);
183
183
  }
184
184
  };
185
185
  }
@@ -196,7 +196,7 @@ const version = /* @__PURE__ */ getVersion();
196
196
  function isLight(object) {
197
197
  return object.isLight;
198
198
  }
199
- function isGeometry$1(object) {
199
+ function isGeometry(object) {
200
200
  return !!object.geometry;
201
201
  }
202
202
  const accumulativeContext = /* @__PURE__ */ React.createContext(
@@ -441,7 +441,7 @@ vec3 texelOld = texture2D(previousShadowMap, vUv).rgb;
441
441
  this.lights = [];
442
442
  this.meshes = [];
443
443
  this.scene.traverse((object) => {
444
- if (isGeometry$1(object)) {
444
+ if (isGeometry(object)) {
445
445
  this.meshes.push({ object, material: object.material });
446
446
  } else if (isLight(object)) {
447
447
  this.lights.push({ object, intensity: object.intensity });
@@ -1062,6 +1062,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
1062
1062
  }
1063
1063
  );
1064
1064
 
1065
+ let DeviceOrientationControls$1 = class DeviceOrientationControls extends EventDispatcher {
1066
+ object;
1067
+ changeEvent = { type: "change" };
1068
+ EPS = 1e-6;
1069
+ enabled = true;
1070
+ deviceOrientation = { alpha: 0, beta: 0, gamma: 0 };
1071
+ screenOrientation = 0;
1072
+ alphaOffset = 0;
1073
+ // radians
1074
+ constructor(object) {
1075
+ super();
1076
+ this.object = object;
1077
+ this.object.rotation.reorder("YXZ");
1078
+ this.connect();
1079
+ }
1080
+ onDeviceOrientationChangeEvent = (event) => {
1081
+ this.deviceOrientation = event;
1082
+ };
1083
+ onScreenOrientationChangeEvent = () => {
1084
+ this.screenOrientation = window.orientation || 0;
1085
+ };
1086
+ // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
1087
+ zee = new Vector3(0, 0, 1);
1088
+ euler = new Euler();
1089
+ q0 = new Quaternion();
1090
+ q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
1091
+ // - PI/2 around the x-axis
1092
+ setObjectQuaternion = (quaternion, alpha, beta, gamma, orient) => {
1093
+ this.euler.set(beta, alpha, -gamma, "YXZ");
1094
+ quaternion.setFromEuler(this.euler);
1095
+ quaternion.multiply(this.q1);
1096
+ quaternion.multiply(this.q0.setFromAxisAngle(this.zee, -orient));
1097
+ };
1098
+ connect = () => {
1099
+ this.onScreenOrientationChangeEvent();
1100
+ if (window.DeviceOrientationEvent !== void 0 && // @ts-ignore
1101
+ typeof window.DeviceOrientationEvent.requestPermission === "function") {
1102
+ window.DeviceOrientationEvent.requestPermission().then((response) => {
1103
+ if (response == "granted") {
1104
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1105
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1106
+ }
1107
+ }).catch((error) => {
1108
+ console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:", error);
1109
+ });
1110
+ } else {
1111
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1112
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1113
+ }
1114
+ this.enabled = true;
1115
+ };
1116
+ disconnect = () => {
1117
+ window.removeEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1118
+ window.removeEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1119
+ this.enabled = false;
1120
+ };
1121
+ lastQuaternion = new Quaternion();
1122
+ update = () => {
1123
+ if (this.enabled === false) return;
1124
+ const device = this.deviceOrientation;
1125
+ if (device) {
1126
+ const alpha = device.alpha ? MathUtils.degToRad(device.alpha) + this.alphaOffset : 0;
1127
+ const beta = device.beta ? MathUtils.degToRad(device.beta) : 0;
1128
+ const gamma = device.gamma ? MathUtils.degToRad(device.gamma) : 0;
1129
+ const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation) : 0;
1130
+ this.setObjectQuaternion(this.object.quaternion, alpha, beta, gamma, orient);
1131
+ if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
1132
+ this.lastQuaternion.copy(this.object.quaternion);
1133
+ this.dispatchEvent(this.changeEvent);
1134
+ }
1135
+ }
1136
+ };
1137
+ dispose = () => this.disconnect();
1138
+ };
1139
+
1065
1140
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1066
1141
  (props, ref) => {
1067
1142
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -1131,8 +1206,9 @@ const DragControls = React.forwardRef(
1131
1206
  onDragStart && onDragStart(initialModelPosition);
1132
1207
  invalidate();
1133
1208
  },
1134
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
1209
+ onDrag: ({ xy: [dragX, dragY], intentional, event }) => {
1135
1210
  if (!intentional) return;
1211
+ if (dragConfig && dragConfig.preventOverlap) event.preventDefault();
1136
1212
  const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
1137
1213
  const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
1138
1214
  mousePosition2D.set(normalizedMouseX, normalizedMouseY);
@@ -1301,12 +1377,21 @@ function KeyboardControls({ map, children, onChange, domElement }) {
1301
1377
  obj.pressed = false;
1302
1378
  if (up) fn(false);
1303
1379
  };
1380
+ const blurHandler = () => {
1381
+ const resetState = map.reduce((prev, cur) => ({ ...prev, [cur.name]: false }), {});
1382
+ set(resetState);
1383
+ Object.values(keyMap).forEach((obj) => {
1384
+ obj.pressed = false;
1385
+ });
1386
+ };
1304
1387
  const source = domElement || window;
1305
1388
  source.addEventListener("keydown", downHandler, { passive: true });
1306
1389
  source.addEventListener("keyup", upHandler, { passive: true });
1390
+ window.addEventListener("blur", blurHandler);
1307
1391
  return () => {
1308
1392
  source.removeEventListener("keydown", downHandler);
1309
1393
  source.removeEventListener("keyup", upHandler);
1394
+ window.removeEventListener("blur", blurHandler);
1310
1395
  };
1311
1396
  }, [domElement, key]);
1312
1397
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
@@ -1657,7 +1742,13 @@ function PresentationControls({
1657
1742
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
1658
1743
  }
1659
1744
 
1745
+ function roundToPixelRatio(value) {
1746
+ const ratio = window.devicePixelRatio || 1;
1747
+ return Math.round(value * ratio) / ratio;
1748
+ }
1749
+
1660
1750
  const context$7 = /* @__PURE__ */ React.createContext(null);
1751
+ const rootCache = /* @__PURE__ */ new WeakMap();
1661
1752
  function useScroll() {
1662
1753
  return React.useContext(context$7);
1663
1754
  }
@@ -1671,6 +1762,7 @@ function ScrollControls({
1671
1762
  damping = 0.25,
1672
1763
  maxSpeed = Infinity,
1673
1764
  prepend = false,
1765
+ pixelPerfect = true,
1674
1766
  style = {},
1675
1767
  children
1676
1768
  }) {
@@ -1692,6 +1784,7 @@ function ScrollControls({
1692
1784
  delta: 0,
1693
1785
  scroll,
1694
1786
  pages,
1787
+ pixelPerfect,
1695
1788
  // 0-1 for a range between from -> from + distance
1696
1789
  range(from, distance2, margin = 0) {
1697
1790
  const start = from - margin;
@@ -1710,7 +1803,7 @@ function ScrollControls({
1710
1803
  }
1711
1804
  };
1712
1805
  return state2;
1713
- }, [eps, damping, horizontal, pages]);
1806
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
1714
1807
  React.useEffect(() => {
1715
1808
  el.style.position = "absolute";
1716
1809
  el.style.width = "100%";
@@ -1823,10 +1916,23 @@ const ScrollHtml = React.forwardRef(
1823
1916
  React.useImperativeHandle(ref, () => group.current, []);
1824
1917
  const { width, height } = useThree((state2) => state2.size);
1825
1918
  const fiberState = React.useContext(context$9);
1826
- const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
1919
+ const root = React.useMemo(() => {
1920
+ let root2 = rootCache.get(state.fixed);
1921
+ if (!root2) {
1922
+ root2 = ReactDOM.createRoot(state.fixed);
1923
+ rootCache.set(state.fixed, root2);
1924
+ }
1925
+ return root2;
1926
+ }, [state.fixed]);
1827
1927
  useFrame(() => {
1828
1928
  if (state.delta > state.eps) {
1829
- group.current.style.transform = `translate3d(${state.horizontal ? -width * (state.pages - 1) * state.offset : 0}px,${state.horizontal ? 0 : height * (state.pages - 1) * -state.offset}px,0)`;
1929
+ let x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
1930
+ let y = state.horizontal ? 0 : height * (state.pages - 1) * -state.offset;
1931
+ if (state.pixelPerfect) {
1932
+ x = roundToPixelRatio(x);
1933
+ y = roundToPixelRatio(y);
1934
+ }
1935
+ group.current.style.transform = `translate3d(${x}px,${y}px,0)`;
1830
1936
  }
1831
1937
  });
1832
1938
  root.render(
@@ -2755,377 +2861,6 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
2755
2861
  }
2756
2862
  );
2757
2863
 
2758
- const WireframeMaterialShaders = {
2759
- uniforms: {
2760
- strokeOpacity: 1,
2761
- fillOpacity: 0.25,
2762
- fillMix: 0,
2763
- thickness: 0.05,
2764
- colorBackfaces: false,
2765
- dashInvert: true,
2766
- dash: false,
2767
- dashRepeats: 4,
2768
- dashLength: 0.5,
2769
- squeeze: false,
2770
- squeezeMin: 0.2,
2771
- squeezeMax: 1,
2772
- stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
2773
- backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
2774
- fill: /* @__PURE__ */ new THREE.Color("#00ff00")
2775
- },
2776
- vertex: (
2777
- /* glsl */
2778
- `
2779
- attribute vec3 barycentric;
2780
-
2781
- varying vec3 v_edges_Barycentric;
2782
- varying vec3 v_edges_Position;
2783
-
2784
- void initWireframe() {
2785
- v_edges_Barycentric = barycentric;
2786
- v_edges_Position = position.xyz;
2787
- }
2788
- `
2789
- ),
2790
- fragment: (
2791
- /* glsl */
2792
- `
2793
- #ifndef PI
2794
- #define PI 3.1415926535897932384626433832795
2795
- #endif
2796
-
2797
- varying vec3 v_edges_Barycentric;
2798
- varying vec3 v_edges_Position;
2799
-
2800
- uniform float strokeOpacity;
2801
- uniform float fillOpacity;
2802
- uniform float fillMix;
2803
- uniform float thickness;
2804
- uniform bool colorBackfaces;
2805
-
2806
- // Dash
2807
- uniform bool dashInvert;
2808
- uniform bool dash;
2809
- uniform bool dashOnly;
2810
- uniform float dashRepeats;
2811
- uniform float dashLength;
2812
-
2813
- // Squeeze
2814
- uniform bool squeeze;
2815
- uniform float squeezeMin;
2816
- uniform float squeezeMax;
2817
-
2818
- // Colors
2819
- uniform vec3 stroke;
2820
- uniform vec3 backfaceStroke;
2821
- uniform vec3 fill;
2822
-
2823
- // This is like
2824
- float wireframe_aastep(float threshold, float dist) {
2825
- float afwidth = fwidth(dist) * 0.5;
2826
- return smoothstep(threshold - afwidth, threshold + afwidth, dist);
2827
- }
2828
-
2829
- float wireframe_map(float value, float min1, float max1, float min2, float max2) {
2830
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
2831
- }
2832
-
2833
- float getWireframe() {
2834
- vec3 barycentric = v_edges_Barycentric;
2835
-
2836
- // Distance from center of each triangle to its edges.
2837
- float d = min(min(barycentric.x, barycentric.y), barycentric.z);
2838
-
2839
- // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
2840
- float positionAlong = max(barycentric.x, barycentric.y);
2841
- if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
2842
- positionAlong = 1.0 - positionAlong;
2843
- }
2844
-
2845
- // the thickness of the stroke
2846
- float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
2847
-
2848
- // if we want to shrink the thickness toward the center of the line segment
2849
- if (squeeze) {
2850
- computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
2851
- }
2852
-
2853
- // Create dash pattern
2854
- if (dash) {
2855
- // here we offset the stroke position depending on whether it
2856
- // should overlap or not
2857
- float offset = 1.0 / dashRepeats * dashLength / 2.0;
2858
- if (!dashInvert) {
2859
- offset += 1.0 / dashRepeats / 2.0;
2860
- }
2861
-
2862
- // if we should animate the dash or not
2863
- // if (dashAnimate) {
2864
- // offset += time * 0.22;
2865
- // }
2866
-
2867
- // create the repeating dash pattern
2868
- float pattern = fract((positionAlong + offset) * dashRepeats);
2869
- computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
2870
- }
2871
-
2872
- // compute the anti-aliased stroke edge
2873
- float edge = 1.0 - wireframe_aastep(computedThickness, d);
2874
-
2875
- return edge;
2876
- }
2877
- `
2878
- )
2879
- };
2880
- const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
2881
- WireframeMaterialShaders.uniforms,
2882
- WireframeMaterialShaders.vertex + /* glsl */
2883
- `
2884
- void main() {
2885
- initWireframe();
2886
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
2887
- }
2888
- `,
2889
- WireframeMaterialShaders.fragment + /* glsl */
2890
- `
2891
- void main () {
2892
- // Compute color
2893
-
2894
- float edge = getWireframe();
2895
- vec4 colorStroke = vec4(stroke, edge);
2896
-
2897
- #ifdef FLIP_SIDED
2898
- colorStroke.rgb = backfaceStroke;
2899
- #endif
2900
-
2901
- vec4 colorFill = vec4(fill, fillOpacity);
2902
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
2903
-
2904
- gl_FragColor = outColor;
2905
- }
2906
- `
2907
- );
2908
- function setWireframeOverride(material, uniforms) {
2909
- material.onBeforeCompile = (shader) => {
2910
- shader.uniforms = {
2911
- ...shader.uniforms,
2912
- ...uniforms
2913
- };
2914
- shader.vertexShader = shader.vertexShader.replace(
2915
- "void main() {",
2916
- `
2917
- ${WireframeMaterialShaders.vertex}
2918
- void main() {
2919
- initWireframe();
2920
- `
2921
- );
2922
- shader.fragmentShader = shader.fragmentShader.replace(
2923
- "void main() {",
2924
- `
2925
- ${WireframeMaterialShaders.fragment}
2926
- void main() {
2927
- `
2928
- );
2929
- shader.fragmentShader = shader.fragmentShader.replace(
2930
- "#include <color_fragment>",
2931
- /* glsl */
2932
- `
2933
- #include <color_fragment>
2934
- float edge = getWireframe();
2935
- vec4 colorStroke = vec4(stroke, edge);
2936
- #ifdef FLIP_SIDED
2937
- colorStroke.rgb = backfaceStroke;
2938
- #endif
2939
- vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
2940
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
2941
-
2942
- diffuseColor.rgb = outColor.rgb;
2943
- diffuseColor.a *= outColor.a;
2944
- `
2945
- );
2946
- };
2947
- material.side = THREE.DoubleSide;
2948
- material.transparent = true;
2949
- }
2950
- function useWireframeUniforms(uniforms, props) {
2951
- React.useEffect(
2952
- () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
2953
- [props.fillOpacity]
2954
- );
2955
- React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
2956
- React.useEffect(
2957
- () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
2958
- [props.strokeOpacity]
2959
- );
2960
- React.useEffect(
2961
- () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
2962
- [props.thickness]
2963
- );
2964
- React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
2965
- React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
2966
- React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
2967
- React.useEffect(
2968
- () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
2969
- [props.dashRepeats]
2970
- );
2971
- React.useEffect(
2972
- () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
2973
- [props.dashLength]
2974
- );
2975
- React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
2976
- React.useEffect(
2977
- () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
2978
- [props.squeezeMin]
2979
- );
2980
- React.useEffect(
2981
- () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
2982
- [props.squeezeMax]
2983
- );
2984
- React.useEffect(
2985
- () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
2986
- [props.stroke]
2987
- );
2988
- React.useEffect(
2989
- () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
2990
- [props.fill]
2991
- );
2992
- React.useEffect(
2993
- () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
2994
- [props.backfaceStroke]
2995
- );
2996
- }
2997
-
2998
- function isWithGeometry(object) {
2999
- return !!object?.geometry;
3000
- }
3001
- function isGeometry(object) {
3002
- return !!object?.isBufferGeometry;
3003
- }
3004
- function isRefObject$1(object) {
3005
- return !!object?.current;
3006
- }
3007
- function isRef$1(object) {
3008
- return object?.current !== void 0;
3009
- }
3010
- function isWireframeGeometry(geometry) {
3011
- return geometry.type === "WireframeGeometry";
3012
- }
3013
- function getUniforms() {
3014
- const u = {};
3015
- for (const key in WireframeMaterialShaders.uniforms) {
3016
- u[key] = { value: WireframeMaterialShaders.uniforms[key] };
3017
- }
3018
- return u;
3019
- }
3020
- function getBarycentricCoordinates(geometry, removeEdge) {
3021
- const position = geometry.getAttribute("position");
3022
- const count = position.count;
3023
- const barycentric = [];
3024
- for (let i = 0; i < count; i++) {
3025
- const even = i % 2 === 0;
3026
- const Q = removeEdge ? 1 : 0;
3027
- if (even) {
3028
- barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
3029
- } else {
3030
- barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
3031
- }
3032
- }
3033
- return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
3034
- }
3035
- function getInputGeometry(inputGeometry) {
3036
- const geo = isRefObject$1(inputGeometry) ? inputGeometry.current : inputGeometry;
3037
- if (!isGeometry(geo)) {
3038
- if (isWireframeGeometry(geo)) {
3039
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3040
- }
3041
- const parent = geo.parent;
3042
- if (isWithGeometry(parent)) {
3043
- if (isWireframeGeometry(parent.geometry)) {
3044
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3045
- }
3046
- return parent.geometry;
3047
- }
3048
- } else {
3049
- return geo;
3050
- }
3051
- }
3052
- function setBarycentricCoordinates(geometry, simplify) {
3053
- if (geometry.index) {
3054
- console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
3055
- const nonIndexedGeo = geometry.toNonIndexed();
3056
- geometry.copy(nonIndexedGeo);
3057
- geometry.setIndex(null);
3058
- }
3059
- const newBarycentric = getBarycentricCoordinates(geometry, simplify);
3060
- geometry.setAttribute("barycentric", newBarycentric);
3061
- }
3062
- function WireframeWithCustomGeo({
3063
- geometry: customGeometry,
3064
- simplify = false,
3065
- ...props
3066
- }) {
3067
- extend({ MeshWireframeMaterial: WireframeMaterial });
3068
- const [geometry, setGeometry] = React.useState(null);
3069
- React.useLayoutEffect(() => {
3070
- const geom = getInputGeometry(customGeometry);
3071
- if (!geom) {
3072
- throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
3073
- }
3074
- setBarycentricCoordinates(geom, simplify);
3075
- if (isRef$1(customGeometry)) {
3076
- setGeometry(geom);
3077
- }
3078
- }, [simplify, customGeometry]);
3079
- const drawnGeo = isRef$1(customGeometry) ? geometry : customGeometry;
3080
- return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
3081
- "meshWireframeMaterial",
3082
- {
3083
- attach: "material",
3084
- transparent: true,
3085
- side: THREE.DoubleSide,
3086
- polygonOffset: true,
3087
- polygonOffsetFactor: -4,
3088
- ...props
3089
- }
3090
- )));
3091
- }
3092
- function WireframeWithoutCustomGeo({
3093
- simplify = false,
3094
- ...props
3095
- }) {
3096
- const objectRef = React.useRef(null);
3097
- const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
3098
- useWireframeUniforms(uniforms, props);
3099
- React.useLayoutEffect(() => {
3100
- const geom = getInputGeometry(objectRef);
3101
- if (!geom) {
3102
- throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
3103
- }
3104
- const og = geom.clone();
3105
- setBarycentricCoordinates(geom, simplify);
3106
- return () => {
3107
- geom.copy(og);
3108
- og.dispose();
3109
- };
3110
- }, [simplify]);
3111
- React.useLayoutEffect(() => {
3112
- const parentMesh = objectRef.current.parent;
3113
- const og = parentMesh.material.clone();
3114
- setWireframeOverride(parentMesh.material, uniforms);
3115
- return () => {
3116
- parentMesh.material.dispose();
3117
- parentMesh.material = og;
3118
- };
3119
- }, []);
3120
- return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
3121
- }
3122
- function Wireframe({ geometry: customGeometry, ...props }) {
3123
- if (customGeometry) {
3124
- return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
3125
- }
3126
- return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
3127
- }
3128
-
3129
2864
  const Context = /* @__PURE__ */ React.createContext({});
3130
2865
  const useGizmoContext = () => {
3131
2866
  return React.useContext(Context);
@@ -3421,6 +3156,11 @@ const GizmoViewport = ({
3421
3156
  return /* @__PURE__ */ React.createElement("group", { scale: 40, ...props }, /* @__PURE__ */ React.createElement(Axis, { color: colorX, rotation: [0, 0, 0], scale: axisScale }), /* @__PURE__ */ React.createElement(Axis, { color: colorY, rotation: [0, 0, Math.PI / 2], scale: axisScale }), /* @__PURE__ */ React.createElement(Axis, { color: colorZ, rotation: [0, -Math.PI / 2, 0], scale: axisScale }), !hideAxisHeads && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorX, position: [1, 0, 0], label: labels[0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorY, position: [0, 1, 0], label: labels[1], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorZ, position: [0, 0, 1], label: labels[2], ...axisHeadProps }), !hideNegativeAxes && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorX, position: [-1, 0, 0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorY, position: [0, -1, 0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorZ, position: [0, 0, -1], ...axisHeadProps }))));
3422
3157
  };
3423
3158
 
3159
+ const roundEven = (n) => {
3160
+ const rounded = Math.ceil(n);
3161
+ return rounded % 2 === 0 ? rounded : rounded + 1;
3162
+ };
3163
+
3424
3164
  const v1 = /* @__PURE__ */ new Vector3();
3425
3165
  const v2 = /* @__PURE__ */ new Vector3();
3426
3166
  const v3 = /* @__PURE__ */ new Vector3();
@@ -3519,9 +3259,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
3519
3259
  as = "div",
3520
3260
  wrapperClass,
3521
3261
  pointerEvents = "auto",
3262
+ pixelPerfect = true,
3522
3263
  ...props
3523
3264
  }, ref) => {
3524
3265
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
3266
+ const forceEven = useThree((state) => state.internal.forceEven);
3525
3267
  const [el] = React.useState(() => document.createElement(as));
3526
3268
  const root = React.useRef(null);
3527
3269
  const group = React.useRef(null);
@@ -3546,38 +3288,54 @@ const Html = /* @__PURE__ */ React.forwardRef(
3546
3288
  el2.style.position = null;
3547
3289
  el2.style.pointerEvents = null;
3548
3290
  }
3549
- }, [occlude]);
3291
+ }, [occlude, zIndexRange, gl.domElement]);
3550
3292
  React.useLayoutEffect(() => {
3551
3293
  if (group.current) {
3552
- const currentRoot = root.current = ReactDOM.createRoot(el);
3294
+ if (!root.current) {
3295
+ root.current = ReactDOM.createRoot(el);
3296
+ }
3553
3297
  scene.updateMatrixWorld();
3554
3298
  if (transform) {
3555
3299
  el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3556
3300
  } else {
3557
- const vec = calculatePosition(group.current, camera, size);
3558
- el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(${vec[0]}px,${vec[1]}px,0);transform-origin:0 0;`;
3301
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
3559
3302
  }
3560
3303
  if (target) {
3561
3304
  if (prepend) target.prepend(el);
3562
3305
  else target.appendChild(el);
3563
3306
  }
3564
3307
  return () => {
3565
- if (target) target.removeChild(el);
3566
- currentRoot.unmount();
3308
+ if (root.current) {
3309
+ root.current.unmount();
3310
+ root.current = null;
3311
+ }
3312
+ if (target && target.contains(el)) {
3313
+ target.removeChild(el);
3314
+ }
3567
3315
  };
3568
3316
  }
3569
- }, [target, transform]);
3317
+ }, [target, transform, el, prepend, scene]);
3570
3318
  React.useLayoutEffect(() => {
3571
3319
  if (wrapperClass) el.className = wrapperClass;
3572
- }, [wrapperClass]);
3320
+ }, [wrapperClass, el]);
3321
+ React.useLayoutEffect(() => {
3322
+ return () => {
3323
+ if (root.current) {
3324
+ root.current.unmount();
3325
+ root.current = null;
3326
+ }
3327
+ };
3328
+ }, []);
3573
3329
  const styles = React.useMemo(() => {
3330
+ const width = forceEven ? roundEven(size.width) : size.width;
3331
+ const height = forceEven ? roundEven(size.height) : size.height;
3574
3332
  if (transform) {
3575
3333
  return {
3576
3334
  position: "absolute",
3577
3335
  top: 0,
3578
3336
  left: 0,
3579
- width: size.width,
3580
- height: size.height,
3337
+ width,
3338
+ height,
3581
3339
  transformStyle: "preserve-3d",
3582
3340
  pointerEvents: "none"
3583
3341
  };
@@ -3586,15 +3344,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
3586
3344
  position: "absolute",
3587
3345
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
3588
3346
  ...fullscreen && {
3589
- top: -size.height / 2,
3590
- left: -size.width / 2,
3591
- width: size.width,
3592
- height: size.height
3347
+ top: forceEven ? roundEven(-size.height / 2) : -size.height / 2,
3348
+ left: forceEven ? roundEven(-size.width / 2) : -size.width / 2,
3349
+ width,
3350
+ height
3593
3351
  },
3594
3352
  ...style
3595
3353
  };
3596
3354
  }
3597
- }, [style, center, fullscreen, size, transform]);
3355
+ }, [style, center, fullscreen, size, transform, forceEven]);
3598
3356
  const transformInnerStyles = React.useMemo(
3599
3357
  () => ({ position: "absolute", pointerEvents }),
3600
3358
  [pointerEvents]
@@ -3608,7 +3366,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3608
3366
  } else {
3609
3367
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
3610
3368
  }
3611
- });
3369
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
3612
3370
  const visible = React.useRef(true);
3613
3371
  useFrame((gl2) => {
3614
3372
  if (group.current) {
@@ -3640,7 +3398,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3640
3398
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
3641
3399
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
3642
3400
  if (transform) {
3643
- const [widthHalf, heightHalf] = [size.width / 2, size.height / 2];
3401
+ let widthHalf = forceEven ? roundEven(size.width / 2) : size.width / 2;
3402
+ let heightHalf = forceEven ? roundEven(size.height / 2) : size.height / 2;
3403
+ if (pixelPerfect) {
3404
+ widthHalf = roundToPixelRatio(widthHalf);
3405
+ heightHalf = roundToPixelRatio(heightHalf);
3406
+ }
3644
3407
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
3645
3408
  const { isOrthographicCamera, top, left, bottom, right } = camera;
3646
3409
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -3651,8 +3414,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
3651
3414
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
3652
3415
  matrix.elements[15] = 1;
3653
3416
  }
3654
- el.style.width = size.width + "px";
3655
- el.style.height = size.height + "px";
3417
+ const elWidth = forceEven ? roundEven(size.width) : size.width;
3418
+ const elHeight = forceEven ? roundEven(size.height) : size.height;
3419
+ el.style.width = elWidth + "px";
3420
+ el.style.height = elHeight + "px";
3656
3421
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
3657
3422
  if (transformOuterRef.current && transformInnerRef.current) {
3658
3423
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -3660,7 +3425,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
3660
3425
  }
3661
3426
  } else {
3662
3427
  const scale = distanceFactor === void 0 ? 1 : objectScale(group.current, camera) * distanceFactor;
3663
- el.style.transform = `translate3d(${vec[0]}px,${vec[1]}px,0) scale(${scale})`;
3428
+ let x = forceEven ? roundEven(vec[0]) : vec[0];
3429
+ let y = forceEven ? roundEven(vec[1]) : vec[1];
3430
+ if (pixelPerfect) {
3431
+ x = roundToPixelRatio(x);
3432
+ y = roundToPixelRatio(y);
3433
+ }
3434
+ el.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`;
3664
3435
  }
3665
3436
  oldPosition.current = vec;
3666
3437
  oldZoom.current = camera.zoom;
@@ -3766,6 +3537,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
3766
3537
  );
3767
3538
 
3768
3539
  const context$4 = /* @__PURE__ */ React.createContext(null);
3540
+ const resolveObject = (object, fallback) => {
3541
+ if (!object) return fallback;
3542
+ if ("current" in object) return object.current;
3543
+ return object;
3544
+ };
3769
3545
 
3770
3546
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
3771
3547
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -3800,6 +3576,8 @@ const AxisArrow = ({ direction, axis }) => {
3800
3576
  onDragStart,
3801
3577
  onDrag,
3802
3578
  onDragEnd,
3579
+ onHover,
3580
+ dragState,
3803
3581
  userData,
3804
3582
  LineComponent: Line
3805
3583
  } = React.useContext(context$4);
@@ -3831,7 +3609,13 @@ const AxisArrow = ({ direction, axis }) => {
3831
3609
  const onPointerMove = React.useCallback(
3832
3610
  (e) => {
3833
3611
  e.stopPropagation();
3834
- if (!isHovered) setIsHovered(true);
3612
+ if (!isHovered) {
3613
+ const drag = dragState.current;
3614
+ if (drag === null || drag.component === "Arrow" && drag.axis === axis) {
3615
+ setIsHovered(true);
3616
+ onHover({ component: "Arrow", axis, hovering: true });
3617
+ }
3618
+ }
3835
3619
  if (clickInfo.current) {
3836
3620
  const { clickPoint, dir } = clickInfo.current;
3837
3621
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -3850,7 +3634,7 @@ const AxisArrow = ({ direction, axis }) => {
3850
3634
  onDrag(offsetMatrix$1);
3851
3635
  }
3852
3636
  },
3853
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
3637
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
3854
3638
  );
3855
3639
  const onPointerUp = React.useCallback(
3856
3640
  (e) => {
@@ -3865,10 +3649,16 @@ const AxisArrow = ({ direction, axis }) => {
3865
3649
  },
3866
3650
  [annotations, camControls, onDragEnd]
3867
3651
  );
3868
- const onPointerOut = React.useCallback((e) => {
3869
- e.stopPropagation();
3870
- setIsHovered(false);
3871
- }, []);
3652
+ const onPointerOut = React.useCallback(
3653
+ (e) => {
3654
+ e.stopPropagation();
3655
+ if (isHovered) {
3656
+ setIsHovered(false);
3657
+ onHover({ component: "Arrow", axis, hovering: false });
3658
+ }
3659
+ },
3660
+ [onHover, axis, isHovered]
3661
+ );
3872
3662
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
3873
3663
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
3874
3664
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -3972,6 +3762,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
3972
3762
  const posNew = /* @__PURE__ */ new THREE.Vector3();
3973
3763
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
3974
3764
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
3765
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
3766
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
3975
3767
  const AxisRotator = ({
3976
3768
  dir1,
3977
3769
  dir2,
@@ -3992,6 +3784,8 @@ const AxisRotator = ({
3992
3784
  onDragStart,
3993
3785
  onDrag,
3994
3786
  onDragEnd,
3787
+ onHover,
3788
+ dragState,
3995
3789
  userData,
3996
3790
  LineComponent: Line
3997
3791
  } = React.useContext(context$4);
@@ -4025,14 +3819,29 @@ const AxisRotator = ({
4025
3819
  const onPointerMove = React.useCallback(
4026
3820
  (e) => {
4027
3821
  e.stopPropagation();
4028
- if (!isHovered) setIsHovered(true);
3822
+ if (!isHovered) {
3823
+ const drag = dragState.current;
3824
+ if (drag === null || drag.component === "Rotator" && drag.axis === axis) {
3825
+ setIsHovered(true);
3826
+ onHover({ component: "Rotator", axis, hovering: true });
3827
+ }
3828
+ }
4029
3829
  if (clickInfo.current) {
4030
3830
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
4031
3831
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
4032
3832
  ray$1.copy(e.ray);
4033
- ray$1.intersectPlane(plane, intersection$1);
4034
- ray$1.direction.negate();
4035
- ray$1.intersectPlane(plane, intersection$1);
3833
+ const dotProduct = Math.abs(ray$1.direction.dot(normal));
3834
+ if (dotProduct < 0.1) {
3835
+ viewPlane.setFromNormalAndCoplanarPoint(ray$1.direction.clone().negate(), origin);
3836
+ ray$1.intersectPlane(viewPlane, viewIntersection);
3837
+ const toIntersection = viewIntersection.clone().sub(origin);
3838
+ const projectedDist = toIntersection.dot(normal);
3839
+ intersection$1.copy(viewIntersection).sub(normal.clone().multiplyScalar(projectedDist));
3840
+ } else {
3841
+ ray$1.intersectPlane(plane, intersection$1);
3842
+ ray$1.direction.negate();
3843
+ ray$1.intersectPlane(plane, intersection$1);
3844
+ }
4036
3845
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
4037
3846
  let degrees = toDegrees(deltaAngle);
4038
3847
  if (e.shiftKey) {
@@ -4058,7 +3867,7 @@ const AxisRotator = ({
4058
3867
  onDrag(rotMatrix);
4059
3868
  }
4060
3869
  },
4061
- [annotations, onDrag, isHovered, rotationLimits, axis]
3870
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
4062
3871
  );
4063
3872
  const onPointerUp = React.useCallback(
4064
3873
  (e) => {
@@ -4074,10 +3883,16 @@ const AxisRotator = ({
4074
3883
  },
4075
3884
  [annotations, camControls, onDragEnd]
4076
3885
  );
4077
- const onPointerOut = React.useCallback((e) => {
4078
- e.stopPropagation();
4079
- setIsHovered(false);
4080
- }, []);
3886
+ const onPointerOut = React.useCallback(
3887
+ (e) => {
3888
+ e.stopPropagation();
3889
+ if (isHovered) {
3890
+ setIsHovered(false);
3891
+ onHover({ component: "Rotator", axis, hovering: false });
3892
+ }
3893
+ },
3894
+ [onHover, axis, isHovered]
3895
+ );
4081
3896
  const matrixL = React.useMemo(() => {
4082
3897
  const dir1N = dir1.clone().normalize();
4083
3898
  const dir2N = dir2.clone().normalize();
@@ -4178,6 +3993,8 @@ const PlaneSlider = ({
4178
3993
  onDragStart,
4179
3994
  onDrag,
4180
3995
  onDragEnd,
3996
+ onHover,
3997
+ dragState,
4181
3998
  userData,
4182
3999
  LineComponent: Line
4183
4000
  } = React.useContext(context$4);
@@ -4213,7 +4030,13 @@ const PlaneSlider = ({
4213
4030
  const onPointerMove = React.useCallback(
4214
4031
  (e) => {
4215
4032
  e.stopPropagation();
4216
- if (!isHovered) setIsHovered(true);
4033
+ if (!isHovered) {
4034
+ const drag = dragState.current;
4035
+ if (drag === null || drag.component === "Slider" && drag.axis === axis) {
4036
+ setIsHovered(true);
4037
+ onHover({ component: "Slider", axis, hovering: true });
4038
+ }
4039
+ }
4217
4040
  if (clickInfo.current) {
4218
4041
  const { clickPoint, e1, e2, plane } = clickInfo.current;
4219
4042
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -4249,7 +4072,7 @@ const PlaneSlider = ({
4249
4072
  onDrag(offsetMatrix);
4250
4073
  }
4251
4074
  },
4252
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
4075
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4253
4076
  );
4254
4077
  const onPointerUp = React.useCallback(
4255
4078
  (e) => {
@@ -4264,10 +4087,16 @@ const PlaneSlider = ({
4264
4087
  },
4265
4088
  [annotations, camControls, onDragEnd]
4266
4089
  );
4267
- const onPointerOut = React.useCallback((e) => {
4268
- e.stopPropagation();
4269
- setIsHovered(false);
4270
- }, []);
4090
+ const onPointerOut = React.useCallback(
4091
+ (e) => {
4092
+ e.stopPropagation();
4093
+ if (isHovered) {
4094
+ setIsHovered(false);
4095
+ onHover({ component: "Slider", axis, hovering: false });
4096
+ }
4097
+ },
4098
+ [onHover, axis, isHovered]
4099
+ );
4271
4100
  const matrixL = React.useMemo(() => {
4272
4101
  const dir1N = dir1.clone().normalize();
4273
4102
  const dir2N = dir2.clone().normalize();
@@ -4405,6 +4234,8 @@ const ScalingSphere = ({ direction, axis }) => {
4405
4234
  onDragStart,
4406
4235
  onDrag,
4407
4236
  onDragEnd,
4237
+ onHover,
4238
+ dragState,
4408
4239
  userData
4409
4240
  } = React.useContext(context$4);
4410
4241
  const size = useThree((state) => state.size);
@@ -4441,7 +4272,13 @@ const ScalingSphere = ({ direction, axis }) => {
4441
4272
  const onPointerMove = React.useCallback(
4442
4273
  (e) => {
4443
4274
  e.stopPropagation();
4444
- if (!isHovered) setIsHovered(true);
4275
+ if (!isHovered) {
4276
+ const drag = dragState.current;
4277
+ if (drag === null || drag.component === "Sphere" && drag.axis === axis) {
4278
+ setIsHovered(true);
4279
+ onHover({ component: "Sphere", axis, hovering: true });
4280
+ }
4281
+ }
4445
4282
  if (clickInfo.current) {
4446
4283
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
4447
4284
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -4467,7 +4304,7 @@ const ScalingSphere = ({ direction, axis }) => {
4467
4304
  onDrag(scaleMatrix);
4468
4305
  }
4469
4306
  },
4470
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
4307
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
4471
4308
  );
4472
4309
  const onPointerUp = React.useCallback(
4473
4310
  (e) => {
@@ -4484,10 +4321,16 @@ const ScalingSphere = ({ direction, axis }) => {
4484
4321
  },
4485
4322
  [annotations, camControls, onDragEnd, position]
4486
4323
  );
4487
- const onPointerOut = React.useCallback((e) => {
4488
- e.stopPropagation();
4489
- setIsHovered(false);
4490
- }, []);
4324
+ const onPointerOut = React.useCallback(
4325
+ (e) => {
4326
+ e.stopPropagation();
4327
+ if (isHovered) {
4328
+ setIsHovered(false);
4329
+ onHover({ component: "Sphere", axis, hovering: false });
4330
+ }
4331
+ },
4332
+ [onHover, axis, isHovered]
4333
+ );
4491
4334
  const { radius, matrixL } = React.useMemo(() => {
4492
4335
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
4493
4336
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -4557,9 +4400,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4557
4400
  ({
4558
4401
  enabled = true,
4559
4402
  matrix,
4403
+ object,
4560
4404
  onDragStart,
4561
4405
  onDrag,
4562
4406
  onDragEnd,
4407
+ onHover,
4563
4408
  autoTransform = true,
4564
4409
  anchor,
4565
4410
  disableAxes = false,
@@ -4595,14 +4440,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4595
4440
  const gizmoRef = React.useRef(null);
4596
4441
  const childrenRef = React.useRef(null);
4597
4442
  const translation = React.useRef([0, 0, 0]);
4443
+ const dragState = React.useRef(null);
4598
4444
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
4599
4445
  const gizmoScale = React.useRef(new THREE.Vector3(1, 1, 1));
4446
+ React.useEffect(() => {
4447
+ if (object) {
4448
+ const target = resolveObject(object);
4449
+ if (target) {
4450
+ const pivot = ref.current;
4451
+ const didAutoUpdate = target.matrixAutoUpdate;
4452
+ target.updateWorldMatrix(true, true);
4453
+ target.matrixAutoUpdate = false;
4454
+ pivot.matrix.copy(target.matrix);
4455
+ return () => {
4456
+ if (didAutoUpdate) {
4457
+ target.matrixAutoUpdate = true;
4458
+ target.matrix.decompose(target.position, target.quaternion, target.scale);
4459
+ }
4460
+ };
4461
+ }
4462
+ }
4463
+ }, [object]);
4600
4464
  React.useLayoutEffect(() => {
4601
4465
  if (!anchor) return;
4602
- childrenRef.current.updateWorldMatrix(true, true);
4603
- mPInv.copy(childrenRef.current.matrixWorld).invert();
4466
+ const anchorTarget = resolveObject(object, childrenRef.current);
4467
+ if (!anchorTarget) return;
4468
+ anchorTarget.updateWorldMatrix(true, true);
4469
+ mPInv.copy(anchorTarget.matrixWorld).invert();
4604
4470
  bb.makeEmpty();
4605
- childrenRef.current.traverse((obj) => {
4471
+ anchorTarget.traverse((obj) => {
4606
4472
  if (!obj.geometry) return;
4607
4473
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
4608
4474
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -4617,9 +4483,17 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4617
4483
  gizmoRef.current.position.copy(vPosition);
4618
4484
  invalidate();
4619
4485
  });
4486
+ const mergedUserData = React.useMemo(
4487
+ () => ({
4488
+ ...userData,
4489
+ ...depthTest === false && { interactivePriority: 1 }
4490
+ }),
4491
+ [userData, depthTest]
4492
+ );
4620
4493
  const config = React.useMemo(
4621
4494
  () => ({
4622
4495
  onDragStart: (props2) => {
4496
+ dragState.current = props2;
4623
4497
  mL0.copy(ref.current.matrix);
4624
4498
  mW0.copy(ref.current.matrixWorld);
4625
4499
  onDragStart && onDragStart(props2);
@@ -4635,13 +4509,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4635
4509
  if (autoTransform) {
4636
4510
  ref.current.matrix.copy(mL);
4637
4511
  }
4512
+ const target = resolveObject(object);
4513
+ if (target) target.matrix.copy(mL);
4638
4514
  onDrag && onDrag(mL, mdL, mW, mdW);
4639
4515
  invalidate();
4640
4516
  },
4641
4517
  onDragEnd: () => {
4518
+ dragState.current = null;
4642
4519
  if (onDragEnd) onDragEnd();
4643
4520
  invalidate();
4644
4521
  },
4522
+ onHover: (props2) => {
4523
+ if (onHover) onHover(props2);
4524
+ },
4525
+ dragState,
4645
4526
  translation,
4646
4527
  translationLimits,
4647
4528
  rotationLimits,
@@ -4653,7 +4534,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4653
4534
  fixed,
4654
4535
  depthTest,
4655
4536
  renderOrder,
4656
- userData,
4537
+ userData: mergedUserData,
4657
4538
  annotations,
4658
4539
  annotationsClass,
4659
4540
  LineComponent: LineComp
@@ -4662,6 +4543,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4662
4543
  onDragStart,
4663
4544
  onDrag,
4664
4545
  onDragEnd,
4546
+ onHover,
4665
4547
  translation,
4666
4548
  translationLimits,
4667
4549
  rotationLimits,
@@ -4673,11 +4555,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4673
4555
  ...axisColors,
4674
4556
  hoveredColor,
4675
4557
  opacity,
4676
- userData,
4558
+ mergedUserData,
4677
4559
  autoTransform,
4678
4560
  annotations,
4679
4561
  annotationsClass,
4680
- LineComp
4562
+ LineComp,
4563
+ object
4681
4564
  ]
4682
4565
  );
4683
4566
  const vec = new THREE.Vector3();
@@ -5039,7 +4922,7 @@ function createInstances() {
5039
4922
  ];
5040
4923
  }
5041
4924
  const InstancedAttribute = React.forwardRef(
5042
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
4925
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
5043
4926
  const ref = React.useRef(null);
5044
4927
  React.useImperativeHandle(fref, () => ref.current, []);
5045
4928
  React.useLayoutEffect(() => {
@@ -5047,7 +4930,26 @@ const InstancedAttribute = React.forwardRef(
5047
4930
  parent.geometry.attributes[name] = ref.current;
5048
4931
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
5049
4932
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
5050
- ref.current.array = new Float32Array(array);
4933
+ switch (type) {
4934
+ case "float":
4935
+ ref.current.array = new Float32Array(array);
4936
+ break;
4937
+ case "int":
4938
+ ref.current.array = new Int32Array(array);
4939
+ break;
4940
+ case "uint":
4941
+ ref.current.array = new Uint32Array(array);
4942
+ break;
4943
+ case "short":
4944
+ ref.current.array = new Int16Array(array);
4945
+ break;
4946
+ case "ushort":
4947
+ ref.current.array = new Uint16Array(array);
4948
+ break;
4949
+ case "byte":
4950
+ ref.current.array = new Uint8Array(array);
4951
+ break;
4952
+ }
5051
4953
  ref.current.itemSize = value.length;
5052
4954
  ref.current.count = array.length / ref.current.itemSize;
5053
4955
  return () => {
@@ -6572,12 +6474,17 @@ const Gltf = /* @__PURE__ */ React.forwardRef(
6572
6474
  }
6573
6475
  );
6574
6476
 
6575
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
6576
- function useKTX2(input, basisPath = `${cdn}/basis/`) {
6477
+ let transcoderPath = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
6478
+ function useKTX2(input, basisPath = true, extendLoader) {
6577
6479
  const renderer = useThree((state) => state.renderer);
6578
6480
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
6481
+ if (extendLoader) {
6482
+ extendLoader(loader);
6483
+ }
6484
+ if (basisPath) {
6485
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
6486
+ }
6579
6487
  loader.detectSupport(renderer);
6580
- loader.setTranscoderPath(basisPath);
6581
6488
  });
6582
6489
  useEffect(() => {
6583
6490
  const array = Array.isArray(textures) ? textures : [textures];
@@ -6592,16 +6499,20 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
6592
6499
  return textures;
6593
6500
  }
6594
6501
  }
6595
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
6596
- loader.setTranscoderPath(basisPath);
6502
+ useKTX2.preload = (url, basisPath = true, extendLoader) => useLoader.preload(KTX2Loader, url, (loader) => {
6503
+ if (extendLoader) {
6504
+ extendLoader(loader);
6505
+ }
6506
+ if (basisPath) {
6507
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
6508
+ }
6597
6509
  });
6598
6510
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
6599
- const Ktx2 = ({
6600
- children,
6601
- input,
6602
- basisPath
6603
- }) => {
6604
- const texture = useKTX2(input, basisPath);
6511
+ useKTX2.setTranscoderPath = (path) => {
6512
+ transcoderPath = path;
6513
+ };
6514
+ const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
6515
+ const texture = useKTX2(input, basisPath, extendLoader);
6605
6516
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
6606
6517
  };
6607
6518
 
@@ -7077,18 +6988,21 @@ function Fisheye({
7077
6988
  const normal = new THREE.Vector3();
7078
6989
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
7079
6990
  const normalMatrix = new THREE.Matrix3();
7080
- const compute = React.useCallback((event, state, prev) => {
7081
- state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7082
- state.raycaster.setFromCamera(state.pointer, orthoC);
7083
- if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7084
- else normal.normalize();
7085
- normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7086
- cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7087
- state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7088
- state.raycaster.ray.direction.x *= -1;
7089
- state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7090
- return void 0;
7091
- }, []);
6991
+ const compute = React.useCallback(
6992
+ (event, state, prev) => {
6993
+ state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
6994
+ state.raycaster.setFromCamera(state.pointer, orthoC);
6995
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
6996
+ else normal.normalize();
6997
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
6998
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
6999
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7000
+ state.raycaster.ray.direction.x *= -1;
7001
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7002
+ return void 0;
7003
+ },
7004
+ [width, height]
7005
+ );
7092
7006
  useFrame(
7093
7007
  ({ gl }) => {
7094
7008
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -7223,14 +7137,22 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
7223
7137
  const col = /* @__PURE__ */ new THREE.Color();
7224
7138
  const TUNNEL_KEY = "__drei_view_tunnel__";
7225
7139
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
7226
- function computeContainerPosition(canvasSize, trackRect) {
7140
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
7227
7141
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
7228
7142
  const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
7229
7143
  const canvasBottom = canvasSize.top + canvasSize.height;
7230
- const bottom = canvasBottom - trackBottom;
7231
- const left = trackLeft - canvasSize.left;
7232
- const top = trackTop - canvasSize.top;
7233
- return { position: { width, height, left, top, bottom, right }, isOffscreen };
7144
+ const rawBottom = canvasBottom - trackBottom;
7145
+ const rawLeft = trackLeft - canvasSize.left;
7146
+ const rawTop = trackTop - canvasSize.top;
7147
+ const position = forceEven ? {
7148
+ width: roundEven(width),
7149
+ height: roundEven(height),
7150
+ left: roundEven(rawLeft),
7151
+ top: roundEven(rawTop),
7152
+ bottom: roundEven(rawBottom),
7153
+ right: roundEven(right)
7154
+ } : { width, height, left: rawLeft, top: rawTop, bottom: rawBottom, right };
7155
+ return { position, isOffscreen };
7234
7156
  }
7235
7157
  function prepareSkissor(renderer, camera, {
7236
7158
  left,
@@ -7270,6 +7192,7 @@ function clear(renderer) {
7270
7192
  }
7271
7193
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
7272
7194
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
7195
+ const forceEven = useThree((state) => state.internal.forceEven);
7273
7196
  const [isOffscreen, setOffscreen] = React.useState(false);
7274
7197
  const mountedRef = React.useRef(true);
7275
7198
  React.useEffect(() => {
@@ -7287,7 +7210,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7287
7210
  frameCount++;
7288
7211
  }
7289
7212
  if (rect.current) {
7290
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
7213
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
7291
7214
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
7292
7215
  if (visible && !_isOffscreen && rect.current) {
7293
7216
  const targetScene = children ? portalScene : scene;
@@ -7309,14 +7232,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7309
7232
  React.useLayoutEffect(() => {
7310
7233
  const curRect = rect.current;
7311
7234
  if (curRect && (!visible || !isOffscreen)) {
7312
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7235
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7313
7236
  if (!_isOffscreen) {
7314
7237
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7315
7238
  clear(renderer);
7316
7239
  finishSkissor(renderer, autoClear);
7317
7240
  }
7318
7241
  }
7319
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
7242
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
7320
7243
  React.useEffect(() => {
7321
7244
  if (!track) return;
7322
7245
  const curRect = rect.current;
@@ -7324,7 +7247,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7324
7247
  setEvents({ connected: track.current });
7325
7248
  return () => {
7326
7249
  if (curRect) {
7327
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7250
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7328
7251
  if (!_isOffscreen) {
7329
7252
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7330
7253
  clear(renderer);
@@ -7333,13 +7256,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7333
7256
  }
7334
7257
  setEvents({ connected: old });
7335
7258
  };
7336
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
7259
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
7337
7260
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
7338
7261
  }
7339
7262
  const CanvasView = /* @__PURE__ */ React.forwardRef(
7340
7263
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
7341
7264
  const rect = React.useRef(null);
7342
7265
  const { size, scene } = useThree();
7266
+ const forceEven = useThree((state) => state.internal.forceEven);
7343
7267
  const [virtualScene] = React.useState(() => new THREE.Scene());
7344
7268
  const [ready, toggle] = React.useReducer(() => true, false);
7345
7269
  const compute = React.useCallback(
@@ -7376,12 +7300,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
7376
7300
  {
7377
7301
  events: { compute, priority: index },
7378
7302
  size: {
7379
- width: rect.current?.width,
7380
- height: rect.current?.height,
7303
+ width: forceEven && rect.current?.width ? roundEven(rect.current.width) : rect.current?.width,
7304
+ height: forceEven && rect.current?.height ? roundEven(rect.current.height) : rect.current?.height,
7381
7305
  // @ts-ignore
7382
- top: rect.current?.top,
7306
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
7383
7307
  // @ts-ignore
7384
- left: rect.current?.left
7308
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
7385
7309
  }
7386
7310
  }
7387
7311
  ));
@@ -8556,4 +8480,4 @@ function useSelect() {
8556
8480
  return React.useContext(context);
8557
8481
  }
8558
8482
 
8559
- export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, CameraShake, Capsule, CatmullRomLine, Center, Circle, Clone, Cloud, CloudInstance, Clouds, ComputedAttribute, Cone, CubeCamera, CubeTexture, CubicBezierLine, CurveModifier, CycleRaycast, Cylinder, Decal, Detailed, DetectGPU, DeviceOrientationControls, Dodecahedron, DragControls, Edges, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, Example, Extrude, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, PerformanceMonitor, PerspectiveCamera, PivotControls, Plane, Point, PointMaterial, PointMaterialImpl, PointerLockControls, Points, PointsBuffer, Polyhedron, PositionMesh, PositionPoint, PositionalAudio, Preload, PresentationControls, Progress, QuadraticBezierLine, RenderCubeTexture, RenderTexture, Resize, Ring, RoundedBox, RoundedBoxGeometry, Sampler, ScreenQuad, ScreenSizer, ScreenSpace, ScreenVideoTexture, Scroll, ScrollControls, Select, Shadow, Shape, Sky, Sphere, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, Wireframe, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrailTexture, useVideoTexture };
8483
+ export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, CameraShake, Capsule, CatmullRomLine, Center, Circle, Clone, Cloud, CloudInstance, Clouds, ComputedAttribute, Cone, CubeCamera, CubeTexture, CubicBezierLine, CurveModifier, CycleRaycast, Cylinder, Decal, Detailed, DetectGPU, DeviceOrientationControls, Dodecahedron, DragControls, Edges, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, Example, Extrude, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, PerformanceMonitor, PerspectiveCamera, PivotControls, Plane, Point, PointMaterial, PointMaterialImpl, PointerLockControls, Points, PointsBuffer, Polyhedron, PositionMesh, PositionPoint, PositionalAudio, Preload, PresentationControls, Progress, QuadraticBezierLine, RenderCubeTexture, RenderTexture, Resize, Ring, RoundedBox, RoundedBoxGeometry, Sampler, ScreenQuad, ScreenSizer, ScreenSpace, ScreenVideoTexture, Scroll, ScrollControls, Select, Shadow, Shape, Sky, Sphere, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrailTexture, useVideoTexture };