@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/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, Suspense } from 'react';
3
3
  import { useThree, useFrame, extend, createPortal, applyProps, useInstanceHandle, context as context$9, useLoader, addEffect, addAfterEffect } from '@react-three/fiber';
4
4
  import * as THREE from 'three';
5
- import { REVISION, WebGLRenderTarget, Frustum, Matrix4, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, Quaternion, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, DoubleSide, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DepthTexture, DepthFormat, UnsignedShortType, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, Camera, CubeReflectionMapping, EquirectangularReflectionMapping, Scene, RepeatWrapping, Mesh, Spherical } from 'three';
5
+ import { REVISION, WebGLRenderTarget, Frustum, Matrix4, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, 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, Mesh, Spherical } from 'three';
6
6
  import { ArcballControls as ArcballControls$1 } from 'three/examples/jsm/controls/ArcballControls.js';
7
- import { DeviceOrientationControls as DeviceOrientationControls$1 } from 'three-stdlib';
8
- 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';
@@ -184,6 +182,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
184
182
  });
185
183
  }
186
184
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
185
+ Object.assign(this, parameters);
187
186
  }
188
187
  };
189
188
  }
@@ -200,7 +199,7 @@ const version = /* @__PURE__ */ getVersion();
200
199
  function isLight(object) {
201
200
  return object.isLight;
202
201
  }
