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

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, AdditiveBlending, SpriteNodeMaterial, ShadowNode, CylinderGeometry } from 'three/webgpu';
3
+ import { Fn, float, vec4, uv, vec2 as vec2$2, uniform, uniformTexture, Discard, materialReference, context as context$a, max, materialAlphaTest, sub, mix, output, mul, negate, add, dot, refract, div, length, cross, select, vec3, normalize, normalWorld, positionWorld, materialColor, 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, materialIOR, equirectUV, materialOpacity, reflect, smoothstep, round, distance, positionLocal, mx_noise_vec3, cos, mat3, normalLocal, attribute, modelViewMatrix, varying, min, frontFacing, fwidth, texture, 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
- 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
+ import { useThree, useFrame, extend, createPortal, applyProps, useInstanceHandle, context as context$b, useLoader, addEffect, addAfterEffect } from '@react-three/fiber';
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';
@@ -44,43 +44,150 @@ import { TransformControls as TransformControls$1 } from 'three/examples/jsm/con
44
44
  import tunnel from 'tunnel-rat';
45
45
  import 'zustand/middleware';
46
46
 
47
- class DepthNodeMaterial extends MeshBasicNodeMaterial {
48
- _opacity;
47
+ function assignUniform(node, factory, v) {
48
+ const current = node.value;
49
+ if (v === null || v === void 0) {
50
+ if (current && current.isTexture) node.value = factory().value;
51
+ else node.value = v;
52
+ return;
53
+ }
54
+ if (typeof current === "number") {
55
+ node.value = typeof v === "boolean" ? v ? 1 : 0 : v;
56
+ return;
57
+ }
58
+ if (typeof current === "boolean") {
59
+ node.value = !!v;
60
+ return;
61
+ }
62
+ if (current && typeof current === "object") {
63
+ if (typeof v === "object" && !Array.isArray(v)) {
64
+ node.value = v;
65
+ return;
66
+ }
67
+ if (Array.isArray(v) && typeof current.fromArray === "function") {
68
+ current.fromArray(v);
69
+ return;
70
+ }
71
+ if (current.isColor) {
72
+ current.set(v);
73
+ return;
74
+ }
75
+ if (typeof v === "number" && typeof current.setScalar === "function") {
76
+ current.setScalar(v);
77
+ return;
78
+ }
79
+ }
80
+ node.value = v;
81
+ }
82
+ function withUniforms(Base, table) {
83
+ const keys = Object.keys(table);
84
+ const factories = table;
85
+ let installed = false;
86
+ const install = (instance) => {
87
+ for (const key of keys) {
88
+ if (key.startsWith("_") || key in instance) {
89
+ throw new Error(
90
+ `withUniforms: "${key}" is reserved on ${Base.name}. Read three's own node for it (materialOpacity, materialColor, materialReference(...)) instead of declaring a uniform named "${key}".`
91
+ );
92
+ }
93
+ }
94
+ for (const key of keys) {
95
+ Object.defineProperty(Wrapped.prototype, key, {
96
+ get() {
97
+ return this._uniforms?.[key]?.value;
98
+ },
99
+ set(v) {
100
+ const node = this._uniforms?.[key];
101
+ if (node) assignUniform(node, factories[key], v);
102
+ },
103
+ enumerable: false,
104
+ configurable: true
105
+ });
106
+ }
107
+ installed = true;
108
+ };
109
+ const splitParameters = (args) => {
110
+ const params = args[0];
111
+ if (!params || typeof params !== "object" || Array.isArray(params)) return { args, own: null };
112
+ const own = {};
113
+ const rest = {};
114
+ let hasOwn = false;
115
+ for (const k in params) {
116
+ if (Object.prototype.hasOwnProperty.call(factories, k)) {
117
+ own[k] = params[k];
118
+ hasOwn = true;
119
+ } else rest[k] = params[k];
120
+ }
121
+ return hasOwn ? { args: [rest, ...args.slice(1)], own } : { args, own: null };
122
+ };
123
+ class Wrapped extends Base {
124
+ constructor(...args) {
125
+ const { args: baseArgs, own } = splitParameters(args);
126
+ super(...baseArgs);
127
+ if (!installed) install(this);
128
+ const nodes = {};
129
+ for (const key of keys) nodes[key] = factories[key]();
130
+ Object.defineProperty(this, "_uniforms", { value: nodes, enumerable: false, writable: false });
131
+ if (own) for (const key in own) this[key] = own[key];
132
+ }
133
+ get uniforms() {
134
+ return this._uniforms;
135
+ }
136
+ /** Copies values for inherited accessors that NodeMaterial.copy does not visit. */
137
+ copy(source) {
138
+ super.copy(source);
139
+ const from = source._uniforms;
140
+ if (!from) return this;
141
+ for (const key of keys) {
142
+ const value = from[key]?.value;
143
+ const node = this._uniforms[key];
144
+ const own = node.value;
145
+ if (value && value.isTexture) node.value = value;
146
+ else if (own && typeof own.copy === "function") own.copy(value);
147
+ else node.value = value;
148
+ }
149
+ return this;
150
+ }
151
+ }
152
+ return Wrapped;
153
+ }
154
+
155
+ class DepthNodeMaterial extends withUniforms(MeshBasicNodeMaterial, {
156
+ /** Shadow strength, 1 = full darkening, 0 = none */
157
+ shadowOpacity: () => uniform(1)
158
+ }) {
49
159
  constructor(opacity = 1) {
50
160
  super();
51
- this._opacity = uniform(opacity);
161
+ this.shadowOpacity = opacity;
52
162
  this.depthTest = false;
53
163
  this.depthWrite = false;
54
164
  this._buildShader();
55
165
  }
56
166
  _buildShader() {
57
- const opacityUniform = this._opacity;
167
+ const { shadowOpacity } = this.uniforms;
58
168
  this.colorNode = Fn(() => {
59
- const shadowStrength = opacityUniform.mul(0.7);
169
+ const shadowStrength = shadowOpacity.mul(0.7);
60
170
  const gray = float(1).sub(shadowStrength);
61
171
  return vec4(gray, gray, gray, 1);
62
172
  })();
63
173
  }
64
- set shadowOpacity(value) {
65
- this._opacity.value = value;
66
- }
67
174
  }
68
- class BlurNodeMaterial extends MeshBasicNodeMaterial {
69
- _inputTexture;
70
- _blurAmount;
175
+ class BlurNodeMaterial extends withUniforms(MeshBasicNodeMaterial, {
176
+ /** Texture to blur */
177
+ inputTexture: () => uniformTexture(new THREE.Texture()),
178
+ /** Per-tap UV offset */
179
+ blurAmount: () => uniform(0)
180
+ }) {
71
181
  _direction;
72
182
  constructor(direction = "horizontal") {
73
183
  super();
74
- this._inputTexture = uniformTexture(new THREE.Texture());
75
- this._blurAmount = uniform(0);
76
184
  this._direction = direction;
77
185
  this.depthTest = false;
78
186
  this.depthWrite = false;
79
187
  this._buildShader();
80
188
  }
81
189
  _buildShader() {
82
- const inputTex = this._inputTexture;
83
- const blurAmountUniform = this._blurAmount;
190
+ const { inputTexture: inputTex, blurAmount: blurAmountUniform } = this.uniforms;
84
191
  const isHorizontal = this._direction === "horizontal";
85
192
  const weights = [0.051, 0.0918, 0.12245, 0.1531, 0.1633, 0.1531, 0.12245, 0.0918, 0.051];
86
193
  this.colorNode = Fn(() => {
@@ -89,18 +196,12 @@ class BlurNodeMaterial extends MeshBasicNodeMaterial {
89
196
  for (let i = 0; i < 9; i++) {
90
197
  const offset = float(i - 4).mul(blurAmountUniform);
91
198
  const sampleUV = isHorizontal ? texCoord.add(vec2$2(offset, 0)) : texCoord.add(vec2$2(0, offset));
92
- const sampledColor = texture(inputTex, sampleUV);
199
+ const sampledColor = inputTex.sample(sampleUV);
93
200
  result.addAssign(sampledColor.mul(weights[i]));
94
201
  }
95
202
  return result;
96
203
  })();
97
204
  }
98
- set inputTexture(value) {
99
- this._inputTexture.value = value;
100
- }
101
- set blurAmount(value) {
102
- this._blurAmount.value = value;
103
- }
104
205
  }
105
206
  const ContactShadows = /* @__PURE__ */ React.forwardRef(
106
207
  ({
@@ -231,28 +332,304 @@ const ContactShadows = /* @__PURE__ */ React.forwardRef(
231
332
  }
232
333
  );
233
334
 
335
+ const discardFragment = /* @__PURE__ */ Fn(() => {
336
+ Discard();
337
+ return vec4(0, 0, 0, 0);
338
+ });
339
+ class DiscardNodeMaterial extends MeshBasicNodeMaterial {
340
+ constructor() {
341
+ super();
342
+ this.fragmentNode = discardFragment();
343
+ }
344
+ }
345
+ const DiscardMaterial = /* @__PURE__ */ new DiscardNodeMaterial();
346
+
347
+ function isLight(object) {
348
+ return object.isLight;
349
+ }
350
+ function isGeometry$1(object) {
351
+ return !!object.geometry;
352
+ }
234
353
  const accumulativeContext = /* @__PURE__ */ React.createContext(
235
354
  null
236
355
  );
237
- let hasWarned$1 = false;
238
- function warnOnce$1(message) {
239
- if (!hasWarned$1) {
240
- console.warn(message);
241
- hasWarned$1 = true;
356
+ class SoftShadowMaterialImpl extends withUniforms(MeshBasicNodeMaterial, {
357
+ /** Shadow color, black */
358
+ shadowColor: () => uniform(new THREE.Color()),
359
+ /** Colorblend, how much colors turn to black, 0 is black */
360
+ blend: () => uniform(2)
361
+ }) {
362
+ constructor() {
363
+ super();
364
+ this.transparent = true;
365
+ this.depthWrite = false;
366
+ this.opacity = 0;
367
+ this.alphaTest = 0.75;
368
+ this.map = new THREE.Texture();
369
+ this.alphaTestNode = float(0);
370
+ this._buildShader();
371
+ }
372
+ _buildShader() {
373
+ const { shadowColor, blend } = this.uniforms;
374
+ const map = materialReference("map", "texture", this);
375
+ this.colorNode = Fn(() => {
376
+ const sampled = context$a(map, { getUV: () => uv() });
377
+ const rgb = shadowColor.mul(sampled.r).mul(blend);
378
+ const sum = sampled.r.add(sampled.g).add(sampled.b);
379
+ const alpha = max(float(0), float(1).sub(sum.div(materialAlphaTest)));
380
+ return vec4(rgb, alpha);
381
+ })();
242
382
  }
243
383
  }
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
- });
384
+ class ProgressiveShadowMaterial extends withUniforms(MeshPhongNodeMaterial, {
385
+ /** Accumulation buffer from the previous frame, blended with the current shading */
386
+ previousShadowMap: () => uniformTexture(new THREE.Texture()),
387
+ /** Mix 1/averagingWindow of the new shading into the accumulated frames */
388
+ averagingWindow: () => uniform(100)
389
+ }) {
390
+ constructor(initialTexture) {
391
+ super();
392
+ this.previousShadowMap = initialTexture;
393
+ this.fog = false;
394
+ this._buildShader();
395
+ }
396
+ _buildShader() {
397
+ const { previousShadowMap, averagingWindow } = this.uniforms;
398
+ const uvNode = uv();
399
+ this.vertexNode = vec4(sub(uvNode, vec2$2(0.5)).mul(2), 1, 1);
400
+ this.outputNode = vec4(mix(previousShadowMap.sample(uv()), output, float(1).div(averagingWindow)));
401
+ }
402
+ }
403
+ class ProgressiveLightMap {
404
+ renderer;
405
+ res;
406
+ scene;
407
+ object;
408
+ buffer1Active;
409
+ progressiveLightMap1;
410
+ progressiveLightMap2;
411
+ discardMat;
412
+ targetMat;
413
+ clearColor;
414
+ clearAlpha;
415
+ lights;
416
+ meshes;
417
+ constructor(renderer, scene, res = 1024) {
418
+ this.renderer = renderer;
419
+ this.res = res;
420
+ this.scene = scene;
421
+ this.buffer1Active = false;
422
+ this.lights = [];
423
+ this.meshes = [];
424
+ this.object = null;
425
+ this.clearColor = new THREE.Color();
426
+ this.clearAlpha = 0;
427
+ const textureParams = {
428
+ type: THREE.HalfFloatType,
429
+ magFilter: THREE.NearestFilter,
430
+ minFilter: THREE.NearestFilter
431
+ };
432
+ this.progressiveLightMap1 = new RenderTarget(this.res, this.res, textureParams);
433
+ this.progressiveLightMap2 = new RenderTarget(this.res, this.res, textureParams);
434
+ this.discardMat = DiscardMaterial;
435
+ this.targetMat = new ProgressiveShadowMaterial(this.progressiveLightMap1.texture);
436
+ }
437
+ clear() {
438
+ this.renderer.getClearColor(this.clearColor);
439
+ this.clearAlpha = this.renderer.getClearAlpha();
440
+ this.renderer.setClearColor("black", 1);
441
+ this.renderer.setRenderTarget(this.progressiveLightMap1);
442
+ this.renderer.clear();
443
+ this.renderer.setRenderTarget(this.progressiveLightMap2);
444
+ this.renderer.clear();
445
+ this.renderer.setRenderTarget(null);
446
+ this.renderer.setClearColor(this.clearColor, this.clearAlpha);
447
+ this.lights = [];
448
+ this.meshes = [];
449
+ this.scene.traverse((object) => {
450
+ if (isGeometry$1(object)) {
451
+ this.meshes.push({ object, material: object.material });
452
+ } else if (isLight(object)) {
453
+ this.lights.push({ object, intensity: object.intensity });
454
+ }
455
+ });
456
+ }
457
+ prepare() {
458
+ this.lights.forEach((light) => light.object.intensity = 0);
459
+ this.meshes.forEach((mesh) => mesh.object.material = this.discardMat);
460
+ }
461
+ finish() {
462
+ this.lights.forEach((light) => light.object.intensity = light.intensity);
463
+ this.meshes.forEach((mesh) => mesh.object.material = mesh.material);
464
+ }
465
+ configure(object) {
466
+ this.object = object;
467
+ }
468
+ update(camera, blendWindow = 100) {
469
+ if (!this.object) return;
470
+ this.targetMat.averagingWindow = blendWindow;
471
+ const originalMaterial = this.object.material;
472
+ this.object.material = this.targetMat;
473
+ const oldFrustumCulled = this.object.frustumCulled;
474
+ this.object.frustumCulled = false;
475
+ const activeMap = this.buffer1Active ? this.progressiveLightMap1 : this.progressiveLightMap2;
476
+ const inactiveMap = this.buffer1Active ? this.progressiveLightMap2 : this.progressiveLightMap1;
477
+ const oldBg = this.scene.background;
478
+ this.scene.background = null;
479
+ this.renderer.setRenderTarget(activeMap);
480
+ this.targetMat.previousShadowMap = inactiveMap.texture;
481
+ this.buffer1Active = !this.buffer1Active;
482
+ this.renderer.render(this.scene, camera);
483
+ this.renderer.setRenderTarget(null);
484
+ this.scene.background = oldBg;
485
+ this.object.frustumCulled = oldFrustumCulled;
486
+ this.object.material = originalMaterial;
487
+ }
488
+ }
489
+ const AccumulativeShadows = /* @__PURE__ */ React.forwardRef(
490
+ ({
491
+ children,
492
+ temporal,
493
+ frames = 40,
494
+ limit = Infinity,
495
+ blend = 20,
496
+ scale = 10,
497
+ opacity = 1,
498
+ alphaTest = 0.75,
499
+ color = "black",
500
+ colorBlend = 2,
501
+ resolution = 1024,
502
+ toneMapped = true,
503
+ ...props
504
+ }, forwardRef) => {
505
+ const gl = useThree((state) => state.gl);
506
+ const scene = useThree((state) => state.scene);
507
+ const camera = useThree((state) => state.camera);
508
+ const invalidate = useThree((state) => state.invalidate);
509
+ const gPlane = React.useRef(null);
510
+ const gLights = React.useRef(null);
511
+ const [softShadowMat] = React.useState(() => new SoftShadowMaterialImpl());
512
+ const [plm] = React.useState(() => new ProgressiveLightMap(gl, scene, resolution));
513
+ React.useLayoutEffect(() => {
514
+ plm.configure(gPlane.current);
515
+ }, [plm]);
516
+ React.useEffect(() => {
517
+ softShadowMat.shadowColor.set(color);
518
+ softShadowMat.blend = colorBlend;
519
+ softShadowMat.map = plm.progressiveLightMap2.texture;
520
+ softShadowMat.toneMapped = toneMapped;
521
+ }, [softShadowMat, color, colorBlend, plm, toneMapped]);
522
+ const api = React.useMemo(
523
+ () => ({
524
+ lights: /* @__PURE__ */ new Map(),
525
+ temporal: !!temporal,
526
+ frames: Math.max(2, frames),
527
+ blend: Math.max(2, frames === Infinity ? blend : frames),
528
+ count: 0,
529
+ getMesh: () => gPlane.current,
530
+ reset: () => {
531
+ plm.clear();
532
+ softShadowMat.opacity = 0;
533
+ softShadowMat.alphaTest = 0;
534
+ api.count = 0;
535
+ },
536
+ update: (frames2 = 1) => {
537
+ if (!api.temporal) {
538
+ softShadowMat.opacity = opacity;
539
+ softShadowMat.alphaTest = alphaTest;
540
+ } else {
541
+ softShadowMat.opacity = Math.min(opacity, softShadowMat.opacity + opacity / api.blend);
542
+ softShadowMat.alphaTest = Math.min(alphaTest, softShadowMat.alphaTest + alphaTest / api.blend);
543
+ }
544
+ gLights.current.visible = true;
545
+ plm.prepare();
546
+ for (let i = 0; i < frames2; i++) {
547
+ api.lights.forEach((light) => light.update());
548
+ plm.update(camera, api.blend);
549
+ }
550
+ gLights.current.visible = false;
551
+ plm.finish();
552
+ }
553
+ }),
554
+ [plm, camera, scene, temporal, frames, blend, opacity, alphaTest, softShadowMat]
555
+ );
556
+ React.useLayoutEffect(() => {
557
+ api.reset();
558
+ if (!api.temporal && api.frames !== Infinity) api.update(api.blend);
559
+ });
560
+ React.useImperativeHandle(forwardRef, () => api, [api]);
561
+ useFrame(() => {
562
+ if ((api.temporal || api.frames === Infinity) && api.count < api.frames && api.count < limit) {
563
+ invalidate();
564
+ api.update();
565
+ api.count++;
566
+ }
567
+ });
568
+ 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" })));
569
+ }
570
+ );
571
+ const RandomizedLight = /* @__PURE__ */ React.forwardRef(
572
+ ({
573
+ castShadow = true,
574
+ bias = 1e-3,
575
+ mapSize = 512,
576
+ size = 5,
577
+ near = 0.5,
578
+ far = 500,
579
+ frames = 1,
580
+ position = [0, 0, 0],
581
+ radius = 1,
582
+ amount = 8,
583
+ intensity = Math.PI,
584
+ ambient = 0.5,
585
+ ...props
586
+ }, forwardRef) => {
587
+ const gLights = React.useRef(null);
588
+ const length = new THREE.Vector3(...position).length();
589
+ const parent = React.useContext(accumulativeContext);
590
+ const update = React.useCallback(() => {
591
+ let light;
592
+ if (gLights.current) {
593
+ for (let l = 0; l < gLights.current.children.length; l++) {
594
+ light = gLights.current.children[l];
595
+ if (Math.random() > ambient) {
596
+ light.position.set(
597
+ position[0] + THREE.MathUtils.randFloatSpread(radius),
598
+ position[1] + THREE.MathUtils.randFloatSpread(radius),
599
+ position[2] + THREE.MathUtils.randFloatSpread(radius)
600
+ );
601
+ } else {
602
+ let lambda = Math.acos(2 * Math.random() - 1) - Math.PI / 2;
603
+ let phi = 2 * Math.PI * Math.random();
604
+ light.position.set(
605
+ Math.cos(lambda) * Math.cos(phi) * length,
606
+ Math.abs(Math.cos(lambda) * Math.sin(phi) * length),
607
+ Math.sin(lambda) * length
608
+ );
609
+ }
610
+ }
611
+ }
612
+ }, [radius, ambient, length, ...position]);
613
+ const api = React.useMemo(() => ({ update }), [update]);
614
+ React.useImperativeHandle(forwardRef, () => api, [api]);
615
+ React.useLayoutEffect(() => {
616
+ const group = gLights.current;
617
+ if (parent) parent.lights?.set(group.uuid, api);
618
+ return () => void parent?.lights?.delete(group.uuid);
619
+ }, [parent, api]);
620
+ return /* @__PURE__ */ React.createElement("group", { ref: gLights, ...props }, Array.from({ length: amount }, (_, index) => /* @__PURE__ */ React.createElement(
621
+ "directionalLight",
622
+ {
623
+ key: index,
624
+ castShadow,
625
+ "shadow-bias": bias,
626
+ "shadow-mapSize": [mapSize, mapSize],
627
+ intensity: intensity / amount
628
+ },
629
+ /* @__PURE__ */ React.createElement("orthographicCamera", { attach: "shadow-camera", args: [-size, size, size, -size, near, far] })
630
+ )));
631
+ }
632
+ );
256
633
 
257
634
  function useFBO(width, height, settings) {
258
635
  const size = useThree((state) => state.size);
@@ -350,19 +727,18 @@ const Edges = /* @__PURE__ */ React.forwardRef(
350
727
  }
351
728
  );
352
729
 
353
- class CausticsProjectionMaterial extends MeshBasicNodeMaterial {
354
- _causticsTexture;
355
- _causticsTextureB;
356
- _color;
357
- _lightProjMatrix;
358
- _lightViewMatrix;
730
+ class CausticsProjectionMaterial extends withUniforms(MeshBasicNodeMaterial, {
731
+ /** Front-face caustics, rendered by CausticsMaterial */
732
+ causticsTexture: () => uniformTexture(new THREE.Texture()),
733
+ /** Back-face caustics, rendered by CausticsMaterial when `backside` is enabled */
734
+ causticsTextureB: () => uniformTexture(new THREE.Texture()),
735
+ /** Projection matrix of the light camera */
736
+ lightProjMatrix: () => uniform(new THREE.Matrix4()),
737
+ /** View matrix (matrixWorldInverse) of the light camera */
738
+ lightViewMatrix: () => uniform(new THREE.Matrix4())
739
+ }) {
359
740
  constructor() {
360
741
  super();
361
- this._causticsTexture = uniformTexture(new THREE.Texture());
362
- this._causticsTextureB = uniformTexture(new THREE.Texture());
363
- this._color = uniform(new THREE.Color(1, 1, 1));
364
- this._lightProjMatrix = uniform(new THREE.Matrix4());
365
- this._lightViewMatrix = uniform(new THREE.Matrix4());
366
742
  this.transparent = true;
367
743
  this.blending = THREE.CustomBlending;
368
744
  this.blendSrc = THREE.OneFactor;
@@ -371,100 +747,68 @@ class CausticsProjectionMaterial extends MeshBasicNodeMaterial {
371
747
  this._buildShader();
372
748
  }
373
749
  _buildShader() {
374
- const causticsTexture = this._causticsTexture;
375
- const causticsTextureB = this._causticsTextureB;
376
- const colorUniform = this._color;
377
- const lightProjMatrix = this._lightProjMatrix;
378
- const lightViewMatrix = this._lightViewMatrix;
750
+ const { causticsTexture, causticsTextureB, lightProjMatrix, lightViewMatrix } = this.uniforms;
379
751
  this.colorNode = Fn(() => {
380
752
  const worldPos = positionWorld;
381
753
  const worldPos4 = vec4(worldPos, 1);
382
754
  const lightSpacePos = mul(lightProjMatrix, mul(lightViewMatrix, worldPos4)).toVar();
383
755
  const xyz = lightSpacePos.xyz.div(lightSpacePos.w);
384
756
  const lightUv = xyz.mul(0.5).add(0.5).xy;
385
- const front = texture(causticsTexture, lightUv).rgb;
386
- const back = texture(causticsTextureB, lightUv).rgb;
387
- return vec4(add(front, back).mul(colorUniform), 1);
757
+ const front = causticsTexture.sample(lightUv).rgb;
758
+ const back = causticsTextureB.sample(lightUv).rgb;
759
+ return vec4(add(front, back).mul(materialColor), 1);
388
760
  })();
389
761
  }
390
- //* Uniform Accessors ==============================
391
- get causticsTexture() {
392
- return this._causticsTexture.value;
393
- }
394
- set causticsTexture(v) {
395
- this._causticsTexture.value = v;
396
- }
397
- get causticsTextureB() {
398
- return this._causticsTextureB.value;
399
- }
400
- set causticsTextureB(v) {
401
- this._causticsTextureB.value = v;
402
- }
403
- get color() {
404
- return this._color.value;
405
- }
406
- set color(v) {
407
- this._color.value = v;
408
- }
409
- get lightProjMatrix() {
410
- return this._lightProjMatrix.value;
411
- }
412
- set lightProjMatrix(v) {
413
- this._lightProjMatrix.value = v;
414
- }
415
- get lightViewMatrix() {
416
- return this._lightViewMatrix.value;
417
- }
418
- set lightViewMatrix(v) {
419
- this._lightViewMatrix.value = v;
420
- }
421
762
  }
422
- class CausticsMaterial extends MeshBasicNodeMaterial {
423
- _cameraMatrixWorld;
424
- _cameraProjectionMatrixInv;
425
- _normalTexture;
426
- _depthTexture;
427
- _lightDir;
428
- _lightPlaneNormal;
429
- _lightPlaneConstant;
430
- _near;
431
- _far;
432
- _worldRadius;
433
- _ior;
434
- _resolution;
435
- _size;
436
- _intensity;
763
+ class CausticsMaterial extends withUniforms(MeshBasicNodeMaterial, {
764
+ /** World matrix of the light camera */
765
+ cameraMatrixWorld: () => uniform(new THREE.Matrix4()),
766
+ /** Inverse projection matrix of the light camera, for depth reconstruction */
767
+ cameraProjectionMatrixInv: () => uniform(new THREE.Matrix4()),
768
+ /** World-space normals of the refractive geometry, rendered by CausticsNormalMaterial */
769
+ normalTexture: () => uniformTexture(new THREE.Texture()),
770
+ /** Depth of the refractive geometry, from the same render target as `normalTexture` */
771
+ depthTexture: () => uniformTexture(new THREE.Texture()),
772
+ /** Direction light travels in (inverse of the light position direction) */
773
+ lightDir: () => uniform(new THREE.Vector3(0, 1, 0)),
774
+ /** Normal of the receiving plane in light space */
775
+ lightPlaneNormal: () => uniform(new THREE.Vector3(0, 1, 0)),
776
+ /** Plane constant of the receiving plane */
777
+ lightPlaneConstant: () => uniform(0),
778
+ /** Light camera near plane */
779
+ near: () => uniform(0.1),
780
+ /** Light camera far plane */
781
+ far: () => uniform(100),
782
+ /** The texel size in world units */
783
+ worldRadius: () => uniform(1 / 40),
784
+ /** Refraction index of the surface */
785
+ ior: () => uniform(1.1),
786
+ /** Buffer resolution in pixels */
787
+ resolution: () => uniform(1024),
788
+ /** Half-extent of the light camera frustum */
789
+ size: () => uniform(10),
790
+ /** Intensity of the projected caustics */
791
+ intensity: () => uniform(0.5)
792
+ }) {
437
793
  constructor() {
438
794
  super();
439
- this._cameraMatrixWorld = uniform(new THREE.Matrix4());
440
- this._cameraProjectionMatrixInv = uniform(new THREE.Matrix4());
441
- this._normalTexture = uniformTexture(new THREE.Texture());
442
- this._depthTexture = uniformTexture(new THREE.Texture());
443
- this._lightDir = uniform(new THREE.Vector3(0, 1, 0));
444
- this._lightPlaneNormal = uniform(new THREE.Vector3(0, 1, 0));
445
- this._lightPlaneConstant = uniform(0);
446
- this._near = uniform(0.1);
447
- this._far = uniform(100);
448
- this._worldRadius = uniform(1 / 40);
449
- this._ior = uniform(1.1);
450
- this._resolution = uniform(1024);
451
- this._size = uniform(10);
452
- this._intensity = uniform(0.5);
453
795
  this._buildShader();
454
796
  }
455
797
  _buildShader() {
456
- const cameraMatrixWorld = this._cameraMatrixWorld;
457
- const cameraProjectionMatrixInv = this._cameraProjectionMatrixInv;
458
- const normalTexture = this._normalTexture;
459
- const depthTexture = this._depthTexture;
460
- const lightDir = this._lightDir;
461
- const lightPlaneNormal = this._lightPlaneNormal;
462
- const lightPlaneConstant = this._lightPlaneConstant;
463
- const worldRadius = this._worldRadius;
464
- const ior = this._ior;
465
- const resolution = this._resolution;
466
- const size = this._size;
467
- const intensity = this._intensity;
798
+ const {
799
+ cameraMatrixWorld,
800
+ cameraProjectionMatrixInv,
801
+ normalTexture,
802
+ depthTexture,
803
+ lightDir,
804
+ lightPlaneNormal,
805
+ lightPlaneConstant,
806
+ worldRadius,
807
+ ior,
808
+ resolution,
809
+ size,
810
+ intensity
811
+ } = this.uniforms;
468
812
  const worldPosFromDepth = Fn(({ depth, coord }) => {
469
813
  const z = depth.mul(2).sub(1);
470
814
  const clipSpacePos = vec4(coord.mul(2).sub(1), z, 1);
@@ -491,7 +835,7 @@ class CausticsMaterial extends MeshBasicNodeMaterial {
491
835
  );
492
836
  this.colorNode = Fn(() => {
493
837
  const baseUv = uv();
494
- const centerDepth = texture(depthTexture, baseUv).x;
838
+ const centerDepth = depthTexture.sample(baseUv).x;
495
839
  const causticTexelSize = div(float(1), resolution).mul(size).mul(2);
496
840
  const texelsNeeded = div(worldRadius, causticTexelSize);
497
841
  const sampleRadius = div(texelsNeeded, resolution);
@@ -503,14 +847,14 @@ class CausticsMaterial extends MeshBasicNodeMaterial {
503
847
  const uv2 = add(baseUv, mul(offset2, sampleRadius));
504
848
  const uv3 = add(baseUv, mul(offset3, sampleRadius));
505
849
  const uv4 = add(baseUv, mul(offset4, sampleRadius));
506
- const normal1 = sub(mul(texture(normalTexture, uv1).rgb, 2), 1);
507
- const normal2 = sub(mul(texture(normalTexture, uv2).rgb, 2), 1);
508
- const normal3 = sub(mul(texture(normalTexture, uv3).rgb, 2), 1);
509
- const normal4 = sub(mul(texture(normalTexture, uv4).rgb, 2), 1);
510
- const depth1 = texture(depthTexture, uv1).x;
511
- const depth2 = texture(depthTexture, uv2).x;
512
- const depth3 = texture(depthTexture, uv3).x;
513
- const depth4 = texture(depthTexture, uv4).x;
850
+ const normal1 = sub(mul(normalTexture.sample(uv1).rgb, 2), 1);
851
+ const normal2 = sub(mul(normalTexture.sample(uv2).rgb, 2), 1);
852
+ const normal3 = sub(mul(normalTexture.sample(uv3).rgb, 2), 1);
853
+ const normal4 = sub(mul(normalTexture.sample(uv4).rgb, 2), 1);
854
+ const depth1 = depthTexture.sample(uv1).x;
855
+ const depth2 = depthTexture.sample(uv2).x;
856
+ const depth3 = depthTexture.sample(uv3).x;
857
+ const depth4 = depthTexture.sample(uv4).x;
514
858
  const pos1 = worldPosFromDepth({ depth: depth1, coord: uv1 });
515
859
  const pos2 = worldPosFromDepth({ depth: depth2, coord: uv2 });
516
860
  const pos3 = worldPosFromDepth({ depth: depth3, coord: uv3 });
@@ -549,91 +893,6 @@ class CausticsMaterial extends MeshBasicNodeMaterial {
549
893
  return vec4(vec3(finalCaustic), 1);
550
894
  })();
551
895
  }
552
- //* Uniform Accessors ==============================
553
- get cameraMatrixWorld() {
554
- return this._cameraMatrixWorld.value;
555
- }
556
- set cameraMatrixWorld(v) {
557
- this._cameraMatrixWorld.value = v;
558
- }
559
- get cameraProjectionMatrixInv() {
560
- return this._cameraProjectionMatrixInv.value;
561
- }
562
- set cameraProjectionMatrixInv(v) {
563
- this._cameraProjectionMatrixInv.value = v;
564
- }
565
- get normalTexture() {
566
- return this._normalTexture.value;
567
- }
568
- set normalTexture(v) {
569
- this._normalTexture.value = v ?? new THREE.Texture();
570
- }
571
- get depthTexture() {
572
- return this._depthTexture.value;
573
- }
574
- set depthTexture(v) {
575
- this._depthTexture.value = v ?? new THREE.Texture();
576
- }
577
- get lightDir() {
578
- return this._lightDir.value;
579
- }
580
- set lightDir(v) {
581
- this._lightDir.value = v;
582
- }
583
- get lightPlaneNormal() {
584
- return this._lightPlaneNormal.value;
585
- }
586
- set lightPlaneNormal(v) {
587
- this._lightPlaneNormal.value = v;
588
- }
589
- get lightPlaneConstant() {
590
- return this._lightPlaneConstant.value;
591
- }
592
- set lightPlaneConstant(v) {
593
- this._lightPlaneConstant.value = v;
594
- }
595
- get near() {
596
- return this._near.value;
597
- }
598
- set near(v) {
599
- this._near.value = v;
600
- }
601
- get far() {
602
- return this._far.value;
603
- }
604
- set far(v) {
605
- this._far.value = v;
606
- }
607
- get worldRadius() {
608
- return this._worldRadius.value;
609
- }
610
- set worldRadius(v) {
611
- this._worldRadius.value = v;
612
- }
613
- get ior() {
614
- return this._ior.value;
615
- }
616
- set ior(v) {
617
- this._ior.value = v;
618
- }
619
- get resolution() {
620
- return this._resolution.value;
621
- }
622
- set resolution(v) {
623
- this._resolution.value = v;
624
- }
625
- get size() {
626
- return this._size.value;
627
- }
628
- set size(v) {
629
- this._size.value = v;
630
- }
631
- get intensity() {
632
- return this._intensity.value;
633
- }
634
- set intensity(v) {
635
- this._intensity.value = v;
636
- }
637
896
  }
638
897
  class CausticsNormalMaterial extends MeshNormalNodeMaterial {
639
898
  constructor(side = THREE.FrontSide) {
@@ -645,225 +904,376 @@ class CausticsNormalMaterial extends MeshNormalNodeMaterial {
645
904
  }
646
905
  }
647
906
 
648
- const DiscardMaterial = /* @__PURE__ */ new MeshBasicNodeMaterial({
649
- colorNode: Discard()
907
+ const EnvironmentBRDF = /* @__PURE__ */ Fn((inputs) => {
908
+ const { dotNV, specularColor, specularF90, roughness: roughness2 } = inputs;
909
+ const fab = DFGLUT({ dotNV, roughness: roughness2 });
910
+ return specularColor.mul(fab.x).add(specularF90.mul(fab.y));
650
911
  });
651
-
652
- const rand = /* @__PURE__ */ Fn(({ seed, fragCoord }) => {
912
+ const rand = /* @__PURE__ */ Fn((inputs) => {
913
+ const [seed, fragCoord] = inputs;
653
914
  return fract(sin(dot(vec3(fragCoord.xy, seed), vec3(12.9898, 78.233, 45.164))).mul(43758.5453));
654
915
  });
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));
667
- }
668
- );
669
- class MeshTransmissionMaterialImpl extends MeshPhysicalNodeMaterial {
670
- //* Private Uniform Nodes --
916
+ const applyVolumeAttenuation = /* @__PURE__ */ Fn((inputs) => {
917
+ const [radiance, transmissionDistance, attColor, attDistance] = inputs;
918
+ const isInfinite = attDistance.greaterThan(1e10);
919
+ const safeColor = max(attColor, vec3(1e-4));
920
+ const attenuationCoefficient = log(safeColor).negate().div(attDistance);
921
+ const transmittance = exp(attenuationCoefficient.negate().mul(transmissionDistance));
922
+ return select(isInfinite, radiance, transmittance.mul(radiance));
923
+ });
924
+ const getVolumeTransmissionRay = /* @__PURE__ */ Fn((inputs) => {
925
+ const [n, v, thicknessVal, iorVal] = inputs;
926
+ const refractionVector = refract(v.negate(), normalize(n), float(1).div(iorVal));
927
+ const refractLen = length(refractionVector);
928
+ const safeRefractionVector = select(
929
+ refractLen.greaterThan(1e-3),
930
+ refractionVector,
931
+ v.negate()
932
+ // Fallback: ray goes straight through
933
+ );
934
+ const modelScale = vec3(
935
+ length(modelWorldMatrix[0].xyz),
936
+ length(modelWorldMatrix[1].xyz),
937
+ length(modelWorldMatrix[2].xyz)
938
+ );
939
+ return normalize(safeRefractionVector).mul(thicknessVal.mul(modelScale));
940
+ });
941
+ const getRefractionUV = /* @__PURE__ */ Fn((inputs) => {
942
+ const [norm, viewDir, iorVal, thicknessVal, position] = inputs;
943
+ const transmissionRay = getVolumeTransmissionRay(norm, viewDir, thicknessVal, iorVal);
944
+ const refractedRayExit = position.add(transmissionRay);
945
+ const ndcPos = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedRayExit, 1)));
946
+ const safeW = max(ndcPos.w, float(1e-3));
947
+ const refractionCoords = vec2$2(ndcPos.xy.div(safeW)).toVar();
948
+ refractionCoords.addAssign(1);
949
+ refractionCoords.divAssign(2);
950
+ refractionCoords.assign(vec2$2(refractionCoords.x, refractionCoords.y.oneMinus()));
951
+ refractionCoords.assign(refractionCoords.clamp(1e-3, 0.999));
952
+ return refractionCoords;
953
+ });
954
+ class TransmissionLightingModel extends PhysicalLightingModel {
955
+ _bufferTex;
671
956
  _chromaticAberration;
672
957
  _anisotropicBlur;
673
- _time;
958
+ _timeUniform;
674
959
  _distortion;
675
960
  _distortionScale;
676
961
  _temporalDistortion;
677
- _buffer;
678
- _transmissionValue;
679
- _attenuationDistance;
680
- _attenuationColor;
681
962
  _samples;
682
- _transmissionSampler;
963
+ _debugMode;
964
+ constructor(options, transmissionUniforms) {
965
+ super(
966
+ options.clearcoat ?? false,
967
+ options.sheen ?? false,
968
+ options.iridescence ?? false,
969
+ options.anisotropy ?? false,
970
+ true,
971
+ // transmission always true for our material
972
+ options.dispersion ?? false
973
+ );
974
+ this._bufferTex = transmissionUniforms.bufferTex;
975
+ this._chromaticAberration = transmissionUniforms.chromaticAberration;
976
+ this._anisotropicBlur = transmissionUniforms.anisotropicBlur;
977
+ this._timeUniform = transmissionUniforms.timeUniform;
978
+ this._distortion = transmissionUniforms.distortion;
979
+ this._distortionScale = transmissionUniforms.distortionScale;
980
+ this._temporalDistortion = transmissionUniforms.temporalDistortion;
981
+ this._samples = transmissionUniforms.samples;
982
+ this._debugMode = transmissionUniforms.debugMode;
983
+ }
984
+ start(builder) {
985
+ if (this.clearcoat === true) {
986
+ this.clearcoatRadiance = vec3().toVar("clearcoatRadiance");
987
+ this.clearcoatSpecularDirect = vec3().toVar("clearcoatSpecularDirect");
988
+ this.clearcoatSpecularIndirect = vec3().toVar("clearcoatSpecularIndirect");
989
+ }
990
+ if (this.sheen === true) {
991
+ this.sheenSpecularDirect = vec3().toVar("sheenSpecularDirect");
992
+ this.sheenSpecularIndirect = vec3().toVar("sheenSpecularIndirect");
993
+ }
994
+ if (this.transmission === true) {
995
+ const position = positionWorld;
996
+ const v = normalize(cameraPosition.sub(positionWorld));
997
+ const n = normalWorld;
998
+ const context = builder.context;
999
+ const bufferTex = this._bufferTex;
1000
+ const chromaticAberrationUniform = this._chromaticAberration;
1001
+ const anisotropicBlurUniform = this._anisotropicBlur;
1002
+ const timeUniform = this._timeUniform;
1003
+ const distortionUniform = this._distortion;
1004
+ const distortionScaleUniform = this._distortionScale;
1005
+ const temporalDistortionUniform = this._temporalDistortion;
1006
+ const sampleCount = this._samples;
1007
+ const debugModeUniform = this._debugMode;
1008
+ const getCustomTransmission = Fn(() => {
1009
+ const transmissionAccum = vec3(0).toVar("transmissionAccum");
1010
+ const fragCoord = screenCoordinate.xy;
1011
+ const runningSeed = float(0).toVar("runningSeed");
1012
+ const randomCoords = rand(runningSeed, fragCoord);
1013
+ runningSeed.addAssign(1);
1014
+ const temporalOffset = vec3(timeUniform, timeUniform.negate(), timeUniform.negate()).mul(
1015
+ temporalDistortionUniform
1016
+ );
1017
+ const noiseInput = position.mul(distortionScaleUniform).add(temporalOffset);
1018
+ const distortionNormal = select(
1019
+ distortionUniform.greaterThan(0),
1020
+ mx_fractal_noise_vec3(noiseInput, int(4), float(2), float(0.5)).mul(distortionUniform),
1021
+ vec3(0)
1022
+ );
1023
+ const thicknessSmear = thickness.mul(max(pow(roughness, 0.33), anisotropicBlurUniform));
1024
+ Loop(sampleCount, ({ i }) => {
1025
+ const fi = float(i);
1026
+ const progress = fi.add(randomCoords).div(float(sampleCount));
1027
+ const randX = rand(runningSeed, fragCoord).sub(0.5);
1028
+ runningSeed.addAssign(1);
1029
+ const randY = rand(runningSeed, fragCoord).sub(0.5);
1030
+ runningSeed.addAssign(1);
1031
+ const randZ = rand(runningSeed, fragCoord).sub(0.5);
1032
+ runningSeed.addAssign(1);
1033
+ const randW = rand(runningSeed, fragCoord);
1034
+ runningSeed.addAssign(1);
1035
+ const randomDir = normalize(vec3(randX, randY, randZ)).mul(pow(randW, 0.33));
1036
+ const sampleNorm = normalize(n.add(roughness.mul(roughness).mul(2).mul(randomDir)).add(distortionNormal));
1037
+ const sampleThickness = thickness.add(thicknessSmear.mul(progress));
1038
+ const iorR = ior;
1039
+ const iorG = ior.mul(float(1).add(chromaticAberrationUniform.mul(progress)));
1040
+ const iorB = ior.mul(float(1).add(chromaticAberrationUniform.mul(2).mul(progress)));
1041
+ const uvR = getRefractionUV(sampleNorm, v, iorR, sampleThickness, position);
1042
+ const uvG = getRefractionUV(sampleNorm, v, iorG, sampleThickness, position);
1043
+ const uvB = getRefractionUV(sampleNorm, v, iorB, sampleThickness, position);
1044
+ const sampleR = bufferTex.sample(uvR);
1045
+ const sampleG = bufferTex.sample(uvG);
1046
+ const sampleB = bufferTex.sample(uvB);
1047
+ transmissionAccum.x.addAssign(sampleR.r);
1048
+ transmissionAccum.y.addAssign(sampleG.g);
1049
+ transmissionAccum.z.addAssign(sampleB.b);
1050
+ });
1051
+ const avgTransmission = transmissionAccum.div(float(sampleCount));
1052
+ const debugUV = getRefractionUV(n, v, ior, thickness, position);
1053
+ const debugNdotV = n.dot(v);
1054
+ const isNeg = debugNdotV.lessThan(0);
1055
+ const sampleUVFlipped = vec2$2(screenUV.x, screenUV.y.oneMinus()).clamp(1e-3, 0.999);
1056
+ const directSampleFlipped = bufferTex.sample(sampleUVFlipped);
1057
+ const testRay = getVolumeTransmissionRay(n, v, thickness, ior);
1058
+ const rayLen = length(testRay);
1059
+ const manualUV = screenCoordinate.xy.div(screenSize);
1060
+ const noFlipUV = screenUV.clamp(1e-3, 0.999);
1061
+ const flippedUV = vec2$2(screenUV.x, screenUV.y.oneMinus()).clamp(1e-3, 0.999);
1062
+ const useFlipped = screenUV.x.greaterThan(0.5);
1063
+ const splitSampleUV = select(useFlipped, flippedUV, noFlipUV);
1064
+ const splitSample = bufferTex.sample(splitSampleUV);
1065
+ const directSampleNoFlip = bufferTex.sample(screenUV.clamp(1e-3, 0.999));
1066
+ const centerSample = bufferTex.sample(vec2$2(0.5, 0.5));
1067
+ const transmissionRayDbg = getVolumeTransmissionRay(n, v, thickness, ior);
1068
+ const refractedRayExitDbg = position.add(transmissionRayDbg);
1069
+ const ndcPosDbg = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(refractedRayExitDbg, 1)));
1070
+ const safeWDbg = max(ndcPosDbg.w, float(1e-3));
1071
+ const rawCoordsDbg = vec2$2(ndcPosDbg.xy.div(safeWDbg)).toVar();
1072
+ rawCoordsDbg.addAssign(1);
1073
+ rawCoordsDbg.divAssign(2);
1074
+ rawCoordsDbg.assign(vec2$2(rawCoordsDbg.x, rawCoordsDbg.y.oneMinus()));
1075
+ const outOfRange = rawCoordsDbg.x.lessThan(0).or(rawCoordsDbg.x.greaterThan(1)).or(rawCoordsDbg.y.lessThan(0)).or(rawCoordsDbg.y.greaterThan(1));
1076
+ const clipPosDbg = cameraProjectionMatrix.mul(cameraViewMatrix.mul(vec4(position, 1)));
1077
+ const safeWDbg2 = max(clipPosDbg.w, float(1e-3));
1078
+ const projUVDbg = vec2$2(clipPosDbg.xy.div(safeWDbg2)).toVar();
1079
+ projUVDbg.addAssign(1);
1080
+ projUVDbg.divAssign(2);
1081
+ projUVDbg.assign(vec2$2(projUVDbg.x, projUVDbg.y.oneMinus()));
1082
+ projUVDbg.assign(projUVDbg.clamp(1e-3, 0.999));
1083
+ const projectedSample = bufferTex.sample(projUVDbg);
1084
+ const wNorm = clipPosDbg.w.div(10).add(0.5).clamp(0, 1);
1085
+ const isBehind = clipPosDbg.w.lessThan(0);
1086
+ const rawNdcX = clipPosDbg.x.div(safeWDbg2);
1087
+ const rawNdcY = clipPosDbg.y.div(safeWDbg2);
1088
+ const result = vec4(0, 0, 0, 1).toVar();
1089
+ result.assign(
1090
+ select(
1091
+ debugModeUniform.equal(1),
1092
+ vec4(avgTransmission, float(1)),
1093
+ select(
1094
+ debugModeUniform.equal(2),
1095
+ vec4(debugUV.x, debugUV.y, float(0), float(1)),
1096
+ select(
1097
+ debugModeUniform.equal(3),
1098
+ vec4(
1099
+ select(isNeg, debugNdotV.abs(), float(0)),
1100
+ select(isNeg, float(0), debugNdotV),
1101
+ float(0),
1102
+ float(1)
1103
+ ),
1104
+ select(
1105
+ debugModeUniform.equal(4),
1106
+ vec4(v.mul(0.5).add(0.5), float(1)),
1107
+ select(
1108
+ debugModeUniform.equal(5),
1109
+ vec4(n.mul(0.5).add(0.5), float(1)),
1110
+ select(
1111
+ debugModeUniform.equal(6),
1112
+ vec4(directSampleFlipped.rgb, float(1)),
1113
+ select(
1114
+ debugModeUniform.equal(7),
1115
+ vec4(rayLen, rayLen, rayLen, float(1)),
1116
+ select(
1117
+ debugModeUniform.equal(8),
1118
+ vec4(screenUV.x, screenUV.y, float(0), float(1)),
1119
+ select(
1120
+ debugModeUniform.equal(9),
1121
+ vec4(screenSize.x.div(2e3), screenSize.y.div(2e3), float(0), float(1)),
1122
+ select(
1123
+ debugModeUniform.equal(10),
1124
+ vec4(manualUV.x, manualUV.y, float(0), float(1)),
1125
+ select(
1126
+ debugModeUniform.equal(11),
1127
+ vec4(splitSample.rgb, float(1)),
1128
+ select(
1129
+ debugModeUniform.equal(12),
1130
+ vec4(directSampleNoFlip.rgb, float(1)),
1131
+ select(
1132
+ debugModeUniform.equal(13),
1133
+ vec4(
1134
+ select(outOfRange, float(1), float(0)),
1135
+ rawCoordsDbg.x.clamp(0, 1),
1136
+ rawCoordsDbg.y.clamp(0, 1),
1137
+ float(1)
1138
+ ),
1139
+ select(
1140
+ debugModeUniform.equal(14),
1141
+ vec4(
1142
+ select(isBehind, float(1), float(0)),
1143
+ wNorm,
1144
+ select(isBehind, float(0), float(1)),
1145
+ float(1)
1146
+ ),
1147
+ select(
1148
+ debugModeUniform.equal(15),
1149
+ vec4(centerSample.rgb, float(1)),
1150
+ select(
1151
+ debugModeUniform.equal(16),
1152
+ vec4(projectedSample.rgb, float(1)),
1153
+ select(
1154
+ debugModeUniform.equal(17),
1155
+ // Raw NDC as color: map [-1,1] to [0,1]
1156
+ vec4(rawNdcX.add(1).div(2), rawNdcY.add(1).div(2), float(0.5), float(1)),
1157
+ select(
1158
+ debugModeUniform.equal(18),
1159
+ // Solid magenta - if black here, shader isn't running
1160
+ vec4(1, 0, 1, 1),
1161
+ select(
1162
+ debugModeUniform.equal(19),
1163
+ // Tinted FBO sample: mix sample with UV color
1164
+ vec4(
1165
+ directSampleFlipped.r.add(screenUV.x.mul(0.3)),
1166
+ directSampleFlipped.g.add(screenUV.y.mul(0.3)),
1167
+ directSampleFlipped.b,
1168
+ float(1)
1169
+ ),
1170
+ vec4(0, 0, 0, 1)
1171
+ // Placeholder for mode 0
1172
+ )
1173
+ )
1174
+ )
1175
+ )
1176
+ )
1177
+ )
1178
+ )
1179
+ )
1180
+ )
1181
+ )
1182
+ )
1183
+ )
1184
+ )
1185
+ )
1186
+ )
1187
+ )
1188
+ )
1189
+ )
1190
+ )
1191
+ );
1192
+ const attenuated = applyVolumeAttenuation(avgTransmission, thickness, attenuationColor, attenuationDistance);
1193
+ const NdotV = n.dot(v).abs().clamp(1e-3, 1);
1194
+ const glassF0 = vec3(0.04);
1195
+ const brdf = EnvironmentBRDF({ dotNV: NdotV, specularColor: glassF0, specularF90: float(1), roughness });
1196
+ const transmissionResult = brdf.oneMinus().mul(attenuated).mul(diffuseColor.rgb);
1197
+ const normalOutput = vec4(transmissionResult, float(1));
1198
+ return select(debugModeUniform.greaterThan(0), result, normalOutput);
1199
+ });
1200
+ context.backdrop = getCustomTransmission();
1201
+ context.backdropAlpha = transmission;
1202
+ diffuseColor.a.mulAssign(mix(1, context.backdrop.a, transmission));
1203
+ }
1204
+ }
1205
+ }
1206
+ function createPlaceholderTexture() {
1207
+ const placeholder = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
1208
+ placeholder.needsUpdate = true;
1209
+ return placeholder;
1210
+ }
1211
+ class MeshTransmissionMaterialImpl extends withUniforms(MeshPhysicalNodeMaterial, {
1212
+ /** Chromatic aberration, default: 0.05 */
1213
+ chromaticAberration: () => uniform(0.05),
1214
+ /** Anisotropic blur, default: 0.1 */
1215
+ anisotropicBlur: () => uniform(0.1),
1216
+ /** Elapsed time, drives the temporal distortion */
1217
+ time: () => uniform(0),
1218
+ /** Distortion, default: 0 */
1219
+ distortion: () => uniform(0),
1220
+ /** Distortion scale, default: 0.5 */
1221
+ distortionScale: () => uniform(0.5),
1222
+ /** Temporal distortion (speed of movement), default: 0 */
1223
+ temporalDistortion: () => uniform(0),
1224
+ /** The scene rendered into a texture (use it to share a texture between materials) */
1225
+ buffer: () => uniformTexture(createPlaceholderTexture()),
1226
+ /** Debug mode for development (0 = normal, 1-19 = various debug outputs), default: 0 */
1227
+ debugMode: () => uniform(0)
1228
+ }) {
683
1229
  /** Type flag for identification */
684
1230
  isMeshTransmissionMaterial = true;
1231
+ _samples;
1232
+ _transmissionSampler;
685
1233
  constructor(samples = 6, transmissionSampler = false) {
686
1234
  super();
687
1235
  this._samples = samples;
688
1236
  this._transmissionSampler = transmissionSampler;
689
- this._chromaticAberration = uniform(0.05);
690
- this._anisotropicBlur = uniform(0.1);
691
- this._time = uniform(0);
692
- this._distortion = uniform(0);
693
- this._distortionScale = uniform(0.5);
694
- this._temporalDistortion = uniform(0);
695
- this._buffer = uniformTexture(new THREE.Texture());
696
- this._transmissionValue = uniform(1);
697
- this._attenuationDistance = uniform(Infinity);
698
- this._attenuationColor = uniform(new THREE.Color("white"));
699
- this.transmission = transmissionSampler ? 1 : 0;
1237
+ this.transmission = 1;
700
1238
  this.roughness = 0;
701
1239
  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
- })();
796
- }
797
- //* Uniform Accessors ==============================
798
- /** Chromatic aberration strength, default: 0.05 */
799
- get chromaticAberration() {
800
- return this._chromaticAberration.value;
801
- }
802
- set chromaticAberration(v) {
803
- this._chromaticAberration.value = v;
804
- }
805
- /** Anisotropic blur amount, default: 0.1 */
806
- get anisotropicBlur() {
807
- return this._anisotropicBlur.value;
808
- }
809
- set anisotropicBlur(v) {
810
- this._anisotropicBlur.value = v;
811
- }
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
- get distortion() {
821
- return this._distortion.value;
822
- }
823
- set distortion(v) {
824
- this._distortion.value = v;
825
- }
826
- /** Distortion noise scale, default: 0.5 */
827
- get distortionScale() {
828
- return this._distortionScale.value;
829
- }
830
- set distortionScale(v) {
831
- this._distortionScale.value = v;
832
- }
833
- /** Temporal distortion speed, default: 0 */
834
- get temporalDistortion() {
835
- return this._temporalDistortion.value;
836
- }
837
- set temporalDistortion(v) {
838
- this._temporalDistortion.value = v;
839
- }
840
- /** Scene buffer texture for refraction sampling */
841
- get buffer() {
842
- return this._buffer.value;
843
- }
844
- set buffer(v) {
845
- this._buffer.value = v ?? new THREE.Texture();
846
- }
847
- /** Internal transmission value (use transmission prop instead) */
848
- get _transmission() {
849
- return this._transmissionValue.value;
850
- }
851
- set _transmission(v) {
852
- this._transmissionValue.value = v;
853
- }
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;
1240
+ this.ior = 1.5;
1241
+ this.transparent = true;
864
1242
  }
865
- set attenuationColor(v) {
866
- this._attenuationColor.value = v;
1243
+ setupLightingModel() {
1244
+ if (this._transmissionSampler || !this.useTransmission) {
1245
+ return super.setupLightingModel();
1246
+ }
1247
+ const {
1248
+ buffer,
1249
+ chromaticAberration,
1250
+ anisotropicBlur,
1251
+ time,
1252
+ distortion,
1253
+ distortionScale,
1254
+ temporalDistortion,
1255
+ debugMode
1256
+ } = this.uniforms;
1257
+ return new TransmissionLightingModel(
1258
+ {
1259
+ clearcoat: this.useClearcoat,
1260
+ sheen: this.useSheen,
1261
+ iridescence: this.useIridescence,
1262
+ anisotropy: this.useAnisotropy,
1263
+ dispersion: this.useDispersion
1264
+ },
1265
+ {
1266
+ bufferTex: buffer,
1267
+ chromaticAberration,
1268
+ anisotropicBlur,
1269
+ timeUniform: time,
1270
+ distortion,
1271
+ distortionScale,
1272
+ temporalDistortion,
1273
+ samples: this._samples,
1274
+ debugMode
1275
+ }
1276
+ );
867
1277
  }
868
1278
  }
869
1279
  const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
@@ -872,8 +1282,8 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
872
1282
  transmissionSampler = false,
873
1283
  backside = false,
874
1284
  side = THREE.FrontSide,
875
- transmission = 1,
876
- thickness = 0,
1285
+ transmission: transmission2 = 1,
1286
+ thickness: thickness2 = 0,
877
1287
  backsideThickness = 0,
878
1288
  backsideEnvMapIntensity = 1,
879
1289
  samples = 10,
@@ -882,48 +1292,111 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
882
1292
  background,
883
1293
  anisotropy,
884
1294
  anisotropicBlur,
1295
+ debugMode = 0,
885
1296
  ...props
886
1297
  }, fref) => {
887
1298
  extend({ MeshTransmissionMaterial: MeshTransmissionMaterialImpl });
888
1299
  const ref = React.useRef(null);
889
- const [discardMaterial] = React.useState(() => DiscardMaterial);
890
- const fboBack = useFBO(backsideResolution || resolution);
891
- const fboMain = useFBO(resolution);
1300
+ const backsideMaterialRef = React.useRef(null);
1301
+ const { size, viewport } = useThree();
1302
+ const screenWidth = size.width * viewport.dpr;
1303
+ const screenHeight = size.height * viewport.dpr;
1304
+ const fboWidth = resolution || screenWidth;
1305
+ const fboHeight = resolution ? Math.round(resolution / screenWidth * screenHeight) : screenHeight;
1306
+ const fboBack = useFBO(
1307
+ backsideResolution || fboWidth,
1308
+ backsideResolution ? Math.round(backsideResolution / screenWidth * screenHeight) : fboHeight
1309
+ );
1310
+ const fboMain = useFBO(fboWidth, fboHeight);
1311
+ React.useEffect(() => {
1312
+ if (backside && ref.current && !backsideMaterialRef.current) {
1313
+ backsideMaterialRef.current = new MeshTransmissionMaterialImpl(samples, transmissionSampler);
1314
+ backsideMaterialRef.current.buffer = fboBack.texture;
1315
+ }
1316
+ return () => {
1317
+ if (backsideMaterialRef.current) {
1318
+ backsideMaterialRef.current.dispose();
1319
+ backsideMaterialRef.current = null;
1320
+ }
1321
+ };
1322
+ }, [backside, samples, transmissionSampler]);
1323
+ React.useEffect(() => {
1324
+ if (backsideMaterialRef.current) {
1325
+ backsideMaterialRef.current.buffer = fboBack.texture;
1326
+ }
1327
+ }, [fboBack.texture]);
892
1328
  let oldBg;
893
1329
  let oldEnvMapIntensity;
894
1330
  let oldTone;
895
1331
  let parent;
896
1332
  useFrame((state) => {
897
1333
  ref.current.time = state.elapsed;
1334
+ const renderer = state.renderer;
1335
+ if (backsideMaterialRef.current) {
1336
+ backsideMaterialRef.current.time = state.elapsed;
1337
+ }
898
1338
  if (ref.current.buffer === fboMain.texture && !transmissionSampler) {
899
1339
  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;
1340
+ if (!parent) return;
1341
+ oldTone = renderer.toneMapping;
1342
+ oldBg = state.scene.background;
1343
+ oldEnvMapIntensity = ref.current.envMapIntensity;
1344
+ renderer.toneMapping = THREE.NoToneMapping;
1345
+ if (background)
1346
+ state.scene.background = background;
1347
+ parent.material = DiscardMaterial;
1348
+ const camera = state.camera;
1349
+ const isOrtho = camera.isOrthographicCamera;
1350
+ let oldLeft, oldRight, oldTop, oldBottom;
1351
+ if (isOrtho) {
1352
+ oldLeft = camera.left;
1353
+ oldRight = camera.right;
1354
+ oldTop = camera.top;
1355
+ oldBottom = camera.bottom;
1356
+ const fboAspect = fboWidth / fboHeight;
1357
+ const screenAspect = screenWidth / screenHeight;
1358
+ if (Math.abs(fboAspect - screenAspect) > 1e-3) {
1359
+ const viewHeight = oldTop - oldBottom;
1360
+ const newHalfWidth = viewHeight * fboAspect / 2;
1361
+ const centerX = (oldLeft + oldRight) / 2;
1362
+ camera.left = centerX - newHalfWidth;
1363
+ camera.right = centerX + newHalfWidth;
1364
+ camera.updateProjectionMatrix();
915
1365
  }
916
- state.gl.setRenderTarget(fboMain);
917
- state.gl.render(state.scene, state.camera);
1366
+ }
1367
+ if (backside && backsideMaterialRef.current) {
1368
+ renderer.setRenderTarget(fboBack);
1369
+ renderer.clear();
1370
+ state.renderer.render(state.scene, state.camera);
1371
+ parent.material = backsideMaterialRef.current;
1372
+ backsideMaterialRef.current.thickness = backsideThickness;
1373
+ backsideMaterialRef.current.side = THREE.BackSide;
1374
+ backsideMaterialRef.current.envMapIntensity = backsideEnvMapIntensity;
1375
+ renderer.setRenderTarget(fboMain);
1376
+ renderer.clear();
1377
+ state.renderer.render(state.scene, state.camera);
918
1378
  parent.material = ref.current;
919
- ref.current.thickness = thickness;
1379
+ ref.current.thickness = thickness2;
920
1380
  ref.current.side = side;
921
- ref.current.buffer = fboMain.texture;
922
1381
  ref.current.envMapIntensity = oldEnvMapIntensity;
923
- state.scene.background = oldBg;
924
- state.gl.setRenderTarget(null);
925
- state.gl.toneMapping = oldTone;
1382
+ } else {
1383
+ renderer.setRenderTarget(fboMain);
1384
+ renderer.clear();
1385
+ state.renderer.render(state.scene, state.camera);
1386
+ parent.material = ref.current;
1387
+ ref.current.thickness = thickness2;
1388
+ ref.current.side = side;
926
1389
  }
1390
+ if (isOrtho && Math.abs(fboWidth / fboHeight - screenWidth / screenHeight) > 1e-3) {
1391
+ camera.left = oldLeft;
1392
+ camera.right = oldRight;
1393
+ camera.top = oldTop;
1394
+ camera.bottom = oldBottom;
1395
+ camera.updateProjectionMatrix();
1396
+ }
1397
+ state.scene.background = oldBg;
1398
+ renderer.setRenderTarget(null);
1399
+ renderer.toneMapping = oldTone;
927
1400
  }
928
1401
  });
929
1402
  React.useImperativeHandle(fref, () => ref.current, []);
@@ -934,33 +1407,46 @@ const MeshTransmissionMaterial = /* @__PURE__ */ React.forwardRef(
934
1407
  ref,
935
1408
  ...props,
936
1409
  buffer: buffer || fboMain.texture,
937
- _transmission: transmission,
938
- anisotropicBlur: anisotropicBlur ?? anisotropy ?? 0.1,
939
- transmission: transmissionSampler ? transmission : 0,
940
- thickness,
941
- side
1410
+ transmission: transmission2,
1411
+ anisotropicBlur: anisotropicBlur ?? anisotropy,
1412
+ thickness: thickness2,
1413
+ side,
1414
+ debugMode
942
1415
  }
943
1416
  );
944
1417
  }
945
1418
  );
946
1419
 
947
- let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends THREE.MeshStandardNodeMaterial {
1420
+ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends withUniforms(THREE.MeshStandardNodeMaterial, {
1421
+ /** Mirror strength, 0 = base material only, 1 = pure reflection */
1422
+ mirror: () => uniform(0),
1423
+ /** Positive values enable the blurred reflection sample */
1424
+ mixBlur: () => uniform(0),
1425
+ /** Strength of the reflection added on top of the base colour */
1426
+ mixStrength: () => uniform(1),
1427
+ /** Lower depth threshold for depth-based blur */
1428
+ minDepthThreshold: () => uniform(0.9),
1429
+ /** Upper depth threshold for depth-based blur */
1430
+ maxDepthThreshold: () => uniform(1),
1431
+ /** Depth scale for depth-based blur, 0 = disabled */
1432
+ depthScale: () => uniform(0),
1433
+ /** Bias added to the depth-to-blur ratio */
1434
+ depthToBlurRatioBias: () => uniform(0.25),
1435
+ /** Distortion strength */
1436
+ distortion: () => uniform(1),
1437
+ /** Contrast applied to the (clamped) reflection colour */
1438
+ mixContrast: () => uniform(1),
1439
+ /** Blur radius in the 0-1 range, scaled to 0.01-0.15 in the shader */
1440
+ blurRadius: () => uniform(0.1)
1441
+ }) {
948
1442
  //* Reflector Node ----------------------------------------
949
1443
  reflection;
950
- //* Uniforms ----------------------------------------
951
- mirrorUniform;
952
- mixBlurUniform;
953
- mixStrengthUniform;
954
- minDepthThresholdUniform;
955
- maxDepthThresholdUniform;
956
- depthScaleUniform;
957
- depthToBlurRatioBiasUniform;
958
- distortionUniform;
959
- mixContrastUniform;
960
- blurRadiusUniform;
961
- // Feature flags
962
- hasBlurUniform;
963
- hasDepthUniform;
1444
+ /**
1445
+ * Add this target to the scene and align it with the reflective surface.
1446
+ */
1447
+ get reflectorTarget() {
1448
+ return this.reflection?.target;
1449
+ }
964
1450
  constructor(options = {}, parameters = {}) {
965
1451
  super();
966
1452
  const { distortionMap, ...validParams } = parameters;
@@ -984,109 +1470,37 @@ let MeshReflectorMaterial$1 = class MeshReflectorMaterial extends THREE.MeshStan
984
1470
  resolutionScale,
985
1471
  generateMipmaps: true
986
1472
  });
987
- this.mirrorUniform = uniform(mirror);
988
- this.mixBlurUniform = uniform(mixBlur);
989
- this.mixStrengthUniform = uniform(mixStrength);
990
- this.minDepthThresholdUniform = uniform(minDepthThreshold);
991
- this.maxDepthThresholdUniform = uniform(maxDepthThreshold);
992
- this.depthScaleUniform = uniform(depthScale);
993
- this.depthToBlurRatioBiasUniform = uniform(depthToBlurRatioBias);
994
- this.distortionUniform = uniform(distortion);
995
- this.mixContrastUniform = uniform(mixContrast);
996
- this.blurRadiusUniform = uniform(0.1);
997
- this.hasBlurUniform = uniform(mixBlur > 0 ? 1 : 0);
998
- this.hasDepthUniform = uniform(depthScale > 0 ? 1 : 0);
1473
+ this.mirror = mirror;
1474
+ this.mixBlur = mixBlur;
1475
+ this.mixStrength = mixStrength;
1476
+ this.minDepthThreshold = minDepthThreshold;
1477
+ this.maxDepthThreshold = maxDepthThreshold;
1478
+ this.depthScale = depthScale;
1479
+ this.depthToBlurRatioBias = depthToBlurRatioBias;
1480
+ this.distortion = distortion;
1481
+ this.mixContrast = mixContrast;
999
1482
  this.setupNodes();
1000
1483
  }
1001
1484
  setupNodes() {
1485
+ const { mirror, mixBlur, mixStrength, mixContrast, blurRadius } = this.uniforms;
1002
1486
  this.colorNode = Fn(() => {
1003
1487
  const reflectionSample = this.reflection.sample(this.reflection.uvNode);
1004
- const blurRadius = mix(float(0.01), float(0.15), this.blurRadiusUniform);
1005
- const reflectionBlurred = hashBlur(this.reflection, blurRadius, {
1488
+ const radius = mix(float(0.01), float(0.15), blurRadius);
1489
+ const reflectionBlurred = hashBlur(this.reflection, radius, {
1006
1490
  repeats: float(20),
1007
1491
  premultipliedAlpha: false
1008
1492
  });
1009
- const reflectionColor = mix(reflectionSample.rgb, reflectionBlurred.rgb, clamp(this.hasBlurUniform, 0, 1));
1493
+ const reflectionColor = select(mixBlur.greaterThan(0), reflectionBlurred.rgb, reflectionSample.rgb);
1010
1494
  const reflectionClamped = clamp(reflectionColor, 0, 1);
1011
1495
  const contrastAdjusted = vec3(
1012
- reflectionClamped.x.sub(0.5).mul(this.mixContrastUniform).add(0.5),
1013
- reflectionClamped.y.sub(0.5).mul(this.mixContrastUniform).add(0.5),
1014
- reflectionClamped.z.sub(0.5).mul(this.mixContrastUniform).add(0.5)
1496
+ reflectionClamped.x.sub(0.5).mul(mixContrast).add(0.5),
1497
+ reflectionClamped.y.sub(0.5).mul(mixContrast).add(0.5),
1498
+ reflectionClamped.z.sub(0.5).mul(mixContrast).add(0.5)
1015
1499
  );
1016
- const mirrorClamped = clamp(this.mirrorUniform, 0, 1);
1017
- return vec4(vec3(1).sub(vec3(mirrorClamped)).add(contrastAdjusted.mul(this.mixStrengthUniform)), 1);
1500
+ const mirrorClamped = clamp(mirror, 0, 1);
1501
+ return vec4(vec3(1).sub(vec3(mirrorClamped)).add(contrastAdjusted.mul(mixStrength)), 1);
1018
1502
  })();
1019
1503
  }
1020
- //* Getters and Setters ----------------------------------------
1021
- get mirror() {
1022
- return this.mirrorUniform.value;
1023
- }
1024
- set mirror(v) {
1025
- this.mirrorUniform.value = v;
1026
- }
1027
- get mixBlur() {
1028
- return this.mixBlurUniform.value;
1029
- }
1030
- set mixBlur(v) {
1031
- this.mixBlurUniform.value = v;
1032
- this.hasBlurUniform.value = v > 0 ? 1 : 0;
1033
- }
1034
- get mixStrength() {
1035
- return this.mixStrengthUniform.value;
1036
- }
1037
- set mixStrength(v) {
1038
- this.mixStrengthUniform.value = v;
1039
- }
1040
- get minDepthThreshold() {
1041
- return this.minDepthThresholdUniform.value;
1042
- }
1043
- set minDepthThreshold(v) {
1044
- this.minDepthThresholdUniform.value = v;
1045
- }
1046
- get maxDepthThreshold() {
1047
- return this.maxDepthThresholdUniform.value;
1048
- }
1049
- set maxDepthThreshold(v) {
1050
- this.maxDepthThresholdUniform.value = v;
1051
- }
1052
- get depthScale() {
1053
- return this.depthScaleUniform.value;
1054
- }
1055
- set depthScale(v) {
1056
- this.depthScaleUniform.value = v;
1057
- this.hasDepthUniform.value = v > 0 ? 1 : 0;
1058
- }
1059
- get depthToBlurRatioBias() {
1060
- return this.depthToBlurRatioBiasUniform.value;
1061
- }
1062
- set depthToBlurRatioBias(v) {
1063
- this.depthToBlurRatioBiasUniform.value = v;
1064
- }
1065
- get distortion() {
1066
- return this.distortionUniform.value;
1067
- }
1068
- set distortion(v) {
1069
- this.distortionUniform.value = v;
1070
- }
1071
- get mixContrast() {
1072
- return this.mixContrastUniform.value;
1073
- }
1074
- set mixContrast(v) {
1075
- this.mixContrastUniform.value = v;
1076
- }
1077
- get blurRadius() {
1078
- return this.blurRadiusUniform.value;
1079
- }
1080
- set blurRadius(v) {
1081
- this.blurRadiusUniform.value = v;
1082
- }
1083
- /**
1084
- * Get the reflector target mesh that needs to be added to the scene.
1085
- * The target should be positioned and rotated to match the reflective surface.
1086
- */
1087
- get reflectorTarget() {
1088
- return this.reflection.target;
1089
- }
1090
1504
  };
1091
1505
 
1092
1506
  const MeshReflectorMaterial = /* @__PURE__ */ React.forwardRef(
@@ -1189,25 +1603,16 @@ const MeshReflectorMaterial = /* @__PURE__ */ React.forwardRef(
1189
1603
  }
1190
1604
  );
1191
1605
 
1192
- const fresnelEffect = /* @__PURE__ */ Fn((inputs) => {
1193
- const [viewDir, normal, power] = inputs;
1606
+ const fresnelEffect = /* @__PURE__ */ Fn(([viewDir, normal, power]) => {
1194
1607
  const NdotV = float(1).add(dot(viewDir, normal));
1195
1608
  return pow(NdotV, power);
1196
1609
  });
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;
1610
+ const approximateRefraction = /* @__PURE__ */ Fn(([rayDir, normal, iorValue, bounceCount]) => {
1206
1611
  const entryDir = refract(rayDir, normal, float(1).div(iorValue)).toVar();
1207
1612
  const entryValid = length(entryDir).greaterThan(0);
1208
1613
  const exitNormal = normal.negate().toVar();
1209
1614
  const currentDir = entryDir.toVar();
1210
- Loop(int(bounceCount), ({ i }) => {
1615
+ Loop({ start: 0, end: int(bounceCount), type: "int" }, ({ i }) => {
1211
1616
  const fi = float(i).add(1);
1212
1617
  const exitDir = refract(currentDir, exitNormal, iorValue);
1213
1618
  const canExit = length(exitDir).greaterThan(0);
@@ -1217,165 +1622,64 @@ const approximateRefraction = /* @__PURE__ */ Fn((inputs) => {
1217
1622
  });
1218
1623
  return select(entryValid, currentDir, reflect(rayDir, normal));
1219
1624
  });
1220
- class MeshRefractionMaterialImpl extends MeshPhysicalNodeMaterial {
1221
- //* Private Uniform Nodes --
1222
- _envMap;
1223
- _bounces;
1224
- _ior;
1225
- _fresnel;
1226
- _aberrationStrength;
1227
- _color;
1228
- _opacityValue;
1229
- _resolution;
1230
- _thickness;
1625
+ class MeshRefractionMaterialImpl extends withUniforms(MeshPhysicalNodeMaterial, {
1626
+ /** Number of internal bounces, default: 3 */
1627
+ bounces: () => uniform(3),
1628
+ /** Fresnel intensity, default: 0 */
1629
+ fresnel: () => uniform(0),
1630
+ /** Chromatic aberration strength, default: 0.01 */
1631
+ aberrationStrength: () => uniform(0.01),
1632
+ /** Tint applied to the refracted environment, default: white */
1633
+ tintColor: () => uniform(new THREE.Color("white"))
1634
+ }) {
1231
1635
  _fastChroma;
1232
1636
  /** Type flag for identification */
1233
1637
  isMeshRefractionMaterial = true;
1234
1638
  constructor(fastChroma = true) {
1235
1639
  super();
1236
1640
  this._fastChroma = fastChroma;
1237
- this._envMap = uniformTexture(new THREE.Texture());
1238
- this._bounces = uniform(3);
1239
- this._ior = uniform(2.4);
1240
- this._fresnel = uniform(0);
1241
- this._aberrationStrength = uniform(0.01);
1242
- this._color = uniform(new THREE.Color("white"));
1243
- this._opacityValue = uniform(1);
1244
- this._resolution = uniform(new THREE.Vector2(1, 1));
1245
- this._thickness = uniform(1);
1641
+ this.ior = 2.4;
1642
+ this.envMap = new THREE.Texture();
1643
+ this.lights = false;
1246
1644
  this.transparent = true;
1247
1645
  this.side = THREE.FrontSide;
1248
1646
  this._buildRefractionShader();
1249
1647
  }
1250
1648
  _buildRefractionShader() {
1251
- const envMapTex = this._envMap;
1252
- const bouncesUniform = this._bounces;
1253
- const iorUniform = this._ior;
1254
- const fresnelUniform = this._fresnel;
1255
- const aberrationUniform = this._aberrationStrength;
1256
- const colorUniform = this._color;
1257
- const opacityUniform = this._opacityValue;
1258
- const thicknessUniform = this._thickness;
1649
+ const { bounces, fresnel, aberrationStrength, tintColor } = this.uniforms;
1259
1650
  const fastChroma = this._fastChroma;
1651
+ const envSample = (uvNode) => context$a(materialReference("envMap", "texture", this), { getUV: () => uvNode });
1260
1652
  this.outputNode = Fn(() => {
1261
1653
  const worldPos = positionWorld;
1262
1654
  const worldNormal = normalize(normalWorld);
1263
1655
  const viewDir = normalize(worldPos.sub(cameraPosition));
1264
- const baseColor = colorUniform.toVar();
1265
- const refractedDirG = approximateRefraction(
1266
- viewDir,
1267
- worldNormal,
1268
- max(iorUniform, 1),
1269
- bouncesUniform,
1270
- thicknessUniform
1271
- );
1272
- const aberration = aberrationUniform;
1656
+ const baseColor = tintColor.toVar();
1657
+ const iorValue = max(materialIOR, 1);
1658
+ const refractedDirG = approximateRefraction(viewDir, worldNormal, iorValue, bounces);
1659
+ const aberration = aberrationStrength;
1273
1660
  const refractedDirR = vec3(0, 0, 0).toVar();
1274
1661
  const refractedDirB = vec3(0, 0, 0).toVar();
1275
- const useFastChroma = fastChroma;
1276
- if (useFastChroma) {
1662
+ if (fastChroma) {
1277
1663
  refractedDirR.assign(normalize(refractedDirG.add(vec3(aberration.mul(0.5)))));
1278
1664
  refractedDirB.assign(normalize(refractedDirG.sub(vec3(aberration.mul(0.5)))));
1279
1665
  } else {
1280
1666
  refractedDirR.assign(
1281
- approximateRefraction(
1282
- viewDir,
1283
- worldNormal,
1284
- max(iorUniform.mul(float(1).sub(aberration)), 1),
1285
- bouncesUniform,
1286
- thicknessUniform
1287
- )
1667
+ approximateRefraction(viewDir, worldNormal, max(materialIOR.mul(float(1).sub(aberration)), 1), bounces)
1288
1668
  );
1289
1669
  refractedDirB.assign(
1290
- approximateRefraction(
1291
- viewDir,
1292
- worldNormal,
1293
- max(iorUniform.mul(float(1).add(aberration)), 1),
1294
- bouncesUniform,
1295
- thicknessUniform
1296
- )
1670
+ approximateRefraction(viewDir, worldNormal, max(materialIOR.mul(float(1).add(aberration)), 1), bounces)
1297
1671
  );
1298
1672
  }
1299
- const uvR = equirectUv(refractedDirR);
1300
- const uvG = equirectUv(refractedDirG);
1301
- const uvB = equirectUv(refractedDirB);
1302
- const envColorR = texture(envMapTex, uvR).r;
1303
- const envColorG = texture(envMapTex, uvG).g;
1304
- const envColorB = texture(envMapTex, uvB).b;
1673
+ const envColorR = envSample(equirectUV(refractedDirR)).r;
1674
+ const envColorG = envSample(equirectUV(refractedDirG)).g;
1675
+ const envColorB = envSample(equirectUV(refractedDirB)).b;
1305
1676
  const envColor = vec3(envColorR, envColorG, envColorB);
1306
1677
  const tintedColor = envColor.mul(baseColor);
1307
- const fresnelValue = fresnelEffect(viewDir, worldNormal, float(10)).mul(fresnelUniform);
1678
+ const fresnelValue = fresnelEffect(viewDir, worldNormal, float(10)).mul(fresnel);
1308
1679
  const finalColor = mix(tintedColor, vec3(1), fresnelValue);
1309
- return vec4(finalColor, opacityUniform);
1680
+ return vec4(finalColor, materialOpacity);
1310
1681
  })();
1311
1682
  }
1312
- //* Uniform Accessors ==============================
1313
- /** Environment map texture */
1314
- get envMap() {
1315
- return this._envMap.value;
1316
- }
1317
- set envMap(v) {
1318
- if (v) {
1319
- this._envMap.value = v;
1320
- }
1321
- }
1322
- /** Number of internal bounces, default: 3 */
1323
- get bounces() {
1324
- return this._bounces.value;
1325
- }
1326
- set bounces(v) {
1327
- this._bounces.value = v;
1328
- }
1329
- /** Index of refraction, default: 2.4 (diamond) */
1330
- get ior() {
1331
- return this._ior.value;
1332
- }
1333
- set ior(v) {
1334
- this._ior.value = v;
1335
- }
1336
- /** Fresnel intensity, default: 0 */
1337
- get fresnel() {
1338
- return this._fresnel.value;
1339
- }
1340
- set fresnel(v) {
1341
- this._fresnel.value = v;
1342
- }
1343
- /** Chromatic aberration strength, default: 0.01 */
1344
- get aberrationStrength() {
1345
- return this._aberrationStrength.value;
1346
- }
1347
- set aberrationStrength(v) {
1348
- this._aberrationStrength.value = v;
1349
- }
1350
- /** Tint color - use tintColor to avoid base class conflict */
1351
- get tintColor() {
1352
- return this._color.value;
1353
- }
1354
- set tintColor(v) {
1355
- if (v instanceof THREE.Color) this._color.value = v;
1356
- else this._color.value = new THREE.Color(v);
1357
- }
1358
- /** Opacity, default: 1 */
1359
- get opacity() {
1360
- return this._opacityValue.value;
1361
- }
1362
- set opacity(v) {
1363
- this._opacityValue.value = v;
1364
- }
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
1683
  }
1380
1684
  const MeshRefractionMaterial = /* @__PURE__ */ React.forwardRef(
1381
1685
  ({
@@ -1391,12 +1695,6 @@ const MeshRefractionMaterial = /* @__PURE__ */ React.forwardRef(
1391
1695
  }, fref) => {
1392
1696
  extend({ MeshRefractionMaterial: MeshRefractionMaterialImpl });
1393
1697
  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
1698
  React.useEffect(() => {
1401
1699
  if (ref.current && envMap) {
1402
1700
  ref.current.envMap = envMap;
@@ -1604,175 +1902,88 @@ function Container$2({
1604
1902
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
1605
1903
  }
1606
1904
 
1607
- class PortalMaterialImpl extends MeshBasicNodeMaterial {
1608
- _blur;
1609
- _map;
1610
- _sdf;
1611
- _blend;
1612
- _size;
1613
- _resolution;
1905
+ class PortalMaterialImpl extends withUniforms(MeshBasicNodeMaterial, {
1906
+ /** Edge fade blur, 0 = no blur */
1907
+ blur: () => uniform(0),
1908
+ /** Signed distance field of the parent geometry, generated on demand */
1909
+ sdf: () => uniformTexture(new THREE.Texture()),
1910
+ /** 0 = world scene, 0.5 = both, 1 = portal scene */
1911
+ blend: () => uniform(0),
1912
+ /** Largest SDF distance, used to normalise the field */
1913
+ size: () => uniform(0),
1914
+ /** Drawing-buffer size in pixels */
1915
+ resolution: () => uniform(new THREE.Vector2())
1916
+ }) {
1614
1917
  constructor() {
1615
1918
  super();
1616
- this._blur = uniform(0);
1617
- this._map = uniformTexture(new THREE.Texture());
1618
- this._sdf = uniformTexture(new THREE.Texture());
1619
- this._blend = uniform(0);
1620
- this._size = uniform(0);
1621
- this._resolution = uniform(new THREE.Vector2());
1919
+ this.map = new THREE.Texture();
1622
1920
  this._buildShader();
1623
1921
  }
1624
1922
  _buildShader() {
1625
- const blurUniform = this._blur;
1626
- const mapTex = this._map;
1627
- const sdfTex = this._sdf;
1628
- const sizeUniform = this._size;
1629
- const resolutionUniform = this._resolution;
1923
+ const { blur, sdf, size, resolution } = this.uniforms;
1924
+ const map = materialReference("map", "texture", this);
1630
1925
  this.colorNode = Fn(() => {
1631
- const screenUv = screenCoordinate.xy.div(resolutionUniform);
1632
- const t = texture(mapTex, screenUv);
1633
- const meshUv = uv();
1634
- const d = texture(sdfTex, meshUv).r.div(sizeUniform);
1635
- const k = blurUniform;
1636
- const edgeFactor = clamp(d.div(k).add(1), 0, 1);
1926
+ const screenUv = screenCoordinate.xy.div(resolution);
1927
+ const t = context$a(map, { getUV: () => screenUv });
1928
+ const d = sdf.sample(uv()).r.div(size);
1929
+ const edgeFactor = clamp(d.div(blur).add(1), 0, 1);
1637
1930
  const alpha = float(1).sub(smoothstep(float(0), float(1), edgeFactor));
1638
- const isBlurZero = k.equal(0);
1639
- const finalAlpha = select(isBlurZero, t.a, t.a.mul(alpha));
1931
+ const finalAlpha = select(blur.equal(0), t.a, t.a.mul(alpha));
1640
1932
  return vec4(t.rgb, finalAlpha);
1641
1933
  })();
1642
1934
  }
1643
- //* Uniform Accessors --
1644
- get blur() {
1645
- return this._blur.value;
1646
- }
1647
- set blur(v) {
1648
- this._blur.value = v;
1649
- }
1650
- get map() {
1651
- return this._map.value;
1652
- }
1653
- set map(v) {
1654
- this._map.value = v ?? new THREE.Texture();
1655
- }
1656
- get sdf() {
1657
- return this._sdf.value;
1658
- }
1659
- set sdf(v) {
1660
- if (v) this._sdf.value = v;
1661
- }
1662
- get blend() {
1663
- return this._blend.value;
1664
- }
1665
- set blend(v) {
1666
- this._blend.value = v;
1667
- }
1668
- get size() {
1669
- return this._size.value;
1670
- }
1671
- set size(v) {
1672
- this._size.value = v;
1673
- }
1674
- get resolution() {
1675
- return this._resolution.value;
1676
- }
1677
- set resolution(v) {
1678
- if (Array.isArray(v)) this._resolution.value.set(v[0], v[1]);
1679
- else this._resolution.value.copy(v);
1680
- }
1681
1935
  }
1682
- class BlendMaterial extends MeshBasicNodeMaterial {
1683
- _textureA;
1684
- _textureB;
1685
- _blend;
1936
+ class BlendMaterial extends withUniforms(MeshBasicNodeMaterial, {
1937
+ textureA: () => uniformTexture(new THREE.Texture()),
1938
+ textureB: () => uniformTexture(new THREE.Texture()),
1939
+ blend: () => uniform(0)
1940
+ }) {
1686
1941
  constructor() {
1687
1942
  super();
1688
- this._textureA = uniformTexture(new THREE.Texture());
1689
- this._textureB = uniformTexture(new THREE.Texture());
1690
- this._blend = uniform(0);
1691
- this._buildShader();
1692
- }
1693
- _buildShader() {
1694
- const texA = this._textureA;
1695
- const texB = this._textureB;
1696
- const blendUniform = this._blend;
1943
+ const { textureA, textureB, blend } = this.uniforms;
1697
1944
  this.colorNode = Fn(() => {
1698
1945
  const uvCoord = uv();
1699
- const ta = texture(texA, uvCoord);
1700
- const tb = texture(texB, uvCoord);
1701
- return mix(tb, ta, blendUniform);
1946
+ return mix(textureB.sample(uvCoord), textureA.sample(uvCoord), blend);
1702
1947
  })();
1703
1948
  }
1704
- get textureA() {
1705
- return this._textureA.value;
1706
- }
1707
- set textureA(v) {
1708
- this._textureA.value = v;
1709
- }
1710
- get textureB() {
1711
- return this._textureB.value;
1712
- }
1713
- set textureB(v) {
1714
- this._textureB.value = v;
1715
- }
1716
- get blend() {
1717
- return this._blend.value;
1718
- }
1719
- set blend(v) {
1720
- this._blend.value = v;
1721
- }
1722
1949
  }
1723
- class UVRenderMaterial extends MeshBasicNodeMaterial {
1724
- _tex;
1725
- _inside;
1950
+ class UVRenderMaterial extends withUniforms(MeshBasicNodeMaterial, {
1951
+ tex: () => uniformTexture(new THREE.Texture()),
1952
+ /** 1 = pack the inside of the mask, 0 = the outside */
1953
+ inside: () => uniform(0)
1954
+ }) {
1726
1955
  constructor(inside = false) {
1727
- super();
1728
- this._tex = uniformTexture(new THREE.Texture());
1729
- this._inside = uniform(inside ? 1 : 0);
1730
- this._buildShader();
1731
- }
1732
- _buildShader() {
1733
- const tex = this._tex;
1734
- const insideUniform = this._inside;
1735
- this.colorNode = Fn(() => {
1956
+ super({ toneMapped: false });
1957
+ this.inside = inside ? 1 : 0;
1958
+ const { tex, inside: insideUniform } = this.uniforms;
1959
+ this.fragmentNode = Fn(() => {
1736
1960
  const uvCoord = uv();
1737
- const mask = texture(tex, uvCoord).x;
1738
- const roundedMask = round(mask);
1961
+ const roundedMask = round(tex.sample(uvCoord).x);
1739
1962
  const isInside = insideUniform.greaterThan(0.5);
1740
1963
  const factor = select(isInside, float(1).sub(roundedMask), roundedMask);
1741
1964
  const packedUv = uvCoord.mul(factor);
1742
1965
  return vec4(packedUv.x, packedUv.y, 0, 1);
1743
1966
  })();
1744
1967
  }
1745
- get tex() {
1746
- return this._tex.value;
1747
- }
1748
- set tex(v) {
1749
- this._tex.value = v;
1750
- }
1751
1968
  }
1752
- class JumpFloodMaterial extends MeshBasicNodeMaterial {
1753
- _tex;
1754
- _offset;
1755
- _texelSize;
1969
+ class JumpFloodMaterial extends withUniforms(MeshBasicNodeMaterial, {
1970
+ tex: () => uniformTexture(new THREE.Texture()),
1971
+ offset: () => uniform(0),
1972
+ texelSize: () => uniform(new THREE.Vector2(1, 1))
1973
+ }) {
1756
1974
  constructor(clientWidth, clientHeight) {
1757
- super();
1758
- this._tex = uniformTexture(new THREE.Texture());
1759
- this._offset = uniform(0);
1760
- this._texelSize = uniform(new THREE.Vector2(1 / clientWidth, 1 / clientHeight));
1761
- this._buildShader();
1762
- }
1763
- _buildShader() {
1764
- const tex = this._tex;
1765
- const offsetUniform = this._offset;
1766
- const texelSizeUniform = this._texelSize;
1767
- this.colorNode = Fn(() => {
1975
+ super({ toneMapped: false });
1976
+ this.uniforms.texelSize.value.set(1 / clientWidth, 1 / clientHeight);
1977
+ const { tex, offset, texelSize } = this.uniforms;
1978
+ this.fragmentNode = Fn(() => {
1768
1979
  const uvCoord = uv();
1769
1980
  const closestDist = float(99999999e-1).toVar();
1770
1981
  const closestPos = vec2$2(0, 0).toVar();
1771
1982
  Loop({ start: int(-1), end: int(2), type: "int", condition: "<" }, ({ i: x }) => {
1772
1983
  Loop({ start: int(-1), end: int(2), type: "int", condition: "<" }, ({ i: y }) => {
1773
- const sampleOffset = vec2$2(float(x), float(y)).mul(texelSizeUniform).mul(offsetUniform);
1984
+ const sampleOffset = vec2$2(float(x), float(y)).mul(texelSize).mul(offset);
1774
1985
  const sampleUv = uvCoord.add(sampleOffset);
1775
- const pos = texture(tex, sampleUv).xy;
1986
+ const pos = tex.sample(sampleUv).xy;
1776
1987
  const dist = distance(pos, uvCoord);
1777
1988
  const isValid = pos.x.notEqual(0).and(pos.y.notEqual(0));
1778
1989
  const isCloser = dist.lessThan(closestDist);
@@ -1784,88 +1995,40 @@ class JumpFloodMaterial extends MeshBasicNodeMaterial {
1784
1995
  return vec4(closestPos, 0, 1);
1785
1996
  })();
1786
1997
  }
1787
- get tex() {
1788
- return this._tex.value;
1789
- }
1790
- set tex(v) {
1791
- this._tex.value = v;
1792
- }
1793
- get offset() {
1794
- return this._offset.value;
1795
- }
1796
- set offset(v) {
1797
- this._offset.value = v;
1798
- }
1799
1998
  }
1800
- class DistanceFieldMaterial extends MeshBasicNodeMaterial {
1801
- _tex;
1802
- _size;
1999
+ class DistanceFieldMaterial extends withUniforms(MeshBasicNodeMaterial, {
2000
+ tex: () => uniformTexture(new THREE.Texture()),
2001
+ size: () => uniform(new THREE.Vector2(1, 1))
2002
+ }) {
1803
2003
  constructor(clientWidth, clientHeight) {
1804
- super();
1805
- this._tex = uniformTexture(new THREE.Texture());
1806
- this._size = uniform(new THREE.Vector2(clientWidth, clientHeight));
1807
- this._buildShader();
1808
- }
1809
- _buildShader() {
1810
- const tex = this._tex;
1811
- const sizeUniform = this._size;
1812
- this.colorNode = Fn(() => {
2004
+ super({ toneMapped: false });
2005
+ this.uniforms.size.value.set(clientWidth, clientHeight);
2006
+ const { tex, size } = this.uniforms;
2007
+ this.fragmentNode = Fn(() => {
1813
2008
  const uvCoord = uv();
1814
- const pos = texture(tex, uvCoord).xy;
1815
- const dist = distance(sizeUniform.mul(pos), sizeUniform.mul(uvCoord));
2009
+ const pos = tex.sample(uvCoord).xy;
2010
+ const dist = distance(size.mul(pos), size.mul(uvCoord));
1816
2011
  return vec4(dist, 0, 0, 1);
1817
2012
  })();
1818
2013
  }
1819
- get tex() {
1820
- return this._tex.value;
1821
- }
1822
- set tex(v) {
1823
- this._tex.value = v;
1824
- }
1825
2014
  }
1826
- class CompositeMaterial extends MeshBasicNodeMaterial {
1827
- _inside;
1828
- _outside;
1829
- _mask;
2015
+ class CompositeMaterial extends withUniforms(MeshBasicNodeMaterial, {
2016
+ inside: () => uniformTexture(new THREE.Texture()),
2017
+ outside: () => uniformTexture(new THREE.Texture()),
2018
+ mask: () => uniformTexture(new THREE.Texture())
2019
+ }) {
1830
2020
  constructor() {
1831
- super();
1832
- this._inside = uniformTexture(new THREE.Texture());
1833
- this._outside = uniformTexture(new THREE.Texture());
1834
- this._mask = uniformTexture(new THREE.Texture());
1835
- this._buildShader();
1836
- }
1837
- _buildShader() {
1838
- const insideTex = this._inside;
1839
- const outsideTex = this._outside;
1840
- const maskTex = this._mask;
1841
- this.colorNode = Fn(() => {
2021
+ super({ toneMapped: false });
2022
+ const { inside, outside, mask } = this.uniforms;
2023
+ this.fragmentNode = Fn(() => {
1842
2024
  const uvCoord = uv();
1843
- const i = texture(insideTex, uvCoord).x;
1844
- const o = texture(outsideTex, uvCoord).x;
1845
- const maskVal = texture(maskTex, uvCoord).x;
1846
- const isOutside = maskVal.equal(0);
1847
- const result = select(isOutside, o, i.negate());
2025
+ const i = inside.sample(uvCoord).x;
2026
+ const o = outside.sample(uvCoord).x;
2027
+ const maskVal = mask.sample(uvCoord).x;
2028
+ const result = select(maskVal.equal(0), o, i.negate());
1848
2029
  return vec4(result, 0, 0, 1);
1849
2030
  })();
1850
2031
  }
1851
- get inside() {
1852
- return this._inside.value;
1853
- }
1854
- set inside(v) {
1855
- this._inside.value = v;
1856
- }
1857
- get outside() {
1858
- return this._outside.value;
1859
- }
1860
- set outside(v) {
1861
- this._outside.value = v;
1862
- }
1863
- get mask() {
1864
- return this._mask.value;
1865
- }
1866
- set mask(v) {
1867
- this._mask.value = v;
1868
- }
1869
2032
  }
1870
2033
  const makeSDFGenerator = (clientWidth, clientHeight, renderer) => {
1871
2034
  const finalTarget = new RenderTarget(clientWidth, clientHeight, {
@@ -1913,7 +2076,7 @@ const makeSDFGenerator = (clientWidth, clientHeight, renderer) => {
1913
2076
  const jumpFloodQuad = new QuadMesh(jumpFloodMat);
1914
2077
  const distanceFieldQuad = new QuadMesh(distanceFieldMat);
1915
2078
  const compositeQuad = new QuadMesh(compositeMat);
1916
- return (image) => {
2079
+ const generate = (image) => {
1917
2080
  image.minFilter = THREE.NearestFilter;
1918
2081
  image.magFilter = THREE.NearestFilter;
1919
2082
  uvRenderMat.tex = image;
@@ -1955,9 +2118,28 @@ const makeSDFGenerator = (clientWidth, clientHeight, renderer) => {
1955
2118
  compositeMat.outside = outsideRenderTargetFinal.texture;
1956
2119
  compositeMat.mask = image;
1957
2120
  compositeQuad.render(renderer);
1958
- renderer.setRenderTarget(null);
1959
2121
  return finalTarget;
1960
2122
  };
2123
+ return {
2124
+ generate,
2125
+ dispose() {
2126
+ for (const resource of [
2127
+ finalTarget,
2128
+ outsideRenderTarget,
2129
+ insideRenderTarget,
2130
+ outsideRenderTarget2,
2131
+ insideRenderTarget2,
2132
+ outsideRenderTargetFinal,
2133
+ insideRenderTargetFinal,
2134
+ uvRenderMat,
2135
+ uvRenderInsideMat,
2136
+ jumpFloodMat,
2137
+ distanceFieldMat,
2138
+ compositeMat
2139
+ ])
2140
+ resource.dispose();
2141
+ }
2142
+ };
1961
2143
  };
1962
2144
  const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
1963
2145
  ({
@@ -1972,7 +2154,7 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
1972
2154
  }, fref) => {
1973
2155
  extend({ PortalMaterialImpl });
1974
2156
  const ref = React.useRef(null);
1975
- const { scene, gl, size, viewport, setEvents } = useThree();
2157
+ const { scene, renderer, size, viewport, setEvents, invalidate } = useThree();
1976
2158
  const maskRenderTarget = useFBO(resolution, resolution);
1977
2159
  const [priority, setPriority] = React.useState(0);
1978
2160
  useFrame(() => {
@@ -1983,43 +2165,69 @@ const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
1983
2165
  if (events !== void 0) setEvents({ enabled: !events });
1984
2166
  }, [events]);
1985
2167
  const [visible, setVisible] = React.useState(true);
2168
+ const needsSdf = blur !== 0;
1986
2169
  const parent = useIntersect(setVisible);
1987
2170
  React.useLayoutEffect(() => {
1988
2171
  parent.current = ref.current?.__r3f?.parent?.object;
1989
2172
  }, []);
1990
2173
  React.useLayoutEffect(() => {
1991
- if (!parent.current) return;
1992
- if (blur && ref.current.sdf === null) {
1993
- const tempMesh = new THREE.Mesh(parent.current.geometry, new THREE.MeshBasicMaterial());
1994
- const boundingBox = new THREE.Box3().setFromBufferAttribute(
1995
- tempMesh.geometry.attributes.position
1996
- );
1997
- const orthoCam = new THREE.OrthographicCamera(
1998
- boundingBox.min.x * (1 + 2 / resolution),
1999
- boundingBox.max.x * (1 + 2 / resolution),
2000
- boundingBox.max.y * (1 + 2 / resolution),
2001
- boundingBox.min.y * (1 + 2 / resolution),
2002
- 0.1,
2003
- 1e3
2004
- );
2005
- orthoCam.position.set(0, 0, 1);
2006
- orthoCam.lookAt(0, 0, 0);
2007
- gl.setRenderTarget(maskRenderTarget);
2008
- gl.render(tempMesh, orthoCam);
2009
- const sg = makeSDFGenerator(resolution, resolution, gl);
2010
- const sdf = sg(maskRenderTarget.texture);
2011
- const readSdf = new Float32Array(resolution * resolution);
2012
- gl.readRenderTargetPixels(sdf, 0, 0, resolution, resolution, readSdf);
2174
+ if (!parent.current || !needsSdf) return;
2175
+ const material = ref.current;
2176
+ const previousSdf = material.sdf;
2177
+ const previousSize = material.size;
2178
+ const gpuRenderer = renderer;
2179
+ const tempMesh = new THREE.Mesh(parent.current.geometry, new THREE.MeshBasicMaterial());
2180
+ const boundingBox = new THREE.Box3().setFromBufferAttribute(
2181
+ tempMesh.geometry.attributes.position
2182
+ );
2183
+ const orthoCam = new THREE.OrthographicCamera(
2184
+ boundingBox.min.x * (1 + 2 / resolution),
2185
+ boundingBox.max.x * (1 + 2 / resolution),
2186
+ boundingBox.max.y * (1 + 2 / resolution),
2187
+ boundingBox.min.y * (1 + 2 / resolution),
2188
+ 0.1,
2189
+ 1e3
2190
+ );
2191
+ orthoCam.position.set(0, 0, 1);
2192
+ orthoCam.lookAt(0, 0, 0);
2193
+ const sg = makeSDFGenerator(resolution, resolution, gpuRenderer);
2194
+ const previousTarget = gpuRenderer.getRenderTarget();
2195
+ const previousClearColor = gpuRenderer.getClearColor(new THREE.Color());
2196
+ const previousClearAlpha = gpuRenderer.getClearAlpha();
2197
+ let sdf;
2198
+ try {
2199
+ gpuRenderer.setClearColor(0, 0);
2200
+ gpuRenderer.setRenderTarget(maskRenderTarget);
2201
+ gpuRenderer.render(tempMesh, orthoCam);
2202
+ sdf = sg.generate(maskRenderTarget.texture);
2203
+ } catch (error) {
2204
+ sg.dispose();
2205
+ throw error;
2206
+ } finally {
2207
+ gpuRenderer.setRenderTarget(previousTarget);
2208
+ gpuRenderer.setClearColor(previousClearColor, previousClearAlpha);
2209
+ tempMesh.material.dispose();
2210
+ }
2211
+ let cancelled = false;
2212
+ gpuRenderer.readRenderTargetPixelsAsync(sdf, 0, 0, resolution, resolution).then((readSdf) => {
2213
+ if (cancelled) return;
2013
2214
  let min = Infinity;
2014
2215
  for (let i = 0; i < readSdf.length; i++) {
2015
2216
  if (readSdf[i] < min) min = readSdf[i];
2016
2217
  }
2017
- min = -min;
2018
- ref.current.size = min;
2019
- ref.current.sdf = sdf.texture;
2020
- gl.setRenderTarget(null);
2021
- }
2022
- }, [resolution, blur]);
2218
+ material.size = -min;
2219
+ material.sdf = sdf.texture;
2220
+ invalidate();
2221
+ }).catch((error) => {
2222
+ if (!cancelled) console.error("MeshPortalMaterial: SDF readback failed.", error);
2223
+ });
2224
+ return () => {
2225
+ cancelled = true;
2226
+ material.sdf = previousSdf;
2227
+ material.size = previousSize;
2228
+ sg.dispose();
2229
+ };
2230
+ }, [resolution, needsSdf, renderer, maskRenderTarget, invalidate]);
2023
2231
  React.useImperativeHandle(fref, () => ref.current);
2024
2232
  const compute = React.useCallback(
2025
2233
  (event, state, _previous) => {
@@ -2124,44 +2332,27 @@ function ManagePortalScene({
2124
2332
  return /* @__PURE__ */ React.createElement(React.Fragment, null);
2125
2333
  }
2126
2334
 
2127
- class DistortMaterialImplWebGPU extends THREE.MeshPhysicalNodeMaterial {
2128
- timeUniform;
2129
- distortUniform;
2130
- radiusUniform;
2335
+ class DistortMaterialImplWebGPU extends withUniforms(THREE.MeshPhysicalNodeMaterial, {
2336
+ /** Animation time, advanced by the component each frame */
2337
+ time: () => uniform(0),
2338
+ /** Distortion intensity */
2339
+ distort: () => uniform(0.4),
2340
+ /** Base scale the distortion is added to */
2341
+ radius: () => uniform(1)
2342
+ }) {
2131
2343
  constructor(parameters = {}) {
2132
2344
  super(parameters);
2133
- this.setValues(parameters);
2134
- this.timeUniform = uniform(0);
2135
- this.distortUniform = uniform(0.4);
2136
- this.radiusUniform = uniform(1);
2345
+ const { time, distort, radius } = this.uniforms;
2137
2346
  this.positionNode = Fn(() => {
2138
2347
  const pos = positionLocal.toVar();
2139
- const updateTime = this.timeUniform.div(50);
2348
+ const updateTime = time.div(50);
2140
2349
  const noiseInput = pos.div(2).add(updateTime.mul(5));
2141
2350
  const noiseVec = mx_noise_vec3(noiseInput);
2142
2351
  const noise = noiseVec.x;
2143
- const distortFactor = noise.mul(pow(this.distortUniform, float(2))).add(this.radiusUniform);
2352
+ const distortFactor = noise.mul(pow(distort, float(2))).add(radius);
2144
2353
  return pos.mul(distortFactor);
2145
2354
  })();
2146
2355
  }
2147
- get time() {
2148
- return this.timeUniform.value;
2149
- }
2150
- set time(v) {
2151
- this.timeUniform.value = v;
2152
- }
2153
- get distort() {
2154
- return this.distortUniform.value;
2155
- }
2156
- set distort(v) {
2157
- this.distortUniform.value = v;
2158
- }
2159
- get radius() {
2160
- return this.radiusUniform.value;
2161
- }
2162
- set radius(v) {
2163
- this.radiusUniform.value = v;
2164
- }
2165
2356
  }
2166
2357
  const MeshDistortMaterial = /* @__PURE__ */ React.forwardRef(({ speed = 1, ...props }, ref) => {
2167
2358
  const [material] = React.useState(() => new DistortMaterialImplWebGPU());
@@ -2169,17 +2360,18 @@ const MeshDistortMaterial = /* @__PURE__ */ React.forwardRef(({ speed = 1, ...pr
2169
2360
  return /* @__PURE__ */ React.createElement("primitive", { object: material, ref, attach: "material", ...props });
2170
2361
  });
2171
2362
 
2172
- class WobbleMaterialImpl extends THREE.MeshStandardNodeMaterial {
2173
- timeUniform;
2174
- factorUniform;
2363
+ class WobbleMaterialImpl extends withUniforms(THREE.MeshStandardNodeMaterial, {
2364
+ /** Animation time, advanced by the component each frame */
2365
+ time: () => uniform(0),
2366
+ /** Wobble strength */
2367
+ factor: () => uniform(1)
2368
+ }) {
2175
2369
  constructor(parameters = {}) {
2176
2370
  super(parameters);
2177
- this.setValues(parameters);
2178
- this.timeUniform = uniform(0);
2179
- this.factorUniform = uniform(1);
2371
+ const { time, factor } = this.uniforms;
2180
2372
  this.positionNode = Fn(() => {
2181
2373
  const pos = positionLocal.toVar();
2182
- const theta = sin(this.timeUniform.add(pos.y)).div(2).mul(this.factorUniform);
2374
+ const theta = sin(time.add(pos.y)).div(2).mul(factor);
2183
2375
  const c = cos(theta);
2184
2376
  const s = sin(theta);
2185
2377
  const rotMatrix = mat3(c, float(0), s, float(0), float(1), float(0), s.negate(), float(0), c);
@@ -2188,25 +2380,13 @@ class WobbleMaterialImpl extends THREE.MeshStandardNodeMaterial {
2188
2380
  this.normalNode = Fn(() => {
2189
2381
  const pos = positionLocal.toVar();
2190
2382
  const norm = normalLocal.toVar();
2191
- const theta = sin(this.timeUniform.add(pos.y)).div(2).mul(this.factorUniform);
2383
+ const theta = sin(time.add(pos.y)).div(2).mul(factor);
2192
2384
  const c = cos(theta);
2193
2385
  const s = sin(theta);
2194
2386
  const rotMatrix = mat3(c, float(0), s, float(0), float(1), float(0), s.negate(), float(0), c);
2195
2387
  return norm.mul(rotMatrix);
2196
2388
  })();
2197
2389
  }
2198
- get time() {
2199
- return this.timeUniform.value;
2200
- }
2201
- set time(v) {
2202
- this.timeUniform.value = v;
2203
- }
2204
- get factor() {
2205
- return this.factorUniform.value;
2206
- }
2207
- set factor(v) {
2208
- this.factorUniform.value = v;
2209
- }
2210
2390
  }
2211
2391
  const MeshWobbleMaterial = /* @__PURE__ */ React.forwardRef(({ speed = 1, ...props }, ref) => {
2212
2392
  const [material] = React.useState(() => new WobbleMaterialImpl());
@@ -2225,84 +2405,36 @@ function FakeCloudMaterial(BaseMaterial) {
2225
2405
  return mat;
2226
2406
  }
2227
2407
 
2228
- class OutlinesMaterial extends MeshBasicNodeMaterial {
2229
- //* Private Uniform Nodes --
2230
- _screenspace;
2231
- // Using number as bool (0/1)
2232
- _color;
2233
- _opacityValue;
2234
- _thickness;
2235
- _size;
2408
+ class OutlinesMaterial extends withUniforms(MeshBasicNodeMaterial, {
2409
+ /** Whether thickness is constant in screen pixels */
2410
+ screenspace: () => uniform(false),
2411
+ /** Outline thickness */
2412
+ thickness: () => uniform(0.05),
2413
+ /** Drawing-buffer size, for screenspace thickness */
2414
+ size: () => uniform(new THREE.Vector2(1, 1))
2415
+ }) {
2236
2416
  /** Type flag for identification */
2237
2417
  isOutlinesMaterial = true;
2238
2418
  constructor() {
2239
2419
  super();
2240
- this._screenspace = uniform(0);
2241
- this._color = uniform(new THREE.Color("black"));
2242
- this._opacityValue = uniform(1);
2243
- this._thickness = uniform(0.05);
2244
- this._size = uniform(new THREE.Vector2(1, 1));
2420
+ this.color.set("black");
2245
2421
  this.side = THREE.BackSide;
2246
2422
  this.transparent = false;
2247
2423
  this._buildOutlineShader();
2248
2424
  }
2249
2425
  _buildOutlineShader() {
2250
- const screenspaceUniform = this._screenspace;
2251
- const colorUniform = this._color;
2252
- const opacityUniform = this._opacityValue;
2253
- const thicknessUniform = this._thickness;
2254
- const sizeUniform = this._size;
2426
+ const { screenspace, thickness, size } = this.uniforms;
2255
2427
  this.positionNode = Fn(() => {
2256
2428
  const position = positionLocal.toVar();
2257
2429
  const normal = normalLocal.toVar();
2258
- const isScreenspace = screenspaceUniform.greaterThan(0.5);
2259
- const screenspacePos = position.add(normal.mul(thicknessUniform));
2430
+ const screenspacePos = position.add(normal.mul(thickness));
2260
2431
  const clipPos = cameraProjectionMatrix.mul(modelViewMatrix.mul(vec4(position, 1)));
2261
2432
  const clipNormal = cameraProjectionMatrix.mul(modelViewMatrix.mul(vec4(normal, 0)));
2262
- normalize(clipNormal.xy).mul(thicknessUniform).div(sizeUniform).mul(clipPos.w).mul(2);
2263
- const worldOffset = normal.mul(thicknessUniform);
2264
- return select(isScreenspace, screenspacePos, position.add(worldOffset));
2265
- })();
2266
- this.outputNode = Fn(() => {
2267
- return vec4(colorUniform, opacityUniform);
2433
+ normalize(clipNormal.xy).mul(thickness).div(size).mul(clipPos.w).mul(2);
2434
+ const worldOffset = normal.mul(thickness);
2435
+ return select(screenspace, screenspacePos, position.add(worldOffset));
2268
2436
  })();
2269
- }
2270
- //* Uniform Accessors ==============================
2271
- /** Whether to use screenspace thickness */
2272
- get screenspace() {
2273
- return this._screenspace.value > 0.5;
2274
- }
2275
- set screenspace(v) {
2276
- this._screenspace.value = v ? 1 : 0;
2277
- }
2278
- /** Outline color */
2279
- get color() {
2280
- return this._color.value;
2281
- }
2282
- set color(v) {
2283
- if (v instanceof THREE.Color) this._color.value = v;
2284
- else this._color.value = new THREE.Color(v);
2285
- }
2286
- /** Outline opacity */
2287
- get opacity() {
2288
- return this._opacityValue.value;
2289
- }
2290
- set opacity(v) {
2291
- this._opacityValue.value = v;
2292
- }
2293
- /** Outline thickness */
2294
- get thickness() {
2295
- return this._thickness.value;
2296
- }
2297
- set thickness(v) {
2298
- this._thickness.value = v;
2299
- }
2300
- /** Screen size for screenspace calculations */
2301
- get size() {
2302
- return this._size.value;
2303
- }
2304
- set size(v) {
2305
- this._size.value = v;
2437
+ this.outputNode = Fn(() => vec4(materialColor, materialOpacity))();
2306
2438
  }
2307
2439
  }
2308
2440
  function Outlines({
@@ -2431,11 +2563,11 @@ const RenderCubeTexture = /* @__PURE__ */ React.forwardRef(
2431
2563
  depthBuffer,
2432
2564
  generateMipmaps
2433
2565
  };
2434
- const fboA2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
2566
+ const fboA2 = new CubeRenderTarget(fboSize, settings);
2435
2567
  fboA2.texture.isRenderTargetTexture = !flip;
2436
2568
  fboA2.texture.flipY = true;
2437
2569
  fboA2.texture.type = THREE.HalfFloatType;
2438
- const fboB2 = new THREE.WebGLCubeRenderTarget(fboSize, settings);
2570
+ const fboB2 = new CubeRenderTarget(fboSize, settings);
2439
2571
  fboB2.texture.isRenderTargetTexture = !flip;
2440
2572
  fboB2.texture.flipY = true;
2441
2573
  fboB2.texture.type = THREE.HalfFloatType;
@@ -2885,7 +3017,7 @@ function useCubeCamera({ resolution = 256, near = 0.1, far = 1e3, envMap, fog }
2885
3017
  const gl = useThree(({ gl: gl2 }) => gl2);
2886
3018
  const scene = useThree(({ scene: scene2 }) => scene2);
2887
3019
  const fbo = useMemo(() => {
2888
- const fbo2 = new WebGLCubeRenderTarget(resolution);
3020
+ const fbo2 = new CubeRenderTarget(resolution);
2889
3021
  fbo2.texture.type = HalfFloatType;
2890
3022
  return fbo2;
2891
3023
  }, [resolution]);
@@ -3268,9 +3400,8 @@ function PresentationControls({
3268
3400
  azimuth = [-Infinity, Infinity],
3269
3401
  damping = 0.25
3270
3402
  }) {
3271
- const events = useThree((state) => state.events);
3272
- const gl = useThree((state) => state.gl);
3273
- const explDomElement = domElement || events.connected || gl.domElement;
3403
+ const { events, renderer, invalidate } = useThree();
3404
+ const explDomElement = domElement || events.connected || renderer.domElement;
3274
3405
  const { size } = useThree();
3275
3406
  const rPolar = React.useMemo(
3276
3407
  () => [rotation[0] + polar[0], rotation[0] + polar[1]],
@@ -3287,18 +3418,20 @@ function PresentationControls({
3287
3418
  React.useEffect(() => {
3288
3419
  if (global && cursor && enabled) {
3289
3420
  explDomElement.style.cursor = "grab";
3290
- gl.domElement.style.cursor = "";
3421
+ renderer.domElement.style.cursor = "";
3291
3422
  return () => {
3292
3423
  explDomElement.style.cursor = "default";
3293
- gl.domElement.style.cursor = "default";
3424
+ renderer.domElement.style.cursor = "default";
3294
3425
  };
3295
3426
  }
3296
3427
  }, [global, cursor, explDomElement, enabled]);
3297
3428
  const [animation] = React.useState({ scale: 1, rotation: rInitial, damping });
3298
3429
  const ref = React.useRef(null);
3299
3430
  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);
3431
+ let changed = false;
3432
+ changed ||= easing.damp3(ref.current.scale, animation.scale, animation.damping, delta);
3433
+ changed ||= easing.dampE(ref.current.rotation, animation.rotation, animation.damping, delta);
3434
+ if (changed) state.invalidate();
3302
3435
  });
3303
3436
  const bind = useGesture(
3304
3437
  {
@@ -3306,6 +3439,7 @@ function PresentationControls({
3306
3439
  if (cursor && !global && enabled) explDomElement.style.cursor = last ? "auto" : "grab";
3307
3440
  },
3308
3441
  onDrag: ({ down, delta: [x, y], memo: [oldY, oldX] = animation.rotation || rInitial }) => {
3442
+ invalidate();
3309
3443
  if (!enabled) return [y, x];
3310
3444
  if (cursor) explDomElement.style.cursor = down ? "grabbing" : "grab";
3311
3445
  x = MathUtils.clamp(oldX + x / size.width * Math.PI * speed, ...rAzimuth);
@@ -3314,9 +3448,12 @@ function PresentationControls({
3314
3448
  animation.rotation = snap && !down ? rInitial : [y, x, 0];
3315
3449
  animation.damping = snap && !down && typeof snap !== "boolean" ? snap : damping;
3316
3450
  return [y, x];
3451
+ },
3452
+ onDragEnd: () => {
3453
+ invalidate();
3317
3454
  }
3318
3455
  },
3319
- { target: global ? explDomElement : void 0 }
3456
+ { target: global ? explDomElement : void 0, enabled }
3320
3457
  );
3321
3458
  return /* @__PURE__ */ React.createElement("group", { ref, ...bind?.() }, children);
3322
3459
  }
@@ -3325,6 +3462,10 @@ function roundToPixelRatio(value) {
3325
3462
  const ratio = window.devicePixelRatio || 1;
3326
3463
  return Math.round(value * ratio) / ratio;
3327
3464
  }
3465
+ function getMaxAnisotropy(renderer) {
3466
+ const r = renderer;
3467
+ return r?.getMaxAnisotropy?.() ?? r?.capabilities?.getMaxAnisotropy?.() ?? 1;
3468
+ }
3328
3469
 
3329
3470
  const context$7 = /* @__PURE__ */ React.createContext(null);
3330
3471
  const rootCache = /* @__PURE__ */ new WeakMap();
@@ -3350,7 +3491,7 @@ const ScrollHtml = React.forwardRef(
3350
3491
  const group = React.useRef(null);
3351
3492
  React.useImperativeHandle(ref, () => group.current, []);
3352
3493
  const { width, height } = useThree((state2) => state2.size);
3353
- const fiberState = React.useContext(context$a);
3494
+ const fiberState = React.useContext(context$b);
3354
3495
  const root = React.useMemo(() => {
3355
3496
  let root2 = rootCache.get(state.fixed);
3356
3497
  if (!root2) {
@@ -3378,7 +3519,7 @@ const ScrollHtml = React.forwardRef(
3378
3519
  style: { ...style, position: "absolute", top: 0, left: 0, willChange: "transform" },
3379
3520
  ...props
3380
3521
  },
3381
- /* @__PURE__ */ React.createElement(context$7.Provider, { value: state }, /* @__PURE__ */ React.createElement(context$a.Provider, { value: fiberState }, children))
3522
+ /* @__PURE__ */ React.createElement(context$7.Provider, { value: state }, /* @__PURE__ */ React.createElement(context$b.Provider, { value: fiberState }, children))
3382
3523
  )
3383
3524
  );
3384
3525
  return null;
@@ -3690,10 +3831,10 @@ const CloudInstance = /* @__PURE__ */ React.forwardRef(
3690
3831
  return /* @__PURE__ */ React.createElement("group", { ref, ...props });
3691
3832
  }
3692
3833
  );
3693
- const Cloud = /* @__PURE__ */ React.forwardRef((props, fref) => {
3834
+ const Cloud = /* @__PURE__ */ React.forwardRef(({ materialFactory, ...props }, fref) => {
3694
3835
  const parent = React.useContext(context$6);
3695
3836
  if (parent) return /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props });
3696
- return /* @__PURE__ */ React.createElement(Clouds, null, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
3837
+ return /* @__PURE__ */ React.createElement(Clouds, { materialFactory }, /* @__PURE__ */ React.createElement(CloudInstance, { ref: fref, ...props }));
3697
3838
  });
3698
3839
 
3699
3840
  const CatmullRomLine = /* @__PURE__ */ React.forwardRef(function CatmullRomLine2({
@@ -4215,7 +4356,7 @@ const Text3D = /* @__PURE__ */ React.forwardRef(
4215
4356
  return {
4216
4357
  font,
4217
4358
  size,
4218
- height,
4359
+ depth: height,
4219
4360
  bevelThickness,
4220
4361
  bevelSize,
4221
4362
  bevelEnabled,
@@ -4398,7 +4539,7 @@ const FaceMaterial = ({
4398
4539
  "meshBasicMaterial",
4399
4540
  {
4400
4541
  map: texture,
4401
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
4542
+ "map-anisotropy": getMaxAnisotropy(renderer),
4402
4543
  attach: `material-${index}`,
4403
4544
  color: hover ? hoverColor : "white",
4404
4545
  transparent: true,
@@ -4509,7 +4650,7 @@ function AxisHead({
4509
4650
  "spriteMaterial",
4510
4651
  {
4511
4652
  map: texture,
4512
- "map-anisotropy": renderer.getMaxAnisotropy() || 1,
4653
+ "map-anisotropy": getMaxAnisotropy(renderer),
4513
4654
  alphaTest: 0.3,
4514
4655
  opacity: label ? 1 : 0.75,
4515
4656
  toneMapped: false
@@ -4654,6 +4795,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4654
4795
  }, ref) => {
4655
4796
  const { gl, camera, scene, size, raycaster, events, viewport } = useThree();
4656
4797
  const forceEven = useThree((state) => state.internal.forceEven);
4798
+ const { frustum } = useThree();
4657
4799
  const [el] = React.useState(() => document.createElement(as));
4658
4800
  const root = React.useRef(null);
4659
4801
  const group = React.useRef(null);
@@ -4683,22 +4825,18 @@ const Html = /* @__PURE__ */ React.forwardRef(
4683
4825
  if (group.current) {
4684
4826
  if (!root.current) {
4685
4827
  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;`;
4828
+ scene.updateMatrixWorld();
4829
+ if (transform) {
4830
+ el.style.cssText = `position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;`;
4831
+ } else {
4832
+ el.style.cssText = `position:absolute;top:0;left:0;transform:translate3d(0,-9999px,0);transform-origin:0 0;`;
4833
+ }
4692
4834
  }
4693
4835
  if (target) {
4694
4836
  if (prepend) target.prepend(el);
4695
4837
  else target.appendChild(el);
4696
4838
  }
4697
4839
  return () => {
4698
- if (root.current) {
4699
- root.current.unmount();
4700
- root.current = null;
4701
- }
4702
4840
  if (target && target.contains(el)) {
4703
4841
  target.removeChild(el);
4704
4842
  }
@@ -4832,8 +4970,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4832
4970
  if (transformOuterRef.current) {
4833
4971
  const el2 = transformOuterRef.current.children[0];
4834
4972
  if (el2?.clientWidth && el2?.clientHeight) {
4835
- const { isOrthographicCamera } = camera;
4836
- if (isOrthographicCamera || geometry) {
4973
+ if (geometry) {
4837
4974
  if (props.scale) {
4838
4975
  if (!Array.isArray(props.scale)) {
4839
4976
  occlusionMeshRef.current.scale.setScalar(1 / props.scale);
@@ -4865,64 +5002,7 @@ const Html = /* @__PURE__ */ React.forwardRef(
4865
5002
  }
4866
5003
  }
4867
5004
  });
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
- )));
5005
+ 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
5006
  }
4927
5007
  );
4928
5008
 
@@ -5775,6 +5855,7 @@ const mW = /* @__PURE__ */ new THREE.Matrix4();
5775
5855
  const mL = /* @__PURE__ */ new THREE.Matrix4();
5776
5856
  const mL0Inv = /* @__PURE__ */ new THREE.Matrix4();
5777
5857
  const mdL = /* @__PURE__ */ new THREE.Matrix4();
5858
+ const mdW = /* @__PURE__ */ new THREE.Matrix4();
5778
5859
  const mG = /* @__PURE__ */ new THREE.Matrix4();
5779
5860
  const bb = /* @__PURE__ */ new THREE.Box3();
5780
5861
  const bbObj = /* @__PURE__ */ new THREE.Box3();
@@ -5889,7 +5970,8 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
5889
5970
  onDragStart && onDragStart(props2);
5890
5971
  invalidate();
5891
5972
  },
5892
- onDrag: (mdW) => {
5973
+ onDrag: (mdW_) => {
5974
+ mdW.copy(mdW_);
5893
5975
  mP.copy(parentRef.current.matrixWorld);
5894
5976
  mPInv.copy(mP).invert();
5895
5977
  mW.copy(mW0).premultiply(mdW);
@@ -5901,12 +5983,12 @@ const PivotControls = /* @__PURE__ */ React.forwardRef(
5901
5983
  }
5902
5984
  const target = resolveObject(object);
5903
5985
  if (target) target.matrix.copy(mL);
5904
- onDrag && onDrag(mL, mdL, mW, mdW);
5986
+ onDrag && onDrag({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
5905
5987
  invalidate();
5906
5988
  },
5907
5989
  onDragEnd: () => {
5908
5990
  dragState.current = null;
5909
- if (onDragEnd) onDragEnd();
5991
+ if (onDragEnd) onDragEnd({ local: mL, deltaLocal: mdL, world: mW, deltaWorld: mdW });
5910
5992
  invalidate();
5911
5993
  },
5912
5994
  onHover: (props2) => {
@@ -6017,7 +6099,9 @@ const TransformControls = /* @__PURE__ */ React.forwardRef(
6017
6099
  const group = React.useRef(null);
6018
6100
  React.useLayoutEffect(() => {
6019
6101
  if (object) {
6020
- controls.attach(object instanceof THREE.Object3D ? object : object.current);
6102
+ controls.attach(
6103
+ object instanceof THREE.Object3D ? object : object.current
6104
+ );
6021
6105
  } else if (group.current instanceof THREE.Object3D) {
6022
6106
  controls.attach(group.current);
6023
6107
  }
@@ -6116,7 +6200,7 @@ const PointMaterial = /* @__PURE__ */ React.forwardRef((props, ref) => {
6116
6200
  return /* @__PURE__ */ React.createElement("primitive", { ...props, object: material, ref, attach: "material" });
6117
6201
  });
6118
6202
 
6119
- const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = true, autoplay, ...props }, ref) => {
6203
+ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, loop = false, autoplay, ...props }, ref) => {
6120
6204
  const sound = React.useRef(null);
6121
6205
  React.useImperativeHandle(ref, () => sound.current, []);
6122
6206
  const camera = useThree(({ camera: camera2 }) => camera2);
@@ -6128,7 +6212,9 @@ const PositionalAudio = /* @__PURE__ */ React.forwardRef(({ url, distance = 1, l
6128
6212
  _sound.setBuffer(Array.isArray(buffer) ? buffer[0] : buffer);
6129
6213
  _sound.setRefDistance(distance);
6130
6214
  _sound.setLoop(loop);
6131
- if (autoplay && !_sound.isPlaying) _sound.play();
6215
+ if (autoplay && !_sound.isPlaying) {
6216
+ _sound.play();
6217
+ }
6132
6218
  }
6133
6219
  }, [buffer, camera, distance, loop]);
6134
6220
  React.useEffect(() => {
@@ -6197,7 +6283,6 @@ const tempMatrix = /* @__PURE__ */ new THREE.Matrix4();
6197
6283
  const translation = /* @__PURE__ */ new THREE.Vector3();
6198
6284
  const rotation = /* @__PURE__ */ new THREE.Quaternion();
6199
6285
  const scale = /* @__PURE__ */ new THREE.Vector3();
6200
- const isInstancedBufferAttribute = (attr) => attr.isInstancedBufferAttribute;
6201
6286
  const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }, ref) => {
6202
6287
  React.useMemo(() => extend({ PositionMesh }), []);
6203
6288
  const group = React.useRef(null);
@@ -6217,24 +6302,20 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
6217
6302
  const parentRef = React.useRef(null);
6218
6303
  React.useImperativeHandle(ref, () => parentRef.current, []);
6219
6304
  const [instances, setInstances] = React.useState([]);
6305
+ const boundsDirty = React.useRef(true);
6220
6306
  const [[matrices, colors]] = React.useState(() => {
6221
6307
  const mArray = new Float32Array(limit * 16);
6222
6308
  for (let i = 0; i < limit; i++) tempMatrix.identity().toArray(mArray, i * 16);
6223
6309
  return [mArray, new Float32Array([...new Array(limit * 3)].map(() => 1))];
6224
6310
  });
6225
6311
  React.useEffect(() => {
6226
- parentRef.current.instanceMatrix.needsUpdate = true;
6312
+ if (parentRef.current) parentRef.current.instanceMatrix.needsUpdate = true;
6227
6313
  });
6228
6314
  let iterations = 0;
6229
6315
  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
6316
  useFrame(() => {
6237
6317
  if (frames === Infinity || iterations < frames) {
6318
+ if (!parentRef.current) return;
6238
6319
  parentRef.current.updateMatrix();
6239
6320
  parentRef.current.updateMatrixWorld();
6240
6321
  parentMatrix.copy(parentRef.current.matrixWorld).invert();
@@ -6255,13 +6336,22 @@ const Instances = /* @__PURE__ */ React.forwardRef(({ context, children, range,
6255
6336
  }
6256
6337
  iterations++;
6257
6338
  }
6339
+ if (boundsDirty.current && (frames === Infinity || iterations === frames)) {
6340
+ boundsDirty.current = false;
6341
+ if (parentRef.current.boundingBox) parentRef.current.computeBoundingBox();
6342
+ if (parentRef.current.boundingSphere) parentRef.current.computeBoundingSphere();
6343
+ }
6258
6344
  });
6259
6345
  const api = React.useMemo(
6260
6346
  () => ({
6261
6347
  getParent: () => parentRef,
6262
6348
  subscribe: (ref2) => {
6263
6349
  setInstances((instances2) => [...instances2, ref2]);
6264
- return () => setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
6350
+ boundsDirty.current = true;
6351
+ return () => {
6352
+ setInstances((instances2) => instances2.filter((item) => item.current !== ref2.current));
6353
+ boundsDirty.current = true;
6354
+ };
6265
6355
  }
6266
6356
  }),
6267
6357
  []
@@ -6286,10 +6376,23 @@ const Merged = /* @__PURE__ */ React.forwardRef(function Merged2({ meshes, child
6286
6376
  if (!isArray) {
6287
6377
  for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key];
6288
6378
  }
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 }));
6379
+ const render = (args) => {
6380
+ if (isArray) {
6381
+ return children(
6382
+ ...args
6383
+ );
6384
+ }
6385
+ const instanceMap = {};
6386
+ const keys = Object.keys(meshes).filter((key) => meshes[key].isMesh);
6387
+ keys.forEach((key, i) => {
6388
+ instanceMap[key] = args[i];
6389
+ });
6390
+ return children(instanceMap);
6391
+ };
6392
+ const components = (isArray ? meshes : Object.values(meshes)).map((mesh) => {
6393
+ const { geometry, material } = mesh;
6394
+ return /* @__PURE__ */ React.createElement(Instances, { key: geometry.uuid, geometry, material, ...props });
6395
+ });
6293
6396
  return /* @__PURE__ */ React.createElement("group", { ref }, renderRecursive(render, components));
6294
6397
  });
6295
6398
  function renderRecursive(render, components, results = []) {
@@ -7363,12 +7466,13 @@ function Sampler({ children, weight, transform, instances, mesh, count = 16, ...
7363
7466
  return /* @__PURE__ */ React.createElement("group", { ref: group, ...props }, children);
7364
7467
  }
7365
7468
 
7469
+ const pathStyle = (path) => path.userData?.style;
7366
7470
  const Svg = /* @__PURE__ */ forwardRef(
7367
7471
  function R3FSvg({ src, skipFill, skipStrokes, fillMaterial, strokeMaterial, fillMeshProps, strokeMeshProps, ...props }, ref) {
7368
7472
  const svg = useLoader(SVGLoader, !src.startsWith("<svg") ? src : `data:image/svg+xml;utf8,${src}`);
7369
7473
  const strokeGeometries = useMemo(
7370
7474
  () => 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))
7475
+ (path) => pathStyle(path)?.stroke === void 0 || pathStyle(path).stroke === "none" ? null : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), pathStyle(path)))
7372
7476
  ),
7373
7477
  [svg, skipStrokes]
7374
7478
  );
@@ -7376,21 +7480,21 @@ const Svg = /* @__PURE__ */ forwardRef(
7376
7480
  return () => strokeGeometries.forEach((group) => group && group.map((g) => g.dispose()));
7377
7481
  }, [strokeGeometries]);
7378
7482
  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(
7483
+ 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
7484
  "meshBasicMaterial",
7381
7485
  {
7382
- color: path.userData.style.fill,
7383
- opacity: path.userData.style.fillOpacity,
7486
+ color: pathStyle(path).fill,
7487
+ opacity: pathStyle(path).fillOpacity,
7384
7488
  transparent: true,
7385
7489
  side: DoubleSide,
7386
7490
  depthWrite: false,
7387
7491
  ...fillMaterial
7388
7492
  }
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(
7493
+ ))), !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
7494
  "meshBasicMaterial",
7391
7495
  {
7392
- color: path.userData.style.stroke,
7393
- opacity: path.userData.style.strokeOpacity,
7496
+ color: pathStyle(path).stroke,
7497
+ opacity: pathStyle(path).strokeOpacity,
7394
7498
  transparent: true,
7395
7499
  side: DoubleSide,
7396
7500
  depthWrite: false,
@@ -7561,7 +7665,7 @@ function Preload({ all, scene, camera }) {
7561
7665
  });
7562
7666
  }
7563
7667
  gl.compile(scene || dScene, camera || dCamera);
7564
- const cubeRenderTarget = new WebGLCubeRenderTarget(128);
7668
+ const cubeRenderTarget = new CubeRenderTarget(128);
7565
7669
  const cubeCamera = new CubeCamera$1(0.01, 1e5, cubeRenderTarget);
7566
7670
  cubeCamera.update(gl, scene || dScene);
7567
7671
  cubeRenderTarget.dispose();
@@ -7613,8 +7717,8 @@ function useVideoTexture(srcOrSrcObject, {
7613
7717
  });
7614
7718
  if (src && IS_BROWSER && src.endsWith(".m3u8")) {
7615
7719
  const hls = hlsRef.current = await getHls(hlsConfig);
7616
- if (hls) {
7617
- hls.on(Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
7720
+ if (hls && _HLSModule) {
7721
+ hls.on(_HLSModule.Events.MEDIA_ATTACHED, () => void hls.loadSource(src));
7618
7722
  hls.attachMedia(video2);
7619
7723
  }
7620
7724
  }
@@ -7834,9 +7938,125 @@ function Fbx({
7834
7938
  return /* @__PURE__ */ React.createElement(Clone, { ...props, object });
7835
7939
  }
7836
7940
 
7941
+ const SERVICE_KEY = "__drei_ktx2_service__";
7942
+ const DEFAULT_TRANSCODER_PATH = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
7943
+ class KTX2LoaderServiceImpl {
7944
+ loader = null;
7945
+ renderer = null;
7946
+ _isReady = false;
7947
+ transcoderPath = DEFAULT_TRANSCODER_PATH;
7948
+ pendingCallbacks = [];
7949
+ /**
7950
+ * Get or create the singleton KTX2Loader instance.
7951
+ * The loader is created lazily on first access.
7952
+ */
7953
+ getLoader() {
7954
+ if (!this.loader) {
7955
+ this.loader = new KTX2Loader();
7956
+ this.loader.setTranscoderPath(this.transcoderPath);
7957
+ }
7958
+ return this.loader;
7959
+ }
7960
+ /**
7961
+ * Register a renderer and call detectSupport.
7962
+ * Should be called from within React context (useLayoutEffect).
7963
+ * Flushes any queued callbacks.
7964
+ */
7965
+ registerRenderer(renderer) {
7966
+ if (this._isReady && this.renderer === renderer) {
7967
+ return;
7968
+ }
7969
+ this.renderer = renderer;
7970
+ const loader = this.getLoader();
7971
+ loader.detectSupport(renderer);
7972
+ this._isReady = true;
7973
+ this.flushPendingCallbacks();
7974
+ }
7975
+ /**
7976
+ * Check if the service is ready (has renderer registered).
7977
+ */
7978
+ isReady() {
7979
+ return this._isReady;
7980
+ }
7981
+ /**
7982
+ * Get the registered renderer, if any.
7983
+ */
7984
+ getRenderer() {
7985
+ return this.renderer;
7986
+ }
7987
+ /**
7988
+ * Register a callback to run when renderer becomes available.
7989
+ * If renderer is already available, runs immediately.
7990
+ */
7991
+ onReady(callback) {
7992
+ if (this._isReady) {
7993
+ callback();
7994
+ } else {
7995
+ this.pendingCallbacks.push(callback);
7996
+ }
7997
+ }
7998
+ /**
7999
+ * Process all pending callbacks.
8000
+ */
8001
+ flushPendingCallbacks() {
8002
+ const callbacks = this.pendingCallbacks;
8003
+ this.pendingCallbacks = [];
8004
+ for (const callback of callbacks) {
8005
+ try {
8006
+ callback();
8007
+ } catch (error) {
8008
+ console.error("KTX2LoaderService: Error in pending callback:", error);
8009
+ }
8010
+ }
8011
+ }
8012
+ /**
8013
+ * Set the transcoder path for basis/ktx2 decoding.
8014
+ */
8015
+ setTranscoderPath(path) {
8016
+ this.transcoderPath = path;
8017
+ if (this.loader) {
8018
+ this.loader.setTranscoderPath(path);
8019
+ }
8020
+ }
8021
+ /**
8022
+ * Get the current transcoder path.
8023
+ */
8024
+ getTranscoderPath() {
8025
+ return this.transcoderPath;
8026
+ }
8027
+ /**
8028
+ * Dispose of the loader and reset state.
8029
+ * Useful for testing or cleanup.
8030
+ */
8031
+ dispose() {
8032
+ if (this.loader) {
8033
+ this.loader.dispose();
8034
+ }
8035
+ const path = this.transcoderPath;
8036
+ this.loader = null;
8037
+ this.renderer = null;
8038
+ this._isReady = false;
8039
+ this.transcoderPath = path;
8040
+ this.pendingCallbacks = [];
8041
+ }
8042
+ }
8043
+ const KTX2LoaderService = globalThis[SERVICE_KEY] || (globalThis[SERVICE_KEY] = new KTX2LoaderServiceImpl());
8044
+
7837
8045
  let dracoLoader = null;
7838
8046
  let decoderPath = "https://www.gstatic.com/draco/versioned/decoders/1.5.5/";
7839
- function extensions(useDraco = true, useMeshopt = true, extendLoader) {
8047
+ let legacyApiWarned = false;
8048
+ function isOptionsObject(arg) {
8049
+ return typeof arg === "object" && arg !== null && !Array.isArray(arg);
8050
+ }
8051
+ function warnLegacyApi() {
8052
+ if (!legacyApiWarned) {
8053
+ legacyApiWarned = true;
8054
+ console.warn(
8055
+ "useGLTF: Boolean arguments are deprecated. Use options object instead:\n useGLTF(path, { draco: true, meshopt: true, ktx2: true })"
8056
+ );
8057
+ }
8058
+ }
8059
+ function extensions(useDraco = true, useMeshopt = true, useKTX2 = false, extendLoader, renderer) {
7840
8060
  return (loader) => {
7841
8061
  if (extendLoader) {
7842
8062
  extendLoader(loader);
@@ -7852,36 +8072,110 @@ function extensions(useDraco = true, useMeshopt = true, extendLoader) {
7852
8072
  const decoder = typeof MeshoptDecoder === "function" ? MeshoptDecoder() : MeshoptDecoder;
7853
8073
  loader.setMeshoptDecoder(decoder);
7854
8074
  }
8075
+ if (useKTX2) {
8076
+ const ktx2Loader = KTX2LoaderService.getLoader();
8077
+ if (typeof useKTX2 === "string") {
8078
+ ktx2Loader.setTranscoderPath(useKTX2);
8079
+ }
8080
+ if (renderer) {
8081
+ ktx2Loader.detectSupport(renderer);
8082
+ }
8083
+ loader.setKTX2Loader(ktx2Loader);
8084
+ }
7855
8085
  };
7856
8086
  }
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));
8087
+ function useGLTF(path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) {
8088
+ let draco = true;
8089
+ let meshopt = true;
8090
+ let ktx2 = false;
8091
+ let extendLoader;
8092
+ if (isOptionsObject(optionsOrDraco)) {
8093
+ draco = optionsOrDraco.draco ?? true;
8094
+ meshopt = optionsOrDraco.meshopt ?? true;
8095
+ ktx2 = optionsOrDraco.ktx2 ?? false;
8096
+ extendLoader = optionsOrDraco.extendLoader;
8097
+ } else if (optionsOrDraco !== void 0) {
8098
+ warnLegacyApi();
8099
+ draco = optionsOrDraco;
8100
+ meshopt = useMeshoptArg ?? true;
8101
+ ktx2 = useKTX2Arg ?? false;
8102
+ extendLoader = extendLoaderArg;
8103
+ }
8104
+ const renderer = useThree((state) => state.renderer);
8105
+ useLayoutEffect(() => {
8106
+ if (ktx2) {
8107
+ KTX2LoaderService.registerRenderer(renderer);
8108
+ }
8109
+ }, [ktx2, renderer]);
8110
+ return useLoader(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, ktx2 ? renderer : void 0));
8111
+ }
8112
+ useGLTF.preload = (path, optionsOrDraco, useMeshoptArg, useKTX2Arg, extendLoaderArg) => {
8113
+ let draco = true;
8114
+ let meshopt = true;
8115
+ let ktx2 = false;
8116
+ let extendLoader;
8117
+ if (isOptionsObject(optionsOrDraco)) {
8118
+ draco = optionsOrDraco.draco ?? true;
8119
+ meshopt = optionsOrDraco.meshopt ?? true;
8120
+ ktx2 = optionsOrDraco.ktx2 ?? false;
8121
+ extendLoader = optionsOrDraco.extendLoader;
8122
+ } else if (optionsOrDraco !== void 0) {
8123
+ warnLegacyApi();
8124
+ draco = optionsOrDraco;
8125
+ meshopt = useMeshoptArg ?? true;
8126
+ ktx2 = useKTX2Arg ?? false;
8127
+ extendLoader = extendLoaderArg;
8128
+ }
8129
+ if (ktx2) {
8130
+ KTX2LoaderService.onReady(() => {
8131
+ const renderer = KTX2LoaderService.getRenderer();
8132
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, ktx2, extendLoader, renderer));
8133
+ });
8134
+ } else {
8135
+ useLoader.preload(GLTFLoader, path, extensions(draco, meshopt, false, extendLoader));
8136
+ }
8137
+ };
7859
8138
  useGLTF.clear = (path) => useLoader.clear(GLTFLoader, path);
7860
8139
  useGLTF.setDecoderPath = (path) => {
7861
8140
  decoderPath = path;
7862
8141
  };
8142
+ useGLTF.setKTX2TranscoderPath = (path) => {
8143
+ KTX2LoaderService.setTranscoderPath(path);
8144
+ };
7863
8145
  const Gltf = /* @__PURE__ */ React.forwardRef(
7864
- ({ src, useDraco, useMeshOpt, extendLoader, ...props }, ref) => {
7865
- const { scene } = useGLTF(src, useDraco, useMeshOpt, extendLoader);
8146
+ ({ src, useDraco, useMeshOpt, extendLoader, options, ...props }, ref) => {
8147
+ const opts = options ?? {
8148
+ draco: useDraco,
8149
+ meshopt: useMeshOpt,
8150
+ extendLoader
8151
+ };
8152
+ const { scene } = useGLTF(src, opts);
7866
8153
  return /* @__PURE__ */ React.createElement(Clone, { ref, ...props, object: scene });
7867
8154
  }
7868
8155
  );
7869
8156
 
7870
- let transcoderPath = "https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/";
7871
8157
  function useKTX2(input, basisPath = true, extendLoader) {
7872
8158
  const renderer = useThree((state) => state.renderer);
8159
+ useLayoutEffect(() => {
8160
+ KTX2LoaderService.registerRenderer(renderer);
8161
+ }, [renderer]);
7873
8162
  const textures = useLoader(KTX2Loader, IsObject(input) ? Object.values(input) : input, (loader) => {
7874
8163
  if (extendLoader) {
7875
8164
  extendLoader(loader);
7876
8165
  }
7877
8166
  if (basisPath) {
7878
- loader.setTranscoderPath(typeof basisPath === "string" ? basisPath : transcoderPath);
8167
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
8168
+ loader.setTranscoderPath(path);
7879
8169
  }
7880
8170
  loader.detectSupport(renderer);
7881
8171
  });
7882
8172
  useEffect(() => {
7883
8173
  const array = Array.isArray(textures) ? textures : [textures];
7884
- array.forEach((texture) => renderer.initTexture(texture));
8174
+ array.forEach((texture) => {
8175
+ if ("initTexture" in renderer) {
8176
+ renderer.initTexture(texture);
8177
+ }
8178
+ });
7885
8179
  }, [renderer, textures]);
7886
8180
  if (IsObject(input)) {
7887
8181
  const keys = Object.keys(input);
@@ -7892,17 +8186,26 @@ function useKTX2(input, basisPath = true, extendLoader) {
7892
8186
  return textures;
7893
8187
  }
7894
8188
  }
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
- });
8189
+ useKTX2.preload = (url, basisPath = true, extendLoader) => {
8190
+ KTX2LoaderService.onReady(() => {
8191
+ const renderer = KTX2LoaderService.getRenderer();
8192
+ useLoader.preload(KTX2Loader, url, (loader) => {
8193
+ if (extendLoader) {
8194
+ extendLoader(loader);
8195
+ }
8196
+ if (basisPath) {
8197
+ const path = typeof basisPath === "string" ? basisPath : KTX2LoaderService.getTranscoderPath();
8198
+ loader.setTranscoderPath(path);
8199
+ }
8200
+ if (renderer) {
8201
+ loader.detectSupport(renderer);
8202
+ }
8203
+ });
8204
+ });
8205
+ };
7903
8206
  useKTX2.clear = (input) => useLoader.clear(KTX2Loader, input);
7904
8207
  useKTX2.setTranscoderPath = (path) => {
7905
- transcoderPath = path;
8208
+ KTX2LoaderService.setTranscoderPath(path);
7906
8209
  };
7907
8210
  const Ktx2 = ({ children, input, basisPath, extendLoader }) => {
7908
8211
  const texture = useKTX2(input, basisPath, extendLoader);
@@ -8096,6 +8399,7 @@ function useEffectfulState(fn, deps = [], cb) {
8096
8399
  }
8097
8400
 
8098
8401
  function Stats({ showPanel = 0, className, parent }) {
8402
+ React.useEffect(() => void console.warn("drei: <Stats /> is deprecated, use <StatsGl /> instead."), []);
8099
8403
  const stats = useEffectfulState(() => new StatsImpl(), []);
8100
8404
  React.useEffect(() => {
8101
8405
  if (stats) {
@@ -8124,6 +8428,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8124
8428
  const stats2 = new Stats$1({
8125
8429
  ...props
8126
8430
  });
8431
+ stats2.init(renderer);
8127
8432
  return stats2;
8128
8433
  }, [renderer]);
8129
8434
  React.useImperativeHandle(fref, () => stats.domElement, [stats]);
@@ -8141,7 +8446,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8141
8446
  return () => {
8142
8447
  if (classNames.length) stats.domElement.classList.remove(...classNames);
8143
8448
  node?.removeChild(stats.domElement);
8144
- stats.end();
8449
+ stats.dispose();
8145
8450
  };
8146
8451
  }
8147
8452
  }, [parent, stats, className, id, clearStatsGlStyle]);
@@ -8149,7 +8454,7 @@ const StatsGl = /* @__PURE__ */ React.forwardRef(
8149
8454
  () => {
8150
8455
  stats.begin();
8151
8456
  },
8152
- { before: "render" }
8457
+ { before: "update" }
8153
8458
  );
8154
8459
  useFrame(
8155
8460
  () => {
@@ -8314,8 +8619,9 @@ const TUNNEL_KEY = "__drei_view_tunnel__";
8314
8619
  const tracked = globalThis[TUNNEL_KEY] || (globalThis[TUNNEL_KEY] = tunnel());
8315
8620
  function computeContainerPosition(canvasSize, trackRect, forceEven) {
8316
8621
  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
8622
  const canvasBottom = canvasSize.top + canvasSize.height;
8623
+ const canvasRight = canvasSize.left + canvasSize.width;
8624
+ const isOffscreen = trackBottom < canvasSize.top || trackTop > canvasBottom || right < canvasSize.left || trackLeft > canvasRight;
8319
8625
  const rawBottom = canvasBottom - trackBottom;
8320
8626
  const rawLeft = trackLeft - canvasSize.left;
8321
8627
  const rawTop = trackTop - canvasSize.top;
@@ -8507,7 +8813,7 @@ const HtmlView = /* @__PURE__ */ React.forwardRef(
8507
8813
  );
8508
8814
  const View = /* @__PURE__ */ (() => {
8509
8815
  const _View = React.forwardRef((props, fref) => {
8510
- const store = React.useContext(context$a);
8816
+ const store = React.useContext(context$b);
8511
8817
  if (!store) return /* @__PURE__ */ React.createElement(HtmlView, { ref: fref, ...props });
8512
8818
  else return /* @__PURE__ */ React.createElement(CanvasView, { ref: fref, ...props });
8513
8819
  });
@@ -8875,12 +9181,12 @@ useEnvironment.preload = (preloadOptions) => {
8875
9181
  if (extensions) extensions(loader2);
8876
9182
  });
8877
9183
  };
8878
- const clearDefaultOptins = {
9184
+ const clearDefaultOptions = {
8879
9185
  files: defaultFiles,
8880
9186
  preset: void 0
8881
9187
  };
8882
9188
  useEnvironment.clear = (clearOptions) => {
8883
- const options = { ...clearDefaultOptins, ...clearOptions };
9189
+ const options = { ...clearDefaultOptions, ...clearOptions };
8884
9190
  let { files } = options;
8885
9191
  const { preset } = options;
8886
9192
  if (preset) {
@@ -9003,7 +9309,7 @@ function EnvironmentPortal({
9003
9309
  const camera = React.useRef(null);
9004
9310
  const [virtualScene] = React.useState(() => new Scene());
9005
9311
  const fbo = React.useMemo(() => {
9006
- const fbo2 = new WebGLCubeRenderTarget(resolution);
9312
+ const fbo2 = new CubeRenderTarget(resolution);
9007
9313
  fbo2.texture.type = HalfFloatType;
9008
9314
  return fbo2;
9009
9315
  }, [resolution]);
@@ -9574,9 +9880,14 @@ function Select({
9574
9880
  const oldControlsEnabled = controls?.enabled;
9575
9881
  let isDown = false;
9576
9882
  function prepareRay(event, vec) {
9577
- const { offsetX, offsetY } = event;
9883
+ const canvas = gl.domElement;
9884
+ const rect = canvas.getBoundingClientRect();
9885
+ const x = event.clientX - rect.left;
9886
+ const y = event.clientY - rect.top;
9578
9887
  const { width, height } = size;
9579
- vec.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
9888
+ const normalizedX = x / width * 2 - 1;
9889
+ const normalizedY = -(y / height) * 2 + 1;
9890
+ vec.set(normalizedX, normalizedY, -1);
9580
9891
  }
9581
9892
  function onSelectStart(event) {
9582
9893
  if (controls) controls.enabled = false;
@@ -9664,62 +9975,63 @@ const remap = /* @__PURE__ */ Fn((inputs) => {
9664
9975
  const [value, min1, max1, min2, max2] = inputs;
9665
9976
  return min2.add(value.sub(min1).mul(max2.sub(min2)).div(max1.sub(min1)));
9666
9977
  });
9667
- class WireframeMaterialImpl extends MeshBasicNodeMaterial {
9668
- //* Private Uniform Nodes --
9669
- _strokeOpacity;
9670
- _fillOpacity;
9671
- _fillMix;
9672
- _thickness;
9673
- _colorBackfaces;
9674
- // Using number as bool (0/1)
9675
- _dashInvert;
9676
- _dash;
9677
- _dashRepeats;
9678
- _dashLength;
9679
- _squeeze;
9680
- _squeezeMin;
9681
- _squeezeMax;
9682
- _stroke;
9683
- _backfaceStroke;
9684
- _fill;
9978
+ class WireframeMaterialImpl extends withUniforms(MeshBasicNodeMaterial, {
9979
+ /** Stroke (edge) opacity, default: 1 */
9980
+ strokeOpacity: () => uniform(1),
9981
+ /** Fill opacity (background), default: 0.25 */
9982
+ fillOpacity: () => uniform(0.25),
9983
+ /** Mix factor between material color and fill color, default: 0 */
9984
+ fillMix: () => uniform(0),
9985
+ /** Edge thickness 0-1, default: 0.05 */
9986
+ thickness: () => uniform(0.05),
9987
+ /** Use different color for backfaces, default: false */
9988
+ colorBackfaces: () => uniform(false),
9989
+ /** Invert dash pattern, default: true */
9990
+ dashInvert: () => uniform(true),
9991
+ /** Enable dashed lines, default: false */
9992
+ dash: () => uniform(false),
9993
+ /** Number of dash repeats, default: 4 */
9994
+ dashRepeats: () => uniform(4),
9995
+ /** Dash length 0-1, default: 0.5 */
9996
+ dashLength: () => uniform(0.5),
9997
+ /** Squeeze thickness toward line center, default: false */
9998
+ squeeze: () => uniform(false),
9999
+ /** Minimum squeeze factor, default: 0.2 */
10000
+ squeezeMin: () => uniform(0.2),
10001
+ /** Maximum squeeze factor, default: 1 */
10002
+ squeezeMax: () => uniform(1),
10003
+ /** Stroke (edge) color */
10004
+ stroke: () => uniform(new THREE.Color("#ff0000")),
10005
+ /** Backface stroke color */
10006
+ backfaceStroke: () => uniform(new THREE.Color("#0000ff")),
10007
+ /** Fill (background) color */
10008
+ fill: () => uniform(new THREE.Color("#00ff00"))
10009
+ }) {
9685
10010
  /** Type flag for identification */
9686
10011
  isWireframeMaterial = true;
9687
10012
  constructor() {
9688
10013
  super();
9689
- this._strokeOpacity = uniform(1);
9690
- this._fillOpacity = uniform(0.25);
9691
- this._fillMix = uniform(0);
9692
- this._thickness = uniform(0.05);
9693
- this._colorBackfaces = uniform(0);
9694
- this._dashInvert = uniform(1);
9695
- this._dash = uniform(0);
9696
- this._dashRepeats = uniform(4);
9697
- this._dashLength = uniform(0.5);
9698
- this._squeeze = uniform(0);
9699
- this._squeezeMin = uniform(0.2);
9700
- this._squeezeMax = uniform(1);
9701
- this._stroke = uniform(new THREE.Color("#ff0000"));
9702
- this._backfaceStroke = uniform(new THREE.Color("#0000ff"));
9703
- this._fill = uniform(new THREE.Color("#00ff00"));
9704
10014
  this.transparent = true;
9705
10015
  this.side = THREE.DoubleSide;
9706
10016
  this._buildWireframeShader();
9707
10017
  }
9708
10018
  _buildWireframeShader() {
9709
- const strokeOpacityUniform = this._strokeOpacity;
9710
- const fillOpacityUniform = this._fillOpacity;
9711
- const thicknessUniform = this._thickness;
9712
- const colorBackfacesUniform = this._colorBackfaces;
9713
- const dashInvertUniform = this._dashInvert;
9714
- const dashUniform = this._dash;
9715
- const dashRepeatsUniform = this._dashRepeats;
9716
- const dashLengthUniform = this._dashLength;
9717
- const squeezeUniform = this._squeeze;
9718
- const squeezeMinUniform = this._squeezeMin;
9719
- const squeezeMaxUniform = this._squeezeMax;
9720
- const strokeUniform = this._stroke;
9721
- const backfaceStrokeUniform = this._backfaceStroke;
9722
- const fillUniform = this._fill;
10019
+ const {
10020
+ strokeOpacity: strokeOpacityUniform,
10021
+ fillOpacity: fillOpacityUniform,
10022
+ thickness: thicknessUniform,
10023
+ colorBackfaces: colorBackfacesUniform,
10024
+ dashInvert: dashInvertUniform,
10025
+ dash: dashUniform,
10026
+ dashRepeats: dashRepeatsUniform,
10027
+ dashLength: dashLengthUniform,
10028
+ squeeze: squeezeUniform,
10029
+ squeezeMin: squeezeMinUniform,
10030
+ squeezeMax: squeezeMaxUniform,
10031
+ stroke: strokeUniform,
10032
+ backfaceStroke: backfaceStrokeUniform,
10033
+ fill: fillUniform
10034
+ } = this.uniforms;
9723
10035
  const barycentricAttr = attribute("barycentric", "vec3");
9724
10036
  const vBarycentric = varying(barycentricAttr, "v_barycentric");
9725
10037
  this.outputNode = Fn(() => {
@@ -9730,136 +10042,23 @@ class WireframeMaterialImpl extends MeshBasicNodeMaterial {
9730
10042
  const isYSmallest = barycentric.y.lessThan(barycentric.x).and(barycentric.y.lessThan(barycentric.z));
9731
10043
  positionAlong.assign(select(isYSmallest, float(1).sub(maxXY), maxXY));
9732
10044
  const computedThickness = remap(thicknessUniform, float(0), float(1), float(0), float(0.34)).toVar();
9733
- const squeezeEnabled = squeezeUniform.greaterThan(0.5);
9734
10045
  const squeezeFactor = mix(squeezeMinUniform, squeezeMaxUniform, float(1).sub(sin(positionAlong.mul(Math.PI))));
9735
- computedThickness.assign(select(squeezeEnabled, computedThickness.mul(squeezeFactor), computedThickness));
9736
- const dashEnabled = dashUniform.greaterThan(0.5);
9737
- const dashInverted = dashInvertUniform.greaterThan(0.5);
10046
+ computedThickness.assign(select(squeezeUniform, computedThickness.mul(squeezeFactor), computedThickness));
9738
10047
  const baseOffset = float(1).div(dashRepeatsUniform).mul(dashLengthUniform).mul(0.5);
9739
10048
  const additionalOffset = float(1).div(dashRepeatsUniform).mul(0.5);
9740
- const dashOffset = select(dashInverted, baseOffset, baseOffset.add(additionalOffset));
10049
+ const dashOffset = select(dashInvertUniform, baseOffset, baseOffset.add(additionalOffset));
9741
10050
  const pattern = fract(positionAlong.add(dashOffset).mul(dashRepeatsUniform));
9742
10051
  const dashMask = float(1).sub(aastep(dashLengthUniform, pattern));
9743
- computedThickness.assign(select(dashEnabled, computedThickness.mul(dashMask), computedThickness));
10052
+ computedThickness.assign(select(dashUniform, computedThickness.mul(dashMask), computedThickness));
9744
10053
  const edge = float(1).sub(aastep(computedThickness, d));
9745
- const useBackfaceColor = colorBackfacesUniform.greaterThan(0.5);
9746
10054
  const isFrontFace = frontFacing;
9747
- const currentStroke = select(useBackfaceColor.and(isFrontFace.not()), backfaceStrokeUniform, strokeUniform);
10055
+ const currentStroke = select(colorBackfacesUniform.and(isFrontFace.not()), backfaceStrokeUniform, strokeUniform);
9748
10056
  const colorStroke = vec4(currentStroke, edge);
9749
10057
  const colorFill = vec4(fillUniform, fillOpacityUniform);
9750
10058
  const outColor = mix(colorFill, colorStroke, edge.mul(strokeOpacityUniform));
9751
10059
  return outColor;
9752
10060
  })();
9753
10061
  }
9754
- //* Uniform Accessors ==============================
9755
- /** Stroke (edge) opacity, default: 1 */
9756
- get strokeOpacity() {
9757
- return this._strokeOpacity.value;
9758
- }
9759
- set strokeOpacity(v) {
9760
- this._strokeOpacity.value = v;
9761
- }
9762
- /** Fill opacity (background), default: 0.25 */
9763
- get fillOpacity() {
9764
- return this._fillOpacity.value;
9765
- }
9766
- set fillOpacity(v) {
9767
- this._fillOpacity.value = v;
9768
- }
9769
- /** Mix factor between material color and fill color, default: 0 */
9770
- get fillMix() {
9771
- return this._fillMix.value;
9772
- }
9773
- set fillMix(v) {
9774
- this._fillMix.value = v;
9775
- }
9776
- /** Edge thickness 0-1, default: 0.05 */
9777
- get thickness() {
9778
- return this._thickness.value;
9779
- }
9780
- set thickness(v) {
9781
- this._thickness.value = v;
9782
- }
9783
- /** Use different color for backfaces */
9784
- get colorBackfaces() {
9785
- return this._colorBackfaces.value > 0.5;
9786
- }
9787
- set colorBackfaces(v) {
9788
- this._colorBackfaces.value = v ? 1 : 0;
9789
- }
9790
- /** Invert dash pattern */
9791
- get dashInvert() {
9792
- return this._dashInvert.value > 0.5;
9793
- }
9794
- set dashInvert(v) {
9795
- this._dashInvert.value = v ? 1 : 0;
9796
- }
9797
- /** Enable dashed lines */
9798
- get dash() {
9799
- return this._dash.value > 0.5;
9800
- }
9801
- set dash(v) {
9802
- this._dash.value = v ? 1 : 0;
9803
- }
9804
- /** Number of dash repeats, default: 4 */
9805
- get dashRepeats() {
9806
- return this._dashRepeats.value;
9807
- }
9808
- set dashRepeats(v) {
9809
- this._dashRepeats.value = v;
9810
- }
9811
- /** Dash length 0-1, default: 0.5 */
9812
- get dashLength() {
9813
- return this._dashLength.value;
9814
- }
9815
- set dashLength(v) {
9816
- this._dashLength.value = v;
9817
- }
9818
- /** Squeeze thickness toward line center */
9819
- get squeeze() {
9820
- return this._squeeze.value > 0.5;
9821
- }
9822
- set squeeze(v) {
9823
- this._squeeze.value = v ? 1 : 0;
9824
- }
9825
- /** Minimum squeeze factor, default: 0.2 */
9826
- get squeezeMin() {
9827
- return this._squeezeMin.value;
9828
- }
9829
- set squeezeMin(v) {
9830
- this._squeezeMin.value = v;
9831
- }
9832
- /** Maximum squeeze factor, default: 1 */
9833
- get squeezeMax() {
9834
- return this._squeezeMax.value;
9835
- }
9836
- set squeezeMax(v) {
9837
- this._squeezeMax.value = v;
9838
- }
9839
- /** Stroke (edge) color */
9840
- get stroke() {
9841
- return this._stroke.value;
9842
- }
9843
- set stroke(v) {
9844
- if (v instanceof THREE.Color) this._stroke.value = v;
9845
- else this._stroke.value = new THREE.Color(v);
9846
- }
9847
- /** Backface stroke color */
9848
- get backfaceStroke() {
9849
- return this._backfaceStroke.value;
9850
- }
9851
- set backfaceStroke(v) {
9852
- if (v instanceof THREE.Color) this._backfaceStroke.value = v;
9853
- else this._backfaceStroke.value = new THREE.Color(v);
9854
- }
9855
- /** Fill (background) color */
9856
- get fill() {
9857
- return this._fill.value;
9858
- }
9859
- set fill(v) {
9860
- if (v instanceof THREE.Color) this._fill.value = v;
9861
- else this._fill.value = new THREE.Color(v);
9862
- }
9863
10062
  }
9864
10063
  function setBarycentricCoordinates(geometry) {
9865
10064
  const position = geometry.getAttribute("position");
@@ -9921,9 +10120,9 @@ const WireframeMaterial = /* @__PURE__ */ React.forwardRef(
9921
10120
  squeeze,
9922
10121
  squeezeMin,
9923
10122
  squeezeMax,
9924
- stroke: stroke instanceof THREE.Color ? stroke : new THREE.Color(stroke),
9925
- backfaceStroke: backfaceStroke instanceof THREE.Color ? backfaceStroke : new THREE.Color(backfaceStroke),
9926
- fill: fill instanceof THREE.Color ? fill : new THREE.Color(fill)
10123
+ stroke,
10124
+ backfaceStroke,
10125
+ fill
9927
10126
  }
9928
10127
  );
9929
10128
  }
@@ -10039,52 +10238,74 @@ function Wireframe({ geometry: customGeometry, ...props }) {
10039
10238
  return /* @__PURE__ */ React.createElement(WireframeWithoutCustomGeo, { ...props });
10040
10239
  }
10041
10240
 
10241
+ function isShadowCastingLight(object) {
10242
+ const light = object;
10243
+ return light.isLight === true && light.castShadow === true && light.shadow != null;
10244
+ }
10042
10245
  function BakeShadows() {
10043
- const gl = useThree((state) => state.gl);
10044
- useEffect(() => {
10045
- gl.shadowMap.autoUpdate = false;
10046
- gl.shadowMap.needsUpdate = true;
10246
+ const scene = useThree((state) => state.scene);
10247
+ const priorRef = React.useRef(/* @__PURE__ */ new WeakMap());
10248
+ useFrame(() => {
10249
+ const prior = priorRef.current;
10250
+ scene.traverse((object) => {
10251
+ if (!isShadowCastingLight(object)) return;
10252
+ const shadow = object.shadow;
10253
+ if (prior.has(shadow)) return;
10254
+ prior.set(shadow, { autoUpdate: shadow.autoUpdate, needsUpdate: shadow.needsUpdate });
10255
+ shadow.autoUpdate = false;
10256
+ shadow.needsUpdate = true;
10257
+ });
10258
+ });
10259
+ React.useEffect(() => {
10260
+ const prior = priorRef.current;
10047
10261
  return () => {
10048
- gl.shadowMap.autoUpdate = gl.shadowMap.needsUpdate = true;
10262
+ scene.traverse((object) => {
10263
+ if (!isShadowCastingLight(object)) return;
10264
+ const previous = prior.get(object.shadow);
10265
+ if (previous === void 0) return;
10266
+ object.shadow.autoUpdate = previous.autoUpdate;
10267
+ object.shadow.needsUpdate = previous.needsUpdate;
10268
+ prior.delete(object.shadow);
10269
+ });
10049
10270
  };
10050
- }, [gl.shadowMap]);
10271
+ }, [scene]);
10051
10272
  return null;
10052
10273
  }
10053
10274
 
10054
- class ConvolutionMaterial extends MeshBasicNodeMaterial {
10055
- //* Private Uniform Nodes --
10056
- _inputBuffer;
10057
- _depthBuffer;
10058
- _resolution;
10059
- _texelSize;
10060
- _halfTexelSize;
10061
- _kernel;
10062
- _scale;
10063
- _cameraNear;
10064
- _cameraFar;
10065
- _minDepthThreshold;
10066
- _maxDepthThreshold;
10067
- _depthScale;
10068
- _depthToBlurRatioBias;
10069
- _useDepth;
10275
+ class ConvolutionMaterial extends withUniforms(MeshBasicNodeMaterial, {
10276
+ /** Input color buffer to blur */
10277
+ inputBuffer: () => uniformTexture(new THREE.Texture()),
10278
+ /** Depth buffer for depth-aware blur */
10279
+ depthBuffer: () => uniformTexture(new THREE.Texture()),
10280
+ /** Render resolution */
10281
+ resolution: () => uniform(new THREE.Vector2()),
10282
+ /** Texel size (1/resolution) */
10283
+ texelSize: () => uniform(new THREE.Vector2()),
10284
+ /** Half texel size for offset calculations */
10285
+ halfTexelSize: () => uniform(new THREE.Vector2()),
10286
+ /** Kernel offset multiplier (changes per blur pass) */
10287
+ kernelValue: () => uniform(0),
10288
+ /** Scale factor for blur spread */
10289
+ scale: () => uniform(1),
10290
+ /** Camera near plane for depth calculations */
10291
+ cameraNear: () => uniform(0),
10292
+ /** Camera far plane for depth calculations */
10293
+ cameraFar: () => uniform(1),
10294
+ /** Minimum depth threshold for blur (objects closer than this get less blur) */
10295
+ minDepthThreshold: () => uniform(0),
10296
+ /** Maximum depth threshold for blur (objects farther than this get full blur) */
10297
+ maxDepthThreshold: () => uniform(1),
10298
+ /** Scale factor for depth-based blur adjustment */
10299
+ depthScale: () => uniform(0),
10300
+ /** Bias added to depth-to-blur ratio */
10301
+ depthToBlurRatioBias: () => uniform(0.25),
10302
+ /** Enable depth-based blur */
10303
+ useDepth: () => uniform(false)
10304
+ }) {
10070
10305
  /** Kernel weights for multi-pass blur */
10071
10306
  kernel;
10072
10307
  constructor(texelSize = new THREE.Vector2()) {
10073
10308
  super();
10074
- this._inputBuffer = uniformTexture(new THREE.Texture());
10075
- this._depthBuffer = uniformTexture(new THREE.Texture());
10076
- this._resolution = uniform(new THREE.Vector2());
10077
- this._texelSize = uniform(new THREE.Vector2());
10078
- this._halfTexelSize = uniform(new THREE.Vector2());
10079
- this._kernel = uniform(0);
10080
- this._scale = uniform(1);
10081
- this._cameraNear = uniform(0);
10082
- this._cameraFar = uniform(1);
10083
- this._minDepthThreshold = uniform(0);
10084
- this._maxDepthThreshold = uniform(1);
10085
- this._depthScale = uniform(0);
10086
- this._depthToBlurRatioBias = uniform(0.25);
10087
- this._useDepth = uniform(0);
10088
10309
  this.blending = THREE.NoBlending;
10089
10310
  this.depthWrite = false;
10090
10311
  this.depthTest = false;
@@ -10094,16 +10315,19 @@ class ConvolutionMaterial extends MeshBasicNodeMaterial {
10094
10315
  this._buildShader();
10095
10316
  }
10096
10317
  _buildShader() {
10097
- const inputBufferTex = this._inputBuffer;
10098
- const depthBufferTex = this._depthBuffer;
10099
- const texelSizeUniform = this._texelSize;
10100
- const halfTexelSizeUniform = this._halfTexelSize;
10101
- const kernelUniform = this._kernel;
10102
- const scaleUniform = this._scale;
10103
- const minDepthThresholdUniform = this._minDepthThreshold;
10104
- const maxDepthThresholdUniform = this._maxDepthThreshold;
10105
- const depthScaleUniform = this._depthScale;
10106
- const useDepthUniform = this._useDepth;
10318
+ const {
10319
+ inputBuffer: inputBufferTex,
10320
+ depthBuffer: depthBufferTex,
10321
+ texelSize: texelSizeUniform,
10322
+ halfTexelSize: halfTexelSizeUniform,
10323
+ kernelValue: kernelUniform,
10324
+ scale: scaleUniform,
10325
+ minDepthThreshold: minDepthThresholdUniform,
10326
+ maxDepthThreshold: maxDepthThresholdUniform,
10327
+ depthScale: depthScaleUniform,
10328
+ depthToBlurRatioBias: depthToBlurRatioBiasUniform,
10329
+ useDepth: useDepthUniform
10330
+ } = this.uniforms;
10107
10331
  const baseUv = varying(uv(), "vUv");
10108
10332
  const dUv = varying(
10109
10333
  Fn(() => {
@@ -10117,17 +10341,16 @@ class ConvolutionMaterial extends MeshBasicNodeMaterial {
10117
10341
  const vUv3 = varying(Fn(() => vec2$2(baseUv.x.sub(dUv.x), baseUv.y.sub(dUv.y)))(), "vUv3");
10118
10342
  this.colorNode = Fn(() => {
10119
10343
  const depthFactor = float(0).toVar();
10120
- const isUsingDepth = useDepthUniform.greaterThan(0.5);
10121
- const depthSample = texture(depthBufferTex, baseUv);
10344
+ const depthSample = depthBufferTex.sample(baseUv);
10122
10345
  const rawDepth = float(1).sub(depthSample.r.mul(depthSample.a));
10123
10346
  const smoothedDepth = smoothstep(minDepthThresholdUniform, maxDepthThresholdUniform, rawDepth);
10124
10347
  const scaledDepth = smoothedDepth.mul(depthScaleUniform);
10125
- const clampedDepth = max(float(0), min(float(1), scaledDepth.add(0.25)));
10126
- depthFactor.assign(select(isUsingDepth, clampedDepth, float(0)));
10127
- const sample0 = texture(inputBufferTex, mix(vUv0, baseUv, depthFactor));
10128
- const sample1 = texture(inputBufferTex, mix(vUv1, baseUv, depthFactor));
10129
- const sample2 = texture(inputBufferTex, mix(vUv2, baseUv, depthFactor));
10130
- const sample3 = texture(inputBufferTex, mix(vUv3, baseUv, depthFactor));
10348
+ const clampedDepth = max(float(0), min(float(1), scaledDepth.add(depthToBlurRatioBiasUniform)));
10349
+ depthFactor.assign(select(useDepthUniform, clampedDepth, float(0)));
10350
+ const sample0 = inputBufferTex.sample(mix(vUv0, baseUv, depthFactor));
10351
+ const sample1 = inputBufferTex.sample(mix(vUv1, baseUv, depthFactor));
10352
+ const sample2 = inputBufferTex.sample(mix(vUv2, baseUv, depthFactor));
10353
+ const sample3 = inputBufferTex.sample(mix(vUv3, baseUv, depthFactor));
10131
10354
  const sum = sample0.add(sample1).add(sample2).add(sample3);
10132
10355
  return sum.mul(0.25);
10133
10356
  })();
@@ -10135,111 +10358,12 @@ class ConvolutionMaterial extends MeshBasicNodeMaterial {
10135
10358
  //* Utility Methods ==============================
10136
10359
  /** Set texel size for blur calculations */
10137
10360
  setTexelSize(x, y) {
10138
- this._texelSize.value.set(x, y);
10139
- this._halfTexelSize.value.set(x * 0.5, y * 0.5);
10361
+ this.uniforms.texelSize.value.set(x, y);
10362
+ this.uniforms.halfTexelSize.value.set(x * 0.5, y * 0.5);
10140
10363
  }
10141
10364
  /** Set render resolution */
10142
10365
  setResolution(resolution) {
10143
- this._resolution.value.copy(resolution);
10144
- }
10145
- //* Uniform Accessors ==============================
10146
- /** Input color buffer to blur */
10147
- get inputBuffer() {
10148
- return this._inputBuffer.value;
10149
- }
10150
- set inputBuffer(v) {
10151
- this._inputBuffer.value = v ?? new THREE.Texture();
10152
- }
10153
- /** Depth buffer for depth-aware blur */
10154
- get depthBuffer() {
10155
- return this._depthBuffer.value;
10156
- }
10157
- set depthBuffer(v) {
10158
- this._depthBuffer.value = v ?? new THREE.Texture();
10159
- }
10160
- /** Render resolution */
10161
- get resolution() {
10162
- return this._resolution.value;
10163
- }
10164
- set resolution(v) {
10165
- this._resolution.value = v;
10166
- }
10167
- /** Texel size (1/resolution) */
10168
- get texelSize() {
10169
- return this._texelSize.value;
10170
- }
10171
- set texelSize(v) {
10172
- this._texelSize.value = v;
10173
- }
10174
- /** Half texel size for offset calculations */
10175
- get halfTexelSize() {
10176
- return this._halfTexelSize.value;
10177
- }
10178
- set halfTexelSize(v) {
10179
- this._halfTexelSize.value = v;
10180
- }
10181
- /** Kernel offset multiplier (changes per blur pass) */
10182
- get kernelValue() {
10183
- return this._kernel.value;
10184
- }
10185
- set kernelValue(v) {
10186
- this._kernel.value = v;
10187
- }
10188
- /** Scale factor for blur spread */
10189
- get scale() {
10190
- return this._scale.value;
10191
- }
10192
- set scale(v) {
10193
- this._scale.value = v;
10194
- }
10195
- /** Camera near plane for depth calculations */
10196
- get cameraNear() {
10197
- return this._cameraNear.value;
10198
- }
10199
- set cameraNear(v) {
10200
- this._cameraNear.value = v;
10201
- }
10202
- /** Camera far plane for depth calculations */
10203
- get cameraFar() {
10204
- return this._cameraFar.value;
10205
- }
10206
- set cameraFar(v) {
10207
- this._cameraFar.value = v;
10208
- }
10209
- /** Minimum depth threshold for blur (objects closer than this get less blur) */
10210
- get minDepthThreshold() {
10211
- return this._minDepthThreshold.value;
10212
- }
10213
- set minDepthThreshold(v) {
10214
- this._minDepthThreshold.value = v;
10215
- }
10216
- /** Maximum depth threshold for blur (objects farther than this get full blur) */
10217
- get maxDepthThreshold() {
10218
- return this._maxDepthThreshold.value;
10219
- }
10220
- set maxDepthThreshold(v) {
10221
- this._maxDepthThreshold.value = v;
10222
- }
10223
- /** Scale factor for depth-based blur adjustment */
10224
- get depthScale() {
10225
- return this._depthScale.value;
10226
- }
10227
- set depthScale(v) {
10228
- this._depthScale.value = v;
10229
- }
10230
- /** Bias added to depth-to-blur ratio */
10231
- get depthToBlurRatioBias() {
10232
- return this._depthToBlurRatioBias.value;
10233
- }
10234
- set depthToBlurRatioBias(v) {
10235
- this._depthToBlurRatioBias.value = v;
10236
- }
10237
- /** Enable/disable depth-based blur (0 = off, 1 = on) */
10238
- get useDepth() {
10239
- return this._useDepth.value > 0.5;
10240
- }
10241
- set useDepth(v) {
10242
- this._useDepth.value = v ? 1 : 0;
10366
+ this.uniforms.resolution.value.copy(resolution);
10243
10367
  }
10244
10368
  }
10245
10369
 
@@ -10428,7 +10552,7 @@ const ImageWithUrl = /* @__PURE__ */ React.forwardRef(
10428
10552
  return /* @__PURE__ */ React.createElement(ImageBase, { ...props, texture, ref });
10429
10553
  }
10430
10554
  );
10431
- const ImageWithTexture = /* @__PURE__ */ React.forwardRef(({ url: _url, ...props }, ref) => {
10555
+ const ImageWithTexture = /* @__PURE__ */ React.forwardRef(({ ...props }, ref) => {
10432
10556
  return /* @__PURE__ */ React.createElement(ImageBase, { ...props, ref });
10433
10557
  });
10434
10558
  const Image = /* @__PURE__ */ React.forwardRef((props, ref) => {
@@ -10437,28 +10561,26 @@ const Image = /* @__PURE__ */ React.forwardRef((props, ref) => {
10437
10561
  else throw new Error("<Image /> requires a url or texture");
10438
10562
  });
10439
10563
 
10440
- class SpotLightMaterial extends MeshBasicNodeMaterial {
10441
- //* Private Uniform Nodes --
10442
- _depth;
10443
- _opacity;
10444
- _attenuation;
10445
- _anglePower;
10446
- _spotPosition;
10447
- _lightColor;
10448
- _cameraNear;
10449
- _cameraFar;
10450
- _resolution;
10564
+ class SpotLightMaterial extends withUniforms(MeshBasicNodeMaterial, {
10565
+ /** Scene depth (in the R channel) for soft intersection with scene geometry */
10566
+ depth: () => uniformTexture(new THREE.Texture()),
10567
+ /** Distance attenuation factor, default: 5 */
10568
+ attenuation: () => uniform(5),
10569
+ /** Angle falloff power - higher = sharper edges, default: 5 */
10570
+ anglePower: () => uniform(5),
10571
+ /** World position of the cone tip */
10572
+ spotPosition: () => uniform(new THREE.Vector3(0, 0, 0)),
10573
+ /** Light color, default: white */
10574
+ lightColor: () => uniform(new THREE.Color("white")),
10575
+ /** Camera near plane, for depth reconstruction */
10576
+ cameraNear: () => uniform(0.1),
10577
+ /** Camera far plane, for depth reconstruction */
10578
+ cameraFar: () => uniform(100),
10579
+ /** Drawing-buffer size in pixels. Zero disables depth-based soft edges. */
10580
+ resolution: () => uniform(new THREE.Vector2(0, 0))
10581
+ }) {
10451
10582
  constructor() {
10452
10583
  super();
10453
- this._depth = uniformTexture(new THREE.Texture());
10454
- this._opacity = uniform(1);
10455
- this._attenuation = uniform(5);
10456
- this._anglePower = uniform(5);
10457
- this._spotPosition = uniform(new THREE.Vector3(0, 0, 0));
10458
- this._lightColor = uniform(new THREE.Color("white"));
10459
- this._cameraNear = uniform(0.1);
10460
- this._cameraFar = uniform(100);
10461
- this._resolution = uniform(new THREE.Vector2(0, 0));
10462
10584
  this.transparent = true;
10463
10585
  this.depthWrite = false;
10464
10586
  this.side = THREE.DoubleSide;
@@ -10466,15 +10588,16 @@ class SpotLightMaterial extends MeshBasicNodeMaterial {
10466
10588
  }
10467
10589
  //* Build TSL Shader ==============================
10468
10590
  _buildShader() {
10469
- const depthTex = this._depth;
10470
- const opacityUniform = this._opacity;
10471
- const attenuationUniform = this._attenuation;
10472
- const anglePowerUniform = this._anglePower;
10473
- const spotPositionUniform = this._spotPosition;
10474
- const lightColorUniform = this._lightColor;
10475
- const cameraNearUniform = this._cameraNear;
10476
- const cameraFarUniform = this._cameraFar;
10477
- const resolutionUniform = this._resolution;
10591
+ const {
10592
+ depth: depthTex,
10593
+ attenuation: attenuationUniform,
10594
+ anglePower: anglePowerUniform,
10595
+ spotPosition: spotPositionUniform,
10596
+ lightColor: lightColorUniform,
10597
+ cameraNear: cameraNearUniform,
10598
+ cameraFar: cameraFarUniform,
10599
+ resolution: resolutionUniform
10600
+ } = this.uniforms;
10478
10601
  const vNormal = varying(normalize(modelViewMatrix.transformDirection(normalLocal)), "vNormal");
10479
10602
  const vIntensity = varying(
10480
10603
  saturate(float(1).sub(distance(positionWorld, spotPositionUniform).div(attenuationUniform))),
@@ -10489,79 +10612,27 @@ class SpotLightMaterial extends MeshBasicNodeMaterial {
10489
10612
  screenCoordinate.x.div(resolutionUniform.x),
10490
10613
  float(1).sub(screenCoordinate.y.div(resolutionUniform.y))
10491
10614
  );
10492
- const sampledDepth = texture(depthTex, screenUV).r;
10615
+ const sampledDepth = depthTex.sample(screenUV).r;
10493
10616
  const near = cameraNearUniform;
10494
10617
  const far = cameraFarUniform;
10495
10618
  const sceneViewZ = near.mul(far).div(far.sub(near).mul(sampledDepth).sub(far));
10496
10619
  const depthDiff = vViewZ.sub(sceneViewZ);
10497
10620
  const softFactor = smoothstep(float(0), float(1), depthDiff);
10498
10621
  intensity.assign(select(hasDepth, intensity.mul(softFactor), intensity));
10499
- return vec4(lightColorUniform, intensity.mul(opacityUniform));
10622
+ return vec4(lightColorUniform, intensity.mul(materialOpacity));
10500
10623
  })();
10501
10624
  }
10502
- //* Uniform Accessors ==============================
10503
- get depth() {
10504
- return this._depth?.value;
10505
- }
10506
- set depth(v) {
10507
- if (this._depth) this._depth.value = v ?? new THREE.Texture();
10508
- }
10509
- get opacity() {
10510
- return this._opacity?.value;
10511
- }
10512
- set opacity(v) {
10513
- if (this._opacity) this._opacity.value = v;
10514
- }
10515
- get attenuation() {
10516
- return this._attenuation?.value;
10517
- }
10518
- set attenuation(v) {
10519
- if (this._attenuation) this._attenuation.value = v;
10520
- }
10521
- get anglePower() {
10522
- return this._anglePower?.value;
10523
- }
10524
- set anglePower(v) {
10525
- if (this._anglePower) this._anglePower.value = v;
10526
- }
10527
- get spotPosition() {
10528
- return this._spotPosition?.value;
10529
- }
10530
- set spotPosition(v) {
10531
- if (this._spotPosition) this._spotPosition.value = v;
10532
- }
10533
- get lightColor() {
10534
- return this._lightColor?.value;
10535
- }
10536
- set lightColor(v) {
10537
- if (this._lightColor) this._lightColor.value = v;
10538
- }
10539
- get cameraNear() {
10540
- return this._cameraNear?.value;
10541
- }
10542
- set cameraNear(v) {
10543
- if (this._cameraNear) this._cameraNear.value = v;
10544
- }
10545
- get cameraFar() {
10546
- return this._cameraFar?.value;
10547
- }
10548
- set cameraFar(v) {
10549
- if (this._cameraFar) this._cameraFar.value = v;
10550
- }
10551
- get resolution() {
10552
- return this._resolution?.value;
10553
- }
10554
- set resolution(v) {
10555
- if (this._resolution) this._resolution.value = v;
10556
- }
10557
10625
  }
10558
10626
 
10559
- class StarfieldMaterial extends SpriteNodeMaterial {
10560
- //* Uniforms ==============================
10561
- _time = uniform(0);
10562
- _fade = uniform(1);
10627
+ class StarfieldMaterial extends withUniforms(SpriteNodeMaterial, {
10628
+ /** Animation time, advanced by the component each frame */
10629
+ time: () => uniform(0),
10630
+ /** 1 = soft circular fade, 0 = hard-edged circle */
10631
+ fade: () => uniform(1)
10632
+ }) {
10563
10633
  constructor() {
10564
10634
  super();
10635
+ const { time, fade } = this.uniforms;
10565
10636
  this.transparent = true;
10566
10637
  this.depthWrite = false;
10567
10638
  this.blending = AdditiveBlending;
@@ -10575,7 +10646,7 @@ class StarfieldMaterial extends SpriteNodeMaterial {
10575
10646
  const worldPos = modelWorldMatrix.mul(vec4(this.positionNode, 1));
10576
10647
  const viewPos = cameraViewMatrix.mul(worldPos);
10577
10648
  const distanceAttenuation = float(30).div(viewPos.z.negate());
10578
- const timeScale = float(3.5).add(sin(this._time.add(100)).mul(0.3));
10649
+ const timeScale = float(3.5).add(sin(time.add(100)).mul(0.3));
10579
10650
  const size = particleSize.mul(distanceAttenuation).mul(timeScale).mul(0.04);
10580
10651
  return vec2$2(size);
10581
10652
  })();
@@ -10584,38 +10655,28 @@ class StarfieldMaterial extends SpriteNodeMaterial {
10584
10655
  const d = length(quadUV.sub(vec2$2(0.5)));
10585
10656
  const softFade = float(1).div(float(1).add(exp(float(16).mul(d.sub(0.25)))));
10586
10657
  const hardMask = float(1).sub(d.smoothstep(0.3, 0.5));
10587
- const circularOpacity = select(this._fade.equal(1), softFade, hardMask);
10658
+ const circularOpacity = select(fade.equal(1), softFade, hardMask);
10588
10659
  const opacity = circularOpacity.mul(0.7);
10589
10660
  return vec4(particleColor, opacity);
10590
10661
  })();
10591
10662
  }
10592
- //* Uniform Accessors ==============================
10593
10663
  setTime(time) {
10594
- this._time.value = time;
10664
+ this.time = time;
10595
10665
  }
10596
10666
  setFade(fade) {
10597
- this._fade.value = fade;
10598
- }
10599
- get time() {
10600
- return this._time.value;
10601
- }
10602
- set time(value) {
10603
- this._time.value = value;
10604
- }
10605
- get fade() {
10606
- return this._fade.value;
10607
- }
10608
- set fade(value) {
10609
- this._fade.value = value;
10667
+ this.fade = fade;
10610
10668
  }
10611
10669
  }
10612
10670
 
10613
- class SparklesMaterial extends SpriteNodeMaterial {
10614
- // Uniforms exposed for external updates
10615
- _time = uniform(0);
10616
- _pixelRatio = uniform(1);
10671
+ class SparklesMaterial extends withUniforms(SpriteNodeMaterial, {
10672
+ /** Animation time, advanced by the component each frame */
10673
+ time: () => uniform(0),
10674
+ /** Device pixel ratio, scales the point size */
10675
+ pixelRatio: () => uniform(1)
10676
+ }) {
10617
10677
  constructor() {
10618
10678
  super();
10679
+ const { time, pixelRatio } = this.uniforms;
10619
10680
  this.transparent = true;
10620
10681
  this.depthWrite = false;
10621
10682
  const particlePosition = attribute("particlePosition", "vec3");
@@ -10626,101 +10687,86 @@ class SparklesMaterial extends SpriteNodeMaterial {
10626
10687
  const particleColor = attribute("color", "vec3");
10627
10688
  this.positionNode = Fn(() => {
10628
10689
  const pos = particlePosition.toVar();
10629
- pos.y.addAssign(sin(this._time.mul(particleSpeed).add(pos.x.mul(particleNoise.x).mul(100))).mul(0.2));
10630
- pos.z.addAssign(cos(this._time.mul(particleSpeed).add(pos.x.mul(particleNoise.y).mul(100))).mul(0.2));
10631
- pos.x.addAssign(cos(this._time.mul(particleSpeed).add(pos.x.mul(particleNoise.z).mul(100))).mul(0.2));
10690
+ pos.y.addAssign(sin(time.mul(particleSpeed).add(pos.x.mul(particleNoise.x).mul(100))).mul(0.2));
10691
+ pos.z.addAssign(cos(time.mul(particleSpeed).add(pos.x.mul(particleNoise.y).mul(100))).mul(0.2));
10692
+ pos.x.addAssign(cos(time.mul(particleSpeed).add(pos.x.mul(particleNoise.z).mul(100))).mul(0.2));
10632
10693
  return pos;
10633
10694
  })();
10634
10695
  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);
10696
+ const pixelSize = particleSize.mul(25).mul(pixelRatio);
10697
+ const projY = cameraProjectionMatrix.element(1).y;
10698
+ const viewportH = screenSize.y;
10699
+ const size = pixelSize.mul(2).div(projY.mul(viewportH));
10639
10700
  return vec2$2(size);
10640
10701
  })();
10641
10702
  this.colorNode = Fn(() => {
10642
10703
  const quadUV = uv();
10643
10704
  const distanceToCenter = length(quadUV.sub(vec2$2(0.5)));
10644
- const strength = float(0.05).div(distanceToCenter).sub(0.1);
10705
+ const strength = clamp(float(0.05).div(distanceToCenter).sub(0.1), 0, 1);
10645
10706
  return vec4(particleColor, strength.mul(particleOpacity));
10646
10707
  })();
10647
10708
  }
10648
- //* Uniform Accessors --------------------------------
10649
- get time() {
10650
- return this._time.value;
10651
- }
10652
- set time(value) {
10653
- this._time.value = value;
10654
- }
10655
- get pixelRatio() {
10656
- return this._pixelRatio.value;
10657
- }
10658
- set pixelRatio(value) {
10659
- this._pixelRatio.value = value;
10660
- }
10661
- }
10662
-
10663
- class GridMaterialImpl extends MeshBasicNodeMaterial {
10664
- //* Private Uniform Nodes --
10665
- _cellSize;
10666
- _sectionSize;
10667
- _fadeDistance;
10668
- _fadeStrength;
10669
- _fadeFrom;
10670
- _cellThickness;
10671
- _sectionThickness;
10672
- _cellColor;
10673
- _sectionColor;
10674
- _infiniteGrid;
10675
- // bool as 0/1
10676
- _followCamera;
10677
- // bool as 0/1
10678
- _worldCamProjPosition;
10679
- _worldPlanePosition;
10709
+ }
10710
+
10711
+ class GridMaterialImpl extends withUniforms(MeshBasicNodeMaterial, {
10712
+ /** Cell size, default: 0.5 */
10713
+ cellSize: () => uniform(0.5),
10714
+ /** Section size, default: 1 */
10715
+ sectionSize: () => uniform(1),
10716
+ /** Fade distance, default: 100 */
10717
+ fadeDistance: () => uniform(100),
10718
+ /** Fade strength, default: 1 */
10719
+ fadeStrength: () => uniform(1),
10720
+ /** Fade from camera (1) or origin (0), default: 1 */
10721
+ fadeFrom: () => uniform(1),
10722
+ /** Cell thickness, default: 0.5 */
10723
+ cellThickness: () => uniform(0.5),
10724
+ /** Section thickness, default: 1 */
10725
+ sectionThickness: () => uniform(1),
10726
+ /** Cell color, default: black */
10727
+ cellColor: () => uniform(new THREE.Color("#000000")),
10728
+ /** Section color, default: #2080ff */
10729
+ sectionColor: () => uniform(new THREE.Color("#2080ff")),
10730
+ /** Display the grid infinitely */
10731
+ infiniteGrid: () => uniform(false),
10732
+ /** Follow camera position */
10733
+ followCamera: () => uniform(false),
10734
+ /** Camera projection position on plane (set by component) */
10735
+ worldCamProjPosition: () => uniform(new THREE.Vector3()),
10736
+ /** Plane world position (set by component) */
10737
+ worldPlanePosition: () => uniform(new THREE.Vector3())
10738
+ }) {
10680
10739
  /** Type flag for identification */
10681
10740
  isGridMaterial = true;
10682
10741
  constructor() {
10683
10742
  super();
10684
- this._cellSize = uniform(0.5);
10685
- this._sectionSize = uniform(1);
10686
- this._fadeDistance = uniform(100);
10687
- this._fadeStrength = uniform(1);
10688
- this._fadeFrom = uniform(1);
10689
- this._cellThickness = uniform(0.5);
10690
- this._sectionThickness = uniform(1);
10691
- this._cellColor = uniform(new THREE.Color("#000000"));
10692
- this._sectionColor = uniform(new THREE.Color("#2080ff"));
10693
- this._infiniteGrid = uniform(0);
10694
- this._followCamera = uniform(0);
10695
- this._worldCamProjPosition = uniform(new THREE.Vector3());
10696
- this._worldPlanePosition = uniform(new THREE.Vector3());
10697
10743
  this.transparent = true;
10698
10744
  this.side = THREE.BackSide;
10699
10745
  this._buildGridShader();
10700
10746
  }
10701
10747
  _buildGridShader() {
10702
- const cellSizeUniform = this._cellSize;
10703
- const sectionSizeUniform = this._sectionSize;
10704
- const fadeDistanceUniform = this._fadeDistance;
10705
- const fadeStrengthUniform = this._fadeStrength;
10706
- const fadeFromUniform = this._fadeFrom;
10707
- const cellThicknessUniform = this._cellThickness;
10708
- const sectionThicknessUniform = this._sectionThickness;
10709
- const cellColorUniform = this._cellColor;
10710
- const sectionColorUniform = this._sectionColor;
10711
- const infiniteGridUniform = this._infiniteGrid;
10712
- const followCameraUniform = this._followCamera;
10713
- const worldCamProjPositionUniform = this._worldCamProjPosition;
10714
- const worldPlanePositionUniform = this._worldPlanePosition;
10748
+ const {
10749
+ cellSize: cellSizeUniform,
10750
+ sectionSize: sectionSizeUniform,
10751
+ fadeDistance: fadeDistanceUniform,
10752
+ fadeStrength: fadeStrengthUniform,
10753
+ fadeFrom: fadeFromUniform,
10754
+ cellThickness: cellThicknessUniform,
10755
+ sectionThickness: sectionThicknessUniform,
10756
+ cellColor: cellColorUniform,
10757
+ sectionColor: sectionColorUniform,
10758
+ infiniteGrid: infiniteGridUniform,
10759
+ followCamera: followCameraUniform,
10760
+ worldCamProjPosition: worldCamProjPositionUniform,
10761
+ worldPlanePosition: worldPlanePositionUniform
10762
+ } = this.uniforms;
10715
10763
  const computeLocalPosition = Fn(() => {
10716
10764
  const pos = positionGeometry;
10717
10765
  let localPos = vec3(pos.x, pos.z, pos.y).toVar();
10718
- const isInfinite = infiniteGridUniform.greaterThan(0.5);
10719
10766
  const scaleFactor = float(1).add(fadeDistanceUniform);
10720
- localPos.assign(select(isInfinite, localPos.mul(scaleFactor), localPos));
10721
- const isFollowCamera = followCameraUniform.greaterThan(0.5);
10767
+ localPos.assign(select(infiniteGridUniform, localPos.mul(scaleFactor), localPos));
10722
10768
  const cameraOffset = worldCamProjPositionUniform.sub(worldPlanePositionUniform);
10723
- localPos.assign(select(isFollowCamera, localPos.add(cameraOffset), localPos));
10769
+ localPos.assign(select(followCameraUniform, localPos.add(cameraOffset), localPos));
10724
10770
  return localPos;
10725
10771
  });
10726
10772
  const vLocalPosition = varying(computeLocalPosition(), "vLocalPosition");
@@ -10732,12 +10778,10 @@ class GridMaterialImpl extends MeshBasicNodeMaterial {
10732
10778
  this.positionNode = Fn(() => {
10733
10779
  const pos = positionGeometry;
10734
10780
  let localPos = vec3(pos.x, pos.z, pos.y).toVar();
10735
- const isInfinite = infiniteGridUniform.greaterThan(0.5);
10736
10781
  const scaleFactor = float(1).add(fadeDistanceUniform);
10737
- localPos.assign(select(isInfinite, localPos.mul(scaleFactor), localPos));
10738
- const isFollowCamera = followCameraUniform.greaterThan(0.5);
10782
+ localPos.assign(select(infiniteGridUniform, localPos.mul(scaleFactor), localPos));
10739
10783
  const cameraOffset = worldCamProjPositionUniform.sub(worldPlanePositionUniform);
10740
- localPos.assign(select(isFollowCamera, localPos.add(cameraOffset), localPos));
10784
+ localPos.assign(select(followCameraUniform, localPos.add(cameraOffset), localPos));
10741
10785
  return localPos;
10742
10786
  })();
10743
10787
  const getGrid = Fn((inputs) => {
@@ -10763,100 +10807,6 @@ class GridMaterialImpl extends MeshBasicNodeMaterial {
10763
10807
  return vec4(gridColor, finalAlpha);
10764
10808
  })();
10765
10809
  }
10766
- //* Uniform Accessors ==============================
10767
- /** Cell size, default: 0.5 */
10768
- get cellSize() {
10769
- return this._cellSize.value;
10770
- }
10771
- set cellSize(v) {
10772
- this._cellSize.value = v;
10773
- }
10774
- /** Section size, default: 1 */
10775
- get sectionSize() {
10776
- return this._sectionSize.value;
10777
- }
10778
- set sectionSize(v) {
10779
- this._sectionSize.value = v;
10780
- }
10781
- /** Fade distance, default: 100 */
10782
- get fadeDistance() {
10783
- return this._fadeDistance.value;
10784
- }
10785
- set fadeDistance(v) {
10786
- this._fadeDistance.value = v;
10787
- }
10788
- /** Fade strength, default: 1 */
10789
- get fadeStrength() {
10790
- return this._fadeStrength.value;
10791
- }
10792
- set fadeStrength(v) {
10793
- this._fadeStrength.value = v;
10794
- }
10795
- /** Fade from camera (1) or origin (0), default: 1 */
10796
- get fadeFrom() {
10797
- return this._fadeFrom.value;
10798
- }
10799
- set fadeFrom(v) {
10800
- this._fadeFrom.value = v;
10801
- }
10802
- /** Cell thickness, default: 0.5 */
10803
- get cellThickness() {
10804
- return this._cellThickness.value;
10805
- }
10806
- set cellThickness(v) {
10807
- this._cellThickness.value = v;
10808
- }
10809
- /** Section thickness, default: 1 */
10810
- get sectionThickness() {
10811
- return this._sectionThickness.value;
10812
- }
10813
- set sectionThickness(v) {
10814
- this._sectionThickness.value = v;
10815
- }
10816
- /** Cell color */
10817
- get cellColor() {
10818
- return this._cellColor.value;
10819
- }
10820
- set cellColor(v) {
10821
- if (v instanceof THREE.Color) this._cellColor.value = v;
10822
- else this._cellColor.value = new THREE.Color(v);
10823
- }
10824
- /** Section color */
10825
- get sectionColor() {
10826
- return this._sectionColor.value;
10827
- }
10828
- set sectionColor(v) {
10829
- if (v instanceof THREE.Color) this._sectionColor.value = v;
10830
- else this._sectionColor.value = new THREE.Color(v);
10831
- }
10832
- /** Display the grid infinitely */
10833
- get infiniteGrid() {
10834
- return this._infiniteGrid.value > 0.5;
10835
- }
10836
- set infiniteGrid(v) {
10837
- this._infiniteGrid.value = v ? 1 : 0;
10838
- }
10839
- /** Follow camera position */
10840
- get followCamera() {
10841
- return this._followCamera.value > 0.5;
10842
- }
10843
- set followCamera(v) {
10844
- this._followCamera.value = v ? 1 : 0;
10845
- }
10846
- /** Camera projection position on plane (set by component) */
10847
- get worldCamProjPosition() {
10848
- return this._worldCamProjPosition.value;
10849
- }
10850
- set worldCamProjPosition(v) {
10851
- this._worldCamProjPosition.value = v;
10852
- }
10853
- /** Plane world position (set by component) */
10854
- get worldPlanePosition() {
10855
- return this._worldPlanePosition.value;
10856
- }
10857
- set worldPlanePosition(v) {
10858
- this._worldPlanePosition.value = v;
10859
- }
10860
10810
  }
10861
10811
  const Grid = /* @__PURE__ */ React.forwardRef(
10862
10812
  ({
@@ -10912,17 +10862,95 @@ const Grid = /* @__PURE__ */ React.forwardRef(
10912
10862
  }
10913
10863
  );
10914
10864
 
10915
- let hasWarned = false;
10916
- function warnOnce(message) {
10917
- if (!hasWarned) {
10918
- console.warn(message);
10919
- hasWarned = true;
10865
+ const createPCSSFilter = (size, samples, focus) => {
10866
+ const sampleCount = Math.min(Math.max(samples, 1), 20);
10867
+ return Fn(({ depthTexture, shadowCoord, shadow, depthLayer }) => {
10868
+ const sampleDepth = (uv2) => {
10869
+ const tex = texture(depthTexture, uv2);
10870
+ const depth = depthTexture.isArrayTexture ? tex.depth(depthLayer) : tex;
10871
+ return depth.r;
10872
+ };
10873
+ const mapSize = reference("mapSize", "vec2", shadow).setGroup(renderGroup);
10874
+ const texelSize = vec2$2(1).div(mapSize);
10875
+ const phi = interleavedGradientNoise(screenCoordinate.xy).mul(6.28318530718);
10876
+ const uv = shadowCoord.xy;
10877
+ const zReceiver = shadowCoord.z.add(1).mul(0.5);
10878
+ const blockerSearchRadius = texelSize.x.mul(size).mul(2);
10879
+ const blockerDepthSum = float(focus).toVar("blockerDepthSum");
10880
+ const blockerCount = float(0).toVar("blockerCount");
10881
+ for (let i = 0; i < sampleCount; i++) {
10882
+ const offset = vogelDiskSample(int(i), int(sampleCount), phi).mul(blockerSearchRadius);
10883
+ const sampleUV = uv.add(offset);
10884
+ const sampleDepthVal = sampleDepth(sampleUV);
10885
+ const isBlocker = step(sampleDepthVal, zReceiver).sub(step(zReceiver, sampleDepthVal)).max(0);
10886
+ blockerDepthSum.addAssign(sampleDepthVal.mul(isBlocker));
10887
+ blockerCount.addAssign(isBlocker);
10888
+ }
10889
+ const avgBlockerDepth = blockerDepthSum.div(blockerCount.max(1));
10890
+ const penumbraRatio = zReceiver.sub(avgBlockerDepth).div(avgBlockerDepth.max(1e-4));
10891
+ const filterRadius = texelSize.x.mul(float(1).add(penumbraRatio.mul(1.25).mul(size)));
10892
+ const shadowSum = float(0).toVar("shadowSum");
10893
+ for (let i = 0; i < sampleCount; i++) {
10894
+ const offset = vogelDiskSample(int(i), int(sampleCount), phi).mul(filterRadius);
10895
+ const sampleUV = uv.add(offset);
10896
+ const sampleDepthVal = sampleDepth(sampleUV);
10897
+ shadowSum.addAssign(step(zReceiver, sampleDepthVal));
10898
+ }
10899
+ const shadowResult = shadowSum.div(sampleCount);
10900
+ const hasBlockers = blockerCount.greaterThan(0.5);
10901
+ return hasBlockers.select(shadowResult, float(1));
10902
+ });
10903
+ };
10904
+ class PCSSShadowNode extends ShadowNode {
10905
+ size;
10906
+ samples;
10907
+ focus;
10908
+ constructor(light, { size = 25, samples = 10, focus = 0 } = {}) {
10909
+ super(light, light.shadow);
10910
+ this.size = size;
10911
+ this.samples = samples;
10912
+ this.focus = focus;
10913
+ }
10914
+ /**
10915
+ * Override to disable hardware depth comparison.
10916
+ * PCSS needs raw depth values for blocker search, so we can't use comparison samplers.
10917
+ */
10918
+ setupRenderTarget(shadow, builder) {
10919
+ const parentMethod = Object.getPrototypeOf(Object.getPrototypeOf(this)).setupRenderTarget;
10920
+ const result = parentMethod.call(this, shadow, builder);
10921
+ if (result.depthTexture) {
10922
+ result.depthTexture.compareFunction = null;
10923
+ }
10924
+ return result;
10925
+ }
10926
+ /**
10927
+ * Override to use our PCSS filter instead of the default filter.
10928
+ */
10929
+ getShadowFilterFn(_type) {
10930
+ return createPCSSFilter(this.size, this.samples, this.focus);
10920
10931
  }
10921
10932
  }
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
- );
10933
+
10934
+ function SoftShadows({ size = 25, samples = 10, focus = 0 }) {
10935
+ const scene = useThree((state) => state.scene);
10936
+ React.useLayoutEffect(() => {
10937
+ const originalShadowNodes = /* @__PURE__ */ new Map();
10938
+ scene.traverse((obj) => {
10939
+ const light = obj;
10940
+ if (light.isLight && light.castShadow && light.shadow) {
10941
+ originalShadowNodes.set(light.uuid, light.shadow.shadowNode);
10942
+ light.shadow.shadowNode = new PCSSShadowNode(light, { size, samples, focus });
10943
+ }
10944
+ });
10945
+ return () => {
10946
+ scene.traverse((obj) => {
10947
+ const light = obj;
10948
+ if (originalShadowNodes.has(light.uuid)) {
10949
+ light.shadow.shadowNode = originalShadowNodes.get(light.uuid);
10950
+ }
10951
+ });
10952
+ };
10953
+ }, [scene, size, samples, focus]);
10926
10954
  return null;
10927
10955
  }
10928
10956