@react-three/drei 11.0.0-alpha.4 → 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,9 +2,8 @@ import * as React from 'react';
2
2
  import { forwardRef, useImperativeHandle, useMemo, useEffect, useCallback, useRef, useLayoutEffect, useState, Fragment, createContext, useContext } from 'react';
3
3
  import { useThree, useFrame, extend, createPortal, 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
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';
@@ -188,6 +187,7 @@ function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
188
187
  });
189
188
  }
190
189
  this.uniforms = THREE.UniformsUtils.clone(this.uniforms);
190
+ Object.assign(this, parameters);
191
191
  onInit?.(this);
192
192
  }
193
193
  };
@@ -1517,6 +1517,7 @@ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends MeshStandardMa
1517
1517
  uniform float depthScale;
1518
1518
  uniform float depthToBlurRatioBias;
1519
1519
  varying vec4 my_vUv;
1520
+ #include <tonemapping_pars_fragment>
1520
1521
  ${shader.fragmentShader}`;
1521
1522
  shader.fragmentShader = shader.fragmentShader.replace(
1522
1523
  "#include <emissivemap_fragment>",
@@ -1577,6 +1578,9 @@ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends MeshStandardMa
1577
1578
  merge = mix(merge, blur, blurFactor);
1578
1579
  #endif
1579
1580
 
1581
+ // Apply tone mapping to HDR reflection values before contrast adjustment
1582
+ merge.rgb = toneMapping(merge.rgb);
1583
+
1580
1584
  vec4 newMerge = vec4(0.0, 0.0, 0.0, 1.0);
1581
1585
  newMerge.r = (merge.r - 0.5) * mixContrast + 0.5;
1582
1586
  newMerge.g = (merge.g - 0.5) * mixContrast + 0.5;
@@ -1835,6 +1839,7 @@ const MeshReflectorMaterial = /* @__PURE__ */ React.forwardRef(
1835
1839
  useFrame(() => {
1836
1840
  const parent = materialRef.current.parent || materialRef.current?.__r3f.parent?.object;
1837
1841
  if (!parent) return;
1842
+ if (gl.xr.isPresenting) return;
1838
1843
  parent.visible = false;
1839
1844
  const currentXrEnabled = gl.xr.enabled;
1840
1845
  const currentShadowAutoUpdate = gl.shadowMap.autoUpdate;
@@ -2248,6 +2253,8 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2248
2253
  children,
2249
2254
  events = void 0,
2250
2255
  blur = 0,
2256
+ blend = 0,
2257
+ samples = 8,
2251
2258
  eventPriority = 0,
2252
2259
  renderPriority = 0,
2253
2260
  worldUnits = false,
@@ -2260,7 +2267,7 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2260
2267
  const maskRenderTarget = useFBO(resolution, resolution);
2261
2268
  const [priority, setPriority] = React.useState(0);
2262
2269
  useFrame(() => {
2263
- const p = ref.current.blend > 0 ? Math.max(1, renderPriority) : 0;
2270
+ const p = (ref.current.blend ?? 0) > 0 ? Math.max(1, renderPriority) : 0;
2264
2271
  if (priority !== p) setPriority(p);
2265
2272
  });
2266
2273
  React.useEffect(() => {
@@ -2334,7 +2341,8 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
2334
2341
  frames: visible ? Infinity : 0,
2335
2342
  eventPriority,
2336
2343
  renderPriority,
2337
- compute
2344
+ compute,
2345
+ samples
2338
2346
  },
2339
2347
  children,
2340
2348
  /* @__PURE__ */ React.createElement(
@@ -2414,7 +2422,7 @@ function ManagePortalScene({
2414
2422
  scene.matrixWorld.copy(parent.matrixWorld);
2415
2423
  } else scene.matrixWorld.identity();
2416
2424
  if (priority) {
2417
- if (material.current?.blend > 0 && material.current?.blend < 1) {
2425
+ if (material.current?.blend && material.current?.blend > 0 && material.current?.blend < 1) {
2418
2426
  blend.value = material.current.blend;
2419
2427
  state.gl.setRenderTarget(buffer1);
2420
2428
  state.gl.render(scene, state.camera);
@@ -3207,12 +3215,12 @@ const Segments = /* @__PURE__ */ React.forwardRef((props, forwardedRef) => {
3207
3215
  for (let i = 0; i < limit; i++) {
3208
3216
  const segment = segments[i]?.current;
3209
3217
  if (segment) {
3210
- positions[i * 6 + 0] = segment.start.x;
3211
- positions[i * 6 + 1] = segment.start.y;
3212
- positions[i * 6 + 2] = segment.start.z;
3213
- positions[i * 6 + 3] = segment.end.x;
3214
- positions[i * 6 + 4] = segment.end.y;
3215
- 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;
3216
3224
  colors[i * 6 + 0] = segment.color.r;
3217
3225
  colors[i * 6 + 1] = segment.color.g;
3218
3226
  colors[i * 6 + 2] = segment.color.b;
@@ -3247,7 +3255,10 @@ class SegmentObject {
3247
3255
  this.end = new THREE.Vector3(0, 0, 0);
3248
3256
  }
3249
3257
  }
3250
- 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
+ };
3251
3262
  const Segment = /* @__PURE__ */ React.forwardRef(({ color, start, end }, forwardedRef) => {
3252
3263
  const api = React.useContext(context$9);
3253
3264
  if (!api) throw "Segment must used inside Segments component.";
@@ -3569,6 +3580,81 @@ const ArcballControls = /* @__PURE__ */ forwardRef(
3569
3580
  }
3570
3581
  );
3571
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
+
3572
3658
  const DeviceOrientationControls = /* @__PURE__ */ React.forwardRef(
3573
3659
  (props, ref) => {
3574
3660
  const { camera, onChange, makeDefault, ...rest } = props;
@@ -3638,8 +3724,9 @@ const DragControls = React.forwardRef(
3638
3724
  onDragStart && onDragStart(initialModelPosition);
3639
3725
  invalidate();
3640
3726
  },
3641
- onDrag: ({ xy: [dragX, dragY], intentional }) => {
3727
+ onDrag: ({ xy: [dragX, dragY], intentional, event }) => {
3642
3728
  if (!intentional) return;
3729
+ if (dragConfig && dragConfig.preventOverlap) event.preventDefault();
3643
3730
  const normalizedMouseX = (dragX - size.left) / size.width * 2 - 1;
3644
3731
  const normalizedMouseY = -((dragY - size.top) / size.height) * 2 + 1;
3645
3732
  mousePosition2D.set(normalizedMouseX, normalizedMouseY);
@@ -3808,12 +3895,21 @@ function KeyboardControls({ map, children, onChange, domElement }) {
3808
3895
  obj.pressed = false;
3809
3896
  if (up) fn(false);
3810
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
+ };
3811
3905
  const source = domElement || window;
3812
3906
  source.addEventListener("keydown", downHandler, { passive: true });
3813
3907
  source.addEventListener("keyup", upHandler, { passive: true });
3908
+ window.addEventListener("blur", blurHandler);
3814
3909
  return () => {
3815
3910
  source.removeEventListener("keydown", downHandler);
3816
3911
  source.removeEventListener("keyup", upHandler);
3912
+ window.removeEventListener("blur", blurHandler);
3817
3913
  };
3818
3914
  }, [domElement, key]);
3819
3915
  return /* @__PURE__ */ React.createElement(context$8.Provider, { value: api, children });
@@ -4164,7 +4260,13 @@ function PresentationControls({
4164
4260
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
4165
4261
  }
4166
4262
 
4263
+ function roundToPixelRatio(value) {
4264
+ const ratio = window.devicePixelRatio || 1;
4265
+ return Math.round(value * ratio) / ratio;
4266
+ }
4267
+
4167
4268
  const context$7 = /* @__PURE__ */ React.createContext(null);
4269
+ const rootCache = /* @__PURE__ */ new WeakMap();
4168
4270
  function useScroll() {
4169
4271
  return React.useContext(context$7);
4170
4272
  }
@@ -4178,6 +4280,7 @@ function ScrollControls({
4178
4280
  damping = 0.25,
4179
4281
  maxSpeed = Infinity,
4180
4282
  prepend = false,
4283
+ pixelPerfect = true,
4181
4284
  style = {},
4182
4285
  children
4183
4286
  }) {
@@ -4199,6 +4302,7 @@ function ScrollControls({
4199
4302
  delta: 0,
4200
4303
  scroll,
4201
4304
  pages,
4305
+ pixelPerfect,
4202
4306
  // 0-1 for a range between from -> from + distance
4203
4307
  range(from, distance2, margin = 0) {
4204
4308
  const start = from - margin;
@@ -4217,7 +4321,7 @@ function ScrollControls({
4217
4321
  }
4218
4322
  };
4219
4323
  return state2;
4220
- }, [eps, damping, horizontal, pages]);
4324
+ }, [eps, damping, horizontal, pages, pixelPerfect]);
4221
4325
  React.useEffect(() => {
4222
4326
  el.style.position = "absolute";
4223
4327
  el.style.width = "100%";
@@ -4330,10 +4434,23 @@ const ScrollHtml = React.forwardRef(
4330
4434
  React.useImperativeHandle(ref, () => group.current, []);
4331
4435
  const { width, height } = useThree((state2) => state2.size);
4332
4436
  const fiberState = React.useContext(context$a);
4333
- 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]);
4334
4445
  useFrame(() => {
4335
4446
  if (state.delta > state.eps) {
4336
- 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)`;
4337
4454
  }
4338
4455
  });
4339
4456
  root.render(
@@ -5262,460 +5379,89 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
5262
5379
  }
5263
5380
  );
5264
5381
 
