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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/legacy/index.mjs CHANGED
@@ -2,11 +2,10 @@ import * as React from 'react';
2
2
  import { forwardRef, useImperativeHandle, useMemo, useEffect, useCallback, useRef, useLayoutEffect, useState, Fragment, createContext, useContext } from 'react';
3
3
  import { useThree, useFrame, extend, createPortal, applyProps, useInstanceHandle, context as context$a, useLoader, addEffect, addAfterEffect } from '@react-three/fiber';
4
4
  import * as THREE from 'three';
5
- import { REVISION, WebGLRenderTarget, HalfFloatType, LinearFilter, Vector2, Scene, Camera, BufferGeometry, BufferAttribute, Mesh, MeshStandardMaterial, Plane as Plane$1, Vector3, Matrix4, Vector4, PerspectiveCamera as PerspectiveCamera$1, DepthTexture, DepthFormat, UnsignedShortType, Frustum, MeshPhysicalMaterial, RGBAFormat, UnsignedByteType, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, CubeCamera as CubeCamera$1, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, Quaternion, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, DoubleSide, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, Box3, Sphere as Sphere$1, InstancedBufferAttribute, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, CubeReflectionMapping, EquirectangularReflectionMapping, RepeatWrapping, ShaderMaterial, CylinderGeometry, AdditiveBlending, Spherical } from 'three';
5
+ import { REVISION, WebGLRenderTarget, HalfFloatType, LinearFilter, Vector2, Scene, Camera, BufferGeometry, BufferAttribute, Mesh, MeshStandardMaterial, Plane as Plane$1, Vector3, Matrix4, Vector4, PerspectiveCamera as PerspectiveCamera$1, DepthTexture, DepthFormat, UnsignedShortType, Frustum, MeshPhysicalMaterial, RGBAFormat, UnsignedByteType, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, DoubleSide, OrthographicCamera as OrthographicCamera$1, AudioListener, AudioLoader, AnimationMixer, Box3, Sphere as Sphere$1, InstancedBufferAttribute, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, CubeReflectionMapping, EquirectangularReflectionMapping, RepeatWrapping, ShaderMaterial, CylinderGeometry, AdditiveBlending, Spherical } from 'three';
6
6
  import { ArcballControls as ArcballControls$1 } from 'three/examples/jsm/controls/ArcballControls.js';
7
- import { DeviceOrientationControls as DeviceOrientationControls$1 } from 'three-stdlib';
8
7
  import { useGesture } from '@use-gesture/react';
9
- import { FirstPersonControls as FirstPersonControls$1 } from 'three/examples/jsm/controls/FirstPersonControls';
8
+ import { FirstPersonControls as FirstPersonControls$1 } from 'three/examples/jsm/controls/FirstPersonControls.js';
10
9
  import { FlyControls as FlyControls$1 } from 'three/examples/jsm/controls/FlyControls.js';
11
10
  import { create as create$1 } from 'zustand';
12
11
  import { subscribeWithSelector } from 'zustand/middleware';
@@ -22,44 +21,43 @@ import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js';
22
21
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
23
22
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
24
23
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
25
- import { Flow } from 'three/examples/jsm/modifiers/CurveModifier';
24
+ import { Flow } from 'three/examples/jsm/modifiers/CurveModifier.js';
26
25
  import { DecalGeometry } from 'three/examples/jsm/geometries/DecalGeometry.js';
27
- import { toCreasedNormals, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils';
28
- import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
29
- import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
26
+ import { toCreasedNormals, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
27
+ import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
28
+ import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
30
29
  import { suspend, preload, clear as clear$1 } from 'suspend-react';
31
- import { TransformControls as TransformControls$1 } from 'three/examples/jsm/controls/TransformControls';
32
- import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';
30
+ import { TransformControls as TransformControls$1 } from 'three/examples/jsm/controls/TransformControls.js';
31
+ import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
33
32
  import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
34
- import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader';
33
+ import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js';
35
34
  import { Events } from 'hls.js';
36
- import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
37
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
38
- import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
35
+ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
36
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
37
+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
39
38
  import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
40
- import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader';
39
+ import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
41
40
  import { getGPUTier } from 'detect-gpu';
42
41
  import StatsImpl from 'stats.js';
43
42
  import Stats$1 from 'stats-gl';
44
43
  import tunnel from 'tunnel-rat';
45
- import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox';
46
- import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
47
- import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader';
44
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
45
+ import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
46
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
48
47
  import { HDRJPGLoader, GainMapLoader } from '@monogrid/gainmap-js';
49
48
  import { Sky as Sky$1 } from 'three/examples/jsm/objects/Sky.js';
50
49
  import { SkyMesh } from 'three/examples/jsm/objects/SkyMesh.js';
51
- import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader';
52
- import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader';
53
- import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox';
50
+ import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
51
+ import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
52
+ import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox.js';
54
53
  import { shallow } from 'zustand/shallow';
55
- import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader';
56
- import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
57
- import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
58
- import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
59
- import { AsciiEffect } from 'three/examples/jsm/effects/AsciiEffect';
60
- import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass';
54
+ import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
55
+ import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
56
+ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
57
+ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
58
+ import { AsciiEffect } from 'three/examples/jsm/effects/AsciiEffect.js';
59
+ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
61
60
  import { shaderStructs, shaderIntersectFunction, MeshBVHUniformStruct } from 'three-mesh-bvh';
62
- import { FullScreenQuad as FullScreenQuad$1 } from 'three/examples/jsm/postprocessing/Pass.js';
63
61
  import { Text as Text$1, preloadFont } from 'troika-three-text';
64
62
 
65
63
  const ContactShadows = /* @__PURE__ */ React.forwardRef(
@@ -189,6 +187,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
189
187
  });
190
188
  }
191
189
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
190
+ Object.assign(this, parameters);
192
191
  onInit?.(this);
193
192
  }
194
193
  };
@@ -1518,6 +1517,7 @@ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends MeshStandardMa
1518
1517
  uniform float depthScale;
1519
1518
  uniform float depthToBlurRatioBias;
1520
1519
  varying vec4 my_vUv;
1520
+ #include <tonemapping_pars_fragment>
1521
1521
  ${shader.fragmentShader}`;
1522
1522
  shader.fragmentShader = shader.fragmentShader.replace(
1523
1523
  "#include <emissivemap_fragment>",
@@ -1578,6 +1578,9 @@ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends MeshStandardMa
1578
1578
  merge = mix(merge, blur, blurFactor);
1579
1579
  #endif
1580
1580
 
1581
+ // Apply tone mapping to HDR reflection values before contrast adjustment
1582
+ merge.rgb = toneMapping(merge.rgb);
1583
+
1581
1584
  vec4 newMerge = vec4(0.0, 0.0, 0.0, 1.0);
1582
1585
  newMerge.r = (merge.r - 0.5) * mixContrast + 0.5;
1583
1586
  newMerge.g = (merge.g - 0.5) * mixContrast + 0.5;
@@ -1836,6 +1839,7 @@ const MeshReflectorMaterial = /* @__PURE__ */ React.forwardRef(
1836
1839
  useFrame(() => {
1837
1840
  const parent = materialRef.current.parent || materialRef.current?.__r3f.parent?.object;
1838
1841
  if (!parent) return;
1842
+ if (gl.xr.isPresenting) return;
1839
1843
  parent.visible = false;
1840
1844
  const currentXrEnabled = gl.xr.enabled;
1841
1845
  const currentShadowAutoUpdate = gl.shadowMap.autoUpdate;
@@ -2249,6 +2253,8 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2249
2253
  children,
2250
2254
  events = void 0,
2251
2255
  blur = 0,
2256
+ blend = 0,
2257
+ samples = 8,
2252
2258
  eventPriority = 0,
2253
2259
  renderPriority = 0,
2254
2260
  worldUnits = false,
@@ -2261,7 +2267,7 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2261
2267
  const maskRenderTarget = useFBO(resolution, resolution);
2262
2268
  const [priority, setPriority] = React.useState(0);
2263
2269
  useFrame(() => {
2264
- const p = ref.current.blend > 0 ? Math.max(1, renderPriority) : 0;
2270
+ const p = (ref.current.blend ?? 0) > 0 ? Math.max(1, renderPriority) : 0;
2265
2271
  if (priority !== p) setPriority(p);
2266
2272
  });
2267
2273
  React.useEffect(() => {
@@ -2335,7 +2341,8 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2335
2341
  frames: visible ? Infinity : 0,
2336
2342
  eventPriority,
2337
2343
  renderPriority,
2338
- compute
2344
+ compute,
2345
+ samples
2339
2346
  },
2340
2347
  children,
2341
2348
  /* @__PURE__ */ React.createElement(
@@ -2415,7 +2422,7 @@ function ManagePortalScene({
2415
2422
  scene.matrixWorld.copy(parent.matrixWorld);
2416
2423
  } else scene.matrixWorld.identity();
2417
2424
  if (priority) {
2418
- if (material.current?.blend > 0 && material.current?.blend < 1) {
2425
+ if (material.current?.blend && material.current?.blend > 0 && material.current?.blend < 1) {
2419
2426
  blend.value = material.current.blend;
2420
2427
  state.gl.setRenderTarget(buffer1);
2421
2428
  state.gl.render(scene, state.camera);
@@ -3208,12 +3215,12 @@ const Segments = /* @__PURE__ */ React.forwardRef((props, forwardedRef) => {
3208
3215
  for (let i = 0; i < limit; i++) {
3209
3216
  const segment = segments[i]?.current;
3210
3217
  if (segment) {
3211
- positions[i * 6 + 0] = segment.start.x;
3212
- positions[i * 6 + 1] = segment.start.y;
3213
- positions[i * 6 + 2] = segment.start.z;
3214
- positions[i * 6 + 3] = segment.end.x;
3215
- positions[i * 6 + 4] = segment.end.y;
3216
- positions[i * 6 + 5] = segment.end.z;
3218
+ positions[i * 6 + 0] = segment.start?.x || 0;
3219
+ positions[i * 6 + 1] = segment.start?.y || 0;
3220
+ positions[i * 6 + 2] = segment.start?.z || 0;
3221
+ positions[i * 6 + 3] = segment.end?.x || 0;
3222
+ positions[i * 6 + 4] = segment.end?.y || 0;
3223
+ positions[i * 6 + 5] = segment.end?.z || 0;
3217
3224
  colors[i * 6 + 0] = segment.color.r;
3218
3225
  colors[i * 6 + 1] = segment.color.g;
3219
3226
  colors[i * 6 + 2] = segment.color.b;
@@ -3248,7 +3255,10 @@ class SegmentObject {
3248
3255
  this.end = new THREE.Vector3(0, 0, 0);
3249
3256
  }
3250
3257
  }
3251
- const normPos = (pos) => pos instanceof THREE.Vector3 ? pos : new THREE.Vector3(...typeof pos === "number" ? [pos, pos, pos] : pos);
3258
+ const normPos = (pos) => {
3259
+ if (!pos) return new THREE.Vector3(0, 0, 0);
3260
+ return pos instanceof THREE.Vector3 ? pos : new THREE.Vector3(...typeof pos === "number" ? [pos, pos, pos] : pos);
3261
+ };
3252
3262
  const Segment = /* @__PURE__ */ React.forwardRef(({ color, start, end }, forwardedRef) => {
3253
3263
  const api = React.useContext(context$9);
3254
3264
  if (!api) throw "Segment must used inside Segments component.";
@@ -3570,6 +3580,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
3570
3580
  }
3571
3581
  );
3572
3582
 
3583
+ let DeviceOrientationControls$1 = class DeviceOrientationControls extends EventDispatcher {
3584
+ object;
3585
+ changeEvent = { type: "change" };
3586
+ EPS = 1e-6;
3587
+ enabled = true;
3588
+ deviceOrientation = { alpha: 0, beta: 0, gamma: 0 };
3589
+ screenOrientation = 0;
3590
+ alphaOffset = 0;
3591
+ // radians
3592
+ constructor(object) {
3593
+ super();
3594
+ this.object = object;
3595
+ this.object.rotation.reorder("YXZ");
3596
+ this.connect();
3597
+ }
3598
+ onDeviceOrientationChangeEvent = (event) => {
3599
+ this.deviceOrientation = event;
3600
+ };
3601
+ onScreenOrientationChangeEvent = () => {
3602
+ this.screenOrientation = window.orientation || 0;
3603
+ };
3604
+ // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
3605
+ zee = new Vector3(0, 0, 1);
3606
+ euler = new Euler();
3607
+ q0 = new Quaternion();
3608
+ q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
3609
+ // - PI/2 around the x-axis
3610
+ setObjectQuaternion = (quaternion, alpha, beta, gamma, orient) => {
3611
+ this.euler.set(beta, alpha, -gamma, "YXZ");
3612
+ quaternion.setFromEuler(this.euler);
3613
+ quaternion.multiply(this.q1);
3614
+ quaternion.multiply(this.q0.setFromAxisAngle(this.zee, -orient));
3615
+ };
3616
+ connect = () => {
3617
+ this.onScreenOrientationChangeEvent();
3618
+ if (window.DeviceOrientationEvent !== void 0 && // @ts-ignore
3619
+ typeof window.DeviceOrientationEvent.requestPermission === "function") {
3620
+ window.DeviceOrientationEvent.requestPermission().then((response) => {
3621
+ if (response == "granted") {
3622
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
3623
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
3624
+ }
3625
+ }).catch((error) => {
3626
+ console.error("THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:", error);
3627
+ });
3628
+ } else {
3629
+ window.addEventListener("orientationchange", this.onScreenOrientationChangeEvent);
3630
+ window.addEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
3631
+ }
3632
+ this.enabled = true;
3633
+ };
3634
+ disconnect = () => {
3635
+ window.removeEventListener("orientationchange", this.onScreenOrientationChangeEvent);
3636
+ window.removeEventListener("deviceorientation", this.onDeviceOrientationChangeEvent);
3637
+ this.enabled = false;
3638
+ };
3639
+ lastQuaternion = new Quaternion();
3640
+ update = () => {
3641
+ if (this.enabled === false) return;
3642
+ const device = this.deviceOrientation;
3643
+ if (device) {
3644
+ const alpha = device.alpha ? MathUtils.degToRad(device.alpha) + this.alphaOffset : 0;
3645
+ const beta = device.beta ? MathUtils.degToRad(device.beta) : 0;
3646
+ const gamma = device.gamma ? MathUtils.degToRad(device.gamma) : 0;
3647
+ const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation) : 0;
3648
+ this.setObjectQuaternion(this.object.quaternion, alpha, beta, gamma, orient);
3649
+ if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > this.EPS) {
3650
+ this.lastQuaternion.copy(this.object.quaternion);
3651
+ this.dispatchEvent(this.changeEvent);
3652
+ }
3653
+ }
3654
+ };
3655
+ dispose = () => this.disconnect();
3656
+ };
3657
+
3573
3658
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
3574
3659
  (props, ref) => {
3575
3660
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -3639,8 +3724,9 @@ const DragControls = React.forwardRef(
3639
3724
  onDragStart && onDragStart(initialModelPosition);
3640
3725
  invalidate();
3641
3726
  },
3642
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
3727
+ onDrag: ({ xy: [dragX, dragY], intentional, event }) => {
3643
3728
  if (!intentional) return;
3729
+ if (dragConfig && dragConfig.preventOverlap) event.preventDefault();
3644
3730
  const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
3645
3731
  const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
3646
3732
  mousePosition2D.set(normalizedMouseX, normalizedMouseY);
@@ -3809,12 +3895,21 @@ function KeyboardControls({ map, children, onChange, domElement }) {
3809
3895
  obj.pressed = false;
3810
3896
  if (up) fn(false);
3811
3897
  };
3898
+ const blurHandler = () => {
3899
+ const resetState = map.reduce((prev, cur) => ({ ...prev, [cur.name]: false }), {});
3900
+ set(resetState);
3901
+ Object.values(keyMap).forEach((obj) => {
3902
+ obj.pressed = false;
3903
+ });
3904
+ };
3812
3905
  const source = domElement || window;
3813
3906
  source.addEventListener("keydown", downHandler, { passive: true });
3814
3907
  source.addEventListener("keyup", upHandler, { passive: true });
3908
+ window.addEventListener("blur", blurHandler);
3815
3909
  return () => {
3816
3910
  source.removeEventListener("keydown", downHandler);
3817
3911
  source.removeEventListener("keyup", upHandler);
3912
+ window.removeEventListener("blur", blurHandler);
3818
3913
  };
3819
3914
  }, [domElement, key]);
3820
3915
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
@@ -4165,7 +4260,13 @@ function PresentationControls({
4165
4260
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
4166
4261
  }
4167
4262
 
4263
+ function roundToPixelRatio(value) {
4264
+ const ratio = window.devicePixelRatio || 1;
4265
+ return Math.round(value * ratio) / ratio;
4266
+ }
4267
+
4168
4268
  const context$7 = /* @__PURE__ */ React.createContext(null);
4269
+ const rootCache = /* @__PURE__ */ new WeakMap();
4169
4270
  function useScroll() {
4170
4271
  return React.useContext(context$7);
4171
4272
  }
@@ -4179,6 +4280,7 @@ function ScrollControls({
4179
4280
  damping = 0.25,
4180
4281
  maxSpeed = Infinity,
4181
4282
  prepend = false,
4283
+ pixelPerfect = true,
4182
4284
  style = {},
4183
4285
  children
4184
4286
  }) {
@@ -4200,6 +4302,7 @@ function ScrollControls({
4200
4302
  delta: 0,
4201
4303
  scroll,
4202
4304
  pages,
4305
+ pixelPerfect,
4203
4306
  // 0-1 for a range between from -> from + distance
4204
4307
  range(from, distance2, margin = 0) {
4205
4308
  const start = from - margin;
@@ -4218,7 +4321,7 @@ function ScrollControls({
4218
4321
  }
4219
4322
  };
4220
4323
  return state2;
4221
- }, [eps, damping, horizontal, pages]);
4324
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
4222
4325
  React.useEffect(() => {
4223
4326
  el.style.position = "absolute";
4224
4327
  el.style.width = "100%";
@@ -4331,10 +4434,23 @@ const ScrollHtml = React.forwardRef(
4331
4434
  React.useImperativeHandle(ref, () => group.current, []);
4332
4435
  const { width, height } = useThree((state2) => state2.size);
4333
4436
  const fiberState = React.useContext(context$a);
4334
- const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
4437
+ const root = React.useMemo(() => {
4438
+ let root2 = rootCache.get(state.fixed);
4439
+ if (!root2) {
4440
+ root2 = ReactDOM.createRoot(state.fixed);
4441
+ rootCache.set(state.fixed, root2);
4442
+ }
4443
+ return root2;
4444
+ }, [state.fixed]);
4335
4445
  useFrame(() => {
4336
4446
  if (state.delta > state.eps) {
4337
- 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)`;
4447
+ let x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
4448
+ let y = state.horizontal ? 0 : height * (state.pages - 1) * -state.offset;
4449
+ if (state.pixelPerfect) {
4450
+ x = roundToPixelRatio(x);
4451
+ y = roundToPixelRatio(y);
4452
+ }
4453
+ group.current.style.transform = `translate3d(${x}px,${y}px,0)`;
4338
4454
  }
