@react-three/drei 11.0.0-alpha.4 → 11.0.0-alpha.6

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,10 +2,8 @@ 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, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DoubleSide, 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
- import { useGesture } from '@use-gesture/react';
9
7
  import { FirstPersonControls as FirstPersonControls$1 } from 'three/examples/jsm/controls/FirstPersonControls.js';
10
8
  import { FlyControls as FlyControls$1 } from 'three/examples/jsm/controls/FlyControls.js';
11
9
  import { create as create$1 } from 'zustand';
@@ -14,6 +12,7 @@ import { MapControls as MapControls$1 } from 'three/examples/jsm/controls/MapCon
14
12
  import { misc, easing } from 'maath';
15
13
  import { OrbitControls as OrbitControls$1 } from 'three/addons/controls/OrbitControls.js';
16
14
  import { PointerLockControls as PointerLockControls$1 } from 'three/examples/jsm/controls/PointerLockControls.js';
15
+ import { useGesture } from '@use-gesture/react';
17
16
  import * as ReactDOM from 'react-dom/client';
18
17
  import { TrackballControls as TrackballControls$1 } from 'three/examples/jsm/controls/TrackballControls.js';
19
18
  import { SimplexNoise } from 'three/examples/jsm/math/SimplexNoise.js';
@@ -32,7 +31,6 @@ import { TransformControls as TransformControls$1 } from 'three/examples/jsm/con
32
31
  import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
33
32
  import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
34
33
  import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js';
35
- import { Events } from 'hls.js';
36
34
  import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
37
35
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
38
36
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
@@ -180,6 +178,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
180
178
  });
181
179
  }
182
180
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
181
+ Object.assign(this, parameters);
183
182
  }
184
183
  };
185
184
  }
@@ -196,7 +195,7 @@ const version = /* @__PURE__ */ getVersion();
196
195
  function isLight(object) {
197
196
  return object.isLight;
198
197
  }
