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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/native/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { MeshBasicNodeMaterial, RenderTarget, MeshPhongNodeMaterial, MeshNormalNodeMaterial, MeshPhysicalNodeMaterial, Frustum, Matrix4, QuadMesh, CubeRenderTarget, Line2NodeMaterial, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, HalfFloatType, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, REVISION, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DoubleSide, DepthTexture, DepthFormat, UnsignedShortType, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, Camera, CubeReflectionMapping, EquirectangularReflectionMapping, Scene, RepeatWrapping, SpriteNodeMaterial, AdditiveBlending, ShadowNode, CylinderGeometry } from 'three/webgpu';
3
+ import { uniform, Fn, float, vec4, uniformTexture, uv, vec2 as vec2$2, texture, Discard, max, sub, mix, output, mul, negate, add, dot, refract, div, length, cross, select, vec3, normalize, normalWorld, positionWorld, cameraPosition, screenCoordinate, mx_fractal_noise_vec3, int, thickness, pow, roughness, Loop, ior, screenUV, screenSize, cameraProjectionMatrix, cameraViewMatrix, attenuationColor, attenuationDistance, diffuseColor, transmission, fract, sin, modelWorldMatrix, log, exp, DFGLUT, reflector, clamp, equirectUV, reflect, smoothstep, round, distance, positionLocal, mx_noise_vec3, cos, mat3, normalLocal, attribute, modelViewMatrix, varying, min, frontFacing, fwidth, abs, saturate, positionView, positionGeometry, reference, renderGroup, interleavedGradientNoise, vogelDiskSample, step } from 'three/tsl';
1
4
  import * as React from 'react';
2
5
  import { forwardRef, useImperativeHandle, useRef, useMemo, useEffect, useCallback, useLayoutEffect, useState, Fragment, createContext, useContext } from 'react';
3
6
  import { useThree, useFrame, extend, createPortal, applyProps, useInstanceHandle, context as context$a, useLoader, addEffect, addAfterEffect } from '@react-three/fiber';
4
- import * as THREE from 'three/webgpu';
5
- import { MeshBasicNodeMaterial, RenderTarget, MeshNormalNodeMaterial, MeshPhysicalNodeMaterial, Frustum, Matrix4, QuadMesh, Line2NodeMaterial, Vector3, Vector4, Vector2, Color, InstancedInterleavedBuffer, InterleavedBufferAttribute, Object3D, WebGLCubeRenderTarget, HalfFloatType, CubeCamera as CubeCamera$1, EventDispatcher, Euler, Quaternion, MathUtils, TextureLoader, Texture as Texture$1, MeshLambertMaterial, DynamicDrawUsage, CatmullRomCurve3, CubicBezierCurve3, QuadraticBezierCurve3, Shape as Shape$1, CanvasTexture, DoubleSide, PerspectiveCamera as PerspectiveCamera$1, OrthographicCamera as OrthographicCamera$1, REVISION, AudioListener, AudioLoader, AnimationMixer, BufferAttribute, Box3, Sphere as Sphere$1, InstancedBufferAttribute, DepthTexture, DepthFormat, UnsignedShortType, CubeTextureLoader, DefaultLoadingManager, Ray, Raycaster, Camera, CubeReflectionMapping, EquirectangularReflectionMapping, Scene, RepeatWrapping, SpriteNodeMaterial, AdditiveBlending, CylinderGeometry } from 'three/webgpu';
6
7
  import { SimplexNoise } from 'three/examples/jsm/math/SimplexNoise.js';
7
8
  import { Line2 } from 'three/examples/jsm/lines/webgpu/Line2.js';
8
9
  import { LineSegments2 } from 'three/examples/jsm/lines/webgpu/LineSegments2.js';
9
10
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
10
11
  import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js';
11
- import { uniform, Fn, float, vec4, uniformTexture, uv, vec2 as vec2$2, texture, mul, negate, add, dot, refract, div, sub, length, cross, select, max, vec3, normalize, normalWorld, positionWorld, Discard, screenCoordinate, cameraPosition, pow, mx_fractal_noise_vec3, int, Loop, cameraProjectionMatrix, cameraViewMatrix, diffuseColor, mix, fract, sin, log, exp, reflector, clamp, reflect, smoothstep, round, distance, positionLocal, mx_noise_vec3, cos, mat3, normalLocal, attribute, modelViewMatrix, varying, min, frontFacing, fwidth, abs, saturate, positionView, modelWorldMatrix, positionGeometry } from 'three/tsl';
12
12
  import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
13
13
  import { Flow } from 'three/examples/jsm/modifiers/CurveModifier.js';
14
14
  import { DecalGeometry } from 'three/examples/jsm/geometries/DecalGeometry.js';
@@ -28,11 +28,11 @@ import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
28
28
  import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
29
29
  import { create as create$1 } from 'zustand';
30
30
  import { hashBlur } from 'three/examples/jsm/tsl/display/hashBlur.js';
31
+ import PhysicalLightingModel from 'three/src/nodes/functions/PhysicalLightingModel.js';
31
32
  import { misc, easing } from 'maath';
32
33
  import * as ReactDOM from 'react-dom/client';
33
34
  import { useGesture } from '@use-gesture/react';
34
35
  import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
35
- import { Events } from 'hls.js';
36
36
  import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox.js';
37
37
  import { shallow } from 'zustand/shallow';
38
38
  import { Sky as Sky$1 } from 'three/examples/jsm/objects/Sky.js';
@@ -231,28 +231,354 @@ const ContactShadows = /* @__PURE__ */ React.forwardRef(
231
231
  }
232
232
  );
233
233
 
234
+ const discardFragment = /* @__PURE__ */ Fn(() => {
235
+ Discard();
236
+ return vec4(0, 0, 0, 0);
237
+ });
238
+ const DiscardMaterial = /* @__PURE__ */ new MeshBasicNodeMaterial({
239
+ fragmentNode: discardFragment()
240
+ });
241
+
242
+ function isLight(object) {
243
+ return object.isLight;
244
+ }
245
+ function isGeometry$1(object) {
246
+ return !!object.geometry;
247
+ }
234
248
  const accumulativeContext = /* @__PURE__ */ React.createContext(
235
249
  null
236
250
  );