4339
4455
  });
4340
4456
  root.render(
@@ -5263,460 +5379,89 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
5263
5379
  }
5264
5380
  );
5265
5381
 
5266
- const WireframeMaterialShaders = {
5267
- uniforms: {
5268
- strokeOpacity: 1,
5269
- fillOpacity: 0.25,
5270
- fillMix: 0,
5271
- thickness: 0.05,
5272
- colorBackfaces: false,
5273
- dashInvert: true,
5274
- dash: false,
5275
- dashRepeats: 4,
5276
- dashLength: 0.5,
5277
- squeeze: false,
5278
- squeezeMin: 0.2,
5279
- squeezeMax: 1,
5280
- stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
5281
- backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
5282
- fill: /* @__PURE__ */ new THREE.Color("#00ff00")
5283
- },
5284
- vertex: (
5285
- /* glsl */
5286
- `
5287
- attribute vec3 barycentric;
5288
-
5289
- varying vec3 v_edges_Barycentric;
5290
- varying vec3 v_edges_Position;
5291
-
5292
- void initWireframe() {
5293
- v_edges_Barycentric = barycentric;
5294
- v_edges_Position = position.xyz;
5295
- }
5296
- `
5297
- ),
5298
- fragment: (
5299
- /* glsl */
5300
- `
5301
- #ifndef PI
5302
- #define PI 3.1415926535897932384626433832795
5303
- #endif
5304
-
5305
- varying vec3 v_edges_Barycentric;
5306
- varying vec3 v_edges_Position;
5307
-
5308
- uniform float strokeOpacity;
5309
- uniform float fillOpacity;
5310
- uniform float fillMix;
5311
- uniform float thickness;
5312
- uniform bool colorBackfaces;
5313
-
5314
- // Dash
5315
- uniform bool dashInvert;
5316
- uniform bool dash;
5317
- uniform bool dashOnly;
5318
- uniform float dashRepeats;
5319
- uniform float dashLength;
5320
-
5321
- // Squeeze
5322
- uniform bool squeeze;
5323
- uniform float squeezeMin;
5324
- uniform float squeezeMax;
5325
-
5326
- // Colors
5327
- uniform vec3 stroke;
5328
- uniform vec3 backfaceStroke;
5329
- uniform vec3 fill;
5330
-
5331
- // This is like
5332
- float wireframe_aastep(float threshold, float dist) {
5333
- float afwidth = fwidth(dist) * 0.5;
5334
- return smoothstep(threshold - afwidth, threshold + afwidth, dist);
5335
- }
5336
-
5337
- float wireframe_map(float value, float min1, float max1, float min2, float max2) {
5338
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
5339
- }
5340
-
5341
- float getWireframe() {
5342
- vec3 barycentric = v_edges_Barycentric;
5343
-
5344
- // Distance from center of each triangle to its edges.
5345
- float d = min(min(barycentric.x, barycentric.y), barycentric.z);
5346
-
5347
- // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
5348
- float positionAlong = max(barycentric.x, barycentric.y);
5349
- if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
5350
- positionAlong = 1.0 - positionAlong;
5351
- }
5352
-
5353
- // the thickness of the stroke
5354
- float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
5355
-
5356
- // if we want to shrink the thickness toward the center of the line segment
5357
- if (squeeze) {
5358
- computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
5359
- }
5360
-
5361
- // Create dash pattern
5362
- if (dash) {
5363
- // here we offset the stroke position depending on whether it
5364
- // should overlap or not
5365
- float offset = 1.0 / dashRepeats * dashLength / 2.0;
5366
- if (!dashInvert) {
5367
- offset += 1.0 / dashRepeats / 2.0;
5368
- }
5369
-
5370
- // if we should animate the dash or not
5371
- // if (dashAnimate) {
5372
- // offset += time * 0.22;
5373
- // }
5374
-
5375
- // create the repeating dash pattern
5376
- float pattern = fract((positionAlong + offset) * dashRepeats);
5377
- computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
5378
- }
5379
-
5380
- // compute the anti-aliased stroke edge
5381
- float edge = 1.0 - wireframe_aastep(computedThickness, d);
5382
-
5383
- return edge;
5384
- }
5385
- `
5386
- )
5387
- };
5388
- const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
5389
- WireframeMaterialShaders.uniforms,
5390
- WireframeMaterialShaders.vertex + /* glsl */
5391
- `
5392
- void main() {
5393
- initWireframe();
5394
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
5395
- }
5396
- `,
5397
- WireframeMaterialShaders.fragment + /* glsl */
5398
- `
5399
- void main () {
5400
- // Compute color
5401
-
5402
- float edge = getWireframe();
5403
- vec4 colorStroke = vec4(stroke, edge);
5404
-
5405
- #ifdef FLIP_SIDED
5406
- colorStroke.rgb = backfaceStroke;
5407
- #endif
5408
-
5409
- vec4 colorFill = vec4(fill, fillOpacity);
5410
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
5411
-
5412
- gl_FragColor = outColor;
5413
- }
5414
- `
5415
- );
5416
- function setWireframeOverride(material, uniforms) {
5417
- material.onBeforeCompile = (shader) => {
5418
- shader.uniforms = {
5419
- ...shader.uniforms,
5420
- ...uniforms
5421
- };
5422
- shader.vertexShader = shader.vertexShader.replace(
5423
- "void main() {",
5424
- `
5425
- ${WireframeMaterialShaders.vertex}
5426
- void main() {
5427
- initWireframe();
5428
- `
5429
- );
5430
- shader.fragmentShader = shader.fragmentShader.replace(
5431
- "void main() {",
5432
- `
5433
- ${WireframeMaterialShaders.fragment}
5434
- void main() {
5435
- `
5436
- );
5437
- shader.fragmentShader = shader.fragmentShader.replace(
5438
- "#include <color_fragment>",
5439
- /* glsl */
5440
- `
5441
- #include <color_fragment>
5442
- float edge = getWireframe();
5443
- vec4 colorStroke = vec4(stroke, edge);
5444
- #ifdef FLIP_SIDED
5445
- colorStroke.rgb = backfaceStroke;
5446
- #endif
5447
- vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
5448
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
5449
-
5450
- diffuseColor.rgb = outColor.rgb;
5451
- diffuseColor.a *= outColor.a;
5452
- `
5453
- );
5454
- };
5455
- material.side = THREE.DoubleSide;
5456
- material.transparent = true;
5457
- }
5458
- function useWireframeUniforms(uniforms, props) {
5459
- React.useEffect(
5460
- () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
5461
- [props.fillOpacity]
5462
- );
5463
- React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
5464
- React.useEffect(
5465
- () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
5466
- [props.strokeOpacity]
5467
- );
5468
- React.useEffect(
5469
- () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
5470
- [props.thickness]
5471
- );
5472
- React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
5473
- React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
5474
- React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
5475
- React.useEffect(
5476
- () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
5477
- [props.dashRepeats]
5478
- );
5479
- React.useEffect(
5480
- () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
5481
- [props.dashLength]
5482
- );
5483
- React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
5484
- React.useEffect(
5485
- () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
5486
- [props.squeezeMin]
5487
- );
5488
- React.useEffect(
5489
- () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
5490
- [props.squeezeMax]
5491
- );
5492
- React.useEffect(
5493
- () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
5494
- [props.stroke]
5495
- );
5496
- React.useEffect(
5497
- () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
5498
- [props.fill]
5499
- );
5500
- React.useEffect(
5501
- () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
5502
- [props.backfaceStroke]
5503
- );
5504
- }
5505
-
5506
- function isWithGeometry(object) {
5507
- return !!object?.geometry;
5508
- }
5509
- function isGeometry(object) {
5510
- return !!object?.isBufferGeometry;
5511
- }
5512
- function isRefObject$1(object) {
5513
- return !!object?.current;
5514
- }
5515
- function isRef$1(object) {
5516
- return object?.current !== void 0;
5517
- }
5518
- function isWireframeGeometry(geometry) {
5519
- return geometry.type === "WireframeGeometry";
5520
- }
5521
- function getUniforms() {
5522
- const u = {};
5523
- for (const key in WireframeMaterialShaders.uniforms) {
5524
- u[key] = { value: WireframeMaterialShaders.uniforms[key] };
5525
- }
5526
- return u;
5527
- }
5528
- function getBarycentricCoordinates(geometry, removeEdge) {
5529
- const position = geometry.getAttribute("position");
5530
- const count = position.count;
5531
- const barycentric = [];
5532
- for (let i = 0; i < count; i++) {
5533
- const even = i % 2 === 0;
5534
- const Q = removeEdge ? 1 : 0;
5535
- if (even) {
5536
- barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
5537
- } else {
5538
- barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
5539
- }
5540
- }
5541
- return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
5542
- }
5543
- function getInputGeometry(inputGeometry) {
5544
- const geo = isRefObject$1(inputGeometry) ? inputGeometry.current : inputGeometry;
5545
- if (!isGeometry(geo)) {
5546
- if (isWireframeGeometry(geo)) {
5547
- throw new Error("Wireframe: WireframeGeometry is not supported.");
5548
- }
5549
- const parent = geo.parent;
5550
- if (isWithGeometry(parent)) {
5551
- if (isWireframeGeometry(parent.geometry)) {
5552
- throw new Error("Wireframe: WireframeGeometry is not supported.");
5553
- }
5554
- return parent.geometry;
5555
- }
5556
- } else {
5557
- return geo;
5558
- }
5559
- }
5560
- function setBarycentricCoordinates(geometry, simplify) {
5561
- if (geometry.index) {
5562
- console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
5563
- const nonIndexedGeo = geometry.toNonIndexed();
5564
- geometry.copy(nonIndexedGeo);
5565
- geometry.setIndex(null);
5566
- }
5567
- const newBarycentric = getBarycentricCoordinates(geometry, simplify);
5568
- geometry.setAttribute("barycentric", newBarycentric);
5569
- }
5570
- function WireframeWithCustomGeo({
5571
- geometry: customGeometry,
5572
- simplify = false,
5573
- ...props
5574
- }) {
5575
- extend({ MeshWireframeMaterial: WireframeMaterial });
5576
- const [geometry, setGeometry] = React.useState(null);
5577
- React.useLayoutEffect(() => {
5578
- const geom = getInputGeometry(customGeometry);
5579
- if (!geom) {
5580
- throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
5581
- }
5582
- setBarycentricCoordinates(geom, simplify);
5583
- if (isRef$1(customGeometry)) {
5584
- setGeometry(geom);
5585
- }
5586
- }, [simplify, customGeometry]);
5587
- const drawnGeo = isRef$1(customGeometry) ? geometry : customGeometry;
5588
- return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
5589
- "meshWireframeMaterial",
5590
- {
5591
- attach: "material",
5592
- transparent: true,
5593
- side: THREE.DoubleSide,
5594
- polygonOffset: true,
5595
- polygonOffsetFactor: -4,
5596
- ...props
5597
- }
5598
- )));
5599
- }
5600
- function WireframeWithoutCustomGeo({
5601
- simplify = false,
5602
- ...props
5603
- }) {
5604
- const objectRef = React.useRef(null);
5605
- const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
5606
- useWireframeUniforms(uniforms, props);
5607
- React.useLayoutEffect(() => {
5608
- const geom = getInputGeometry(objectRef);
5609
- if (!geom) {
5610
- throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
5611
- }
5612
- const og = geom.clone();
5613
- setBarycentricCoordinates(geom, simplify);
5614
- return () => {
5615
- geom.copy(og);
5616
- og.dispose();
5617
- };
5618
- }, [simplify]);
5619
- React.useLayoutEffect(() => {
5620
- const parentMesh = objectRef.current.parent;
5621
- const og = parentMesh.material.clone();
5622
- setWireframeOverride(parentMesh.material, uniforms);
5623
- return () => {
5624
- parentMesh.material.dispose();
5625
- parentMesh.material = og;
5626
- };
5627
- }, []);
5628
- return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
5629
- }
5630
- function Wireframe({ geometry: customGeometry, ...props }) {
5631
- if (customGeometry) {
5632
- return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
5633
- }
5634
- return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
5635
- }
5636
-
5637
- const Context = /* @__PURE__ */ React.createContext({});
5638
- const useGizmoContext = () => {
5639
- return React.useContext(Context);
5640
- };
5641
- const turnRate = 2 * Math.PI;
5642
- const dummy = /* @__PURE__ */ new Object3D();
5643
- const matrix = /* @__PURE__ */ new Matrix4();
5644
- const [q1, q2] = [/* @__PURE__ */ new Quaternion(), /* @__PURE__ */ new Quaternion()];
5645
- const target = /* @__PURE__ */ new Vector3();
5646
- const targetPosition = /* @__PURE__ */ new Vector3();
5647
- const isOrbitControls = (controls) => {
5648
- return "minPolarAngle" in controls;
5649
- };
5650
- const isCameraControls = (controls) => {
5651
- return "getTarget" in controls;
5652
- };
5653
- const GizmoHelper = ({
5654
- alignment = "bottom-right",
5655
- margin = [80, 80],
5656
- renderPriority = 1,
5657
- onUpdate,
5658
- onTarget,
5659
- children
5660
- }) => {
5661
- const size = useThree((state) => state.size);
5662
- const mainCamera = useThree((state) => state.camera);
5663
- const defaultControls = useThree((state) => state.controls);
5664
- const invalidate = useThree((state) => state.invalidate);
5665
- const gizmoRef = React.useRef(null);
5666
- const virtualCam = React.useRef(null);
5667
- const animating = React.useRef(false);
5668
- const radius = React.useRef(0);
5669
- const focusPoint = React.useRef(new Vector3(0, 0, 0));
5670
- const defaultUp = React.useRef(new Vector3(0, 0, 0));
5671
- React.useEffect(() => {
5672
- defaultUp.current.copy(mainCamera.up);
5673
- dummy.up.copy(mainCamera.up);
5674
- }, [mainCamera]);
5675
- const tweenCamera = React.useCallback(
5676
- (direction) => {
5677
- animating.current = true;
5678
- if (defaultControls || onTarget) {
5679
- focusPoint.current = onTarget?.() || (isCameraControls(defaultControls) ? defaultControls.getTarget(focusPoint.current) : defaultControls?.target);
5680
- }
5681
- radius.current = mainCamera.position.distanceTo(target);
5682
- q1.copy(mainCamera.quaternion);
5683
- targetPosition.copy(direction).multiplyScalar(radius.current).add(target);
5684
- dummy.lookAt(targetPosition);
5685
- q2.copy(dummy.quaternion);
5686
- invalidate();
5687
- },
5688
- [defaultControls, mainCamera, onTarget, invalidate]
5689
- );
5690
- useFrame((_, delta) => {
5691
- if (virtualCam.current && gizmoRef.current) {
5692
- if (animating.current) {
5693
- if (q1.angleTo(q2) < 0.01) {
5694
- animating.current = false;
5695
- if (isOrbitControls(defaultControls)) {
5696
- mainCamera.up.copy(defaultUp.current);
5697
- }
5698
- } else {
5699
- const step = delta * turnRate;
5700
- q1.rotateTowards(q2, step);
5701
- mainCamera.position.set(0, 0, 1).applyQuaternion(q1).multiplyScalar(radius.current).add(focusPoint.current);
5702
- mainCamera.up.set(0, 1, 0).applyQuaternion(q1).normalize();
5703
- mainCamera.quaternion.copy(q1);
5704
- if (isCameraControls(defaultControls))
5705
- defaultControls.setPosition(mainCamera.position.x, mainCamera.position.y, mainCamera.position.z);
5706
- if (onUpdate) onUpdate();
5707
- else if (defaultControls) defaultControls.update(delta);
5708
- invalidate();
5709
- }
5710
- }
5711
- matrix.copy(mainCamera.matrix).invert();
5712
- gizmoRef.current?.quaternion.setFromRotationMatrix(matrix);
5713
- }
5714
- });
5715
- const gizmoHelperContext = React.useMemo(() => ({ tweenCamera }), [tweenCamera]);
5716
- const [marginX, marginY] = margin;
5717
- const x = alignment.endsWith("-center") ? 0 : alignment.endsWith("-left") ? -size.width / 2 + marginX : size.width / 2 - marginX;
5718
- const y = alignment.startsWith("center-") ? 0 : alignment.startsWith("top-") ? size.height / 2 - marginY : -size.height / 2 + marginY;
5719
- return /* @__PURE__ */ React.createElement(Hud$1, { renderPriority }, /* @__PURE__ */ React.createElement(Context.Provider, { value: gizmoHelperContext }, /* @__PURE__ */ React.createElement(OrthographicCamera, { makeDefault: true, ref: virtualCam, position: [0, 0, 200] }), /* @__PURE__ */ React.createElement("group", { ref: gizmoRef, position: [x, y, 0] }, children)));
5382
+ const Context = /* @__PURE__ */ React.createContext({});
5383
+ const useGizmoContext = () => {
5384
+ return React.useContext(Context);
5385
+ };
5386
+ const turnRate = 2 * Math.PI;
5387
+ const dummy = /* @__PURE__ */ new Object3D();
5388
+ const matrix = /* @__PURE__ */ new Matrix4();
5389
+ const [q1, q2] = [/* @__PURE__ */ new Quaternion(), /* @__PURE__ */ new Quaternion()];
5390
+ const target = /* @__PURE__ */ new Vector3();
5391
+ const targetPosition = /* @__PURE__ */ new Vector3();
5392
+ const isOrbitControls = (controls) => {
5393
+ return "minPolarAngle" in controls;
5394
+ };
5395
+ const isCameraControls = (controls) => {
5396
+ return "getTarget" in controls;
5397
+ };
5398
+ const GizmoHelper = ({
5399
+ alignment = "bottom-right",
5400
+ margin = [80, 80],
5401
+ renderPriority = 1,
5402
+ onUpdate,
5403
+ onTarget,
5404
+ children
5405
+ }) => {
5406
+ const size = useThree((state) => state.size);
5407
+ const mainCamera = useThree((state) => state.camera);
5408
+ const defaultControls = useThree((state) => state.controls);
5409
+ const invalidate = useThree((state) => state.invalidate);
5410
+ const gizmoRef = React.useRef(null);
5411
+ const virtualCam = React.useRef(null);
5412
+ const animating = React.useRef(false);
5413
+ const radius = React.useRef(0);
5414
+ const focusPoint = React.useRef(new Vector3(0, 0, 0));
5415
+ const defaultUp = React.useRef(new Vector3(0, 0, 0));
5416
+ React.useEffect(() => {
5417
+ defaultUp.current.copy(mainCamera.up);
5418
+ dummy.up.copy(mainCamera.up);
5419
+ }, [mainCamera]);
5420
+ const tweenCamera = React.useCallback(
5421
+ (direction) => {
5422
+ animating.current = true;
5423
+ if (defaultControls || onTarget) {
5424
+ focusPoint.current = onTarget?.() || (isCameraControls(defaultControls) ? defaultControls.getTarget(focusPoint.current) : defaultControls?.target);
5425
+ }
5426
+ radius.current = mainCamera.position.distanceTo(target);
5427
+ q1.copy(mainCamera.quaternion);
5428
+ targetPosition.copy(direction).multiplyScalar(radius.current).add(target);
5429
+ dummy.lookAt(targetPosition);
5430
+ q2.copy(dummy.quaternion);
5431
+ invalidate();
5432
+ },
5433
+ [defaultControls, mainCamera, onTarget, invalidate]
5434
+ );
5435
+ useFrame((_, delta) => {
5436
+ if (virtualCam.current && gizmoRef.current) {
5437
+ if (animating.current) {
5438
+ if (q1.angleTo(q2) < 0.01) {
5439
+ animating.current = false;
5440
+ if (isOrbitControls(defaultControls)) {
5441
+ mainCamera.up.copy(defaultUp.current);
5442
+ }
5443
+ } else {
5444
+ const step = delta * turnRate;
5445
+ q1.rotateTowards(q2, step);
5446
+ mainCamera.position.set(0, 0, 1).applyQuaternion(q1).multiplyScalar(radius.current).add(focusPoint.current);
5447
+ mainCamera.up.set(0, 1, 0).applyQuaternion(q1).normalize();
5448
+ mainCamera.quaternion.copy(q1);
5449
+ if (isCameraControls(defaultControls))
5450
+ defaultControls.setPosition(mainCamera.position.x, mainCamera.position.y, mainCamera.position.z);
5451
+ if (onUpdate) onUpdate();
5452
+ else if (defaultControls) defaultControls.update(delta);
5453
+ invalidate();
5454
+ }
5455
+ }
5456
+ matrix.copy(mainCamera.matrix).invert();
5457
+ gizmoRef.current?.quaternion.setFromRotationMatrix(matrix);
5458
+ }
5459
+ });
5460
+ const gizmoHelperContext = React.useMemo(() => ({ tweenCamera }), [tweenCamera]);
5461
+ const [marginX, marginY] = margin;
5462
+ const x = alignment.endsWith("-center") ? 0 : alignment.endsWith("-left") ? -size.width / 2 + marginX : size.width / 2 - marginX;
5463
+ const y = alignment.startsWith("center-") ? 0 : alignment.startsWith("top-") ? size.height / 2 - marginY : -size.height / 2 + marginY;
5464
+ return /* @__PURE__ */ React.createElement(Hud$1, { renderPriority }, /* @__PURE__ */ React.createElement(Context.Provider, { value: gizmoHelperContext }, /* @__PURE__ */ React.createElement(OrthographicCamera, { makeDefault: true, ref: virtualCam, position: [0, 0, 200] }), /* @__PURE__ */ React.createElement("group", { ref: gizmoRef, position: [x, y, 0] }, children)));
5720
5465
  };
5721
5466
 
5722
5467
  const colors = { bg: "#f0f0f0", hover: "#999", text: "black", stroke: "black" };
@@ -5929,6 +5674,11 @@ const GizmoViewport = ({
5929
5674
  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 }))));