203
- function isGeometry$1(object) {
202
+ function isGeometry(object) {
204
203
  return !!object.geometry;
205
204
  }
206
205
  const accumulativeContext = /* @__PURE__ */ React.createContext(
@@ -445,7 +444,7 @@ vec3 texelOld = texture2D(previousShadowMap, vUv).rgb;
445
444
  this.lights = [];
446
445
  this.meshes = [];
447
446
  this.scene.traverse((object) => {
448
- if (isGeometry$1(object)) {
447
+ if (isGeometry(object)) {
449
448
  this.meshes.push({ object, material: object.material });
450
449
  } else if (isLight(object)) {
451
450
  this.lights.push({ object, intensity: object.intensity });
@@ -1334,6 +1333,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
1334
1333
  }
1335
1334
  );
1336
1335
 
1336
+ let DeviceOrientationControls$1 = class DeviceOrientationControls extends EventDispatcher {
1337
+ object;
1338
+ changeEvent = { type: "change" };
1339
+ EPS = 1e-6;
1340
+ enabled = true;
1341
+ deviceOrientation = { alpha: 0, beta: 0, gamma: 0 };
1342
+ screenOrientation = 0;
1343
+ alphaOffset = 0;
1344
+ // radians
1345
+ constructor(object) {
1346
+ super();
1347
+ this.object = object;
1348
+ this.object.rotation.reorder("YXZ");
1349
+ this.connect();
1350
+ }
1351
+ onDeviceOrientationChangeEvent = (event) => {
1352
+ this.deviceOrientation = event;
1353
+ };
1354
+ onScreenOrientationChangeEvent = () => {
1355
+ this.screenOrientation = window.orientation || 0;
1356
+ };
1357
+ // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
1358
+ zee = new Vector3(0, 0, 1);
1359
+ euler = new Euler();
1360
+ q0 = new Quaternion();
1361
+ q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
1362
+ // - PI/2 around the x-axis
1363
+ setObjectQuaternion = (quaternion, alpha, beta, gamma, orient) => {
1364
+ this.euler.set(beta, alpha, -gamma, "YXZ");
1365
+ quaternion.setFromEuler(this.euler);
1366
+ quaternion.multiply(this.q1);
1367
+ quaternion.multiply(this.q0.setFromAxisAngle(this.zee, -orient));
1368
+ };
1369
+ connect = () => {
1370
+ this.onScreenOrientationChangeEvent();
1371
+ if (window.DeviceOrientationEvent !== void 0 && // @ts-ignore
1372
+ typeof window.DeviceOrientationEvent.requestPermission === "function") {
1373
+ window.DeviceOrientationEvent.requestPermission().then((response) => {
1374
+ if (response == "granted") {
1375
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1376
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1377
+ }
1378
+ }).catch((error) => {
1379
+ console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:", error);
1380
+ });
1381
+ } else {
1382
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1383
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1384
+ }
1385
+ this.enabled = true;
1386
+ };
1387
+ disconnect = () => {
1388
+ window.removeEventListener("orientationchange", this.onScreenOrientationChangeEvent);
1389
+ window.removeEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
1390
+ this.enabled = false;
1391
+ };
1392
+ lastQuaternion = new Quaternion();
1393
+ update = () => {
1394
+ if (this.enabled === false) return;
1395
+ const device = this.deviceOrientation;
1396
+ if (device) {
1397
+ const alpha = device.alpha ? MathUtils.degToRad(device.alpha) + this.alphaOffset : 0;
1398
+ const beta = device.beta ? MathUtils.degToRad(device.beta) : 0;
1399
+ const gamma = device.gamma ? MathUtils.degToRad(device.gamma) : 0;
1400
+ const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation) : 0;
1401
+ this.setObjectQuaternion(this.object.quaternion, alpha, beta, gamma, orient);
1402
+ if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
1403
+ this.lastQuaternion.copy(this.object.quaternion);
1404
+ this.dispatchEvent(this.changeEvent);
1405
+ }
1406
+ }
1407
+ };
1408
+ dispose = () => this.disconnect();
1409
+ };
1410
+
1337
1411
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1338
1412
  (props, ref) => {
1339
1413
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -1369,11 +1443,11 @@ const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
1369
1443
  );
1370
1444
 
1371
1445
  const initialModelPosition = /* @__PURE__ */ new THREE.Vector3();
1372
- const mousePosition2D = /* @__PURE__ */ new THREE.Vector2();
1373
1446
  const mousePosition3D = /* @__PURE__ */ new THREE.Vector3();
1374
1447
  const dragOffset = /* @__PURE__ */ new THREE.Vector3();
1375
1448
  const dragPlaneNormal = /* @__PURE__ */ new THREE.Vector3();
1376
1449
  const dragPlane = /* @__PURE__ */ new THREE.Plane();
1450
+ const startPointerPos = /* @__PURE__ */ new THREE.Vector3();
1377
1451
  const DragControls = React.forwardRef(
1378
1452
  ({
1379
1453
  autoTransform = true,
@@ -1389,89 +1463,132 @@ const DragControls = React.forwardRef(
1389
1463
  ...props
1390
1464
  }, fRef) => {
1391
1465
  const defaultControls = useThree((state) => state.controls);
1392
- const { camera, size, raycaster, invalidate } = useThree();
1466
+ const { camera, invalidate } = useThree();
1393
1467
  const ref = React.useRef(null);
1394
- const bind = useGesture(
1395
- {
1396
- onHover: ({ hovering }) => onHover && onHover(hovering ?? false),
1397
- onDragStart: ({ event }) => {
1398
- if (defaultControls) defaultControls.enabled = false;
1399
- const { point } = event;
1400
- ref.current.matrix.decompose(initialModelPosition, new THREE.Quaternion(), new THREE.Vector3());
1401
- mousePosition3D.copy(point);
1402
- dragOffset.copy(mousePosition3D).sub(initialModelPosition);
1403
- onDragStart && onDragStart(initialModelPosition);
1404
- invalidate();
1405
- },
1406
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
1407
- if (!intentional) return;
1408
- const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
1409
- const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
1410
- mousePosition2D.set(normalizedMouseX, normalizedMouseY);
1411
- raycaster.setFromCamera(mousePosition2D, camera);
1412
- if (!axisLock) {
1413
- camera.getWorldDirection(dragPlaneNormal).negate();
1414
- } else {
1415
- switch (axisLock) {
1416
- case "x":
1417
- dragPlaneNormal.set(1, 0, 0);
1418
- break;
1419
- case "y":
1420
- dragPlaneNormal.set(0, 1, 0);
1421
- break;
1422
- case "z":
1423
- dragPlaneNormal.set(0, 0, 1);
1424
- break;
1425
- }
1426
- }
1427
- dragPlane.setFromNormalAndCoplanarPoint(dragPlaneNormal, mousePosition3D);
1428
- raycaster.ray.intersectPlane(dragPlane, mousePosition3D);
1429
- const previousLocalMatrix = ref.current.matrix.clone();
1430
- const previousWorldMatrix = ref.current.matrixWorld.clone();
1431
- const intendedNewPosition = new THREE.Vector3(
1432
- mousePosition3D.x - dragOffset.x,
1433
- mousePosition3D.y - dragOffset.y,
1434
- mousePosition3D.z - dragOffset.z
1435
- );
1436
- if (dragLimits) {
1437
- intendedNewPosition.x = dragLimits[0] ? Math.max(Math.min(intendedNewPosition.x, dragLimits[0][1]), dragLimits[0][0]) : intendedNewPosition.x;
1438
- intendedNewPosition.y = dragLimits[1] ? Math.max(Math.min(intendedNewPosition.y, dragLimits[1][1]), dragLimits[1][0]) : intendedNewPosition.y;
1439
- intendedNewPosition.z = dragLimits[2] ? Math.max(Math.min(intendedNewPosition.z, dragLimits[2][1]), dragLimits[2][0]) : intendedNewPosition.z;
1440
- }
1441
- if (autoTransform) {
1442
- ref.current.matrix.setPosition(intendedNewPosition);
1443
- const deltaLocalMatrix = ref.current.matrix.clone().multiply(previousLocalMatrix.invert());
1444
- const deltaWorldMatrix = ref.current.matrix.clone().multiply(previousWorldMatrix.invert());
1445
- onDrag && onDrag(ref.current.matrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1446
- } else {
1447
- const tempMatrix = new THREE.Matrix4().copy(ref.current.matrix);
1448
- tempMatrix.setPosition(intendedNewPosition);
1449
- const deltaLocalMatrix = tempMatrix.clone().multiply(previousLocalMatrix.invert());
1450
- const deltaWorldMatrix = tempMatrix.clone().multiply(previousWorldMatrix.invert());
1451
- onDrag && onDrag(tempMatrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1468
+ const dragState = React.useRef({
1469
+ active: false,
1470
+ intentional: false,
1471
+ // Has moved past threshold
1472
+ pointerId: -1
1473
+ });
1474
+ const threshold = dragConfig?.threshold ?? 0;
1475
+ const filterTaps = dragConfig?.filterTaps ?? true;
1476
+ const handlePointerDown = React.useCallback(
1477
+ (event) => {
1478
+ event.stopPropagation();
1479
+ event.target.setPointerCapture(event.pointerId);
1480
+ dragState.current.active = true;
1481
+ dragState.current.intentional = threshold === 0;
1482
+ dragState.current.pointerId = event.pointerId;
1483
+ if (defaultControls) defaultControls.enabled = false;
1484
+ ref.current.matrix.decompose(initialModelPosition, new THREE.Quaternion(), new THREE.Vector3());
1485
+ mousePosition3D.copy(event.point);
1486
+ startPointerPos.copy(event.point);
1487
+ dragOffset.copy(mousePosition3D).sub(initialModelPosition);
1488
+ if (!axisLock) {
1489
+ camera.getWorldDirection(dragPlaneNormal).negate();
1490
+ } else {
1491
+ switch (axisLock) {
1492
+ case "x":
1493
+ dragPlaneNormal.set(1, 0, 0);
1494
+ break;
1495
+ case "y":
1496
+ dragPlaneNormal.set(0, 1, 0);
1497
+ break;
1498
+ case "z":
1499
+ dragPlaneNormal.set(0, 0, 1);
1500
+ break;
1452
1501
  }
1453
- invalidate();
1454
- },
1455
- onDragEnd: () => {
1456
- if (defaultControls) defaultControls.enabled = true;
1457
- onDragEnd && onDragEnd();
1458
- invalidate();
1459
1502
  }
1503
+ dragPlane.setFromNormalAndCoplanarPoint(dragPlaneNormal, mousePosition3D);
1504
+ if (dragState.current.intentional) {
1505
+ onDragStart && onDragStart(initialModelPosition);
1506
+ }
1507
+ invalidate();
1460
1508
  },
1461
- {
1462
- drag: {
1463
- filterTaps: true,
1464
- threshold: 1,
1465
- ...typeof dragConfig === "object" ? dragConfig : {}
1509
+ [defaultControls, camera, axisLock, threshold, onDragStart, invalidate]
1510
+ );
1511
+ const handlePointerMove = React.useCallback(
1512
+ (event) => {
1513
+ if (!dragState.current.active || event.pointerId !== dragState.current.pointerId) return;
1514
+ const intersection = event.ray.intersectPlane(dragPlane, mousePosition3D);
1515
+ if (!intersection) return;
1516
+ if (!dragState.current.intentional) {
1517
+ const distance = mousePosition3D.distanceTo(startPointerPos);
1518
+ if (distance < threshold) return;
1519
+ dragState.current.intentional = true;
1520
+ onDragStart && onDragStart(initialModelPosition);
1466
1521
  }
1467
- }
1522
+ const previousLocalMatrix = ref.current.matrix.clone();
1523
+ const previousWorldMatrix = ref.current.matrixWorld.clone();
1524
+ const intendedNewPosition = new THREE.Vector3(
1525
+ mousePosition3D.x - dragOffset.x,
1526
+ mousePosition3D.y - dragOffset.y,
1527
+ mousePosition3D.z - dragOffset.z
1528
+ );
1529
+ if (dragLimits) {
1530
+ intendedNewPosition.x = dragLimits[0] ? Math.max(Math.min(intendedNewPosition.x, dragLimits[0][1]), dragLimits[0][0]) : intendedNewPosition.x;
1531
+ intendedNewPosition.y = dragLimits[1] ? Math.max(Math.min(intendedNewPosition.y, dragLimits[1][1]), dragLimits[1][0]) : intendedNewPosition.y;
1532
+ intendedNewPosition.z = dragLimits[2] ? Math.max(Math.min(intendedNewPosition.z, dragLimits[2][1]), dragLimits[2][0]) : intendedNewPosition.z;
1533
+ }
1534
+ if (autoTransform) {
1535
+ ref.current.matrix.setPosition(intendedNewPosition);
1536
+ const deltaLocalMatrix = ref.current.matrix.clone().multiply(previousLocalMatrix.invert());
1537
+ const deltaWorldMatrix = ref.current.matrix.clone().multiply(previousWorldMatrix.invert());
1538
+ onDrag && onDrag(ref.current.matrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1539
+ } else {
1540
+ const tempMatrix = new THREE.Matrix4().copy(ref.current.matrix);
1541
+ tempMatrix.setPosition(intendedNewPosition);
1542
+ const deltaLocalMatrix = tempMatrix.clone().multiply(previousLocalMatrix.invert());
1543
+ const deltaWorldMatrix = tempMatrix.clone().multiply(previousWorldMatrix.invert());
1544
+ onDrag && onDrag(tempMatrix, deltaLocalMatrix, ref.current.matrixWorld, deltaWorldMatrix);
1545
+ }
1546
+ invalidate();
1547
+ },
1548
+ [autoTransform, dragLimits, threshold, onDragStart, onDrag, invalidate]
1549
+ );
1550
+ const handlePointerUp = React.useCallback(
1551
+ (event) => {
1552
+ if (!dragState.current.active || event.pointerId !== dragState.current.pointerId) return;
1553
+ event.target.releasePointerCapture(event.pointerId);
1554
+ const wasIntentional = dragState.current.intentional;
1555
+ dragState.current.active = false;
1556
+ dragState.current.intentional = false;
1557
+ dragState.current.pointerId = -1;
1558
+ if (defaultControls) defaultControls.enabled = true;
1559
+ if (wasIntentional || !filterTaps) {
1560
+ onDragEnd && onDragEnd();
1561
+ }
1562
+ invalidate();
1563
+ },
1564
+ [defaultControls, filterTaps, onDragEnd, invalidate]
1468
1565
  );
1566
+ const handlePointerEnter = React.useCallback(() => {
1567
+ onHover && onHover(true);
1568
+ }, [onHover]);
1569
+ const handlePointerLeave = React.useCallback(() => {
1570
+ onHover && onHover(false);
1571
+ }, [onHover]);
1469
1572
  React.useImperativeHandle(fRef, () => ref.current, []);
1470
1573
  React.useLayoutEffect(() => {
1471
1574
  if (!matrix) return;
1472
1575
  ref.current.matrix = matrix;
1473
1576
  }, [matrix]);
1474
- return /* @__PURE__ */ React.createElement("group", { ref, ...bind(), matrix, matrixAutoUpdate: false, ...props }, children);
1577
+ return /* @__PURE__ */ React.createElement(
1578
+ "group",
1579
+ {
1580
+ ref,
1581
+ onPointerDown: handlePointerDown,
1582
+ onPointerMove: handlePointerMove,
1583
+ onPointerUp: handlePointerUp,
1584
+ onPointerEnter: handlePointerEnter,
1585
+ onPointerLeave: handlePointerLeave,
1586
+ matrix,
1587
+ matrixAutoUpdate: false,
1588
+ ...props
1589
+ },
1590
+ children
1591
+ );
1475
1592
  }
1476
1593
  );
1477
1594
 
@@ -1573,14 +1690,23 @@ function KeyboardControls({ map, children, onChange, domElement }) {
1573
1690
  obj.pressed = false;
1574
1691
  if (up) fn(false);
1575
1692
  };
1693
+ const blurHandler = () => {
1694
+ const resetState = map.reduce((prev, cur) => ({ ...prev, [cur.name]: false }), {});
1695
+ set(resetState);
1696
+ Object.values(keyMap).forEach((obj) => {
1697
+ obj.pressed = false;
1698
+ });
1699
+ };
1576
1700
  const source = domElement || window;
1577
1701
  source.addEventListener("keydown", downHandler, { passive: true });
1578
1702
  source.addEventListener("keyup", upHandler, { passive: true });
1703
+ window.addEventListener("blur", blurHandler);
1579
1704
  return () => {
1580
1705
  source.removeEventListener("keydown", downHandler);
1581
1706
  source.removeEventListener("keyup", upHandler);
1707
+ window.removeEventListener("blur", blurHandler);
1582
1708
  };
1583
- }, [domElement, key]);
1709
+ }, [domElement, key, onChange]);
1584
1710
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
1585
1711
  }
1586
1712
  function useKeyboardControls(sel) {
@@ -1602,20 +1728,28 @@ const MapControls = /* @__PURE__ */ React.forwardRef((props = { enableDamping: t
1602
1728
  const controls = React.useMemo(() => new MapControls$1(explCamera), [explCamera]);
1603
1729
  React.useEffect(() => {
1604
1730
  controls.connect(explDomElement);
1731
+ return () => void controls.dispose();
1732
+ }, [explDomElement, controls]);
1733
+ React.useEffect(() => {
1605
1734
  const callback = (e) => {
1606
1735
  invalidate();
1607
1736
  if (onChange) onChange(e);
1608
1737
  };
1738
+ const onStartCb = (e) => {
1739
+ if (onStart) onStart(e);
1740
+ };
1741
+ const onEndCb = (e) => {
1742
+ if (onEnd) onEnd(e);
1743
+ };
1609
1744
  controls.addEventListener("change", callback);
1610
- if (onStart) controls.addEventListener("start", onStart);
1611
- if (onEnd) controls.addEventListener("end", onEnd);
1745
+ controls.addEventListener("start", onStartCb);
1746
+ controls.addEventListener("end", onEndCb);
1612
1747
  return () => {
1613
- controls.dispose();
1748
+ controls.removeEventListener("start", onStartCb);
1749
+ controls.removeEventListener("end", onEndCb);
1614
1750
  controls.removeEventListener("change", callback);
1615
- if (onStart) controls.removeEventListener("start", onStart);
1616
- if (onEnd) controls.removeEventListener("end", onEnd);
1617
1751
  };
1618
- }, [onChange, onStart, onEnd, controls, invalidate, explDomElement]);
1752
+ }, [onChange, onStart, onEnd, controls, invalidate]);
1619
1753
  React.useEffect(() => {
1620
1754
  if (makeDefault) {
1621
1755
  const old = get().controls;
@@ -1623,7 +1757,9 @@ const MapControls = /* @__PURE__ */ React.forwardRef((props = { enableDamping: t
1623
1757
  return () => set({ controls: old });
1624
1758
  }
1625
1759
  }, [makeDefault, controls]);
1626
- useFrame(() => controls.update(), -1);
1760
+ useFrame(() => {
1761
+ if (controls.enabled) controls.update();
1762
+ }, -1);
1627
1763
  return /* @__PURE__ */ React.createElement("primitive", { ref, object: controls, enableDamping: true, ...rest });
1628
1764
  });
1629
1765
 
@@ -1749,20 +1885,16 @@ const OrbitControls = /* @__PURE__ */ React.forwardRef(
1749
1885
  onEnd,
1750
1886
  ...restProps
1751
1887
  }, ref) => {
1752
- const invalidate = useThree((state) => state.invalidate);
1753
- const defaultCamera = useThree((state) => state.camera);
1754
- const gl = useThree((state) => state.gl);
1755
- const events = useThree((state) => state.events);
1756
- const setEvents = useThree((state) => state.setEvents);
1757
- const set = useThree((state) => state.set);
1758
- const get = useThree((state) => state.get);
1759
- const performance = useThree((state) => state.performance);
1888
+ const { invalidate, camera: defaultCamera, renderer, events, setEvents, set, get, performance } = useThree();
1760
1889
  const explCamera = camera || defaultCamera;
1761
- const explDomElement = domElement || events.connected || gl.domElement;
1890
+ const explDomElement = domElement || events.connected || renderer.domElement;
1762
1891
  const controls = React.useMemo(() => new OrbitControls$1(explCamera), [explCamera]);
1763
- useFrame(() => {
1764
- if (controls.enabled) controls.update();
1765
- }, -1);
1892
+ useFrame(
1893
+ () => {
1894
+ if (controls.enabled) controls.update();
1895
+ },
1896
+ { before: "update" }
1897
+ );
1766
1898
  React.useEffect(() => {
1767
1899
  if (keyEvents) {
1768
1900
  controls.connect(keyEvents === true ? explDomElement : keyEvents);
@@ -1876,9 +2008,8 @@ function PresentationControls({
1876
2008
  azimuth = [-Infinity, Infinity],
1877
2009
  damping = 0.25
1878
2010
  }) {
1879
- const events = useThree((state) => state.events);
1880
- const gl = useThree((state) => state.gl);
1881
- const explDomElement = domElement || events.connected || gl.domElement;
2011
+ const { events, renderer, invalidate } = useThree();
2012
+ const explDomElement = domElement || events.connected || renderer.domElement;
1882
2013
  const { size } = useThree();
1883
2014
  const rPolar = React.useMemo(
1884
2015
  () => [rotation[0] + polar[0], rotation[0] + polar[1]],
@@ -1895,18 +2026,20 @@ function PresentationControls({
1895
2026
  React.useEffect(() => {
1896
2027
  if (global && cursor && enabled) {
1897
2028
  explDomElement.style.cursor = "grab";
1898
- gl.domElement.style.cursor = "";
2029
+ renderer.domElement.style.cursor = "";
1899
2030
  return () => {
1900
2031
  explDomElement.style.cursor = "default";
1901
- gl.domElement.style.cursor = "default";
2032
+ renderer.domElement.style.cursor = "default";
1902
2033
  };
1903
2034
  }
1904
2035
  }, [global, cursor, explDomElement, enabled]);
1905
2036
  const [animation] = React.useState({ scale: 1, rotation: rInitial, damping });
1906
2037
  const ref = React.useRef(null);
1907
2038
  useFrame((state, delta) => {
1908
- easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
1909
- easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
2039
+ let changed = false;
2040
+ changed ||= easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
2041
+ changed ||= easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
2042
+ if (changed) state.invalidate();
1910
2043
  });
1911
2044
  const bind = useGesture(
1912
2045
  {
@@ -1914,6 +2047,7 @@ function PresentationControls({
1914
2047
  if (cursor && !global && enabled) explDomElement.style.cursor = last ? "auto" : "grab";
1915
2048
  },
1916
2049
  onDrag: ({ down, delta: [x, y], memo: [oldY, oldX] = animation.rotation || rInitial }) => {
2050
+ invalidate();
1917
2051
  if (!enabled) return [y, x];
1918
2052
  if (cursor) explDomElement.style.cursor = down ? "grabbing" : "grab";
1919
2053
  x = MathUtils.clamp(oldX + x / size.width * Math.PI * speed, ...rAzimuth);
@@ -1922,14 +2056,27 @@ function PresentationControls({
1922
2056
  animation.rotation = snap && !down ? rInitial : [y, x, 0];
1923
2057
  animation.damping = snap && !down && typeof snap !== "boolean" ? snap : damping;
1924
2058
  return [y, x];
2059
+ },
2060
+ onDragEnd: () => {
2061
+ invalidate();
1925
2062
  }
1926
2063
  },
1927
- { target: global ? explDomElement : void 0 }
2064
+ { target: global ? explDomElement : void 0, enabled }
1928
2065
  );
1929
2066
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
1930
2067
  }
1931
2068
 
2069
+ function roundToPixelRatio(value) {
2070
+ const ratio = window.devicePixelRatio || 1;
2071
+ return Math.round(value * ratio) / ratio;
2072
+ }
2073
+ function getMaxAnisotropy(renderer) {
2074
+ const r = renderer;
2075
+ return r?.getMaxAnisotropy?.() ?? r?.capabilities?.getMaxAnisotropy?.() ?? 1;
2076
+ }
2077
+
1932
2078
  const context$7 = /* @__PURE__ */ React.createContext(null);
2079
+ const rootCache = /* @__PURE__ */ new WeakMap();
1933
2080
  function useScroll() {
1934
2081
  return React.useContext(context$7);
1935
2082
  }
@@ -1943,6 +2090,7 @@ function ScrollControls({
1943
2090
  damping = 0.25,
1944
2091
  maxSpeed = Infinity,
1945
2092
  prepend = false,
2093
+ pixelPerfect = true,
1946
2094
  style = {},
1947
2095
  children
1948
2096
  }) {
@@ -1952,6 +2100,7 @@ function ScrollControls({
1952
2100
  const [fixed] = React.useState(() => document.createElement("div"));
1953
2101
  const target = gl.domElement.parentNode;
1954
2102
  const scroll = React.useRef(0);
2103
+ const disableScrollUpdate = React.useRef(false);
1955
2104
  const state = React.useMemo(() => {
1956
2105
  const state2 = {
1957
2106
  el,
@@ -1964,6 +2113,7 @@ function ScrollControls({
1964
2113
  delta: 0,
1965
2114
  scroll,
1966
2115
  pages,
2116
+ pixelPerfect,
1967
2117
  // 0-1 for a range between from -> from + distance
1968
2118
  range(from, distance2, margin = 0) {
1969
2119
  const start = from - margin;
@@ -1982,7 +2132,7 @@ function ScrollControls({
1982
2132
  }
1983
2133
  };
1984
2134
  return state2;
1985
- }, [eps, damping, horizontal, pages]);
2135
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
1986
2136
  React.useEffect(() => {
1987
2137
  el.style.position = "absolute";
1988
2138
  el.style.width = "100%";
@@ -2032,16 +2182,20 @@ function ScrollControls({
2032
2182
  const scrollLength = el[horizontal ? "scrollWidth" : "scrollHeight"];
2033
2183
  const scrollThreshold = scrollLength - containerLength;
2034
2184
  let current = 0;
2035
- let disableScroll = true;
2185
+ let disableScroll = false;
2036
2186
  let firstRun = true;
2037
2187
  const onScroll = () => {
2038
2188
  if (!enabled || firstRun) return;
2189
+ if (disableScrollUpdate.current) {
2190
+ disableScrollUpdate.current = false;
2191
+ return;
2192
+ }
2039
2193
  invalidate();
2040
2194
  current = el[horizontal ? "scrollLeft" : "scrollTop"];
2041
2195
  scroll.current = current / scrollThreshold;
2042
2196
  if (infinite) {
2043
2197
  if (!disableScroll) {
2044
- if (current >= scrollThreshold) {
2198
+ if (current >= scrollThreshold - 1) {
2045
2199
  const damp = 1 - state.offset;
2046
2200
  el[horizontal ? "scrollLeft" : "scrollTop"] = 1;
2047
2201
  scroll.current = state.offset = -damp;
@@ -2058,11 +2212,34 @@ function ScrollControls({
2058
2212
  };
2059
2213
  el.addEventListener("scroll", onScroll, { passive: true });
2060
2214
  requestAnimationFrame(() => firstRun = false);
2061
- const onWheel = (e) => el.scrollLeft += e.deltaY / 2;
2062
- if (horizontal) el.addEventListener("wheel", onWheel, { passive: true });
2215
+ const onResize = () => {
2216
+ if (!el) return;
2217
+ const scrollLength2 = el[horizontal ? "scrollWidth" : "scrollHeight"];
2218
+ const containerLength2 = el[horizontal ? "clientWidth" : "clientHeight"];
2219
+ disableScrollUpdate.current = true;
2220
+ el[horizontal ? "scrollLeft" : "scrollTop"] = state.offset * (scrollLength2 - containerLength2);
2221
+ };
2222
+ const onWheel = (e) => {
2223
+ if (horizontal) {
2224
+ const previous = el.scrollLeft;
2225
+ el.scrollLeft += e.deltaY / 2;
2226
+ if (infinite && previous === el.scrollLeft) {
2227
+ onScroll();
2228
+ }
2229
+ } else if (infinite) {
2230
+ if (el.scrollTop <= 1 && e.deltaY < 0) {
2231
+ onScroll();
2232
+ } else if (el.scrollTop >= el.scrollHeight - el.clientHeight - 1 && e.deltaY > 0) {
2233
+ onScroll();
2234
+ }
2235
+ }
2236
+ };
2237
+ if (horizontal || infinite) el.addEventListener("wheel", onWheel, { passive: true });
2238
+ if (horizontal) window.addEventListener("resize", onResize);
2063
2239
  return () => {
2064
2240
  el.removeEventListener("scroll", onScroll);
2065
- if (horizontal) el.removeEventListener("wheel", onWheel);
2241
+ if (horizontal || infinite) el.removeEventListener("wheel", onWheel);
2242
+ if (horizontal) window.removeEventListener("resize", onResize);
2066
2243
  };
2067
2244
  }
2068
2245
  }, [el, events, size, infinite, state, invalidate, horizontal, enabled]);
@@ -2095,10 +2272,23 @@ const ScrollHtml = React.forwardRef(
2095
2272
  React.useImperativeHandle(ref, () => group.current, []);
2096
2273
  const { width, height } = useThree((state2) => state2.size);
2097
2274
  const fiberState = React.useContext(context$9);
2098
- const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
2275
+ const root = React.useMemo(() => {
2276
+ let root2 = rootCache.get(state.fixed);
2277
+ if (!root2) {
2278
+ root2 = ReactDOM.createRoot(state.fixed);
2279
+ rootCache.set(state.fixed, root2);
2280
+ }
2281
+ return root2;
2282
+ }, [state.fixed]);
2099
2283
  useFrame(() => {
2100
2284
  if (state.delta > state.eps) {
2101
- group.current.style.transform = `translate3d(${state.horizontal ? -width * (state.pages - 1) * state.offset : 0}px,${state.horizontal ? 0 : height * (state.pages - 1) * -state.offset}px,0)`;
2285
+ let x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
2286
+ let y = state.horizontal ? 0 : height * (state.pages - 1) * -state.offset;
2287
+ if (state.pixelPerfect) {
2288
+ x = roundToPixelRatio(x);
2289
+ y = roundToPixelRatio(y);
2290
+ }
2291
+ group.current.style.transform = `translate3d(${x}px,${y}px,0)`;
2102
2292
  }
2103
2293
  });
2104
2294
  root.render(
@@ -2466,10 +2656,10 @@ const CloudInstance = /* @__PURE__ */ React.forwardRef(
2466
2656
  return /* @__PURE__ */ React.createElement("group", { ref, ...props });
2467
2657
  }
2468
2658
  );
2469
- const Cloud = /* @__PURE__ */ React.forwardRef((props, fref) => {
2659
+ const Cloud = /* @__PURE__ */ React.forwardRef(({ materialFactory, ...props }, fref) => {
2470
2660
  const parent = React.useContext(context$6);
2471
2661
  if (parent) return /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props });
2472
- return /* @__PURE__ */ React.createElement(Clouds, null, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
2662
+ return /* @__PURE__ */ React.createElement(Clouds, { materialFactory }, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
2473
2663
  });
2474
2664
 
2475
2665
  const CatmullRomLine = /* @__PURE__ */ React.forwardRef(function CatmullRomLine2({
@@ -2991,7 +3181,7 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
2991
3181
  return {
2992
3182
  font,
2993
3183
  size,
2994
- height,
3184
+ depth: height,
2995
3185
  bevelThickness,
2996
3186
  bevelSize,
2997
3187
  bevelEnabled,
@@ -3027,377 +3217,6 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
3027
3217
  }
3028
3218
  );
3029
3219
 
3030
- const WireframeMaterialShaders = {
3031
- uniforms: {
3032
- strokeOpacity: 1,
3033
- fillOpacity: 0.25,
3034
- fillMix: 0,
3035
- thickness: 0.05,
3036
- colorBackfaces: false,
3037
- dashInvert: true,
3038
- dash: false,
3039
- dashRepeats: 4,
3040
- dashLength: 0.5,
3041
- squeeze: false,
3042
- squeezeMin: 0.2,
3043
- squeezeMax: 1,
3044
- stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
3045
- backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
3046
- fill: /* @__PURE__ */ new THREE.Color("#00ff00")
3047
- },
3048
- vertex: (
3049
- /* glsl */
3050
- `
3051
- attribute vec3 barycentric;
3052
-
3053
- varying vec3 v_edges_Barycentric;
3054
- varying vec3 v_edges_Position;
3055
-
3056
- void initWireframe() {
3057
- v_edges_Barycentric = barycentric;
3058
- v_edges_Position = position.xyz;
3059
- }
3060
- `
3061
- ),
3062
- fragment: (
3063
- /* glsl */
3064
- `
3065
- #ifndef PI
3066
- #define PI 3.1415926535897932384626433832795
3067
- #endif
3068
-
3069
- varying vec3 v_edges_Barycentric;
3070
- varying vec3 v_edges_Position;
3071
-
3072
- uniform float strokeOpacity;
3073
- uniform float fillOpacity;
3074
- uniform float fillMix;
3075
- uniform float thickness;
3076
- uniform bool colorBackfaces;
3077
-
3078
- // Dash
3079
- uniform bool dashInvert;
3080
- uniform bool dash;
3081
- uniform bool dashOnly;
3082
- uniform float dashRepeats;
3083
- uniform float dashLength;
3084
-
3085
- // Squeeze
3086
- uniform bool squeeze;
3087
- uniform float squeezeMin;
3088
- uniform float squeezeMax;
3089
-
3090
- // Colors
3091
- uniform vec3 stroke;
3092
- uniform vec3 backfaceStroke;
3093
- uniform vec3 fill;
3094
-
3095
- // This is like
3096
- float wireframe_aastep(float threshold, float dist) {
3097
- float afwidth = fwidth(dist) * 0.5;
3098
- return smoothstep(threshold - afwidth, threshold + afwidth, dist);
3099
- }
3100
-
3101
- float wireframe_map(float value, float min1, float max1, float min2, float max2) {
3102
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
3103
- }
3104
-
3105
- float getWireframe() {
3106
- vec3 barycentric = v_edges_Barycentric;
3107
-
3108
- // Distance from center of each triangle to its edges.
3109
- float d = min(min(barycentric.x, barycentric.y), barycentric.z);
3110
-
3111
- // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
3112
- float positionAlong = max(barycentric.x, barycentric.y);
3113
- if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
3114
- positionAlong = 1.0 - positionAlong;
3115
- }
3116
-
3117
- // the thickness of the stroke
3118
- float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
3119
-
3120
- // if we want to shrink the thickness toward the center of the line segment
3121
- if (squeeze) {
3122
- computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
3123
- }
3124
-
3125
- // Create dash pattern
3126
- if (dash) {
3127
- // here we offset the stroke position depending on whether it
3128
- // should overlap or not
3129
- float offset = 1.0 / dashRepeats * dashLength / 2.0;
3130
- if (!dashInvert) {
3131
- offset += 1.0 / dashRepeats / 2.0;
3132
- }
3133
-
3134
- // if we should animate the dash or not
3135
- // if (dashAnimate) {
3136
- // offset += time * 0.22;
3137
- // }
3138
-
3139
- // create the repeating dash pattern
3140
- float pattern = fract((positionAlong + offset) * dashRepeats);
3141
- computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
3142
- }
3143
-
3144
- // compute the anti-aliased stroke edge
3145
- float edge = 1.0 - wireframe_aastep(computedThickness, d);
3146
-
3147
- return edge;
3148
- }
3149
- `
3150
- )
3151
- };
3152
- const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
3153
- WireframeMaterialShaders.uniforms,
3154
- WireframeMaterialShaders.vertex + /* glsl */
3155
- `
3156
- void main() {
3157
- initWireframe();
3158
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
3159
- }
3160
- `,
3161
- WireframeMaterialShaders.fragment + /* glsl */
3162
- `
3163
- void main () {
3164
- // Compute color
3165
-
3166
- float edge = getWireframe();
3167
- vec4 colorStroke = vec4(stroke, edge);
3168
-
3169
- #ifdef FLIP_SIDED
3170
- colorStroke.rgb = backfaceStroke;
3171
- #endif
3172
-
3173
- vec4 colorFill = vec4(fill, fillOpacity);
3174
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
3175
-
3176
- gl_FragColor = outColor;
3177
- }
3178
- `
3179
- );
3180
- function setWireframeOverride(material, uniforms) {
3181
- material.onBeforeCompile = (shader) => {
3182
- shader.uniforms = {
3183
- ...shader.uniforms,
3184
- ...uniforms
3185
- };
3186
- shader.vertexShader = shader.vertexShader.replace(
3187
- "void main() {",
3188
- `
3189
- ${WireframeMaterialShaders.vertex}
3190
- void main() {
3191
- initWireframe();
3192
- `
3193
- );
3194
- shader.fragmentShader = shader.fragmentShader.replace(
3195
- "void main() {",
3196
- `
3197
- ${WireframeMaterialShaders.fragment}
3198
- void main() {
3199
- `
3200
- );
3201
- shader.fragmentShader = shader.fragmentShader.replace(
3202
- "#include <color_fragment>",
3203
- /* glsl */
3204
- `
3205
- #include <color_fragment>
3206
- float edge = getWireframe();
3207
- vec4 colorStroke = vec4(stroke, edge);
3208
- #ifdef FLIP_SIDED
3209
- colorStroke.rgb = backfaceStroke;
3210
- #endif
3211
- vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
3212
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
3213
-
3214
- diffuseColor.rgb = outColor.rgb;
3215
- diffuseColor.a *= outColor.a;
3216
- `
3217
- );
3218
- };
3219
- material.side = THREE.DoubleSide;
3220
- material.transparent = true;
3221
- }
3222
- function useWireframeUniforms(uniforms, props) {
3223
- React.useEffect(
3224
- () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
3225
- [props.fillOpacity]
3226
- );
3227
- React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
3228
- React.useEffect(
3229
- () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
3230
- [props.strokeOpacity]
3231
- );
3232
- React.useEffect(
3233
- () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
3234
- [props.thickness]
3235
- );
3236
- React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
3237
- React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
3238
- React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
3239
- React.useEffect(
3240
- () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
3241
- [props.dashRepeats]
3242
- );
3243
- React.useEffect(
3244
- () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
3245
- [props.dashLength]
3246
- );
3247
- React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
3248
- React.useEffect(
3249
- () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
3250
- [props.squeezeMin]
3251
- );
3252
- React.useEffect(
3253
- () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
3254
- [props.squeezeMax]
3255
- );
3256
- React.useEffect(
3257
- () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
3258
- [props.stroke]
3259
- );
3260
- React.useEffect(
3261
- () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
3262
- [props.fill]
3263
- );
3264
- React.useEffect(
3265
- () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
3266
- [props.backfaceStroke]
3267
- );
3268
- }
3269
-
3270
- function isWithGeometry(object) {
3271
- return !!object?.geometry;
3272
- }
3273
- function isGeometry(object) {
3274
- return !!object?.isBufferGeometry;
3275
- }
3276
- function isRefObject$1(object) {
3277
- return !!object?.current;
3278
- }
3279
- function isRef$1(object) {
3280
- return object?.current !== void 0;
3281
- }
3282
- function isWireframeGeometry(geometry) {
3283
- return geometry.type === "WireframeGeometry";
3284
- }
3285
- function getUniforms() {
3286
- const u = {};
3287
- for (const key in WireframeMaterialShaders.uniforms) {
3288
- u[key] = { value: WireframeMaterialShaders.uniforms[key] };
3289
- }
3290
- return u;
3291
- }
3292
- function getBarycentricCoordinates(geometry, removeEdge) {
3293
- const position = geometry.getAttribute("position");
3294
- const count = position.count;
3295
- const barycentric = [];
3296
- for (let i = 0; i < count; i++) {
3297
- const even = i % 2 === 0;
3298
- const Q = removeEdge ? 1 : 0;
3299
- if (even) {
3300
- barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
3301
- } else {
3302
- barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
3303
- }
3304
- }
3305
- return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
3306
- }
3307
- function getInputGeometry(inputGeometry) {
3308
- const geo = isRefObject$1(inputGeometry) ? inputGeometry.current : inputGeometry;
3309
- if (!isGeometry(geo)) {
3310
- if (isWireframeGeometry(geo)) {
3311
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3312
- }
3313
- const parent = geo.parent;
3314
- if (isWithGeometry(parent)) {
3315
- if (isWireframeGeometry(parent.geometry)) {
3316
- throw new Error("Wireframe: WireframeGeometry is not supported.");
3317
- }
3318
- return parent.geometry;
3319
- }
3320
- } else {
3321
- return geo;
3322
- }
3323
- }
3324
- function setBarycentricCoordinates(geometry, simplify) {
3325
- if (geometry.index) {
3326
- console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
3327
- const nonIndexedGeo = geometry.toNonIndexed();
3328
- geometry.copy(nonIndexedGeo);
3329
- geometry.setIndex(null);
3330
- }
3331
- const newBarycentric = getBarycentricCoordinates(geometry, simplify);
3332
- geometry.setAttribute("barycentric", newBarycentric);
3333
- }
3334
- function WireframeWithCustomGeo({
3335
- geometry: customGeometry,
3336
- simplify = false,
3337
- ...props
3338
- }) {
3339
- extend({ MeshWireframeMaterial: WireframeMaterial });
3340
- const [geometry, setGeometry] = React.useState(null);
3341
- React.useLayoutEffect(() => {
3342
- const geom = getInputGeometry(customGeometry);
3343
- if (!geom) {
3344
- throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
3345
- }
3346
- setBarycentricCoordinates(geom, simplify);
3347
- if (isRef$1(customGeometry)) {
3348
- setGeometry(geom);
3349
- }
3350
- }, [simplify, customGeometry]);
3351
- const drawnGeo = isRef$1(customGeometry) ? geometry : customGeometry;
3352
- return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
3353
- "meshWireframeMaterial",
3354
- {
3355
- attach: "material",
3356
- transparent: true,
3357
- side: THREE.DoubleSide,
3358
- polygonOffset: true,
3359
- polygonOffsetFactor: -4,
3360
- ...props
3361
- }
3362
- )));
3363
- }
3364
- function WireframeWithoutCustomGeo({
3365
- simplify = false,
3366
- ...props
3367
- }) {
3368
- const objectRef = React.useRef(null);
3369
- const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
3370
- useWireframeUniforms(uniforms, props);
3371
- React.useLayoutEffect(() => {
3372
- const geom = getInputGeometry(objectRef);
3373
- if (!geom) {
3374
- throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
3375
- }
3376
- const og = geom.clone();
3377
- setBarycentricCoordinates(geom, simplify);
3378
- return () => {
3379
- geom.copy(og);
3380
- og.dispose();
3381
- };
3382
- }, [simplify]);
3383
- React.useLayoutEffect(() => {
3384
- const parentMesh = objectRef.current.parent;
3385
- const og = parentMesh.material.clone();
3386
- setWireframeOverride(parentMesh.material, uniforms);
3387
- return () => {
3388
- parentMesh.material.dispose();
3389
- parentMesh.material = og;
3390
- };
3391
- }, []);
3392
- return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
3393
- }
3394
- function Wireframe({ geometry: customGeometry, ...props }) {
3395
- if (customGeometry) {
3396
- return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
3397
- }
3398
- return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
3399
- }
3400
-
3401
3220
  const Context = /* @__PURE__ */ React.createContext({});
3402
3221
  const useGizmoContext = () => {
3403
3222
  return React.useContext(Context);
@@ -3545,7 +3364,7 @@ const FaceMaterial = ({
3545
3364
  "meshBasicMaterial",
3546
3365
  {
3547
3366
  map: texture,
3548
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
3367
+ "map-anisotropy": getMaxAnisotropy(renderer),
3549
3368
  attach: `material-${index}`,
3550
3369
  color: hover ? hoverColor : "white",
3551
3370
  transparent: true,
@@ -3656,7 +3475,7 @@ function AxisHead({
3656
3475
  "spriteMaterial",
3657
3476
  {
3658
3477
  map: texture,
3659
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
3478
+ "map-anisotropy": getMaxAnisotropy(renderer),
3660
3479
  alphaTest: 0.3,
3661
3480
  opacity: label ? 1 : 0.75,
3662
3481
  toneMapped: false
@@ -3693,6 +3512,11 @@ const GizmoViewport = ({
3693
3512
  return /* @__PURE__ */ React.createElement("group", { scale: 40, ...props }, /* @__PURE__ */ React.createElement(Axis, { color: colorX, rotation: [0, 0, 0], scale: axisScale }), /* @__PURE__ */ React.createElement(Axis, { color: colorY, rotation: [0, 0, Math.PI / 2], scale: axisScale }), /* @__PURE__ */ React.createElement(Axis, { color: colorZ, rotation: [0, -Math.PI / 2, 0], scale: axisScale }), !hideAxisHeads && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorX, position: [1, 0, 0], label: labels[0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorY, position: [0, 1, 0], label: labels[1], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorZ, position: [0, 0, 1], label: labels[2], ...axisHeadProps }), !hideNegativeAxes && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorX, position: [-1, 0, 0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorY, position: [0, -1, 0], ...axisHeadProps }), /* @__PURE__ */ React.createElement(AxisHead, { arcStyle: colorZ, position: [0, 0, -1], ...axisHeadProps }))));
3694
3513
  };
3695
3514
 
3515
+ const roundEven = (n) => {
3516
+ const rounded = Math.ceil(n);
3517
+ return rounded % 2 === 0 ? rounded : rounded + 1;
3518
+ };
3519
+
3696
3520
  const v1 = /* @__PURE__ */ new Vector3();
3697
3521
  const v2 = /* @__PURE__ */ new Vector3();
3698
3522
  const v3 = /* @__PURE__ */ new Vector3();
@@ -3791,9 +3615,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3791
3615
  as = "div",
3792
3616
  wrapperClass,
3793
3617
  pointerEvents = "auto",
3618
+ pixelPerfect = true,
3794
3619
  ...props
3795
3620
  }, ref) => {
3796
3621
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
3622
+ const forceEven = useThree((state) => state.internal.forceEven);
3623
+ const { frustum } = useThree();
3797
3624
  const [el] = React.useState(() => document.createElement(as));
3798
3625
  const root = React.useRef(null);
3799
3626
  const group = React.useRef(null);
@@ -3818,38 +3645,50 @@ const Html = /* @__PURE__ */ React.forwardRef(
3818
3645
  el2.style.position = null;
3819
3646
  el2.style.pointerEvents = null;
3820
3647
  }
3821
- }, [occlude]);
3648
+ }, [occlude, zIndexRange, gl.domElement]);
3822
3649
  React.useLayoutEffect(() => {
3823
3650
  if (group.current) {
3824
- const currentRoot = root.current = ReactDOM.createRoot(el);
3825
- scene.updateMatrixWorld();
3826
- if (transform) {
3827
- el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3828
- } else {
3829
- const vec = calculatePosition(group.current, camera, size);
3830
- el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(${vec[0]}px,${vec[1]}px,0);transform-origin:0 0;`;
3651
+ if (!root.current) {
3652
+ root.current = ReactDOM.createRoot(el);
3653
+ scene.updateMatrixWorld();
3654
+ if (transform) {
3655
+ el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
3656
+ } else {
3657
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
3658
+ }
3831
3659
  }
3832
3660
  if (target) {
3833
3661
  if (prepend) target.prepend(el);
3834
3662
  else target.appendChild(el);
3835
3663
  }
3836
3664
  return () => {
3837
- if (target) target.removeChild(el);
3838
- currentRoot.unmount();
3665
+ if (target && target.contains(el)) {
3666
+ target.removeChild(el);
3667
+ }
3839
3668
  };
3840
3669
  }
3841
- }, [target, transform]);
3670
+ }, [target, transform, el, prepend, scene]);
3842
3671
  React.useLayoutEffect(() => {
3843
3672
  if (wrapperClass) el.className = wrapperClass;
3844
- }, [wrapperClass]);
3673
+ }, [wrapperClass, el]);
3674
+ React.useLayoutEffect(() => {
3675
+ return () => {
3676
+ if (root.current) {
3677
+ root.current.unmount();
3678
+ root.current = null;
3679
+ }
3680
+ };
3681
+ }, []);
3845
3682
  const styles = React.useMemo(() => {
3683
+ const width = forceEven ? roundEven(size.width) : size.width;
3684
+ const height = forceEven ? roundEven(size.height) : size.height;
3846
3685
  if (transform) {
3847
3686
  return {
3848
3687
  position: "absolute",
3849
3688
  top: 0,
3850
3689
  left: 0,
3851
- width: size.width,
3852
- height: size.height,
3690
+ width,
3691
+ height,
3853
3692
  transformStyle: "preserve-3d",
3854
3693
  pointerEvents: "none"
3855
3694
  };
@@ -3858,15 +3697,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
3858
3697
  position: "absolute",
3859
3698
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
3860
3699
  ...fullscreen && {
3861
- top: -size.height / 2,
3862
- left: -size.width / 2,
3863
- width: size.width,
3864
- height: size.height
3700
+ top: forceEven ? roundEven(-size.height / 2) : -size.height / 2,
3701
+ left: forceEven ? roundEven(-size.width / 2) : -size.width / 2,
3702
+ width,
3703
+ height
3865
3704
  },
3866
3705
  ...style
3867
3706
  };
3868
3707
  }
3869
- }, [style, center, fullscreen, size, transform]);
3708
+ }, [style, center, fullscreen, size, transform, forceEven]);
3870
3709
  const transformInnerStyles = React.useMemo(
3871
3710
  () => ({ position: "absolute", pointerEvents }),
3872
3711
  [pointerEvents]
@@ -3880,7 +3719,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3880
3719
  } else {
3881
3720
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
3882
3721
  }
3883
- });
3722
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
3884
3723
  const visible = React.useRef(true);
3885
3724
  useFrame((gl2) => {
3886
3725
  if (group.current) {
@@ -3912,7 +3751,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
3912
3751
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
3913
3752
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
3914
3753
  if (transform) {
3915
- const [widthHalf, heightHalf] = [size.width / 2, size.height / 2];
3754
+ let widthHalf = forceEven ? roundEven(size.width / 2) : size.width / 2;
3755
+ let heightHalf = forceEven ? roundEven(size.height / 2) : size.height / 2;
3756
+ if (pixelPerfect) {
3757
+ widthHalf = roundToPixelRatio(widthHalf);
3758
+ heightHalf = roundToPixelRatio(heightHalf);
3759
+ }
3916
3760
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
3917
3761
  const { isOrthographicCamera, top, left, bottom, right } = camera;
3918
3762
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -3923,8 +3767,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
3923
3767
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
3924
3768
  matrix.elements[15] = 1;
3925
3769
  }
3926
- el.style.width = size.width + "px";
3927
- el.style.height = size.height + "px";
3770
+ const elWidth = forceEven ? roundEven(size.width) : size.width;
3771
+ const elHeight = forceEven ? roundEven(size.height) : size.height;
3772
+ el.style.width = elWidth + "px";
3773
+ el.style.height = elHeight + "px";
3928
3774
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
3929
3775
  if (transformOuterRef.current && transformInnerRef.current) {
3930
3776
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -3932,7 +3778,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
3932
3778
  }
3933
3779
  } else {
3934
3780
  const scale = distanceFactor === void 0 ? 1 : objectScale(group.current, camera) * distanceFactor;
3935
- el.style.transform = `translate3d(${vec[0]}px,${vec[1]}px,0) scale(${scale})`;
3781
+ let x = forceEven ? roundEven(vec[0]) : vec[0];
3782
+ let y = forceEven ? roundEven(vec[1]) : vec[1];
3783
+ if (pixelPerfect) {
3784
+ x = roundToPixelRatio(x);
3785
+ y = roundToPixelRatio(y);
3786
+ }
3787
+ el.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`;
3936
3788
  }
3937
3789
  oldPosition.current = vec;
3938
3790
  oldZoom.current = camera.zoom;
@@ -3943,8 +3795,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
3943
3795
  if (transformOuterRef.current) {
3944
3796
  const el2 = transformOuterRef.current.children[0];
3945
3797
  if (el2?.clientWidth && el2?.clientHeight) {
3946
- const { isOrthographicCamera } = camera;
3947
- if (isOrthographicCamera || geometry) {
3798
+ if (geometry) {
3948
3799
  if (props.scale) {
3949
3800
  if (!Array.isArray(props.scale)) {
3950
3801
  occlusionMeshRef.current.scale.setScalar(1 / props.scale);
@@ -3976,68 +3827,16 @@ const Html = /* @__PURE__ */ React.forwardRef(
3976
3827
  }
3977
3828
  }
3978
3829
  });
3979
- const shaders = React.useMemo(
3980
- () => ({
3981
- vertexShader: !transform ? (
3982
- /* glsl */
3983
- `
3984
- /*
3985
- This shader is from the THREE's SpriteMaterial.
3986
- We need to turn the backing plane into a Sprite
3987
- (make it always face the camera) if "transfrom"
3988
- is false.
3989
- */
3990
- #include <common>
3991
-
3992
- void main() {
3993
- vec2 center = vec2(0., 1.);
3994
- float rotation = 0.0;
3995
-
3996
- // This is somewhat arbitrary, but it seems to work well
3997
- // Need to figure out how to derive this dynamically if it even matters
3998
- float size = 0.03;
3999
-
4000
- vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );
4001
- vec2 scale;
4002
- scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );
4003
- scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );
4004
-
4005
- bool isPerspective = isPerspectiveMatrix( projectionMatrix );
4006
- if ( isPerspective ) scale *= - mvPosition.z;
4007
-
4008
- vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale * size;
4009
- vec2 rotatedPosition;
4010
- rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
4011
- rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
4012
- mvPosition.xy += rotatedPosition;
4013
-
4014
- gl_Position = projectionMatrix * mvPosition;
4015
- }
4016
- `
4017
- ) : void 0,
4018
- fragmentShader: (
4019
- /* glsl */
4020
- `
4021
- void main() {
4022
- gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
4023
- }
4024
- `
4025
- )
4026
- }),
4027
- [transform]
4028
- );
4029
- 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(
4030
- "shaderMaterial",
4031
- {
4032
- side: DoubleSide,
4033
- vertexShader: shaders.vertexShader,
4034
- fragmentShader: shaders.fragmentShader
4035
- }
4036
- )));
3830
+ 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));
4037
3831
  }
4038
3832
  );
4039
3833
 
4040
3834
  const context$4 = /* @__PURE__ */ React.createContext(null);
3835
+ const resolveObject = (object, fallback) => {
3836
+ if (!object) return fallback;
3837
+ if ("current" in object) return object.current;
3838
+ return object;
3839
+ };
4041
3840
 
4042
3841
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
4043
3842
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -4072,6 +3871,8 @@ const AxisArrow = ({ direction, axis }) => {
4072
3871
  onDragStart,
4073
3872
  onDrag,
4074
3873
  onDragEnd,
3874
+ onHover,
3875
+ dragState,
4075
3876
  userData,
4076
3877
  LineComponent: Line
4077
3878
  } = React.useContext(context$4);
@@ -4103,7 +3904,13 @@ const AxisArrow = ({ direction, axis }) => {
4103
3904
  const onPointerMove = React.useCallback(
4104
3905
  (e) => {
4105
3906
  e.stopPropagation();
4106
- if (!isHovered) setIsHovered(true);
3907
+ if (!isHovered) {
3908
+ const drag = dragState.current;
3909
+ if (drag === null || drag.component === "Arrow" && drag.axis === axis) {
3910
+ setIsHovered(true);
3911
+ onHover({ component: "Arrow", axis, hovering: true });
3912
+ }
3913
+ }
4107
3914
  if (clickInfo.current) {
4108
3915
  const { clickPoint, dir } = clickInfo.current;
4109
3916
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -4122,7 +3929,7 @@ const AxisArrow = ({ direction, axis }) => {
4122
3929
  onDrag(offsetMatrix$1);
4123
3930
  }
4124
3931
  },
4125
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
3932
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4126
3933
  );
4127
3934
  const onPointerUp = React.useCallback(
4128
3935
  (e) => {
@@ -4137,10 +3944,16 @@ const AxisArrow = ({ direction, axis }) => {
4137
3944
  },
4138
3945
  [annotations, camControls, onDragEnd]
4139
3946
  );
4140
- const onPointerOut = React.useCallback((e) => {
4141
- e.stopPropagation();
4142
- setIsHovered(false);
4143
- }, []);
3947
+ const onPointerOut = React.useCallback(
3948
+ (e) => {
3949
+ e.stopPropagation();
3950
+ if (isHovered) {
3951
+ setIsHovered(false);
3952
+ onHover({ component: "Arrow", axis, hovering: false });
3953
+ }
3954
+ },
3955
+ [onHover, axis, isHovered]
3956
+ );
4144
3957
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
4145
3958
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
4146
3959
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -4244,6 +4057,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
4244
4057
  const posNew = /* @__PURE__ */ new THREE.Vector3();
4245
4058
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
4246
4059
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
4060
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
4061
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
4247
4062
  const AxisRotator = ({
4248
4063
  dir1,
4249
4064
  dir2,
@@ -4264,6 +4079,8 @@ const AxisRotator = ({
4264
4079
  onDragStart,
4265
4080
  onDrag,
4266
4081
  onDragEnd,
4082
+ onHover,
4083
+ dragState,
4267
4084
  userData,
4268
4085
  LineComponent: Line
4269
4086
  } = React.useContext(context$4);
@@ -4297,14 +4114,29 @@ const AxisRotator = ({
4297
4114
  const onPointerMove = React.useCallback(
4298
4115
  (e) => {
4299
4116
  e.stopPropagation();
4300
- if (!isHovered) setIsHovered(true);
4117
+ if (!isHovered) {
4118
+ const drag = dragState.current;
4119
+ if (drag === null || drag.component === "Rotator" && drag.axis === axis) {
4120
+ setIsHovered(true);
4121
+ onHover({ component: "Rotator", axis, hovering: true });
4122
+ }
4123
+ }
4301
4124
  if (clickInfo.current) {
4302
4125
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
4303
4126
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
4304
4127
  ray$1.copy(e.ray);
4305
- ray$1.intersectPlane(plane, intersection$1);
4306
- ray$1.direction.negate();
4307
- ray$1.intersectPlane(plane, intersection$1);
4128
+ const dotProduct = Math.abs(ray$1.direction.dot(normal));
4129
+ if (dotProduct < 0.1) {
4130
+ viewPlane.setFromNormalAndCoplanarPoint(ray$1.direction.clone().negate(), origin);
4131
+ ray$1.intersectPlane(viewPlane, viewIntersection);
4132
+ const toIntersection = viewIntersection.clone().sub(origin);
4133
+ const projectedDist = toIntersection.dot(normal);
4134
+ intersection$1.copy(viewIntersection).sub(normal.clone().multiplyScalar(projectedDist));
4135
+ } else {
4136
+ ray$1.intersectPlane(plane, intersection$1);
4137
+ ray$1.direction.negate();
4138
+ ray$1.intersectPlane(plane, intersection$1);
4139
+ }
4308
4140
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
4309
4141
  let degrees = toDegrees(deltaAngle);
4310
4142
  if (e.shiftKey) {
@@ -4330,7 +4162,7 @@ const AxisRotator = ({
4330
4162
  onDrag(rotMatrix);
4331
4163
  }
4332
4164
  },
4333
- [annotations, onDrag, isHovered, rotationLimits, axis]
4165
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
4334
4166
  );
4335
4167
  const onPointerUp = React.useCallback(
4336
4168
  (e) => {
@@ -4346,10 +4178,16 @@ const AxisRotator = ({
4346
4178
  },
4347
4179
  [annotations, camControls, onDragEnd]
4348
4180
  );
4349
- const onPointerOut = React.useCallback((e) => {
4350
- e.stopPropagation();
4351
- setIsHovered(false);
4352
- }, []);
4181
+ const onPointerOut = React.useCallback(
4182
+ (e) => {
4183
+ e.stopPropagation();
4184
+ if (isHovered) {
4185
+ setIsHovered(false);
4186
+ onHover({ component: "Rotator", axis, hovering: false });
4187
+ }
4188
+ },
4189
+ [onHover, axis, isHovered]
4190
+ );
4353
4191
  const matrixL = React.useMemo(() => {
4354
4192
  const dir1N = dir1.clone().normalize();
4355
4193
  const dir2N = dir2.clone().normalize();
@@ -4450,6 +4288,8 @@ const PlaneSlider = ({
4450
4288
  onDragStart,
4451
4289
  onDrag,
4452
4290
  onDragEnd,
4291
+ onHover,
4292
+ dragState,
4453
4293
  userData,
4454
4294
  LineComponent: Line
4455
4295
  } = React.useContext(context$4);
@@ -4485,7 +4325,13 @@ const PlaneSlider = ({
4485
4325
  const onPointerMove = React.useCallback(
4486
4326
  (e) => {
4487
4327
  e.stopPropagation();
4488
- if (!isHovered) setIsHovered(true);
4328
+ if (!isHovered) {
4329
+ const drag = dragState.current;
4330
+ if (drag === null || drag.component === "Slider" && drag.axis === axis) {
4331
+ setIsHovered(true);
4332
+ onHover({ component: "Slider", axis, hovering: true });
4333
+ }
4334
+ }
4489
4335
  if (clickInfo.current) {
4490
4336
  const { clickPoint, e1, e2, plane } = clickInfo.current;
4491
4337
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -4521,7 +4367,7 @@ const PlaneSlider = ({
4521
4367
  onDrag(offsetMatrix);
4522
4368
  }
4523
4369
  },
4524
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
4370
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
4525
4371
  );
4526
4372
  const onPointerUp = React.useCallback(
4527
4373
  (e) => {
@@ -4536,10 +4382,16 @@ const PlaneSlider = ({
4536
4382
  },
4537
4383
  [annotations, camControls, onDragEnd]
4538
4384
  );
4539
- const onPointerOut = React.useCallback((e) => {
4540
- e.stopPropagation();
4541
- setIsHovered(false);
4542
- }, []);
4385
+ const onPointerOut = React.useCallback(
4386
+ (e) => {
4387
+ e.stopPropagation();
4388
+ if (isHovered) {
4389
+ setIsHovered(false);
4390
+ onHover({ component: "Slider", axis, hovering: false });
4391
+ }
4392
+ },
4393
+ [onHover, axis, isHovered]
4394
+ );
4543
4395
  const matrixL = React.useMemo(() => {
4544
4396
  const dir1N = dir1.clone().normalize();
4545
4397
  const dir2N = dir2.clone().normalize();
@@ -4677,6 +4529,8 @@ const ScalingSphere = ({ direction, axis }) => {
4677
4529
  onDragStart,
4678
4530
  onDrag,
4679
4531
  onDragEnd,
4532
+ onHover,
4533
+ dragState,
4680
4534
  userData
4681
4535
  } = React.useContext(context$4);
4682
4536
  const size = useThree((state) => state.size);
@@ -4713,7 +4567,13 @@ const ScalingSphere = ({ direction, axis }) => {
4713
4567
  const onPointerMove = React.useCallback(
4714
4568
  (e) => {
4715
4569
  e.stopPropagation();
4716
- if (!isHovered) setIsHovered(true);
4570
+ if (!isHovered) {
4571
+ const drag = dragState.current;
4572
+ if (drag === null || drag.component === "Sphere" && drag.axis === axis) {
4573
+ setIsHovered(true);
4574
+ onHover({ component: "Sphere", axis, hovering: true });
4575
+ }
4576
+ }
4717
4577
  if (clickInfo.current) {
4718
4578
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
4719
4579
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -4739,7 +4599,7 @@ const ScalingSphere = ({ direction, axis }) => {
4739
4599
  onDrag(scaleMatrix);
4740
4600
  }
4741
4601
  },
4742
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
4602
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
4743
4603
  );
4744
4604
  const onPointerUp = React.useCallback(
4745
4605
  (e) => {
@@ -4756,10 +4616,16 @@ const ScalingSphere = ({ direction, axis }) => {
4756
4616
  },
4757
4617
  [annotations, camControls, onDragEnd, position]
4758
4618
  );
4759
- const onPointerOut = React.useCallback((e) => {
4760
- e.stopPropagation();
4761
- setIsHovered(false);
4762
- }, []);
4619
+ const onPointerOut = React.useCallback(
4620
+ (e) => {
4621
+ e.stopPropagation();
4622
+ if (isHovered) {
4623
+ setIsHovered(false);
4624
+ onHover({ component: "Sphere", axis, hovering: false });
4625
+ }
4626
+ },
4627
+ [onHover, axis, isHovered]
4628
+ );
4763
4629
  const { radius, matrixL } = React.useMemo(() => {
4764
4630
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
4765
4631
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -4814,6 +4680,7 @@ const mW = /* @__PURE__ */ new THREE.Matrix4();
4814
4680
  const mL = /* @__PURE__ */ new THREE.Matrix4();
4815
4681
  const mL0Inv = /* @__PURE__ */ new THREE.Matrix4();
4816
4682
  const mdL = /* @__PURE__ */ new THREE.Matrix4();
4683
+ const mdW = /* @__PURE__ */ new THREE.Matrix4();
4817
4684
  const mG = /* @__PURE__ */ new THREE.Matrix4();
4818
4685
  const bb = /* @__PURE__ */ new THREE.Box3();
4819
4686
  const bbObj = /* @__PURE__ */ new THREE.Box3();
@@ -4829,9 +4696,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4829
4696
  ({
4830
4697
  enabled = true,
4831
4698
  matrix,
4699
+ object,
4832
4700
  onDragStart,
4833
4701
  onDrag,
4834
4702
  onDragEnd,
4703
+ onHover,
4835
4704
  autoTransform = true,
4836
4705
  anchor,
4837
4706
  disableAxes = false,
@@ -4867,14 +4736,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4867
4736
  const gizmoRef = React.useRef(null);
4868
4737
  const childrenRef = React.useRef(null);
4869
4738
  const translation = React.useRef([0, 0, 0]);
4739
+ const dragState = React.useRef(null);
4870
4740
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
4871
4741
  const gizmoScale = React.useRef(new THREE.Vector3(1, 1, 1));
4742
+ React.useEffect(() => {
4743
+ if (object) {
4744
+ const target = resolveObject(object);
4745
+ if (target) {
4746
+ const pivot = ref.current;
4747
+ const didAutoUpdate = target.matrixAutoUpdate;
4748
+ target.updateWorldMatrix(true, true);
4749
+ target.matrixAutoUpdate = false;
4750
+ pivot.matrix.copy(target.matrix);
4751
+ return () => {
4752
+ if (didAutoUpdate) {
4753
+ target.matrixAutoUpdate = true;
4754
+ target.matrix.decompose(target.position, target.quaternion, target.scale);
4755
+ }
4756
+ };
4757
+ }
4758
+ }
4759
+ }, [object]);
4872
4760
  React.useLayoutEffect(() => {
4873
4761
  if (!anchor) return;
4874
- childrenRef.current.updateWorldMatrix(true, true);
4875
- mPInv.copy(childrenRef.current.matrixWorld).invert();
4762
+ const anchorTarget = resolveObject(object, childrenRef.current);
4763
+ if (!anchorTarget) return;
4764
+ anchorTarget.updateWorldMatrix(true, true);
4765
+ mPInv.copy(anchorTarget.matrixWorld).invert();
4876
4766
  bb.makeEmpty();
4877
- childrenRef.current.traverse((obj) => {
4767
+ anchorTarget.traverse((obj) => {
4878
4768
  if (!obj.geometry) return;
4879
4769
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
4880
4770
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -4889,15 +4779,24 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4889
4779
  gizmoRef.current.position.copy(vPosition);
4890
4780
  invalidate();
4891
4781
  });
4782
+ const mergedUserData = React.useMemo(
4783
+ () => ({
4784
+ ...userData,
4785
+ ...depthTest === false && { interactivePriority: 1 }
4786
+ }),
4787
+ [userData, depthTest]
4788
+ );
4892
4789
  const config = React.useMemo(
4893
4790
  () => ({
4894
4791
  onDragStart: (props2) => {
4792
+ dragState.current = props2;
4895
4793
  mL0.copy(ref.current.matrix);
4896
4794
  mW0.copy(ref.current.matrixWorld);
4897
4795
  onDragStart && onDragStart(props2);
4898
4796
  invalidate();
4899
4797
  },
4900
- onDrag: (mdW) => {
4798
+ onDrag: (mdW_) => {
4799
+ mdW.copy(mdW_);
4901
4800
  mP.copy(parentRef.current.matrixWorld);
4902
4801
  mPInv.copy(mP).invert();
4903
4802
  mW.copy(mW0).premultiply(mdW);
@@ -4907,13 +4806,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4907
4806
  if (autoTransform) {
4908
4807
  ref.current.matrix.copy(mL);
4909
4808
  }
4910
- onDrag && onDrag(mL, mdL, mW, mdW);
4809
+ const target = resolveObject(object);
4810
+ if (target) target.matrix.copy(mL);
4811
+ onDrag && onDrag({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
4911
4812
  invalidate();
4912
4813
  },
4913
4814
  onDragEnd: () => {
4914
- if (onDragEnd) onDragEnd();
4815
+ dragState.current = null;
4816
+ if (onDragEnd) onDragEnd({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
4915
4817
  invalidate();
4916
4818
  },
4819
+ onHover: (props2) => {
4820
+ if (onHover) onHover(props2);
4821
+ },
4822
+ dragState,
4917
4823
  translation,
4918
4824
  translationLimits,
4919
4825
  rotationLimits,
@@ -4925,7 +4831,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4925
4831
  fixed,
4926
4832
  depthTest,
4927
4833
  renderOrder,
4928
- userData,
4834
+ userData: mergedUserData,
4929
4835
  annotations,
4930
4836
  annotationsClass,
4931
4837
  LineComponent: LineComp
@@ -4934,6 +4840,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4934
4840
  onDragStart,
4935
4841
  onDrag,
4936
4842
  onDragEnd,
4843
+ onHover,
4937
4844
  translation,
4938
4845
  translationLimits,
4939
4846
  rotationLimits,
@@ -4945,11 +4852,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
4945
4852
  ...axisColors,
4946
4853
  hoveredColor,
4947
4854
  opacity,
4948
- userData,
4855
+ mergedUserData,
4949
4856
  autoTransform,
4950
4857
  annotations,
4951
4858
  annotationsClass,
4952
- LineComp
4859
+ LineComp,
4860
+ object
4953
4861
  ]
4954
4862
  );
4955
4863
  const vec = new THREE.Vector3();
@@ -5016,7 +4924,9 @@ const TransformControls = /* @__PURE__ */ React.forwardRef(
5016
4924
  const group = React.useRef(null);
5017
4925
  React.useLayoutEffect(() => {
5018
4926
  if (object) {
5019
- controls.attach(object instanceof THREE.Object3D ? object : object.current);
4927
+ controls.attach(
4928
+ object instanceof THREE.Object3D ? object : object.current
4929
+ );
5020
4930
  } else if (group.current instanceof THREE.Object3D) {
5021
4931
  controls.attach(group.current);
5022
4932
  }
@@ -5112,7 +5022,7 @@ const PointMaterial = /* @__PURE__ */ React.forwardRef((props, ref) => {
5112
5022
  return /* @__PURE__ */ React.createElement("primitive", { ...props, object: material, ref, attach: "material" });
5113
5023
  });
5114
5024
 
5115
- const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = true, autoplay, ...props }, ref) => {
5025
+ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = false, autoplay, ...props }, ref) => {
5116
5026
  const sound = React.useRef(null);
5117
5027
  React.useImperativeHandle(ref, () => sound.current, []);
5118
5028
  const camera = useThree(({ camera: camera2 }) => camera2);
@@ -5124,7 +5034,9 @@ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, l
5124
5034
  _sound.setBuffer(Array.isArray(buffer) ? buffer[0] : buffer);
5125
5035
  _sound.setRefDistance(distance);
5126
5036
  _sound.setLoop(loop);
5127
- if (autoplay && !_sound.isPlaying) _sound.play();
5037
+ if (autoplay && !_sound.isPlaying) {
5038
+ _sound.play();
5039
+ }
5128
5040
  }
5129
5041
  }, [buffer, camera, distance, loop]);
5130
5042
  React.useEffect(() => {
@@ -5193,7 +5105,6 @@ const tempMatrix = /* @__PURE__ */ new THREE.Matrix4();
5193
5105
  const translation = /* @__PURE__ */ new THREE.Vector3();
5194
5106
  const rotation = /* @__PURE__ */ new THREE.Quaternion();
5195
5107
  const scale = /* @__PURE__ */ new THREE.Vector3();
5196
- const isInstancedBufferAttribute = (attr) => attr.isInstancedBufferAttribute;
5197
5108
  const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }, ref) => {
5198
5109
  React.useMemo(() => extend({ PositionMesh }), []);
5199
5110
  const group = React.useRef(null);
@@ -5213,24 +5124,20 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
5213
5124
  const parentRef = React.useRef(null);
5214
5125
  React.useImperativeHandle(ref, () => parentRef.current, []);
5215
5126
  const [instances, setInstances] = React.useState([]);
5127
+ const boundsDirty = React.useRef(true);
5216
5128
  const [[matrices, colors]] = React.useState(() => {
5217
5129
  const mArray = new Float32Array(limit * 16);
5218
5130
  for (let i = 0; i < limit; i++) tempMatrix.identity().toArray(mArray, i * 16);
5219
5131
  return [mArray, new Float32Array([...new Array(limit * 3)].map(() => 1))];
5220
5132
  });
5221
5133
  React.useEffect(() => {
5222
- parentRef.current.instanceMatrix.needsUpdate = true;
5134
+ if (parentRef.current) parentRef.current.instanceMatrix.needsUpdate = true;
5223
5135
  });
5224
5136
  let iterations = 0;
5225
5137
  let count = 0;
5226
- const attributes = React.useRef([]);
5227
- React.useLayoutEffect(() => {
5228
- attributes.current = Object.entries(parentRef.current.geometry.attributes).filter(
5229
- ([_name, value]) => isInstancedBufferAttribute(value)
5230
- );
5231
- });
5232
5138
  useFrame(() => {
5233
5139
  if (frames === Infinity || iterations < frames) {
5140
+ if (!parentRef.current) return;
5234
5141
  parentRef.current.updateMatrix();
5235
5142
  parentRef.current.updateMatrixWorld();
5236
5143
  parentMatrix.copy(parentRef.current.matrixWorld).invert();
@@ -5251,13 +5158,22 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
5251
5158
  }
5252
5159
  iterations++;
5253
5160
  }
5161
+ if (boundsDirty.current && (frames === Infinity || iterations === frames)) {
5162
+ boundsDirty.current = false;
5163
+ if (parentRef.current.boundingBox) parentRef.current.computeBoundingBox();
5164
+ if (parentRef.current.boundingSphere) parentRef.current.computeBoundingSphere();
5165
+ }
5254
5166
  });
5255
5167
  const api = React.useMemo(
5256
5168
  () => ({
5257
5169
  getParent: () => parentRef,
5258
5170
  subscribe: (ref2) => {
5259
5171
  setInstances((instances2) => [...instances2, ref2]);
5260
- return () => setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
5172
+ boundsDirty.current = true;
5173
+ return () => {
5174
+ setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
5175
+ boundsDirty.current = true;
5176
+ };
5261
5177
  }
5262
5178
  }),
5263
5179
  []
@@ -5282,10 +5198,23 @@ const Merged = /* @__PURE__ */ React.forwardRef(function Merged2({ meshes, child
5282
5198
  if (!isArray) {
5283
5199
  for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key];
5284
5200
  }
5285
- const render = (args) => isArray ? children(...args) : children(
5286
- Object.keys(meshes).filter((key) => meshes[key].isMesh).reduce((acc, key, i) => ({ ...acc, [key]: args[i] }), {})
5287
- );
5288
- const components = (isArray ? meshes : Object.values(meshes)).map(({ geometry, material }) => /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props }));
5201
+ const render = (args) => {
5202
+ if (isArray) {
5203
+ return children(
5204
+ ...args
5205
+ );
5206
+ }
5207
+ const instanceMap = {};
5208
+ const keys = Object.keys(meshes).filter((key) => meshes[key].isMesh);
5209
+ keys.forEach((key, i) => {
5210
+ instanceMap[key] = args[i];
5211
+ });
5212
+ return children(instanceMap);
5213
+ };
5214
+ const components = (isArray ? meshes : Object.values(meshes)).map((mesh) => {
5215
+ const { geometry, material } = mesh;
5216
+ return /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props });
5217
+ });
5289
5218
  return /* @__PURE__ */ React.createElement("group", { ref }, renderRecursive(render, components));
5290
5219
  });
5291
5220
  function renderRecursive(render, components, results = []) {
@@ -5311,7 +5240,7 @@ function createInstances() {
5311
5240
  ];
5312
5241
  }
5313
5242
  const InstancedAttribute = React.forwardRef(
5314
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
5243
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
5315
5244
  const ref = React.useRef(null);
5316
5245
  React.useImperativeHandle(fref, () => ref.current, []);
5317
5246
  React.useLayoutEffect(() => {
@@ -5319,7 +5248,26 @@ const InstancedAttribute = React.forwardRef(
5319
5248
  parent.geometry.attributes[name] = ref.current;
5320
5249
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
5321
5250
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
5322
- ref.current.array = new Float32Array(array);
5251
+ switch (type) {
5252
+ case "float":
5253
+ ref.current.array = new Float32Array(array);
5254
+ break;
5255
+ case "int":
5256
+ ref.current.array = new Int32Array(array);
5257
+ break;
5258
+ case "uint":
5259
+ ref.current.array = new Uint32Array(array);
5260
+ break;
5261
+ case "short":
5262
+ ref.current.array = new Int16Array(array);
5263
+ break;
5264
+ case "ushort":
5265
+ ref.current.array = new Uint16Array(array);
5266
+ break;
5267
+ case "byte":
5268
+ ref.current.array = new Uint8Array(array);
5269
+ break;
5270
+ }
5323
5271
  ref.current.itemSize = value.length;
5324
5272
  ref.current.count = array.length / ref.current.itemSize;
5325
5273
  return () => {
@@ -6340,12 +6288,13 @@ function Sampler({ children, weight, transform, instances, mesh, count = 16, ...
6340
6288
  return /* @__PURE__ */ React.createElement("group", { ref: group, ...props }, children);
6341
6289
  }
6342
6290
 
6291
+ const pathStyle = (path) => path.userData?.style;
6343
6292
  const Svg = /* @__PURE__ */ forwardRef(
6344
6293
  function R3FSvg({ src, skipFill, skipStrokes, fillMaterial, strokeMaterial, fillMeshProps, strokeMeshProps, ...props }, ref) {
6345
6294
  const svg = useLoader(SVGLoader, !src.startsWith("<svg") ? src : `data:image/svg+xml;utf8,${src}`);
6346
6295
  const strokeGeometries = useMemo(
6347
6296
  () => skipStrokes ? [] : svg.paths.map(
6348
- (path) => path.userData?.style.stroke === void 0 || path.userData.style.stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), path.userData.style))
6297
+ (path) => pathStyle(path)?.stroke === void 0 || pathStyle(path).stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), pathStyle(path)))
6349
6298
  ),
6350
6299
  [svg, skipStrokes]
6351
6300
  );
@@ -6353,21 +6302,21 @@ const Svg = /* @__PURE__ */ forwardRef(
6353
6302
  return () => strokeGeometries.forEach((group) => group && group.map((g) => g.dispose()));
6354
6303
  }, [strokeGeometries]);
6355
6304
  let renderOrder = 0;
6356
- 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(
6305
+ 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(
6357
6306
  "meshBasicMaterial",
6358
6307
  {
6359
- color: path.userData.style.fill,
6360
- opacity: path.userData.style.fillOpacity,
6308
+ color: pathStyle(path).fill,
6309
+ opacity: pathStyle(path).fillOpacity,
6361
6310
  transparent: true,
6362
6311
  side: DoubleSide,
6363
6312
  depthWrite: false,
6364
6313
  ...fillMaterial
6365
6314
  }
6366
- ))), !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(
6315
+ ))), !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(
6367
6316
  "meshBasicMaterial",
6368
6317
  {
6369
- color: path.userData.style.stroke,
6370
- opacity: path.userData.style.strokeOpacity,
6318
+ color: pathStyle(path).stroke,
6319
+ opacity: pathStyle(path).strokeOpacity,
6371
6320
  transparent: true,
6372
6321
  side: DoubleSide,
6373
6322
  depthWrite: false,
@@ -6590,8 +6539,8 @@ function useVideoTexture(srcOrSrcObject, {
6590
6539
  });
6591
6540
  if (src && IS_BROWSER && src.endsWith(".m3u8")) {
6592
6541
  const hls = hlsRef.current = await getHls(hlsConfig);
6593
- if (hls) {
6594
- hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
6542
+ if (hls && _HLSModule) {
6543
+ hls.on(_HLSModule.Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
6595
6544
  hls.attachMedia(video2);
6596
6545
  }
6597
6546
  }
@@ -6811,9 +6760,125 @@ function Fbx({
6811
6760
  return /* @__PURE__ */ React.createElement(Clone, { ...props, object });
6812
6761
  }
6813
6762
 
6763
+ const SERVICE_KEY = "__drei_ktx2_service__";
6764
+ const DEFAULT_TRANSCODER_PATH = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
6765
+ class KTX2LoaderServiceImpl {
6766
+ loader = null;
6767
+ renderer = null;
6768
+ _isReady = false;
6769
+ transcoderPath = DEFAULT_TRANSCODER_PATH;
6770
+ pendingCallbacks = [];
6771
+ /**
6772
+ * Get or create the singleton KTX2Loader instance.
6773
+ * The loader is created lazily on first access.
6774
+ */
6775
+ getLoader() {
6776
+ if (!this.loader) {
6777
+ this.loader = new KTX2Loader();
6778
+ this.loader.setTranscoderPath(this.transcoderPath);
6779
+ }
6780
+ return this.loader;
6781
+ }
6782
+ /**
6783
+ * Register a renderer and call detectSupport.
6784
+ * Should be called from within React context (useLayoutEffect).
6785
+ * Flushes any queued callbacks.
6786
+ */
6787
+ registerRenderer(renderer) {
6788
+ if (this._isReady && this.renderer === renderer) {
6789
+ return;
6790
+ }
6791
+ this.renderer = renderer;
6792
+ const loader = this.getLoader();
6793
+ loader.detectSupport(renderer);
6794
+ this._isReady = true;
6795
+ this.flushPendingCallbacks();
6796
+ }
6797
+ /**
6798
+ * Check if the service is ready (has renderer registered).
6799
+ */
6800
+ isReady() {
6801
+ return this._isReady;
6802
+ }
6803
+ /**
6804
+ * Get the registered renderer, if any.
6805
+ */
6806
+ getRenderer() {
6807
+ return this.renderer;
6808
+ }
6809
+ /**
6810
+ * Register a callback to run when renderer becomes available.
6811
+ * If renderer is already available, runs immediately.
6812
+ */
6813
+ onReady(callback) {
6814
+ if (this._isReady) {
6815
+ callback();
6816
+ } else {
6817
+ this.pendingCallbacks.push(callback);
6818
+ }
6819
+ }
6820
+ /**
6821
+ * Process all pending callbacks.
6822
+ */
6823
+ flushPendingCallbacks() {
6824
+ const callbacks = this.pendingCallbacks;
6825
+ this.pendingCallbacks = [];
6826
+ for (const callback of callbacks) {
6827
+ try {
6828
+ callback();
6829
+ } catch (error) {
6830
+ console.error("KTX2LoaderService: Error in pending callback:", error);
6831
+ }
6832
+ }
6833
+ }
6834
+ /**
6835
+ * Set the transcoder path for basis/ktx2 decoding.
6836
+ */
6837
+ setTranscoderPath(path) {
6838
+ this.transcoderPath = path;
6839
+ if (this.loader) {
6840
+ this.loader.setTranscoderPath(path);
6841
+ }
6842
+ }
6843
+ /**
6844
+ * Get the current transcoder path.
6845
+ */
6846
+ getTranscoderPath() {
6847
+ return this.transcoderPath;
6848
+ }
6849
+ /**
6850
+ * Dispose of the loader and reset state.
6851
+ * Useful for testing or cleanup.
6852
+ */
6853
+ dispose() {
6854
+ if (this.loader) {
6855
+ this.loader.dispose();
6856
+ }
6857
+ const path = this.transcoderPath;
6858
+ this.loader = null;
6859
+ this.renderer = null;
6860
+ this._isReady = false;
6861
+ this.transcoderPath = path;
6862
+ this.pendingCallbacks = [];
6863
+ }
6864
+ }
6865
+ const KTX2LoaderService = globalThis[SERVICE_KEY] || (globalThis[SERVICE_KEY] = new KTX2LoaderServiceImpl());
6866
+
6814
6867
  let dracoLoader = null;
6815
6868
  let decoderPath = "https://www.gstatic.com/draco/versioned/decoders/1.5.5/";
6816
- function extensions(useDraco = true, useMeshopt = true, extendLoader) {
6869
+ let legacyApiWarned = false;
6870
+ function isOptionsObject(arg) {
6871
+ return typeof arg === "object" && arg !== null && !Array.isArray(arg);
6872
+ }
6873
+ function warnLegacyApi() {
6874
+ if (!legacyApiWarned) {
6875
+ legacyApiWarned = true;
6876
+ console.warn(
6877
+ "useGLTF: Boolean arguments are deprecated. Use options object instead:\n useGLTF(path, { draco: true, meshopt: true, ktx2: true })"
6878
+ );
6879
+ }
6880
+ }
6881
+ function extensions(useDraco = true, useMeshopt = true, useKTX2 = false, extendLoader, renderer) {
6817
6882
  return (loader) => {
6818
6883
  if (extendLoader) {
6819
6884
  extendLoader(loader);
@@ -6829,31 +6894,110 @@ function extensions(useDraco = true, useMeshopt = true, extendLoader) {
6829
6894
  const decoder = typeof MeshoptDecoder === "function" ? MeshoptDecoder() : MeshoptDecoder;
6830
6895
  loader.setMeshoptDecoder(decoder);
6831
6896
  }
6897
+ if (useKTX2) {
6898
+ const ktx2Loader = KTX2LoaderService.getLoader();
6899
+ if (typeof useKTX2 === "string") {
6900
+ ktx2Loader.setTranscoderPath(useKTX2);
6901
+ }
6902
+ if (renderer) {
6903
+ ktx2Loader.detectSupport(renderer);
6904
+ }
6905
+ loader.setKTX2Loader(ktx2Loader);
6906
+ }
6832
6907
  };
6833
6908
  }
6834
- const useGLTF = (path, useDraco, useMeshopt, extendLoader) => useLoader(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
6835
- useGLTF.preload = (path, useDraco, useMeshopt, extendLoader) => useLoader.preload(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
6909
+ function useGLTF(path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) {
6910
+ let draco = true;
6911
+ let meshopt = true;
6912
+ let ktx2 = false;
6913
+ let extendLoader;
6914
+ if (isOptionsObject(optionsOrDraco)) {
6915
+ draco = optionsOrDraco.draco ?? true;
6916
+ meshopt = optionsOrDraco.meshopt ?? true;
6917
+ ktx2 = optionsOrDraco.ktx2 ?? false;
6918
+ extendLoader = optionsOrDraco.extendLoader;
6919
+ } else if (optionsOrDraco !== void 0) {
6920
+ warnLegacyApi();
6921
+ draco = optionsOrDraco;
6922
+ meshopt = useMeshoptArg ?? true;
6923
+ ktx2 = useKTX2Arg ?? false;
6924
+ extendLoader = extendLoaderArg;
6925
+ }
6926
+ const renderer = useThree((state) => state.renderer);
6927
+ useLayoutEffect(() => {
6928
+ if (ktx2) {
6929
+ KTX2LoaderService.registerRenderer(renderer);
6930
+ }
6931
+ }, [ktx2, renderer]);
6932
+ return useLoader(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, ktx2 ? renderer : void 0));
6933
+ }
6934
+ useGLTF.preload = (path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) => {
6935
+ let draco = true;
6936
+ let meshopt = true;
6937
+ let ktx2 = false;
6938
+ let extendLoader;
6939
+ if (isOptionsObject(optionsOrDraco)) {
6940
+ draco = optionsOrDraco.draco ?? true;
6941
+ meshopt = optionsOrDraco.meshopt ?? true;
6942
+ ktx2 = optionsOrDraco.ktx2 ?? false;
6943
+ extendLoader = optionsOrDraco.extendLoader;
6944
+ } else if (optionsOrDraco !== void 0) {
6945
+ warnLegacyApi();
6946
+ draco = optionsOrDraco;
6947
+ meshopt = useMeshoptArg ?? true;
6948
+ ktx2 = useKTX2Arg ?? false;
6949
+ extendLoader = extendLoaderArg;
6950
+ }
6951
+ if (ktx2) {
6952
+ KTX2LoaderService.onReady(() => {
6953
+ const renderer = KTX2LoaderService.getRenderer();
6954
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, renderer));
6955
+ });
6956
+ } else {
6957
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, false, extendLoader));
6958
+ }
6959
+ };
6836
6960
  useGLTF.clear = (path) => useLoader.clear(GLTFLoader, path);
6837
6961
  useGLTF.setDecoderPath = (path) => {
6838
6962
  decoderPath = path;
6839
6963
  };
6964
+ useGLTF.setKTX2TranscoderPath = (path) => {
6965
+ KTX2LoaderService.setTranscoderPath(path);
6966
+ };
6840
6967
  const Gltf = /* @__PURE__ */ React.forwardRef(
6841
- ({ src, useDraco, useMeshOpt, extendLoader, ...props }, ref) => {
6842
- const { scene } = useGLTF(src, useDraco, useMeshOpt, extendLoader);
6968
+ ({ src, useDraco, useMeshOpt, extendLoader, options, ...props }, ref) => {
6969
+ const opts = options ?? {
6970
+ draco: useDraco,
6971
+ meshopt: useMeshOpt,
6972
+ extendLoader
6973
+ };
6974
+ const { scene } = useGLTF(src, opts);
6843
6975
  return /* @__PURE__ */ React.createElement(Clone, { ref, ...props, object: scene });
6844
6976
  }
6845
6977
  );
6846
6978
 
6847
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
6848
- function useKTX2(input, basisPath = `${cdn}/basis/`) {
6979
+ function useKTX2(input, basisPath = true, extendLoader) {
6849
6980
  const renderer = useThree((state) => state.renderer);
6981
+ useLayoutEffect(() => {
6982
+ KTX2LoaderService.registerRenderer(renderer);
6983
+ }, [renderer]);
6850
6984
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
6985
+ if (extendLoader) {
6986
+ extendLoader(loader);
6987
+ }
6988
+ if (basisPath) {
6989
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
6990
+ loader.setTranscoderPath(path);
6991
+ }
6851
6992
  loader.detectSupport(renderer);
6852
- loader.setTranscoderPath(basisPath);
6853
6993
  });
6854
6994
  useEffect(() => {
6855
6995
  const array = Array.isArray(textures) ? textures : [textures];
6856
- array.forEach((texture) => renderer.initTexture(texture));
6996
+ array.forEach((texture) => {
6997
+ if ("initTexture" in renderer) {
6998
+ renderer.initTexture(texture);
6999
+ }
7000
+ });
6857
7001
  }, [renderer, textures]);
6858
7002
  if (IsObject(input)) {
6859
7003
  const keys = Object.keys(input);
@@ -6864,19 +7008,81 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
6864
7008
  return textures;
6865
7009
  }
6866
7010
  }
6867
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
6868
- loader.setTranscoderPath(basisPath);
6869
- });
7011
+ useKTX2.preload = (url, basisPath = true, extendLoader) => {
7012
+ KTX2LoaderService.onReady(() => {
7013
+ const renderer = KTX2LoaderService.getRenderer();
7014
+ useLoader.preload(KTX2Loader, url, (loader) => {
7015
+ if (extendLoader) {
7016
+ extendLoader(loader);
7017
+ }
7018
+ if (basisPath) {
7019
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
7020
+ loader.setTranscoderPath(path);
7021
+ }
7022
+ if (renderer) {
7023
+ loader.detectSupport(renderer);
7024
+ }
7025
+ });
7026
+ });
7027
+ };
6870
7028
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
6871
- const Ktx2 = ({
6872
- children,
6873
- input,
6874
- basisPath
6875
- }) => {
6876
- const texture = useKTX2(input, basisPath);
7029
+ useKTX2.setTranscoderPath = (path) => {
7030
+ KTX2LoaderService.setTranscoderPath(path);
7031
+ };
7032
+ const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
7033
+ const texture = useKTX2(input, basisPath, extendLoader);
6877
7034
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
6878
7035
  };
6879
7036
 
7037
+ const useVariants = (model, inVariant) => {
7038
+ const [activeVariant, setActiveVariant] = useState(null);
7039
+ const [materials, setMaterials] = useState([]);
7040
+ useEffect(() => {
7041
+ model.parser.getDependencies("material").then((materials2) => {
7042
+ setMaterials(materials2);
7043
+ });
7044
+ }, [model]);
7045
+ const variants = useMemo(() => {
7046
+ return model.userData.gltfExtensions?.KHR_materials_variants?.variants;
7047
+ }, [model]);
7048
+ const applyVariant = useCallback(
7049
+ (variantIndex) => {
7050
+ model.scene.traverse(async (object) => {
7051
+ if (!("isMesh" in object) || !object.userData.gltfExtensions) return;
7052
+ const meshObject = object;
7053
+ const meshVariantDef = meshObject.userData.gltfExtensions.KHR_materials_variants;
7054
+ if (!meshVariantDef) return;
7055
+ if (!meshObject.userData.originalMaterial) meshObject.userData.originalMaterial = meshObject.material;
7056
+ const mapping = meshVariantDef.mappings.find((mapping2) => mapping2.variants.includes(variantIndex));
7057
+ if (mapping) {
7058
+ meshObject.material = await model.parser.getDependency("material", mapping.material);
7059
+ model.parser.assignFinalMaterial(meshObject);
7060
+ } else meshObject.material = meshObject.userData.originalMaterial;
7061
+ if (Array.isArray(meshObject.material)) for (const mat of meshObject.material) mat.needsUpdate = true;
7062
+ else meshObject.material.needsUpdate = true;
7063
+ });
7064
+ },
7065
+ [model]
7066
+ );
7067
+ const setVariant = useCallback(
7068
+ (variantName) => {
7069
+ if (!variants) return;
7070
+ const newVariant = variantName && variants.some((v) => v.name === variantName) ? variantName : null;
7071
+ setActiveVariant(newVariant);
7072
+ },
7073
+ [variants]
7074
+ );
7075
+ useEffect(() => {
7076
+ if (!variants) return;
7077
+ const variantIndex = variants.findIndex((v) => v.name === activeVariant);
7078
+ applyVariant(variantIndex > -1 ? variantIndex : 0);
7079
+ }, [variants, activeVariant, applyVariant]);
7080
+ useEffect(() => {
7081
+ setVariant(inVariant ?? null);
7082
+ }, [inVariant, setVariant]);
7083
+ return { variants, activeVariant, setVariant, materials };
7084
+ };
7085
+
6880
7086
  const WebcamVideoTexture = /* @__PURE__ */ forwardRef(
6881
7087
  ({
6882
7088
  constraints = {
@@ -7064,6 +7270,7 @@ function useEffectfulState(fn, deps = [], cb) {
7064
7270
  }
7065
7271
 
7066
7272
  function Stats({ showPanel = 0, className, parent }) {
7273
+ React.useEffect(() => void console.warn("drei: <Stats /> is deprecated, use <StatsGl /> instead."), []);
7067
7274
  const stats = useEffectfulState(() => new StatsImpl(), []);
7068
7275
  React.useEffect(() => {
7069
7276
  if (stats) {
@@ -7092,6 +7299,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
7092
7299
  const stats2 = new Stats$1({
7093
7300
  ...props
7094
7301
  });
7302
+ stats2.init(renderer);
7095
7303
  return stats2;
7096
7304
  }, [renderer]);
7097
7305
  React.useImperativeHandle(fref, () => stats.domElement, [stats]);
@@ -7109,7 +7317,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
7109
7317
  return () => {
7110
7318
  if (classNames.length) stats.domElement.classList.remove(...classNames);
7111
7319
  node?.removeChild(stats.domElement);
7112
- stats.end();
7320
+ stats.dispose();
7113
7321
  };
7114
7322
  }
7115
7323
  }, [parent, stats, className, id, clearStatsGlStyle]);
@@ -7117,7 +7325,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
7117
7325
  () => {
7118
7326
  stats.begin();
7119
7327
  },
7120
- { before: "render" }
7328
+ { before: "update" }
7121
7329
  );
7122
7330
  useFrame(
7123
7331
  () => {
@@ -7167,11 +7375,11 @@ const RenderCubeTexture = /* @__PURE__ */ React.forwardRef(
7167
7375
  depthBuffer,
7168
7376
  generateMipmaps
7169
7377
  };
7170
- const fboA2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
7378
+ const fboA2 = new WebGLCubeRenderTarget(fboSize, settings);
7171
7379
  fboA2.texture.isRenderTargetTexture = !flip;
7172
7380
  fboA2.texture.flipY = true;
7173
7381
  fboA2.texture.type = THREE.HalfFloatType;
7174
- const fboB2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
7382
+ const fboB2 = new WebGLCubeRenderTarget(fboSize, settings);
7175
7383
  fboB2.texture.isRenderTargetTexture = !flip;
7176
7384
  fboB2.texture.flipY = true;
7177
7385
  fboB2.texture.type = THREE.HalfFloatType;
@@ -7349,18 +7557,21 @@ function Fisheye({
7349
7557
  const normal = new THREE.Vector3();
7350
7558
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
7351
7559
  const normalMatrix = new THREE.Matrix3();
7352
- const compute = React.useCallback((event, state, prev) => {
7353
- state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7354
- state.raycaster.setFromCamera(state.pointer, orthoC);
7355
- if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7356
- else normal.normalize();
7357
- normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7358
- cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7359
- state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7360
- state.raycaster.ray.direction.x *= -1;
7361
- state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7362
- return void 0;
7363
- }, []);
7560
+ const compute = React.useCallback(
7561
+ (event, state, prev) => {
7562
+ state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
7563
+ state.raycaster.setFromCamera(state.pointer, orthoC);
7564
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
7565
+ else normal.normalize();
7566
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
7567
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
7568
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
7569
+ state.raycaster.ray.direction.x *= -1;
7570
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
7571
+ return void 0;
7572
+ },
7573
+ [width, height]
7574
+ );
7364
7575
  useFrame(
7365
7576
  ({ gl }) => {
7366
7577
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -7495,14 +7706,23 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
7495
7706
  const col = /* @__PURE__ */ new THREE.Color();
7496
7707
  const TUNNEL_KEY = "__drei_view_tunnel__";
7497
7708
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
7498
- function computeContainerPosition(canvasSize, trackRect) {
7709
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
7499
7710
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
7500
- const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
7501
7711
  const canvasBottom = canvasSize.top + canvasSize.height;
7502
- const bottom = canvasBottom - trackBottom;
7503
- const left = trackLeft - canvasSize.left;
7504
- const top = trackTop - canvasSize.top;
7505
- return { position: { width, height, left, top, bottom, right }, isOffscreen };
7712
+ const canvasRight = canvasSize.left + canvasSize.width;
7713
+ const isOffscreen = trackBottom < canvasSize.top || trackTop > canvasBottom || right < canvasSize.left || trackLeft > canvasRight;
7714
+ const rawBottom = canvasBottom - trackBottom;
7715
+ const rawLeft = trackLeft - canvasSize.left;
7716
+ const rawTop = trackTop - canvasSize.top;
7717
+ const position = forceEven ? {
7718
+ width: roundEven(width),
7719
+ height: roundEven(height),
7720
+ left: roundEven(rawLeft),
7721
+ top: roundEven(rawTop),
7722
+ bottom: roundEven(rawBottom),
7723
+ right: roundEven(right)
7724
+ } : { width, height, left: rawLeft, top: rawTop, bottom: rawBottom, right };
7725
+ return { position, isOffscreen };
7506
7726
  }
7507
7727
  function prepareSkissor(renderer, camera, {
7508
7728
  left,
@@ -7542,6 +7762,7 @@ function clear(renderer) {
7542
7762
  }
7543
7763
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
7544
7764
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
7765
+ const forceEven = useThree((state) => state.internal.forceEven);
7545
7766
  const [isOffscreen, setOffscreen] = React.useState(false);
7546
7767
  const mountedRef = React.useRef(true);
7547
7768
  React.useEffect(() => {
@@ -7559,7 +7780,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7559
7780
  frameCount++;
7560
7781
  }
7561
7782
  if (rect.current) {
7562
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
7783
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
7563
7784
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
7564
7785
  if (visible && !_isOffscreen && rect.current) {
7565
7786
  const targetScene = children ? portalScene : scene;
@@ -7581,14 +7802,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7581
7802
  React.useLayoutEffect(() => {
7582
7803
  const curRect = rect.current;
7583
7804
  if (curRect && (!visible || !isOffscreen)) {
7584
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7805
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7585
7806
  if (!_isOffscreen) {
7586
7807
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7587
7808
  clear(renderer);
7588
7809
  finishSkissor(renderer, autoClear);
7589
7810
  }
7590
7811
  }
7591
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
7812
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
7592
7813
  React.useEffect(() => {
7593
7814
  if (!track) return;
7594
7815
  const curRect = rect.current;
@@ -7596,7 +7817,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7596
7817
  setEvents({ connected: track.current });
7597
7818
  return () => {
7598
7819
  if (curRect) {
7599
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
7820
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
7600
7821
  if (!_isOffscreen) {
7601
7822
  const autoClear = prepareSkissor(renderer, portalCamera, position);
7602
7823
  clear(renderer);
@@ -7605,13 +7826,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
7605
7826
  }
7606
7827
  setEvents({ connected: old });
7607
7828
  };
7608
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
7829
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
7609
7830
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
7610
7831
  }
7611
7832
  const CanvasView = /* @__PURE__ */ React.forwardRef(
7612
7833
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
7613
7834
  const rect = React.useRef(null);
7614
7835
  const { size, scene } = useThree();
7836
+ const forceEven = useThree((state) => state.internal.forceEven);
7615
7837
  const [virtualScene] = React.useState(() => new THREE.Scene());
7616
7838
  const [ready, toggle] = React.useReducer(() => true, false);
7617
7839
  const compute = React.useCallback(
@@ -7648,12 +7870,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
7648
7870
  {
7649
7871
  events: { compute, priority: index },
7650
7872
  size: {
7651
- width: rect.current?.width,
7652
- height: rect.current?.height,
7873
+ width: forceEven && rect.current?.width ? roundEven(rect.current.width) : rect.current?.width,
7874
+ height: forceEven && rect.current?.height ? roundEven(rect.current.height) : rect.current?.height,
7653
7875
  // @ts-ignore
7654
- top: rect.current?.top,
7876
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
7655
7877
  // @ts-ignore
7656
- left: rect.current?.left
7878
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
7657
7879
  }
7658
7880
  }
7659
7881
  ));
@@ -8048,12 +8270,12 @@ useEnvironment.preload = (preloadOptions) => {
8048
8270
  if (extensions) extensions(loader2);
8049
8271
  });
8050
8272
  };
8051
- const clearDefaultOptins = {
8273
+ const clearDefaultOptions = {
8052
8274
  files: defaultFiles,
8053
8275
  preset: void 0
8054
8276
  };
8055
8277
  useEnvironment.clear = (clearOptions) => {
8056
- const options = { ...clearDefaultOptins, ...clearOptions };
8278
+ const options = { ...clearDefaultOptions, ...clearOptions };
8057
8279
  let { files } = options;
8058
8280
  const { preset } = options;
8059
8281
  if (preset) {
@@ -8747,9 +8969,14 @@ function Select({
8747
8969
  const oldControlsEnabled = controls?.enabled;
8748
8970
  let isDown = false;
8749
8971
  function prepareRay(event, vec) {
8750
- const { offsetX, offsetY } = event;
8972
+ const canvas = gl.domElement;
8973
+ const rect = canvas.getBoundingClientRect();
8974
+ const x = event.clientX - rect.left;
8975
+ const y = event.clientY - rect.top;
8751
8976
  const { width, height } = size;
8752
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
8977
+ const normalizedX = x / width * 2 - 1;
8978
+ const normalizedY = -(y / height) * 2 + 1;
8979
+ vec.set(normalizedX, normalizedY, -1);
8753
8980
  }
8754
8981
  function onSelectStart(event) {
8755
8982
  if (controls) controls.enabled = false;
@@ -12994,7 +13221,7 @@ const FacemeshDatas = {
12994
13221
 
12995
13222
  const FaceLandmarkerContext = /* @__PURE__ */ createContext({});
12996
13223
  const FaceLandmarkerDefaults = {
12997
- basePath: "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.17/wasm",
13224
+ basePath: `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${"0.10.17"}/wasm`,
12998
13225
  options: {
12999
13226
  baseOptions: {
13000
13227
  modelAssetPath: "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task",
@@ -13742,4 +13969,4 @@ const MarchingPlane = /* @__PURE__ */ React.forwardRef(
13742
13969
  }
13743
13970
  );
13744
13971
 
13745
- export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, Bvh, CameraControls, CameraShake, Capsule, CatmullRomLine, Center, Circle, Clone, Cloud, CloudInstance, Clouds, ComputedAttribute, Cone, CubeCamera, CubeTexture, CubicBezierLine, CurveModifier, CycleRaycast, Cylinder, Decal, Detailed, DetectGPU, DeviceOrientationControls, Dodecahedron, DragControls, Edges, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, Example, Extrude, FaceControls, FaceLandmarker, FaceLandmarkerDefaults, Facemesh, FacemeshDatas, FacemeshEye, FacemeshEyeDefaults, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, MarchingCube, MarchingCubes, MarchingPlane, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, Outlines, PerformanceMonitor, PerspectiveCamera, PivotControls, Plane, Point, PointMaterial, PointMaterialImpl, PointerLockControls, Points, PointsBuffer, Polyhedron, PositionMesh, PositionPoint, PositionalAudio, Preload, PresentationControls, Progress, QuadraticBezierLine, RenderCubeTexture, RenderTexture, Resize, Ring, RoundedBox, RoundedBoxGeometry, Sampler, ScreenQuad, ScreenSizer, ScreenSpace, ScreenVideoTexture, Scroll, ScrollControls, Select, Shadow, Shape, Sky, Sphere, Splat, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, Trail, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, Wireframe, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBVH, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFaceControls, useFaceLandmarker, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrail, useTrailTexture, useVideoTexture };
13972
+ export { AdaptiveDpr, AdaptiveEvents, ArcballControls, BBAnchor, Backdrop, Billboard, Bounds, Box, Bvh, CameraControls, CameraShake, Capsule, CatmullRomLine, Center, Circle, Clone, Cloud, CloudInstance, Clouds, ComputedAttribute, Cone, CubeCamera, CubeTexture, CubicBezierLine, CurveModifier, CycleRaycast, Cylinder, Decal, Detailed, DetectGPU, DeviceOrientationControls, Dodecahedron, DragControls, Edges, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, Example, Extrude, FaceControls, FaceLandmarker, FaceLandmarkerDefaults, Facemesh, FacemeshDatas, FacemeshEye, FacemeshEyeDefaults, Fbo, Fbx, FirstPersonControls, Fisheye, Float, FlyControls, GizmoHelper, GizmoViewcube, GizmoViewport, Gltf, Helper, Html, Hud, Icosahedron, Instance, InstancedAttribute, Instances, IsObject, KeyboardControls, Ktx2, Lathe, Lightformer, Loader, MapControls, MarchingCube, MarchingCubes, MarchingPlane, Mask, MatcapTexture, Merged, MotionPathControls, NormalTexture, Octahedron, OrbitControls, OrthographicCamera, Outlines, PerformanceMonitor, PerspectiveCamera, PivotControls, Plane, Point, PointMaterial, PointMaterialImpl, PointerLockControls, Points, PointsBuffer, Polyhedron, PositionMesh, PositionPoint, PositionalAudio, Preload, PresentationControls, Progress, QuadraticBezierLine, RenderCubeTexture, RenderTexture, Resize, Ring, RoundedBox, RoundedBoxGeometry, Sampler, ScreenQuad, ScreenSizer, ScreenSpace, ScreenVideoTexture, Scroll, ScrollControls, Select, Shadow, Shape, Sky, Sphere, Splat, SpriteAnimator, Stage, Stats, StatsGl, Svg, Tetrahedron, Text3D, Texture, Torus, TorusKnot, TrackballControls, Trail, TrailTexture, TransformControls, Tube, VideoTexture, View, WebcamVideoTexture, calcPosFromAngles, checkIfFrameIsEmpty, createInstances, getFirstFrame, meshBounds, presetsObj, useAnimations, useAspect, useBVH, useBounds, useContextBridge, useCubeCamera, useCubeTexture, useCursor, useCustomRaycast, useDepthBuffer, useDetectGPU, useEnvironment, useFBO, useFBX, useFaceControls, useFaceLandmarker, useFont, useGLTF, useGizmoContext, useHelper, useIntersect, useKTX2, useKeyboardControls, useMask, useMatcapTexture, useMotion, useNormalTexture, usePerformanceMonitor, useProgress, useScroll, useSelect, useSpriteAnimator, useSpriteLoader, useSurfaceSampler, useTexture, useTrail, useTrailTexture, useVariants, useVideoTexture };