@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/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, Suspense } from 'react';
3
3
  import { useThree, useFrame, extend, createPortal, applyProps, useInstanceHandle, context as context$9, useLoader, 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, Object3D, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, Quaternion, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, 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, Mesh, Spherical } from 'three';
5
+ import { REVISION, WebGLRenderTarget, Frustum, Matrix4, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, 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, Mesh, Spherical } 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,40 +21,40 @@ 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
  import { SAH, acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from 'three-mesh-bvh';
56
55
  import CameraControlsImpl from 'camera-controls';
57
56
  export { default as CameraControlsImpl } from 'camera-controls';
58
- import { MarchingCubes as MarchingCubes$1 } from 'three/examples/jsm/objects/MarchingCubes';
57
+ import { MarchingCubes as MarchingCubes$1 } from 'three/examples/jsm/objects/MarchingCubes.js';
59
58
 
60
59
  const ContactShadows = /* @__PURE__ */ React.forwardRef(
61
60
  ({
@@ -184,6 +183,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
184
183
  });
185
184
  }
186
185
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
186
+ Object.assign(this, parameters);
187
187
  }
188
188
  };
189
189
  }
@@ -200,7 +200,7 @@ const version = /* @__PURE__ */ getVersion();
200
200
  function isLight(object) {
201
201
  return object.isLight;
202
202
  }