5930
5675
  };
5931
5676
 
5677
+ const roundEven = (n) => {
5678
+ const rounded = Math.ceil(n);
5679
+ return rounded % 2 === 0 ? rounded : rounded + 1;
5680
+ };
5681
+
5932
5682
  const v1 = /* @__PURE__ */ new Vector3();
5933
5683
  const v2 = /* @__PURE__ */ new Vector3();
5934
5684
  const v3 = /* @__PURE__ */ new Vector3();
@@ -6000,7 +5750,7 @@ const getCameraCSSMatrix = /* @__PURE__ */ ((multipliers) => {
6000
5750
  const getObjectCSSMatrix = /* @__PURE__ */ ((scaleMultipliers) => {
6001
5751
  return (matrix, factor) => getCSSMatrix(matrix, scaleMultipliers(factor), "translate(-50%,-50%)");
6002
5752
  })((f) => [1 / f, 1 / f, 1 / f, 1, -1 / f, -1 / f, -1 / f, -1, 1 / f, 1 / f, 1 / f, 1, 1, 1, 1, 1]);
6003
- function isRefObject(ref) {
5753
+ function isRefObject$1(ref) {
6004
5754
  return ref && typeof ref === "object" && "current" in ref;
6005
5755
  }
6006
5756
  const Html = /* @__PURE__ */ React.forwardRef(
@@ -6027,9 +5777,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
6027
5777
  as = "div",
6028
5778
  wrapperClass,
6029
5779
  pointerEvents = "auto",
5780
+ pixelPerfect = true,
6030
5781
  ...props
6031
5782
  }, ref) => {
6032
5783
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
5784
+ const forceEven = useThree((state) => state.internal.forceEven);
6033
5785
  const [el] = React.useState(() => document.createElement(as));
6034
5786
  const root = React.useRef(null);
6035
5787
  const group = React.useRef(null);
@@ -6041,7 +5793,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
6041
5793
  const occlusionMeshRef = React.useRef(null);
6042
5794
  const isMeshSizeSet = React.useRef(false);
6043
5795
  const isRayCastOcclusion = React.useMemo(() => {
6044
- return occlude && occlude !== "blending" || Array.isArray(occlude) && occlude.length && isRefObject(occlude[0]);
5796
+ return occlude && occlude !== "blending" || Array.isArray(occlude) && occlude.length && isRefObject$1(occlude[0]);
6045
5797
  }, [occlude]);
6046
5798
  React.useLayoutEffect(() => {
6047
5799
  const el2 = gl.domElement;
@@ -6054,38 +5806,54 @@ const Html = /* @__PURE__ */ React.forwardRef(
6054
5806
  el2.style.position = null;
6055
5807
  el2.style.pointerEvents = null;
6056
5808
  }
6057
- }, [occlude]);
5809
+ }, [occlude, zIndexRange, gl.domElement]);
6058
5810
  React.useLayoutEffect(() => {
6059
5811
  if (group.current) {
6060
- const currentRoot = root.current = ReactDOM.createRoot(el);
5812
+ if (!root.current) {
5813
+ root.current = ReactDOM.createRoot(el);
5814
+ }
6061
5815
  scene.updateMatrixWorld();
6062
5816
  if (transform) {
6063
5817
  el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
6064
5818
  } else {
6065
- const vec = calculatePosition(group.current, camera, size);
6066
- el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(${vec[0]}px,${vec[1]}px,0);transform-origin:0 0;`;
5819
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
6067
5820
  }
6068
5821
  if (target) {
6069
5822
  if (prepend) target.prepend(el);
6070
5823
  else target.appendChild(el);
6071
5824
  }
6072
5825
  return () => {
6073
- if (target) target.removeChild(el);
6074
- currentRoot.unmount();
5826
+ if (root.current) {
5827
+ root.current.unmount();
5828
+ root.current = null;
5829
+ }
5830
+ if (target && target.contains(el)) {
5831
+ target.removeChild(el);
5832
+ }
6075
5833
  };
6076
5834
  }
6077
- }, [target, transform]);
5835
+ }, [target, transform, el, prepend, scene]);
6078
5836
  React.useLayoutEffect(() => {
6079
5837
  if (wrapperClass) el.className = wrapperClass;
6080
- }, [wrapperClass]);
5838
+ }, [wrapperClass, el]);
5839
+ React.useLayoutEffect(() => {
5840
+ return () => {
5841
+ if (root.current) {
5842
+ root.current.unmount();
5843
+ root.current = null;
5844
+ }
5845
+ };
5846
+ }, []);
6081
5847
  const styles = React.useMemo(() => {
5848
+ const width = forceEven ? roundEven(size.width) : size.width;
5849
+ const height = forceEven ? roundEven(size.height) : size.height;
6082
5850
  if (transform) {
6083
5851
  return {
6084
5852
  position: "absolute",
6085
5853
  top: 0,
6086
5854
  left: 0,
6087
- width: size.width,
6088
- height: size.height,
5855
+ width,
5856
+ height,
6089
5857
  transformStyle: "preserve-3d",
6090
5858
  pointerEvents: "none"
6091
5859
  };
@@ -6094,15 +5862,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
6094
5862
  position: "absolute",
6095
5863
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
6096
5864
  ...fullscreen && {
6097
- top: -size.height / 2,
6098
- left: -size.width / 2,
6099
- width: size.width,
6100
- height: size.height
5865
+ top: forceEven ? roundEven(-size.height / 2) : -size.height / 2,
5866
+ left: forceEven ? roundEven(-size.width / 2) : -size.width / 2,
5867
+ width,
5868
+ height
6101
5869
  },
6102
5870
  ...style
6103
5871
  };
6104
5872
  }
6105
- }, [style, center, fullscreen, size, transform]);
5873
+ }, [style, center, fullscreen, size, transform, forceEven]);
6106
5874
  const transformInnerStyles = React.useMemo(
6107
5875
  () => ({ position: "absolute", pointerEvents }),
6108
5876
  [pointerEvents]
@@ -6116,7 +5884,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
6116
5884
  } else {
6117
5885
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
6118
5886
  }
6119
- });
5887
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
6120
5888
  const visible = React.useRef(true);
6121
5889
  useFrame((gl2) => {
6122
5890
  if (group.current) {
@@ -6148,7 +5916,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
6148
5916
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
6149
5917
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
6150
5918
  if (transform) {
6151
- const [widthHalf, heightHalf] = [size.width / 2, size.height / 2];
5919
+ let widthHalf = forceEven ? roundEven(size.width / 2) : size.width / 2;
5920
+ let heightHalf = forceEven ? roundEven(size.height / 2) : size.height / 2;
5921
+ if (pixelPerfect) {
5922
+ widthHalf = roundToPixelRatio(widthHalf);
5923
+ heightHalf = roundToPixelRatio(heightHalf);
5924
+ }
6152
5925
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
6153
5926
  const { isOrthographicCamera, top, left, bottom, right } = camera;
6154
5927
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -6159,8 +5932,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
6159
5932
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
6160
5933
  matrix.elements[15] = 1;
6161
5934
  }
6162
- el.style.width = size.width + "px";
6163
- el.style.height = size.height + "px";
5935
+ const elWidth = forceEven ? roundEven(size.width) : size.width;
5936
+ const elHeight = forceEven ? roundEven(size.height) : size.height;
5937
+ el.style.width = elWidth + "px";
5938
+ el.style.height = elHeight + "px";
6164
5939
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
6165
5940
  if (transformOuterRef.current && transformInnerRef.current) {
6166
5941
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -6168,7 +5943,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
6168
5943
  }
6169
5944
  } else {
6170
5945
  const scale = distanceFactor === void 0 ? 1 : objectScale(group.current, camera) * distanceFactor;
6171
- el.style.transform = `translate3d(${vec[0]}px,${vec[1]}px,0) scale(${scale})`;
5946
+ let x = forceEven ? roundEven(vec[0]) : vec[0];
5947
+ let y = forceEven ? roundEven(vec[1]) : vec[1];
5948
+ if (pixelPerfect) {
5949
+ x = roundToPixelRatio(x);
5950
+ y = roundToPixelRatio(y);
5951
+ }
5952
+ el.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`;
6172
5953
  }
6173
5954
  oldPosition.current = vec;
6174
5955
  oldZoom.current = camera.zoom;
@@ -6274,6 +6055,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
6274
6055
  );
6275
6056
 
6276
6057
  const context$4 = /* @__PURE__ */ React.createContext(null);
6058
+ const resolveObject = (object, fallback) => {
6059
+ if (!object) return fallback;
6060
+ if ("current" in object) return object.current;
6061
+ return object;
6062
+ };
6277
6063
 
6278
6064
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
6279
6065
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -6308,6 +6094,8 @@ const AxisArrow = ({ direction, axis }) => {
6308
6094
  onDragStart,
6309
6095
  onDrag,
6310
6096
  onDragEnd,
6097
+ onHover,
6098
+ dragState,
6311
6099
  userData,
6312
6100
  LineComponent: Line
6313
6101
  } = React.useContext(context$4);
@@ -6339,7 +6127,13 @@ const AxisArrow = ({ direction, axis }) => {
6339
6127
  const onPointerMove = React.useCallback(
6340
6128
  (e) => {
6341
6129
  e.stopPropagation();
6342
- if (!isHovered) setIsHovered(true);
6130
+ if (!isHovered) {
6131
+ const drag = dragState.current;
6132
+ if (drag === null || drag.component === "Arrow" && drag.axis === axis) {
6133
+ setIsHovered(true);
6134
+ onHover({ component: "Arrow", axis, hovering: true });
6135
+ }
6136
+ }
6343
6137
  if (clickInfo.current) {
6344
6138
  const { clickPoint, dir } = clickInfo.current;
6345
6139
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -6358,7 +6152,7 @@ const AxisArrow = ({ direction, axis }) => {
6358
6152
  onDrag(offsetMatrix$1);
6359
6153
  }
6360
6154
  },
6361
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
6155
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
6362
6156
  );
6363
6157
  const onPointerUp = React.useCallback(
6364
6158
  (e) => {
@@ -6373,10 +6167,16 @@ const AxisArrow = ({ direction, axis }) => {
6373
6167
  },
6374
6168
  [annotations, camControls, onDragEnd]
6375
6169
  );
6376
- const onPointerOut = React.useCallback((e) => {
6377
- e.stopPropagation();
6378
- setIsHovered(false);
6379
- }, []);
6170
+ const onPointerOut = React.useCallback(
6171
+ (e) => {
6172
+ e.stopPropagation();
6173
+ if (isHovered) {
6174
+ setIsHovered(false);
6175
+ onHover({ component: "Arrow", axis, hovering: false });
6176
+ }
6177
+ },
6178
+ [onHover, axis, isHovered]
6179
+ );
6380
6180
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
6381
6181
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
6382
6182
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -6480,6 +6280,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
6480
6280
  const posNew = /* @__PURE__ */ new THREE.Vector3();
6481
6281
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
6482
6282
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
6283
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
6284
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
6483
6285
  const AxisRotator = ({
6484
6286
  dir1,
6485
6287
  dir2,
@@ -6500,6 +6302,8 @@ const AxisRotator = ({
6500
6302
  onDragStart,
6501
6303
  onDrag,
6502
6304
  onDragEnd,
6305
+ onHover,
6306
+ dragState,
6503
6307
  userData,
6504
6308
  LineComponent: Line
6505
6309
  } = React.useContext(context$4);
@@ -6533,14 +6337,29 @@ const AxisRotator = ({
6533
6337
  const onPointerMove = React.useCallback(
6534
6338
  (e) => {
6535
6339
  e.stopPropagation();
6536
- if (!isHovered) setIsHovered(true);
6340
+ if (!isHovered) {
6341
+ const drag = dragState.current;
6342
+ if (drag === null || drag.component === "Rotator" && drag.axis === axis) {
6343
+ setIsHovered(true);
6344
+ onHover({ component: "Rotator", axis, hovering: true });
6345
+ }
6346
+ }
6537
6347
  if (clickInfo.current) {
6538
6348
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
6539
6349
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
6540
6350
  ray$1.copy(e.ray);
6541
- ray$1.intersectPlane(plane, intersection$1);
6542
- ray$1.direction.negate();
6543
- ray$1.intersectPlane(plane, intersection$1);
6351
+ const dotProduct = Math.abs(ray$1.direction.dot(normal));
6352
+ if (dotProduct < 0.1) {
6353
+ viewPlane.setFromNormalAndCoplanarPoint(ray$1.direction.clone().negate(), origin);
6354
+ ray$1.intersectPlane(viewPlane, viewIntersection);
6355
+ const toIntersection = viewIntersection.clone().sub(origin);
6356
+ const projectedDist = toIntersection.dot(normal);
6357
+ intersection$1.copy(viewIntersection).sub(normal.clone().multiplyScalar(projectedDist));
6358
+ } else {
6359
+ ray$1.intersectPlane(plane, intersection$1);
6360
+ ray$1.direction.negate();
6361
+ ray$1.intersectPlane(plane, intersection$1);
6362
+ }
6544
6363
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
6545
6364
  let degrees = toDegrees(deltaAngle);
6546
6365
  if (e.shiftKey) {
@@ -6566,7 +6385,7 @@ const AxisRotator = ({
6566
6385
  onDrag(rotMatrix);
6567
6386
  }
6568
6387
  },
6569
- [annotations, onDrag, isHovered, rotationLimits, axis]
6388
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
6570
6389
  );
6571
6390
  const onPointerUp = React.useCallback(
6572
6391
  (e) => {
@@ -6582,10 +6401,16 @@ const AxisRotator = ({
6582
6401
  },
6583
6402
  [annotations, camControls, onDragEnd]
6584
6403
  );
6585
- const onPointerOut = React.useCallback((e) => {
6586
- e.stopPropagation();
6587
- setIsHovered(false);
6588
- }, []);
6404
+ const onPointerOut = React.useCallback(
6405
+ (e) => {
6406
+ e.stopPropagation();
6407
+ if (isHovered) {
6408
+ setIsHovered(false);
6409
+ onHover({ component: "Rotator", axis, hovering: false });
6410
+ }
6411
+ },
6412
+ [onHover, axis, isHovered]
6413
+ );
6589
6414
  const matrixL = React.useMemo(() => {
6590
6415
  const dir1N = dir1.clone().normalize();
6591
6416
  const dir2N = dir2.clone().normalize();
@@ -6686,6 +6511,8 @@ const PlaneSlider = ({
6686
6511
  onDragStart,
6687
6512
  onDrag,
6688
6513
  onDragEnd,
6514
+ onHover,
6515
+ dragState,
6689
6516
  userData,
6690
6517
  LineComponent: Line
6691
6518
  } = React.useContext(context$4);
@@ -6721,7 +6548,13 @@ const PlaneSlider = ({
6721
6548
  const onPointerMove = React.useCallback(
6722
6549
  (e) => {
6723
6550
  e.stopPropagation();
6724
- if (!isHovered) setIsHovered(true);
6551
+ if (!isHovered) {
6552
+ const drag = dragState.current;
6553
+ if (drag === null || drag.component === "Slider" && drag.axis === axis) {
6554
+ setIsHovered(true);
6555
+ onHover({ component: "Slider", axis, hovering: true });
6556
+ }
6557
+ }
6725
6558
  if (clickInfo.current) {
6726
6559
  const { clickPoint, e1, e2, plane } = clickInfo.current;
6727
6560
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -6757,7 +6590,7 @@ const PlaneSlider = ({
6757
6590
  onDrag(offsetMatrix);
6758
6591
  }
6759
6592
  },
6760
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
6593
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
6761
6594
  );
6762
6595
  const onPointerUp = React.useCallback(
6763
6596
  (e) => {
@@ -6772,10 +6605,16 @@ const PlaneSlider = ({
6772
6605
  },
6773
6606
  [annotations, camControls, onDragEnd]
6774
6607
  );
6775
- const onPointerOut = React.useCallback((e) => {
6776
- e.stopPropagation();
6777
- setIsHovered(false);
6778
- }, []);
6608
+ const onPointerOut = React.useCallback(
6609
+ (e) => {
6610
+ e.stopPropagation();
6611
+ if (isHovered) {
6612
+ setIsHovered(false);
6613
+ onHover({ component: "Slider", axis, hovering: false });
6614
+ }
6615
+ },
6616
+ [onHover, axis, isHovered]
6617
+ );
6779
6618
  const matrixL = React.useMemo(() => {
6780
6619
  const dir1N = dir1.clone().normalize();
6781
6620
  const dir2N = dir2.clone().normalize();
@@ -6913,6 +6752,8 @@ const ScalingSphere = ({ direction, axis }) => {
6913
6752
  onDragStart,
6914
6753
  onDrag,
6915
6754
  onDragEnd,
6755
+ onHover,
6756
+ dragState,
6916
6757
  userData
6917
6758
  } = React.useContext(context$4);
6918
6759
  const size = useThree((state) => state.size);
@@ -6949,7 +6790,13 @@ const ScalingSphere = ({ direction, axis }) => {
6949
6790
  const onPointerMove = React.useCallback(
6950
6791
  (e) => {
6951
6792
  e.stopPropagation();
6952
- if (!isHovered) setIsHovered(true);
6793
+ if (!isHovered) {
6794
+ const drag = dragState.current;
6795
+ if (drag === null || drag.component === "Sphere" && drag.axis === axis) {
6796
+ setIsHovered(true);
6797
+ onHover({ component: "Sphere", axis, hovering: true });
6798
+ }
6799
+ }
6953
6800
  if (clickInfo.current) {
6954
6801
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
6955
6802
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -6975,7 +6822,7 @@ const ScalingSphere = ({ direction, axis }) => {
6975
6822
  onDrag(scaleMatrix);
6976
6823
  }
6977
6824
  },
6978
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
6825
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
6979
6826
  );
6980
6827
  const onPointerUp = React.useCallback(
6981
6828
  (e) => {
@@ -6992,10 +6839,16 @@ const ScalingSphere = ({ direction, axis }) => {
6992
6839
  },
6993
6840
  [annotations, camControls, onDragEnd, position]
6994
6841
  );
6995
- const onPointerOut = React.useCallback((e) => {
6996
- e.stopPropagation();
6997
- setIsHovered(false);
6998
- }, []);
6842
+ const onPointerOut = React.useCallback(
6843
+ (e) => {
6844
+ e.stopPropagation();
6845
+ if (isHovered) {
6846
+ setIsHovered(false);
6847
+ onHover({ component: "Sphere", axis, hovering: false });
6848
+ }
6849
+ },
6850
+ [onHover, axis, isHovered]
6851
+ );
6999
6852
  const { radius, matrixL } = React.useMemo(() => {
7000
6853
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
7001
6854
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -7065,9 +6918,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7065
6918
  ({
7066
6919
  enabled = true,
7067
6920
  matrix,
6921
+ object,
7068
6922
  onDragStart,
7069
6923
  onDrag,
7070
6924
  onDragEnd,
6925
+ onHover,
7071
6926
  autoTransform = true,
7072
6927
  anchor,
7073
6928
  disableAxes = false,
@@ -7103,14 +6958,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7103
6958
  const gizmoRef = React.useRef(null);
7104
6959
  const childrenRef = React.useRef(null);
7105
6960
  const translation = React.useRef([0, 0, 0]);
6961
+ const dragState = React.useRef(null);
7106
6962
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
7107
6963
  const gizmoScale = React.useRef(new THREE.Vector3(1, 1, 1));
6964
+ React.useEffect(() => {
6965
+ if (object) {
6966
+ const target = resolveObject(object);
6967
+ if (target) {
6968
+ const pivot = ref.current;
6969
+ const didAutoUpdate = target.matrixAutoUpdate;
6970
+ target.updateWorldMatrix(true, true);
6971
+ target.matrixAutoUpdate = false;
6972
+ pivot.matrix.copy(target.matrix);
6973
+ return () => {
6974
+ if (didAutoUpdate) {
6975
+ target.matrixAutoUpdate = true;
6976
+ target.matrix.decompose(target.position, target.quaternion, target.scale);
6977
+ }
6978
+ };
6979
+ }
6980
+ }
6981
+ }, [object]);
7108
6982
  React.useLayoutEffect(() => {
7109
6983
  if (!anchor) return;
7110
- childrenRef.current.updateWorldMatrix(true, true);
7111
- mPInv.copy(childrenRef.current.matrixWorld).invert();
6984
+ const anchorTarget = resolveObject(object, childrenRef.current);
6985
+ if (!anchorTarget) return;
6986
+ anchorTarget.updateWorldMatrix(true, true);
6987
+ mPInv.copy(anchorTarget.matrixWorld).invert();
7112
6988
  bb.makeEmpty();
7113
- childrenRef.current.traverse((obj) => {
6989
+ anchorTarget.traverse((obj) => {
7114
6990
  if (!obj.geometry) return;
7115
6991
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
7116
6992
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -7125,9 +7001,17 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7125
7001
  gizmoRef.current.position.copy(vPosition);
7126
7002
  invalidate();
7127
7003
  });
7004
+ const mergedUserData = React.useMemo(
7005
+ () => ({
7006
+ ...userData,
7007
+ ...depthTest === false && { interactivePriority: 1 }
7008
+ }),
7009
+ [userData, depthTest]
7010
+ );
7128
7011
  const config = React.useMemo(
7129
7012
  () => ({
7130
7013
  onDragStart: (props2) => {
7014
+ dragState.current = props2;
7131
7015
  mL0.copy(ref.current.matrix);
7132
7016
  mW0.copy(ref.current.matrixWorld);
7133
7017
  onDragStart && onDragStart(props2);
@@ -7143,13 +7027,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7143
7027
  if (autoTransform) {
7144
7028
  ref.current.matrix.copy(mL);
7145
7029
  }
7030
+ const target = resolveObject(object);
7031
+ if (target) target.matrix.copy(mL);
7146
7032
  onDrag && onDrag(mL, mdL, mW, mdW);
7147
7033
  invalidate();
7148
7034
  },
7149
7035
  onDragEnd: () => {
7036
+ dragState.current = null;
7150
7037
  if (onDragEnd) onDragEnd();
7151
7038
  invalidate();
7152
7039
  },
7040
+ onHover: (props2) => {
7041
+ if (onHover) onHover(props2);
7042
+ },
7043
+ dragState,
7153
7044
  translation,
7154
7045
  translationLimits,
7155
7046
  rotationLimits,
@@ -7161,7 +7052,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7161
7052
  fixed,
7162
7053
  depthTest,
7163
7054
  renderOrder,
7164
- userData,
7055
+ userData: mergedUserData,
7165
7056
  annotations,
7166
7057
  annotationsClass,
7167
7058
  LineComponent: LineComp
@@ -7170,6 +7061,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7170
7061
  onDragStart,
7171
7062
  onDrag,
7172
7063
  onDragEnd,
7064
+ onHover,
7173
7065
  translation,
7174
7066
  translationLimits,
7175
7067
  rotationLimits,
@@ -7181,11 +7073,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7181
7073
  ...axisColors,
7182
7074
  hoveredColor,
7183
7075
  opacity,
7184
- userData,
7076
+ mergedUserData,
7185
7077
  autoTransform,
7186
7078
  annotations,
7187
7079
  annotationsClass,
7188
- LineComp
7080
+ LineComp,
7081
+ object
7189
7082
  ]
7190
7083
  );
7191
7084
  const vec = new THREE.Vector3();
@@ -7547,7 +7440,7 @@ function createInstances() {
7547
7440
  ];
7548
7441
  }
7549
7442
  const InstancedAttribute = React.forwardRef(
7550
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
7443
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
7551
7444
  const ref = React.useRef(null);
7552
7445
  React.useImperativeHandle(fref, () => ref.current, []);
7553
7446
  React.useLayoutEffect(() => {
@@ -7555,7 +7448,26 @@ const InstancedAttribute = React.forwardRef(
7555
7448
  parent.geometry.attributes[name] = ref.current;
7556
7449
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
7557
7450
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
7558
- ref.current.array = new Float32Array(array);
7451
+ switch (type) {
7452
+ case "float":
7453
+ ref.current.array = new Float32Array(array);
7454
+ break;
7455
+ case "int":
7456
+ ref.current.array = new Int32Array(array);
7457
+ break;
7458
+ case "uint":
7459
+ ref.current.array = new Uint32Array(array);
7460
+ break;
7461
+ case "short":
7462
+ ref.current.array = new Int16Array(array);
7463
+ break;
7464
+ case "ushort":
7465
+ ref.current.array = new Uint16Array(array);
7466
+ break;
7467
+ case "byte":
7468
+ ref.current.array = new Uint8Array(array);
7469
+ break;
7470
+ }
7559
7471
  ref.current.itemSize = value.length;
7560
7472
  ref.current.count = array.length / ref.current.itemSize;
7561
7473
  return () => {
@@ -9080,12 +8992,17 @@ const Gltf = /* @__PURE__ */ React.forwardRef(
9080
8992
  }
9081
8993
  );
9082
8994
 
9083
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
9084
- function useKTX2(input, basisPath = `${cdn}/basis/`) {
8995
+ let transcoderPath = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
8996
+ function useKTX2(input, basisPath = true, extendLoader) {
9085
8997
  const renderer = useThree((state) => state.renderer);
9086
8998
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
8999
+ if (extendLoader) {
9000
+ extendLoader(loader);
9001
+ }
9002
+ if (basisPath) {
9003
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
9004
+ }
9087
9005
  loader.detectSupport(renderer);
9088
- loader.setTranscoderPath(basisPath);
9089
9006
  });
9090
9007
  useEffect(() => {
9091
9008
  const array = Array.isArray(textures) ? textures : [textures];
@@ -9100,16 +9017,20 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
9100
9017
  return textures;
9101
9018
  }
9102
9019
  }
9103
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
9104
- loader.setTranscoderPath(basisPath);
9020
+ useKTX2.preload = (url, basisPath = true, extendLoader) => useLoader.preload(KTX2Loader, url, (loader) => {
9021
+ if (extendLoader) {
9022
+ extendLoader(loader);
9023
+ }
9024
+ if (basisPath) {
9025
+ loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
9026
+ }
9105
9027
  });
9106
9028
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
9107
- const Ktx2 = ({
9108
- children,
9109
- input,
9110
- basisPath
9111
- }) => {
9112
- const texture = useKTX2(input, basisPath);
9029
+ useKTX2.setTranscoderPath = (path) => {
9030
+ transcoderPath = path;
9031
+ };
9032
+ const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
9033
+ const texture = useKTX2(input, basisPath, extendLoader);
9113
9034
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
9114
9035
  };
9115
9036
 
@@ -9585,18 +9506,21 @@ function Fisheye({
9585
9506
  const normal = new THREE.Vector3();
9586
9507
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
9587
9508
  const normalMatrix = new THREE.Matrix3();
9588
- const compute = React.useCallback((event, state, prev) => {
9589
- state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
9590
- state.raycaster.setFromCamera(state.pointer, orthoC);
9591
- if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
9592
- else normal.normalize();
9593
- normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
9594
- cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
9595
- state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
9596
- state.raycaster.ray.direction.x *= -1;
9597
- state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
9598
- return void 0;
9599
- }, []);
9509
+ const compute = React.useCallback(
9510
+ (event, state, prev) => {
9511
+ state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
9512
+ state.raycaster.setFromCamera(state.pointer, orthoC);
9513
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
9514
+ else normal.normalize();
9515
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
9516
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
9517
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
9518
+ state.raycaster.ray.direction.x *= -1;
9519
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
9520
+ return void 0;
9521
+ },
9522
+ [width, height]
9523
+ );
9600
9524
  useFrame(
9601
9525
  ({ gl }) => {
9602
9526
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -9731,14 +9655,22 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
9731
9655
  const col = /* @__PURE__ */ new THREE.Color();
9732
9656
  const TUNNEL_KEY = "__drei_view_tunnel__";
9733
9657
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
9734
- function computeContainerPosition(canvasSize, trackRect) {
9658
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
9735
9659
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
9736
9660
  const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
9737
9661
  const canvasBottom = canvasSize.top + canvasSize.height;
9738
- const bottom = canvasBottom - trackBottom;
9739
- const left = trackLeft - canvasSize.left;
9740
- const top = trackTop - canvasSize.top;
9741
- return { position: { width, height, left, top, bottom, right }, isOffscreen };
9662
+ const rawBottom = canvasBottom - trackBottom;
9663
+ const rawLeft = trackLeft - canvasSize.left;
9664
+ const rawTop = trackTop - canvasSize.top;
9665
+ const position = forceEven ? {
9666
+ width: roundEven(width),
9667
+ height: roundEven(height),
9668
+ left: roundEven(rawLeft),
9669
+ top: roundEven(rawTop),
9670
+ bottom: roundEven(rawBottom),
9671
+ right: roundEven(right)
9672
+ } : { width, height, left: rawLeft, top: rawTop, bottom: rawBottom, right };
9673
+ return { position, isOffscreen };
9742
9674
  }
9743
9675
  function prepareSkissor(renderer, camera, {
9744
9676
  left,
@@ -9778,6 +9710,7 @@ function clear(renderer) {
9778
9710
  }
9779
9711
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
9780
9712
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
9713
+ const forceEven = useThree((state) => state.internal.forceEven);
9781
9714
  const [isOffscreen, setOffscreen] = React.useState(false);
9782
9715
  const mountedRef = React.useRef(true);
9783
9716
  React.useEffect(() => {
@@ -9795,7 +9728,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9795
9728
  frameCount++;
9796
9729
  }
9797
9730
  if (rect.current) {
9798
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
9731
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
9799
9732
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
9800
9733
  if (visible && !_isOffscreen && rect.current) {
9801
9734
  const targetScene = children ? portalScene : scene;
@@ -9817,14 +9750,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9817
9750
  React.useLayoutEffect(() => {
9818
9751
  const curRect = rect.current;
9819
9752
  if (curRect && (!visible || !isOffscreen)) {
9820
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
9753
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
9821
9754
  if (!_isOffscreen) {
9822
9755
  const autoClear = prepareSkissor(renderer, portalCamera, position);
9823
9756
  clear(renderer);
9824
9757
  finishSkissor(renderer, autoClear);
9825
9758
  }
9826
9759
  }
9827
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
9760
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
9828
9761
  React.useEffect(() => {
9829
9762
  if (!track) return;
9830
9763
  const curRect = rect.current;
@@ -9832,7 +9765,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9832
9765
  setEvents({ connected: track.current });
9833
9766
  return () => {
9834
9767
  if (curRect) {
9835
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
9768
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
9836
9769
  if (!_isOffscreen) {
9837
9770
  const autoClear = prepareSkissor(renderer, portalCamera, position);
9838
9771
  clear(renderer);
@@ -9841,13 +9774,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9841
9774
  }
9842
9775
  setEvents({ connected: old });
9843
9776
  };
9844
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
9777
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
9845
9778
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
9846
9779
  }
9847
9780
  const CanvasView = /* @__PURE__ */ React.forwardRef(
9848
9781
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
9849
9782
  const rect = React.useRef(null);
9850
9783
  const { size, scene } = useThree();
9784
+ const forceEven = useThree((state) => state.internal.forceEven);
9851
9785
  const [virtualScene] = React.useState(() => new THREE.Scene());
9852
9786
  const [ready, toggle] = React.useReducer(() => true, false);
9853
9787
  const compute = React.useCallback(
@@ -9884,12 +9818,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
9884
9818
  {
9885
9819
  events: { compute, priority: index },
9886
9820
  size: {
9887
- width: rect.current?.width,
9888
- height: rect.current?.height,
9821
+ width: forceEven && rect.current?.width ? roundEven(rect.current.width) : rect.current?.width,
9822
+ height: forceEven && rect.current?.height ? roundEven(rect.current.height) : rect.current?.height,
9889
9823
  // @ts-ignore
9890
- top: rect.current?.top,
9824
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
9891
9825
  // @ts-ignore
9892
- left: rect.current?.left
9826
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
9893
9827
  }
9894
9828
  }
9895
9829
  ));
@@ -10316,8 +10250,8 @@ function getLoader(extension) {
10316
10250
  return loader;
10317
10251
  }
10318
10252
 
10319
- const isRef = (obj) => obj.current && obj.current.isScene;
10320
- const resolveScene = (scene) => isRef(scene) ? scene.current : scene;
10253
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
10254
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
10321
10255
  function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
10322
10256
  sceneProps = {
10323
10257
  backgroundBlurriness: 0,
@@ -10674,433 +10608,804 @@ const Sky = /* @__PURE__ */ React.forwardRef(
10674
10608
  }
10675
10609
  );
10676
10610
 
10677
- const presets = {
10678
- rembrandt: {
10679
- main: [1, 2, 1],
10680
- fill: [-2, -0.5, -2]
10681
- },
10682
- portrait: {
10683
- main: [-1, 2, 0.5],
10684
- fill: [-1, 0.5, -1.5]
10685
- },
10686
- upfront: {
10687
- main: [0, 2, 1],
10688
- fill: [-1, 0.5, -1.5]
10689
- },
10690
- soft: {
10691
- main: [-2, 4, 4],
10692
- fill: [-1, 0.5, -1.5]
10693
- }
10611
+ const presets = {
10612
+ rembrandt: {
10613
+ main: [1, 2, 1],
10614
+ fill: [-2, -0.5, -2]
10615
+ },
10616
+ portrait: {
10617
+ main: [-1, 2, 0.5],
10618
+ fill: [-1, 0.5, -1.5]
10619
+ },
10620
+ upfront: {
10621
+ main: [0, 2, 1],
10622
+ fill: [-1, 0.5, -1.5]
10623
+ },
10624
+ soft: {
10625
+ main: [-2, 4, 4],
10626
+ fill: [-1, 0.5, -1.5]
10627
+ }
10628
+ };
10629
+ function Refit({ radius, adjustCamera }) {
10630
+ const api = useBounds();
10631
+ React.useEffect(() => {
10632
+ if (adjustCamera) api.refresh().clip().fit();
10633
+ }, [radius, adjustCamera]);
10634
+ return null;
10635
+ }
10636
+ function Stage({
10637
+ children,
10638
+ center,
10639
+ adjustCamera = true,
10640
+ intensity = 0.5,
10641
+ shadows = "contact",
10642
+ environment = "city",
10643
+ preset = "rembrandt",
10644
+ ...props
10645
+ }) {
10646
+ const config = typeof preset === "string" ? presets[preset] : preset;
10647
+ const [{ radius, height }, set] = React.useState({ radius: 0, width: 0, height: 0, depth: 0 });
10648
+ const shadowBias = shadows?.bias ?? -1e-4;
10649
+ const normalBias = shadows?.normalBias ?? 0;
10650
+ const shadowSize = shadows?.size ?? 1024;
10651
+ const shadowOffset = shadows?.offset ?? 0;
10652
+ const contactShadow = shadows === "contact" || shadows?.type === "contact";
10653
+ const accumulativeShadow = shadows === "accumulative" || shadows?.type === "accumulative";
10654
+ const shadowSpread = { ...typeof shadows === "object" ? shadows : {} };
10655
+ const environmentProps = !environment ? null : typeof environment === "string" ? { preset: environment } : environment;
10656
+ const onCentered = React.useCallback((props2) => {
10657
+ const { width, height: height2, depth, boundingSphere } = props2;
10658
+ set({ radius: boundingSphere.radius, width, height: height2, depth });
10659
+ if (center?.onCentered) center.onCentered(props2);
10660
+ }, []);
10661
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ambientLight", { intensity: intensity / 3 }), /* @__PURE__ */ React.createElement(
10662
+ "spotLight",
10663
+ {
10664
+ penumbra: 1,
10665
+ position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10666
+ intensity: intensity * 2,
10667
+ castShadow: !!shadows,
10668
+ "shadow-bias": shadowBias,
10669
+ "shadow-normalBias": normalBias,
10670
+ "shadow-mapSize": shadowSize
10671
+ }
10672
+ ), /* @__PURE__ */ React.createElement(
10673
+ "pointLight",
10674
+ {
10675
+ position: [config.fill[0] * radius, config.fill[1] * radius, config.fill[2] * radius],
10676
+ intensity
10677
+ }
10678
+ ), /* @__PURE__ */ React.createElement(Bounds, { fit: !!adjustCamera, clip: !!adjustCamera, margin: Number(adjustCamera), observe: true, ...props }, /* @__PURE__ */ React.createElement(Refit, { radius, adjustCamera }), /* @__PURE__ */ React.createElement(Center, { ...center, position: [0, shadowOffset / 2, 0], onCentered }, children)), /* @__PURE__ */ React.createElement("group", { position: [0, -height / 2 - shadowOffset / 2, 0] }, contactShadow && /* @__PURE__ */ React.createElement(ContactShadows, { scale: radius * 4, far: radius, blur: 2, ...shadowSpread }), accumulativeShadow && /* @__PURE__ */ React.createElement(
10679
+ AccumulativeShadows,
10680
+ {
10681
+ temporal: true,
10682
+ frames: 100,
10683
+ alphaTest: 0.9,
10684
+ toneMapped: true,
10685
+ scale: radius * 4,
10686
+ ...shadowSpread
10687
+ },
10688
+ /* @__PURE__ */ React.createElement(
10689
+ RandomizedLight,
10690
+ {
10691
+ amount: shadowSpread.amount ?? 8,
10692
+ radius: shadowSpread.radius ?? radius,
10693
+ ambient: shadowSpread.ambient ?? 0.5,
10694
+ intensity: shadowSpread.intensity ?? 1,
10695
+ position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10696
+ size: radius * 4,
10697
+ bias: -shadowBias,
10698
+ mapSize: shadowSize
10699
+ }
10700
+ )
10701
+ )), environment && /* @__PURE__ */ React.createElement(Environment, { ...environmentProps }));
10702
+ }
10703
+
10704
+ function getFormatString(format) {
10705
+ switch (format) {
10706
+ case 64:
10707
+ return "-64px";
10708
+ case 128:
10709
+ return "-128px";
10710
+ case 256:
10711
+ return "-256px";
10712
+ case 512:
10713
+ return "-512px";
10714
+ default:
10715
+ return "";
10716
+ }
10717
+ }
10718
+ const LIST_URL$1 = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/matcaps.json";
10719
+ const MATCAP_ROOT = "https://rawcdn.githack.com/emmelleppi/matcaps/9b36ccaaf0a24881a39062d05566c9e92be4aa0d";
10720
+ function useMatcapTexture(id = 0, format = 1024, onLoad) {
10721
+ const matcapList = suspend(() => fetch(LIST_URL$1).then((res) => res.json()), ["matcapList"]);
10722
+ const DEFAULT_MATCAP = matcapList[0];
10723
+ const numTot = React.useMemo(() => Object.keys(matcapList).length, []);
10724
+ const fileHash = React.useMemo(() => {
10725
+ if (typeof id === "string") {
10726
+ return id;
10727
+ } else if (typeof id === "number") {
10728
+ return matcapList[id];
10729
+ }
10730
+ return null;
10731
+ }, [id]);
10732
+ const fileName = `${fileHash || DEFAULT_MATCAP}${getFormatString(format)}.png`;
10733
+ const url = `${MATCAP_ROOT}/${format}/${fileName}`;
10734
+ const matcapTexture = useTexture(url, onLoad);
10735
+ return [matcapTexture, url, numTot];
10736
+ }
10737
+ const MatcapTexture = ({
10738
+ children,
10739
+ id,
10740
+ format,
10741
+ onLoad
10742
+ }) => {
10743
+ const texture = useMatcapTexture(id, format, onLoad);
10744
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
10745
+ };
10746
+
10747
+ const NORMAL_ROOT = "https://rawcdn.githack.com/pmndrs/drei-assets/7a3104997e1576f83472829815b00880d88b32fb";
10748
+ const LIST_URL = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/normals/normals.json";
10749
+ function useNormalTexture(id = 0, settings = {}, onLoad) {
10750
+ const { repeat = [1, 1], anisotropy = 1, offset = [0, 0] } = settings;
10751
+ const normalsList = suspend(() => fetch(LIST_URL).then((res) => res.json()), ["normalsList"]);
10752
+ const numTot = React.useMemo(() => Object.keys(normalsList).length, []);
10753
+ const DEFAULT_NORMAL = normalsList[0];
10754
+ const imageName = normalsList[id] || DEFAULT_NORMAL;
10755
+ const url = `${NORMAL_ROOT}/normals/${imageName}`;
10756
+ const normalTexture = useTexture(url, onLoad);
10757
+ React.useLayoutEffect(() => {
10758
+ if (!normalTexture) return;
10759
+ normalTexture.wrapS = normalTexture.wrapT = RepeatWrapping;
10760
+ normalTexture.repeat = new Vector2(repeat[0], repeat[1]);
10761
+ normalTexture.offset = new Vector2(offset[0], offset[1]);
10762
+ normalTexture.anisotropy = anisotropy;
10763
+ }, [normalTexture, anisotropy, repeat, offset]);
10764
+ return [normalTexture, url, numTot];
10765
+ }
10766
+ const NormalTexture = ({
10767
+ children,
10768
+ id,
10769
+ onLoad,
10770
+ ...settings
10771
+ }) => {
10772
+ const ret = useNormalTexture(id, settings, onLoad);
10773
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(ret));
10694
10774
  };
10695
- function Refit({ radius, adjustCamera }) {
10696
- const api = useBounds();
10775
+
10776
+ function CycleRaycast({
10777
+ onChanged,
10778
+ portal,
10779
+ preventDefault = true,
10780
+ scroll = true,
10781
+ keyCode = 9
10782
+ }) {
10783
+ const cycle = React.useRef(0);
10784
+ const setEvents = useThree((state) => state.setEvents);
10785
+ const get = useThree((state) => state.get);
10786
+ const gl = useThree((state) => state.gl);
10697
10787
  React.useEffect(() => {
10698
- if (adjustCamera) api.refresh().clip().fit();
10699
- }, [radius, adjustCamera]);
10788
+ let hits = [];
10789
+ let lastEvent = void 0;
10790
+ const prev = get().events.filter;
10791
+ const target = portal?.current ?? gl.domElement.parentNode;
10792
+ const renderStatus = () => target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
10793
+ setEvents({
10794
+ filter: (intersections, state) => {
10795
+ let clone = [...intersections];
10796
+ if (clone.length !== hits.length || !hits.every((hit) => clone.map((e) => e.object.uuid).includes(hit.object.uuid))) {
10797
+ cycle.current = 0;
10798
+ hits = clone;
10799
+ renderStatus();
10800
+ }
10801
+ if (prev) clone = prev(clone, state);
10802
+ for (let i = 0; i < Math.round(cycle.current) % clone.length; i++) {
10803
+ const first = clone.shift();
10804
+ clone = [...clone, first];
10805
+ }
10806
+ return clone;
10807
+ }
10808
+ });
10809
+ const refresh = (fn) => {
10810
+ cycle.current = fn(cycle.current);
10811
+ get().events.handlers?.onPointerCancel(void 0);
10812
+ get().events.handlers?.onPointerMove(lastEvent);
10813
+ renderStatus();
10814
+ };
10815
+ const tabEvent = (event) => {
10816
+ if ((event.keyCode || event.which) === keyCode) {
10817
+ if (preventDefault) event.preventDefault();
10818
+ if (hits.length > 1) refresh((current) => current + 1);
10819
+ }
10820
+ };
10821
+ const wheelEvent = (event) => {
10822
+ if (preventDefault) event.preventDefault();
10823
+ let delta = 0;
10824
+ if (!event) event = window.event;
10825
+ if (event.wheelDelta) delta = event.wheelDelta / 120;
10826
+ else if (event.detail) delta = -event.detail / 3;
10827
+ if (hits.length > 1) refresh((current) => Math.abs(current - delta));
10828
+ };
10829
+ const moveEvent = (event) => lastEvent = event;
10830
+ document.addEventListener("pointermove", moveEvent, { passive: true });
10831
+ if (scroll) document.addEventListener("wheel", wheelEvent);
10832
+ if (keyCode !== void 0) document.addEventListener("keydown", tabEvent);
10833
+ return () => {
10834
+ setEvents({ filter: prev });
10835
+ if (keyCode !== void 0) document.removeEventListener("keydown", tabEvent);
10836
+ if (scroll) document.removeEventListener("wheel", wheelEvent);
10837
+ document.removeEventListener("pointermove", moveEvent);
10838
+ };
10839
+ }, [gl, get, setEvents, preventDefault, scroll, keyCode]);
10700
10840
  return null;
10701
10841
  }
10702
- function Stage({
10842
+
10843
+ function useAspect(width, height, factor = 1) {
10844
+ const v = useThree((state) => state.viewport);
10845
+ const adaptedHeight = height * (v.aspect > width / height ? v.width / width : v.height / height);
10846
+ const adaptedWidth = width * (v.aspect > width / height ? v.width / width : v.height / height);
10847
+ return [adaptedWidth * factor, adaptedHeight * factor, 1];
10848
+ }
10849
+
10850
+ function useCursor(hovered, onPointerOver = "pointer", onPointerOut = "auto", container = document.body) {
10851
+ React.useEffect(() => {
10852
+ if (hovered) {
10853
+ container.style.cursor = onPointerOver;
10854
+ return () => void (container.style.cursor = onPointerOut);
10855
+ }
10856
+ }, [hovered]);
10857
+ }
10858
+
10859
+ const ScreenSpace = /* @__PURE__ */ React.forwardRef(({ children, depth = -1, ...rest }, ref) => {
10860
+ const localRef = React.useRef(null);
10861
+ React.useImperativeHandle(ref, () => localRef.current, []);
10862
+ useFrame(({ camera }) => {
10863
+ localRef.current.quaternion.copy(camera.quaternion);
10864
+ localRef.current.position.copy(camera.position);
10865
+ });
10866
+ return /* @__PURE__ */ React.createElement("group", { ref: localRef, ...rest }, /* @__PURE__ */ React.createElement("group", { "position-z": -depth }, children));
10867
+ });
10868
+
10869
+ const context = /* @__PURE__ */ React.createContext([]);
10870
+ function Select({
10871
+ box,
10872
+ multiple,
10703
10873
  children,
10704
- center,
10705
- adjustCamera = true,
10706
- intensity = 0.5,
10707
- shadows = "contact",
10708
- environment = "city",
10709
- preset = "rembrandt",
10874
+ onChange,
10875
+ onChangePointerUp,
10876
+ border = "1px solid #55aaff",
10877
+ backgroundColor = "rgba(75, 160, 255, 0.1)",
10878
+ filter: customFilter = (item) => item,
10710
10879
  ...props
10711
10880
  }) {
10712
- const config = typeof preset === "string" ? presets[preset] : preset;
10713
- const [{ radius, height }, set] = React.useState({ radius: 0, width: 0, height: 0, depth: 0 });
10714
- const shadowBias = shadows?.bias ?? -1e-4;
10715
- const normalBias = shadows?.normalBias ?? 0;
10716
- const shadowSize = shadows?.size ?? 1024;
10717
- const shadowOffset = shadows?.offset ?? 0;
10718
- const contactShadow = shadows === "contact" || shadows?.type === "contact";
10719
- const accumulativeShadow = shadows === "accumulative" || shadows?.type === "accumulative";
10720
- const shadowSpread = { ...typeof shadows === "object" ? shadows : {} };
10721
- const environmentProps = !environment ? null : typeof environment === "string" ? { preset: environment } : environment;
10722
- const onCentered = React.useCallback((props2) => {
10723
- const { width, height: height2, depth, boundingSphere } = props2;
10724
- set({ radius: boundingSphere.radius, width, height: height2, depth });
10725
- if (center?.onCentered) center.onCentered(props2);
10881
+ const [downed, down] = React.useState(false);
10882
+ const { setEvents, camera, raycaster, gl, controls, size, get } = useThree();
10883
+ const [hovered, hover] = React.useState(false);
10884
+ const [active, dispatch] = React.useReducer(
10885
+ // @ts-expect-error
10886
+ (state, { object, shift }) => {
10887
+ if (object === void 0) return [];
10888
+ else if (Array.isArray(object)) return object;
10889
+ else if (!shift) return state[0] === object ? [] : [object];
10890
+ else if (state.includes(object)) return state.filter((o) => o !== object);
10891
+ else return [object, ...state];
10892
+ },
10893
+ []
10894
+ );
10895
+ React.useEffect(() => {
10896
+ if (downed) onChange?.(active);
10897
+ else onChangePointerUp?.(active);
10898
+ }, [active, downed]);
10899
+ const onClick = React.useCallback((e) => {
10900
+ e.stopPropagation();
10901
+ dispatch({ object: customFilter([e.object])[0], shift: multiple && e.shiftKey });
10726
10902
  }, []);
10727
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ambientLight", { intensity: intensity / 3 }), /* @__PURE__ */ React.createElement(
10728
- "spotLight",
10729
- {
10730
- penumbra: 1,
10731
- position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10732
- intensity: intensity * 2,
10733
- castShadow: !!shadows,
10734
- "shadow-bias": shadowBias,
10735
- "shadow-normalBias": normalBias,
10736
- "shadow-mapSize": shadowSize
10903
+ const onPointerMissed = React.useCallback((e) => !hovered && dispatch({}), [hovered]);
10904
+ const ref = React.useRef(null);
10905
+ React.useEffect(() => {
10906
+ if (!box || !multiple) return;
10907
+ const selBox = new SelectionBox(camera, ref.current);
10908
+ const element = document.createElement("div");
10909
+ element.style.pointerEvents = "none";
10910
+ element.style.border = border;
10911
+ element.style.backgroundColor = backgroundColor;
10912
+ element.style.position = "fixed";
10913
+ const startPoint = new THREE.Vector2();
10914
+ const pointTopLeft = new THREE.Vector2();
10915
+ const pointBottomRight = new THREE.Vector2();
10916
+ const oldRaycasterEnabled = get().events.enabled;
10917
+ const oldControlsEnabled = controls?.enabled;
10918
+ let isDown = false;
10919
+ function prepareRay(event, vec) {
10920
+ const { offsetX, offsetY } = event;
10921
+ const { width, height } = size;
10922
+ vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
10923
+ }
10924
+ function onSelectStart(event) {
10925
+ if (controls) controls.enabled = false;
10926
+ setEvents({ enabled: false });
10927
+ down(isDown = true);
10928
+ gl.domElement.parentElement?.appendChild(element);
10929
+ element.style.left = `${event.clientX}px`;
10930
+ element.style.top = `${event.clientY}px`;
10931
+ element.style.width = "0px";
10932
+ element.style.height = "0px";
10933
+ startPoint.x = event.clientX;
10934
+ startPoint.y = event.clientY;
10935
+ }
10936
+ function onSelectMove(event) {
10937
+ pointBottomRight.x = Math.max(startPoint.x, event.clientX);
10938
+ pointBottomRight.y = Math.max(startPoint.y, event.clientY);
10939
+ pointTopLeft.x = Math.min(startPoint.x, event.clientX);
10940
+ pointTopLeft.y = Math.min(startPoint.y, event.clientY);
10941
+ element.style.left = `${pointTopLeft.x}px`;
10942
+ element.style.top = `${pointTopLeft.y}px`;
10943
+ element.style.width = `${pointBottomRight.x - pointTopLeft.x}px`;
10944
+ element.style.height = `${pointBottomRight.y - pointTopLeft.y}px`;
10945
+ }
10946
+ function onSelectOver() {
10947
+ if (isDown) {
10948
+ if (controls) controls.enabled = oldControlsEnabled;
10949
+ setEvents({ enabled: oldRaycasterEnabled });
10950
+ down(isDown = false);
10951
+ element.parentElement?.removeChild(element);
10952
+ }
10737
10953
  }
10738
- ), /* @__PURE__ */ React.createElement(
10739
- "pointLight",
10740
- {
10741
- position: [config.fill[0] * radius, config.fill[1] * radius, config.fill[2] * radius],
10742
- intensity
10954
+ function pointerDown(event) {
10955
+ if (event.shiftKey) {
10956
+ onSelectStart(event);
10957
+ prepareRay(event, selBox.startPoint);
10958
+ }
10743
10959
  }
10744
- ), /* @__PURE__ */ React.createElement(Bounds, { fit: !!adjustCamera, clip: !!adjustCamera, margin: Number(adjustCamera), observe: true, ...props }, /* @__PURE__ */ React.createElement(Refit, { radius, adjustCamera }), /* @__PURE__ */ React.createElement(Center, { ...center, position: [0, shadowOffset / 2, 0], onCentered }, children)), /* @__PURE__ */ React.createElement("group", { position: [0, -height / 2 - shadowOffset / 2, 0] }, contactShadow && /* @__PURE__ */ React.createElement(ContactShadows, { scale: radius * 4, far: radius, blur: 2, ...shadowSpread }), accumulativeShadow && /* @__PURE__ */ React.createElement(
10745
- AccumulativeShadows,
10960
+ let previous = [];
10961
+ function pointerMove(event) {
10962
+ if (isDown) {
10963
+ onSelectMove(event);
10964
+ prepareRay(event, selBox.endPoint);
10965
+ const allSelected = selBox.select().sort((o) => o.uuid).filter((o) => o.isMesh);
10966
+ if (!shallow(allSelected, previous)) {
10967
+ previous = allSelected;
10968
+ dispatch({ object: customFilter(allSelected) });
10969
+ }
10970
+ }
10971
+ }
10972
+ function pointerUp(event) {
10973
+ if (isDown) onSelectOver();
10974
+ }
10975
+ document.addEventListener("pointerdown", pointerDown, { passive: true });
10976
+ document.addEventListener("pointermove", pointerMove, { passive: true, capture: true });
10977
+ document.addEventListener("pointerup", pointerUp, { passive: true });
10978
+ return () => {
10979
+ document.removeEventListener("pointerdown", pointerDown);
10980
+ document.removeEventListener("pointermove", pointerMove, true);
10981
+ document.removeEventListener("pointerup", pointerUp);
10982
+ };
10983
+ }, [size.width, size.height, raycaster, camera, controls, gl]);
10984
+ return /* @__PURE__ */ React.createElement(
10985
+ "group",
10746
10986
  {
10747
- temporal: true,
10748
- frames: 100,
10749
- alphaTest: 0.9,
10750
- toneMapped: true,
10751
- scale: radius * 4,
10752
- ...shadowSpread
10987
+ ref,
10988
+ onClick,
10989
+ onPointerOver: () => hover(true),
10990
+ onPointerOut: () => hover(false),
10991
+ onPointerMissed,
10992
+ ...props
10753
10993
  },
10754
- /* @__PURE__ */ React.createElement(
10755
- RandomizedLight,
10756
- {
10757
- amount: shadowSpread.amount ?? 8,
10758
- radius: shadowSpread.radius ?? radius,
10759
- ambient: shadowSpread.ambient ?? 0.5,
10760
- intensity: shadowSpread.intensity ?? 1,
10761
- position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10762
- size: radius * 4,
10763
- bias: -shadowBias,
10764
- mapSize: shadowSize
10765
- }
10766
- )
10767
- )), environment && /* @__PURE__ */ React.createElement(Environment, { ...environmentProps }));
10994
+ /* @__PURE__ */ React.createElement(context.Provider, { value: active }, children)
10995
+ );
10768
10996
  }
10769
-
10770
- function getFormatString(format) {
10771
- switch (format) {
10772
- case 64:
10773
- return "-64px";
10774
- case 128:
10775
- return "-128px";
10776
- case 256:
10777
- return "-256px";
10778
- case 512:
10779
- return "-512px";
10780
- default:
10781
- return "";
10782
- }
10997
+ function useSelect() {
10998
+ return React.useContext(context);
10783
10999
  }
10784
- const LIST_URL$1 = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/matcaps.json";
10785
- const MATCAP_ROOT = "https://rawcdn.githack.com/emmelleppi/matcaps/9b36ccaaf0a24881a39062d05566c9e92be4aa0d";
10786
- function useMatcapTexture(id = 0, format = 1024, onLoad) {
10787
- const matcapList = suspend(() => fetch(LIST_URL$1).then((res) => res.json()), ["matcapList"]);
10788
- const DEFAULT_MATCAP = matcapList[0];
10789
- const numTot = React.useMemo(() => Object.keys(matcapList).length, []);
10790
- const fileHash = React.useMemo(() => {
10791
- if (typeof id === "string") {
10792
- return id;
10793
- } else if (typeof id === "number") {
10794
- return matcapList[id];
10795
- }
10796
- return null;
10797
- }, [id]);
10798
- const fileName = `${fileHash || DEFAULT_MATCAP}${getFormatString(format)}.png`;
10799
- const url = `${MATCAP_ROOT}/${format}/${fileName}`;
10800
- const matcapTexture = useTexture(url, onLoad);
10801
- return [matcapTexture, url, numTot];
11000
+
11001
+ function AsciiRenderer({
11002
+ renderIndex = 1,
11003
+ bgColor = "black",
11004
+ fgColor = "white",
11005
+ characters = " .:-+*=%@#",
11006
+ invert = true,
11007
+ color = false,
11008
+ resolution = 0.15
11009
+ }) {
11010
+ const { size, gl, scene, camera } = useThree();
11011
+ const effect = React.useMemo(() => {
11012
+ const effect2 = new AsciiEffect(gl, characters, { invert, color, resolution });
11013
+ effect2.domElement.style.position = "absolute";
11014
+ effect2.domElement.style.top = "0px";
11015
+ effect2.domElement.style.left = "0px";
11016
+ effect2.domElement.style.pointerEvents = "none";
11017
+ return effect2;
11018
+ }, [characters, invert, color, resolution]);
11019
+ React.useLayoutEffect(() => {
11020
+ effect.domElement.style.color = fgColor;
11021
+ effect.domElement.style.backgroundColor = bgColor;
11022
+ }, [fgColor, bgColor]);
11023
+ React.useEffect(() => {
11024
+ gl.domElement.style.opacity = "0";
11025
+ gl.domElement.parentNode.appendChild(effect.domElement);
11026
+ return () => {
11027
+ gl.domElement.style.opacity = "1";
11028
+ gl.domElement.parentNode.removeChild(effect.domElement);
11029
+ };
11030
+ }, [effect]);
11031
+ React.useEffect(() => {
11032
+ effect.setSize(size.width, size.height);
11033
+ }, [effect, size]);
11034
+ useFrame((state) => {
11035
+ effect.render(scene, camera);
11036
+ }, renderIndex);
11037
+ return /* @__PURE__ */ React.createElement(React.Fragment, null);
10802
11038
  }
10803
- const MatcapTexture = ({
10804
- children,
10805
- id,
10806
- format,
10807
- onLoad
10808
- }) => {
10809
- const texture = useMatcapTexture(id, format, onLoad);
10810
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
10811
- };
10812
11039
 
10813
- const NORMAL_ROOT = "https://rawcdn.githack.com/pmndrs/drei-assets/7a3104997e1576f83472829815b00880d88b32fb";
10814
- const LIST_URL = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/normals/normals.json";
10815
- function useNormalTexture(id = 0, settings = {}, onLoad) {
10816
- const { repeat = [1, 1], anisotropy = 1, offset = [0, 0] } = settings;
10817
- const normalsList = suspend(() => fetch(LIST_URL).then((res) => res.json()), ["normalsList"]);
10818
- const numTot = React.useMemo(() => Object.keys(normalsList).length, []);
10819
- const DEFAULT_NORMAL = normalsList[0];
10820
- const imageName = normalsList[id] || DEFAULT_NORMAL;
10821
- const url = `${NORMAL_ROOT}/normals/${imageName}`;
10822
- const normalTexture = useTexture(url, onLoad);
10823
- React.useLayoutEffect(() => {
10824
- if (!normalTexture) return;
10825
- normalTexture.wrapS = normalTexture.wrapT = RepeatWrapping;
10826
- normalTexture.repeat = new Vector2(repeat[0], repeat[1]);
10827
- normalTexture.offset = new Vector2(offset[0], offset[1]);
10828
- normalTexture.anisotropy = anisotropy;
10829
- }, [normalTexture, anisotropy, repeat, offset]);
10830
- return [normalTexture, url, numTot];
10831
- }
10832
- const NormalTexture = ({
10833
- children,
10834
- id,
10835
- onLoad,
10836
- ...settings
10837
- }) => {
10838
- const ret = useNormalTexture(id, settings, onLoad);
10839
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(ret));
10840
- };
11040
+ const WireframeMaterialShaders = {
11041
+ uniforms: {
11042
+ strokeOpacity: 1,
11043
+ fillOpacity: 0.25,
11044
+ fillMix: 0,
11045
+ thickness: 0.05,
11046
+ colorBackfaces: false,
11047
+ dashInvert: true,
11048
+ dash: false,
11049
+ dashRepeats: 4,
11050
+ dashLength: 0.5,
11051
+ squeeze: false,
11052
+ squeezeMin: 0.2,
11053
+ squeezeMax: 1,
11054
+ stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
11055
+ backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
11056
+ fill: /* @__PURE__ */ new THREE.Color("#00ff00")
11057
+ },
11058
+ vertex: (
11059
+ /* glsl */
11060
+ `
11061
+ attribute vec3 barycentric;
11062
+
11063
+ varying vec3 v_edges_Barycentric;
11064
+ varying vec3 v_edges_Position;
11065
+
11066
+ void initWireframe() {
11067
+ v_edges_Barycentric = barycentric;
11068
+ v_edges_Position = position.xyz;
11069
+ }
11070
+ `
11071
+ ),
11072
+ fragment: (
11073
+ /* glsl */
11074
+ `
11075
+ #ifndef PI
11076
+ #define PI 3.1415926535897932384626433832795
11077
+ #endif
11078
+
11079
+ varying vec3 v_edges_Barycentric;
11080
+ varying vec3 v_edges_Position;
11081
+
11082
+ uniform float strokeOpacity;
11083
+ uniform float fillOpacity;
11084
+ uniform float fillMix;
11085
+ uniform float thickness;
11086
+ uniform bool colorBackfaces;
11087
+
11088
+ // Dash
11089
+ uniform bool dashInvert;
11090
+ uniform bool dash;
11091
+ uniform bool dashOnly;
11092
+ uniform float dashRepeats;
11093
+ uniform float dashLength;
11094
+
11095
+ // Squeeze
11096
+ uniform bool squeeze;
11097
+ uniform float squeezeMin;
11098
+ uniform float squeezeMax;
11099
+
11100
+ // Colors
11101
+ uniform vec3 stroke;
11102
+ uniform vec3 backfaceStroke;
11103
+ uniform vec3 fill;
11104
+
11105
+ // This is like
11106
+ float wireframe_aastep(float threshold, float dist) {
11107
+ float afwidth = fwidth(dist) * 0.5;
11108
+ return smoothstep(threshold - afwidth, threshold + afwidth, dist);
11109
+ }
11110
+
11111
+ float wireframe_map(float value, float min1, float max1, float min2, float max2) {
11112
+ return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
11113
+ }
11114
+
11115
+ float getWireframe() {
11116
+ vec3 barycentric = v_edges_Barycentric;
11117
+
11118
+ // Distance from center of each triangle to its edges.
11119
+ float d = min(min(barycentric.x, barycentric.y), barycentric.z);
11120
+
11121
+ // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
11122
+ float positionAlong = max(barycentric.x, barycentric.y);
11123
+ if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
11124
+ positionAlong = 1.0 - positionAlong;
11125
+ }
11126
+
11127
+ // the thickness of the stroke
11128
+ float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
11129
+
11130
+ // if we want to shrink the thickness toward the center of the line segment
11131
+ if (squeeze) {
11132
+ computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
11133
+ }
11134
+
11135
+ // Create dash pattern
11136
+ if (dash) {
11137
+ // here we offset the stroke position depending on whether it
11138
+ // should overlap or not
11139
+ float offset = 1.0 / dashRepeats * dashLength / 2.0;
11140
+ if (!dashInvert) {
11141
+ offset += 1.0 / dashRepeats / 2.0;
11142
+ }
11143
+
11144
+ // if we should animate the dash or not
11145
+ // if (dashAnimate) {
11146
+ // offset += time * 0.22;
11147
+ // }
11148
+
11149
+ // create the repeating dash pattern
11150
+ float pattern = fract((positionAlong + offset) * dashRepeats);
11151
+ computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
11152
+ }
10841
11153
 
10842
- function CycleRaycast({
10843
- onChanged,
10844
- portal,
10845
- preventDefault = true,
10846
- scroll = true,
10847
- keyCode = 9
10848
- }) {
10849
- const cycle = React.useRef(0);
10850
- const setEvents = useThree((state) => state.setEvents);
10851
- const get = useThree((state) => state.get);
10852
- const gl = useThree((state) => state.gl);
10853
- React.useEffect(() => {
10854
- let hits = [];
10855
- let lastEvent = void 0;
10856
- const prev = get().events.filter;
10857
- const target = portal?.current ?? gl.domElement.parentNode;
10858
- const renderStatus = () => target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
10859
- setEvents({
10860
- filter: (intersections, state) => {
10861
- let clone = [...intersections];
10862
- if (clone.length !== hits.length || !hits.every((hit) => clone.map((e) => e.object.uuid).includes(hit.object.uuid))) {
10863
- cycle.current = 0;
10864
- hits = clone;
10865
- renderStatus();
10866
- }
10867
- if (prev) clone = prev(clone, state);
10868
- for (let i = 0; i < Math.round(cycle.current) % clone.length; i++) {
10869
- const first = clone.shift();
10870
- clone = [...clone, first];
10871
- }
10872
- return clone;
10873
- }
10874
- });
10875
- const refresh = (fn) => {
10876
- cycle.current = fn(cycle.current);
10877
- get().events.handlers?.onPointerCancel(void 0);
10878
- get().events.handlers?.onPointerMove(lastEvent);
10879
- renderStatus();
10880
- };
10881
- const tabEvent = (event) => {
10882
- if ((event.keyCode || event.which) === keyCode) {
10883
- if (preventDefault) event.preventDefault();
10884
- if (hits.length > 1) refresh((current) => current + 1);
10885
- }
10886
- };
10887
- const wheelEvent = (event) => {
10888
- if (preventDefault) event.preventDefault();
10889
- let delta = 0;
10890
- if (!event) event = window.event;
10891
- if (event.wheelDelta) delta = event.wheelDelta / 120;
10892
- else if (event.detail) delta = -event.detail / 3;
10893
- if (hits.length > 1) refresh((current) => Math.abs(current - delta));
10894
- };
10895
- const moveEvent = (event) => lastEvent = event;
10896
- document.addEventListener("pointermove", moveEvent, { passive: true });
10897
- if (scroll) document.addEventListener("wheel", wheelEvent);
10898
- if (keyCode !== void 0) document.addEventListener("keydown", tabEvent);
10899
- return () => {
10900
- setEvents({ filter: prev });
10901
- if (keyCode !== void 0) document.removeEventListener("keydown", tabEvent);
10902
- if (scroll) document.removeEventListener("wheel", wheelEvent);
10903
- document.removeEventListener("pointermove", moveEvent);
10904
- };
10905
- }, [gl, get, setEvents, preventDefault, scroll, keyCode]);
10906
- return null;
10907
- }
11154
+ // compute the anti-aliased stroke edge
11155
+ float edge = 1.0 - wireframe_aastep(computedThickness, d);
10908
11156
 
10909
- function useAspect(width, height, factor = 1) {
10910
- const v = useThree((state) => state.viewport);
10911
- const adaptedHeight = height * (v.aspect > width / height ? v.width / width : v.height / height);
10912
- const adaptedWidth = width * (v.aspect > width / height ? v.width / width : v.height / height);
10913
- return [adaptedWidth * factor, adaptedHeight * factor, 1];
10914
- }
11157
+ return edge;
11158
+ }
11159
+ `
11160
+ )
11161
+ };
11162
+ const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
11163
+ WireframeMaterialShaders.uniforms,
11164
+ WireframeMaterialShaders.vertex + /* glsl */
11165
+ `
11166
+ void main() {
11167
+ initWireframe();
11168
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
11169
+ }
11170
+ `,
11171
+ WireframeMaterialShaders.fragment + /* glsl */
11172
+ `
11173
+ void main () {
11174
+ // Compute color
10915
11175
 
10916
- function useCursor(hovered, onPointerOver = "pointer", onPointerOut = "auto", container = document.body) {
10917
- React.useEffect(() => {
10918
- if (hovered) {
10919
- container.style.cursor = onPointerOver;
10920
- return () => void (container.style.cursor = onPointerOut);
10921
- }
10922
- }, [hovered]);
10923
- }
11176
+ float edge = getWireframe();
11177
+ vec4 colorStroke = vec4(stroke, edge);
10924
11178
 
10925
- const ScreenSpace = /* @__PURE__ */ React.forwardRef(({ children, depth = -1, ...rest }, ref) => {
10926
- const localRef = React.useRef(null);
10927
- React.useImperativeHandle(ref, () => localRef.current, []);
10928
- useFrame(({ camera }) => {
10929
- localRef.current.quaternion.copy(camera.quaternion);
10930
- localRef.current.position.copy(camera.position);
10931
- });
10932
- return /* @__PURE__ */ React.createElement("group", { ref: localRef, ...rest }, /* @__PURE__ */ React.createElement("group", { "position-z": -depth }, children));
10933
- });
11179
+ #ifdef FLIP_SIDED
11180
+ colorStroke.rgb = backfaceStroke;
11181
+ #endif
11182
+
11183
+ vec4 colorFill = vec4(fill, fillOpacity);
11184
+ vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
10934
11185
 
10935
- const context = /* @__PURE__ */ React.createContext([]);
10936
- function Select({
10937
- box,
10938
- multiple,
10939
- children,
10940
- onChange,
10941
- onChangePointerUp,
10942
- border = "1px solid #55aaff",
10943
- backgroundColor = "rgba(75, 160, 255, 0.1)",
10944
- filter: customFilter = (item) => item,
10945
- ...props
10946
- }) {
10947
- const [downed, down] = React.useState(false);
10948
- const { setEvents, camera, raycaster, gl, controls, size, get } = useThree();
10949
- const [hovered, hover] = React.useState(false);
10950
- const [active, dispatch] = React.useReducer(
10951
- // @ts-expect-error
10952
- (state, { object, shift }) => {
10953
- if (object === void 0) return [];
10954
- else if (Array.isArray(object)) return object;
10955
- else if (!shift) return state[0] === object ? [] : [object];
10956
- else if (state.includes(object)) return state.filter((o) => o !== object);
10957
- else return [object, ...state];
10958
- },
10959
- []
11186
+ gl_FragColor = outColor;
11187
+ }
11188
+ `
11189
+ );
11190
+ function setWireframeOverride(material, uniforms) {
11191
+ material.onBeforeCompile = (shader) => {
11192
+ shader.uniforms = {
11193
+ ...shader.uniforms,
11194
+ ...uniforms
11195
+ };
11196
+ shader.vertexShader = shader.vertexShader.replace(
11197
+ "void main() {",
11198
+ `
11199
+ ${WireframeMaterialShaders.vertex}
11200
+ void main() {
11201
+ initWireframe();
11202
+ `
11203
+ );
11204
+ shader.fragmentShader = shader.fragmentShader.replace(
11205
+ "void main() {",
11206
+ `
11207
+ ${WireframeMaterialShaders.fragment}
11208
+ void main() {
11209
+ `
11210
+ );
11211
+ shader.fragmentShader = shader.fragmentShader.replace(
11212
+ "#include <color_fragment>",
11213
+ /* glsl */
11214
+ `
11215
+ #include <color_fragment>
11216
+ float edge = getWireframe();
11217
+ vec4 colorStroke = vec4(stroke, edge);
11218
+ #ifdef FLIP_SIDED
11219
+ colorStroke.rgb = backfaceStroke;
11220
+ #endif
11221
+ vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
11222
+ vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
11223
+
11224
+ diffuseColor.rgb = outColor.rgb;
11225
+ diffuseColor.a *= outColor.a;
11226
+ `
11227
+ );
11228
+ };
11229
+ material.side = THREE.DoubleSide;
11230
+ material.transparent = true;
11231
+ }
11232
+ function useWireframeUniforms(uniforms, props) {
11233
+ React.useEffect(
11234
+ () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
11235
+ [props.fillOpacity]
11236
+ );
11237
+ React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
11238
+ React.useEffect(
11239
+ () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
11240
+ [props.strokeOpacity]
11241
+ );
11242
+ React.useEffect(
11243
+ () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
11244
+ [props.thickness]
11245
+ );
11246
+ React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
11247
+ React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
11248
+ React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
11249
+ React.useEffect(
11250
+ () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
11251
+ [props.dashRepeats]
11252
+ );
11253
+ React.useEffect(
11254
+ () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
11255
+ [props.dashLength]
11256
+ );
11257
+ React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
11258
+ React.useEffect(
11259
+ () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
11260
+ [props.squeezeMin]
11261
+ );
11262
+ React.useEffect(
11263
+ () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
11264
+ [props.squeezeMax]
10960
11265
  );
10961
- React.useEffect(() => {
10962
- if (downed) onChange?.(active);
10963
- else onChangePointerUp?.(active);
10964
- }, [active, downed]);
10965
- const onClick = React.useCallback((e) => {
10966
- e.stopPropagation();
10967
- dispatch({ object: customFilter([e.object])[0], shift: multiple && e.shiftKey });
10968
- }, []);
10969
- const onPointerMissed = React.useCallback((e) => !hovered && dispatch({}), [hovered]);
10970
- const ref = React.useRef(null);
10971
- React.useEffect(() => {
10972
- if (!box || !multiple) return;
10973
- const selBox = new SelectionBox(camera, ref.current);
10974
- const element = document.createElement("div");
10975
- element.style.pointerEvents = "none";
10976
- element.style.border = border;
10977
- element.style.backgroundColor = backgroundColor;
10978
- element.style.position = "fixed";
10979
- const startPoint = new THREE.Vector2();
10980
- const pointTopLeft = new THREE.Vector2();
10981
- const pointBottomRight = new THREE.Vector2();
10982
- const oldRaycasterEnabled = get().events.enabled;
10983
- const oldControlsEnabled = controls?.enabled;
10984
- let isDown = false;
10985
- function prepareRay(event, vec) {
10986
- const { offsetX, offsetY } = event;
10987
- const { width, height } = size;
10988
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
10989
- }
10990
- function onSelectStart(event) {
10991
- if (controls) controls.enabled = false;
10992
- setEvents({ enabled: false });
10993
- down(isDown = true);
10994
- gl.domElement.parentElement?.appendChild(element);
10995
- element.style.left = `${event.clientX}px`;
10996
- element.style.top = `${event.clientY}px`;
10997
- element.style.width = "0px";
10998
- element.style.height = "0px";
10999
- startPoint.x = event.clientX;
11000
- startPoint.y = event.clientY;
11001
- }
11002
- function onSelectMove(event) {
11003
- pointBottomRight.x = Math.max(startPoint.x, event.clientX);
11004
- pointBottomRight.y = Math.max(startPoint.y, event.clientY);
11005
- pointTopLeft.x = Math.min(startPoint.x, event.clientX);
11006
- pointTopLeft.y = Math.min(startPoint.y, event.clientY);
11007
- element.style.left = `${pointTopLeft.x}px`;
11008
- element.style.top = `${pointTopLeft.y}px`;
11009
- element.style.width = `${pointBottomRight.x - pointTopLeft.x}px`;
11010
- element.style.height = `${pointBottomRight.y - pointTopLeft.y}px`;
11266
+ React.useEffect(
11267
+ () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
11268
+ [props.stroke]
11269
+ );
11270
+ React.useEffect(
11271
+ () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
11272
+ [props.fill]
11273
+ );
11274
+ React.useEffect(
11275
+ () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
11276
+ [props.backfaceStroke]
11277
+ );
11278
+ }
11279
+
11280
+ function isWithGeometry(object) {
11281
+ return !!object?.geometry;
11282
+ }
11283
+ function isGeometry(object) {
11284
+ return !!object?.isBufferGeometry;
11285
+ }
11286
+ function isRefObject(object) {
11287
+ return !!object?.current;
11288
+ }
11289
+ function isRef(object) {
11290
+ return object?.current !== void 0;
11291
+ }
11292
+ function isWireframeGeometry(geometry) {
11293
+ return geometry.type === "WireframeGeometry";
11294
+ }
11295
+ function getUniforms() {
11296
+ const u = {};
11297
+ for (const key in WireframeMaterialShaders.uniforms) {
11298
+ u[key] = { value: WireframeMaterialShaders.uniforms[key] };
11299
+ }
11300
+ return u;
11301
+ }
11302
+ function getBarycentricCoordinates(geometry, removeEdge) {
11303
+ const position = geometry.getAttribute("position");
11304
+ const count = position.count;
11305
+ const barycentric = [];
11306
+ for (let i = 0; i < count; i++) {
11307
+ const even = i % 2 === 0;
11308
+ const Q = removeEdge ? 1 : 0;
11309
+ if (even) {
11310
+ barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
11311
+ } else {
11312
+ barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
11011
11313
  }
11012
- function onSelectOver() {
11013
- if (isDown) {
11014
- if (controls) controls.enabled = oldControlsEnabled;
11015
- setEvents({ enabled: oldRaycasterEnabled });
11016
- down(isDown = false);
11017
- element.parentElement?.removeChild(element);
11018
- }
11314
+ }
11315
+ return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
11316
+ }
11317
+ function getInputGeometry(inputGeometry) {
11318
+ const geo = isRefObject(inputGeometry) ? inputGeometry.current : inputGeometry;
11319
+ if (!isGeometry(geo)) {
11320
+ if (isWireframeGeometry(geo)) {
11321
+ throw new Error("Wireframe: WireframeGeometry is not supported.");
11019
11322
  }
11020
- function pointerDown(event) {
11021
- if (event.shiftKey) {
11022
- onSelectStart(event);
11023
- prepareRay(event, selBox.startPoint);
11323
+ const parent = geo.parent;
11324
+ if (isWithGeometry(parent)) {
11325
+ if (isWireframeGeometry(parent.geometry)) {
11326
+ throw new Error("Wireframe: WireframeGeometry is not supported.");
11024
11327
  }
11328
+ return parent.geometry;
11025
11329
  }
11026
- let previous = [];
11027
- function pointerMove(event) {
11028
- if (isDown) {
11029
- onSelectMove(event);
11030
- prepareRay(event, selBox.endPoint);
11031
- const allSelected = selBox.select().sort((o) => o.uuid).filter((o) => o.isMesh);
11032
- if (!shallow(allSelected, previous)) {
11033
- previous = allSelected;
11034
- dispatch({ object: customFilter(allSelected) });
11035
- }
11036
- }
11330
+ } else {
11331
+ return geo;
11332
+ }
11333
+ }
11334
+ function setBarycentricCoordinates(geometry, simplify) {
11335
+ if (geometry.index) {
11336
+ console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
11337
+ const nonIndexedGeo = geometry.toNonIndexed();
11338
+ geometry.copy(nonIndexedGeo);
11339
+ geometry.setIndex(null);
11340
+ }
11341
+ const newBarycentric = getBarycentricCoordinates(geometry, simplify);
11342
+ geometry.setAttribute("barycentric", newBarycentric);
11343
+ }
11344
+ function WireframeWithCustomGeo({
11345
+ geometry: customGeometry,
11346
+ simplify = false,
11347
+ ...props
11348
+ }) {
11349
+ extend({ MeshWireframeMaterial: WireframeMaterial });
11350
+ const [geometry, setGeometry] = React.useState(null);
11351
+ React.useLayoutEffect(() => {
11352
+ const geom = getInputGeometry(customGeometry);
11353
+ if (!geom) {
11354
+ throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
11037
11355
  }
11038
- function pointerUp(event) {
11039
- if (isDown) onSelectOver();
11356
+ setBarycentricCoordinates(geom, simplify);
11357
+ if (isRef(customGeometry)) {
11358
+ setGeometry(geom);
11040
11359
  }
11041
- document.addEventListener("pointerdown", pointerDown, { passive: true });
11042
- document.addEventListener("pointermove", pointerMove, { passive: true, capture: true });
11043
- document.addEventListener("pointerup", pointerUp, { passive: true });
11044
- return () => {
11045
- document.removeEventListener("pointerdown", pointerDown);
11046
- document.removeEventListener("pointermove", pointerMove, true);
11047
- document.removeEventListener("pointerup", pointerUp);
11048
- };
11049
- }, [size.width, size.height, raycaster, camera, controls, gl]);
11050
- return /* @__PURE__ */ React.createElement(
11051
- "group",
11360
+ }, [simplify, customGeometry]);
11361
+ const drawnGeo = isRef(customGeometry) ? geometry : customGeometry;
11362
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
11363
+ "meshWireframeMaterial",
11052
11364
  {
11053
- ref,
11054
- onClick,
11055
- onPointerOver: () => hover(true),
11056
- onPointerOut: () => hover(false),
11057
- onPointerMissed,
11365
+ attach: "material",
11366
+ transparent: true,
11367
+ side: THREE.DoubleSide,
11368
+ polygonOffset: true,
11369
+ polygonOffsetFactor: -4,
11058
11370
  ...props
11059
- },
11060
- /* @__PURE__ */ React.createElement(context.Provider, { value: active }, children)
11061
- );
11062
- }
11063
- function useSelect() {
11064
- return React.useContext(context);
11371
+ }
11372
+ )));
11065
11373
  }
11066
-
11067
- function AsciiRenderer({
11068
- renderIndex = 1,
11069
- bgColor = "black",
11070
- fgColor = "white",
11071
- characters = " .:-+*=%@#",
11072
- invert = true,
11073
- color = false,
11074
- resolution = 0.15
11374
+ function WireframeWithoutCustomGeo({
11375
+ simplify = false,
11376
+ ...props
11075
11377
  }) {
11076
- const { size, gl, scene, camera } = useThree();
11077
- const effect = React.useMemo(() => {
11078
- const effect2 = new AsciiEffect(gl, characters, { invert, color, resolution });
11079
- effect2.domElement.style.position = "absolute";
11080
- effect2.domElement.style.top = "0px";
11081
- effect2.domElement.style.left = "0px";
11082
- effect2.domElement.style.pointerEvents = "none";
11083
- return effect2;
11084
- }, [characters, invert, color, resolution]);
11378
+ const objectRef = React.useRef(null);
11379
+ const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
11380
+ useWireframeUniforms(uniforms, props);
11085
11381
  React.useLayoutEffect(() => {
11086
- effect.domElement.style.color = fgColor;
11087
- effect.domElement.style.backgroundColor = bgColor;
11088
- }, [fgColor, bgColor]);
11089
- React.useEffect(() => {
11090
- gl.domElement.style.opacity = "0";
11091
- gl.domElement.parentNode.appendChild(effect.domElement);
11382
+ const geom = getInputGeometry(objectRef);
11383
+ if (!geom) {
11384
+ throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
11385
+ }
11386
+ const og = geom.clone();
11387
+ setBarycentricCoordinates(geom, simplify);
11092
11388
  return () => {
11093
- gl.domElement.style.opacity = "1";
11094
- gl.domElement.parentNode.removeChild(effect.domElement);
11389
+ geom.copy(og);
11390
+ og.dispose();
11095
11391
  };
11096
- }, [effect]);
11097
- React.useEffect(() => {
11098
- effect.setSize(size.width, size.height);
11099
- }, [effect, size]);
11100
- useFrame((state) => {
11101
- effect.render(scene, camera);
11102
- }, renderIndex);
11103
- return /* @__PURE__ */ React.createElement(React.Fragment, null);
11392
+ }, [simplify]);
11393
+ React.useLayoutEffect(() => {
11394
+ const parentMesh = objectRef.current.parent;
11395
+ const og = parentMesh.material.clone();
11396
+ setWireframeOverride(parentMesh.material, uniforms);
11397
+ return () => {
11398
+ parentMesh.material.dispose();
11399
+ parentMesh.material = og;
11400
+ };
11401
+ }, []);
11402
+ return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
11403
+ }
11404
+ function Wireframe({ geometry: customGeometry, ...props }) {
11405
+ if (customGeometry) {
11406
+ return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
11407
+ }
11408
+ return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
11104
11409
  }
11105
11410
 
11106
11411
  function BakeShadows() {
@@ -11850,7 +12155,7 @@ function VolumetricMesh({
11850
12155
  depthWrite: true,
11851
12156
  depthTest: false
11852
12157
  });
11853
- const copyQuad = new FullScreenQuad$1(copyMaterial);
12158
+ const copyQuad = new FullScreenQuad(copyMaterial);
11854
12159
  return { depthCopyTarget, copyMaterial, copyQuad };
11855
12160
  }, [depthBuffer]);
11856
12161
  React.useEffect(() => {
@@ -11964,7 +12269,7 @@ function SpotlightShadowWithShader({
11964
12269
  });
11965
12270
  React.useEffect(() => void (uniforms.current.uShadowMap.value = map), [map]);
11966
12271
  const fsQuad = React.useMemo(
11967
- () => new FullScreenQuad$1(
12272
+ () => new FullScreenQuad(
11968
12273
  new ShaderMaterial({
11969
12274
  uniforms: uniforms.current,
11970
12275
  vertexShader: (