199
- function isGeometry$1(object) {
198
+ function isGeometry(object) {
200
199
  return !!object.geometry;
201
200
  }
202
201
  const accumulativeContext = /* @__PURE__ */ React.createContext(
@@ -441,7 +440,7 @@ vec3 texelOld = texture2D(previousShadowMap, vUv).rgb;
441
440
  this.lights = [];
442
441
  this.meshes = [];
443
442
  this.scene.traverse((object) => {
444
- if (isGeometry$1(object)) {
443
+ if (isGeometry(object)) {
445
444
  this.meshes.push({ object, material: object.material });
446
445
  } else if (isLight(object)) {
447
446
  this.lights.push({ object, intensity: object.intensity });
@@ -1062,6 +1061,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
1062
1061
  }
1063
1062
  );
1064
1063
 
1064
+ let DeviceOrientationControls$1 = class DeviceOrientationControls extends EventDispatcher {
1065
+ object;
1066
+ changeEvent = { type: "change" };
1067
+ EPS = 1e-6;
1068
+ enabled = true;
1069
+ deviceOrientation = { alpha: 0, beta: 0, gamma: 0 };
1070
+ screenOrientation = 0;
1071
+ alphaOffset = 0;
1072
+ // radians
1073
+ constructor(object) {
1074
+ super();
1075
+ this.object = object;
1076
+ this.object.rotation.reorder("YXZ");
1077
+ this.connect();
1078
+ }
1079
+ onDeviceOrientationChangeEvent = (event) => {
1080
+ this.deviceOrientation = event;
1081
+ };
1082
+ onScreenOrientationChangeEvent = () => {
1083
+ this.screenOrientation = window.orientation || 0;
1084
+ };
1085
+ // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
1086
+ zee = new Vector3(0, 0, 1);
1087
+ euler = new Euler();
1088
+ q0 = new Quaternion();
1089
+ q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
1090
+ // - PI/2 around the x-axis
1091
+ setObjectQuaternion = (quaternion, alpha, beta, gamma, orient) => {
1092
+ this.euler.set(beta, alpha, -gamma, "YXZ");
1093
+ quaternion.setFromEuler(this.euler);
1094
+ quaternion.multiply(this.q1);
1095
+ quaternion.multiply(this.q0.setFromAxisAngle(this.zee, -orient));
1096
+ };
1097
+ connect = () => {
1098
+ this.onScreenOrientationChangeEvent();
1099
+ if (window.DeviceOrientationEvent !== void 0 && // @ts-ignore
1100
+ typeof window.DeviceOrientationEvent.requestPermission === "function") {
1101
+ window.DeviceOrientationEvent.requestPermission().then((response) => {
1102
+ if (response == "granted") {
1103
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1104
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1105
+ }
1106
+ }).catch((error) => {
1107
+ console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:", error);
1108
+ });
1109
+ } else {
1110
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1111
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1112
+ }
1113
+ this.enabled = true;
1114
+ };
1115
+ disconnect = () => {
1116
+ window.removeEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1117
+ window.removeEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1118
+ this.enabled = false;
1119
+ };
1120
+ lastQuaternion = new Quaternion();
1121
+ update = () => {
1122
+ if (this.enabled === false) return;
1123
+ const device = this.deviceOrientation;
1124
+ if (device) {
1125
+ const alpha = device.alpha ? MathUtils.degToRad(device.alpha) + this.alphaOffset : 0;
1126
+ const beta = device.beta ? MathUtils.degToRad(device.beta) : 0;
1127
+ const gamma = device.gamma ? MathUtils.degToRad(device.gamma) : 0;
1128
+ const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation) : 0;
1129
+ this.setObjectQuaternion(this.object.quaternion, alpha, beta, gamma, orient);
1130
+ if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
1131
+ this.lastQuaternion.copy(this.object.quaternion);
1132
+ this.dispatchEvent(this.changeEvent);
1133
+ }
1134
+ }
1135
+ };
1136
+ dispose = () => this.disconnect();
1137
+ };
1138
+
1065
1139
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1066
1140
  (props, ref) => {
1067
1141
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -1097,11 +1171,11 @@ const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1097
1171
  );
1098
1172
 
1099
1173
  const initialModelPosition = /* @__PURE__ */ new THREE.Vector3();
1100
- const mousePosition2D = /* @__PURE__ */ new THREE.Vector2();
1101
1174
  const mousePosition3D = /* @__PURE__ */ new THREE.Vector3();
1102
1175
  const dragOffset = /* @__PURE__ */ new THREE.Vector3();
1103
1176
  const dragPlaneNormal = /* @__PURE__ */ new THREE.Vector3();
1104
1177
  const dragPlane = /* @__PURE__ */ new THREE.Plane();
1178
+ const startPointerPos = /* @__PURE__ */ new THREE.Vector3();
1105
1179
  const DragControls = React.forwardRef(
1106
1180
  ({
1107
1181
  autoTransform = true,
@@ -1117,89 +1191,132 @@ const DragControls = React.forwardRef(
1117
1191
  ...props
1118
1192
  }, fRef) => {
1119
1193
  const defaultControls = useThree((state) => state.controls);
1120
- const { camera, size, raycaster, invalidate } = useThree();
1194
+ const { camera, invalidate } = useThree();
1121
1195
  const ref = React.useRef(null);
1122
- const bind = useGesture(
1123
- {
1124
- onHover: ({ hovering }) => onHover && onHover(hovering ?? false),
1125
- onDragStart: ({ event }) => {
1126
- if (defaultControls) defaultControls.enabled = false;
1127
- const { point } = event;
1128
- ref.current.matrix.decompose(initialModelPosition, new THREE.Quaternion(), new THREE.Vector3());
1129
- mousePosition3D.copy(point);
1130
- dragOffset.copy(mousePosition3D).sub(initialModelPosition);
1131
- onDragStart && onDragStart(initialModelPosition);
1132
- invalidate();
1133
- },
1134
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
1135
- if (!intentional) return;
1136
- const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
1137
- const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
1138
- mousePosition2D.set(normalizedMouseX, normalizedMouseY);
1139
- raycaster.setFromCamera(mousePosition2D, camera);
1140
- if (!axisLock) {
1141
- camera.getWorldDirection(dragPlaneNormal).negate();
1142
- } else {
1143
- switch (axisLock) {
1144
- case "x":
1145
- dragPlaneNormal.set(1, 0, 0);
1146
- break;
1147
- case "y":
1148
- dragPlaneNormal.set(0, 1, 0);
1149
- break;
1150
- case "z":
1151
- dragPlaneNormal.set(0, 0, 1);
1152
- break;
1153
- }
1154
- }
1155
- dragPlane.setFromNormalAndCoplanarPoint(dragPlaneNormal, mousePosition3D);
1156
- raycaster.ray.intersectPlane(dragPlane, mousePosition3D);
1157
- const previousLocalMatrix = ref.current.matrix.clone();
1158
- const previousWorldMatrix = ref.current.matrixWorld.clone();
1159
- const intendedNewPosition = new THREE.Vector3(
1160
- mousePosition3D.x - dragOffset.x,
1161
- mousePosition3D.y - dragOffset.y,
1162
- mousePosition3D.z - dragOffset.z
1163
- );
1164
- if (dragLimits) {
1165
- intendedNewPosition.x = dragLimits[0] ? Math.max(Math.min(intendedNewPosition.x, dragLimits[0][1]), dragLimits[0][0]) : intendedNewPosition.x;
1166
- intendedNewPosition.y = dragLimits[1] ? Math.max(Math.min(intendedNewPosition.y, dragLimits[1][1]), dragLimits[1][0]) : intendedNewPosition.y;
1167
- intendedNewPosition.z = dragLimits[2] ? Math.max(Math.min(intendedNewPosition.z, dragLimits[2][1]), dragLimits[2][0]) : intendedNewPosition.z;
1168
- }
1169
- if (autoTransform) {
1170
- ref.current.matrix.setPosition(intendedNewPosition);
1171
- const deltaLocalMatrix = ref.current.matrix.clone().multiply(previousLocalMatrix.invert());
1172
- const deltaWorldMatrix = ref.current.matrix.clone().multiply(previousWorldMatrix.invert());
1173
- onDrag && onDrag(ref.current.matrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1174
- } else {
1175
- const tempMatrix = new THREE.Matrix4().copy(ref.current.matrix);
1176
- tempMatrix.setPosition(intendedNewPosition);
1177
- const deltaLocalMatrix = tempMatrix.clone().multiply(previousLocalMatrix.invert());
1178
- const deltaWorldMatrix = tempMatrix.clone().multiply(previousWorldMatrix.invert());
1179
- onDrag && onDrag(tempMatrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1196
+ const dragState = React.useRef({
1197
+ active: false,
1198
+ intentional: false,
1199
+ // Has moved past threshold
1200
+ pointerId: -1
1201
+ });
1202
+ const threshold = dragConfig?.threshold ?? 0;
1203
+ const filterTaps = dragConfig?.filterTaps ?? true;
1204
+ const handlePointerDown = React.useCallback(
1205
+ (event) => {
1206
+ event.stopPropagation();
1207
+ event.target.setPointerCapture(event.pointerId);
1208
+ dragState.current.active = true;
1209
+ dragState.current.intentional = threshold === 0;
1210
+ dragState.current.pointerId = event.pointerId;
1211
+ if (defaultControls) defaultControls.enabled = false;
1212
+ ref.current.matrix.decompose(initialModelPosition, new THREE.Quaternion(), new THREE.Vector3());
1213
+ mousePosition3D.copy(event.point);
1214
+ startPointerPos.copy(event.point);
1215
+ dragOffset.copy(mousePosition3D).sub(initialModelPosition);
1216
+ if (!axisLock) {
1217
+ camera.getWorldDirection(dragPlaneNormal).negate();
1218
+ } else {
1219
+ switch (axisLock) {
1220
+ case "x":
1221
+ dragPlaneNormal.set(1, 0, 0);
1222
+ break;
1223
+ case "y":
1224
+ dragPlaneNormal.set(0, 1, 0);
1225
+ break;
1226
+ case "z":
1227
+ dragPlaneNormal.set(0, 0, 1);
1228
+ break;
1180
1229
  }
1181
- invalidate();
1182
- },
1183
- onDragEnd: () => {
1184
- if (defaultControls) defaultControls.enabled = true;
1185
- onDragEnd && onDragEnd();
1186
- invalidate();
1187
1230
  }
1231
+ dragPlane.setFromNormalAndCoplanarPoint(dragPlaneNormal, mousePosition3D);
1232
+ if (dragState.current.intentional) {
1233
+ onDragStart && onDragStart(initialModelPosition);
1234
+ }
1235
+ invalidate();
1188
1236
  },
1189
- {
1190
- drag: {
1191
- filterTaps: true,
1192
- threshold: 1,
1193
- ...typeof dragConfig === "object" ? dragConfig : {}
1237
+ [defaultControls, camera, axisLock, threshold, onDragStart, invalidate]
1238
+ );
1239
+ const handlePointerMove = React.useCallback(
1240
+ (event) => {
1241
+ if (!dragState.current.active || event.pointerId !== dragState.current.pointerId) return;
1242
+ const intersection = event.ray.intersectPlane(dragPlane, mousePosition3D);
1243
+ if (!intersection) return;
1244
+ if (!dragState.current.intentional) {
1245
+ const distance = mousePosition3D.distanceTo(startPointerPos);
1246
+ if (distance < threshold) return;
1247
+ dragState.current.intentional = true;
1248
+ onDragStart && onDragStart(initialModelPosition);
1194
1249
  }
1195
- }
1250
+ const previousLocalMatrix = ref.current.matrix.clone();
1251
+ const previousWorldMatrix = ref.current.matrixWorld.clone();
1252
+ const intendedNewPosition = new THREE.Vector3(
1253
+ mousePosition3D.x - dragOffset.x,
1254
+ mousePosition3D.y - dragOffset.y,
1255
+ mousePosition3D.z - dragOffset.z
1256
+ );
1257
+ if (dragLimits) {
1258
+ intendedNewPosition.x = dragLimits[0] ? Math.max(Math.min(intendedNewPosition.x, dragLimits[0][1]), dragLimits[0][0]) : intendedNewPosition.x;
1259
+ intendedNewPosition.y = dragLimits[1] ? Math.max(Math.min(intendedNewPosition.y, dragLimits[1][1]), dragLimits[1][0]) : intendedNewPosition.y;
1260
+ intendedNewPosition.z = dragLimits[2] ? Math.max(Math.min(intendedNewPosition.z, dragLimits[2][1]), dragLimits[2][0]) : intendedNewPosition.z;
1261
+ }
1262
+ if (autoTransform) {
1263
+ ref.current.matrix.setPosition(intendedNewPosition);
1264
+ const deltaLocalMatrix = ref.current.matrix.clone().multiply(previousLocalMatrix.invert());
1265
+ const deltaWorldMatrix = ref.current.matrix.clone().multiply(previousWorldMatrix.invert());
1266
+ onDrag && onDrag(ref.current.matrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1267
+ } else {
1268
+ const tempMatrix = new THREE.Matrix4().copy(ref.current.matrix);
1269
+ tempMatrix.setPosition(intendedNewPosition);
1270
+ const deltaLocalMatrix = tempMatrix.clone().multiply(previousLocalMatrix.invert());
1271
+ const deltaWorldMatrix = tempMatrix.clone().multiply(previousWorldMatrix.invert());
1272
+ onDrag && onDrag(tempMatrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1273
+ }
1274
+ invalidate();
1275
+ },
1276
+ [autoTransform, dragLimits, threshold, onDragStart, onDrag, invalidate]
1277
+ );
1278
+ const handlePointerUp = React.useCallback(
1279
+ (event) => {
1280
+ if (!dragState.current.active || event.pointerId !== dragState.current.pointerId) return;
1281
+ event.target.releasePointerCapture(event.pointerId);
1282
+ const wasIntentional = dragState.current.intentional;
1283
+ dragState.current.active = false;
1284
+ dragState.current.intentional = false;
1285
+ dragState.current.pointerId = -1;
1286
+ if (defaultControls) defaultControls.enabled = true;
1287
+ if (wasIntentional || !filterTaps) {
1288
+ onDragEnd && onDragEnd();
1289
+ }
1290
+ invalidate();
1291
+ },
1292
+ [defaultControls, filterTaps, onDragEnd, invalidate]
1196
1293
  );
1294
+ const handlePointerEnter = React.useCallback(() => {
1295
+ onHover && onHover(true);
1296
+ }, [onHover]);
1297
+ const handlePointerLeave = React.useCallback(() => {
1298
+ onHover && onHover(false);
1299
+ }, [onHover]);
1197
1300
  React.useImperativeHandle(fRef, () => ref.current, []);
1198
1301
  React.useLayoutEffect(() => {
1199
1302
  if (!matrix) return;
1200
1303
  ref.current.matrix = matrix;
1201
1304
  }, [matrix]);
1202
- return /* @__PURE__ */ React.createElement("group", { ref, ...bind(), matrix, matrixAutoUpdate: false, ...props }, children);
1305
+ return /* @__PURE__ */ React.createElement(
1306
+ "group",
1307
+ {
1308
+ ref,
1309
+ onPointerDown: handlePointerDown,
1310
+ onPointerMove: handlePointerMove,
1311
+ onPointerUp: handlePointerUp,
1312
+ onPointerEnter: handlePointerEnter,
1313
+ onPointerLeave: handlePointerLeave,
1314
+ matrix,
1315
+ matrixAutoUpdate: false,
1316
+ ...props
1317
+ },
1318
+ children
1319
+ );
1203
1320
  }
1204
1321
  );
1205
1322
 
@@ -1301,14 +1418,23 @@ function KeyboardControls({ map, children, onChange, domElement }) {
1301
1418
  obj.pressed = false;
1302
1419
  if (up) fn(false);
1303
1420
  };
1421
+ const blurHandler = () => {
1422
+ const resetState = map.reduce((prev, cur) => ({ ...prev, [cur.name]: false }), {});
1423
+ set(resetState);
1424
+ Object.values(keyMap).forEach((obj) => {
1425
+ obj.pressed = false;
1426
+ });
1427
+ };
1304
1428
  const source = domElement || window;
1305
1429
  source.addEventListener("keydown", downHandler, { passive: true });
1306
1430
  source.addEventListener("keyup", upHandler, { passive: true });
1431
+ window.addEventListener("blur", blurHandler);
1307
1432
  return () => {
1308
1433
  source.removeEventListener("keydown", downHandler);
1309
1434
  source.removeEventListener("keyup", upHandler);
1435
+ window.removeEventListener("blur", blurHandler);
1310
1436
  };
1311
- }, [domElement, key]);
1437
+ }, [domElement, key, onChange]);
1312
1438
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
1313
1439
  }
1314
1440
  function useKeyboardControls(sel) {
@@ -1330,20 +1456,28 @@ const MapControls = /* @__PURE__ */ React.forwardRef((props = { enableDamping: t
1330
1456
  const controls = React.useMemo(() => new MapControls$1(explCamera), [explCamera]);
1331
1457
  React.useEffect(() => {
1332
1458
  controls.connect(explDomElement);
1459
+ return () => void controls.dispose();
1460
+ }, [explDomElement, controls]);
1461
+ React.useEffect(() => {
1333
1462
  const callback = (e) => {
1334
1463
  invalidate();
1335
1464
  if (onChange) onChange(e);
1336
1465
  };
1466
+ const onStartCb = (e) => {
1467
+ if (onStart) onStart(e);
1468
+ };
1469
+ const onEndCb = (e) => {
1470
+ if (onEnd) onEnd(e);
1471
+ };
1337
1472
  controls.addEventListener("change", callback);
1338
- if (onStart) controls.addEventListener("start", onStart);
1339
- if (onEnd) controls.addEventListener("end", onEnd);
1473
+ controls.addEventListener("start", onStartCb);
1474
+ controls.addEventListener("end", onEndCb);
1340
1475
  return () => {
1341
- controls.dispose();
1476
+ controls.removeEventListener("start", onStartCb);
1477
+ controls.removeEventListener("end", onEndCb);
1342
1478
  controls.removeEventListener("change", callback);
1343
- if (onStart) controls.removeEventListener("start", onStart);
1344
- if (onEnd) controls.removeEventListener("end", onEnd);
1345
1479
  };
1346
- }, [onChange, onStart, onEnd, controls, invalidate, explDomElement]);
1480
+ }, [onChange, onStart, onEnd, controls, invalidate]);
1347
1481
  React.useEffect(() => {
1348
1482
  if (makeDefault) {
1349
1483
  const old = get().controls;
@@ -1351,7 +1485,9 @@ const MapControls = /* @__PURE__ */ React.forwardRef((props = { enableDamping: t
1351
1485
  return () => set({ controls: old });
1352
1486
  }
1353
1487
  }, [makeDefault, controls]);
1354
- useFrame(() => controls.update(), -1);
1488
+ useFrame(() => {
1489
+ if (controls.enabled) controls.update();
1490
+ }, -1);
1355
1491
  return /* @__PURE__ */ React.createElement("primitive", { ref, object: controls, enableDamping: true, ...rest });
1356
1492
  });
1357
1493
 
@@ -1477,20 +1613,16 @@ const OrbitControls = /* @__PURE__ */ React.forwardRef(
1477
1613
  onEnd,
1478
1614
  ...restProps
1479
1615
  }, ref) => {
1480
- const invalidate = useThree((state) => state.invalidate);
1481
- const defaultCamera = useThree((state) => state.camera);
1482
- const gl = useThree((state) => state.gl);
1483
- const events = useThree((state) => state.events);
1484
- const setEvents = useThree((state) => state.setEvents);
1485
- const set = useThree((state) => state.set);
1486
- const get = useThree((state) => state.get);
1487
- const performance = useThree((state) => state.performance);
1616
+ const { invalidate, camera: defaultCamera, renderer, events, setEvents, set, get, performance } = useThree();
1488
1617
  const explCamera = camera || defaultCamera;
1489
- const explDomElement = domElement || events.connected || gl.domElement;
1618
+ const explDomElement = domElement || events.connected || renderer.domElement;
1490
1619
  const controls = React.useMemo(() => new OrbitControls$1(explCamera), [explCamera]);
1491
- useFrame(() => {
1492
- if (controls.enabled) controls.update();
1493
- }, -1);
1620
+ useFrame(
1621
+ () => {
1622
+ if (controls.enabled) controls.update();
1623
+ },
1624
+ { before: "update" }
1625
+ );
1494
1626
  React.useEffect(() => {
1495
1627
  if (keyEvents) {
1496
1628
  controls.connect(keyEvents === true ? explDomElement : keyEvents);
@@ -1604,9 +1736,8 @@ function PresentationControls({
1604
1736
  azimuth = [-Infinity, Infinity],
1605
1737
  damping = 0.25
1606
1738
  }) {
1607
- const events = useThree((state) => state.events);
1608
- const gl = useThree((state) => state.gl);
1609
- const explDomElement = domElement || events.connected || gl.domElement;
1739
+ const { events, renderer, invalidate } = useThree();
1740
+ const explDomElement = domElement || events.connected || renderer.domElement;
1610
1741
  const { size } = useThree();
1611
1742
  const rPolar = React.useMemo(
1612
1743
  () => [rotation[0] + polar[0], rotation[0] + polar[1]],
@@ -1623,18 +1754,20 @@ function PresentationControls({
1623
1754
  React.useEffect(() => {
1624
1755
  if (global && cursor && enabled) {
1625
1756
  explDomElement.style.cursor = "grab";
1626
- gl.domElement.style.cursor = "";
1757
+ renderer.domElement.style.cursor = "";
1627
1758
  return () => {
1628
1759
  explDomElement.style.cursor = "default";
1629
- gl.domElement.style.cursor = "default";
1760
+ renderer.domElement.style.cursor = "default";
1630
1761
  };
1631
1762
  }
1632
1763
  }, [global, cursor, explDomElement, enabled]);
1633
1764
  const [animation] = React.useState({ scale: 1, rotation: rInitial, damping });
1634
1765
  const ref = React.useRef(null);
1635
1766
  useFrame((state, delta) => {
1636
- easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
1637
- easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
1767
+ let changed = false;
1768
+ changed ||= easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
1769
+ changed ||= easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
1770
+ if (changed) state.invalidate();
1638
1771
  });
1639
1772
  const bind = useGesture(
1640
1773
  {
@@ -1642,6 +1775,7 @@ function PresentationControls({
1642
1775
  if (cursor && !global && enabled) explDomElement.style.cursor = last ? "auto" : "grab";
1643
1776
  },
1644
1777
  onDrag: ({ down, delta: [x, y], memo: [oldY, oldX] = animation.rotation || rInitial }) => {
1778
+ invalidate();
1645
1779
  if (!enabled) return [y, x];
1646
1780
  if (cursor) explDomElement.style.cursor = down ? "grabbing" : "grab";
1647
1781
  x = MathUtils.clamp(oldX + x / size.width * Math.PI * speed, ...rAzimuth);
@@ -1650,14 +1784,27 @@ function PresentationControls({
1650
1784
  animation.rotation = snap && !down ? rInitial : [y, x, 0];
1651
1785
  animation.damping = snap && !down && typeof snap !== "boolean" ? snap : damping;
1652
1786
  return [y, x];
1787
+ },
1788
+ onDragEnd: () => {
1789
+ invalidate();
1653
1790
  }
1654
1791
  },
1655
- { target: global ? explDomElement : void 0 }
1792
+ { target: global ? explDomElement : void 0, enabled }
1656
1793
  );
1657
1794
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
1658
1795
  }
1659
1796
 
1797
+ function roundToPixelRatio(value) {
1798
+ const ratio = window.devicePixelRatio || 1;
1799
+ return Math.round(value * ratio) / ratio;
1800
+ }
1801
+ function getMaxAnisotropy(renderer) {
1802
+ const r = renderer;
1803
+ return r?.getMaxAnisotropy?.() ?? r?.capabilities?.getMaxAnisotropy?.() ?? 1;
1804
+ }
1805
+
1660
1806
  const context$7 = /* @__PURE__ */ React.createContext(null);
1807
+ const rootCache = /* @__PURE__ */ new WeakMap();
1661
1808
  function useScroll() {
1662
1809
  return React.useContext(context$7);
1663
1810
  }
@@ -1671,6 +1818,7 @@ function ScrollControls({
1671
1818
  damping = 0.25,
1672
1819
  maxSpeed = Infinity,
1673
1820
  prepend = false,
1821
+ pixelPerfect = true,
1674
1822
  style = {},
1675
1823
  children
1676
1824
  }) {
@@ -1680,6 +1828,7 @@ function ScrollControls({
1680
1828
  const [fixed] = React.useState(() => document.createElement("div"));
1681
1829
  const target = gl.domElement.parentNode;
1682
1830
  const scroll = React.useRef(0);
1831
+ const disableScrollUpdate = React.useRef(false);
1683
1832
  const state = React.useMemo(() => {
1684
1833
  const state2 = {
1685
1834
  el,
@@ -1692,6 +1841,7 @@ function ScrollControls({
1692
1841
  delta: 0,
1693
1842
  scroll,
1694
1843
  pages,
1844
+ pixelPerfect,
1695
1845
  // 0-1 for a range between from -> from + distance
1696
1846
  range(from, distance2, margin = 0) {
1697
1847
  const start = from - margin;
@@ -1710,7 +1860,7 @@ function ScrollControls({
1710
1860
  }
1711
1861
  };
1712
1862
  return state2;
1713
- }, [eps, damping, horizontal, pages]);
1863
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
1714
1864
  React.useEffect(() => {
1715
1865
  el.style.position = "absolute";
1716
1866
  el.style.width = "100%";
@@ -1760,16 +1910,20 @@ function ScrollControls({
1760
1910
  const scrollLength = el[horizontal ? "scrollWidth" : "scrollHeight"];
1761
1911
  const scrollThreshold = scrollLength - containerLength;
1762
1912
  let current = 0;
1763
- let disableScroll = true;
1913
+ let disableScroll = false;
1764
1914
  let firstRun = true;
1765
1915
  const onScroll = () => {
1766
1916
  if (!enabled || firstRun) return;
1917
+ if (disableScrollUpdate.current) {
1918
+ disableScrollUpdate.current = false;
1919
+ return;
1920
+ }
1767
1921
  invalidate();
1768
1922
  current = el[horizontal ? "scrollLeft" : "scrollTop"];
1769
1923
  scroll.current = current / scrollThreshold;
1770
1924
  if (infinite) {
1771
1925
  if (!disableScroll) {
1772
- if (current >= scrollThreshold) {
1926
+ if (current >= scrollThreshold - 1) {
1773
1927
  const damp = 1 - state.offset;
1774
1928
  el[horizontal ? "scrollLeft" : "scrollTop"] = 1;
1775
1929
  scroll.current = state.offset = -damp;
@@ -1786,11 +1940,34 @@ function ScrollControls({
1786
1940
  };
1787
1941
  el.addEventListener("scroll", onScroll, { passive: true });
1788
1942
  requestAnimationFrame(() => firstRun = false);
1789
- const onWheel = (e) => el.scrollLeft += e.deltaY / 2;
1790
- if (horizontal) el.addEventListener("wheel", onWheel, { passive: true });
1943
+ const onResize = () => {
1944
+ if (!el) return;
1945
+ const scrollLength2 = el[horizontal ? "scrollWidth" : "scrollHeight"];
1946
+ const containerLength2 = el[horizontal ? "clientWidth" : "clientHeight"];
1947
+ disableScrollUpdate.current = true;
1948
+ el[horizontal ? "scrollLeft" : "scrollTop"] = state.offset * (scrollLength2 - containerLength2);
1949
+ };
1950
+ const onWheel = (e) => {
1951
+ if (horizontal) {
1952
+ const previous = el.scrollLeft;
1953
+ el.scrollLeft += e.deltaY / 2;
1954
+ if (infinite && previous === el.scrollLeft) {
1955
+ onScroll();
1956
+ }
1957
+ } else if (infinite) {
1958
+ if (el.scrollTop <= 1 && e.deltaY < 0) {
1959
+ onScroll();
1960
+ } else if (el.scrollTop >= el.scrollHeight - el.clientHeight - 1 && e.deltaY > 0) {
1961
+ onScroll();
1962
+ }
1963
+ }
1964
+ };
1965
+ if (horizontal || infinite) el.addEventListener("wheel", onWheel, { passive: true });
1966
+ if (horizontal) window.addEventListener("resize", onResize);
1791
1967
  return () => {
1792
1968
  el.removeEventListener("scroll", onScroll);
1793
- if (horizontal) el.removeEventListener("wheel", onWheel);
1969
+ if (horizontal || infinite) el.removeEventListener("wheel", onWheel);
1970
+ if (horizontal) window.removeEventListener("resize", onResize);
1794
1971
  };
1795
1972
  }
1796
1973
  }, [el, events, size, infinite, state, invalidate, horizontal, enabled]);
@@ -1823,10 +2000,23 @@ const ScrollHtml = React.forwardRef(
1823
2000
  React.useImperativeHandle(ref, () => group.current, []);
1824
2001
  const { width, height } = useThree((state2) => state2.size);
1825
2002
  const fiberState = React.useContext(context$9);
1826
- const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
2003
+ const root = React.useMemo(() => {
2004
+ let root2 = rootCache.get(state.fixed);
2005
+ if (!root2) {
2006
+ root2 = ReactDOM.createRoot(state.fixed);
2007
+ rootCache.set(state.fixed, root2);
2008
+ }
2009
+ return root2;
2010
+ }, [state.fixed]);
1827
2011
  useFrame(() => {
1828
2012
  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)`;
2013
+ let x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
2014
+ let y = state.horizontal ? 0 : height * (state.pages - 1) * -state.offset;
2015
+ if (state.pixelPerfect) {
2016
+ x = roundToPixelRatio(x);
2017
+ y = roundToPixelRatio(y);
2018
+ }
2019
+ group.current.style.transform = `translate3d(${x}px,${y}px,0)`;
1830
2020
  }
1831
2021
  });
1832
2022
  root.render(
@@ -2194,10 +2384,10 @@ const CloudInstance = /* @__PURE__ */ React.forwardRef(
2194
2384
  return /* @__PURE__ */ React.createElement("group", { ref, ...props });
2195
2385
  }
2196
2386
  );
2197
- const Cloud = /* @__PURE__ */ React.forwardRef((props, fref) => {
2387
+ const Cloud = /* @__PURE__ */ React.forwardRef(({ materialFactory, ...props }, fref) => {
2198
2388
  const parent = React.useContext(context$6);
2199
2389
  if (parent) return /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props });
2200
- return /* @__PURE__ */ React.createElement(Clouds, null, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
2390
+ return /* @__PURE__ */ React.createElement(Clouds, { materialFactory }, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
2201
2391
  });
2202
2392
 
2203
2393
  const CatmullRomLine = /* @__PURE__ */ React.forwardRef(function CatmullRomLine2({
@@ -2719,7 +2909,7 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
2719
2909
  return {
2720
2910
  font,
2721
2911
  size,
2722
- height,
2912
+ depth: height,
2723
2913
  bevelThickness,
2724
2914
  bevelSize,
2725
2915
  bevelEnabled,
@@ -2755,377 +2945,6 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
2755
2945
  }
2756
2946
  );
2757
2947
 
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
2948
  const Context = /* @__PURE__ */ React.createContext({});
3130
2949
  const useGizmoContext = () => {
3131
2950
  return React.useContext(Context);
@@ -3273,7 +3092,7 @@ const FaceMaterial = ({
3273
3092
  "meshBasicMaterial",
3274
3093
  {
3275
3094
  map: texture,
3276
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
3095
+ "map-anisotropy": getMaxAnisotropy(renderer),
3277
3096
  attach: `material-${index}`,
3278
3097
  color: hover ? hoverColor : "white",
3279
3098
  transparent: true,
@@ -3384,7 +3203,7 @@ function AxisHead({
3384
3203
  "spriteMaterial",
3385
3204
  {
3386
3205
  map: texture,
3387
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
3206
+ "map-anisotropy": getMaxAnisotropy(renderer),
3388
3207
  alphaTest: 0.3,
3389
3208
  opacity: label ? 1 : 0.75,
3390
3209
  toneMapped: false
@@ -3421,6 +3240,11 @@ const GizmoViewport = ({
3421
3240
  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
3241
  };
3423
3242
 
3243
+ const roundEven = (n) => {
3244
+ const rounded = Math.ceil(n);
3245
+ return rounded % 2 === 0 ? rounded : rounded + 1;
3246
+ };
3247
+
3424
3248
  const v1 = /* @__PURE__ */ new Vector3();
3425
3249
  const v2 = /* @__PURE__ */ new Vector3();
3426
3250
  const v3 = /* @__PURE__ */ new Vector3();
@@ -3519,9 +3343,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3519
3343
  as = "div",
3520
3344
  wrapperClass,
3521
3345
  pointerEvents = "auto",
3346
+ pixelPerfect = true,
3522
3347
  ...props
3523
3348
  }, ref) => {
3524
3349
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
3350
+ const forceEven = useThree((state) => state.internal.forceEven);
3351
+ const { frustum } = useThree();
3525
3352
  const [el] = React.useState(() => document.createElement(as));
3526
3353
  const root = React.useRef(null);
3527
3354
  const group = React.useRef(null);
@@ -3546,38 +3373,50 @@ const Html = /* @__PURE__ */ React.forwardRef(
3546
3373
  el2.style.position = null;
3547
3374
  el2.style.pointerEvents = null;
3548
3375
  }
3549
- }, [occlude]);
3376
+ }, [occlude, zIndexRange, gl.domElement]);
3550
3377
  React.useLayoutEffect(() => {
3551
3378
  if (group.current) {
3552
- const currentRoot = root.current = ReactDOM.createRoot(el);
3553
- scene.updateMatrixWorld();
3554
- if (transform) {
3555
- el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3556
- } 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;`;
3379
+ if (!root.current) {
3380
+ root.current = ReactDOM.createRoot(el);
3381
+ scene.updateMatrixWorld();
3382
+ if (transform) {
3383
+ el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3384
+ } else {
3385
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
3386
+ }
3559
3387
  }
3560
3388
  if (target) {
3561
3389
  if (prepend) target.prepend(el);
3562
3390
  else target.appendChild(el);
3563
3391
  }
3564
3392
  return () => {
3565
- if (target) target.removeChild(el);
3566
- currentRoot.unmount();
3393
+ if (target && target.contains(el)) {
3394
+ target.removeChild(el);
3395
+ }
3567
3396
  };
3568
3397
  }
3569
- }, [target, transform]);
3398
+ }, [target, transform, el, prepend, scene]);
3570
3399
  React.useLayoutEffect(() => {
3571
3400
  if (wrapperClass) el.className = wrapperClass;
3572
- }, [wrapperClass]);
3401
+ }, [wrapperClass, el]);
3402
+ React.useLayoutEffect(() => {
3403
+ return () => {
3404
+ if (root.current) {
3405
+ root.current.unmount();
3406
+ root.current = null;
3407
+ }
3408
+ };
3409
+ }, []);
3573
3410
  const styles = React.useMemo(() => {
3411
+ const width = forceEven ? roundEven(size.width) : size.width;
3412
+ const height = forceEven ? roundEven(size.height) : size.height;
3574
3413
  if (transform) {
3575
3414
  return {
3576
3415
  position: "absolute",
3577
3416
  top: 0,
3578
3417
  left: 0,
3579
- width: size.width,
3580
- height: size.height,
3418
+ width,
3419
+ height,
3581
3420
  transformStyle: "preserve-3d",
3582
3421
  pointerEvents: "none"
3583
3422
  };
@@ -3586,15 +3425,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
3586
3425
  position: "absolute",
3587
3426
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
3588
3427
  ...fullscreen && {
3589
- top: -size.height / 2,
3590
- left: -size.width / 2,
3591
- width: size.width,
3592
- height: size.height
3428
+ top: forceEven ? roundEven(-size.height / 2) : -size.height / 2,
3429
+ left: forceEven ? roundEven(-size.width / 2) : -size.width / 2,
3430
+ width,
3431
+ height
3593
3432
  },
3594
3433
  ...style
3595
3434
  };
3596
3435
  }
3597
- }, [style, center, fullscreen, size, transform]);
3436
+ }, [style, center, fullscreen, size, transform, forceEven]);
3598
3437
  const transformInnerStyles = React.useMemo(
3599
3438
  () => ({ position: "absolute", pointerEvents }),
3600
3439
  [pointerEvents]
@@ -3608,7 +3447,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3608
3447
  } else {
3609
3448
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
3610
3449
  }
3611
- });
3450
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
3612
3451
  const visible = React.useRef(true);
3613
3452
  useFrame((gl2) => {
3614
3453
  if (group.current) {
@@ -3640,7 +3479,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3640
3479
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
3641
3480
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
3642
3481
  if (transform) {
3643
- const [widthHalf, heightHalf] = [size.width / 2, size.height / 2];
3482
+ let widthHalf = forceEven ? roundEven(size.width / 2) : size.width / 2;
3483
+ let heightHalf = forceEven ? roundEven(size.height / 2) : size.height / 2;
3484
+ if (pixelPerfect) {
3485
+ widthHalf = roundToPixelRatio(widthHalf);
3486
+ heightHalf = roundToPixelRatio(heightHalf);
3487
+ }
3644
3488
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
3645
3489
  const { isOrthographicCamera, top, left, bottom, right } = camera;
3646
3490
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -3651,8 +3495,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
3651
3495
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
3652
3496
  matrix.elements[15] = 1;
3653
3497
  }
3654
- el.style.width = size.width + "px";
3655
- el.style.height = size.height + "px";
3498
+ const elWidth = forceEven ? roundEven(size.width) : size.width;
3499
+ const elHeight = forceEven ? roundEven(size.height) : size.height;
3500
+ el.style.width = elWidth + "px";
3501
+ el.style.height = elHeight + "px";
3656
3502
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
3657
3503
  if (transformOuterRef.current && transformInnerRef.current) {
3658
3504
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -3660,7 +3506,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
3660
3506
  }
3661
3507
  } else {
3662
3508
  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})`;
3509
+ let x = forceEven ? roundEven(vec[0]) : vec[0];
3510
+ let y = forceEven ? roundEven(vec[1]) : vec[1];
3511
+ if (pixelPerfect) {
3512
+ x = roundToPixelRatio(x);
3513
+ y = roundToPixelRatio(y);
3514
+ }
3515
+ el.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`;
3664
3516
  }
3665
3517
  oldPosition.current = vec;
3666
3518
  oldZoom.current = camera.zoom;
@@ -3671,8 +3523,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3671
3523
  if (transformOuterRef.current) {
3672
3524
  const el2 = transformOuterRef.current.children[0];
3673
3525
  if (el2?.clientWidth && el2?.clientHeight) {
3674
- const { isOrthographicCamera } = camera;
3675
- if (isOrthographicCamera || geometry) {
3526
+ if (geometry) {
3676
3527
  if (props.scale) {
3677
3528
  if (!Array.isArray(props.scale)) {
3678
3529
  occlusionMeshRef.current.scale.setScalar(1 / props.scale);
@@ -3704,68 +3555,16 @@ const Html = /* @__PURE__ */ React.forwardRef(
3704
3555
  }
3705
3556
  }
3706
3557
  });
3707
- const shaders = React.useMemo(
3708
- () => ({
3709
- vertexShader: !transform ? (
3710
- /* glsl */
3711
- `
3712
- /*
3713
- This shader is from the THREE's SpriteMaterial.
3714
- We need to turn the backing plane into a Sprite
3715
- (make it always face the camera) if "transfrom"
3716
- is false.
3717
- */
3718
- #include <common>
3719
-
3720
- void main() {
3721
- vec2 center = vec2(0., 1.);
3722
- float rotation = 0.0;
3723
-
3724
- // This is somewhat arbitrary, but it seems to work well
3725
- // Need to figure out how to derive this dynamically if it even matters
3726
- float size = 0.03;
3727
-
3728
- vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );
3729
- vec2 scale;
3730
- scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );
3731
- scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );
3732
-
3733
- bool isPerspective = isPerspectiveMatrix( projectionMatrix );
3734
- if ( isPerspective ) scale *= - mvPosition.z;
3735
-
3736
- vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale * size;
3737
- vec2 rotatedPosition;
3738
- rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
3739
- rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
3740
- mvPosition.xy += rotatedPosition;
3741
-
3742
- gl_Position = projectionMatrix * mvPosition;
3743
- }
3744
- `
3745
- ) : void 0,
3746
- fragmentShader: (
3747
- /* glsl */
3748
- `
3749
- void main() {
3750
- gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
3751
- }
3752
- `
3753
- )
3754
- }),
3755
- [transform]
3756
- );
3757
- return /* @__PURE__ */ React.createElement("group", { ...props, ref: group }, occlude && !isRayCastOcclusion && /* @__PURE__ */ React.createElement("mesh", { castShadow, receiveShadow, ref: occlusionMeshRef }, geometry || /* @__PURE__ */ React.createElement("planeGeometry", null), material || /* @__PURE__ */ React.createElement(
3758
- "shaderMaterial",
3759
- {
3760
- side: DoubleSide,
3761
- vertexShader: shaders.vertexShader,
3762
- fragmentShader: shaders.fragmentShader
3763
- }
3764
- )));
3558
+ return /* @__PURE__ */ React.createElement("group", { ...props, ref: group }, occlude && !isRayCastOcclusion && /* @__PURE__ */ React.createElement("mesh", { castShadow, receiveShadow, ref: occlusionMeshRef }, geometry || /* @__PURE__ */ React.createElement("planeGeometry", null), material));
3765
3559
  }
3766
3560
  );
3767
3561
 
3768
3562
  const context$4 = /* @__PURE__ */ React.createContext(null);
3563
+ const resolveObject = (object, fallback) => {
3564
+ if (!object) return fallback;
3565
+ if ("current" in object) return object.current;
3566
+ return object;
3567
+ };
3769
3568
 
3770
3569
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
3771
3570
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -3800,6 +3599,8 @@ const AxisArrow = ({ direction, axis }) => {
3800
3599
  onDragStart,
3801
3600
  onDrag,
3802
3601
  onDragEnd,
3602
+ onHover,
3603
+ dragState,
3803
3604
  userData,
3804
3605
  LineComponent: Line
3805
3606
  } = React.useContext(context$4);
@@ -3831,7 +3632,13 @@ const AxisArrow = ({ direction, axis }) => {
3831
3632
  const onPointerMove = React.useCallback(
3832
3633
  (e) => {
3833
3634
  e.stopPropagation();
3834
- if (!isHovered) setIsHovered(true);
3635
+ if (!isHovered) {
3636
+ const drag = dragState.current;
3637
+ if (drag === null || drag.component === "Arrow" && drag.axis === axis) {
3638
+ setIsHovered(true);
3639
+ onHover({ component: "Arrow", axis, hovering: true });
3640
+ }
3641
+ }
3835
3642
  if (clickInfo.current) {
3836
3643
  const { clickPoint, dir } = clickInfo.current;
3837
3644
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -3850,7 +3657,7 @@ const AxisArrow = ({ direction, axis }) => {
3850
3657
  onDrag(offsetMatrix$1);
3851
3658
  }
3852
3659
  },
3853
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
3660
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
3854
3661
  );
3855
3662
  const onPointerUp = React.useCallback(
3856
3663
  (e) => {
@@ -3865,10 +3672,16 @@ const AxisArrow = ({ direction, axis }) => {
3865
3672
  },
3866
3673
  [annotations, camControls, onDragEnd]
3867
3674
  );
3868
- const onPointerOut = React.useCallback((e) => {
3869
- e.stopPropagation();
3870
- setIsHovered(false);
3871
- }, []);
3675
+ const onPointerOut = React.useCallback(
3676
+ (e) => {
3677
+ e.stopPropagation();
3678
+ if (isHovered) {
3679
+ setIsHovered(false);
3680
+ onHover({ component: "Arrow", axis, hovering: false });
3681
+ }
3682
+ },
3683
+ [onHover, axis, isHovered]
3684
+ );
3872
3685
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
3873
3686
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
3874
3687
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -3972,6 +3785,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
3972
3785
  const posNew = /* @__PURE__ */ new THREE.Vector3();
3973
3786
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
3974
3787
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
3788
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
3789
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
3975
3790
  const AxisRotator = ({
3976
3791
  dir1,
3977
3792
  dir2,
@@ -3992,6 +3807,8 @@ const AxisRotator = ({
3992
3807
  onDragStart,
3993
3808
  onDrag,
3994
3809
  onDragEnd,
3810
+ onHover,
3811
+ dragState,
3995
3812
  userData,
3996
3813
  LineComponent: Line
3997
3814
  } = React.useContext(context$4);
@@ -4025,14 +3842,29 @@ const AxisRotator = ({
4025
3842
  const onPointerMove = React.useCallback(
4026
3843
  (e) => {
4027
3844
  e.stopPropagation();
4028
- if (!isHovered) setIsHovered(true);
3845
+ if (!isHovered) {
3846
+ const drag = dragState.current;
3847
+ if (drag === null || drag.component === "Rotator" && drag.axis === axis) {
3848
+ setIsHovered(true);
3849
+ onHover({ component: "Rotator", axis, hovering: true });
3850
+ }
3851
+ }
4029
3852
  if (clickInfo.current) {
4030
3853
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
4031
3854
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
4032
3855
  ray$1.copy(e.ray);
4033
- ray$1.intersectPlane(plane, intersection$1);
4034
- ray$1.direction.negate();
4035
- ray$1.intersectPlane(plane, intersection$1);
3856
+ const dotProduct = Math.abs(ray$1.direction.dot(normal));
3857
+ if (dotProduct < 0.1) {
3858
+ viewPlane.setFromNormalAndCoplanarPoint(ray$1.direction.clone().negate(), origin);
3859
+ ray$1.intersectPlane(viewPlane, viewIntersection);
3860
+ const toIntersection = viewIntersection.clone().sub(origin);
3861
+ const projectedDist = toIntersection.dot(normal);
3862
+ intersection$1.copy(viewIntersection).sub(normal.clone().multiplyScalar(projectedDist));
3863
+ } else {
3864
+ ray$1.intersectPlane(plane, intersection$1);
3865
+ ray$1.direction.negate();
3866
+ ray$1.intersectPlane(plane, intersection$1);
3867
+ }
4036
3868
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
4037
3869
  let degrees = toDegrees(deltaAngle);
4038
3870
  if (e.shiftKey) {
@@ -4058,7 +3890,7 @@ const AxisRotator = ({
4058
3890
  onDrag(rotMatrix);
4059
3891
  }
4060
3892
  },
4061
- [annotations, onDrag, isHovered, rotationLimits, axis]
3893
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
4062
3894
  );
4063
3895
  const onPointerUp = React.useCallback(
4064
3896
  (e) => {
@@ -4074,10 +3906,16 @@ const AxisRotator = ({
4074
3906
  },
4075
3907
  [annotations, camControls, onDragEnd]
4076
3908
  );
4077
- const onPointerOut = React.useCallback((e) => {
4078
- e.stopPropagation();
4079
- setIsHovered(false);
4080
- }, []);
3909
+ const onPointerOut = React.useCallback(
3910
+ (e) => {
3911
+ e.stopPropagation();
3912
+ if (isHovered) {
3913
+ setIsHovered(false);
3914
+ onHover({ component: "Rotator", axis, hovering: false });
3915
+ }
3916
+ },
3917
+ [onHover, axis, isHovered]
3918
+ );
4081
3919
  const matrixL = React.useMemo(() => {
4082
3920
  const dir1N = dir1.clone().normalize();
4083
3921
  const dir2N = dir2.clone().normalize();
@@ -4178,6 +4016,8 @@ const PlaneSlider = ({
4178
4016
  onDragStart,
4179
4017
  onDrag,
4180
4018
  onDragEnd,
4019
+ onHover,
4020
+ dragState,
4181
4021
  userData,
4182
4022
  LineComponent: Line
4183
4023
  } = React.useContext(context$4);
@@ -4213,7 +4053,13 @@ const PlaneSlider = ({
4213
4053
  const onPointerMove = React.useCallback(
4214
4054
  (e) => {
4215
4055
  e.stopPropagation();
4216
- if (!isHovered) setIsHovered(true);
4056
+ if (!isHovered) {
4057
+ const drag = dragState.current;
4058
+ if (drag === null || drag.component === "Slider" && drag.axis === axis) {
4059
+ setIsHovered(true);
4060
+ onHover({ component: "Slider", axis, hovering: true });
4061
+ }
4062
+ }
4217
4063
  if (clickInfo.current) {
4218
4064
  const { clickPoint, e1, e2, plane } = clickInfo.current;
4219
4065
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -4249,7 +4095,7 @@ const PlaneSlider = ({
4249
4095
  onDrag(offsetMatrix);
4250
4096
  }
4251
4097
  },
4252
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
4098
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4253
4099
  );
4254
4100
  const onPointerUp = React.useCallback(
4255
4101
  (e) => {
@@ -4264,10 +4110,16 @@ const PlaneSlider = ({
4264
4110
  },
4265
4111
  [annotations, camControls, onDragEnd]
4266
4112
  );
4267
- const onPointerOut = React.useCallback((e) => {
4268
- e.stopPropagation();
4269
- setIsHovered(false);
4270
- }, []);
4113
+ const onPointerOut = React.useCallback(
4114
+ (e) => {
4115
+ e.stopPropagation();
4116
+ if (isHovered) {
4117
+ setIsHovered(false);
4118
+ onHover({ component: "Slider", axis, hovering: false });
4119
+ }
4120
+ },
4121
+ [onHover, axis, isHovered]
4122
+ );
4271
4123
  const matrixL = React.useMemo(() => {
4272
4124
  const dir1N = dir1.clone().normalize();
4273
4125
  const dir2N = dir2.clone().normalize();
@@ -4405,6 +4257,8 @@ const ScalingSphere = ({ direction, axis }) => {
4405
4257
  onDragStart,
4406
4258
  onDrag,
4407
4259
  onDragEnd,
4260
+ onHover,
4261
+ dragState,
4408
4262
  userData
4409
4263
  } = React.useContext(context$4);
4410
4264
  const size = useThree((state) => state.size);
@@ -4441,7 +4295,13 @@ const ScalingSphere = ({ direction, axis }) => {
4441
4295
  const onPointerMove = React.useCallback(
4442
4296
  (e) => {
4443
4297
  e.stopPropagation();
4444
- if (!isHovered) setIsHovered(true);
4298
+ if (!isHovered) {
4299
+ const drag = dragState.current;
4300
+ if (drag === null || drag.component === "Sphere" && drag.axis === axis) {
4301
+ setIsHovered(true);
4302
+ onHover({ component: "Sphere", axis, hovering: true });
4303
+ }
4304
+ }
4445
4305
  if (clickInfo.current) {
4446
4306
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
4447
4307
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -4467,7 +4327,7 @@ const ScalingSphere = ({ direction, axis }) => {
4467
4327
  onDrag(scaleMatrix);
4468
4328
  }
4469
4329
  },
4470
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
4330
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
4471
4331
  );
4472
4332
  const onPointerUp = React.useCallback(
4473
4333
  (e) => {
@@ -4484,10 +4344,16 @@ const ScalingSphere = ({ direction, axis }) => {
4484
4344
  },
4485
4345
  [annotations, camControls, onDragEnd, position]
4486
4346
  );
4487
- const onPointerOut = React.useCallback((e) => {
4488
- e.stopPropagation();
4489
- setIsHovered(false);
4490
- }, []);
4347
+ const onPointerOut = React.useCallback(
4348
+ (e) => {
4349
+ e.stopPropagation();
4350
+ if (isHovered) {
4351
+ setIsHovered(false);
4352
+ onHover({ component: "Sphere", axis, hovering: false });
4353
+ }
4354
+ },
4355
+ [onHover, axis, isHovered]
4356
+ );
4491
4357
  const { radius, matrixL } = React.useMemo(() => {
4492
4358
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
4493
4359
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -4542,6 +4408,7 @@ const mW = /* @__PURE__ */ new THREE.Matrix4();
4542
4408
  const mL = /* @__PURE__ */ new THREE.Matrix4();
4543
4409
  const mL0Inv = /* @__PURE__ */ new THREE.Matrix4();
4544
4410
  const mdL = /* @__PURE__ */ new THREE.Matrix4();
4411
+ const mdW = /* @__PURE__ */ new THREE.Matrix4();
4545
4412
  const mG = /* @__PURE__ */ new THREE.Matrix4();
4546
4413
  const bb = /* @__PURE__ */ new THREE.Box3();
4547
4414
  const bbObj = /* @__PURE__ */ new THREE.Box3();
@@ -4557,9 +4424,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4557
4424
  ({
4558
4425
  enabled = true,
4559
4426
  matrix,
4427
+ object,
4560
4428
  onDragStart,
4561
4429
  onDrag,
4562
4430
  onDragEnd,
4431
+ onHover,
4563
4432
  autoTransform = true,
4564
4433
  anchor,
4565
4434
  disableAxes = false,
@@ -4595,14 +4464,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4595
4464
  const gizmoRef = React.useRef(null);
4596
4465
  const childrenRef = React.useRef(null);
4597
4466
  const translation = React.useRef([0, 0, 0]);
4467
+ const dragState = React.useRef(null);
4598
4468
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
4599
4469
  const gizmoScale = React.useRef(new THREE.Vector3(1, 1, 1));
4470
+ React.useEffect(() => {
4471
+ if (object) {
4472
+ const target = resolveObject(object);
4473
+ if (target) {
4474
+ const pivot = ref.current;
4475
+ const didAutoUpdate = target.matrixAutoUpdate;
4476
+ target.updateWorldMatrix(true, true);
4477
+ target.matrixAutoUpdate = false;
4478
+ pivot.matrix.copy(target.matrix);
4479
+ return () => {
4480
+ if (didAutoUpdate) {
4481
+ target.matrixAutoUpdate = true;
4482
+ target.matrix.decompose(target.position, target.quaternion, target.scale);
4483
+ }
4484
+ };
4485
+ }
4486
+ }
4487
+ }, [object]);
4600
4488
  React.useLayoutEffect(() => {
4601
4489
  if (!anchor) return;
4602
- childrenRef.current.updateWorldMatrix(true, true);
4603
- mPInv.copy(childrenRef.current.matrixWorld).invert();
4490
+ const anchorTarget = resolveObject(object, childrenRef.current);
4491
+ if (!anchorTarget) return;
4492
+ anchorTarget.updateWorldMatrix(true, true);
4493
+ mPInv.copy(anchorTarget.matrixWorld).invert();
4604
4494
  bb.makeEmpty();
4605
- childrenRef.current.traverse((obj) => {
4495
+ anchorTarget.traverse((obj) => {
4606
4496
  if (!obj.geometry) return;
4607
4497
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
4608
4498
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -4617,15 +4507,24 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4617
4507
  gizmoRef.current.position.copy(vPosition);
4618
4508
  invalidate();
4619
4509
  });
4510
+ const mergedUserData = React.useMemo(
4511
+ () => ({
4512
+ ...userData,
4513
+ ...depthTest === false && { interactivePriority: 1 }
4514
+ }),
4515
+ [userData, depthTest]
4516
+ );
4620
4517
  const config = React.useMemo(
4621
4518
  () => ({
4622
4519
  onDragStart: (props2) => {
4520
+ dragState.current = props2;
4623
4521
  mL0.copy(ref.current.matrix);
4624
4522
  mW0.copy(ref.current.matrixWorld);
4625
4523
  onDragStart && onDragStart(props2);
4626
4524
  invalidate();
4627
4525
  },
4628
- onDrag: (mdW) => {
4526
+ onDrag: (mdW_) => {
4527
+ mdW.copy(mdW_);
4629
4528
  mP.copy(parentRef.current.matrixWorld);
4630
4529
  mPInv.copy(mP).invert();
4631
4530
  mW.copy(mW0).premultiply(mdW);
@@ -4635,13 +4534,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4635
4534
  if (autoTransform) {
4636
4535
  ref.current.matrix.copy(mL);
4637
4536
  }
4638
- onDrag && onDrag(mL, mdL, mW, mdW);
4537
+ const target = resolveObject(object);
4538
+ if (target) target.matrix.copy(mL);
4539
+ onDrag && onDrag({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
4639
4540
  invalidate();
4640
4541
  },
4641
4542
  onDragEnd: () => {
4642
- if (onDragEnd) onDragEnd();
4543
+ dragState.current = null;
4544
+ if (onDragEnd) onDragEnd({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
4643
4545
  invalidate();
4644
4546
  },
4547
+ onHover: (props2) => {
4548
+ if (onHover) onHover(props2);
4549
+ },
4550
+ dragState,
4645
4551
  translation,
4646
4552
  translationLimits,
4647
4553
  rotationLimits,
@@ -4653,7 +4559,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4653
4559
  fixed,
4654
4560
  depthTest,
4655
4561
  renderOrder,
4656
- userData,
4562
+ userData: mergedUserData,
4657
4563
  annotations,
4658
4564
  annotationsClass,
4659
4565
  LineComponent: LineComp
@@ -4662,6 +4568,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4662
4568
  onDragStart,
4663
4569
  onDrag,
4664
4570
  onDragEnd,
4571
+ onHover,
4665
4572
  translation,
4666
4573
  translationLimits,
4667
4574
  rotationLimits,
@@ -4673,11 +4580,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4673
4580
  ...axisColors,
4674
4581
  hoveredColor,
4675
4582
  opacity,
4676
- userData,
4583
+ mergedUserData,
4677
4584
  autoTransform,
4678
4585
  annotations,
4679
4586
  annotationsClass,
4680
- LineComp
4587
+ LineComp,
4588
+ object
4681
4589
  ]
4682
4590
  );
4683
4591
  const vec = new THREE.Vector3();
@@ -4744,7 +4652,9 @@ const TransformControls = /* @__PURE__ */ React.forwardRef(
4744
4652
  const group = React.useRef(null);
4745
4653
  React.useLayoutEffect(() => {
4746
4654
  if (object) {
4747
- controls.attach(object instanceof THREE.Object3D ? object : object.current);
4655
+ controls.attach(
4656
+ object instanceof THREE.Object3D ? object : object.current
4657
+ );
4748
4658
  } else if (group.current instanceof THREE.Object3D) {
4749
4659
  controls.attach(group.current);
4750
4660
  }
@@ -4840,7 +4750,7 @@ const PointMaterial = /* @__PURE__ */ React.forwardRef((props, ref) => {
4840
4750
  return /* @__PURE__ */ React.createElement("primitive", { ...props, object: material, ref, attach: "material" });
4841
4751
  });
4842
4752
 
4843
- const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = true, autoplay, ...props }, ref) => {
4753
+ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = false, autoplay, ...props }, ref) => {
4844
4754
  const sound = React.useRef(null);
4845
4755
  React.useImperativeHandle(ref, () => sound.current, []);
4846
4756
  const camera = useThree(({ camera: camera2 }) => camera2);
@@ -4852,7 +4762,9 @@ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, l
4852
4762
  _sound.setBuffer(Array.isArray(buffer) ? buffer[0] : buffer);
4853
4763
  _sound.setRefDistance(distance);
4854
4764
  _sound.setLoop(loop);
4855
- if (autoplay && !_sound.isPlaying) _sound.play();
4765
+ if (autoplay && !_sound.isPlaying) {
4766
+ _sound.play();
4767
+ }
4856
4768
  }
4857
4769
  }, [buffer, camera, distance, loop]);
4858
4770
  React.useEffect(() => {
@@ -4921,7 +4833,6 @@ const tempMatrix = /* @__PURE__ */ new THREE.Matrix4();
4921
4833
  const translation = /* @__PURE__ */ new THREE.Vector3();
4922
4834
  const rotation = /* @__PURE__ */ new THREE.Quaternion();
4923
4835
  const scale = /* @__PURE__ */ new THREE.Vector3();
4924
- const isInstancedBufferAttribute = (attr) => attr.isInstancedBufferAttribute;
4925
4836
  const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }, ref) => {
4926
4837
  React.useMemo(() => extend({ PositionMesh }), []);
4927
4838
  const group = React.useRef(null);
@@ -4941,24 +4852,20 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
4941
4852
  const parentRef = React.useRef(null);
4942
4853
  React.useImperativeHandle(ref, () => parentRef.current, []);
4943
4854
  const [instances, setInstances] = React.useState([]);
4855
+ const boundsDirty = React.useRef(true);
4944
4856
  const [[matrices, colors]] = React.useState(() => {
4945
4857
  const mArray = new Float32Array(limit * 16);
4946
4858
  for (let i = 0; i < limit; i++) tempMatrix.identity().toArray(mArray, i * 16);
4947
4859
  return [mArray, new Float32Array([...new Array(limit * 3)].map(() => 1))];
4948
4860
  });
4949
4861
  React.useEffect(() => {
4950
- parentRef.current.instanceMatrix.needsUpdate = true;
4862
+ if (parentRef.current) parentRef.current.instanceMatrix.needsUpdate = true;
4951
4863
  });
4952
4864
  let iterations = 0;
4953
4865
  let count = 0;
4954
- const attributes = React.useRef([]);
4955
- React.useLayoutEffect(() => {
4956
- attributes.current = Object.entries(parentRef.current.geometry.attributes).filter(
4957
- ([_name, value]) => isInstancedBufferAttribute(value)
4958
- );
4959
- });
4960
4866
  useFrame(() => {
4961
4867
  if (frames === Infinity || iterations < frames) {
4868
+ if (!parentRef.current) return;
4962
4869
  parentRef.current.updateMatrix();
4963
4870
  parentRef.current.updateMatrixWorld();
4964
4871
  parentMatrix.copy(parentRef.current.matrixWorld).invert();
@@ -4979,13 +4886,22 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
4979
4886
  }
4980
4887
  iterations++;
4981
4888
  }
4889
+ if (boundsDirty.current && (frames === Infinity || iterations === frames)) {
4890
+ boundsDirty.current = false;
4891
+ if (parentRef.current.boundingBox) parentRef.current.computeBoundingBox();
4892
+ if (parentRef.current.boundingSphere) parentRef.current.computeBoundingSphere();
4893
+ }
4982
4894
  });
4983
4895
  const api = React.useMemo(
4984
4896
  () => ({
4985
4897
  getParent: () => parentRef,
4986
4898
  subscribe: (ref2) => {
4987
4899
  setInstances((instances2) => [...instances2, ref2]);
4988
- return () => setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
4900
+ boundsDirty.current = true;
4901
+ return () => {
4902
+ setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
4903
+ boundsDirty.current = true;
4904
+ };
4989
4905
  }
4990
4906
  }),
4991
4907
  []
@@ -5010,10 +4926,23 @@ const Merged = /* @__PURE__ */ React.forwardRef(function Merged2({ meshes, child
5010
4926
  if (!isArray) {
5011
4927
  for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key];
5012
4928
  }
5013
- const render = (args) => isArray ? children(...args) : children(
5014
- Object.keys(meshes).filter((key) => meshes[key].isMesh).reduce((acc, key, i) => ({ ...acc, [key]: args[i] }), {})
5015
- );
5016
- const components = (isArray ? meshes : Object.values(meshes)).map(({ geometry, material }) => /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props }));
4929
+ const render = (args) => {
4930
+ if (isArray) {
4931
+ return children(
4932
+ ...args
4933
+ );
4934
+ }
4935
+ const instanceMap = {};
4936
+ const keys = Object.keys(meshes).filter((key) => meshes[key].isMesh);
4937
+ keys.forEach((key, i) => {
4938
+ instanceMap[key] = args[i];
4939
+ });
4940
+ return children(instanceMap);
4941
+ };
4942
+ const components = (isArray ? meshes : Object.values(meshes)).map((mesh) => {
4943
+ const { geometry, material } = mesh;
4944
+ return /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props });
4945
+ });
5017
4946
  return /* @__PURE__ */ React.createElement("group", { ref }, renderRecursive(render, components));
5018
4947
  });
5019
4948
  function renderRecursive(render, components, results = []) {
@@ -5039,7 +4968,7 @@ function createInstances() {
5039
4968
  ];
5040
4969
  }
5041
4970
  const InstancedAttribute = React.forwardRef(
5042
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
4971
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
5043
4972
  const ref = React.useRef(null);
5044
4973
  React.useImperativeHandle(fref, () => ref.current, []);
5045
4974
  React.useLayoutEffect(() => {
@@ -5047,7 +4976,26 @@ const InstancedAttribute = React.forwardRef(
5047
4976
  parent.geometry.attributes[name] = ref.current;
5048
4977
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
5049
4978
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
5050
- ref.current.array = new Float32Array(array);
4979
+ switch (type) {
4980
+ case "float":
4981
+ ref.current.array = new Float32Array(array);
4982
+ break;
4983
+ case "int":
4984
+ ref.current.array = new Int32Array(array);
4985
+ break;
4986
+ case "uint":
4987
+ ref.current.array = new Uint32Array(array);
4988
+ break;
4989
+ case "short":
4990
+ ref.current.array = new Int16Array(array);
4991
+ break;
4992
+ case "ushort":
4993
+ ref.current.array = new Uint16Array(array);
4994
+ break;
4995
+ case "byte":
4996
+ ref.current.array = new Uint8Array(array);
4997
+ break;
4998
+ }
5051
4999
  ref.current.itemSize = value.length;
5052
5000
  ref.current.count = array.length / ref.current.itemSize;
5053
5001
  return () => {
@@ -6068,12 +6016,13 @@ function Sampler({ children, weight, transform, instances, mesh, count = 16, ...
6068
6016
  return /* @__PURE__ */ React.createElement("group", { ref: group, ...props }, children);
6069
6017
  }
6070
6018
 
6019
+ const pathStyle = (path) => path.userData?.style;
6071
6020
  const Svg = /* @__PURE__ */ forwardRef(
6072
6021
  function R3FSvg({ src, skipFill, skipStrokes, fillMaterial, strokeMaterial, fillMeshProps, strokeMeshProps, ...props }, ref) {
6073
6022
  const svg = useLoader(SVGLoader, !src.startsWith("<svg") ? src : `data:image/svg+xml;utf8,${src}`);
6074
6023
  const strokeGeometries = useMemo(
6075
6024
  () => skipStrokes ? [] : svg.paths.map(
6076
- (path) => path.userData?.style.stroke === void 0 || path.userData.style.stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), path.userData.style))
6025
+ (path) => pathStyle(path)?.stroke === void 0 || pathStyle(path).stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), pathStyle(path)))
6077
6026
  ),
6078
6027
  [svg, skipStrokes]
6079
6028
  );
@@ -6081,21 +6030,21 @@ const Svg = /* @__PURE__ */ forwardRef(
6081
6030
  return () => strokeGeometries.forEach((group) => group && group.map((g) => g.dispose()));
6082
6031
  }, [strokeGeometries]);
6083
6032
  let renderOrder = 0;
6084
- return /* @__PURE__ */ React.createElement("object3D", { ref, ...props }, /* @__PURE__ */ React.createElement("object3D", { scale: [1, -1, 1] }, svg.paths.map((path, p) => /* @__PURE__ */ React.createElement(Fragment, { key: p }, !skipFill && path.userData?.style.fill !== void 0 && path.userData.style.fill !== "none" && SVGLoader.createShapes(path).map((shape, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, ...fillMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement("shapeGeometry", { args: [shape] }), /* @__PURE__ */ React.createElement(
6033
+ return /* @__PURE__ */ React.createElement("object3D", { ref, ...props }, /* @__PURE__ */ React.createElement("object3D", { scale: [1, -1, 1] }, svg.paths.map((path, p) => /* @__PURE__ */ React.createElement(Fragment, { key: p }, !skipFill && pathStyle(path)?.fill !== void 0 && pathStyle(path).fill !== "none" && SVGLoader.createShapes(path).map((shape, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, ...fillMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement("shapeGeometry", { args: [shape] }), /* @__PURE__ */ React.createElement(
6085
6034
  "meshBasicMaterial",
6086
6035
  {
6087
- color: path.userData.style.fill,
6088
- opacity: path.userData.style.fillOpacity,
6036
+ color: pathStyle(path).fill,
6037
+ opacity: pathStyle(path).fillOpacity,
6089
6038
  transparent: true,
6090
6039
  side: DoubleSide,
6091
6040
  depthWrite: false,
6092
6041
  ...fillMaterial
6093
6042
  }
6094
- ))), !skipStrokes && path.userData?.style.stroke !== void 0 && path.userData.style.stroke !== "none" && path.subPaths.map((_subPath, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, geometry: strokeGeometries[p][s], ...strokeMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement(
6043
+ ))), !skipStrokes && pathStyle(path)?.stroke !== void 0 && pathStyle(path).stroke !== "none" && path.subPaths.map((_subPath, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, geometry: strokeGeometries[p][s], ...strokeMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement(
6095
6044
  "meshBasicMaterial",
6096
6045
  {
6097
- color: path.userData.style.stroke,
6098
- opacity: path.userData.style.strokeOpacity,
6046
+ color: pathStyle(path).stroke,
6047
+ opacity: pathStyle(path).strokeOpacity,
6099
6048
  transparent: true,
6100
6049
  side: DoubleSide,
6101
6050
  depthWrite: false,
@@ -6318,8 +6267,8 @@ function useVideoTexture(srcOrSrcObject, {
6318
6267
  });
6319
6268
  if (src && IS_BROWSER && src.endsWith(".m3u8")) {
6320
6269
  const hls = hlsRef.current = await getHls(hlsConfig);
6321
- if (hls) {
6322
- hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
6270
+ if (hls && _HLSModule) {
6271
+ hls.on(_HLSModule.Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
6323
6272
  hls.attachMedia(video2);
6324
6273
  }
6325
6274
  }
@@ -6539,9 +6488,125 @@ function Fbx({
6539
6488
  return /* @__PURE__ */ React.createElement(Clone, { ...props, object });
6540
6489
  }
6541
6490
 
6491
+ const SERVICE_KEY = "__drei_ktx2_service__";
6492
+ const DEFAULT_TRANSCODER_PATH = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
6493
+ class KTX2LoaderServiceImpl {
6494
+ loader = null;
6495
+ renderer = null;
6496
+ _isReady = false;
6497
+ transcoderPath = DEFAULT_TRANSCODER_PATH;
6498
+ pendingCallbacks = [];
6499
+ /**
6500
+ * Get or create the singleton KTX2Loader instance.
6501
+ * The loader is created lazily on first access.
6502
+ */
6503
+ getLoader() {
6504
+ if (!this.loader) {
6505
+ this.loader = new KTX2Loader();
6506
+ this.loader.setTranscoderPath(this.transcoderPath);
6507
+ }
6508
+ return this.loader;
6509
+ }
6510
+ /**
6511
+ * Register a renderer and call detectSupport.
6512
+ * Should be called from within React context (useLayoutEffect).
6513
+ * Flushes any queued callbacks.
6514
+ */
6515
+ registerRenderer(renderer) {
6516
+ if (this._isReady && this.renderer === renderer) {
6517
+ return;
6518
+ }
6519
+ this.renderer = renderer;
6520
+ const loader = this.getLoader();
6521
+ loader.detectSupport(renderer);
6522
+ this._isReady = true;
6523
+ this.flushPendingCallbacks();
6524
+ }
6525
+ /**
6526
+ * Check if the service is ready (has renderer registered).
6527
+ */
6528
+ isReady() {
6529
+ return this._isReady;
6530
+ }
6531
+ /**
6532
+ * Get the registered renderer, if any.
6533
+ */
6534
+ getRenderer() {
6535
+ return this.renderer;
6536
+ }
6537
+ /**
6538
+ * Register a callback to run when renderer becomes available.
6539
+ * If renderer is already available, runs immediately.
6540
+ */
6541
+ onReady(callback) {
6542
+ if (this._isReady) {
6543
+ callback();
6544
+ } else {
6545
+ this.pendingCallbacks.push(callback);
6546
+ }
6547
+ }
6548
+ /**
6549
+ * Process all pending callbacks.
6550
+ */
6551
+ flushPendingCallbacks() {
6552
+ const callbacks = this.pendingCallbacks;
6553
+ this.pendingCallbacks = [];
6554
+ for (const callback of callbacks) {
6555
+ try {
6556
+ callback();
6557
+ } catch (error) {
6558
+ console.error("KTX2LoaderService: Error in pending callback:", error);
6559
+ }
6560
+ }
6561
+ }
6562
+ /**
6563
+ * Set the transcoder path for basis/ktx2 decoding.
6564
+ */
6565
+ setTranscoderPath(path) {
6566
+ this.transcoderPath = path;
6567
+ if (this.loader) {
6568
+ this.loader.setTranscoderPath(path);
6569
+ }
6570
+ }
6571
+ /**
6572
+ * Get the current transcoder path.
6573
+ */
6574
+ getTranscoderPath() {
6575
+ return this.transcoderPath;
6576
+ }
6577
+ /**
6578
+ * Dispose of the loader and reset state.
6579
+ * Useful for testing or cleanup.
6580
+ */
6581
+ dispose() {
6582
+ if (this.loader) {
6583
+ this.loader.dispose();
6584
+ }
6585
+ const path = this.transcoderPath;
6586
+ this.loader = null;
6587
+ this.renderer = null;
6588
+ this._isReady = false;
6589
+ this.transcoderPath = path;
6590
+ this.pendingCallbacks = [];
6591
+ }
6592
+ }
6593
+ const KTX2LoaderService = globalThis[SERVICE_KEY] || (globalThis[SERVICE_KEY] = new KTX2LoaderServiceImpl());
6594
+
6542
6595
  let dracoLoader = null;
6543
6596
  let decoderPath = "https://www.gstatic.com/draco/versioned/decoders/1.5.5/";
6544
- function extensions(useDraco = true, useMeshopt = true, extendLoader) {
6597
+ let legacyApiWarned = false;
6598
+ function isOptionsObject(arg) {
6599
+ return typeof arg === "object" && arg !== null && !Array.isArray(arg);
6600
+ }
6601
+ function warnLegacyApi() {
6602
+ if (!legacyApiWarned) {
6603
+ legacyApiWarned = true;
6604
+ console.warn(
6605
+ "useGLTF: Boolean arguments are deprecated. Use options object instead:\n useGLTF(path, { draco: true, meshopt: true, ktx2: true })"
6606
+ );
6607
+ }
6608
+ }
6609
+ function extensions(useDraco = true, useMeshopt = true, useKTX2 = false, extendLoader, renderer) {
6545
6610
  return (loader) => {
6546
6611
  if (extendLoader) {
6547
6612
  extendLoader(loader);
@@ -6557,31 +6622,110 @@ function extensions(useDraco = true, useMeshopt = true, extendLoader) {
6557
6622
  const decoder = typeof MeshoptDecoder === "function" ? MeshoptDecoder() : MeshoptDecoder;
6558
6623
  loader.setMeshoptDecoder(decoder);
6559
6624
  }
6625
+ if (useKTX2) {
6626
+ const ktx2Loader = KTX2LoaderService.getLoader();
6627
+ if (typeof useKTX2 === "string") {
6628
+ ktx2Loader.setTranscoderPath(useKTX2);
6629
+ }
6630
+ if (renderer) {
6631
+ ktx2Loader.detectSupport(renderer);
6632
+ }
6633
+ loader.setKTX2Loader(ktx2Loader);
6634
+ }
6560
6635
  };
6561
6636
  }
6562
- const useGLTF = (path, useDraco, useMeshopt, extendLoader) => useLoader(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
6563
- useGLTF.preload = (path, useDraco, useMeshopt, extendLoader) => useLoader.preload(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
6637
+ function useGLTF(path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) {
6638
+ let draco = true;
6639
+ let meshopt = true;
6640
+ let ktx2 = false;
6641
+ let extendLoader;
6642
+ if (isOptionsObject(optionsOrDraco)) {
6643
+ draco = optionsOrDraco.draco ?? true;
6644
+ meshopt = optionsOrDraco.meshopt ?? true;
6645
+ ktx2 = optionsOrDraco.ktx2 ?? false;
6646
+ extendLoader = optionsOrDraco.extendLoader;
6647
+ } else if (optionsOrDraco !== void 0) {
6648
+ warnLegacyApi();
6649
+ draco = optionsOrDraco;
6650
+ meshopt = useMeshoptArg ?? true;
6651
+ ktx2 = useKTX2Arg ?? false;
6652
+ extendLoader = extendLoaderArg;
6653
+ }
6654
+ const renderer = useThree((state) => state.renderer);
6655
+ useLayoutEffect(() => {
6656
+ if (ktx2) {
6657
+ KTX2LoaderService.registerRenderer(renderer);
6658
+ }
6659
+ }, [ktx2, renderer]);
6660
+ return useLoader(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, ktx2 ? renderer : void 0));
6661
+ }
6662
+ useGLTF.preload = (path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) => {
6663
+ let draco = true;
6664
+ let meshopt = true;
6665
+ let ktx2 = false;
6666
+ let extendLoader;
6667
+ if (isOptionsObject(optionsOrDraco)) {
6668
+ draco = optionsOrDraco.draco ?? true;
6669
+ meshopt = optionsOrDraco.meshopt ?? true;
6670
+ ktx2 = optionsOrDraco.ktx2 ?? false;
6671
+ extendLoader = optionsOrDraco.extendLoader;
6672
+ } else if (optionsOrDraco !== void 0) {
6673
+ warnLegacyApi();
6674
+ draco = optionsOrDraco;
6675
+ meshopt = useMeshoptArg ?? true;
6676
+ ktx2 = useKTX2Arg ?? false;
6677
+ extendLoader = extendLoaderArg;
6678
+ }
6679
+ if (ktx2) {
6680
+ KTX2LoaderService.onReady(() => {
6681
+ const renderer = KTX2LoaderService.getRenderer();
6682
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, renderer));
6683
+ });
6684
+ } else {
6685
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, false, extendLoader));
6686
+ }
6687
+ };
6564
6688
  useGLTF.clear = (path) => useLoader.clear(GLTFLoader, path);
6565
6689
  useGLTF.setDecoderPath = (path) => {
6566
6690
  decoderPath = path;
6567
6691
  };
6692
+ useGLTF.setKTX2TranscoderPath = (path) => {
6693
+ KTX2LoaderService.setTranscoderPath(path);
6694
+ };
6568
6695
  const Gltf = /* @__PURE__ */ React.forwardRef(
6569
- ({ src, useDraco, useMeshOpt, extendLoader, ...props }, ref) => {
6570
- const { scene } = useGLTF(src, useDraco, useMeshOpt, extendLoader);
6696
+ ({ src, useDraco, useMeshOpt, extendLoader, options, ...props }, ref) => {
6697
+ const opts = options ?? {
6698
+ draco: useDraco,
6699
+ meshopt: useMeshOpt,
6700
+ extendLoader
6701
+ };
6702
+ const { scene } = useGLTF(src, opts);
6571
6703
  return /* @__PURE__ */ React.createElement(Clone, { ref, ...props, object: scene });
6572
6704
  }
6573
6705
  );
6574
6706
 
6575
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
6576
- function useKTX2(input, basisPath = `${cdn}/basis/`) {
6707
+ function useKTX2(input, basisPath = true, extendLoader) {
6577
6708
  const renderer = useThree((state) => state.renderer);
6709
+ useLayoutEffect(() => {
6710
+ KTX2LoaderService.registerRenderer(renderer);
6711
+ }, [renderer]);
6578
6712
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
6713
+ if (extendLoader) {
6714
+ extendLoader(loader);
6715
+ }
6716
+ if (basisPath) {
6717
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
6718
+ loader.setTranscoderPath(path);
6719
+ }
6579
6720
  loader.detectSupport(renderer);
6580
- loader.setTranscoderPath(basisPath);
6581
6721
  });
6582
6722
  useEffect(() => {
6583
6723
  const array = Array.isArray(textures) ? textures : [textures];
6584
- array.forEach((texture) => renderer.initTexture(texture));
6724
+ array.forEach((texture) => {
6725
+ if ("initTexture" in renderer) {
6726
+ renderer.initTexture(texture);
6727
+ }
6728
+ });
6585
6729
  }, [renderer, textures]);
6586
6730
  if (IsObject(input)) {
6587
6731
  const keys = Object.keys(input);
@@ -6592,19 +6736,81 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
6592
6736
  return textures;
6593
6737
  }
6594
6738
  }
6595
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
6596
- loader.setTranscoderPath(basisPath);
6597
- });
6739
+ useKTX2.preload = (url, basisPath = true, extendLoader) => {
6740
+ KTX2LoaderService.onReady(() => {
6741
+ const renderer = KTX2LoaderService.getRenderer();
6742
+ useLoader.preload(KTX2Loader, url, (loader) => {
6743
+ if (extendLoader) {
6744
+ extendLoader(loader);
6745
+ }
6746
+ if (basisPath) {
6747
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
6748
+ loader.setTranscoderPath(path);
6749
+ }
6750
+ if (renderer) {
6751
+ loader.detectSupport(renderer);
6752
+ }
6753
+ });
6754
+ });
6755
+ };
6598
6756
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
6599
- const Ktx2 = ({
6600
- children,
6601
- input,
6602
- basisPath
6603
- }) => {
6604
- const texture = useKTX2(input, basisPath);
6757
+ useKTX2.setTranscoderPath = (path) => {
6758
+ KTX2LoaderService.setTranscoderPath(path);
6759
+ };
6760
+ const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
6761
+ const texture = useKTX2(input, basisPath, extendLoader);
6605
6762
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
6606
6763
  };
6607
6764
 
6765
+ const useVariants = (model, inVariant) => {
6766
+ const [activeVariant, setActiveVariant] = useState(null);
6767
+ const [materials, setMaterials] = useState([]);
6768
+ useEffect(() => {
6769
+ model.parser.getDependencies("material").then((materials2) => {
6770
+ setMaterials(materials2);
6771
+ });
6772
+ }, [model]);
6773
+ const variants = useMemo(() => {
6774
+ return model.userData.gltfExtensions?.KHR_materials_variants?.variants;
6775
+ }, [model]);
6776
+ const applyVariant = useCallback(
6777
+ (variantIndex) => {
6778
+ model.scene.traverse(async (object) => {
6779
+ if (!("isMesh" in object) || !object.userData.gltfExtensions) return;
6780
+ const meshObject = object;
6781
+ const meshVariantDef = meshObject.userData.gltfExtensions.KHR_materials_variants;
6782
+ if (!meshVariantDef) return;
6783
+ if (!meshObject.userData.originalMaterial) meshObject.userData.originalMaterial = meshObject.material;
6784
+ const mapping = meshVariantDef.mappings.find((mapping2) => mapping2.variants.includes(variantIndex));
6785
+ if (mapping) {
6786
+ meshObject.material = await model.parser.getDependency("material", mapping.material);
6787
+ model.parser.assignFinalMaterial(meshObject);
6788
+ } else meshObject.material = meshObject.userData.originalMaterial;
6789
+ if (Array.isArray(meshObject.material)) for (const mat of meshObject.material) mat.needsUpdate = true;
6790
+ else meshObject.material.needsUpdate = true;
6791
+ });
6792
+ },
6793
+ [model]
6794
+ );
6795
+ const setVariant = useCallback(
6796
+ (variantName) => {
6797
+ if (!variants) return;
6798
+ const newVariant = variantName && variants.some((v) => v.name === variantName) ? variantName : null;
6799
+ setActiveVariant(newVariant);
6800
+ },
6801
+ [variants]
6802
+ );
6803
+ useEffect(() => {
6804
+ if (!variants) return;
6805
+ const variantIndex = variants.findIndex((v) => v.name === activeVariant);
6806
+ applyVariant(variantIndex > -1 ? variantIndex : 0);
6807
+ }, [variants, activeVariant, applyVariant]);
6808
+ useEffect(() => {
6809
+ setVariant(inVariant ?? null);
6810
+ }, [inVariant, setVariant]);
6811
+ return { variants, activeVariant, setVariant, materials };
6812
+ };
6813
+
6608
6814
  const WebcamVideoTexture = /* @__PURE__ */ forwardRef(
6609
6815
  ({
6610
6816
  constraints = {
@@ -6792,6 +6998,7 @@ function useEffectfulState(fn, deps = [], cb) {
6792
6998
  }
6793
6999
 
6794
7000
  function Stats({ showPanel = 0, className, parent }) {
7001
+ React.useEffect(() => void console.warn("drei: <Stats /> is deprecated, use <StatsGl /> instead."), []);
6795
7002
  const stats = useEffectfulState(() => new StatsImpl(), []);
6796
7003
  React.useEffect(() => {
6797
7004
  if (stats) {
@@ -6820,6 +7027,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
6820
7027
  const stats2 = new Stats$1({
6821
7028
  ...props
6822
7029
  });
7030
+ stats2.init(renderer);
6823
7031
  return stats2;
6824
7032
  }, [renderer]);
6825
7033
  React.useImperativeHandle(fref, () => stats.domElement, [stats]);
@@ -6837,7 +7045,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
6837
7045
  return () => {
6838
7046
  if (classNames.length) stats.domElement.classList.remove(...classNames);
6839
7047
  node?.removeChild(stats.domElement);
6840
- stats.end();
7048
+ stats.dispose();
6841
7049
  };
6842
7050
  }
6843
7051
  }, [parent, stats, className, id, clearStatsGlStyle]);
@@ -6845,7 +7053,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
6845
7053
  () => {
6846
7054
  stats.begin();
6847
7055
  },
6848
- { before: "render" }
7056
+ { before: "update" }
6849
7057
  );
6850
7058
  useFrame(
6851
7059
  () => {
@@ -6895,11 +7103,11 @@ const RenderCubeTexture = /* @__PURE__ */ React.forwardRef(
6895
7103
  depthBuffer,
6896
7104
  generateMipmaps
6897
7105
  };
6898
- const fboA2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
7106
+ const fboA2 = new WebGLCubeRenderTarget(fboSize, settings);
6899
7107
  fboA2.texture.isRenderTargetTexture = !flip;
6900
7108
  fboA2.texture.flipY = true;
6901
7109
  fboA2.texture.type = THREE.HalfFloatType;
6902
- const fboB2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
7110
+ const fboB2 = new WebGLCubeRenderTarget(fboSize, settings);
6903
7111
  fboB2.texture.isRenderTargetTexture = !flip;
6904
7112
  fboB2.texture.flipY = true;
6905
7113
  fboB2.texture.type = THREE.HalfFloatType;
@@ -7077,18 +7285,21 @@ function Fisheye({
7077
7285
  const normal = new THREE.Vector3();
7078
7286
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
7079
7287
  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
- }, []);
7288
+ const compute = React.useCallback(
7289
+ (event, state, prev) => {
7290
+ state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7291
+ state.raycaster.setFromCamera(state.pointer, orthoC);
7292
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7293
+ else normal.normalize();
7294
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7295
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7296
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7297
+ state.raycaster.ray.direction.x *= -1;
7298
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7299
+ return void 0;
7300
+ },
7301
+ [width, height]
7302
+ );
7092
7303
  useFrame(
7093
7304
  ({ gl }) => {
7094
7305
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -7223,14 +7434,23 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
7223
7434
  const col = /* @__PURE__ */ new THREE.Color();
7224
7435
  const TUNNEL_KEY = "__drei_view_tunnel__";
7225
7436
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
7226
- function computeContainerPosition(canvasSize, trackRect) {
7437
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
7227
7438
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
7228
- const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
7229
7439
  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 };
7440
+ const canvasRight = canvasSize.left + canvasSize.width;
7441
+ const isOffscreen = trackBottom < canvasSize.top || trackTop > canvasBottom || right < canvasSize.left || trackLeft > canvasRight;
7442
+ const rawBottom = canvasBottom - trackBottom;
7443
+ const rawLeft = trackLeft - canvasSize.left;
7444
+ const rawTop = trackTop - canvasSize.top;
7445
+ const position = forceEven ? {
7446
+ width: roundEven(width),
7447
+ height: roundEven(height),
7448
+ left: roundEven(rawLeft),
7449
+ top: roundEven(rawTop),
7450
+ bottom: roundEven(rawBottom),
7451
+ right: roundEven(right)
7452
+ } : { width, height, left: rawLeft, top: rawTop, bottom: rawBottom, right };
7453
+ return { position, isOffscreen };
7234
7454
  }
7235
7455
  function prepareSkissor(renderer, camera, {
7236
7456
  left,
@@ -7270,6 +7490,7 @@ function clear(renderer) {
7270
7490
  }
7271
7491
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
7272
7492
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
7493
+ const forceEven = useThree((state) => state.internal.forceEven);
7273
7494
  const [isOffscreen, setOffscreen] = React.useState(false);
7274
7495
  const mountedRef = React.useRef(true);
7275
7496
  React.useEffect(() => {
@@ -7287,7 +7508,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7287
7508
  frameCount++;
7288
7509
  }
7289
7510
  if (rect.current) {
7290
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
7511
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
7291
7512
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
7292
7513
  if (visible && !_isOffscreen && rect.current) {
7293
7514
  const targetScene = children ? portalScene : scene;
@@ -7309,14 +7530,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7309
7530
  React.useLayoutEffect(() => {
7310
7531
  const curRect = rect.current;
7311
7532
  if (curRect && (!visible || !isOffscreen)) {
7312
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7533
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7313
7534
  if (!_isOffscreen) {
7314
7535
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7315
7536
  clear(renderer);
7316
7537
  finishSkissor(renderer, autoClear);
7317
7538
  }
7318
7539
  }
7319
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
7540
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
7320
7541
  React.useEffect(() => {
7321
7542
  if (!track) return;
7322
7543
  const curRect = rect.current;
@@ -7324,7 +7545,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7324
7545
  setEvents({ connected: track.current });
7325
7546
  return () => {
7326
7547
  if (curRect) {
7327
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7548
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7328
7549
  if (!_isOffscreen) {
7329
7550
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7330
7551
  clear(renderer);
@@ -7333,13 +7554,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7333
7554
  }
7334
7555
  setEvents({ connected: old });
7335
7556
  };
7336
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
7557
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
7337
7558
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
7338
7559
  }
7339
7560
  const CanvasView = /* @__PURE__ */ React.forwardRef(
7340
7561
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
7341
7562
  const rect = React.useRef(null);
7342
7563
  const { size, scene } = useThree();
7564
+ const forceEven = useThree((state) => state.internal.forceEven);
7343
7565
  const [virtualScene] = React.useState(() => new THREE.Scene());
7344
7566
  const [ready, toggle] = React.useReducer(() => true, false);
7345
7567
  const compute = React.useCallback(
@@ -7376,12 +7598,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
7376
7598
  {
7377
7599
  events: { compute, priority: index },
7378
7600
  size: {
7379
- width: rect.current?.width,
7380
- height: rect.current?.height,
7601
+ width: forceEven && rect.current?.width ? roundEven(rect.current.width) : rect.current?.width,
7602
+ height: forceEven && rect.current?.height ? roundEven(rect.current.height) : rect.current?.height,
7381
7603
  // @ts-ignore
7382
- top: rect.current?.top,
7604
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
7383
7605
  // @ts-ignore
7384
- left: rect.current?.left
7606
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
7385
7607
  }
7386
7608
  }
7387
7609
  ));
@@ -7776,12 +7998,12 @@ useEnvironment.preload = (preloadOptions) => {
7776
7998
  if (extensions) extensions(loader2);
7777
7999
  });
7778
8000
  };
7779
- const clearDefaultOptins = {
8001
+ const clearDefaultOptions = {
7780
8002
  files: defaultFiles,
7781
8003
  preset: void 0
7782
8004
  };
7783
8005
  useEnvironment.clear = (clearOptions) => {
7784
- const options = { ...clearDefaultOptins, ...clearOptions };
8006
+ const options = { ...clearDefaultOptions, ...clearOptions };
7785
8007
  let { files } = options;
7786
8008
  const { preset } = options;
7787
8009
  if (preset) {
@@ -8475,9 +8697,14 @@ function Select({
8475
8697
  const oldControlsEnabled = controls?.enabled;
8476
8698
  let isDown = false;
8477
8699
  function prepareRay(event, vec) {
8478
- const { offsetX, offsetY } = event;
8700
+ const canvas = gl.domElement;
8701
+ const rect = canvas.getBoundingClientRect();
8702
+ const x = event.clientX - rect.left;
8703
+ const y = event.clientY - rect.top;
8479
8704
  const { width, height } = size;
8480
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
8705
+ const normalizedX = x / width * 2 - 1;
8706
+ const normalizedY = -(y / height) * 2 + 1;
8707
+ vec.set(normalizedX, normalizedY, -1);
8481
8708
  }
8482
8709
  function onSelectStart(event) {
8483
8710
  if (controls) controls.enabled = false;
@@ -8556,4 +8783,4 @@ function useSelect() {
8556
8783
  return React.useContext(context);
8557
8784
  }
8558
8785
 
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 };
8786
+ 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, useVariants, useVideoTexture };