237
- let hasWarned$1 = false;
238
- function warnOnce$1(message) {
239
- if (!hasWarned$1) {
240
- console.warn(message);
241
- hasWarned$1 = true;
251
+ class SoftShadowMaterialImpl extends MeshBasicNodeMaterial {
252
+ _color;
253
+ _blend;
254
+ _alphaTest;
255
+ _opacity;
256
+ _map;
257
+ constructor() {
258
+ super();
259
+ this._color = uniform(new THREE.Color());
260
+ this._blend = uniform(2);
261
+ this._alphaTest = uniform(0.75);
262
+ this._opacity = uniform(0);
263
+ this._map = uniformTexture(new THREE.Texture());
264
+ this.transparent = true;
265
+ this.depthWrite = false;
266
+ this._buildShader();
267
+ }
268
+ _buildShader() {
269
+ const colorUniform = this._color;
270
+ const blendUniform = this._blend;
271
+ const alphaTestUniform = this._alphaTest;
272
+ const opacityUniform = this._opacity;
273
+ const mapTex = this._map;
274
+ this.colorNode = Fn(() => {
275
+ const texCoord = uv();
276
+ const sampled = texture(mapTex, texCoord);
277
+ const rgb = colorUniform.mul(sampled.r).mul(blendUniform);
278
+ const sum = sampled.r.add(sampled.g).add(sampled.b);
279
+ const alpha = max(float(0), float(1).sub(sum.div(alphaTestUniform))).mul(opacityUniform);
280
+ return vec4(rgb, alpha);
281
+ })();
282
+ }
283
+ get shadowColor() {
284
+ return this._color.value;
285
+ }
286
+ set shadowColor(v) {
287
+ if (v instanceof THREE.Color) {
288
+ this._color.value.copy(v);
289
+ } else {
290
+ this._color.value.set(v);
291
+ }
292
+ }
293
+ get blend() {
294
+ return this._blend.value;
295
+ }
296
+ set blend(v) {
297
+ this._blend.value = v;
298
+ }
299
+ get alphaTest() {
300
+ return this._alphaTest.value;
301
+ }
302
+ set alphaTest(v) {
303
+ this._alphaTest.value = v;
304
+ }
305
+ get opacity() {
306
+ return this._opacity.value;
307
+ }
308
+ set opacity(v) {
309
+ if (!this._opacity) this._opacity = uniform(v);
310
+ else this._opacity.value = v;
311
+ }
312
+ get map() {
313
+ if (!this._map) {
314
+ this._map = uniformTexture(new THREE.Texture());
315
+ }
316
+ return this._map.value;
317
+ }
318
+ set map(v) {
319
+ this._map.value = v;
242
320
  }
243
321
  }
244
- const AccumulativeShadows = /* @__PURE__ */ React.forwardRef((props, ref) => {
245
- warnOnce$1(
246
- "drei: AccumulativeShadows is not yet supported in WebGPU. It uses GLSL shaders that need to be converted to TSL. Consider using ContactShadows instead, or import from @react-three/drei/legacy for WebGL."
247
- );
248
- return null;
249
- });
250
- const RandomizedLight = /* @__PURE__ */ React.forwardRef((props, ref) => {
251
- warnOnce$1(
252
- "drei: RandomizedLight is not yet supported in WebGPU. It requires AccumulativeShadows which uses GLSL shaders. Import from @react-three/drei/legacy for WebGL."
253
- );
254
- return null;
255
- });
322
+ class ProgressiveShadowMaterial extends MeshPhongNodeMaterial {
323
+ _previousShadowMap;
324
+ _averagingWindow;
325
+ constructor(initialTexture) {
326
+ super();
327
+ this._previousShadowMap = texture(initialTexture);
328
+ this._averagingWindow = uniform(100);
329
+ this.fog = false;
330
+ this._buildShader();
331
+ }
332
+ _buildShader() {
333
+ const previousMapTex = this._previousShadowMap;
334
+ const averagingWindowUniform = this._averagingWindow;
335
+ const uvNode = uv();
336
+ this.vertexNode = vec4(sub(uvNode, vec2$2(0.5)).mul(2), 1, 1);
337
+ this.outputNode = vec4(mix(previousMapTex.sample(uv()), output, float(1).div(averagingWindowUniform)));
338
+ }
339
+ get previousShadowMap() {
340
+ return this._previousShadowMap.value;
341
+ }
342
+ set previousShadowMap(v) {
343
+ this._previousShadowMap.value = v;
344
+ }
345
+ get averagingWindow() {
346
+ return this._averagingWindow.value;
347
+ }
348
+ set averagingWindow(v) {
349
+ this._averagingWindow.value = v;
350
+ }
351
+ }
352
+ class ProgressiveLightMap {
353
+ renderer;
354
+ res;
355
+ scene;
356
+ object;
357
+ buffer1Active;
358
+ progressiveLightMap1;
359
+ progressiveLightMap2;
360
+ discardMat;
361
+ targetMat;
362
+ clearColor;
363
+ clearAlpha;
364
+ lights;
365
+ meshes;
366
+ constructor(renderer, scene, res = 1024) {
367
+ this.renderer = renderer;
368
+ this.res = res;
369
+ this.scene = scene;
370
+ this.buffer1Active = false;
371
+ this.lights = [];
372
+ this.meshes = [];
373
+ this.object = null;
374
+ this.clearColor = new THREE.Color();
375
+ this.clearAlpha = 0;
376
+ const textureParams = {
377
+ type: THREE.HalfFloatType,
378
+ magFilter: THREE.NearestFilter,
379
+ minFilter: THREE.NearestFilter
380
+ };
381
+ this.progressiveLightMap1 = new RenderTarget(this.res, this.res, textureParams);
382
+ this.progressiveLightMap2 = new RenderTarget(this.res, this.res, textureParams);
383
+ this.discardMat = DiscardMaterial;
384
+ this.targetMat = new ProgressiveShadowMaterial(this.progressiveLightMap1.texture);
385
+ }
386
+ clear() {
387
+ this.renderer.getClearColor(this.clearColor);
388
+ this.clearAlpha = this.renderer.getClearAlpha();
389
+ this.renderer.setClearColor("black", 1);
390
+ this.renderer.setRenderTarget(this.progressiveLightMap1);
391
+ this.renderer.clear();
392
+ this.renderer.setRenderTarget(this.progressiveLightMap2);
393
+ this.renderer.clear();
394
+ this.renderer.setRenderTarget(null);
395
+ this.renderer.setClearColor(this.clearColor, this.clearAlpha);
396
+ this.lights = [];
397
+ this.meshes = [];
398
+ this.scene.traverse((object) => {
399
+ if (isGeometry$1(object)) {
400
+ this.meshes.push({ object, material: object.material });
401
+ } else if (isLight(object)) {
402
+ this.lights.push({ object, intensity: object.intensity });
403
+ }
404
+ });
405
+ }
406
+ prepare() {
407
+ this.lights.forEach((light) => light.object.intensity = 0);
408
+ this.meshes.forEach((mesh) => mesh.object.material = this.discardMat);
409
+ }
410
+ finish() {
411
+ this.lights.forEach((light) => light.object.intensity = light.intensity);
412
+ this.meshes.forEach((mesh) => mesh.object.material = mesh.material);
413
+ }
414
+ configure(object) {
415
+ this.object = object;
416
+ }
417
+ update(camera, blendWindow = 100) {
418
+ if (!this.object) return;
419
+ this.targetMat.averagingWindow = blendWindow;
420
+ const originalMaterial = this.object.material;
421
+ this.object.material = this.targetMat;
422
+ const oldFrustumCulled = this.object.frustumCulled;
423
+ this.object.frustumCulled = false;
424
+ const activeMap = this.buffer1Active ? this.progressiveLightMap1 : this.progressiveLightMap2;
425
+ const inactiveMap = this.buffer1Active ? this.progressiveLightMap2 : this.progressiveLightMap1;
426
+ const oldBg = this.scene.background;
427
+ this.scene.background = null;
428
+ this.renderer.setRenderTarget(activeMap);
429
+ this.targetMat.previousShadowMap = inactiveMap.texture;
430
+ this.buffer1Active = !this.buffer1Active;
431
+ this.renderer.render(this.scene, camera);
432
+ this.renderer.setRenderTarget(null);
433
+ this.scene.background = oldBg;
434
+ this.object.frustumCulled = oldFrustumCulled;
435
+ this.object.material = originalMaterial;
436
+ }
437
+ }
438
+ const AccumulativeShadows = /* @__PURE__ */ React.forwardRef(
439
+ ({
440
+ children,
441
+ temporal,
442
+ frames = 40,
443
+ limit = Infinity,
444
+ blend = 20,
445
+ scale = 10,
446
+ opacity = 1,
447
+ alphaTest = 0.75,
448
+ color = "black",
449
+ colorBlend = 2,
450
+ resolution = 1024,
451
+ toneMapped = true,
452
+ ...props
453
+ }, forwardRef) => {
454
+ const gl = useThree((state) => state.gl);
455
+ const scene = useThree((state) => state.scene);
456
+ const camera = useThree((state) => state.camera);
457
+ const invalidate = useThree((state) => state.invalidate);
458
+ const gPlane = React.useRef(null);
459
+ const gLights = React.useRef(null);
460
+ const [softShadowMat] = React.useState(() => new SoftShadowMaterialImpl());
461
+ const [plm] = React.useState(() => new ProgressiveLightMap(gl, scene, resolution));
462
+ React.useLayoutEffect(() => {
463
+ plm.configure(gPlane.current);
464
+ }, [plm]);
465
+ React.useEffect(() => {
466
+ softShadowMat.shadowColor = color;
467
+ softShadowMat.blend = colorBlend;
468
+ softShadowMat.map = plm.progressiveLightMap2.texture;
469
+ softShadowMat.toneMapped = toneMapped;
470
+ }, [softShadowMat, color, colorBlend, plm, toneMapped]);
471
+ const api = React.useMemo(
472
+ () => ({
473
+ lights: /* @__PURE__ */ new Map(),
474
+ temporal: !!temporal,
475
+ frames: Math.max(2, frames),
476
+ blend: Math.max(2, frames === Infinity ? blend : frames),
477
+ count: 0,
478
+ getMesh: () => gPlane.current,
479
+ reset: () => {
480
+ plm.clear();
481
+ softShadowMat.opacity = 0;
482
+ softShadowMat.alphaTest = 0;
483
+ api.count = 0;
484
+ },
485
+ update: (frames2 = 1) => {
486
+ if (!api.temporal) {
487
+ softShadowMat.opacity = opacity;
488
+ softShadowMat.alphaTest = alphaTest;
489
+ } else {
490
+ softShadowMat.opacity = Math.min(opacity, softShadowMat.opacity + opacity / api.blend);
491
+ softShadowMat.alphaTest = Math.min(alphaTest, softShadowMat.alphaTest + alphaTest / api.blend);
492
+ }
493
+ gLights.current.visible = true;
494
+ plm.prepare();
495
+ for (let i = 0; i < frames2; i++) {
496
+ api.lights.forEach((light) => light.update());
497
+ plm.update(camera, api.blend);
498
+ }
499
+ gLights.current.visible = false;
500
+ plm.finish();
501
+ }
502
+ }),
503
+ [plm, camera, scene, temporal, frames, blend, opacity, alphaTest, softShadowMat]
504
+ );
505
+ React.useLayoutEffect(() => {
506
+ api.reset();
507
+ if (!api.temporal && api.frames !== Infinity) api.update(api.blend);
508
+ });
509
+ React.useImperativeHandle(forwardRef, () => api, [api]);
510
+ useFrame(() => {
511
+ if ((api.temporal || api.frames === Infinity) && api.count < api.frames && api.count < limit) {
512
+ invalidate();
513
+ api.update();
514
+ api.count++;
515
+ }
516
+ });
517
+ return /* @__PURE__ */ React.createElement("group", { ...props }, /* @__PURE__ */ React.createElement("group", { traverse: () => null, ref: gLights }, /* @__PURE__ */ React.createElement(accumulativeContext.Provider, { value: api }, children)), /* @__PURE__ */ React.createElement("mesh", { receiveShadow: true, ref: gPlane, scale, rotation: [-Math.PI / 2, 0, 0] }, /* @__PURE__ */ React.createElement("planeGeometry", null), /* @__PURE__ */ React.createElement("primitive", { object: softShadowMat, attach: "material" })));
518
+ }
519
+ );
520
+ const RandomizedLight = /* @__PURE__ */ React.forwardRef(
521
+ ({
522
+ castShadow = true,
523
+ bias = 1e-3,
524
+ mapSize = 512,
525
+ size = 5,
526
+ near = 0.5,
527
+ far = 500,
528
+ frames = 1,
529
+ position = [0, 0, 0],
530
+ radius = 1,
531
+ amount = 8,
532
+ intensity = Math.PI,
533
+ ambient = 0.5,
534
+ ...props
535
+ }, forwardRef) => {
536
+ const gLights = React.useRef(null);
537
+ const length = new THREE.Vector3(...position).length();
538
+ const parent = React.useContext(accumulativeContext);
539
+ const update = React.useCallback(() => {
540
+ let light;
541
+ if (gLights.current) {
542
+ for (let l = 0; l < gLights.current.children.length; l++) {
543
+ light = gLights.current.children[l];
544
+ if (Math.random() > ambient) {
545
+ light.position.set(
546
+ position[0] + THREE.MathUtils.randFloatSpread(radius),
547
+ position[1] + THREE.MathUtils.randFloatSpread(radius),
548
+ position[2] + THREE.MathUtils.randFloatSpread(radius)
549
+ );
550
+ } else {
551
+ let lambda = Math.acos(2 * Math.random() - 1) - Math.PI / 2;
552
+ let phi = 2 * Math.PI * Math.random();
553
+ light.position.set(
554
+ Math.cos(lambda) * Math.cos(phi) * length,
555
+ Math.abs(Math.cos(lambda) * Math.sin(phi) * length),
556
+ Math.sin(lambda) * length
557
+ );
558
+ }
559
+ }
560
+ }
561
+ }, [radius, ambient, length, ...position]);
562
+ const api = React.useMemo(() => ({ update }), [update]);
563
+ React.useImperativeHandle(forwardRef, () => api, [api]);
564
+ React.useLayoutEffect(() => {
565
+ const group = gLights.current;
566
+ if (parent) parent.lights?.set(group.uuid, api);
567
+ return () => void parent?.lights?.delete(group.uuid);
568
+ }, [parent, api]);
569
+ return /* @__PURE__ */ React.createElement("group", { ref: gLights, ...props }, Array.from({ length: amount }, (_, index) => /* @__PURE__ */ React.createElement(
570
+ "directionalLight",
571
+ {
572
+ key: index,
573
+ castShadow,
574
+ "shadow-bias": bias,
575
+ "shadow-mapSize": [mapSize, mapSize],
576
+ intensity: intensity / amount
577
+ },
578
+ /* @__PURE__ */ React.createElement("orthographicCamera", { attach: "shadow-camera", args: [-size, size, size, -size, near, far] })
579
+ )));
580
+ }
581
+ );
256
582
 
257
583
  function useFBO(width, height, settings) {
258
584
  const size = useThree((state) => state.size);
@@ -401,10 +727,10 @@ class CausticsProjectionMaterial extends MeshBasicNodeMaterial {
401
727
  this._causticsTextureB.value = v;
402
728
  }
403
729
  get color() {
404
- return this._color.value;
730
+ return this._color?.value;
405
731
  }
406
732
  set color(v) {
407
- this._color.value = v;
733
+ if (this._color) this._color.value = v;
408
734
  }
409
735
  get lightProjMatrix() {
410
736
  return this._lightProjMatrix.value;
@@ -645,29 +971,307 @@ class CausticsNormalMaterial extends MeshNormalNodeMaterial {
645
971
  }
646
972
  }
647
973
 
648
- const DiscardMaterial = /* @__PURE__ */ new MeshBasicNodeMaterial({
649
- colorNode: Discard()
974
+ const EnvironmentBRDF = /* @__PURE__ */ Fn((inputs) => {
975
+ const { dotNV, specularColor, specularF90, roughness: roughness2 } = inputs;
976
+ const fab = DFGLUT({ dotNV, roughness: roughness2 });
977
+ return specularColor.mul(fab.x).add(specularF90.mul(fab.y));
650
978
  });
651
-
652
- const rand = /* @__PURE__ */ Fn(({ seed, fragCoord }) => {
979
+ const rand = /* @__PURE__ */ Fn((inputs) => {
980
+ const [seed, fragCoord] = inputs;
653
981
  return fract(sin(dot(vec3(fragCoord.xy, seed), vec3(12.9898, 78.233, 45.164))).mul(43758.5453));
654
982
  });
655
- const applyVolumeAttenuation = /* @__PURE__ */ Fn(
656
- ({
657
- radiance,
658
- transmissionDistance,
659
- attenuationColor,
660
- attenuationDistance
661
- }) => {
662
- const isInfinite = attenuationDistance.greaterThan(1e10);
663
- const safeColor = max(attenuationColor, vec3(1e-4));
664
- const attenuationCoefficient = log(safeColor).negate().div(attenuationDistance);
665
- const transmittance = exp(attenuationCoefficient.negate().mul(transmissionDistance));
666
- return select(isInfinite, radiance, transmittance.mul(radiance));
983
+ const applyVolumeAttenuation = /* @__PURE__ */ Fn((inputs) => {
984
+ const [radiance, transmissionDistance, attColor, attDistance] = inputs;
985
+ const isInfinite = attDistance.greaterThan(1e10);
986
+ const safeColor = max(attColor, vec3(1e-4));
987
+ const attenuationCoefficient = log(safeColor).negate().div(attDistance);
988
+ const transmittance = exp(attenuationCoefficient.negate().mul(transmissionDistance));
989
+ return select(isInfinite, radiance, transmittance.mul(radiance));
990
+ });
991
+ const getVolumeTransmissionRay = /* @__PURE__ */ Fn((inputs) => {
992
+ const [n, v, thicknessVal, iorVal] = inputs;
993
+ const refractionVector = refract(v.negate(), normalize(n), float(1).div(iorVal));
994
+ const refractLen = length(refractionVector);
995
+ const safeRefractionVector = select(
996
+ refractLen.greaterThan(1e-3),
997
+ refractionVector,
998
+ v.negate()
999
+ // Fallback: ray goes straight through
1000
+ );
1001
+ const modelScale = vec3(
1002
+ length(modelWorldMatrix[0].xyz),
1003
+ length(modelWorldMatrix[1].xyz),
1004
+ length(modelWorldMatrix[2].xyz)
1005
+ );
1006
+ return normalize(safeRefractionVector).mul(thicknessVal.mul(modelScale));
1007
+ });
1008
+ const getRefractionUV = /* @__PURE__ */ Fn((inputs) => {
1009
+ const [norm, viewDir, iorVal, thicknessVal, position] = inputs;
1010
+ const transmissionRay = getVolumeTransmissionRay(norm, viewDir, thicknessVal, iorVal);
1011
+ const refractedRayExit = position.add(transmissionRay);
1012
+ const ndcPos = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedRayExit, 1)));
1013
+ const safeW = max(ndcPos.w, float(1e-3));
1014
+ const refractionCoords = vec2$2(ndcPos.xy.div(safeW)).toVar();
1015
+ refractionCoords.addAssign(1);
1016
+ refractionCoords.divAssign(2);
1017
+ refractionCoords.assign(vec2$2(refractionCoords.x, refractionCoords.y.oneMinus()));
1018
+ refractionCoords.assign(refractionCoords.clamp(1e-3, 0.999));
1019
+ return refractionCoords;
1020
+ });
1021
+ class TransmissionLightingModel extends PhysicalLightingModel {
1022
+ _bufferTex;
1023
+ _chromaticAberration;
1024
+ _anisotropicBlur;
1025
+ _timeUniform;
1026
+ _distortion;
1027
+ _distortionScale;
1028
+ _temporalDistortion;
1029
+ _samples;
1030
+ _debugMode;
1031
+ constructor(options, transmissionUniforms) {
1032
+ super(
1033
+ options.clearcoat ?? false,
1034
+ options.sheen ?? false,
1035
+ options.iridescence ?? false,
1036
+ options.anisotropy ?? false,
1037
+ true,
1038
+ // transmission always true for our material
1039
+ options.dispersion ?? false
1040
+ );
1041
+ this._bufferTex = transmissionUniforms.bufferTex;
1042
+ this._chromaticAberration = transmissionUniforms.chromaticAberration;
1043
+ this._anisotropicBlur = transmissionUniforms.anisotropicBlur;
1044
+ this._timeUniform = transmissionUniforms.timeUniform;
1045
+ this._distortion = transmissionUniforms.distortion;
1046
+ this._distortionScale = transmissionUniforms.distortionScale;
1047
+ this._temporalDistortion = transmissionUniforms.temporalDistortion;
1048
+ this._samples = transmissionUniforms.samples;
1049
+ this._debugMode = transmissionUniforms.debugMode;
1050
+ }
1051
+ start(builder) {
1052
+ if (this.clearcoat === true) {
1053
+ this.clearcoatRadiance = vec3().toVar("clearcoatRadiance");
1054
+ this.clearcoatSpecularDirect = vec3().toVar("clearcoatSpecularDirect");
1055
+ this.clearcoatSpecularIndirect = vec3().toVar("clearcoatSpecularIndirect");
1056
+ }
1057
+ if (this.sheen === true) {
1058
+ this.sheenSpecularDirect = vec3().toVar("sheenSpecularDirect");
1059
+ this.sheenSpecularIndirect = vec3().toVar("sheenSpecularIndirect");
1060
+ }
1061
+ if (this.transmission === true) {
1062
+ const position = positionWorld;
1063
+ const v = normalize(cameraPosition.sub(positionWorld));
1064
+ const n = normalWorld;
1065
+ const context = builder.context;
1066
+ const bufferTex = this._bufferTex;
1067
+ const chromaticAberrationUniform = this._chromaticAberration;
1068
+ const anisotropicBlurUniform = this._anisotropicBlur;
1069
+ const timeUniform = this._timeUniform;
1070
+ const distortionUniform = this._distortion;
1071
+ const distortionScaleUniform = this._distortionScale;
1072
+ const temporalDistortionUniform = this._temporalDistortion;
1073
+ const sampleCount = this._samples;
1074
+ const debugModeUniform = this._debugMode;
1075
+ const getCustomTransmission = Fn(() => {
1076
+ const transmissionAccum = vec3(0).toVar("transmissionAccum");
1077
+ const fragCoord = screenCoordinate.xy;
1078
+ const runningSeed = float(0).toVar("runningSeed");
1079
+ const randomCoords = rand(runningSeed, fragCoord);
1080
+ runningSeed.addAssign(1);
1081
+ const temporalOffset = vec3(timeUniform, timeUniform.negate(), timeUniform.negate()).mul(
1082
+ temporalDistortionUniform
1083
+ );
1084
+ const noiseInput = position.mul(distortionScaleUniform).add(temporalOffset);
1085
+ const distortionNormal = select(
1086
+ distortionUniform.greaterThan(0),
1087
+ mx_fractal_noise_vec3(noiseInput, int(4), float(2), float(0.5)).mul(distortionUniform),
1088
+ vec3(0)
1089
+ );
1090
+ const thicknessSmear = thickness.mul(max(pow(roughness, 0.33), anisotropicBlurUniform));
1091
+ Loop(sampleCount, ({ i }) => {
1092
+ const fi = float(i);
1093
+ const progress = fi.add(randomCoords).div(float(sampleCount));
1094
+ const randX = rand(runningSeed, fragCoord).sub(0.5);
1095
+ runningSeed.addAssign(1);
1096
+ const randY = rand(runningSeed, fragCoord).sub(0.5);
1097
+ runningSeed.addAssign(1);
1098
+ const randZ = rand(runningSeed, fragCoord).sub(0.5);
1099
+ runningSeed.addAssign(1);
1100
+ const randW = rand(runningSeed, fragCoord);
1101
+ runningSeed.addAssign(1);
1102
+ const randomDir = normalize(vec3(randX, randY, randZ)).mul(pow(randW, 0.33));
1103
+ const sampleNorm = normalize(n.add(roughness.mul(roughness).mul(2).mul(randomDir)).add(distortionNormal));
1104
+ const sampleThickness = thickness.add(thicknessSmear.mul(progress));
1105
+ const iorR = ior;
1106
+ const iorG = ior.mul(float(1).add(chromaticAberrationUniform.mul(progress)));
1107
+ const iorB = ior.mul(float(1).add(chromaticAberrationUniform.mul(2).mul(progress)));
1108
+ const uvR = getRefractionUV(sampleNorm, v, iorR, sampleThickness, position);
1109
+ const uvG = getRefractionUV(sampleNorm, v, iorG, sampleThickness, position);
1110
+ const uvB = getRefractionUV(sampleNorm, v, iorB, sampleThickness, position);
1111
+ const sampleR = texture(bufferTex, uvR);
1112
+ const sampleG = texture(bufferTex, uvG);
1113
+ const sampleB = texture(bufferTex, uvB);
1114
+ transmissionAccum.x.addAssign(sampleR.r);
1115
+ transmissionAccum.y.addAssign(sampleG.g);
1116
+ transmissionAccum.z.addAssign(sampleB.b);
1117
+ });
1118
+ const avgTransmission = transmissionAccum.div(float(sampleCount));
1119
+ const debugUV = getRefractionUV(n, v, ior, thickness, position);
1120
+ const debugNdotV = n.dot(v);
1121
+ const isNeg = debugNdotV.lessThan(0);
1122
+ const sampleUVFlipped = vec2$2(screenUV.x, screenUV.y.oneMinus()).clamp(1e-3, 0.999);
1123
+ const directSampleFlipped = texture(bufferTex, sampleUVFlipped);
1124
+ const testRay = getVolumeTransmissionRay(n, v, thickness, ior);
1125
+ const rayLen = length(testRay);
1126
+ const manualUV = screenCoordinate.xy.div(screenSize);
1127
+ const noFlipUV = screenUV.clamp(1e-3, 0.999);
1128
+ const flippedUV = vec2$2(screenUV.x, screenUV.y.oneMinus()).clamp(1e-3, 0.999);
1129
+ const useFlipped = screenUV.x.greaterThan(0.5);
1130
+ const splitSampleUV = select(useFlipped, flippedUV, noFlipUV);
1131
+ const splitSample = texture(bufferTex, splitSampleUV);
1132
+ const directSampleNoFlip = texture(bufferTex, screenUV.clamp(1e-3, 0.999));
1133
+ const centerSample = texture(bufferTex, vec2$2(0.5, 0.5));
1134
+ const transmissionRayDbg = getVolumeTransmissionRay(n, v, thickness, ior);
1135
+ const refractedRayExitDbg = position.add(transmissionRayDbg);
1136
+ const ndcPosDbg = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedRayExitDbg, 1)));
1137
+ const safeWDbg = max(ndcPosDbg.w, float(1e-3));
1138
+ const rawCoordsDbg = vec2$2(ndcPosDbg.xy.div(safeWDbg)).toVar();
1139
+ rawCoordsDbg.addAssign(1);
1140
+ rawCoordsDbg.divAssign(2);
1141
+ rawCoordsDbg.assign(vec2$2(rawCoordsDbg.x, rawCoordsDbg.y.oneMinus()));
1142
+ const outOfRange = rawCoordsDbg.x.lessThan(0).or(rawCoordsDbg.x.greaterThan(1)).or(rawCoordsDbg.y.lessThan(0)).or(rawCoordsDbg.y.greaterThan(1));
1143
+ const clipPosDbg = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(position, 1)));
1144
+ const safeWDbg2 = max(clipPosDbg.w, float(1e-3));
1145
+ const projUVDbg = vec2$2(clipPosDbg.xy.div(safeWDbg2)).toVar();
1146
+ projUVDbg.addAssign(1);
1147
+ projUVDbg.divAssign(2);
1148
+ projUVDbg.assign(vec2$2(projUVDbg.x, projUVDbg.y.oneMinus()));
1149
+ projUVDbg.assign(projUVDbg.clamp(1e-3, 0.999));
1150
+ const projectedSample = texture(bufferTex, projUVDbg);
1151
+ const wNorm = clipPosDbg.w.div(10).add(0.5).clamp(0, 1);
1152
+ const isBehind = clipPosDbg.w.lessThan(0);
1153
+ const rawNdcX = clipPosDbg.x.div(safeWDbg2);
1154
+ const rawNdcY = clipPosDbg.y.div(safeWDbg2);
1155
+ const result = vec4(0, 0, 0, 1).toVar();
1156
+ result.assign(
1157
+ select(
1158
+ debugModeUniform.equal(1),
1159
+ vec4(avgTransmission, float(1)),
1160
+ select(
1161
+ debugModeUniform.equal(2),
1162
+ vec4(debugUV.x, debugUV.y, float(0), float(1)),
1163
+ select(
1164
+ debugModeUniform.equal(3),
1165
+ vec4(
1166
+ select(isNeg, debugNdotV.abs(), float(0)),
1167
+ select(isNeg, float(0), debugNdotV),
1168
+ float(0),
1169
+ float(1)
1170
+ ),
1171
+ select(
1172
+ debugModeUniform.equal(4),
1173
+ vec4(v.mul(0.5).add(0.5), float(1)),
1174
+ select(
1175
+ debugModeUniform.equal(5),
1176
+ vec4(n.mul(0.5).add(0.5), float(1)),
1177
+ select(
1178
+ debugModeUniform.equal(6),
1179
+ vec4(directSampleFlipped.rgb, float(1)),
1180
+ select(
1181
+ debugModeUniform.equal(7),
1182
+ vec4(rayLen, rayLen, rayLen, float(1)),
1183
+ select(
1184
+ debugModeUniform.equal(8),
1185
+ vec4(screenUV.x, screenUV.y, float(0), float(1)),
1186
+ select(
1187
+ debugModeUniform.equal(9),
1188
+ vec4(screenSize.x.div(2e3), screenSize.y.div(2e3), float(0), float(1)),
1189
+ select(
1190
+ debugModeUniform.equal(10),
1191
+ vec4(manualUV.x, manualUV.y, float(0), float(1)),
1192
+ select(
1193
+ debugModeUniform.equal(11),
1194
+ vec4(splitSample.rgb, float(1)),
1195
+ select(
1196
+ debugModeUniform.equal(12),
1197
+ vec4(directSampleNoFlip.rgb, float(1)),
1198
+ select(
1199
+ debugModeUniform.equal(13),
1200
+ vec4(
1201
+ select(outOfRange, float(1), float(0)),
1202
+ rawCoordsDbg.x.clamp(0, 1),
1203
+ rawCoordsDbg.y.clamp(0, 1),
1204
+ float(1)
1205
+ ),
1206
+ select(
1207
+ debugModeUniform.equal(14),
1208
+ vec4(
1209
+ select(isBehind, float(1), float(0)),
1210
+ wNorm,
1211
+ select(isBehind, float(0), float(1)),
1212
+ float(1)
1213
+ ),
1214
+ select(
1215
+ debugModeUniform.equal(15),
1216
+ vec4(centerSample.rgb, float(1)),
1217
+ select(
1218
+ debugModeUniform.equal(16),
1219
+ vec4(projectedSample.rgb, float(1)),
1220
+ select(
1221
+ debugModeUniform.equal(17),
1222
+ // Raw NDC as color: map [-1,1] to [0,1]
1223
+ vec4(rawNdcX.add(1).div(2), rawNdcY.add(1).div(2), float(0.5), float(1)),
1224
+ select(
1225
+ debugModeUniform.equal(18),
1226
+ // Solid magenta - if black here, shader isn't running
1227
+ vec4(1, 0, 1, 1),
1228
+ select(
1229
+ debugModeUniform.equal(19),
1230
+ // Tinted FBO sample: mix sample with UV color
1231
+ vec4(
1232
+ directSampleFlipped.r.add(screenUV.x.mul(0.3)),
1233
+ directSampleFlipped.g.add(screenUV.y.mul(0.3)),
1234
+ directSampleFlipped.b,
1235
+ float(1)
1236
+ ),
1237
+ vec4(0, 0, 0, 1)
1238
+ // Placeholder for mode 0
1239
+ )
1240
+ )
1241
+ )
1242
+ )
1243
+ )
1244
+ )
1245
+ )
1246
+ )
1247
+ )
1248
+ )
1249
+ )
1250
+ )
1251
+ )
1252
+ )
1253
+ )
1254
+ )
1255
+ )
1256
+ )
1257
+ )
1258
+ );
1259
+ const attenuated = applyVolumeAttenuation(avgTransmission, thickness, attenuationColor, attenuationDistance);
1260
+ const NdotV = n.dot(v).abs().clamp(1e-3, 1);
1261
+ const glassF0 = vec3(0.04);
1262
+ const brdf = EnvironmentBRDF({ dotNV: NdotV, specularColor: glassF0, specularF90: float(1), roughness });
1263
+ const transmissionResult = brdf.oneMinus().mul(attenuated).mul(diffuseColor.rgb);
1264
+ const normalOutput = vec4(transmissionResult, float(1));
1265
+ return select(debugModeUniform.greaterThan(0), result, normalOutput);
1266
+ });
1267
+ context.backdrop = getCustomTransmission();
1268
+ context.backdropAlpha = transmission;
1269
+ diffuseColor.a.mulAssign(mix(1, context.backdrop.a, transmission));
1270
+ }
667
1271
  }
668
- );
1272
+ }
669
1273
  class MeshTransmissionMaterialImpl extends MeshPhysicalNodeMaterial {
670
- //* Private Uniform Nodes --
1274
+ // Custom uniforms
671
1275
  _chromaticAberration;
672
1276
  _anisotropicBlur;
673
1277
  _time;
@@ -675,13 +1279,13 @@ class MeshTransmissionMaterialImpl extends MeshPhysicalNodeMaterial {
675
1279
  _distortionScale;
676
1280
  _temporalDistortion;
677
1281
  _buffer;
1282
+ // Texture node
678
1283
  _transmissionValue;
679
- _attenuationDistance;
680
- _attenuationColor;
681
- _samples;
682
- _transmissionSampler;
1284
+ _debugMode;
683
1285
  /** Type flag for identification */
684
1286
  isMeshTransmissionMaterial = true;
1287
+ _samples;
1288
+ _transmissionSampler;
685
1289
  constructor(samples = 6, transmissionSampler = false) {
686
1290
  super();
687
1291
  this._samples = samples;
@@ -692,179 +1296,106 @@ class MeshTransmissionMaterialImpl extends MeshPhysicalNodeMaterial {
692
1296
  this._distortion = uniform(0);
693
1297
  this._distortionScale = uniform(0.5);
694
1298
  this._temporalDistortion = uniform(0);
695
- this._buffer = uniformTexture(new THREE.Texture());
1299
+ this._debugMode = uniform(0);
1300
+ const placeholderTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
1301
+ placeholderTexture.needsUpdate = true;
1302
+ this._buffer = uniformTexture(placeholderTexture);
696
1303
  this._transmissionValue = uniform(1);
697
- this._attenuationDistance = uniform(Infinity);
698
- this._attenuationColor = uniform(new THREE.Color("white"));
699
- this.transmission = transmissionSampler ? 1 : 0;
1304
+ this.transmission = transmissionSampler ? 1 : 1e-3;
700
1305
  this.roughness = 0;
701
1306
  this.thickness = 0;
702
- this.envMapIntensity = 1;
703
- if (!transmissionSampler) {
704
- this._buildTransmissionShader();
705
- }
706
- }
707
- _buildTransmissionShader() {
708
- const chromaticAberrationUniform = this._chromaticAberration;
709
- const anisotropicBlurUniform = this._anisotropicBlur;
710
- const timeUniform = this._time;
711
- const distortionUniform = this._distortion;
712
- const distortionScaleUniform = this._distortionScale;
713
- const temporalDistortionUniform = this._temporalDistortion;
714
- const bufferTex = this._buffer;
715
- const transmissionUniform = this._transmissionValue;
716
- const attenuationDistanceUniform = this._attenuationDistance;
717
- const attenuationColorUniform = this._attenuationColor;
718
- const samples = this._samples;
719
- const materialRef = this;
720
- this.outputNode = Fn(() => {
721
- const pos = positionWorld;
722
- const fragCoord = screenCoordinate.xy;
723
- const v = normalize(cameraPosition.sub(pos));
724
- const n = normalWorld;
725
- const transmission = vec3(0, 0, 0).toVar();
726
- const runningSeed = float(0).toVar();
727
- const randomCoords = rand({ seed: runningSeed, fragCoord });
728
- runningSeed.addAssign(1);
729
- const roughnessFactor = float(materialRef.roughness).toVar();
730
- const thicknessVal = float(materialRef.thickness);
731
- const iorVal = float(materialRef.ior);
732
- const thicknessSmear = thicknessVal.mul(max(pow(roughnessFactor, 0.33), anisotropicBlurUniform));
733
- const distortionNormal = vec3(0, 0, 0).toVar();
734
- const distortEnabled = distortionUniform.greaterThan(0);
735
- const temporalOffset = vec3(timeUniform, timeUniform.negate(), timeUniform.negate()).mul(
736
- temporalDistortionUniform
737
- );
738
- const noiseInput = pos.mul(distortionScaleUniform).add(temporalOffset);
739
- const noiseResult = mx_fractal_noise_vec3(noiseInput, int(4), float(2), float(0.5));
740
- const computedDistortion = noiseResult.mul(distortionUniform);
741
- distortionNormal.assign(select(distortEnabled, computedDistortion, vec3(0)));
742
- Loop(samples, ({ i }) => {
743
- const fi = float(i);
744
- const randX = rand({ seed: runningSeed, fragCoord }).sub(0.5);
745
- runningSeed.addAssign(1);
746
- const randY = rand({ seed: runningSeed, fragCoord }).sub(0.5);
747
- runningSeed.addAssign(1);
748
- const randZ = rand({ seed: runningSeed, fragCoord }).sub(0.5);
749
- runningSeed.addAssign(1);
750
- const randW = rand({ seed: runningSeed, fragCoord });
751
- runningSeed.addAssign(1);
752
- const randomDir = normalize(vec3(randX, randY, randZ)).mul(pow(randW, 0.33));
753
- const sampleNorm = normalize(
754
- n.add(roughnessFactor.mul(roughnessFactor).mul(2).mul(randomDir)).add(distortionNormal)
755
- );
756
- const refractionDir = refract(v.negate(), sampleNorm, float(1).div(iorVal));
757
- const sampleThickness = thicknessVal.add(thicknessSmear.mul(fi.add(randomCoords)).div(float(samples)));
758
- const refractedExit = pos.add(refractionDir.mul(sampleThickness));
759
- const clipPos = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedExit, 1)));
760
- const ndcPos = clipPos.xyz.div(clipPos.w);
761
- const refractionUV = ndcPos.xy.mul(0.5).add(0.5);
762
- const sampledColor = texture(bufferTex, refractionUV);
763
- const transmissionR = sampledColor.r;
764
- const chromaticOffsetG = chromaticAberrationUniform.mul(fi.add(randomCoords)).div(float(samples));
765
- const iorG = iorVal.mul(float(1).add(chromaticOffsetG));
766
- const refractionDirG = refract(v.negate(), sampleNorm, float(1).div(iorG));
767
- const refractedExitG = pos.add(refractionDirG.mul(sampleThickness));
768
- const clipPosG = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedExitG, 1)));
769
- const ndcPosG = clipPosG.xyz.div(clipPosG.w);
770
- const refractionUVG = ndcPosG.xy.mul(0.5).add(0.5);
771
- const transmissionG = texture(bufferTex, refractionUVG).g;
772
- const chromaticOffsetB = chromaticAberrationUniform.mul(2).mul(fi.add(randomCoords)).div(float(samples));
773
- const iorB = iorVal.mul(float(1).add(chromaticOffsetB));
774
- const refractionDirB = refract(v.negate(), sampleNorm, float(1).div(iorB));
775
- const refractedExitB = pos.add(refractionDirB.mul(sampleThickness));
776
- const clipPosB = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedExitB, 1)));
777
- const ndcPosB = clipPosB.xyz.div(clipPosB.w);
778
- const refractionUVB = ndcPosB.xy.mul(0.5).add(0.5);
779
- const transmissionB = texture(bufferTex, refractionUVB).b;
780
- transmission.x.addAssign(transmissionR);
781
- transmission.y.addAssign(transmissionG);
782
- transmission.z.addAssign(transmissionB);
783
- });
784
- const avgTransmission = transmission.div(float(samples));
785
- const transmissionRayLength = thicknessVal;
786
- const attenuatedTransmission = applyVolumeAttenuation({
787
- radiance: avgTransmission,
788
- transmissionDistance: transmissionRayLength,
789
- attenuationColor: attenuationColorUniform,
790
- attenuationDistance: attenuationDistanceUniform
791
- });
792
- const baseDiffuse = diffuseColor.rgb;
793
- const finalColor = mix(baseDiffuse, attenuatedTransmission, transmissionUniform);
794
- return vec4(finalColor, diffuseColor.a);
795
- })();
1307
+ this.ior = 1.5;
1308
+ this.transparent = true;
1309
+ }
1310
+ setupLightingModel() {
1311
+ if (this._transmissionSampler) {
1312
+ return super.setupLightingModel();
1313
+ }
1314
+ return new TransmissionLightingModel(
1315
+ {
1316
+ clearcoat: this.useClearcoat,
1317
+ sheen: this.useSheen,
1318
+ iridescence: this.useIridescence,
1319
+ anisotropy: this.useAnisotropy,
1320
+ dispersion: this.useDispersion
1321
+ },
1322
+ {
1323
+ bufferTex: this._buffer,
1324
+ chromaticAberration: this._chromaticAberration,
1325
+ anisotropicBlur: this._anisotropicBlur,
1326
+ timeUniform: this._time,
1327
+ distortion: this._distortion,
1328
+ distortionScale: this._distortionScale,
1329
+ temporalDistortion: this._temporalDistortion,
1330
+ samples: this._samples,
1331
+ debugMode: this._debugMode
1332
+ }
1333
+ );
1334
+ }
1335
+ //* Property Accessors ==============================
1336
+ get time() {
1337
+ return this._time.value;
1338
+ }
1339
+ set time(v) {
1340
+ this._time.value = v;
1341
+ }
1342
+ get buffer() {
1343
+ return this._buffer.value;
1344
+ }
1345
+ set buffer(v) {
1346
+ this._buffer.value = v;
796
1347
  }