203
- function isGeometry$1(object) {
203
+ function isGeometry(object) {
204
204
  return !!object.geometry;
205
205
  }
206
206
  const accumulativeContext = /* @__PURE__ */ React.createContext(
@@ -445,7 +445,7 @@ vec3 texelOld = texture2D(previousShadowMap, vUv).rgb;
445
445
  this.lights = [];
446
446
  this.meshes = [];
447
447
  this.scene.traverse((object) => {
448
- if (isGeometry$1(object)) {
448
+ if (isGeometry(object)) {
449
449
  this.meshes.push({ object, material: object.material });
450
450
  } else if (isLight(object)) {
451
451
  this.lights.push({ object, intensity: object.intensity });
@@ -1334,6 +1334,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
1334
1334
  }
1335
1335
  );
1336
1336
 
1337
+ let DeviceOrientationControls$1 = class DeviceOrientationControls extends EventDispatcher {
1338
+ object;
1339
+ changeEvent = { type: "change" };
1340
+ EPS = 1e-6;
1341
+ enabled = true;
1342
+ deviceOrientation = { alpha: 0, beta: 0, gamma: 0 };
1343
+ screenOrientation = 0;
1344
+ alphaOffset = 0;
1345
+ // radians
1346
+ constructor(object) {
1347
+ super();
1348
+ this.object = object;
1349
+ this.object.rotation.reorder("YXZ");
1350
+ this.connect();
1351
+ }
1352
+ onDeviceOrientationChangeEvent = (event) => {
1353
+ this.deviceOrientation = event;
1354
+ };
1355
+ onScreenOrientationChangeEvent = () => {
1356
+ this.screenOrientation = window.orientation || 0;
1357
+ };
1358
+ // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
1359
+ zee = new Vector3(0, 0, 1);
1360
+ euler = new Euler();
1361
+ q0 = new Quaternion();
1362
+ q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
1363
+ // - PI/2 around the x-axis
1364
+ setObjectQuaternion = (quaternion, alpha, beta, gamma, orient) => {
1365
+ this.euler.set(beta, alpha, -gamma, "YXZ");
1366
+ quaternion.setFromEuler(this.euler);
1367
+ quaternion.multiply(this.q1);
1368
+ quaternion.multiply(this.q0.setFromAxisAngle(this.zee, -orient));
1369
+ };
1370
+ connect = () => {
1371
+ this.onScreenOrientationChangeEvent();
1372
+ if (window.DeviceOrientationEvent !== void 0 && // @ts-ignore
1373
+ typeof window.DeviceOrientationEvent.requestPermission === "function") {
1374
+ window.DeviceOrientationEvent.requestPermission().then((response) => {
1375
+ if (response == "granted") {
1376
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1377
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1378
+ }
1379
+ }).catch((error) => {
1380
+ console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:", error);
1381
+ });
1382
+ } else {
1383
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1384
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1385
+ }
1386
+ this.enabled = true;
1387
+ };
1388
+ disconnect = () => {
1389
+ window.removeEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1390
+ window.removeEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1391
+ this.enabled = false;
1392
+ };
1393
+ lastQuaternion = new Quaternion();
1394
+ update = () => {
1395
+ if (this.enabled === false) return;
1396
+ const device = this.deviceOrientation;
1397
+ if (device) {
1398
+ const alpha = device.alpha ? MathUtils.degToRad(device.alpha) + this.alphaOffset : 0;
1399
+ const beta = device.beta ? MathUtils.degToRad(device.beta) : 0;
1400
+ const gamma = device.gamma ? MathUtils.degToRad(device.gamma) : 0;
1401
+ const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation) : 0;
1402
+ this.setObjectQuaternion(this.object.quaternion, alpha, beta, gamma, orient);
1403
+ if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
1404
+ this.lastQuaternion.copy(this.object.quaternion);
1405
+ this.dispatchEvent(this.changeEvent);
1406
+ }
1407
+ }
1408
+ };
1409
+ dispose = () => this.disconnect();
1410
+ };
1411
+
1337
1412
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1338
1413
  (props, ref) => {
1339
1414
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -1403,8 +1478,9 @@ const DragControls = React.forwardRef(
1403
1478
  onDragStart && onDragStart(initialModelPosition);
1404
1479
  invalidate();
1405
1480
  },
1406
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
1481
+ onDrag: ({ xy: [dragX, dragY], intentional, event }) => {
1407
1482
  if (!intentional) return;
1483
+ if (dragConfig && dragConfig.preventOverlap) event.preventDefault();
1408
1484
  const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
1409
1485
  const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
1410
1486
  mousePosition2D.set(normalizedMouseX, normalizedMouseY);
@@ -1573,12 +1649,21 @@ function KeyboardControls({ map, children, onChange, domElement }) {
1573
1649
  obj.pressed = false;
1574
1650
  if (up) fn(false);
1575
1651
  };
1652
+ const blurHandler = () => {
1653
+ const resetState = map.reduce((prev, cur) => ({ ...prev, [cur.name]: false }), {});
1654
+ set(resetState);
1655
+ Object.values(keyMap).forEach((obj) => {
1656
+ obj.pressed = false;
1657
+ });
1658
+ };
1576
1659
  const source = domElement || window;
1577
1660
  source.addEventListener("keydown", downHandler, { passive: true });
1578
1661
  source.addEventListener("keyup", upHandler, { passive: true });
1662
+ window.addEventListener("blur", blurHandler);
1579
1663
  return () => {
1580
1664
  source.removeEventListener("keydown", downHandler);
1581
1665
  source.removeEventListener("keyup", upHandler);
1666
+ window.removeEventListener("blur", blurHandler);
1582
1667
  };
1583
1668
  }, [domElement, key]);
1584
1669
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
@@ -1929,7 +2014,13 @@ function PresentationControls({
1929
2014
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
1930
2015
  }
1931
2016
 
2017
+ function roundToPixelRatio(value) {
2018
+ const ratio = window.devicePixelRatio || 1;
2019
+ return Math.round(value * ratio) / ratio;
2020
+ }
2021
+
1932
2022
  const context$7 = /* @__PURE__ */ React.createContext(null);
2023
+ const rootCache = /* @__PURE__ */ new WeakMap();
1933
2024
  function useScroll() {
1934
2025
  return React.useContext(context$7);
1935
2026
  }
@@ -1943,6 +2034,7 @@ function ScrollControls({
1943
2034
  damping = 0.25,
1944
2035
  maxSpeed = Infinity,
1945
2036
  prepend = false,
2037
+ pixelPerfect = true,
1946
2038
  style = {},
1947
2039
  children
1948
2040
  }) {
@@ -1964,6 +2056,7 @@ function ScrollControls({
1964
2056
  delta: 0,
1965
2057
  scroll,
1966
2058
  pages,
2059
+ pixelPerfect,
1967
2060
  // 0-1 for a range between from -> from + distance
1968
2061
  range(from, distance2, margin = 0) {
1969
2062
  const start = from - margin;
@@ -1982,7 +2075,7 @@ function ScrollControls({
1982
2075
  }
1983
2076
  };
1984
2077
  return state2;
1985
- }, [eps, damping, horizontal, pages]);
2078
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
1986
2079
  React.useEffect(() => {
1987
2080
  el.style.position = "absolute";
1988
2081
  el.style.width = "100%";
@@ -2095,10 +2188,23 @@ const ScrollHtml = React.forwardRef(
2095
2188
  React.useImperativeHandle(ref, () => group.current, []);
2096
2189
  const { width, height } = useThree((state2) => state2.size);
2097
2190
  const fiberState = React.useContext(context$9);
2098
- const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
2191
+ const root = React.useMemo(() => {
2192
+ let root2 = rootCache.get(state.fixed);
2193
+ if (!root2) {
2194
+ root2 = ReactDOM.createRoot(state.fixed);
2195
+ rootCache.set(state.fixed, root2);
2196
+ }
2197
+ return root2;
2198
+ }, [state.fixed]);
2099
2199
  useFrame(() => {
2100
2200
  if (state.delta > state.eps) {
2101
- 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)`;
2201
+ let x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
2202
+ let y = state.horizontal ? 0 : height * (state.pages - 1) * -state.offset;
2203
+ if (state.pixelPerfect) {
2204
+ x = roundToPixelRatio(x);
2205
+ y = roundToPixelRatio(y);
2206
+ }
2207
+ group.current.style.transform = `translate3d(${x}px,${y}px,0)`;
2102
2208
  }
2103
2209
  });
2104
2210
  root.render(
@@ -3027,377 +3133,6 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
3027
3133
  }
3028
3134
  );
3029
3135
 
3030
- const WireframeMaterialShaders = {
3031
- uniforms: {
3032
- strokeOpacity: 1,
3033
- fillOpacity: 0.25,
3034
- fillMix: 0,
3035
- thickness: 0.05,
3036
- colorBackfaces: false,
3037
- dashInvert: true,
3038
- dash: false,
3039
- dashRepeats: 4,
3040
- dashLength: 0.5,
3041
- squeeze: false,
3042
- squeezeMin: 0.2,
3043
- squeezeMax: 1,
3044
- stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
3045
- backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
3046
- fill: /* @__PURE__ */ new THREE.Color("#00ff00")
3047
- },
3048
- vertex: (
3049
- /* glsl */
3050
- `
3051
- attribute vec3 barycentric;
3052
-
3053
- varying vec3 v_edges_Barycentric;
3054
- varying vec3 v_edges_Position;
3055
-
3056
- void initWireframe() {
3057
- v_edges_Barycentric = barycentric;
3058
- v_edges_Position = position.xyz;
3059
- }
3060
- `
3061
- ),
3062
- fragment: (
3063
- /* glsl */
3064
- `
3065
- #ifndef PI
3066
- #define PI 3.1415926535897932384626433832795
3067
- #endif
3068
-
3069
- varying vec3 v_edges_Barycentric;
3070
- varying vec3 v_edges_Position;
3071
-
3072
- uniform float strokeOpacity;
3073
- uniform float fillOpacity;
3074
- uniform float fillMix;
3075
- uniform float thickness;
3076
- uniform bool colorBackfaces;
3077
-
3078
- // Dash
3079
- uniform bool dashInvert;
3080
- uniform bool dash;
3081
- uniform bool dashOnly;
3082
- uniform float dashRepeats;
3083
- uniform float dashLength;
3084
-
3085
- // Squeeze
3086
- uniform bool squeeze;
3087
- uniform float squeezeMin;
3088
- uniform float squeezeMax;
3089
-
3090
- // Colors
3091
- uniform vec3 stroke;
3092
- uniform vec3 backfaceStroke;
3093
- uniform vec3 fill;
3094
-
3095
- // This is like
3096
- float wireframe_aastep(float threshold, float dist) {
3097
- float afwidth = fwidth(dist) * 0.5;
3098
- return smoothstep(threshold - afwidth, threshold + afwidth, dist);
3099
- }
3100
-
3101
- float wireframe_map(float value, float min1, float max1, float min2, float max2) {
3102
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
3103
- }
3104
-
3105
- float getWireframe() {
3106
- vec3 barycentric = v_edges_Barycentric;
3107
-
3108
- // Distance from center of each triangle to its edges.
3109
- float d = min(min(barycentric.x, barycentric.y), barycentric.z);
3110
-
3111
- // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
3112
- float positionAlong = max(barycentric.x, barycentric.y);
3113
- if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
3114
- positionAlong = 1.0 - positionAlong;
3115
- }
3116
-
3117
- // the thickness of the stroke
3118
- float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
3119
-
3120
- // if we want to shrink the thickness toward the center of the line segment
3121
- if (squeeze) {
3122
- computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
3123
- }
3124
-
3125
- // Create dash pattern
3126
- if (dash) {
3127
- // here we offset the stroke position depending on whether it
3128
- // should overlap or not
3129
- float offset = 1.0 / dashRepeats * dashLength / 2.0;
3130
- if (!dashInvert) {
3131
- offset += 1.0 / dashRepeats / 2.0;
3132
- }
3133
-
3134
- // if we should animate the dash or not
3135
- // if (dashAnimate) {
3136
- // offset += time * 0.22;
3137
- // }
3138
-
3139
- // create the repeating dash pattern
3140
- float pattern = fract((positionAlong + offset) * dashRepeats);
3141
- computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
3142
- }
3143
-
3144
- // compute the anti-aliased stroke edge
3145
- float edge = 1.0 - wireframe_aastep(computedThickness, d);
3146
-
3147
- return edge;
3148
- }
3149
- `
3150
- )
3151
- };
3152
- const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
3153
- WireframeMaterialShaders.uniforms,
3154
- WireframeMaterialShaders.vertex + /* glsl */
3155
- `
3156
- void main() {
3157
- initWireframe();
3158
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
3159
- }
3160
- `,
3161
- WireframeMaterialShaders.fragment + /* glsl */
3162
- `
3163
- void main () {
3164
- // Compute color
3165
-
3166
- float edge = getWireframe();
3167
- vec4 colorStroke = vec4(stroke, edge);
3168
-
3169
- #ifdef FLIP_SIDED
3170
- colorStroke.rgb = backfaceStroke;
3171
- #endif
3172
-
3173
- vec4 colorFill = vec4(fill, fillOpacity);
3174
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
3175
-
3176
- gl_FragColor = outColor;
3177
- }
3178
- `
3179
- );
3180
- function setWireframeOverride(material, uniforms) {
3181
- material.onBeforeCompile = (shader) => {
3182
- shader.uniforms = {
3183
- ...shader.uniforms,
3184
- ...uniforms
3185
- };
3186
- shader.vertexShader = shader.vertexShader.replace(
3187
- "void main() {",
3188
- `
3189
- ${WireframeMaterialShaders.vertex}
3190
- void main() {
3191
- initWireframe();
3192
- `
3193
- );
3194
- shader.fragmentShader = shader.fragmentShader.replace(
3195
- "void main() {",
3196
- `
3197
- ${WireframeMaterialShaders.fragment}
3198
- void main() {
3199
- `
3200
- );
3201
- shader.fragmentShader = shader.fragmentShader.replace(
3202
- "#include <color_fragment>",
3203
- /* glsl */
3204
- `
3205
- #include <color_fragment>
3206
- float edge = getWireframe();
3207
- vec4 colorStroke = vec4(stroke, edge);
3208
- #ifdef FLIP_SIDED
3209
- colorStroke.rgb = backfaceStroke;
3210
- #endif
3211
- vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
3212
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
3213
-
3214
- diffuseColor.rgb = outColor.rgb;
3215
- diffuseColor.a *= outColor.a;
3216
- `
3217
- );
3218
- };
3219
- material.side = THREE.DoubleSide;
3220
- material.transparent = true;
3221
- }
3222
- function useWireframeUniforms(uniforms, props) {
3223
- React.useEffect(
3224
- () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
3225
- [props.fillOpacity]
3226
- );
3227
- React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
3228
- React.useEffect(
3229
- () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
3230
- [props.strokeOpacity]
3231
- );
3232
- React.useEffect(
3233
- () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
3234
- [props.thickness]
3235
- );
3236
- React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
3237
- React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
3238
- React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
3239
- React.useEffect(
3240
- () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
3241
- [props.dashRepeats]
3242
- );
3243
- React.useEffect(
3244
- () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
3245
- [props.dashLength]
3246
- );
3247
- React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
3248
- React.useEffect(
3249
- () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
3250
- [props.squeezeMin]
3251
- );
3252
- React.useEffect(
3253
- () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
3254
- [props.squeezeMax]
3255
- );
3256
- React.useEffect(
3257
- () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
3258
- [props.stroke]
3259
- );
3260
- React.useEffect(
3261
- () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
3262
- [props.fill]
3263
- );
3264
- React.useEffect(
3265
- () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
3266
- [props.backfaceStroke]
3267
- );
3268
- }
3269
-
3270
- function isWithGeometry(object) {
3271
- return !!object?.geometry;
3272
- }
3273
- function isGeometry(object) {
3274
- return !!object?.isBufferGeometry;
3275
- }
3276
- function isRefObject$1(object) {
3277
- return !!object?.current;
3278
- }
3279
- function isRef$1(object) {
3280
- return object?.current !== void 0;
3281
- }
3282
- function isWireframeGeometry(geometry) {
3283
- return geometry.type === "WireframeGeometry";
3284
- }
3285
- function getUniforms() {
3286
- const u = {};
3287
- for (const key in WireframeMaterialShaders.uniforms) {
3288
- u[key] = { value: WireframeMaterialShaders.uniforms[key] };
3289
- }
3290
- return u;
3291
- }
3292
- function getBarycentricCoordinates(geometry, removeEdge) {
3293
- const position = geometry.getAttribute("position");
3294
- const count = position.count;
3295
- const barycentric = [];
3296
- for (let i = 0; i < count; i++) {
3297
- const even = i % 2 === 0;
3298
- const Q = removeEdge ? 1 : 0;
3299
- if (even) {
3300
- barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
3301
- } else {
3302
- barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
3303
- }
3304
- }
3305
- return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
3306
- }
3307
- function getInputGeometry(inputGeometry) {
3308
- const geo = isRefObject$1(inputGeometry) ? inputGeometry.current : inputGeometry;
3309
- if (!isGeometry(geo)) {
3310
- if (isWireframeGeometry(geo)) {
3311
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3312
- }
3313
- const parent = geo.parent;
3314
- if (isWithGeometry(parent)) {
3315
- if (isWireframeGeometry(parent.geometry)) {
3316
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3317
- }
3318
- return parent.geometry;
3319
- }
3320
- } else {
3321
- return geo;
3322
- }
3323
- }
3324
- function setBarycentricCoordinates(geometry, simplify) {
3325
- if (geometry.index) {
3326
- console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
3327
- const nonIndexedGeo = geometry.toNonIndexed();
3328
- geometry.copy(nonIndexedGeo);
3329
- geometry.setIndex(null);
3330
- }
3331
- const newBarycentric = getBarycentricCoordinates(geometry, simplify);
3332
- geometry.setAttribute("barycentric", newBarycentric);
3333
- }
3334
- function WireframeWithCustomGeo({
3335
- geometry: customGeometry,
3336
- simplify = false,
3337
- ...props
3338
- }) {
3339
- extend({ MeshWireframeMaterial: WireframeMaterial });
3340
- const [geometry, setGeometry] = React.useState(null);
3341
- React.useLayoutEffect(() => {
3342
- const geom = getInputGeometry(customGeometry);
3343
- if (!geom) {
3344
- throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
3345
- }
3346
- setBarycentricCoordinates(geom, simplify);
3347
- if (isRef$1(customGeometry)) {
3348
- setGeometry(geom);
3349
- }
3350
- }, [simplify, customGeometry]);
3351
- const drawnGeo = isRef$1(customGeometry) ? geometry : customGeometry;
3352
- return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
3353
- "meshWireframeMaterial",
3354
- {
3355
- attach: "material",
3356
- transparent: true,
3357
- side: THREE.DoubleSide,
3358
- polygonOffset: true,
3359
- polygonOffsetFactor: -4,
3360
- ...props
3361
- }
3362
- )));
3363
- }
3364
- function WireframeWithoutCustomGeo({
3365
- simplify = false,
3366
- ...props
3367
- }) {
3368
- const objectRef = React.useRef(null);
3369
- const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
3370
- useWireframeUniforms(uniforms, props);
3371
- React.useLayoutEffect(() => {
3372
- const geom = getInputGeometry(objectRef);
3373
- if (!geom) {
3374
- throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
3375
- }
3376
- const og = geom.clone();
3377
- setBarycentricCoordinates(geom, simplify);
3378
- return () => {
3379
- geom.copy(og);
3380
- og.dispose();
3381
- };
3382
- }, [simplify]);
3383
- React.useLayoutEffect(() => {
3384
- const parentMesh = objectRef.current.parent;
3385
- const og = parentMesh.material.clone();
3386
- setWireframeOverride(parentMesh.material, uniforms);
3387
- return () => {
3388
- parentMesh.material.dispose();
3389
- parentMesh.material = og;
3390
- };
3391
- }, []);
3392
- return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
3393
- }
3394
- function Wireframe({ geometry: customGeometry, ...props }) {
3395
- if (customGeometry) {
3396
- return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
3397
- }
3398
- return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
3399
- }
3400
-
3401
3136
  const Context = /* @__PURE__ */ React.createContext({});
3402
3137
  const useGizmoContext = () => {
3403
3138
  return React.useContext(Context);
@@ -3693,6 +3428,11 @@ const GizmoViewport = ({
3693
3428
  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 }))));
3694
3429
  };
3695
3430
 
3431
+ const roundEven = (n) => {
3432
+ const rounded = Math.ceil(n);
3433
+ return rounded % 2 === 0 ? rounded : rounded + 1;
3434
+ };
3435
+
3696
3436
  const v1 = /* @__PURE__ */ new Vector3();
3697
3437
  const v2 = /* @__PURE__ */ new Vector3();
3698
3438
  const v3 = /* @__PURE__ */ new Vector3();
@@ -3791,9 +3531,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
3791
3531
  as = "div",
3792
3532
  wrapperClass,
3793
3533
  pointerEvents = "auto",
3534
+ pixelPerfect = true,
3794
3535
  ...props
3795
3536
  }, ref) => {
3796
3537
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
3538
+ const forceEven = useThree((state) => state.internal.forceEven);
3797
3539
  const [el] = React.useState(() => document.createElement(as));
3798
3540
  const root = React.useRef(null);
3799
3541
  const group = React.useRef(null);
@@ -3818,38 +3560,54 @@ const Html = /* @__PURE__ */ React.forwardRef(
3818
3560
  el2.style.position = null;
3819
3561
  el2.style.pointerEvents = null;
3820
3562
  }
3821
- }, [occlude]);
3563
+ }, [occlude, zIndexRange, gl.domElement]);
3822
3564
  React.useLayoutEffect(() => {
3823
3565
  if (group.current) {
3824
- const currentRoot = root.current = ReactDOM.createRoot(el);
3566
+ if (!root.current) {
3567
+ root.current = ReactDOM.createRoot(el);
3568
+ }
3825
3569
  scene.updateMatrixWorld();
3826
3570
  if (transform) {
3827
3571
  el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3828
3572
  } else {
3829
- const vec = calculatePosition(group.current, camera, size);
3830
- el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(${vec[0]}px,${vec[1]}px,0);transform-origin:0 0;`;
3573
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
3831
3574
  }
3832
3575
  if (target) {
3833
3576
  if (prepend) target.prepend(el);
3834
3577
  else target.appendChild(el);
3835
3578
  }
3836
3579
  return () => {
3837
- if (target) target.removeChild(el);
3838
- currentRoot.unmount();
3580
+ if (root.current) {
3581
+ root.current.unmount();
3582
+ root.current = null;
3583
+ }
3584
+ if (target && target.contains(el)) {
3585
+ target.removeChild(el);
3586
+ }
3839
3587
  };
3840
3588
  }
3841
- }, [target, transform]);
3589
+ }, [target, transform, el, prepend, scene]);
3842
3590
  React.useLayoutEffect(() => {
3843
3591
  if (wrapperClass) el.className = wrapperClass;
3844
- }, [wrapperClass]);
3592
+ }, [wrapperClass, el]);
3593
+ React.useLayoutEffect(() => {
3594
+ return () => {
3595
+ if (root.current) {
3596
+ root.current.unmount();
3597
+ root.current = null;
3598
+ }
3599
+ };
3600
+ }, []);
3845
3601
  const styles = React.useMemo(() => {
3602
+ const width = forceEven ? roundEven(size.width) : size.width;
3603
+ const height = forceEven ? roundEven(size.height) : size.height;
3846
3604
  if (transform) {
3847
3605
  return {
3848
3606
  position: "absolute",
3849
3607
  top: 0,
3850
3608
  left: 0,
3851
- width: size.width,
3852
- height: size.height,
3609
+ width,
3610
+ height,
3853
3611
  transformStyle: "preserve-3d",
3854
3612
  pointerEvents: "none"
3855
3613
  };
@@ -3858,15 +3616,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
3858
3616
  position: "absolute",
3859
3617
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
3860
3618
  ...fullscreen && {
3861
- top: -size.height / 2,
3862
- left: -size.width / 2,
3863
- width: size.width,
3864
- height: size.height
3619
+ top: forceEven ? roundEven(-size.height / 2) : -size.height / 2,
3620
+ left: forceEven ? roundEven(-size.width / 2) : -size.width / 2,
3621
+ width,
3622
+ height
3865
3623
  },
3866
3624
  ...style
3867
3625
  };
3868
3626
  }
3869
- }, [style, center, fullscreen, size, transform]);
3627
+ }, [style, center, fullscreen, size, transform, forceEven]);
3870
3628
  const transformInnerStyles = React.useMemo(
3871
3629
  () => ({ position: "absolute", pointerEvents }),
3872
3630
  [pointerEvents]
@@ -3880,7 +3638,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3880
3638
  } else {
3881
3639
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
3882
3640
  }