5265
- const WireframeMaterialShaders = {
5266
- uniforms: {
5267
- strokeOpacity: 1,
5268
- fillOpacity: 0.25,
5269
- fillMix: 0,
5270
- thickness: 0.05,
5271
- colorBackfaces: false,
5272
- dashInvert: true,
5273
- dash: false,
5274
- dashRepeats: 4,
5275
- dashLength: 0.5,
5276
- squeeze: false,
5277
- squeezeMin: 0.2,
5278
- squeezeMax: 1,
5279
- stroke: /* @__PURE__ */ new THREE.Color("#ff0000"),
5280
- backfaceStroke: /* @__PURE__ */ new THREE.Color("#0000ff"),
5281
- fill: /* @__PURE__ */ new THREE.Color("#00ff00")
5282
- },
5283
- vertex: (
5284
- /* glsl */
5285
- `
5286
- attribute vec3 barycentric;
5287
-
5288
- varying vec3 v_edges_Barycentric;
5289
- varying vec3 v_edges_Position;
5290
-
5291
- void initWireframe() {
5292
- v_edges_Barycentric = barycentric;
5293
- v_edges_Position = position.xyz;
5294
- }
5295
- `
5296
- ),
5297
- fragment: (
5298
- /* glsl */
5299
- `
5300
- #ifndef PI
5301
- #define PI 3.1415926535897932384626433832795
5302
- #endif
5303
-
5304
- varying vec3 v_edges_Barycentric;
5305
- varying vec3 v_edges_Position;
5306
-
5307
- uniform float strokeOpacity;
5308
- uniform float fillOpacity;
5309
- uniform float fillMix;
5310
- uniform float thickness;
5311
- uniform bool colorBackfaces;
5312
-
5313
- // Dash
5314
- uniform bool dashInvert;
5315
- uniform bool dash;
5316
- uniform bool dashOnly;
5317
- uniform float dashRepeats;
5318
- uniform float dashLength;
5319
-
5320
- // Squeeze
5321
- uniform bool squeeze;
5322
- uniform float squeezeMin;
5323
- uniform float squeezeMax;
5324
-
5325
- // Colors
5326
- uniform vec3 stroke;
5327
- uniform vec3 backfaceStroke;
5328
- uniform vec3 fill;
5329
-
5330
- // This is like
5331
- float wireframe_aastep(float threshold, float dist) {
5332
- float afwidth = fwidth(dist) * 0.5;
5333
- return smoothstep(threshold - afwidth, threshold + afwidth, dist);
5334
- }
5335
-
5336
- float wireframe_map(float value, float min1, float max1, float min2, float max2) {
5337
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
5338
- }
5339
-
5340
- float getWireframe() {
5341
- vec3 barycentric = v_edges_Barycentric;
5342
-
5343
- // Distance from center of each triangle to its edges.
5344
- float d = min(min(barycentric.x, barycentric.y), barycentric.z);
5345
-
5346
- // for dashed rendering, we can use this to get the 0 .. 1 value of the line length
5347
- float positionAlong = max(barycentric.x, barycentric.y);
5348
- if (barycentric.y < barycentric.x && barycentric.y < barycentric.z) {
5349
- positionAlong = 1.0 - positionAlong;
5350
- }
5351
-
5352
- // the thickness of the stroke
5353
- float computedThickness = wireframe_map(thickness, 0.0, 1.0, 0.0, 0.34);
5354
-
5355
- // if we want to shrink the thickness toward the center of the line segment
5356
- if (squeeze) {
5357
- computedThickness *= mix(squeezeMin, squeezeMax, (1.0 - sin(positionAlong * PI)));
5358
- }
5359
-
5360
- // Create dash pattern
5361
- if (dash) {
5362
- // here we offset the stroke position depending on whether it
5363
- // should overlap or not
5364
- float offset = 1.0 / dashRepeats * dashLength / 2.0;
5365
- if (!dashInvert) {
5366
- offset += 1.0 / dashRepeats / 2.0;
5367
- }
5368
-
5369
- // if we should animate the dash or not
5370
- // if (dashAnimate) {
5371
- // offset += time * 0.22;
5372
- // }
5373
-
5374
- // create the repeating dash pattern
5375
- float pattern = fract((positionAlong + offset) * dashRepeats);
5376
- computedThickness *= 1.0 - wireframe_aastep(dashLength, pattern);
5377
- }
5378
-
5379
- // compute the anti-aliased stroke edge
5380
- float edge = 1.0 - wireframe_aastep(computedThickness, d);
5381
-
5382
- return edge;
5383
- }
5384
- `
5385
- )
5386
- };
5387
- const WireframeMaterial = /* @__PURE__ */ shaderMaterial(
5388
- WireframeMaterialShaders.uniforms,
5389
- WireframeMaterialShaders.vertex + /* glsl */
5390
- `
5391
- void main() {
5392
- initWireframe();
5393
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
5394
- }
5395
- `,
5396
- WireframeMaterialShaders.fragment + /* glsl */
5397
- `
5398
- void main () {
5399
- // Compute color
5400
-
5401
- float edge = getWireframe();
5402
- vec4 colorStroke = vec4(stroke, edge);
5403
-
5404
- #ifdef FLIP_SIDED
5405
- colorStroke.rgb = backfaceStroke;
5406
- #endif
5407
-
5408
- vec4 colorFill = vec4(fill, fillOpacity);
5409
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
5410
-
5411
- gl_FragColor = outColor;
5412
- }
5413
- `
5414
- );
5415
- function setWireframeOverride(material, uniforms) {
5416
- material.onBeforeCompile = (shader) => {
5417
- shader.uniforms = {
5418
- ...shader.uniforms,
5419
- ...uniforms
5420
- };
5421
- shader.vertexShader = shader.vertexShader.replace(
5422
- "void main() {",
5423
- `
5424
- ${WireframeMaterialShaders.vertex}
5425
- void main() {
5426
- initWireframe();
5427
- `
5428
- );
5429
- shader.fragmentShader = shader.fragmentShader.replace(
5430
- "void main() {",
5431
- `
5432
- ${WireframeMaterialShaders.fragment}
5433
- void main() {
5434
- `
5435
- );
5436
- shader.fragmentShader = shader.fragmentShader.replace(
5437
- "#include <color_fragment>",
5438
- /* glsl */
5439
- `
5440
- #include <color_fragment>
5441
- float edge = getWireframe();
5442
- vec4 colorStroke = vec4(stroke, edge);
5443
- #ifdef FLIP_SIDED
5444
- colorStroke.rgb = backfaceStroke;
5445
- #endif
5446
- vec4 colorFill = vec4(mix(diffuseColor.rgb, fill, fillMix), mix(diffuseColor.a, fillOpacity, fillMix));
5447
- vec4 outColor = mix(colorFill, colorStroke, edge * strokeOpacity);
5448
-
5449
- diffuseColor.rgb = outColor.rgb;
5450
- diffuseColor.a *= outColor.a;
5451
- `
5452
- );
5453
- };
5454
- material.side = THREE.DoubleSide;
5455
- material.transparent = true;
5456
- }
5457
- function useWireframeUniforms(uniforms, props) {
5458
- React.useEffect(
5459
- () => void (uniforms.fillOpacity.value = props.fillOpacity ?? uniforms.fillOpacity.value),
5460
- [props.fillOpacity]
5461
- );
5462
- React.useEffect(() => void (uniforms.fillMix.value = props.fillMix ?? uniforms.fillMix.value), [props.fillMix]);
5463
- React.useEffect(
5464
- () => void (uniforms.strokeOpacity.value = props.strokeOpacity ?? uniforms.strokeOpacity.value),
5465
- [props.strokeOpacity]
5466
- );
5467
- React.useEffect(
5468
- () => void (uniforms.thickness.value = props.thickness ?? uniforms.thickness.value),
5469
- [props.thickness]
5470
- );
5471
- React.useEffect(() => void (uniforms.colorBackfaces.value = !!props.colorBackfaces), [props.colorBackfaces]);
5472
- React.useEffect(() => void (uniforms.dash.value = !!props.dash), [props.dash]);
5473
- React.useEffect(() => void (uniforms.dashInvert.value = !!props.dashInvert), [props.dashInvert]);
5474
- React.useEffect(
5475
- () => void (uniforms.dashRepeats.value = props.dashRepeats ?? uniforms.dashRepeats.value),
5476
- [props.dashRepeats]
5477
- );
5478
- React.useEffect(
5479
- () => void (uniforms.dashLength.value = props.dashLength ?? uniforms.dashLength.value),
5480
- [props.dashLength]
5481
- );
5482
- React.useEffect(() => void (uniforms.squeeze.value = !!props.squeeze), [props.squeeze]);
5483
- React.useEffect(
5484
- () => void (uniforms.squeezeMin.value = props.squeezeMin ?? uniforms.squeezeMin.value),
5485
- [props.squeezeMin]
5486
- );
5487
- React.useEffect(
5488
- () => void (uniforms.squeezeMax.value = props.squeezeMax ?? uniforms.squeezeMax.value),
5489
- [props.squeezeMax]
5490
- );
5491
- React.useEffect(
5492
- () => void (uniforms.stroke.value = props.stroke ? new THREE.Color(props.stroke) : uniforms.stroke.value),
5493
- [props.stroke]
5494
- );
5495
- React.useEffect(
5496
- () => void (uniforms.fill.value = props.fill ? new THREE.Color(props.fill) : uniforms.fill.value),
5497
- [props.fill]
5498
- );
5499
- React.useEffect(
5500
- () => void (uniforms.backfaceStroke.value = props.backfaceStroke ? new THREE.Color(props.backfaceStroke) : uniforms.backfaceStroke.value),
5501
- [props.backfaceStroke]
5502
- );
5503
- }
5504
-
5505
- function isWithGeometry(object) {
5506
- return !!object?.geometry;
5507
- }
5508
- function isGeometry(object) {
5509
- return !!object?.isBufferGeometry;
5510
- }
5511
- function isRefObject$1(object) {
5512
- return !!object?.current;
5513
- }
5514
- function isRef$1(object) {
5515
- return object?.current !== void 0;
5516
- }
5517
- function isWireframeGeometry(geometry) {
5518
- return geometry.type === "WireframeGeometry";
5519
- }
5520
- function getUniforms() {
5521
- const u = {};
5522
- for (const key in WireframeMaterialShaders.uniforms) {
5523
- u[key] = { value: WireframeMaterialShaders.uniforms[key] };
5524
- }
5525
- return u;
5526
- }
5527
- function getBarycentricCoordinates(geometry, removeEdge) {
5528
- const position = geometry.getAttribute("position");
5529
- const count = position.count;
5530
- const barycentric = [];
5531
- for (let i = 0; i < count; i++) {
5532
- const even = i % 2 === 0;
5533
- const Q = removeEdge ? 1 : 0;
5534
- if (even) {
5535
- barycentric.push(0, 0, 1, 0, 1, 0, 1, 0, Q);
5536
- } else {
5537
- barycentric.push(0, 1, 0, 0, 0, 1, 1, 0, Q);
5538
- }
5539
- }
5540
- return new THREE.BufferAttribute(Float32Array.from(barycentric), 3);
5541
- }
5542
- function getInputGeometry(inputGeometry) {
5543
- const geo = isRefObject$1(inputGeometry) ? inputGeometry.current : inputGeometry;
5544
- if (!isGeometry(geo)) {
5545
- if (isWireframeGeometry(geo)) {
5546
- throw new Error("Wireframe: WireframeGeometry is not supported.");
5547
- }
5548
- const parent = geo.parent;
5549
- if (isWithGeometry(parent)) {
5550
- if (isWireframeGeometry(parent.geometry)) {
5551
- throw new Error("Wireframe: WireframeGeometry is not supported.");
5552
- }
5553
- return parent.geometry;
5554
- }
5555
- } else {
5556
- return geo;
5557
- }
5558
- }
5559
- function setBarycentricCoordinates(geometry, simplify) {
5560
- if (geometry.index) {
5561
- console.warn("Wireframe: Requires non-indexed geometry, converting to non-indexed geometry.");
5562
- const nonIndexedGeo = geometry.toNonIndexed();
5563
- geometry.copy(nonIndexedGeo);
5564
- geometry.setIndex(null);
5565
- }
5566
- const newBarycentric = getBarycentricCoordinates(geometry, simplify);
5567
- geometry.setAttribute("barycentric", newBarycentric);
5568
- }
5569
- function WireframeWithCustomGeo({
5570
- geometry: customGeometry,
5571
- simplify = false,
5572
- ...props
5573
- }) {
5574
- extend({ MeshWireframeMaterial: WireframeMaterial });
5575
- const [geometry, setGeometry] = React.useState(null);
5576
- React.useLayoutEffect(() => {
5577
- const geom = getInputGeometry(customGeometry);
5578
- if (!geom) {
5579
- throw new Error("Wireframe: geometry prop must be a BufferGeometry or a ref to a BufferGeometry.");
5580
- }
5581
- setBarycentricCoordinates(geom, simplify);
5582
- if (isRef$1(customGeometry)) {
5583
- setGeometry(geom);
5584
- }
5585
- }, [simplify, customGeometry]);
5586
- const drawnGeo = isRef$1(customGeometry) ? geometry : customGeometry;
5587
- return /* @__PURE__ */ React.createElement(React.Fragment, null, drawnGeo && /* @__PURE__ */ React.createElement("mesh", { geometry: drawnGeo }, /* @__PURE__ */ React.createElement(
5588
- "meshWireframeMaterial",
5589
- {
5590
- attach: "material",
5591
- transparent: true,
5592
- side: THREE.DoubleSide,
5593
- polygonOffset: true,
5594
- polygonOffsetFactor: -4,
5595
- ...props
5596
- }
5597
- )));
5598
- }
5599
- function WireframeWithoutCustomGeo({
5600
- simplify = false,
5601
- ...props
5602
- }) {
5603
- const objectRef = React.useRef(null);
5604
- const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
5605
- useWireframeUniforms(uniforms, props);
5606
- React.useLayoutEffect(() => {
5607
- const geom = getInputGeometry(objectRef);
5608
- if (!geom) {
5609
- throw new Error("Wireframe: Must be a child of a Mesh, Line or Points object or specify a geometry prop.");
5610
- }
5611
- const og = geom.clone();
5612
- setBarycentricCoordinates(geom, simplify);
5613
- return () => {
5614
- geom.copy(og);
5615
- og.dispose();
5616
- };
5617
- }, [simplify]);
5618
- React.useLayoutEffect(() => {
5619
- const parentMesh = objectRef.current.parent;
5620
- const og = parentMesh.material.clone();
5621
- setWireframeOverride(parentMesh.material, uniforms);
5622
- return () => {
5623
- parentMesh.material.dispose();
5624
- parentMesh.material = og;
5625
- };
5626
- }, []);
5627
- return /* @__PURE__ */ React.createElement("object3D", { ref: objectRef });
5628
- }
5629
- function Wireframe({ geometry: customGeometry, ...props }) {
5630
- if (customGeometry) {
5631
- return /* @__PURE__ */ React.createElement(WireframeWithCustomGeo, { geometry: customGeometry, ...props });
5632
- }
5633
- return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
5634
- }
5635
-
5636
- const Context = /* @__PURE__ */ React.createContext({});
5637
- const useGizmoContext = () => {
5638
- return React.useContext(Context);
5639
- };
5640
- const turnRate = 2 * Math.PI;
5641
- const dummy = /* @__PURE__ */ new Object3D();
5642
- const matrix = /* @__PURE__ */ new Matrix4();
5643
- const [q1, q2] = [/* @__PURE__ */ new Quaternion(), /* @__PURE__ */ new Quaternion()];
5644
- const target = /* @__PURE__ */ new Vector3();
5645
- const targetPosition = /* @__PURE__ */ new Vector3();
5646
- const isOrbitControls = (controls) => {
5647
- return "minPolarAngle" in controls;
5648
- };
5649
- const isCameraControls = (controls) => {
5650
- return "getTarget" in controls;
5651
- };
5652
- const GizmoHelper = ({
5653
- alignment = "bottom-right",
5654
- margin = [80, 80],
5655
- renderPriority = 1,
5656
- onUpdate,
5657
- onTarget,
5658
- children
5659
- }) => {
5660
- const size = useThree((state) => state.size);
5661
- const mainCamera = useThree((state) => state.camera);
5662
- const defaultControls = useThree((state) => state.controls);
5663
- const invalidate = useThree((state) => state.invalidate);
5664
- const gizmoRef = React.useRef(null);
5665
- const virtualCam = React.useRef(null);
5666
- const animating = React.useRef(false);
5667
- const radius = React.useRef(0);
5668
- const focusPoint = React.useRef(new Vector3(0, 0, 0));
5669
- const defaultUp = React.useRef(new Vector3(0, 0, 0));
5670
- React.useEffect(() => {
5671
- defaultUp.current.copy(mainCamera.up);
5672
- dummy.up.copy(mainCamera.up);
5673
- }, [mainCamera]);
5674
- const tweenCamera = React.useCallback(
5675
- (direction) => {
5676
- animating.current = true;
5677
- if (defaultControls || onTarget) {
5678
- focusPoint.current = onTarget?.() || (isCameraControls(defaultControls) ? defaultControls.getTarget(focusPoint.current) : defaultControls?.target);
5679
- }
5680
- radius.current = mainCamera.position.distanceTo(target);
5681
- q1.copy(mainCamera.quaternion);
5682
- targetPosition.copy(direction).multiplyScalar(radius.current).add(target);
5683
- dummy.lookAt(targetPosition);
5684
- q2.copy(dummy.quaternion);
5685
- invalidate();
5686
- },
5687
- [defaultControls, mainCamera, onTarget, invalidate]
5688
- );
5689
- useFrame((_, delta) => {
5690
- if (virtualCam.current && gizmoRef.current) {
5691
- if (animating.current) {
5692
- if (q1.angleTo(q2) < 0.01) {
5693
- animating.current = false;
5694
- if (isOrbitControls(defaultControls)) {
5695
- mainCamera.up.copy(defaultUp.current);
5696
- }
5697
- } else {
5698
- const step = delta * turnRate;
5699
- q1.rotateTowards(q2, step);
5700
- mainCamera.position.set(0, 0, 1).applyQuaternion(q1).multiplyScalar(radius.current).add(focusPoint.current);
5701
- mainCamera.up.set(0, 1, 0).applyQuaternion(q1).normalize();
5702
- mainCamera.quaternion.copy(q1);
5703
- if (isCameraControls(defaultControls))
5704
- defaultControls.setPosition(mainCamera.position.x, mainCamera.position.y, mainCamera.position.z);
5705
- if (onUpdate) onUpdate();
5706
- else if (defaultControls) defaultControls.update(delta);
5707
- invalidate();
5708
- }
5709
- }
5710
- matrix.copy(mainCamera.matrix).invert();
5711
- gizmoRef.current?.quaternion.setFromRotationMatrix(matrix);
5712
- }
5713
- });
5714
- const gizmoHelperContext = React.useMemo(() => ({ tweenCamera }), [tweenCamera]);
5715
- const [marginX, marginY] = margin;
5716
- const x = alignment.endsWith("-center") ? 0 : alignment.endsWith("-left") ? -size.width / 2 + marginX : size.width / 2 - marginX;
5717
- const y = alignment.startsWith("center-") ? 0 : alignment.startsWith("top-") ? size.height / 2 - marginY : -size.height / 2 + marginY;
5718
- 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)));
5719
5465
  };