797
- //* Uniform Accessors ==============================
798
- /** Chromatic aberration strength, default: 0.05 */
799
1348
  get chromaticAberration() {
800
1349
  return this._chromaticAberration.value;
801
1350
  }
802
1351
  set chromaticAberration(v) {
803
1352
  this._chromaticAberration.value = v;
804
1353
  }
805
- /** Anisotropic blur amount, default: 0.1 */
806
1354
  get anisotropicBlur() {
807
1355
  return this._anisotropicBlur.value;
808
1356
  }
809
1357
  set anisotropicBlur(v) {
810
1358
  this._anisotropicBlur.value = v;
811
1359
  }
812
- /** Animation time (set from useFrame), default: 0 */
813
- get time() {
814
- return this._time.value;
815
- }
816
- set time(v) {
817
- this._time.value = v;
818
- }
819
- /** Noise distortion amount, default: 0 */
820
1360
  get distortion() {
821
1361
  return this._distortion.value;
822
1362
  }
823
1363
  set distortion(v) {
824
1364
  this._distortion.value = v;
825
1365
  }
826
- /** Distortion noise scale, default: 0.5 */
827
1366
  get distortionScale() {
828
1367
  return this._distortionScale.value;
829
1368
  }
830
1369
  set distortionScale(v) {
831
1370
  this._distortionScale.value = v;
832
1371
  }