3883
- });
3641
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
3884
3642
  const visible = React.useRef(true);
3885
3643
  useFrame((gl2) => {
3886
3644
  if (group.current) {
@@ -3912,7 +3670,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3912
3670
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
3913
3671
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
3914
3672
  if (transform) {
3915
- const [widthHalf, heightHalf] = [size.width / 2, size.height / 2];
3673
+ let widthHalf = forceEven ? roundEven(size.width / 2) : size.width / 2;
3674
+ let heightHalf = forceEven ? roundEven(size.height / 2) : size.height / 2;
3675
+ if (pixelPerfect) {
3676
+ widthHalf = roundToPixelRatio(widthHalf);
3677
+ heightHalf = roundToPixelRatio(heightHalf);
3678
+ }
3916
3679
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
3917
3680
  const { isOrthographicCamera, top, left, bottom, right } = camera;
3918
3681
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -3923,8 +3686,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
3923
3686
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
3924
3687
  matrix.elements[15] = 1;
3925
3688
  }
3926
- el.style.width = size.width + "px";
3927
- el.style.height = size.height + "px";
3689
+ const elWidth = forceEven ? roundEven(size.width) : size.width;
3690
+ const elHeight = forceEven ? roundEven(size.height) : size.height;
3691
+ el.style.width = elWidth + "px";
3692
+ el.style.height = elHeight + "px";
3928
3693
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
3929
3694
  if (transformOuterRef.current && transformInnerRef.current) {
3930
3695
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -3932,7 +3697,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
3932
3697
  }
3933
3698
  } else {
3934
3699
  const scale = distanceFactor === void 0 ? 1 : objectScale(group.current, camera) * distanceFactor;
3935
- el.style.transform = `translate3d(${vec[0]}px,${vec[1]}px,0) scale(${scale})`;
3700
+ let x = forceEven ? roundEven(vec[0]) : vec[0];
3701
+ let y = forceEven ? roundEven(vec[1]) : vec[1];
3702
+ if (pixelPerfect) {
3703
+ x = roundToPixelRatio(x);
3704
+ y = roundToPixelRatio(y);
3705
+ }
3706
+ el.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`;
3936
3707
  }
3937
3708
  oldPosition.current = vec;
3938
3709
  oldZoom.current = camera.zoom;
@@ -4038,6 +3809,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
4038
3809
  );
4039
3810
 
4040
3811
  const context$4 = /* @__PURE__ */ React.createContext(null);
3812
+ const resolveObject = (object, fallback) => {
3813
+ if (!object) return fallback;
3814
+ if ("current" in object) return object.current;
3815
+ return object;
3816
+ };
4041
3817
 
4042
3818
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
4043
3819
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -4072,6 +3848,8 @@ const AxisArrow = ({ direction, axis }) => {
4072
3848
  onDragStart,
4073
3849
  onDrag,
4074
3850
  onDragEnd,
3851
+ onHover,
3852
+ dragState,
4075
3853
  userData,
4076
3854
  LineComponent: Line
4077
3855
  } = React.useContext(context$4);
@@ -4103,7 +3881,13 @@ const AxisArrow = ({ direction, axis }) => {
4103
3881
  const onPointerMove = React.useCallback(
4104
3882
  (e) => {
4105
3883
  e.stopPropagation();
4106
- if (!isHovered) setIsHovered(true);
3884
+ if (!isHovered) {
3885
+ const drag = dragState.current;
3886
+ if (drag === null || drag.component === "Arrow" && drag.axis === axis) {
3887
+ setIsHovered(true);
3888
+ onHover({ component: "Arrow", axis, hovering: true });
3889
+ }
3890
+ }
4107
3891
  if (clickInfo.current) {
4108
3892
  const { clickPoint, dir } = clickInfo.current;
4109
3893
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -4122,7 +3906,7 @@ const AxisArrow = ({ direction, axis }) => {
4122
3906
  onDrag(offsetMatrix$1);
4123
3907
  }
4124
3908
  },
4125
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
3909
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4126
3910
  );
4127
3911
  const onPointerUp = React.useCallback(
4128
3912
  (e) => {
@@ -4137,10 +3921,16 @@ const AxisArrow = ({ direction, axis }) => {
4137
3921
  },
4138
3922
  [annotations, camControls, onDragEnd]
4139
3923
  );
4140
- const onPointerOut = React.useCallback((e) => {
4141
- e.stopPropagation();
4142
- setIsHovered(false);
4143
- }, []);
3924
+ const onPointerOut = React.useCallback(
3925
+ (e) => {
3926
+ e.stopPropagation();
3927
+ if (isHovered) {
3928
+ setIsHovered(false);
3929
+ onHover({ component: "Arrow", axis, hovering: false });
3930
+ }
3931
+ },
3932
+ [onHover, axis, isHovered]
3933
+ );
4144
3934
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
4145
3935
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
4146
3936
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -4244,6 +4034,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
4244
4034
  const posNew = /* @__PURE__ */ new THREE.Vector3();
4245
4035
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
4246
4036
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
4037
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
4038
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
4247
4039
  const AxisRotator = ({
4248
4040
  dir1,
4249
4041
  dir2,
@@ -4264,6 +4056,8 @@ const AxisRotator = ({
4264
4056
  onDragStart,
4265
4057
  onDrag,
4266
4058
  onDragEnd,
4059
+ onHover,
4060
+ dragState,
4267
4061
  userData,
4268
4062
  LineComponent: Line
4269
4063
  } = React.useContext(context$4);
@@ -4297,14 +4091,29 @@ const AxisRotator = ({
4297
4091
  const onPointerMove = React.useCallback(
4298
4092
  (e) => {
4299
4093
  e.stopPropagation();
4300
- if (!isHovered) setIsHovered(true);
4094
+ if (!isHovered) {
4095
+ const drag = dragState.current;
4096
+ if (drag === null || drag.component === "Rotator" && drag.axis === axis) {
4097
+ setIsHovered(true);
4098
+ onHover({ component: "Rotator", axis, hovering: true });
4099
+ }
4100
+ }
4301
4101
  if (clickInfo.current) {
4302
4102
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
4303
4103
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
4304
4104
  ray$1.copy(e.ray);
4305
- ray$1.intersectPlane(plane, intersection$1);
4306
- ray$1.direction.negate();
4307
- ray$1.intersectPlane(plane, intersection$1);
4105
+ const dotProduct = Math.abs(ray$1.direction.dot(normal));
4106
+ if (dotProduct < 0.1) {
4107
+ viewPlane.setFromNormalAndCoplanarPoint(ray$1.direction.clone().negate(), origin);
4108
+ ray$1.intersectPlane(viewPlane, viewIntersection);
4109
+ const toIntersection = viewIntersection.clone().sub(origin);
4110
+ const projectedDist = toIntersection.dot(normal);
4111
+ intersection$1.copy(viewIntersection).sub(normal.clone().multiplyScalar(projectedDist));
4112
+ } else {
4113
+ ray$1.intersectPlane(plane, intersection$1);
4114
+ ray$1.direction.negate();
4115
+ ray$1.intersectPlane(plane, intersection$1);
4116
+ }
4308
4117
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
4309
4118
  let degrees = toDegrees(deltaAngle);
4310
4119
  if (e.shiftKey) {
@@ -4330,7 +4139,7 @@ const AxisRotator = ({
4330
4139
  onDrag(rotMatrix);
4331
4140
  }
4332
4141
  },
4333
- [annotations, onDrag, isHovered, rotationLimits, axis]
4142
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
4334
4143
  );
4335
4144
  const onPointerUp = React.useCallback(
4336
4145
  (e) => {
@@ -4346,10 +4155,16 @@ const AxisRotator = ({
4346
4155
  },
4347
4156
  [annotations, camControls, onDragEnd]
4348
4157
  );
4349
- const onPointerOut = React.useCallback((e) => {
4350
- e.stopPropagation();
4351
- setIsHovered(false);
4352
- }, []);
4158
+ const onPointerOut = React.useCallback(
4159
+ (e) => {
4160
+ e.stopPropagation();
4161
+ if (isHovered) {
4162
+ setIsHovered(false);
4163
+ onHover({ component: "Rotator", axis, hovering: false });
4164
+ }
4165
+ },
4166
+ [onHover, axis, isHovered]
4167
+ );
4353
4168
  const matrixL = React.useMemo(() => {
4354
4169
  const dir1N = dir1.clone().normalize();
4355
4170
  const dir2N = dir2.clone().normalize();
@@ -4450,6 +4265,8 @@ const PlaneSlider = ({
4450
4265
  onDragStart,
4451
4266
  onDrag,
4452
4267
  onDragEnd,
4268
+ onHover,
4269
+ dragState,
4453
4270
  userData,
4454
4271
  LineComponent: Line
4455
4272
  } = React.useContext(context$4);
@@ -4485,7 +4302,13 @@ const PlaneSlider = ({
4485
4302
  const onPointerMove = React.useCallback(
4486
4303
  (e) => {
4487
4304
  e.stopPropagation();
4488
- if (!isHovered) setIsHovered(true);
4305
+ if (!isHovered) {
4306
+ const drag = dragState.current;
4307
+ if (drag === null || drag.component === "Slider" && drag.axis === axis) {
4308
+ setIsHovered(true);
4309
+ onHover({ component: "Slider", axis, hovering: true });
4310
+ }
4311
+ }
4489
4312
  if (clickInfo.current) {
4490
4313
  const { clickPoint, e1, e2, plane } = clickInfo.current;
4491
4314
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -4521,7 +4344,7 @@ const PlaneSlider = ({
4521
4344
  onDrag(offsetMatrix);
4522
4345
  }
4523
4346
  },
4524
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
4347
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4525
4348
  );
4526
4349
  const onPointerUp = React.useCallback(
4527
4350
  (e) => {
@@ -4536,10 +4359,16 @@ const PlaneSlider = ({
4536
4359
  },
4537
4360
  [annotations, camControls, onDragEnd]
4538
4361
  );
4539
- const onPointerOut = React.useCallback((e) => {
4540
- e.stopPropagation();
4541
- setIsHovered(false);
4542
- }, []);
4362
+ const onPointerOut = React.useCallback(
4363
+ (e) => {
4364
+ e.stopPropagation();
4365
+ if (isHovered) {
4366
+ setIsHovered(false);
4367
+ onHover({ component: "Slider", axis, hovering: false });
4368
+ }
4369
+ },
4370
+ [onHover, axis, isHovered]
4371
+ );
4543
4372
  const matrixL = React.useMemo(() => {
4544
4373
  const dir1N = dir1.clone().normalize();
4545
4374
  const dir2N = dir2.clone().normalize();
@@ -4677,6 +4506,8 @@ const ScalingSphere = ({ direction, axis }) => {
4677
4506
  onDragStart,
4678
4507
  onDrag,
4679
4508
  onDragEnd,
4509
+ onHover,
4510
+ dragState,
4680
4511
  userData
4681
4512
  } = React.useContext(context$4);
4682
4513
  const size = useThree((state) => state.size);
@@ -4713,7 +4544,13 @@ const ScalingSphere = ({ direction, axis }) => {
4713
4544
  const onPointerMove = React.useCallback(
4714
4545
  (e) => {
4715
4546
  e.stopPropagation();
4716
- if (!isHovered) setIsHovered(true);
4547
+ if (!isHovered) {
4548
+ const drag = dragState.current;
4549
+ if (drag === null || drag.component === "Sphere" && drag.axis === axis) {
4550
+ setIsHovered(true);
4551
+ onHover({ component: "Sphere", axis, hovering: true });
4552
+ }
4553
+ }
4717
4554
  if (clickInfo.current) {
4718
4555
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
4719
4556
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -4739,7 +4576,7 @@ const ScalingSphere = ({ direction, axis }) => {
4739
4576
  onDrag(scaleMatrix);
4740
4577
  }
4741
4578
  },
4742
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
4579
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
4743
4580
  );
4744
4581
  const onPointerUp = React.useCallback(
4745
4582
  (e) => {
@@ -4756,10 +4593,16 @@ const ScalingSphere = ({ direction, axis }) => {
4756
4593
  },
4757
4594
  [annotations, camControls, onDragEnd, position]
4758
4595
  );
4759
- const onPointerOut = React.useCallback((e) => {
4760
- e.stopPropagation();
4761
- setIsHovered(false);
4762
- }, []);
4596
+ const onPointerOut = React.useCallback(
4597
+ (e) => {
4598
+ e.stopPropagation();
4599
+ if (isHovered) {
4600
+ setIsHovered(false);
4601
+ onHover({ component: "Sphere", axis, hovering: false });
4602
+ }
4603
+ },
4604
+ [onHover, axis, isHovered]
4605
+ );
4763
4606
  const { radius, matrixL } = React.useMemo(() => {
4764
4607
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
4765
4608
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -4829,9 +4672,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4829
4672
  ({
4830
4673
  enabled = true,
4831
4674
  matrix,
4675
+ object,
4832
4676
  onDragStart,
4833
4677
  onDrag,
4834
4678
  onDragEnd,
4679
+ onHover,
4835
4680
  autoTransform = true,
4836
4681
  anchor,
4837
4682
  disableAxes = false,
@@ -4867,14 +4712,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4867
4712
  const gizmoRef = React.useRef(null);
4868
4713
  const childrenRef = React.useRef(null);
4869
4714
  const translation = React.useRef([0, 0, 0]);
4715
+ const dragState = React.useRef(null);
4870
4716
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
4871
4717
  const gizmoScale = React.useRef(new THREE.Vector3(1, 1, 1));
4718
+ React.useEffect(() => {
4719
+ if (object) {
4720
+ const target = resolveObject(object);
4721
+ if (target) {
4722
+ const pivot = ref.current;
4723
+ const didAutoUpdate = target.matrixAutoUpdate;
4724
+ target.updateWorldMatrix(true, true);
4725
+ target.matrixAutoUpdate = false;
4726
+ pivot.matrix.copy(target.matrix);
4727
+ return () => {
4728
+ if (didAutoUpdate) {
4729
+ target.matrixAutoUpdate = true;
4730
+ target.matrix.decompose(target.position, target.quaternion, target.scale);
4731
+ }
4732
+ };
4733
+ }
4734
+ }
4735
+ }, [object]);
4872
4736
  React.useLayoutEffect(() => {
4873
4737
  if (!anchor) return;
4874
- childrenRef.current.updateWorldMatrix(true, true);
4875
- mPInv.copy(childrenRef.current.matrixWorld).invert();
4738
+ const anchorTarget = resolveObject(object, childrenRef.current);
4739
+ if (!anchorTarget) return;
4740
+ anchorTarget.updateWorldMatrix(true, true);
4741
+ mPInv.copy(anchorTarget.matrixWorld).invert();
4876
4742
  bb.makeEmpty();
4877
- childrenRef.current.traverse((obj) => {
4743
+ anchorTarget.traverse((obj) => {
4878
4744
  if (!obj.geometry) return;
4879
4745
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
4880
4746
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -4889,9 +4755,17 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4889
4755
  gizmoRef.current.position.copy(vPosition);
4890
4756
  invalidate();
4891
4757
  });
4758
+ const mergedUserData = React.useMemo(
4759
+ () => ({
4760
+ ...userData,
4761
+ ...depthTest === false && { interactivePriority: 1 }
4762
+ }),
4763
+ [userData, depthTest]
4764
+ );
4892
4765
  const config = React.useMemo(
4893
4766
  () => ({
4894
4767
  onDragStart: (props2) => {
4768
+ dragState.current = props2;
4895
4769
  mL0.copy(ref.current.matrix);
4896
4770
  mW0.copy(ref.current.matrixWorld);
4897
4771
  onDragStart && onDragStart(props2);
@@ -4907,13 +4781,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4907
4781
  if (autoTransform) {
4908
4782
  ref.current.matrix.copy(mL);
4909
4783
  }
4784
+ const target = resolveObject(object);
4785
+ if (target) target.matrix.copy(mL);
4910
4786
  onDrag && onDrag(mL, mdL, mW, mdW);
4911
4787
  invalidate();
4912
4788
  },
4913
4789
  onDragEnd: () => {
4790
+ dragState.current = null;
4914
4791
  if (onDragEnd) onDragEnd();
4915
4792
  invalidate();
4916
4793
  },
4794
+ onHover: (props2) => {
4795
+ if (onHover) onHover(props2);
4796
+ },
4797
+ dragState,
4917
4798
  translation,
4918
4799
  translationLimits,
4919
4800
  rotationLimits,
@@ -4925,7 +4806,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4925
4806
  fixed,
4926
4807
  depthTest,
4927
4808
  renderOrder,
4928
- userData,
4809
+ userData: mergedUserData,
4929
4810
  annotations,
4930
4811
  annotationsClass,
4931
4812
  LineComponent: LineComp
@@ -4934,6 +4815,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4934
4815
  onDragStart,
4935
4816
  onDrag,
4936
4817
  onDragEnd,
4818
+ onHover,
4937
4819
  translation,
4938
4820
  translationLimits,
4939
4821
  rotationLimits,
@@ -4945,11 +4827,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4945
4827
  ...axisColors,
4946
4828
  hoveredColor,
4947
4829
  opacity,
4948
- userData,
4830
+ mergedUserData,
4949
4831
  autoTransform,
4950
4832
  annotations,
4951
4833
  annotationsClass,
4952
- LineComp
4834
+ LineComp,
4835
+ object
4953
4836
  ]
4954
4837
  );
4955
4838
  const vec = new THREE.Vector3();
@@ -5311,7 +5194,7 @@ function createInstances() {
5311
5194
  ];
5312
5195
  }
5313
5196
  const InstancedAttribute = React.forwardRef(
5314
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
5197
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
5315
5198
  const ref = React.useRef(null);
5316
5199
  React.useImperativeHandle(fref, () => ref.current, []);
5317
5200
  React.useLayoutEffect(() => {
@@ -5319,7 +5202,26 @@ const InstancedAttribute = React.forwardRef(
5319
5202
  parent.geometry.attributes[name] = ref.current;
5320
5203
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
5321
5204
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
5322
- ref.current.array = new Float32Array(array);
5205
+ switch (type) {
5206
+ case "float":
5207
+ ref.current.array = new Float32Array(array);
5208
+ break;
5209
+ case "int":
5210
+ ref.current.array = new Int32Array(array);
5211
+ break;
5212
+ case "uint":
5213
+ ref.current.array = new Uint32Array(array);
5214
+ break;
5215
+ case "short":
5216
+ ref.current.array = new Int16Array(array);
5217
+ break;
5218
+ case "ushort":
5219
+ ref.current.array = new Uint16Array(array);
5220
+ break;
5221
+ case "byte":
5222
+ ref.current.array = new Uint8Array(array);
5223
+ break;
5224
+ }
5323
5225
  ref.current.itemSize = value.length;
5324
5226
  ref.current.count = array.length / ref.current.itemSize;
5325
5227
  return () => {
@@ -6844,12 +6746,17 @@ const Gltf = /* @__PURE__ */ React.forwardRef(
6844
6746
  }
6845
6747
  );
6846
6748
 
6847
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
6848
- function useKTX2(input, basisPath = `${cdn}/basis/`) {
6749
+ let transcoderPath = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
6750
+ function useKTX2(input, basisPath = true, extendLoader) {
6849
6751
  const renderer = useThree((state) => state.renderer);
6850
6752
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
6753
+ if (extendLoader) {
6754
+ extendLoader(loader);
6755
+ }
6756
+ if (basisPath) {
6757
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
6758
+ }
6851
6759
  loader.detectSupport(renderer);
6852
- loader.setTranscoderPath(basisPath);
6853
6760
  });
6854
6761
  useEffect(() => {
6855
6762
  const array = Array.isArray(textures) ? textures : [textures];
@@ -6864,16 +6771,20 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
6864
6771
  return textures;
6865
6772
  }
6866
6773
  }
6867
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
6868
- loader.setTranscoderPath(basisPath);
6774
+ useKTX2.preload = (url, basisPath = true, extendLoader) => useLoader.preload(KTX2Loader, url, (loader) => {
6775
+ if (extendLoader) {
6776
+ extendLoader(loader);
6777
+ }
6778
+ if (basisPath) {
6779
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
6780
+ }
6869
6781
  });
6870
6782
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
6871
- const Ktx2 = ({
6872
- children,
6873
- input,
6874
- basisPath
6875
- }) => {
6876
- const texture = useKTX2(input, basisPath);
6783
+ useKTX2.setTranscoderPath = (path) => {
6784
+ transcoderPath = path;
6785
+ };
6786
+ const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
6787
+ const texture = useKTX2(input, basisPath, extendLoader);
6877
6788
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
6878
6789
  };
6879
6790
 
@@ -7349,18 +7260,21 @@ function Fisheye({
7349
7260
  const normal = new THREE.Vector3();
7350
7261
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
7351
7262
  const normalMatrix = new THREE.Matrix3();
7352
- const compute = React.useCallback((event, state, prev) => {
7353
- state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7354
- state.raycaster.setFromCamera(state.pointer, orthoC);
7355
- if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7356
- else normal.normalize();
7357
- normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7358
- cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7359
- state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7360
- state.raycaster.ray.direction.x *= -1;
7361
- state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7362
- return void 0;
7363
- }, []);
7263
+ const compute = React.useCallback(
7264
+ (event, state, prev) => {
7265
+ state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7266
+ state.raycaster.setFromCamera(state.pointer, orthoC);
7267
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7268
+ else normal.normalize();
7269
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7270
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7271
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7272
+ state.raycaster.ray.direction.x *= -1;
7273
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7274
+ return void 0;
7275
+ },
7276
+ [width, height]
7277
+ );
7364
7278
  useFrame(
7365
7279
  ({ gl }) => {
7366
7280
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -7495,14 +7409,22 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
7495
7409
  const col = /* @__PURE__ */ new THREE.Color();
7496
7410
  const TUNNEL_KEY = "__drei_view_tunnel__";
7497
7411
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
7498
- function computeContainerPosition(canvasSize, trackRect) {
7412
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
7499
7413
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
7500
7414
  const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
7501
7415
  const canvasBottom = canvasSize.top + canvasSize.height;
7502
- const bottom = canvasBottom - trackBottom;
7503
- const left = trackLeft - canvasSize.left;
7504
- const top = trackTop - canvasSize.top;
7505
- return { position: { width, height, left, top, bottom, right }, isOffscreen };
7416
+ const rawBottom = canvasBottom - trackBottom;
7417
+ const rawLeft = trackLeft - canvasSize.left;
7418
+ const rawTop = trackTop - canvasSize.top;
7419
+ const position = forceEven ? {
7420
+ width: roundEven(width),
7421
+ height: roundEven(height),
7422
+ left: roundEven(rawLeft),
7423
+ top: roundEven(rawTop),
7424
+ bottom: roundEven(rawBottom),
7425
+ right: roundEven(right)
7426
+ } : { width, height, left: rawLeft, top: rawTop, bottom: rawBottom, right };
7427
+ return { position, isOffscreen };
7506
7428
  }
7507
7429
  function prepareSkissor(renderer, camera, {
7508
7430
  left,
@@ -7542,6 +7464,7 @@ function clear(renderer) {
7542
7464
  }
7543
7465
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
7544
7466
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
7467
+ const forceEven = useThree((state) => state.internal.forceEven);
7545
7468
  const [isOffscreen, setOffscreen] = React.useState(false);
7546
7469
  const mountedRef = React.useRef(true);
7547
7470
  React.useEffect(() => {
@@ -7559,7 +7482,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7559
7482
  frameCount++;
7560
7483
  }
7561
7484
  if (rect.current) {
7562
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
7485
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
7563
7486
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
7564
7487
  if (visible && !_isOffscreen && rect.current) {
7565
7488
  const targetScene = children ? portalScene : scene;
@@ -7581,14 +7504,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7581
7504
  React.useLayoutEffect(() => {
7582
7505
  const curRect = rect.current;
7583
7506
  if (curRect && (!visible || !isOffscreen)) {
7584
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7507
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7585
7508
  if (!_isOffscreen) {
7586
7509
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7587
7510
  clear(renderer);
7588
7511
  finishSkissor(renderer, autoClear);
7589
7512
  }
7590
7513
  }
7591
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
7514
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
7592
7515
  React.useEffect(() => {
7593
7516
  if (!track) return;
7594
7517
  const curRect = rect.current;
@@ -7596,7 +7519,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7596
7519
  setEvents({ connected: track.current });
7597
7520
  return () => {
7598
7521
  if (curRect) {
7599
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7522
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7600
7523
  if (!_isOffscreen) {
7601
7524
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7602
7525
  clear(renderer);
@@ -7605,13 +7528,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7605
7528
  }
7606
7529
  setEvents({ connected: old });
7607
7530
  };
7608
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
7531
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
7609
7532
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
7610
7533
  }
7611
7534
  const CanvasView = /* @__PURE__ */ React.forwardRef(
7612
7535
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
7613
7536
  const rect = React.useRef(null);
7614
7537
  const { size, scene } = useThree();
7538
+ const forceEven = useThree((state) => state.internal.forceEven);
7615
7539
  const [virtualScene] = React.useState(() => new THREE.Scene());
7616
7540
  const [ready, toggle] = React.useReducer(() => true, false);
7617
7541
  const compute = React.useCallback(
@@ -7648,12 +7572,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
7648
7572
  {
7649
7573
  events: { compute, priority: index },
7650
7574
  size: {
7651
- width: rect.current?.width,
7652
- height: rect.current?.height,
7575
+ width: forceEven && rect.current?.width ? roundEven(rect.current.width) : rect.current?.width,
7576
+ height: forceEven && rect.current?.height ? roundEven(rect.current.height) : rect.current?.height,
7653
7577
  // @ts-ignore
7654
- top: rect.current?.top,
7578
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
7655
7579
  // @ts-ignore
7656
- left: rect.current?.left
7580
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
7657
7581
  }
7658
7582
  }
7659
7583
  ));
@@ -13742,4 +13666,4 @@ const MarchingPlane = /* @__PURE__ */ React.forwardRef(
13742
13666
  }
13743
13667
  );
13744
13668
 
13745
- export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, Bvh, CameraControls, 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, FaceControls, FaceLandmarker, FaceLandmarkerDefaults, Facemesh, FacemeshDatas, FacemeshEye, FacemeshEyeDefaults, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, MarchingCube, MarchingCubes, MarchingPlane, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, Outlines, 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, Splat, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, Trail, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, Wireframe, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBVH, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFaceControls, useFaceLandmarker, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrail, useTrailTexture, useVideoTexture };
13669
+ export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, Bvh, CameraControls, 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, FaceControls, FaceLandmarker, FaceLandmarkerDefaults, Facemesh, FacemeshDatas, FacemeshEye, FacemeshEyeDefaults, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, MarchingCube, MarchingCubes, MarchingPlane, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, Outlines, 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, Splat, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, Trail, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBVH, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFaceControls, useFaceLandmarker, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrail, useTrailTexture, useVideoTexture };