5720
5466
 
5721
5467
  const colors = { bg: "#f0f0f0", hover: "#999", text: "black", stroke: "black" };
@@ -5928,6 +5674,11 @@ const GizmoViewport = ({
5928
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 }))));
5929
5675
  };
5930
5676
 
5677
+ const roundEven = (n) => {
5678
+ const rounded = Math.ceil(n);
5679
+ return rounded % 2 === 0 ? rounded : rounded + 1;
5680
+ };
5681
+
5931
5682
  const v1 = /* @__PURE__ */ new Vector3();
5932
5683
  const v2 = /* @__PURE__ */ new Vector3();
5933
5684
  const v3 = /* @__PURE__ */ new Vector3();
@@ -5999,7 +5750,7 @@ const getCameraCSSMatrix = /* @__PURE__ */ ((multipliers) => {
5999
5750
  const getObjectCSSMatrix = /* @__PURE__ */ ((scaleMultipliers) => {
6000
5751
  return (matrix, factor) => getCSSMatrix(matrix, scaleMultipliers(factor), "translate(-50%,-50%)");
6001
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]);
6002
- function isRefObject(ref) {
5753
+ function isRefObject$1(ref) {
6003
5754
  return ref && typeof ref === "object" && "current" in ref;
6004
5755
  }
6005
5756
  const Html = /* @__PURE__ */ React.forwardRef(
@@ -6026,9 +5777,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
6026
5777
  as = "div",
6027
5778
  wrapperClass,
6028
5779
  pointerEvents = "auto",
5780
+ pixelPerfect = true,
6029
5781
  ...props
6030
5782
  }, ref) => {
6031
5783
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
5784
+ const forceEven = useThree((state) => state.internal.forceEven);
6032
5785
  const [el] = React.useState(() => document.createElement(as));
6033
5786
  const root = React.useRef(null);
6034
5787
  const group = React.useRef(null);
@@ -6040,7 +5793,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
6040
5793
  const occlusionMeshRef = React.useRef(null);
6041
5794
  const isMeshSizeSet = React.useRef(false);
6042
5795
  const isRayCastOcclusion = React.useMemo(() => {
6043
- 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]);
6044
5797
  }, [occlude]);
6045
5798
  React.useLayoutEffect(() => {
6046
5799
  const el2 = gl.domElement;
@@ -6053,38 +5806,54 @@ const Html = /* @__PURE__ */ React.forwardRef(
6053
5806
  el2.style.position = null;
6054
5807
  el2.style.pointerEvents = null;
6055
5808
  }
6056
- }, [occlude]);
5809
+ }, [occlude, zIndexRange, gl.domElement]);
6057
5810
  React.useLayoutEffect(() => {
6058
5811
  if (group.current) {
6059
- const currentRoot = root.current = ReactDOM.createRoot(el);
5812
+ if (!root.current) {
5813
+ root.current = ReactDOM.createRoot(el);
5814
+ }
6060
5815
  scene.updateMatrixWorld();
6061
5816
  if (transform) {
6062
5817
  el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
6063
5818
  } else {
6064
- const vec = calculatePosition(group.current, camera, size);
6065
- 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;`;
6066
5820
  }
6067
5821
  if (target) {
6068
5822
  if (prepend) target.prepend(el);
6069
5823
  else target.appendChild(el);
6070
5824
  }
6071
5825
  return () => {
6072
- if (target) target.removeChild(el);
6073
- 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
+ }
6074
5833
  };
6075
5834
  }
6076
- }, [target, transform]);
5835
+ }, [target, transform, el, prepend, scene]);
6077
5836
  React.useLayoutEffect(() => {
6078
5837
  if (wrapperClass) el.className = wrapperClass;
6079
- }, [wrapperClass]);
5838
+ }, [wrapperClass, el]);
5839
+ React.useLayoutEffect(() => {
5840
+ return () => {
5841
+ if (root.current) {
5842
+ root.current.unmount();
5843
+ root.current = null;
5844
+ }
5845
+ };
5846
+ }, []);
6080
5847
  const styles = React.useMemo(() => {
5848
+ const width = forceEven ? roundEven(size.width) : size.width;
5849
+ const height = forceEven ? roundEven(size.height) : size.height;
6081
5850
  if (transform) {
6082
5851
  return {
6083
5852
  position: "absolute",
6084
5853
  top: 0,
6085
5854
  left: 0,
6086
- width: size.width,
6087
- height: size.height,
5855
+ width,
5856
+ height,
6088
5857
  transformStyle: "preserve-3d",
6089
5858
  pointerEvents: "none"
6090
5859
  };
@@ -6093,15 +5862,15 @@ const Html = /* @__PURE__ */ React.forwardRef(
6093
5862
  position: "absolute",
6094
5863
  transform: center ? "translate3d(-50%,-50%,0)" : "none",
6095
5864
  ...fullscreen && {
6096
- top: -size.height / 2,
6097
- left: -size.width / 2,
6098
- width: size.width,
6099
- 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
6100
5869
  },
6101
5870
  ...style
6102
5871
  };
6103
5872
  }
6104
- }, [style, center, fullscreen, size, transform]);
5873
+ }, [style, center, fullscreen, size, transform, forceEven]);
6105
5874
  const transformInnerStyles = React.useMemo(
6106
5875
  () => ({ position: "absolute", pointerEvents }),
6107
5876
  [pointerEvents]
@@ -6115,7 +5884,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
6115
5884
  } else {
6116
5885
  root.current?.render(/* @__PURE__ */ React.createElement("div", { ref, style: styles, className, children }));
6117
5886
  }
6118
- });
5887
+ }, [transform, styles, transformInnerStyles, ref, className, style, children]);
6119
5888
  const visible = React.useRef(true);
6120
5889
  useFrame((gl2) => {
6121
5890
  if (group.current) {
@@ -6147,7 +5916,12 @@ const Html = /* @__PURE__ */ React.forwardRef(
6147
5916
  const zRange = occlude ? isRayCastOcclusion ? [zIndexRange[0], halfRange] : [halfRange - 1, 0] : zIndexRange;
6148
5917
  el.style.zIndex = `${objectZIndex(group.current, camera, zRange)}`;
6149
5918
  if (transform) {
6150
- 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
+ }
6151
5925
  const fov = camera.projectionMatrix.elements[5] * heightHalf;
6152
5926
  const { isOrthographicCamera, top, left, bottom, right } = camera;
6153
5927
  const cameraMatrix = getCameraCSSMatrix(camera.matrixWorldInverse);
@@ -6158,8 +5932,10 @@ const Html = /* @__PURE__ */ React.forwardRef(
6158
5932
  matrix.elements[3] = matrix.elements[7] = matrix.elements[11] = 0;
6159
5933
  matrix.elements[15] = 1;
6160
5934
  }
6161
- el.style.width = size.width + "px";
6162
- 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";
6163
5939
  el.style.perspective = isOrthographicCamera ? "" : `${fov}px`;
6164
5940
  if (transformOuterRef.current && transformInnerRef.current) {
6165
5941
  transformOuterRef.current.style.transform = `${cameraTransform}${cameraMatrix}translate(${widthHalf}px,${heightHalf}px)`;
@@ -6167,7 +5943,13 @@ const Html = /* @__PURE__ */ React.forwardRef(
6167
5943
  }
6168
5944
  } else {
6169
5945
  const scale = distanceFactor === void 0 ? 1 : objectScale(group.current, camera) * distanceFactor;
6170
- 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})`;
6171
5953
  }
6172
5954
  oldPosition.current = vec;
6173
5955
  oldZoom.current = camera.zoom;
@@ -6273,6 +6055,11 @@ const Html = /* @__PURE__ */ React.forwardRef(
6273
6055
  );
6274
6056
 
6275
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
+ };
6276
6063
 
6277
6064
  const vec1$1 = /* @__PURE__ */ new THREE.Vector3();
6278
6065
  const vec2$1 = /* @__PURE__ */ new THREE.Vector3();
@@ -6307,6 +6094,8 @@ const AxisArrow = ({ direction, axis }) => {
6307
6094
  onDragStart,
6308
6095
  onDrag,
6309
6096
  onDragEnd,
6097
+ onHover,
6098
+ dragState,
6310
6099
  userData,
6311
6100
  LineComponent: Line
6312
6101
  } = React.useContext(context$4);
@@ -6338,7 +6127,13 @@ const AxisArrow = ({ direction, axis }) => {
6338
6127
  const onPointerMove = React.useCallback(
6339
6128
  (e) => {
6340
6129
  e.stopPropagation();
6341
- 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
+ }
6342
6137
  if (clickInfo.current) {
6343
6138
  const { clickPoint, dir } = clickInfo.current;
6344
6139
  const [min, max] = translationLimits?.[axis] || [void 0, void 0];
@@ -6357,7 +6152,7 @@ const AxisArrow = ({ direction, axis }) => {
6357
6152
  onDrag(offsetMatrix$1);
6358
6153
  }
6359
6154
  },
6360
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
6155
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
6361
6156
  );
6362
6157
  const onPointerUp = React.useCallback(
6363
6158
  (e) => {
@@ -6372,10 +6167,16 @@ const AxisArrow = ({ direction, axis }) => {
6372
6167
  },
6373
6168
  [annotations, camControls, onDragEnd]
6374
6169
  );
6375
- const onPointerOut = React.useCallback((e) => {
6376
- e.stopPropagation();
6377
- setIsHovered(false);
6378
- }, []);
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
+ );
6379
6180
  const { cylinderLength, coneWidth, coneLength, matrixL } = React.useMemo(() => {
6380
6181
  const coneWidth2 = fixed ? lineWidth / scale * 1.6 : scale / 20;
6381
6182
  const coneLength2 = fixed ? 0.2 : scale / 5;
@@ -6479,6 +6280,8 @@ const rotMatrix = /* @__PURE__ */ new THREE.Matrix4();
6479
6280
  const posNew = /* @__PURE__ */ new THREE.Vector3();
6480
6281
  const ray$1 = /* @__PURE__ */ new THREE.Ray();
6481
6282
  const intersection$1 = /* @__PURE__ */ new THREE.Vector3();
6283
+ const viewPlane = /* @__PURE__ */ new THREE.Plane();
6284
+ const viewIntersection = /* @__PURE__ */ new THREE.Vector3();
6482
6285
  const AxisRotator = ({
6483
6286
  dir1,
6484
6287
  dir2,
@@ -6499,6 +6302,8 @@ const AxisRotator = ({
6499
6302
  onDragStart,
6500
6303
  onDrag,
6501
6304
  onDragEnd,
6305
+ onHover,
6306
+ dragState,
6502
6307
  userData,
6503
6308
  LineComponent: Line
6504
6309
  } = React.useContext(context$4);
@@ -6532,14 +6337,29 @@ const AxisRotator = ({
6532
6337
  const onPointerMove = React.useCallback(
6533
6338
  (e) => {
6534
6339
  e.stopPropagation();
6535
- 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
+ }
6536
6347
  if (clickInfo.current) {
6537
6348
  const { clickPoint, origin, e1, e2, normal, plane } = clickInfo.current;
6538
6349
  const [min, max] = rotationLimits?.[axis] || [void 0, void 0];
6539
6350
  ray$1.copy(e.ray);
6540
- ray$1.intersectPlane(plane, intersection$1);
6541
- ray$1.direction.negate();
6542
- 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
+ }
6543
6363
  let deltaAngle = calculateAngle(clickPoint, intersection$1, origin, e1, e2);
6544
6364
  let degrees = toDegrees(deltaAngle);
6545
6365
  if (e.shiftKey) {
@@ -6565,7 +6385,7 @@ const AxisRotator = ({
6565
6385
  onDrag(rotMatrix);
6566
6386
  }
6567
6387
  },
6568
- [annotations, onDrag, isHovered, rotationLimits, axis]
6388
+ [annotations, onDrag, onHover, dragState, isHovered, rotationLimits, axis]
6569
6389
  );
6570
6390
  const onPointerUp = React.useCallback(
6571
6391
  (e) => {
@@ -6581,10 +6401,16 @@ const AxisRotator = ({
6581
6401
  },
6582
6402
  [annotations, camControls, onDragEnd]
6583
6403
  );
6584
- const onPointerOut = React.useCallback((e) => {
6585
- e.stopPropagation();
6586
- setIsHovered(false);
6587
- }, []);
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
+ );
6588
6414
  const matrixL = React.useMemo(() => {
6589
6415
  const dir1N = dir1.clone().normalize();
6590
6416
  const dir2N = dir2.clone().normalize();
@@ -6685,6 +6511,8 @@ const PlaneSlider = ({
6685
6511
  onDragStart,
6686
6512
  onDrag,
6687
6513
  onDragEnd,
6514
+ onHover,
6515
+ dragState,
6688
6516
  userData,
6689
6517
  LineComponent: Line
6690
6518
  } = React.useContext(context$4);
@@ -6720,7 +6548,13 @@ const PlaneSlider = ({
6720
6548
  const onPointerMove = React.useCallback(
6721
6549
  (e) => {
6722
6550
  e.stopPropagation();
6723
- 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
+ }
6724
6558
  if (clickInfo.current) {
6725
6559
  const { clickPoint, e1, e2, plane } = clickInfo.current;
6726
6560
  const [minX, maxX] = translationLimits?.[(axis + 1) % 3] || [void 0, void 0];
@@ -6756,7 +6590,7 @@ const PlaneSlider = ({
6756
6590
  onDrag(offsetMatrix);
6757
6591
  }
6758
6592
  },
6759
- [annotations, onDrag, isHovered, translation, translationLimits, axis]
6593
+ [annotations, onDrag, onHover, dragState, isHovered, translation, translationLimits, axis]
6760
6594
  );
6761
6595
  const onPointerUp = React.useCallback(
6762
6596
  (e) => {
@@ -6771,10 +6605,16 @@ const PlaneSlider = ({
6771
6605
  },
6772
6606
  [annotations, camControls, onDragEnd]
6773
6607
  );
6774
- const onPointerOut = React.useCallback((e) => {
6775
- e.stopPropagation();
6776
- setIsHovered(false);
6777
- }, []);
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
+ );
6778
6618
  const matrixL = React.useMemo(() => {
6779
6619
  const dir1N = dir1.clone().normalize();
6780
6620
  const dir2N = dir2.clone().normalize();
@@ -6912,6 +6752,8 @@ const ScalingSphere = ({ direction, axis }) => {
6912
6752
  onDragStart,
6913
6753
  onDrag,
6914
6754
  onDragEnd,
6755
+ onHover,
6756
+ dragState,
6915
6757
  userData
6916
6758
  } = React.useContext(context$4);
6917
6759
  const size = useThree((state) => state.size);
@@ -6948,7 +6790,13 @@ const ScalingSphere = ({ direction, axis }) => {
6948
6790
  const onPointerMove = React.useCallback(
6949
6791
  (e) => {
6950
6792
  e.stopPropagation();
6951
- 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
+ }
6952
6800
  if (clickInfo.current) {
6953
6801
  const { clickPoint, dir, mPLG, mPLGInv, offsetMultiplier } = clickInfo.current;
6954
6802
  const [min, max] = scaleLimits?.[axis] || [1e-5, void 0];
@@ -6974,7 +6822,7 @@ const ScalingSphere = ({ direction, axis }) => {
6974
6822
  onDrag(scaleMatrix);
6975
6823
  }
6976
6824
  },
6977
- [annotations, position, onDrag, isHovered, scaleLimits, axis]
6825
+ [annotations, position, onDrag, onHover, dragState, isHovered, scaleLimits, axis]
6978
6826
  );
6979
6827
  const onPointerUp = React.useCallback(
6980
6828
  (e) => {
@@ -6991,10 +6839,16 @@ const ScalingSphere = ({ direction, axis }) => {
6991
6839
  },
6992
6840
  [annotations, camControls, onDragEnd, position]
6993
6841
  );
6994
- const onPointerOut = React.useCallback((e) => {
6995
- e.stopPropagation();
6996
- setIsHovered(false);
6997
- }, []);
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
+ );
6998
6852
  const { radius, matrixL } = React.useMemo(() => {
6999
6853
  const radius2 = fixed ? lineWidth / scale * 1.8 : scale / 22.5;
7000
6854
  const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
@@ -7064,9 +6918,11 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7064
6918
  ({
7065
6919
  enabled = true,
7066
6920
  matrix,
6921
+ object,
7067
6922
  onDragStart,
7068
6923
  onDrag,
7069
6924
  onDragEnd,
6925
+ onHover,
7070
6926
  autoTransform = true,
7071
6927
  anchor,
7072
6928
  disableAxes = false,
@@ -7102,14 +6958,35 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7102
6958
  const gizmoRef = React.useRef(null);
7103
6959
  const childrenRef = React.useRef(null);
7104
6960
  const translation = React.useRef([0, 0, 0]);
6961
+ const dragState = React.useRef(null);
7105
6962
  const cameraScale = React.useRef(new THREE.Vector3(1, 1, 1));
7106
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]);
7107
6982
  React.useLayoutEffect(() => {
7108
6983
  if (!anchor) return;
7109
- childrenRef.current.updateWorldMatrix(true, true);
7110
- 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();
7111
6988
  bb.makeEmpty();
7112
- childrenRef.current.traverse((obj) => {
6989
+ anchorTarget.traverse((obj) => {
7113
6990
  if (!obj.geometry) return;
7114
6991
  if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
7115
6992
  mL.copy(obj.matrixWorld).premultiply(mPInv);
@@ -7124,9 +7001,17 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7124
7001
  gizmoRef.current.position.copy(vPosition);
7125
7002
  invalidate();
7126
7003
  });
7004
+ const mergedUserData = React.useMemo(
7005
+ () => ({
7006
+ ...userData,
7007
+ ...depthTest === false && { interactivePriority: 1 }
7008
+ }),
7009
+ [userData, depthTest]
7010
+ );
7127
7011
  const config = React.useMemo(
7128
7012
  () => ({
7129
7013
  onDragStart: (props2) => {
7014
+ dragState.current = props2;
7130
7015
  mL0.copy(ref.current.matrix);
7131
7016
  mW0.copy(ref.current.matrixWorld);
7132
7017
  onDragStart && onDragStart(props2);
@@ -7142,13 +7027,20 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7142
7027
  if (autoTransform) {
7143
7028
  ref.current.matrix.copy(mL);
7144
7029
  }
7030
+ const target = resolveObject(object);
7031
+ if (target) target.matrix.copy(mL);
7145
7032
  onDrag && onDrag(mL, mdL, mW, mdW);
7146
7033
  invalidate();
7147
7034
  },
7148
7035
  onDragEnd: () => {
7036
+ dragState.current = null;
7149
7037
  if (onDragEnd) onDragEnd();
7150
7038
  invalidate();
7151
7039
  },
7040
+ onHover: (props2) => {
7041
+ if (onHover) onHover(props2);
7042
+ },
7043
+ dragState,
7152
7044
  translation,
7153
7045
  translationLimits,
7154
7046
  rotationLimits,
@@ -7160,7 +7052,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7160
7052
  fixed,
7161
7053
  depthTest,
7162
7054
  renderOrder,
7163
- userData,
7055
+ userData: mergedUserData,
7164
7056
  annotations,
7165
7057
  annotationsClass,
7166
7058
  LineComponent: LineComp
@@ -7169,6 +7061,7 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7169
7061
  onDragStart,
7170
7062
  onDrag,
7171
7063
  onDragEnd,
7064
+ onHover,
7172
7065
  translation,
7173
7066
  translationLimits,
7174
7067
  rotationLimits,
@@ -7180,11 +7073,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
7180
7073
  ...axisColors,
7181
7074
  hoveredColor,
7182
7075
  opacity,
7183
- userData,
7076
+ mergedUserData,
7184
7077
  autoTransform,
7185
7078
  annotations,
7186
7079
  annotationsClass,
7187
- LineComp
7080
+ LineComp,
7081
+ object
7188
7082
  ]
7189
7083
  );
7190
7084
  const vec = new THREE.Vector3();
@@ -7546,7 +7440,7 @@ function createInstances() {
7546
7440
  ];
7547
7441
  }
7548
7442
  const InstancedAttribute = React.forwardRef(
7549
- ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage }, fref) => {
7443
+ ({ name, defaultValue, normalized, usage = THREE.DynamicDrawUsage, type = "float" }, fref) => {
7550
7444
  const ref = React.useRef(null);
7551
7445
  React.useImperativeHandle(fref, () => ref.current, []);
7552
7446
  React.useLayoutEffect(() => {
@@ -7554,7 +7448,26 @@ const InstancedAttribute = React.forwardRef(
7554
7448
  parent.geometry.attributes[name] = ref.current;
7555
7449
  const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
7556
7450
  const array = Array.from({ length: parent.userData.limit }, () => value).flat();
7557
- 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
+ }
7558
7471
  ref.current.itemSize = value.length;
7559
7472
  ref.current.count = array.length / ref.current.itemSize;
7560
7473
  return () => {
@@ -9079,12 +8992,17 @@ const Gltf = /* @__PURE__ */ React.forwardRef(
9079
8992
  }
9080
8993
  );
9081
8994
 
9082
- const cdn = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master";
9083
- 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) {
9084
8997
  const renderer = useThree((state) => state.renderer);
9085
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
+ }
9086
9005
  loader.detectSupport(renderer);
9087
- loader.setTranscoderPath(basisPath);
9088
9006
  });
9089
9007
  useEffect(() => {
9090
9008
  const array = Array.isArray(textures) ? textures : [textures];
@@ -9099,16 +9017,20 @@ function useKTX2(input, basisPath = `${cdn}/basis/`) {
9099
9017
  return textures;
9100
9018
  }
9101
9019
  }
9102
- useKTX2.preload = (url, basisPath = `${cdn}/basis/`) => useLoader.preload(KTX2Loader, url, (loader) => {
9103
- 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
+ }
9104
9027
  });
9105
9028
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
9106
- const Ktx2 = ({
9107
- children,
9108
- input,
9109
- basisPath
9110
- }) => {
9111
- 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);
9112
9034
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
9113
9035
  };
9114
9036
 
@@ -9584,18 +9506,21 @@ function Fisheye({
9584
9506
  const normal = new THREE.Vector3();
9585
9507
  const sph = new THREE.Sphere(new THREE.Vector3(), radius);
9586
9508
  const normalMatrix = new THREE.Matrix3();
9587
- const compute = React.useCallback((event, state, prev) => {
9588
- state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
9589
- state.raycaster.setFromCamera(state.pointer, orthoC);
9590
- if (!state.raycaster.ray.intersectSphere(sph, normal)) return;
9591
- else normal.normalize();
9592
- normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld);
9593
- cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin);
9594
- state.raycaster.ray.direction.set(0, 0, 1).reflect(normal);
9595
- state.raycaster.ray.direction.x *= -1;
9596
- state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1);
9597
- return void 0;
9598
- }, []);
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
+ );
9599
9524
  useFrame(
9600
9525
  ({ gl }) => {
9601
9526
  if (renderPriority) gl.render(sphere.current, orthoC);
@@ -9730,14 +9655,22 @@ const isWebGPURenderer = (def) => !!def.isWebGPURenderer;
9730
9655
  const col = /* @__PURE__ */ new THREE.Color();
9731
9656
  const TUNNEL_KEY = "__drei_view_tunnel__";
9732
9657
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
9733
- function computeContainerPosition(canvasSize, trackRect) {
9658
+ function computeContainerPosition(canvasSize, trackRect, forceEven) {
9734
9659
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
9735
9660
  const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
9736
9661
  const canvasBottom = canvasSize.top + canvasSize.height;
9737
- const bottom = canvasBottom - trackBottom;
9738
- const left = trackLeft - canvasSize.left;
9739
- const top = trackTop - canvasSize.top;
9740
- 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 };
9741
9674
  }
9742
9675
  function prepareSkissor(renderer, camera, {
9743
9676
  left,
@@ -9777,6 +9710,7 @@ function clear(renderer) {
9777
9710
  }
9778
9711
  function Container({ visible = true, canvasSize, scene, index, children, frames, rect, track }) {
9779
9712
  const { scene: portalScene, camera: portalCamera, renderer, get, setEvents } = useThree();
9713
+ const forceEven = useThree((state) => state.internal.forceEven);
9780
9714
  const [isOffscreen, setOffscreen] = React.useState(false);
9781
9715
  const mountedRef = React.useRef(true);
9782
9716
  React.useEffect(() => {
@@ -9794,7 +9728,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9794
9728
  frameCount++;
9795
9729
  }
9796
9730
  if (rect.current) {
9797
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current);
9731
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, rect.current, forceEven);
9798
9732
  if (isOffscreen !== _isOffscreen) setOffscreen(_isOffscreen);
9799
9733
  if (visible && !_isOffscreen && rect.current) {
9800
9734
  const targetScene = children ? portalScene : scene;
@@ -9816,14 +9750,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9816
9750
  React.useLayoutEffect(() => {
9817
9751
  const curRect = rect.current;
9818
9752
  if (curRect && (!visible || !isOffscreen)) {
9819
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
9753
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
9820
9754
  if (!_isOffscreen) {
9821
9755
  const autoClear = prepareSkissor(renderer, portalCamera, position);
9822
9756
  clear(renderer);
9823
9757
  finishSkissor(renderer, autoClear);
9824
9758
  }
9825
9759
  }
9826
- }, [visible, isOffscreen, renderer, portalCamera, canvasSize]);
9760
+ }, [visible, isOffscreen, renderer, portalCamera, canvasSize, forceEven]);
9827
9761
  React.useEffect(() => {
9828
9762
  if (!track) return;
9829
9763
  const curRect = rect.current;
@@ -9831,7 +9765,7 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9831
9765
  setEvents({ connected: track.current });
9832
9766
  return () => {
9833
9767
  if (curRect) {
9834
- const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect);
9768
+ const { position, isOffscreen: _isOffscreen } = computeContainerPosition(canvasSize, curRect, forceEven);
9835
9769
  if (!_isOffscreen) {
9836
9770
  const autoClear = prepareSkissor(renderer, portalCamera, position);
9837
9771
  clear(renderer);
@@ -9840,13 +9774,14 @@ function Container({ visible = true, canvasSize, scene, index, children, frames,
9840
9774
  }
9841
9775
  setEvents({ connected: old });
9842
9776
  };
9843
- }, [track, renderer, portalCamera, canvasSize, get, setEvents]);
9777
+ }, [track, renderer, portalCamera, canvasSize, get, setEvents, forceEven]);
9844
9778
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement("group", { onPointerOver: () => null }));
9845
9779
  }
9846
9780
  const CanvasView = /* @__PURE__ */ React.forwardRef(
9847
9781
  ({ track, visible = true, index = 1, id, style, className, frames = Infinity, children, ...props }, fref) => {
9848
9782
  const rect = React.useRef(null);
9849
9783
  const { size, scene } = useThree();
9784
+ const forceEven = useThree((state) => state.internal.forceEven);
9850
9785
  const [virtualScene] = React.useState(() => new THREE.Scene());
9851
9786
  const [ready, toggle] = React.useReducer(() => true, false);
9852
9787
  const compute = React.useCallback(
@@ -9883,12 +9818,12 @@ const CanvasView = /* @__PURE__ */ React.forwardRef(
9883
9818
  {
9884
9819
  events: { compute, priority: index },
9885
9820
  size: {
9886
- width: rect.current?.width,
9887
- 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,
9888
9823
  // @ts-ignore
9889
- top: rect.current?.top,
9824
+ top: forceEven && rect.current?.top ? roundEven(rect.current.top) : rect.current?.top,
9890
9825
  // @ts-ignore
9891
- left: rect.current?.left
9826
+ left: forceEven && rect.current?.left ? roundEven(rect.current.left) : rect.current?.left
9892
9827
  }
9893
9828
  }
9894
9829
  ));
@@ -10315,8 +10250,8 @@ function getLoader(extension) {
10315
10250
  return loader;
10316
10251
  }
10317
10252
 
10318
- const isRef = (obj) => obj.current && obj.current.isScene;
10319
- 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;
10320
10255
  function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
10321
10256
  sceneProps = {
10322
10257
  backgroundBlurriness: 0,
@@ -10673,433 +10608,804 @@ const Sky = /* @__PURE__ */ React.forwardRef(
10673
10608
  }
10674
10609
  );
10675
10610
 
10676
- const presets = {
10677
- rembrandt: {
10678
- main: [1, 2, 1],
10679
- fill: [-2, -0.5, -2]
10680
- },
10681
- portrait: {
10682
- main: [-1, 2, 0.5],
10683
- fill: [-1, 0.5, -1.5]
10684
- },
10685
- upfront: {
10686
- main: [0, 2, 1],
10687
- fill: [-1, 0.5, -1.5]
10688
- },
10689
- soft: {
10690
- main: [-2, 4, 4],
10691
- fill: [-1, 0.5, -1.5]
10692
- }
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));
10693
10774
  };
10694
- function Refit({ radius, adjustCamera }) {
10695
- 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);
10696
10787
  React.useEffect(() => {
10697
- if (adjustCamera) api.refresh().clip().fit();
10698
- }, [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]);
10699
10840
  return null;
10700
10841
  }
10701
- 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,
10702
10873
  children,
10703
- center,
10704
- adjustCamera = true,
10705
- intensity = 0.5,
10706
- shadows = "contact",
10707
- environment = "city",
10708
- preset = "rembrandt",
10874
+ onChange,
10875
+ onChangePointerUp,
10876
+ border = "1px solid #55aaff",
10877
+ backgroundColor = "rgba(75, 160, 255, 0.1)",
10878
+ filter: customFilter = (item) => item,
10709
10879
  ...props
10710
10880
  }) {
10711
- const config = typeof preset === "string" ? presets[preset] : preset;
10712
- const [{ radius, height }, set] = React.useState({ radius: 0, width: 0, height: 0, depth: 0 });
10713
- const shadowBias = shadows?.bias ?? -1e-4;
10714
- const normalBias = shadows?.normalBias ?? 0;
10715
- const shadowSize = shadows?.size ?? 1024;
10716
- const shadowOffset = shadows?.offset ?? 0;
10717
- const contactShadow = shadows === "contact" || shadows?.type === "contact";
10718
- const accumulativeShadow = shadows === "accumulative" || shadows?.type === "accumulative";
10719
- const shadowSpread = { ...typeof shadows === "object" ? shadows : {} };
10720
- const environmentProps = !environment ? null : typeof environment === "string" ? { preset: environment } : environment;
10721
- const onCentered = React.useCallback((props2) => {
10722
- const { width, height: height2, depth, boundingSphere } = props2;
10723
- set({ radius: boundingSphere.radius, width, height: height2, depth });
10724
- 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 });
10725
10902
  }, []);
10726
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ambientLight", { intensity: intensity / 3 }), /* @__PURE__ */ React.createElement(
10727
- "spotLight",
10728
- {
10729
- penumbra: 1,
10730
- position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10731
- intensity: intensity * 2,
10732
- castShadow: !!shadows,
10733
- "shadow-bias": shadowBias,
10734
- "shadow-normalBias": normalBias,
10735
- "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
+ }
10736
10953
  }
10737
- ), /* @__PURE__ */ React.createElement(
10738
- "pointLight",
10739
- {
10740
- position: [config.fill[0] * radius, config.fill[1] * radius, config.fill[2] * radius],
10741
- intensity
10954
+ function pointerDown(event) {
10955
+ if (event.shiftKey) {
10956
+ onSelectStart(event);
10957
+ prepareRay(event, selBox.startPoint);
10958
+ }
10742
10959
  }
10743
- ), /* @__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(
10744
- 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",
10745
10986
  {
10746
- temporal: true,
10747
- frames: 100,
10748
- alphaTest: 0.9,
10749
- toneMapped: true,
10750
- scale: radius * 4,
10751
- ...shadowSpread
10987
+ ref,
10988
+ onClick,
10989
+ onPointerOver: () => hover(true),
10990
+ onPointerOut: () => hover(false),
10991
+ onPointerMissed,
10992
+ ...props
10752
10993
  },
10753
- /* @__PURE__ */ React.createElement(
10754
- RandomizedLight,
10755
- {
10756
- amount: shadowSpread.amount ?? 8,
10757
- radius: shadowSpread.radius ?? radius,
10758
- ambient: shadowSpread.ambient ?? 0.5,
10759
- intensity: shadowSpread.intensity ?? 1,
10760
- position: [config.main[0] * radius, config.main[1] * radius, config.main[2] * radius],
10761
- size: radius * 4,
10762
- bias: -shadowBias,
10763
- mapSize: shadowSize
10764
- }
10765
- )
10766
- )), environment && /* @__PURE__ */ React.createElement(Environment, { ...environmentProps }));
10994
+ /* @__PURE__ */ React.createElement(context.Provider, { value: active }, children)
10995
+ );
10767
10996
  }
10768
-
10769
- function getFormatString(format) {
10770
- switch (format) {
10771
- case 64:
10772
- return "-64px";
10773
- case 128:
10774
- return "-128px";
10775
- case 256:
10776
- return "-256px";
10777
- case 512:
10778
- return "-512px";
10779
- default:
10780
- return "";
10781
- }
10997
+ function useSelect() {
10998
+ return React.useContext(context);
10782
10999
  }
10783
- const LIST_URL$1 = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/matcaps.json";
10784
- const MATCAP_ROOT = "https://rawcdn.githack.com/emmelleppi/matcaps/9b36ccaaf0a24881a39062d05566c9e92be4aa0d";
10785
- function useMatcapTexture(id = 0, format = 1024, onLoad) {
10786
- const matcapList = suspend(() => fetch(LIST_URL$1).then((res) => res.json()), ["matcapList"]);
10787
- const DEFAULT_MATCAP = matcapList[0];
10788
- const numTot = React.useMemo(() => Object.keys(matcapList).length, []);
10789
- const fileHash = React.useMemo(() => {
10790
- if (typeof id === "string") {
10791
- return id;
10792
- } else if (typeof id === "number") {
10793
- return matcapList[id];
10794
- }
10795
- return null;
10796
- }, [id]);
10797
- const fileName = `${fileHash || DEFAULT_MATCAP}${getFormatString(format)}.png`;
10798
- const url = `${MATCAP_ROOT}/${format}/${fileName}`;
10799
- const matcapTexture = useTexture(url, onLoad);
10800
- 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);
10801
11038
  }
10802
- const MatcapTexture = ({
10803
- children,
10804
- id,
10805
- format,
10806
- onLoad
10807
- }) => {
10808
- const texture = useMatcapTexture(id, format, onLoad);
10809
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(texture));
10810
- };
10811
11039
 
10812
- const NORMAL_ROOT = "https://rawcdn.githack.com/pmndrs/drei-assets/7a3104997e1576f83472829815b00880d88b32fb";
10813
- const LIST_URL = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/normals/normals.json";
10814
- function useNormalTexture(id = 0, settings = {}, onLoad) {
10815
- const { repeat = [1, 1], anisotropy = 1, offset = [0, 0] } = settings;
10816
- const normalsList = suspend(() => fetch(LIST_URL).then((res) => res.json()), ["normalsList"]);
10817
- const numTot = React.useMemo(() => Object.keys(normalsList).length, []);
10818
- const DEFAULT_NORMAL = normalsList[0];
10819
- const imageName = normalsList[id] || DEFAULT_NORMAL;
10820
- const url = `${NORMAL_ROOT}/normals/${imageName}`;
10821
- const normalTexture = useTexture(url, onLoad);
10822
- React.useLayoutEffect(() => {
10823
- if (!normalTexture) return;
10824
- normalTexture.wrapS = normalTexture.wrapT = RepeatWrapping;
10825
- normalTexture.repeat = new Vector2(repeat[0], repeat[1]);
10826
- normalTexture.offset = new Vector2(offset[0], offset[1]);
10827
- normalTexture.anisotropy = anisotropy;
10828
- }, [normalTexture, anisotropy, repeat, offset]);
10829
- return [normalTexture, url, numTot];
10830
- }
10831
- const NormalTexture = ({
10832
- children,
10833
- id,
10834
- onLoad,
10835
- ...settings
10836
- }) => {
10837
- const ret = useNormalTexture(id, settings, onLoad);
10838
- return /* @__PURE__ */ React.createElement(React.Fragment, null, children?.(ret));
10839
- };
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
+ }
10840
11153
 
10841
- function CycleRaycast({
10842
- onChanged,
10843
- portal,
10844
- preventDefault = true,
10845
- scroll = true,
10846
- keyCode = 9
10847
- }) {
10848
- const cycle = React.useRef(0);
10849
- const setEvents = useThree((state) => state.setEvents);
10850
- const get = useThree((state) => state.get);
10851
- const gl = useThree((state) => state.gl);
10852
- React.useEffect(() => {
10853
- let hits = [];
10854
- let lastEvent = void 0;
10855
- const prev = get().events.filter;
10856
- const target = portal?.current ?? gl.domElement.parentNode;
10857
- const renderStatus = () => target && onChanged && onChanged(hits, Math.round(cycle.current) % hits.length);
10858
- setEvents({
10859
- filter: (intersections, state) => {
10860
- let clone = [...intersections];
10861
- if (clone.length !== hits.length || !hits.every((hit) => clone.map((e) => e.object.uuid).includes(hit.object.uuid))) {
10862
- cycle.current = 0;
10863
- hits = clone;
10864
- renderStatus();
10865
- }
10866
- if (prev) clone = prev(clone, state);
10867
- for (let i = 0; i < Math.round(cycle.current) % clone.length; i++) {
10868
- const first = clone.shift();
10869
- clone = [...clone, first];
10870
- }
10871
- return clone;
10872
- }
10873
- });
10874
- const refresh = (fn) => {
10875
- cycle.current = fn(cycle.current);
10876
- get().events.handlers?.onPointerCancel(void 0);
10877
- get().events.handlers?.onPointerMove(lastEvent);
10878
- renderStatus();
10879
- };
10880
- const tabEvent = (event) => {
10881
- if ((event.keyCode || event.which) === keyCode) {
10882
- if (preventDefault) event.preventDefault();
10883
- if (hits.length > 1) refresh((current) => current + 1);
10884
- }
10885
- };
10886
- const wheelEvent = (event) => {
10887
- if (preventDefault) event.preventDefault();
10888
- let delta = 0;
10889
- if (!event) event = window.event;
10890
- if (event.wheelDelta) delta = event.wheelDelta / 120;
10891
- else if (event.detail) delta = -event.detail / 3;
10892
- if (hits.length > 1) refresh((current) => Math.abs(current - delta));
10893
- };
10894
- const moveEvent = (event) => lastEvent = event;
10895
- document.addEventListener("pointermove", moveEvent, { passive: true });
10896
- if (scroll) document.addEventListener("wheel", wheelEvent);
10897
- if (keyCode !== void 0) document.addEventListener("keydown", tabEvent);
10898
- return () => {
10899
- setEvents({ filter: prev });
10900
- if (keyCode !== void 0) document.removeEventListener("keydown", tabEvent);
10901
- if (scroll) document.removeEventListener("wheel", wheelEvent);
10902
- document.removeEventListener("pointermove", moveEvent);
10903
- };
10904
- }, [gl, get, setEvents, preventDefault, scroll, keyCode]);
10905
- return null;
10906
- }
11154
+ // compute the anti-aliased stroke edge
11155
+ float edge = 1.0 - wireframe_aastep(computedThickness, d);
10907
11156
 
10908
- function useAspect(width, height, factor = 1) {
10909
- const v = useThree((state) => state.viewport);
10910
- const adaptedHeight = height * (v.aspect > width / height ? v.width / width : v.height / height);
10911
- const adaptedWidth = width * (v.aspect > width / height ? v.width / width : v.height / height);
10912
- return [adaptedWidth * factor, adaptedHeight * factor, 1];
10913
- }
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
10914
11175
 
10915
- function useCursor(hovered, onPointerOver = "pointer", onPointerOut = "auto", container = document.body) {
10916
- React.useEffect(() => {
10917
- if (hovered) {
10918
- container.style.cursor = onPointerOver;
10919
- return () => void (container.style.cursor = onPointerOut);
10920
- }
10921
- }, [hovered]);
10922
- }
11176
+ float edge = getWireframe();
11177
+ vec4 colorStroke = vec4(stroke, edge);
10923
11178
 
10924
- const ScreenSpace = /* @__PURE__ */ React.forwardRef(({ children, depth = -1, ...rest }, ref) => {
10925
- const localRef = React.useRef(null);
10926
- React.useImperativeHandle(ref, () => localRef.current, []);
10927
- useFrame(({ camera }) => {
10928
- localRef.current.quaternion.copy(camera.quaternion);
10929
- localRef.current.position.copy(camera.position);
10930
- });
10931
- return /* @__PURE__ */ React.createElement("group", { ref: localRef, ...rest }, /* @__PURE__ */ React.createElement("group", { "position-z": -depth }, children));
10932
- });
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);
10933
11185
 
10934
- const context = /* @__PURE__ */ React.createContext([]);
10935
- function Select({
10936
- box,
10937
- multiple,
10938
- children,
10939
- onChange,
10940
- onChangePointerUp,
10941
- border = "1px solid #55aaff",
10942
- backgroundColor = "rgba(75, 160, 255, 0.1)",
10943
- filter: customFilter = (item) => item,
10944
- ...props
10945
- }) {
10946
- const [downed, down] = React.useState(false);
10947
- const { setEvents, camera, raycaster, gl, controls, size, get } = useThree();
10948
- const [hovered, hover] = React.useState(false);
10949
- const [active, dispatch] = React.useReducer(
10950
- // @ts-expect-error
10951
- (state, { object, shift }) => {
10952
- if (object === void 0) return [];
10953
- else if (Array.isArray(object)) return object;
10954
- else if (!shift) return state[0] === object ? [] : [object];
10955
- else if (state.includes(object)) return state.filter((o) => o !== object);
10956
- else return [object, ...state];
10957
- },
10958
- []
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]
10959
11265
  );
10960
- React.useEffect(() => {
10961
- if (downed) onChange?.(active);
10962
- else onChangePointerUp?.(active);
10963
- }, [active, downed]);
10964
- const onClick = React.useCallback((e) => {
10965
- e.stopPropagation();
10966
- dispatch({ object: customFilter([e.object])[0], shift: multiple && e.shiftKey });
10967
- }, []);
10968
- const onPointerMissed = React.useCallback((e) => !hovered && dispatch({}), [hovered]);
10969
- const ref = React.useRef(null);
10970
- React.useEffect(() => {
10971
- if (!box || !multiple) return;
10972
- const selBox = new SelectionBox(camera, ref.current);
10973
- const element = document.createElement("div");
10974
- element.style.pointerEvents = "none";
10975
- element.style.border = border;
10976
- element.style.backgroundColor = backgroundColor;
10977
- element.style.position = "fixed";
10978
- const startPoint = new THREE.Vector2();
10979
- const pointTopLeft = new THREE.Vector2();
10980
- const pointBottomRight = new THREE.Vector2();
10981
- const oldRaycasterEnabled = get().events.enabled;
10982
- const oldControlsEnabled = controls?.enabled;
10983
- let isDown = false;
10984
- function prepareRay(event, vec) {
10985
- const { offsetX, offsetY } = event;
10986
- const { width, height } = size;
10987
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
10988
- }
10989
- function onSelectStart(event) {
10990
- if (controls) controls.enabled = false;
10991
- setEvents({ enabled: false });
10992
- down(isDown = true);
10993
- gl.domElement.parentElement?.appendChild(element);
10994
- element.style.left = `${event.clientX}px`;
10995
- element.style.top = `${event.clientY}px`;
10996
- element.style.width = "0px";
10997
- element.style.height = "0px";
10998
- startPoint.x = event.clientX;
10999
- startPoint.y = event.clientY;
11000
- }
11001
- function onSelectMove(event) {
11002
- pointBottomRight.x = Math.max(startPoint.x, event.clientX);
11003
- pointBottomRight.y = Math.max(startPoint.y, event.clientY);
11004
- pointTopLeft.x = Math.min(startPoint.x, event.clientX);
11005
- pointTopLeft.y = Math.min(startPoint.y, event.clientY);
11006
- element.style.left = `${pointTopLeft.x}px`;
11007
- element.style.top = `${pointTopLeft.y}px`;
11008
- element.style.width = `${pointBottomRight.x - pointTopLeft.x}px`;
11009
- 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);
11010
11313
  }
11011
- function onSelectOver() {
11012
- if (isDown) {
11013
- if (controls) controls.enabled = oldControlsEnabled;
11014
- setEvents({ enabled: oldRaycasterEnabled });
11015
- down(isDown = false);
11016
- element.parentElement?.removeChild(element);
11017
- }
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.");
11018
11322
  }
11019
- function pointerDown(event) {
11020
- if (event.shiftKey) {
11021
- onSelectStart(event);
11022
- 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.");
11023
11327
  }
11328
+ return parent.geometry;
11024
11329
  }
11025
- let previous = [];
11026
- function pointerMove(event) {
11027
- if (isDown) {
11028
- onSelectMove(event);
11029
- prepareRay(event, selBox.endPoint);
11030
- const allSelected = selBox.select().sort((o) => o.uuid).filter((o) => o.isMesh);
11031
- if (!shallow(allSelected, previous)) {
11032
- previous = allSelected;
11033
- dispatch({ object: customFilter(allSelected) });
11034
- }
11035
- }
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.");
11036
11355
  }
11037
- function pointerUp(event) {
11038
- if (isDown) onSelectOver();
11356
+ setBarycentricCoordinates(geom, simplify);
11357
+ if (isRef(customGeometry)) {
11358
+ setGeometry(geom);
11039
11359
  }
11040
- document.addEventListener("pointerdown", pointerDown, { passive: true });
11041
- document.addEventListener("pointermove", pointerMove, { passive: true, capture: true });
11042
- document.addEventListener("pointerup", pointerUp, { passive: true });
11043
- return () => {
11044
- document.removeEventListener("pointerdown", pointerDown);
11045
- document.removeEventListener("pointermove", pointerMove, true);
11046
- document.removeEventListener("pointerup", pointerUp);
11047
- };
11048
- }, [size.width, size.height, raycaster, camera, controls, gl]);
11049
- return /* @__PURE__ */ React.createElement(
11050
- "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",
11051
11364
  {
11052
- ref,
11053
- onClick,
11054
- onPointerOver: () => hover(true),
11055
- onPointerOut: () => hover(false),
11056
- onPointerMissed,
11365
+ attach: "material",
11366
+ transparent: true,
11367
+ side: THREE.DoubleSide,
11368
+ polygonOffset: true,
11369
+ polygonOffsetFactor: -4,
11057
11370
  ...props
11058
- },
11059
- /* @__PURE__ */ React.createElement(context.Provider, { value: active }, children)
11060
- );
11061
- }
11062
- function useSelect() {
11063
- return React.useContext(context);
11371
+ }
11372
+ )));
11064
11373
  }
11065
-
11066
- function AsciiRenderer({
11067
- renderIndex = 1,
11068
- bgColor = "black",
11069
- fgColor = "white",
11070
- characters = " .:-+*=%@#",
11071
- invert = true,
11072
- color = false,
11073
- resolution = 0.15
11374
+ function WireframeWithoutCustomGeo({
11375
+ simplify = false,
11376
+ ...props
11074
11377
  }) {
11075
- const { size, gl, scene, camera } = useThree();
11076
- const effect = React.useMemo(() => {
11077
- const effect2 = new AsciiEffect(gl, characters, { invert, color, resolution });
11078
- effect2.domElement.style.position = "absolute";
11079
- effect2.domElement.style.top = "0px";
11080
- effect2.domElement.style.left = "0px";
11081
- effect2.domElement.style.pointerEvents = "none";
11082
- return effect2;
11083
- }, [characters, invert, color, resolution]);
11378
+ const objectRef = React.useRef(null);
11379
+ const uniforms = React.useMemo(() => getUniforms(), [WireframeMaterialShaders.uniforms]);
11380
+ useWireframeUniforms(uniforms, props);
11084
11381
  React.useLayoutEffect(() => {
11085
- effect.domElement.style.color = fgColor;
11086
- effect.domElement.style.backgroundColor = bgColor;
11087
- }, [fgColor, bgColor]);
11088
- React.useEffect(() => {
11089
- gl.domElement.style.opacity = "0";
11090
- 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);
11091
11388
  return () => {
11092
- gl.domElement.style.opacity = "1";
11093
- gl.domElement.parentNode.removeChild(effect.domElement);
11389
+ geom.copy(og);
11390
+ og.dispose();
11094
11391
  };
11095
- }, [effect]);
11096
- React.useEffect(() => {
11097
- effect.setSize(size.width, size.height);
11098
- }, [effect, size]);
11099
- useFrame((state) => {
11100
- effect.render(scene, camera);
11101
- }, renderIndex);
11102
- 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 });
11103
11409
  }
11104
11410
 
11105
11411
  function BakeShadows() {