833
- /** Temporal distortion speed, default: 0 */
834
1372
  get temporalDistortion() {
835
1373
  return this._temporalDistortion.value;
836
1374
  }
837
1375
  set temporalDistortion(v) {
838
1376
  this._temporalDistortion.value = v;
839
1377
  }
840
- /** Scene buffer texture for refraction sampling */
841
- get buffer() {
842
- return this._buffer.value;
1378
+ get debugMode() {
1379
+ return this._debugMode.value;
843
1380
  }
844
- set buffer(v) {
845
- this._buffer.value = v ?? new THREE.Texture();
1381
+ set debugMode(v) {
1382
+ this._debugMode.value = v;
846
1383
  }
847
- /** Internal transmission value (use transmission prop instead) */
1384
+ // Expose _transmission for legacy compatibility
1385
+ // This stores the user's intended transmission value, separate from the base class's transmission
1386
+ // which is kept at 0.001 to enable the transmission code path without triggering Three's internal rendering
848
1387
  get _transmission() {
1388
+ if (!this._transmissionValue) {
1389
+ this._transmissionValue = uniform(1);
1390
+ }
849
1391
  return this._transmissionValue.value;
850
1392
  }
851
1393
  set _transmission(v) {
1394
+ if (!this._transmissionValue) {
1395
+ this._transmissionValue = uniform(1);
1396
+ }
852
1397
  this._transmissionValue.value = v;
853
1398
  }
854
- /** Attenuation distance for Beer's law absorption */
855
- get attenuationDistance() {
856
- return this._attenuationDistance.value;
857
- }
858
- set attenuationDistance(v) {
859
- this._attenuationDistance.value = v;
860
- }
861
- /** Attenuation color for Beer's law absorption */
862
- get attenuationColor() {
863
- return this._attenuationColor.value;
864
- }
865
- set attenuationColor(v) {
866
- this._attenuationColor.value = v;
867
- }
868
1399
  }
869
1400
  const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
870
1401
  ({
@@ -872,8 +1403,8 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
872
1403
  transmissionSampler = false,
873
1404
  backside = false,
874
1405
  side = THREE.FrontSide,
875
- transmission = 1,
876
- thickness = 0,
1406
+ transmission: transmission2 = 1,
1407
+ thickness: thickness2 = 0,
877
1408
  backsideThickness = 0,
878
1409
  backsideEnvMapIntensity = 1,
879
1410
  samples = 10,
@@ -882,48 +1413,111 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
882
1413
  background,
883
1414
  anisotropy,
884
1415
  anisotropicBlur,
1416
+ debugMode = 0,
885
1417
  ...props
886
1418
  }, fref) => {
887
1419
  extend({ MeshTransmissionMaterial: MeshTransmissionMaterialImpl });
888
1420
  const ref = React.useRef(null);
889
- const [discardMaterial] = React.useState(() => DiscardMaterial);
890
- const fboBack = useFBO(backsideResolution || resolution);
891
- const fboMain = useFBO(resolution);
1421
+ const backsideMaterialRef = React.useRef(null);
1422
+ const { size, viewport } = useThree();
1423
+ const screenWidth = size.width * viewport.dpr;
1424
+ const screenHeight = size.height * viewport.dpr;
1425
+ const fboWidth = resolution || screenWidth;
1426
+ const fboHeight = resolution ? Math.round(resolution / screenWidth * screenHeight) : screenHeight;
1427
+ const fboBack = useFBO(
1428
+ backsideResolution || fboWidth,
1429
+ backsideResolution ? Math.round(backsideResolution / screenWidth * screenHeight) : fboHeight
1430
+ );
1431
+ const fboMain = useFBO(fboWidth, fboHeight);
1432
+ React.useEffect(() => {
1433
+ if (backside && ref.current && !backsideMaterialRef.current) {
1434
+ backsideMaterialRef.current = new MeshTransmissionMaterialImpl(samples, transmissionSampler);
1435
+ backsideMaterialRef.current.buffer = fboBack.texture;
1436
+ }
1437
+ return () => {
1438
+ if (backsideMaterialRef.current) {
1439
+ backsideMaterialRef.current.dispose();
1440
+ backsideMaterialRef.current = null;
1441
+ }
1442
+ };
1443
+ }, [backside, samples, transmissionSampler]);
1444
+ React.useEffect(() => {
1445
+ if (backsideMaterialRef.current) {
1446
+ backsideMaterialRef.current.buffer = fboBack.texture;
1447
+ }
1448
+ }, [fboBack.texture]);
892
1449
  let oldBg;
893
1450
  let oldEnvMapIntensity;
894
1451
  let oldTone;
895
1452
  let parent;
896
1453
  useFrame((state) => {
897
1454
  ref.current.time = state.elapsed;
1455
+ const renderer = state.renderer;
1456
+ if (backsideMaterialRef.current) {
1457
+ backsideMaterialRef.current.time = state.elapsed;
1458
+ }
898
1459
  if (ref.current.buffer === fboMain.texture && !transmissionSampler) {
899
1460
  parent = ref.current.__r3f?.parent?.object;
900
- if (parent) {
901
- oldTone = state.gl.toneMapping;
902
- oldBg = state.scene.background;
903
- oldEnvMapIntensity = ref.current.envMapIntensity ?? 1;
904
- state.gl.toneMapping = THREE.NoToneMapping;
905
- if (background) state.scene.background = background;
906
- parent.material = discardMaterial;
907
- if (backside) {
908
- state.gl.setRenderTarget(fboBack);
909
- state.gl.render(state.scene, state.camera);
910
- parent.material = ref.current;
911
- ref.current.buffer = fboBack.texture;
912
- ref.current.thickness = backsideThickness;
913
- ref.current.side = THREE.BackSide;
914
- ref.current.envMapIntensity = backsideEnvMapIntensity;
1461
+ if (!parent) return;
1462
+ oldTone = renderer.toneMapping;
1463
+ oldBg = state.scene.background;
1464
+ oldEnvMapIntensity = ref.current.envMapIntensity;
1465
+ renderer.toneMapping = THREE.NoToneMapping;
1466
+ if (background)
1467
+ state.scene.background = background;
1468
+ parent.material = DiscardMaterial;
1469
+ const camera = state.camera;
1470
+ const isOrtho = camera.isOrthographicCamera;
1471
+ let oldLeft, oldRight, oldTop, oldBottom;
1472
+ if (isOrtho) {
1473
+ oldLeft = camera.left;
1474
+ oldRight = camera.right;
1475
+ oldTop = camera.top;
1476
+ oldBottom = camera.bottom;
1477
+ const fboAspect = fboWidth / fboHeight;
1478
+ const screenAspect = screenWidth / screenHeight;
1479
+ if (Math.abs(fboAspect - screenAspect) > 1e-3) {
1480
+ const viewHeight = oldTop - oldBottom;
1481
+ const newHalfWidth = viewHeight * fboAspect / 2;
1482
+ const centerX = (oldLeft + oldRight) / 2;
1483
+ camera.left = centerX - newHalfWidth;
1484
+ camera.right = centerX + newHalfWidth;
1485
+ camera.updateProjectionMatrix();
915
1486
  }
916
- state.gl.setRenderTarget(fboMain);
917
- state.gl.render(state.scene, state.camera);
1487
+ }
1488
+ if (backside && backsideMaterialRef.current) {
1489
+ renderer.setRenderTarget(fboBack);
1490
+ renderer.clear();
1491
+ state.renderer.render(state.scene, state.camera);
1492
+ parent.material = backsideMaterialRef.current;
1493
+ backsideMaterialRef.current.thickness = backsideThickness;
1494
+ backsideMaterialRef.current.side = THREE.BackSide;
1495
+ backsideMaterialRef.current.envMapIntensity = backsideEnvMapIntensity;
1496
+ renderer.setRenderTarget(fboMain);
1497
+ renderer.clear();
1498
+ state.renderer.render(state.scene, state.camera);
918
1499
  parent.material = ref.current;
919
- ref.current.thickness = thickness;
1500
+ ref.current.thickness = thickness2;
920
1501
  ref.current.side = side;
921
- ref.current.buffer = fboMain.texture;
922
1502
  ref.current.envMapIntensity = oldEnvMapIntensity;
923
- state.scene.background = oldBg;
924
- state.gl.setRenderTarget(null);
925
- state.gl.toneMapping = oldTone;
1503
+ } else {
1504
+ renderer.setRenderTarget(fboMain);
1505
+ renderer.clear();
1506
+ state.renderer.render(state.scene, state.camera);
1507
+ parent.material = ref.current;
1508
+ ref.current.thickness = thickness2;
1509
+ ref.current.side = side;
926
1510
  }
1511
+ if (isOrtho && Math.abs(fboWidth / fboHeight - screenWidth / screenHeight) > 1e-3) {
1512
+ camera.left = oldLeft;
1513
+ camera.right = oldRight;
1514
+ camera.top = oldTop;
1515
+ camera.bottom = oldBottom;
1516
+ camera.updateProjectionMatrix();
1517
+ }
1518
+ state.scene.background = oldBg;
1519
+ renderer.setRenderTarget(null);
1520
+ renderer.toneMapping = oldTone;
927
1521
  }
928
1522
  });
929
1523
  React.useImperativeHandle(fref, () => ref.current, []);
@@ -934,11 +1528,11 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
934
1528
  ref,
935
1529
  ...props,
936
1530
  buffer: buffer || fboMain.texture,
937
- _transmission: transmission,
938
- anisotropicBlur: anisotropicBlur ?? anisotropy ?? 0.1,
939
- transmission: transmissionSampler ? transmission : 0,
940
- thickness,
941
- side
1531
+ _transmission: transmission2,
1532
+ anisotropicBlur: anisotropicBlur ?? anisotropy,
1533
+ thickness: thickness2,
1534
+ side,
1535
+ debugMode
942
1536
  }
943
1537
  );
944
1538
  }
@@ -1189,25 +1783,16 @@ const MeshReflectorMaterial = /* @__PURE__ */ React.forwardRef(
1189
1783
  }
1190
1784
  );
1191
1785
 
1192
- const fresnelEffect = /* @__PURE__ */ Fn((inputs) => {
1193
- const [viewDir, normal, power] = inputs;
1786
+ const fresnelEffect = /* @__PURE__ */ Fn(([viewDir, normal, power]) => {
1194
1787
  const NdotV = float(1).add(dot(viewDir, normal));
1195
1788
  return pow(NdotV, power);
1196
1789
  });
1197
- const equirectUv = /* @__PURE__ */ Fn((inputs) => {
1198
- const [direction] = inputs;
1199
- const dir = normalize(direction);
1200
- const u = float(0.5).add(dir.z.atan2(dir.x).div(float(2).mul(Math.PI)));
1201
- const v = float(0.5).sub(dir.y.asin().div(Math.PI));
1202
- return vec2$2(u, v);
1203
- });
1204
- const approximateRefraction = /* @__PURE__ */ Fn((inputs) => {
1205
- const [rayDir, normal, iorValue, bounceCount, thickness] = inputs;
1790
+ const approximateRefraction = /* @__PURE__ */ Fn(([rayDir, normal, iorValue, bounceCount]) => {
1206
1791
  const entryDir = refract(rayDir, normal, float(1).div(iorValue)).toVar();
1207
1792
  const entryValid = length(entryDir).greaterThan(0);
1208
1793
  const exitNormal = normal.negate().toVar();
1209
1794
  const currentDir = entryDir.toVar();
1210
- Loop(int(bounceCount), ({ i }) => {
1795
+ Loop({ start: 0, end: int(bounceCount), type: "int" }, ({ i }) => {
1211
1796
  const fi = float(i).add(1);
1212
1797
  const exitDir = refract(currentDir, exitNormal, iorValue);
1213
1798
  const canExit = length(exitDir).greaterThan(0);
@@ -1226,8 +1811,6 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1226
1811
  _aberrationStrength;
1227
1812
  _color;
1228
1813
  _opacityValue;
1229
- _resolution;
1230
- _thickness;
1231
1814
  _fastChroma;
1232
1815
  /** Type flag for identification */
1233
1816
  isMeshRefractionMaterial = true;
@@ -1241,8 +1824,6 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1241
1824
  this._aberrationStrength = uniform(0.01);
1242
1825
  this._color = uniform(new THREE.Color("white"));
1243
1826
  this._opacityValue = uniform(1);
1244
- this._resolution = uniform(new THREE.Vector2(1, 1));
1245
- this._thickness = uniform(1);
1246
1827
  this.transparent = true;
1247
1828
  this.side = THREE.FrontSide;
1248
1829
  this._buildRefractionShader();
@@ -1255,20 +1836,13 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1255
1836
  const aberrationUniform = this._aberrationStrength;
1256
1837
  const colorUniform = this._color;
1257
1838
  const opacityUniform = this._opacityValue;
1258
- const thicknessUniform = this._thickness;
1259
1839
  const fastChroma = this._fastChroma;
1260
1840
  this.outputNode = Fn(() => {
1261
1841
  const worldPos = positionWorld;
1262
1842
  const worldNormal = normalize(normalWorld);
1263
1843
  const viewDir = normalize(worldPos.sub(cameraPosition));
1264
1844
  const baseColor = colorUniform.toVar();
1265
- const refractedDirG = approximateRefraction(
1266
- viewDir,
1267
- worldNormal,
1268
- max(iorUniform, 1),
1269
- bouncesUniform,
1270
- thicknessUniform
1271
- );
1845
+ const refractedDirG = approximateRefraction(viewDir, worldNormal, max(iorUniform, 1), bouncesUniform);
1272
1846
  const aberration = aberrationUniform;
1273
1847
  const refractedDirR = vec3(0, 0, 0).toVar();
1274
1848
  const refractedDirB = vec3(0, 0, 0).toVar();
@@ -1282,8 +1856,7 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1282
1856
  viewDir,
1283
1857
  worldNormal,
1284
1858
  max(iorUniform.mul(float(1).sub(aberration)), 1),
1285
- bouncesUniform,
1286
- thicknessUniform
1859
+ bouncesUniform
1287
1860
  )
1288
1861
  );
1289
1862
  refractedDirB.assign(
@@ -1291,14 +1864,13 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1291
1864
  viewDir,
1292
1865
  worldNormal,
1293
1866
  max(iorUniform.mul(float(1).add(aberration)), 1),
1294
- bouncesUniform,
1295
- thicknessUniform
1867
+ bouncesUniform
1296
1868
  )
1297
1869
  );
1298
1870
  }
1299
- const uvR = equirectUv(refractedDirR);
1300
- const uvG = equirectUv(refractedDirG);
1301
- const uvB = equirectUv(refractedDirB);
1871
+ const uvR = equirectUV(refractedDirR);
1872
+ const uvG = equirectUV(refractedDirG);
1873
+ const uvB = equirectUV(refractedDirB);
1302
1874
  const envColorR = texture(envMapTex, uvR).r;
1303
1875
  const envColorG = texture(envMapTex, uvG).g;
1304
1876
  const envColorB = texture(envMapTex, uvB).b;
@@ -1362,20 +1934,6 @@ class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1362
1934
  set opacity(v) {
1363
1935
  this._opacityValue.value = v;
1364
1936
  }
1365
- /** Resolution for screen-space calculations */
1366
- get resolution() {
1367
- return this._resolution.value;
1368
- }
1369
- set resolution(v) {
1370
- this._resolution.value = v;
1371
- }
1372
- /** Approximate thickness for refraction calculations */
1373
- get thickness() {
1374
- return this._thickness.value;
1375
- }
1376
- set thickness(v) {
1377
- this._thickness.value = v;
1378
- }
1379
1937
  }
1380
1938
  const MeshRefractionMaterial = /* @__PURE__ */ React.forwardRef(
1381
1939
  ({
@@ -1391,12 +1949,6 @@ const MeshRefractionMaterial = /* @__PURE__ */ React.forwardRef(
1391
1949
  }, fref) => {
1392
1950
  extend({ MeshRefractionMaterial: MeshRefractionMaterialImpl });
1393
1951
  const ref = React.useRef(null);
1394
- const size = useThree((state) => state.size);
1395
- React.useEffect(() => {
1396
- if (ref.current) {
1397
- ref.current.resolution = new THREE.Vector2(size.width, size.height);
1398
- }
1399
- }, [size]);
1400
1952
  React.useEffect(() => {
1401
1953
  if (ref.current && envMap) {
1402
1954
  ref.current.envMap = envMap;
@@ -1648,10 +2200,10 @@ class PortalMaterialImpl extends MeshBasicNodeMaterial {
1648
2200
  this._blur.value = v;
1649
2201
  }
1650
2202
  get map() {
1651
- return this._map.value;
2203
+ return this._map?.value;
1652
2204
  }
1653
2205
  set map(v) {
1654
- this._map.value = v ?? new THREE.Texture();
2206
+ if (this._map) this._map.value = v ?? new THREE.Texture();
1655
2207
  }
1656
2208
  get sdf() {
1657
2209
  return this._sdf.value;
@@ -2431,11 +2983,11 @@ const RenderCubeTexture = /* @__PURE__ */ React.forwardRef(
2431
2983
  depthBuffer,
2432
2984
  generateMipmaps
2433
2985
  };
2434
- const fboA2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
2986
+ const fboA2 = new CubeRenderTarget(fboSize, settings);
2435
2987
  fboA2.texture.isRenderTargetTexture = !flip;
2436
2988
  fboA2.texture.flipY = true;
2437
2989
  fboA2.texture.type = THREE.HalfFloatType;
2438
- const fboB2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
2990
+ const fboB2 = new CubeRenderTarget(fboSize, settings);
2439
2991
  fboB2.texture.isRenderTargetTexture = !flip;
2440
2992
  fboB2.texture.flipY = true;
2441
2993
  fboB2.texture.type = THREE.HalfFloatType;
@@ -2885,7 +3437,7 @@ function useCubeCamera({ resolution = 256, near = 0.1, far = 1e3, envMap, fog }
2885
3437
  const gl = useThree(({ gl: gl2 }) => gl2);
2886
3438
  const scene = useThree(({ scene: scene2 }) => scene2);
2887
3439
  const fbo = useMemo(() => {
2888
- const fbo2 = new WebGLCubeRenderTarget(resolution);
3440
+ const fbo2 = new CubeRenderTarget(resolution);
2889
3441
  fbo2.texture.type = HalfFloatType;
2890
3442
  return fbo2;
2891
3443
  }, [resolution]);
@@ -3268,9 +3820,8 @@ function PresentationControls({
3268
3820
  azimuth = [-Infinity, Infinity],
3269
3821
  damping = 0.25
3270
3822
  }) {
3271
- const events = useThree((state) => state.events);
3272
- const gl = useThree((state) => state.gl);
3273
- const explDomElement = domElement || events.connected || gl.domElement;
3823
+ const { events, renderer, invalidate } = useThree();
3824
+ const explDomElement = domElement || events.connected || renderer.domElement;
3274
3825
  const { size } = useThree();
3275
3826
  const rPolar = React.useMemo(
3276
3827
  () => [rotation[0] + polar[0], rotation[0] + polar[1]],
@@ -3287,18 +3838,20 @@ function PresentationControls({
3287
3838
  React.useEffect(() => {
3288
3839
  if (global && cursor && enabled) {
3289
3840
  explDomElement.style.cursor = "grab";
3290
- gl.domElement.style.cursor = "";
3841
+ renderer.domElement.style.cursor = "";
3291
3842
  return () => {
3292
3843
  explDomElement.style.cursor = "default";
3293
- gl.domElement.style.cursor = "default";
3844
+ renderer.domElement.style.cursor = "default";
3294
3845
  };
3295
3846
  }
3296
3847
  }, [global, cursor, explDomElement, enabled]);
3297
3848
  const [animation] = React.useState({ scale: 1, rotation: rInitial, damping });
3298
3849
  const ref = React.useRef(null);
3299
3850
  useFrame((state, delta) => {
3300
- easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
3301
- easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
3851
+ let changed = false;
3852
+ changed ||= easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
3853
+ changed ||= easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
3854
+ if (changed) state.invalidate();
3302
3855
  });
3303
3856
  const bind = useGesture(
3304
3857
  {
@@ -3306,6 +3859,7 @@ function PresentationControls({
3306
3859
  if (cursor && !global && enabled) explDomElement.style.cursor = last ? "auto" : "grab";
3307
3860
  },
3308
3861
  onDrag: ({ down, delta: [x, y], memo: [oldY, oldX] = animation.rotation || rInitial }) => {
3862
+ invalidate();
3309
3863
  if (!enabled) return [y, x];
3310
3864
  if (cursor) explDomElement.style.cursor = down ? "grabbing" : "grab";
3311
3865
  x = MathUtils.clamp(oldX + x / size.width * Math.PI * speed, ...rAzimuth);
@@ -3314,9 +3868,12 @@ function PresentationControls({
3314
3868
  animation.rotation = snap && !down ? rInitial : [y, x, 0];
3315
3869
  animation.damping = snap && !down && typeof snap !== "boolean" ? snap : damping;
3316
3870
  return [y, x];
3871
+ },
3872
+ onDragEnd: () => {
3873
+ invalidate();
3317
3874
  }
3318
3875
  },
3319
- { target: global ? explDomElement : void 0 }
3876
+ { target: global ? explDomElement : void 0, enabled }
3320
3877
  );
3321
3878
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
3322
3879
  }
@@ -3325,6 +3882,10 @@ function roundToPixelRatio(value) {
3325
3882
  const ratio = window.devicePixelRatio || 1;
3326
3883
  return Math.round(value * ratio) / ratio;
3327
3884
  }
3885
+ function getMaxAnisotropy(renderer) {
3886
+ const r = renderer;
3887
+ return r?.getMaxAnisotropy?.() ?? r?.capabilities?.getMaxAnisotropy?.() ?? 1;
3888
+ }
3328
3889
 
3329
3890
  const context$7 = /* @__PURE__ */ React.createContext(null);
3330
3891
  const rootCache = /* @__PURE__ */ new WeakMap();
@@ -3690,10 +4251,10 @@ const CloudInstance = /* @__PURE__ */ React.forwardRef(
3690
4251
  return /* @__PURE__ */ React.createElement("group", { ref, ...props });
3691
4252
  }
3692
4253
  );
3693
- const Cloud = /* @__PURE__ */ React.forwardRef((props, fref) => {
4254
+ const Cloud = /* @__PURE__ */ React.forwardRef(({ materialFactory, ...props }, fref) => {
3694
4255
  const parent = React.useContext(context$6);
3695
4256
  if (parent) return /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props });
3696
- return /* @__PURE__ */ React.createElement(Clouds, null, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
4257
+ return /* @__PURE__ */ React.createElement(Clouds, { materialFactory }, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
3697
4258
  });
3698
4259
 
3699
4260
  const CatmullRomLine = /* @__PURE__ */ React.forwardRef(function CatmullRomLine2({
@@ -4215,7 +4776,7 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
4215
4776
  return {
4216
4777
  font,
4217
4778
  size,
4218
- height,
4779
+ depth: height,
4219
4780
  bevelThickness,
4220
4781
  bevelSize,
4221
4782
  bevelEnabled,
@@ -4398,7 +4959,7 @@ const FaceMaterial = ({
4398
4959
  "meshBasicMaterial",
4399
4960
  {
4400
4961
  map: texture,
4401
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
4962
+ "map-anisotropy": getMaxAnisotropy(renderer),
4402
4963
  attach: `material-${index}`,
4403
4964
  color: hover ? hoverColor : "white",
4404
4965
  transparent: true,
@@ -4509,7 +5070,7 @@ function AxisHead({
4509
5070
  "spriteMaterial",
4510
5071
  {
4511
5072
  map: texture,
4512
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
5073
+ "map-anisotropy": getMaxAnisotropy(renderer),
4513
5074
  alphaTest: 0.3,
4514
5075
  opacity: label ? 1 : 0.75,
4515
5076
  toneMapped: false
@@ -4654,6 +5215,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4654
5215
  }, ref) => {
4655
5216
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
4656
5217
  const forceEven = useThree((state) => state.internal.forceEven);
5218
+ const { frustum } = useThree();
4657
5219
  const [el] = React.useState(() => document.createElement(as));
4658
5220
  const root = React.useRef(null);
4659
5221
  const group = React.useRef(null);
@@ -4683,22 +5245,18 @@ const Html = /* @__PURE__ */ React.forwardRef(
4683
5245
  if (group.current) {
4684
5246
  if (!root.current) {
4685
5247
  root.current = ReactDOM.createRoot(el);
4686
- }
4687
- scene.updateMatrixWorld();
4688
- if (transform) {
4689
- el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
4690
- } else {
4691
- el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
5248
+ scene.updateMatrixWorld();
5249
+ if (transform) {
5250
+ el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
5251
+ } else {
5252
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
5253
+ }
4692
5254
  }
4693
5255
  if (target) {
4694
5256
  if (prepend) target.prepend(el);
4695
5257
  else target.appendChild(el);
4696
5258
  }
4697
5259
  return () => {
4698
- if (root.current) {
4699
- root.current.unmount();
4700
- root.current = null;
4701
- }
4702
5260
  if (target && target.contains(el)) {
4703
5261
  target.removeChild(el);
4704
5262
  }
@@ -4832,8 +5390,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4832
5390
  if (transformOuterRef.current) {
4833
5391
  const el2 = transformOuterRef.current.children[0];
4834
5392
  if (el2?.clientWidth && el2?.clientHeight) {
4835
- const { isOrthographicCamera } = camera;
4836
- if (isOrthographicCamera || geometry) {
5393
+ if (geometry) {
4837
5394
  if (props.scale) {
4838
5395
  if (!Array.isArray(props.scale)) {
4839
5396
  occlusionMeshRef.current.scale.setScalar(1 / props.scale);
@@ -4865,64 +5422,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4865
5422
  }
4866
5423
  }
4867
5424
  });
4868
- const shaders = React.useMemo(
4869
- () => ({
4870
- vertexShader: !transform ? (
4871
- /* glsl */
4872
- `
4873
- /*
4874
- This shader is from the THREE's SpriteMaterial.
4875
- We need to turn the backing plane into a Sprite
4876
- (make it always face the camera) if "transfrom"
4877
- is false.
4878
- */
4879
- #include <common>
4880
-
4881
- void main() {
4882
- vec2 center = vec2(0., 1.);
4883
- float rotation = 0.0;
4884
-
4885
- // This is somewhat arbitrary, but it seems to work well
4886
- // Need to figure out how to derive this dynamically if it even matters
4887
- float size = 0.03;
4888
-
4889
- vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );
4890
- vec2 scale;
4891
- scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );
4892
- scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );
4893
-
4894
- bool isPerspective = isPerspectiveMatrix( projectionMatrix );
4895
- if ( isPerspective ) scale *= - mvPosition.z;
4896
-
4897
- vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale * size;
4898
- vec2 rotatedPosition;
4899
- rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
4900
- rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
4901
- mvPosition.xy += rotatedPosition;
4902
-
4903
- gl_Position = projectionMatrix * mvPosition;
4904
- }
4905
- `
4906
- ) : void 0,
4907
- fragmentShader: (
4908
- /* glsl */
4909
- `
4910
- void main() {
4911
- gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
4912
- }
4913
- `
4914
- )
4915
- }),
4916
- [transform]
4917
- );
4918
- return /* @__PURE__ */ React.createElement("group", { ...props, ref: group }, occlude && !isRayCastOcclusion && /* @__PURE__ */ React.createElement("mesh", { castShadow, receiveShadow, ref: occlusionMeshRef }, geometry || /* @__PURE__ */ React.createElement("planeGeometry", null), material || /* @__PURE__ */ React.createElement(
4919
- "shaderMaterial",
4920
- {
4921
- side: DoubleSide,
4922
- vertexShader: shaders.vertexShader,
4923
- fragmentShader: shaders.fragmentShader
4924
- }
4925
- )));
5425
+ return /* @__PURE__ */ React.createElement("group", { ...props, ref: group }, occlude && !isRayCastOcclusion && /* @__PURE__ */ React.createElement("mesh", { castShadow, receiveShadow, ref: occlusionMeshRef }, geometry || /* @__PURE__ */ React.createElement("planeGeometry", null), material));
4926
5426
  }
4927
5427
  );
4928
5428
 
@@ -5775,6 +6275,7 @@ const mW = /* @__PURE__ */ new THREE.Matrix4();
5775
6275
  const mL = /* @__PURE__ */ new THREE.Matrix4();
5776
6276
  const mL0Inv = /* @__PURE__ */ new THREE.Matrix4();
5777
6277
  const mdL = /* @__PURE__ */ new THREE.Matrix4();
6278
+ const mdW = /* @__PURE__ */ new THREE.Matrix4();
5778
6279
  const mG = /* @__PURE__ */ new THREE.Matrix4();
5779
6280
  const bb = /* @__PURE__ */ new THREE.Box3();
5780
6281
  const bbObj = /* @__PURE__ */ new THREE.Box3();
@@ -5889,7 +6390,8 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
5889
6390
  onDragStart && onDragStart(props2);
5890
6391
  invalidate();
5891
6392
  },
5892
- onDrag: (mdW) => {
6393
+ onDrag: (mdW_) => {
6394
+ mdW.copy(mdW_);
5893
6395
  mP.copy(parentRef.current.matrixWorld);
5894
6396
  mPInv.copy(mP).invert();
5895
6397
  mW.copy(mW0).premultiply(mdW);
@@ -5901,12 +6403,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
5901
6403
  }
5902
6404
  const target = resolveObject(object);
5903
6405
  if (target) target.matrix.copy(mL);
5904
- onDrag && onDrag(mL, mdL, mW, mdW);
6406
+ onDrag && onDrag({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
5905
6407
  invalidate();
5906
6408
  },
5907
6409
  onDragEnd: () => {
5908
6410
  dragState.current = null;
5909
- if (onDragEnd) onDragEnd();
6411
+ if (onDragEnd) onDragEnd({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
5910
6412
  invalidate();
5911
6413
  },
5912
6414
  onHover: (props2) => {
@@ -6017,7 +6519,9 @@ const TransformControls = /* @__PURE__ */ React.forwardRef(
6017
6519
  const group = React.useRef(null);
6018
6520
  React.useLayoutEffect(() => {
6019
6521
  if (object) {
6020
- controls.attach(object instanceof THREE.Object3D ? object : object.current);
6522
+ controls.attach(
6523
+ object instanceof THREE.Object3D ? object : object.current
6524
+ );
6021
6525
  } else if (group.current instanceof THREE.Object3D) {
6022
6526
  controls.attach(group.current);
6023
6527
  }
@@ -6116,7 +6620,7 @@ const PointMaterial = /* @__PURE__ */ React.forwardRef((props, ref) => {
6116
6620
  return /* @__PURE__ */ React.createElement("primitive", { ...props, object: material, ref, attach: "material" });
6117
6621
  });
6118
6622
 
6119
- const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = true, autoplay, ...props }, ref) => {
6623
+ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = false, autoplay, ...props }, ref) => {
6120
6624
  const sound = React.useRef(null);
6121
6625
  React.useImperativeHandle(ref, () => sound.current, []);
6122
6626
  const camera = useThree(({ camera: camera2 }) => camera2);
@@ -6128,7 +6632,9 @@ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, l
6128
6632
  _sound.setBuffer(Array.isArray(buffer) ? buffer[0] : buffer);
6129
6633
  _sound.setRefDistance(distance);
6130
6634
  _sound.setLoop(loop);
6131
- if (autoplay && !_sound.isPlaying) _sound.play();
6635
+ if (autoplay && !_sound.isPlaying) {
6636
+ _sound.play();
6637
+ }
6132
6638
  }
6133
6639
  }, [buffer, camera, distance, loop]);
6134
6640
  React.useEffect(() => {
@@ -6197,7 +6703,6 @@ const tempMatrix = /* @__PURE__ */ new THREE.Matrix4();
6197
6703
  const translation = /* @__PURE__ */ new THREE.Vector3();
6198
6704
  const rotation = /* @__PURE__ */ new THREE.Quaternion();
6199
6705
  const scale = /* @__PURE__ */ new THREE.Vector3();
6200
- const isInstancedBufferAttribute = (attr) => attr.isInstancedBufferAttribute;
6201
6706
  const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }, ref) => {
6202
6707
  React.useMemo(() => extend({ PositionMesh }), []);
6203
6708
  const group = React.useRef(null);
@@ -6217,24 +6722,20 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
6217
6722
  const parentRef = React.useRef(null);
6218
6723
  React.useImperativeHandle(ref, () => parentRef.current, []);
6219
6724
  const [instances, setInstances] = React.useState([]);
6725
+ const boundsDirty = React.useRef(true);
6220
6726
  const [[matrices, colors]] = React.useState(() => {
6221
6727
  const mArray = new Float32Array(limit * 16);
6222
6728
  for (let i = 0; i < limit; i++) tempMatrix.identity().toArray(mArray, i * 16);
6223
6729
  return [mArray, new Float32Array([...new Array(limit * 3)].map(() => 1))];
6224
6730
  });
6225
6731
  React.useEffect(() => {
6226
- parentRef.current.instanceMatrix.needsUpdate = true;
6732
+ if (parentRef.current) parentRef.current.instanceMatrix.needsUpdate = true;
6227
6733
  });
6228
6734
  let iterations = 0;
6229
6735
  let count = 0;
6230
- const attributes = React.useRef([]);
6231
- React.useLayoutEffect(() => {
6232
- attributes.current = Object.entries(parentRef.current.geometry.attributes).filter(
6233
- ([_name, value]) => isInstancedBufferAttribute(value)
6234
- );
6235
- });
6236
6736
  useFrame(() => {
6237
6737
  if (frames === Infinity || iterations < frames) {
6738
+ if (!parentRef.current) return;
6238
6739
  parentRef.current.updateMatrix();
6239
6740
  parentRef.current.updateMatrixWorld();
6240
6741
  parentMatrix.copy(parentRef.current.matrixWorld).invert();
@@ -6255,13 +6756,22 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
6255
6756
  }
6256
6757
  iterations++;
6257
6758
  }
6759
+ if (boundsDirty.current && (frames === Infinity || iterations === frames)) {
6760
+ boundsDirty.current = false;
6761
+ if (parentRef.current.boundingBox) parentRef.current.computeBoundingBox();
6762
+ if (parentRef.current.boundingSphere) parentRef.current.computeBoundingSphere();
6763
+ }
6258
6764
  });
6259
6765
  const api = React.useMemo(
6260
6766
  () => ({
6261
6767
  getParent: () => parentRef,
6262
6768
  subscribe: (ref2) => {
6263
6769
  setInstances((instances2) => [...instances2, ref2]);
6264
- return () => setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
6770
+ boundsDirty.current = true;
6771
+ return () => {
6772
+ setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
6773
+ boundsDirty.current = true;
6774
+ };
6265
6775
  }
6266
6776
  }),
6267
6777
  []
@@ -6286,10 +6796,23 @@ const Merged = /* @__PURE__ */ React.forwardRef(function Merged2({ meshes, child
6286
6796
  if (!isArray) {
6287
6797
  for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key];
6288
6798
  }
6289
- const render = (args) => isArray ? children(...args) : children(
6290
- Object.keys(meshes).filter((key) => meshes[key].isMesh).reduce((acc, key, i) => ({ ...acc, [key]: args[i] }), {})
6291
- );
6292
- const components = (isArray ? meshes : Object.values(meshes)).map(({ geometry, material }) => /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props }));
6799
+ const render = (args) => {
6800
+ if (isArray) {
6801
+ return children(
6802
+ ...args
6803
+ );
6804
+ }
6805
+ const instanceMap = {};
6806
+ const keys = Object.keys(meshes).filter((key) => meshes[key].isMesh);
6807
+ keys.forEach((key, i) => {
6808
+ instanceMap[key] = args[i];
6809
+ });
6810
+ return children(instanceMap);
6811
+ };
6812
+ const components = (isArray ? meshes : Object.values(meshes)).map((mesh) => {
6813
+ const { geometry, material } = mesh;
6814
+ return /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props });
6815
+ });
6293
6816
  return /* @__PURE__ */ React.createElement("group", { ref }, renderRecursive(render, components));
6294
6817
  });
6295
6818
  function renderRecursive(render, components, results = []) {
@@ -7363,12 +7886,13 @@ function Sampler({ children, weight, transform, instances, mesh, count = 16, ...
7363
7886
  return /* @__PURE__ */ React.createElement("group", { ref: group, ...props }, children);
7364
7887
  }
7365
7888
 
7889
+ const pathStyle = (path) => path.userData?.style;
7366
7890
  const Svg = /* @__PURE__ */ forwardRef(
7367
7891
  function R3FSvg({ src, skipFill, skipStrokes, fillMaterial, strokeMaterial, fillMeshProps, strokeMeshProps, ...props }, ref) {
7368
7892
  const svg = useLoader(SVGLoader, !src.startsWith("<svg") ? src : `data:image/svg+xml;utf8,${src}`);
7369
7893
  const strokeGeometries = useMemo(
7370
7894
  () => skipStrokes ? [] : svg.paths.map(
7371
- (path) => path.userData?.style.stroke === void 0 || path.userData.style.stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), path.userData.style))
7895
+ (path) => pathStyle(path)?.stroke === void 0 || pathStyle(path).stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), pathStyle(path)))
7372
7896
  ),
7373
7897
  [svg, skipStrokes]
7374
7898
  );
@@ -7376,21 +7900,21 @@ const Svg = /* @__PURE__ */ forwardRef(
7376
7900
  return () => strokeGeometries.forEach((group) => group && group.map((g) => g.dispose()));
7377
7901
  }, [strokeGeometries]);
7378
7902
  let renderOrder = 0;
7379
- return /* @__PURE__ */ React.createElement("object3D", { ref, ...props }, /* @__PURE__ */ React.createElement("object3D", { scale: [1, -1, 1] }, svg.paths.map((path, p) => /* @__PURE__ */ React.createElement(Fragment, { key: p }, !skipFill && path.userData?.style.fill !== void 0 && path.userData.style.fill !== "none" && SVGLoader.createShapes(path).map((shape, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, ...fillMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement("shapeGeometry", { args: [shape] }), /* @__PURE__ */ React.createElement(
7903
+ return /* @__PURE__ */ React.createElement("object3D", { ref, ...props }, /* @__PURE__ */ React.createElement("object3D", { scale: [1, -1, 1] }, svg.paths.map((path, p) => /* @__PURE__ */ React.createElement(Fragment, { key: p }, !skipFill && pathStyle(path)?.fill !== void 0 && pathStyle(path).fill !== "none" && SVGLoader.createShapes(path).map((shape, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, ...fillMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement("shapeGeometry", { args: [shape] }), /* @__PURE__ */ React.createElement(
7380
7904
  "meshBasicMaterial",
7381
7905
  {
7382
- color: path.userData.style.fill,
7383
- opacity: path.userData.style.fillOpacity,
7906
+ color: pathStyle(path).fill,
7907
+ opacity: pathStyle(path).fillOpacity,
7384
7908
  transparent: true,
7385
7909
  side: DoubleSide,
7386
7910
  depthWrite: false,
7387
7911
  ...fillMaterial
7388
7912
  }
7389
- ))), !skipStrokes && path.userData?.style.stroke !== void 0 && path.userData.style.stroke !== "none" && path.subPaths.map((_subPath, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, geometry: strokeGeometries[p][s], ...strokeMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement(
7913
+ ))), !skipStrokes && pathStyle(path)?.stroke !== void 0 && pathStyle(path).stroke !== "none" && path.subPaths.map((_subPath, s) => /* @__PURE__ */ React.createElement("mesh", { key: s, geometry: strokeGeometries[p][s], ...strokeMeshProps, renderOrder: renderOrder++ }, /* @__PURE__ */ React.createElement(
7390
7914
  "meshBasicMaterial",
7391
7915
  {
7392
- color: path.userData.style.stroke,
7393
- opacity: path.userData.style.strokeOpacity,
7916
+ color: pathStyle(path).stroke,
7917
+ opacity: pathStyle(path).strokeOpacity,
7394
7918
  transparent: true,
7395
7919
  side: DoubleSide,
7396
7920
  depthWrite: false,
@@ -7561,7 +8085,7 @@ function Preload({ all, scene, camera }) {
7561
8085
  });
7562
8086
  }
7563
8087
  gl.compile(scene || dScene, camera || dCamera);
7564
- const cubeRenderTarget = new WebGLCubeRenderTarget(128);
8088
+ const cubeRenderTarget = new CubeRenderTarget(128);
7565
8089
  const cubeCamera = new CubeCamera$1(0.01, 1e5, cubeRenderTarget);
7566
8090
  cubeCamera.update(gl, scene || dScene);
7567
8091
  cubeRenderTarget.dispose();
@@ -7613,8 +8137,8 @@ function useVideoTexture(srcOrSrcObject, {
7613
8137
  });
7614
8138
  if (src && IS_BROWSER && src.endsWith(".m3u8")) {
7615
8139
  const hls = hlsRef.current = await getHls(hlsConfig);
7616
- if (hls) {
7617
- hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
8140
+ if (hls && _HLSModule) {
8141
+ hls.on(_HLSModule.Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
7618
8142
  hls.attachMedia(video2);
7619
8143
  }
7620
8144
  }
@@ -7834,9 +8358,125 @@ function Fbx({
7834
8358
  return /* @__PURE__ */ React.createElement(Clone, { ...props, object });
7835
8359
  }
7836
8360
 
8361
+ const SERVICE_KEY = "__drei_ktx2_service__";
8362
+ const DEFAULT_TRANSCODER_PATH = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
8363
+ class KTX2LoaderServiceImpl {
8364
+ loader = null;
8365
+ renderer = null;
8366
+ _isReady = false;
8367
+ transcoderPath = DEFAULT_TRANSCODER_PATH;
8368
+ pendingCallbacks = [];
8369
+ /**
8370
+ * Get or create the singleton KTX2Loader instance.
8371
+ * The loader is created lazily on first access.
8372
+ */
8373
+ getLoader() {
8374
+ if (!this.loader) {
8375
+ this.loader = new KTX2Loader();
8376
+ this.loader.setTranscoderPath(this.transcoderPath);
8377
+ }
8378
+ return this.loader;
8379
+ }
8380
+ /**
8381
+ * Register a renderer and call detectSupport.
8382
+ * Should be called from within React context (useLayoutEffect).
8383
+ * Flushes any queued callbacks.
8384
+ */
8385
+ registerRenderer(renderer) {
8386
+ if (this._isReady && this.renderer === renderer) {
8387
+ return;
8388
+ }
8389
+ this.renderer = renderer;
8390
+ const loader = this.getLoader();
8391
+ loader.detectSupport(renderer);
8392
+ this._isReady = true;
8393
+ this.flushPendingCallbacks();
8394
+ }
8395
+ /**
8396
+ * Check if the service is ready (has renderer registered).
8397
+ */
8398
+ isReady() {
8399
+ return this._isReady;
8400
+ }
8401
+ /**
8402
+ * Get the registered renderer, if any.
8403
+ */
8404
+ getRenderer() {
8405
+ return this.renderer;
8406
+ }
8407
+ /**
8408
+ * Register a callback to run when renderer becomes available.
8409
+ * If renderer is already available, runs immediately.
8410
+ */
8411
+ onReady(callback) {
8412
+ if (this._isReady) {
8413
+ callback();
8414
+ } else {
8415
+ this.pendingCallbacks.push(callback);
8416
+ }
8417
+ }
8418
+ /**
8419
+ * Process all pending callbacks.
8420
+ */
8421
+ flushPendingCallbacks() {
8422
+ const callbacks = this.pendingCallbacks;
8423
+ this.pendingCallbacks = [];
8424
+ for (const callback of callbacks) {
8425
+ try {
8426
+ callback();
8427
+ } catch (error) {
8428
+ console.error("KTX2LoaderService: Error in pending callback:", error);
8429
+ }
8430
+ }
8431
+ }
8432
+ /**
8433
+ * Set the transcoder path for basis/ktx2 decoding.
8434
+ */
8435
+ setTranscoderPath(path) {
8436
+ this.transcoderPath = path;
8437
+ if (this.loader) {
8438
+ this.loader.setTranscoderPath(path);
8439
+ }
8440
+ }
8441
+ /**
8442
+ * Get the current transcoder path.
8443
+ */
8444
+ getTranscoderPath() {
8445
+ return this.transcoderPath;
8446
+ }
8447
+ /**
8448
+ * Dispose of the loader and reset state.
8449
+ * Useful for testing or cleanup.
8450
+ */
8451
+ dispose() {
8452
+ if (this.loader) {
8453
+ this.loader.dispose();
8454
+ }
8455
+ const path = this.transcoderPath;
8456
+ this.loader = null;
8457
+ this.renderer = null;
8458
+ this._isReady = false;
8459
+ this.transcoderPath = path;
8460
+ this.pendingCallbacks = [];
8461
+ }
8462
+ }
8463
+ const KTX2LoaderService = globalThis[SERVICE_KEY] || (globalThis[SERVICE_KEY] = new KTX2LoaderServiceImpl());
8464
+
7837
8465
  let dracoLoader = null;
7838
8466
  let decoderPath = "https://www.gstatic.com/draco/versioned/decoders/1.5.5/";
7839
- function extensions(useDraco = true, useMeshopt = true, extendLoader) {
8467
+ let legacyApiWarned = false;
8468
+ function isOptionsObject(arg) {
8469
+ return typeof arg === "object" && arg !== null && !Array.isArray(arg);
8470
+ }
8471
+ function warnLegacyApi() {
8472
+ if (!legacyApiWarned) {
8473
+ legacyApiWarned = true;
8474
+ console.warn(
8475
+ "useGLTF: Boolean arguments are deprecated. Use options object instead:\n useGLTF(path, { draco: true, meshopt: true, ktx2: true })"
8476
+ );
8477
+ }
8478
+ }
8479
+ function extensions(useDraco = true, useMeshopt = true, useKTX2 = false, extendLoader, renderer) {
7840
8480
  return (loader) => {
7841
8481
  if (extendLoader) {
7842
8482
  extendLoader(loader);
@@ -7852,36 +8492,110 @@ function extensions(useDraco = true, useMeshopt = true, extendLoader) {
7852
8492
  const decoder = typeof MeshoptDecoder === "function" ? MeshoptDecoder() : MeshoptDecoder;
7853
8493
  loader.setMeshoptDecoder(decoder);
7854
8494
  }
8495
+ if (useKTX2) {
8496
+ const ktx2Loader = KTX2LoaderService.getLoader();
8497
+ if (typeof useKTX2 === "string") {
8498
+ ktx2Loader.setTranscoderPath(useKTX2);
8499
+ }
8500
+ if (renderer) {
8501
+ ktx2Loader.detectSupport(renderer);
8502
+ }
8503
+ loader.setKTX2Loader(ktx2Loader);
8504
+ }
7855
8505
  };
7856
8506
  }
7857
- const useGLTF = (path, useDraco, useMeshopt, extendLoader) => useLoader(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
7858
- useGLTF.preload = (path, useDraco, useMeshopt, extendLoader) => useLoader.preload(GLTFLoader, path, extensions(useDraco, useMeshopt, extendLoader));
8507
+ function useGLTF(path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) {
8508
+ let draco = true;
8509
+ let meshopt = true;
8510
+ let ktx2 = false;
8511
+ let extendLoader;
8512
+ if (isOptionsObject(optionsOrDraco)) {
8513
+ draco = optionsOrDraco.draco ?? true;
8514
+ meshopt = optionsOrDraco.meshopt ?? true;
8515
+ ktx2 = optionsOrDraco.ktx2 ?? false;
8516
+ extendLoader = optionsOrDraco.extendLoader;
8517
+ } else if (optionsOrDraco !== void 0) {
8518
+ warnLegacyApi();
8519
+ draco = optionsOrDraco;
8520
+ meshopt = useMeshoptArg ?? true;
8521
+ ktx2 = useKTX2Arg ?? false;
8522
+ extendLoader = extendLoaderArg;
8523
+ }
8524
+ const renderer = useThree((state) => state.renderer);
8525
+ useLayoutEffect(() => {
8526
+ if (ktx2) {
8527
+ KTX2LoaderService.registerRenderer(renderer);
8528
+ }
8529
+ }, [ktx2, renderer]);
8530
+ return useLoader(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, ktx2 ? renderer : void 0));
8531
+ }
8532
+ useGLTF.preload = (path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) => {
8533
+ let draco = true;
8534
+ let meshopt = true;
8535
+ let ktx2 = false;
8536
+ let extendLoader;
8537
+ if (isOptionsObject(optionsOrDraco)) {
8538
+ draco = optionsOrDraco.draco ?? true;
8539
+ meshopt = optionsOrDraco.meshopt ?? true;
8540
+ ktx2 = optionsOrDraco.ktx2 ?? false;
8541
+ extendLoader = optionsOrDraco.extendLoader;
8542
+ } else if (optionsOrDraco !== void 0) {
8543
+ warnLegacyApi();
8544
+ draco = optionsOrDraco;
8545
+ meshopt = useMeshoptArg ?? true;
8546
+ ktx2 = useKTX2Arg ?? false;
8547
+ extendLoader = extendLoaderArg;
8548
+ }
8549
+ if (ktx2) {
8550
+ KTX2LoaderService.onReady(() => {
8551
+ const renderer = KTX2LoaderService.getRenderer();
8552
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, renderer));
8553
+ });
8554
+ } else {
8555
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, false, extendLoader));
8556
+ }
8557
+ };
7859
8558
  useGLTF.clear = (path) => useLoader.clear(GLTFLoader, path);
7860
8559
  useGLTF.setDecoderPath = (path) => {
7861
8560
  decoderPath = path;
7862
8561
  };
8562
+ useGLTF.setKTX2TranscoderPath = (path) => {
8563
+ KTX2LoaderService.setTranscoderPath(path);
8564
+ };
7863
8565
  const Gltf = /* @__PURE__ */ React.forwardRef(
7864
- ({ src, useDraco, useMeshOpt, extendLoader, ...props }, ref) => {
7865
- const { scene } = useGLTF(src, useDraco, useMeshOpt, extendLoader);
8566
+ ({ src, useDraco, useMeshOpt, extendLoader, options, ...props }, ref) => {
8567
+ const opts = options ?? {
8568
+ draco: useDraco,
8569
+ meshopt: useMeshOpt,
8570
+ extendLoader
8571
+ };
8572
+ const { scene } = useGLTF(src, opts);
7866
8573
  return /* @__PURE__ */ React.createElement(Clone, { ref, ...props, object: scene });
7867
8574
  }
7868
8575
  );
7869
8576
 
7870
- let transcoderPath = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
7871
8577
  function useKTX2(input, basisPath = true, extendLoader) {
7872
8578
  const renderer = useThree((state) => state.renderer);
8579
+ useLayoutEffect(() => {
8580
+ KTX2LoaderService.registerRenderer(renderer);
8581
+ }, [renderer]);
7873
8582
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
7874
8583
  if (extendLoader) {
7875
8584
  extendLoader(loader);
7876
8585
  }
7877
8586
  if (basisPath) {
7878
- loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
8587
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
8588
+ loader.setTranscoderPath(path);
7879
8589
  }
7880
8590
  loader.detectSupport(renderer);
7881
8591
  });
7882
8592
  useEffect(() => {
7883
8593
  const array = Array.isArray(textures) ? textures : [textures];
7884
- array.forEach((texture) => renderer.initTexture(texture));
8594
+ array.forEach((texture) => {
8595
+ if ("initTexture" in renderer) {
8596
+ renderer.initTexture(texture);
8597
+ }
8598
+ });
7885
8599
  }, [renderer, textures]);
7886
8600
  if (IsObject(input)) {
7887
8601
  const keys = Object.keys(input);
@@ -7892,17 +8606,26 @@ function useKTX2(input, basisPath = true, extendLoader) {
7892
8606
  return textures;
7893
8607
  }
7894
8608
  }
7895
- useKTX2.preload = (url, basisPath = true, extendLoader) => useLoader.preload(KTX2Loader, url, (loader) => {
7896
- if (extendLoader) {
7897
- extendLoader(loader);
7898
- }
7899
- if (basisPath) {
7900
- loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
7901
- }
7902
- });
8609
+ useKTX2.preload = (url, basisPath = true, extendLoader) => {
8610
+ KTX2LoaderService.onReady(() => {
8611
+ const renderer = KTX2LoaderService.getRenderer();
8612
+ useLoader.preload(KTX2Loader, url, (loader) => {
8613
+ if (extendLoader) {
8614
+ extendLoader(loader);
8615
+ }
8616
+ if (basisPath) {
8617
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
8618
+ loader.setTranscoderPath(path);
8619
+ }
8620
+ if (renderer) {
8621
+ loader.detectSupport(renderer);
8622
+ }
8623
+ });
8624
+ });
8625
+ };
7903
8626
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
7904
8627
  useKTX2.setTranscoderPath = (path) => {
7905
- transcoderPath = path;
8628
+ KTX2LoaderService.setTranscoderPath(path);
7906
8629
  };
7907
8630
  const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
7908
8631
  const texture = useKTX2(input, basisPath, extendLoader);
@@ -8096,6 +8819,7 @@ function useEffectfulState(fn, deps = [], cb) {
8096
8819
  }
8097
8820
 
8098
8821
  function Stats({ showPanel = 0, className, parent }) {
8822
+ React.useEffect(() => void console.warn("drei: <Stats /> is deprecated, use <StatsGl /> instead."), []);
8099
8823
  const stats = useEffectfulState(() => new StatsImpl(), []);
8100
8824
  React.useEffect(() => {
8101
8825
  if (stats) {
@@ -8124,6 +8848,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8124
8848
  const stats2 = new Stats$1({
8125
8849
  ...props
8126
8850
  });
8851
+ stats2.init(renderer);
8127
8852
  return stats2;
8128
8853
  }, [renderer]);
8129
8854
  React.useImperativeHandle(fref, () => stats.domElement, [stats]);
@@ -8141,7 +8866,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8141
8866
  return () => {
8142
8867
  if (classNames.length) stats.domElement.classList.remove(...classNames);
8143
8868
  node?.removeChild(stats.domElement);
8144
- stats.end();
8869
+ stats.dispose();
8145
8870
  };
8146
8871
  }
8147
8872
  }, [parent, stats, className, id, clearStatsGlStyle]);
@@ -8149,7 +8874,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8149
8874
  () => {
8150
8875
  stats.begin();
8151
8876
  },
8152
- { before: "render" }
8877
+ { before: "update" }
8153
8878
  );
8154
8879
  useFrame(
8155
8880
  () => {
@@ -8314,8 +9039,9 @@ const TUNNEL_KEY = "__drei_view_tunnel__";
8314
9039
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
8315
9040
  function computeContainerPosition(canvasSize, trackRect, forceEven) {
8316
9041
  const { right, top: trackTop, left: trackLeft, bottom: trackBottom, width, height } = trackRect;
8317
- const isOffscreen = trackRect.bottom < 0 || trackTop > canvasSize.height || right < 0 || trackRect.left > canvasSize.width;
8318
9042
  const canvasBottom = canvasSize.top + canvasSize.height;
9043
+ const canvasRight = canvasSize.left + canvasSize.width;
9044
+ const isOffscreen = trackBottom < canvasSize.top || trackTop > canvasBottom || right < canvasSize.left || trackLeft > canvasRight;
8319
9045
  const rawBottom = canvasBottom - trackBottom;
8320
9046
  const rawLeft = trackLeft - canvasSize.left;
8321
9047
  const rawTop = trackTop - canvasSize.top;
@@ -8875,12 +9601,12 @@ useEnvironment.preload = (preloadOptions) => {
8875
9601
  if (extensions) extensions(loader2);
8876
9602
  });
8877
9603
  };
8878
- const clearDefaultOptins = {
9604
+ const clearDefaultOptions = {
8879
9605
  files: defaultFiles,
8880
9606
  preset: void 0
8881
9607
  };
8882
9608
  useEnvironment.clear = (clearOptions) => {
8883
- const options = { ...clearDefaultOptins, ...clearOptions };
9609
+ const options = { ...clearDefaultOptions, ...clearOptions };
8884
9610
  let { files } = options;
8885
9611
  const { preset } = options;
8886
9612
  if (preset) {
@@ -9003,7 +9729,7 @@ function EnvironmentPortal({
9003
9729
  const camera = React.useRef(null);
9004
9730
  const [virtualScene] = React.useState(() => new Scene());
9005
9731
  const fbo = React.useMemo(() => {
9006
- const fbo2 = new WebGLCubeRenderTarget(resolution);
9732
+ const fbo2 = new CubeRenderTarget(resolution);
9007
9733
  fbo2.texture.type = HalfFloatType;
9008
9734
  return fbo2;
9009
9735
  }, [resolution]);
@@ -9574,9 +10300,14 @@ function Select({
9574
10300
  const oldControlsEnabled = controls?.enabled;
9575
10301
  let isDown = false;
9576
10302
  function prepareRay(event, vec) {
9577
- const { offsetX, offsetY } = event;
10303
+ const canvas = gl.domElement;
10304
+ const rect = canvas.getBoundingClientRect();
10305
+ const x = event.clientX - rect.left;
10306
+ const y = event.clientY - rect.top;
9578
10307
  const { width, height } = size;
9579
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
10308
+ const normalizedX = x / width * 2 - 1;
10309
+ const normalizedY = -(y / height) * 2 + 1;
10310
+ vec.set(normalizedX, normalizedY, -1);
9580
10311
  }
9581
10312
  function onSelectStart(event) {
9582
10313
  if (controls) controls.enabled = false;
@@ -10428,7 +11159,7 @@ const ImageWithUrl = /* @__PURE__ */ React.forwardRef(
10428
11159
  return /* @__PURE__ */ React.createElement(ImageBase, { ...props, texture, ref });
10429
11160
  }
10430
11161
  );
10431
- const ImageWithTexture = /* @__PURE__ */ React.forwardRef(({ url: _url, ...props }, ref) => {
11162
+ const ImageWithTexture = /* @__PURE__ */ React.forwardRef(({ ...props }, ref) => {
10432
11163
  return /* @__PURE__ */ React.createElement(ImageBase, { ...props, ref });
10433
11164
  });
10434
11165
  const Image = /* @__PURE__ */ React.forwardRef((props, ref) => {
@@ -10632,16 +11363,16 @@ class SparklesMaterial extends SpriteNodeMaterial {
10632
11363
  return pos;
10633
11364
  })();
10634
11365
  this.scaleNode = Fn(() => {
10635
- const worldPos = modelWorldMatrix.mul(vec4(this.positionNode, 1));
10636
- const viewPos = cameraViewMatrix.mul(worldPos);
10637
- const distanceAttenuation = float(1).div(viewPos.z.negate());
10638
- const size = particleSize.mul(0.5).mul(this._pixelRatio).mul(distanceAttenuation);
11366
+ const pixelSize = particleSize.mul(25).mul(this._pixelRatio);
11367
+ const projY = cameraProjectionMatrix.element(1).y;
11368
+ const viewportH = screenSize.y;
11369
+ const size = pixelSize.mul(2).div(projY.mul(viewportH));
10639
11370
  return vec2$2(size);
10640
11371
  })();
10641
11372
  this.colorNode = Fn(() => {
10642
11373
  const quadUV = uv();
10643
11374
  const distanceToCenter = length(quadUV.sub(vec2$2(0.5)));
10644
- const strength = float(0.05).div(distanceToCenter).sub(0.1);
11375
+ const strength = clamp(float(0.05).div(distanceToCenter).sub(0.1), 0, 1);
10645
11376
  return vec4(particleColor, strength.mul(particleOpacity));
10646
11377
  })();
10647
11378
  }
@@ -10912,17 +11643,95 @@ const Grid = /* @__PURE__ */ React.forwardRef(
10912
11643
  }
10913
11644
  );
10914
11645
 
10915
- let hasWarned = false;
10916
- function warnOnce(message) {
10917
- if (!hasWarned) {
10918
- console.warn(message);
10919
- hasWarned = true;
11646
+ const createPCSSFilter = (size, samples, focus) => {
11647
+ const sampleCount = Math.min(Math.max(samples, 1), 20);
11648
+ return Fn(({ depthTexture, shadowCoord, shadow, depthLayer }) => {
11649
+ const sampleDepth = (uv2) => {
11650
+ const tex = texture(depthTexture, uv2);
11651
+ const depth = depthTexture.isArrayTexture ? tex.depth(depthLayer) : tex;
11652
+ return depth.r;
11653
+ };
11654
+ const mapSize = reference("mapSize", "vec2", shadow).setGroup(renderGroup);
11655
+ const texelSize = vec2$2(1).div(mapSize);
11656
+ const phi = interleavedGradientNoise(screenCoordinate.xy).mul(6.28318530718);
11657
+ const uv = shadowCoord.xy;
11658
+ const zReceiver = shadowCoord.z.add(1).mul(0.5);
11659
+ const blockerSearchRadius = texelSize.x.mul(size).mul(2);
11660
+ const blockerDepthSum = float(focus).toVar("blockerDepthSum");
11661
+ const blockerCount = float(0).toVar("blockerCount");
11662
+ for (let i = 0; i < sampleCount; i++) {
11663
+ const offset = vogelDiskSample(int(i), int(sampleCount), phi).mul(blockerSearchRadius);
11664
+ const sampleUV = uv.add(offset);
11665
+ const sampleDepthVal = sampleDepth(sampleUV);
11666
+ const isBlocker = step(sampleDepthVal, zReceiver).sub(step(zReceiver, sampleDepthVal)).max(0);
11667
+ blockerDepthSum.addAssign(sampleDepthVal.mul(isBlocker));
11668
+ blockerCount.addAssign(isBlocker);
11669
+ }
11670
+ const avgBlockerDepth = blockerDepthSum.div(blockerCount.max(1));
11671
+ const penumbraRatio = zReceiver.sub(avgBlockerDepth).div(avgBlockerDepth.max(1e-4));
11672
+ const filterRadius = texelSize.x.mul(float(1).add(penumbraRatio.mul(1.25).mul(size)));
11673
+ const shadowSum = float(0).toVar("shadowSum");
11674
+ for (let i = 0; i < sampleCount; i++) {
11675
+ const offset = vogelDiskSample(int(i), int(sampleCount), phi).mul(filterRadius);
11676
+ const sampleUV = uv.add(offset);
11677
+ const sampleDepthVal = sampleDepth(sampleUV);
11678
+ shadowSum.addAssign(step(zReceiver, sampleDepthVal));
11679
+ }
11680
+ const shadowResult = shadowSum.div(sampleCount);
11681
+ const hasBlockers = blockerCount.greaterThan(0.5);
11682
+ return hasBlockers.select(shadowResult, float(1));
11683
+ });
11684
+ };
11685
+ class PCSSShadowNode extends ShadowNode {
11686
+ size;
11687
+ samples;
11688
+ focus;
11689
+ constructor(light, { size = 25, samples = 10, focus = 0 } = {}) {
11690
+ super(light, light.shadow);
11691
+ this.size = size;
11692
+ this.samples = samples;
11693
+ this.focus = focus;
11694
+ }
11695
+ /**
11696
+ * Override to disable hardware depth comparison.
11697
+ * PCSS needs raw depth values for blocker search, so we can't use comparison samplers.
11698
+ */
11699
+ setupRenderTarget(shadow, builder) {
11700
+ const parentMethod = Object.getPrototypeOf(Object.getPrototypeOf(this)).setupRenderTarget;
11701
+ const result = parentMethod.call(this, shadow, builder);
11702
+ if (result.depthTexture) {
11703
+ result.depthTexture.compareFunction = null;
11704
+ }
11705
+ return result;
11706
+ }
11707
+ /**
11708
+ * Override to use our PCSS filter instead of the default filter.
11709
+ */
11710
+ getShadowFilterFn(_type) {
11711
+ return createPCSSFilter(this.size, this.samples, this.focus);
10920
11712
  }
10921
11713
  }
10922
- function SoftShadows(props) {
10923
- warnOnce(
10924
- "drei: SoftShadows is not yet supported in WebGPU. It uses ShaderChunk to inject PCSS shaders which is WebGL-only. Import from @react-three/drei/legacy for WebGL."
10925
- );
11714
+
11715
+ function SoftShadows({ size = 25, samples = 10, focus = 0 }) {
11716
+ const scene = useThree((state) => state.scene);
11717
+ React.useLayoutEffect(() => {
11718
+ const originalShadowNodes = /* @__PURE__ */ new Map();
11719
+ scene.traverse((obj) => {
11720
+ const light = obj;
11721
+ if (light.isLight && light.castShadow && light.shadow) {
11722
+ originalShadowNodes.set(light.uuid, light.shadow.shadowNode);
11723
+ light.shadow.shadowNode = new PCSSShadowNode(light, { size, samples, focus });
11724
+ }
11725
+ });
11726
+ return () => {
11727
+ scene.traverse((obj) => {
11728
+ const light = obj;
11729
+ if (originalShadowNodes.has(light.uuid)) {
11730
+ light.shadow.shadowNode = originalShadowNodes.get(light.uuid);
11731
+ }
11732
+ });
11733
+ };
11734
+ }, [scene, size, samples, focus]);
10926
11735
  return null;
10927
11736
  }
10928
11737