@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.b0fafc8

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.
@@ -1,11 +1,17 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
2
+ import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, Vector4, PostProcessing } from 'three/webgpu';
3
3
  import { Inspector } from 'three/addons/inspector/Inspector.js';
4
- import { jsx, Fragment } from 'react/jsx-runtime';
4
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
  import * as React from 'react';
6
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
6
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
7
7
  import useMeasure from 'react-use-measure';
8
8
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
9
+ import { useThree as useThree$1, useLoader as useLoader$1, useFrame as useFrame$1, createPortal as createPortal$1, applyProps as applyProps$1, extend as extend$1 } from '@react-three/fiber';
10
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
11
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
12
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
13
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
14
+ import { GainMapLoader } from '@monogrid/gainmap-js';
9
15
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
10
16
  import { createWithEqualityFn } from 'zustand/traditional';
11
17
  import { suspend, preload, clear } from 'suspend-react';
@@ -46,6 +52,374 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
46
52
  WebGLRenderer: WebGLRenderer
47
53
  }, [webgpu]);
48
54
 
55
+ const primaryRegistry = /* @__PURE__ */ new Map();
56
+ const pendingSubscribers = /* @__PURE__ */ new Map();
57
+ function registerPrimary(id, renderer, store) {
58
+ if (primaryRegistry.has(id)) {
59
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
60
+ }
61
+ const entry = { renderer, store };
62
+ primaryRegistry.set(id, entry);
63
+ const subscribers = pendingSubscribers.get(id);
64
+ if (subscribers) {
65
+ subscribers.forEach((callback) => callback(entry));
66
+ pendingSubscribers.delete(id);
67
+ }
68
+ return () => {
69
+ const currentEntry = primaryRegistry.get(id);
70
+ if (currentEntry?.renderer === renderer) {
71
+ primaryRegistry.delete(id);
72
+ }
73
+ };
74
+ }
75
+ function getPrimary(id) {
76
+ return primaryRegistry.get(id);
77
+ }
78
+ function waitForPrimary(id, timeout = 5e3) {
79
+ const existing = primaryRegistry.get(id);
80
+ if (existing) {
81
+ return Promise.resolve(existing);
82
+ }
83
+ return new Promise((resolve, reject) => {
84
+ const timeoutId = setTimeout(() => {
85
+ const subscribers = pendingSubscribers.get(id);
86
+ if (subscribers) {
87
+ const index = subscribers.indexOf(callback);
88
+ if (index !== -1) subscribers.splice(index, 1);
89
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
90
+ }
91
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
92
+ }, timeout);
93
+ const callback = (entry) => {
94
+ clearTimeout(timeoutId);
95
+ resolve(entry);
96
+ };
97
+ if (!pendingSubscribers.has(id)) {
98
+ pendingSubscribers.set(id, []);
99
+ }
100
+ pendingSubscribers.get(id).push(callback);
101
+ });
102
+ }
103
+ function hasPrimary(id) {
104
+ return primaryRegistry.has(id);
105
+ }
106
+ function unregisterPrimary(id) {
107
+ primaryRegistry.delete(id);
108
+ }
109
+ function getPrimaryIds() {
110
+ return Array.from(primaryRegistry.keys());
111
+ }
112
+
113
+ const presetsObj = {
114
+ apartment: "lebombo_1k.hdr",
115
+ city: "potsdamer_platz_1k.hdr",
116
+ dawn: "kiara_1_dawn_1k.hdr",
117
+ forest: "forest_slope_1k.hdr",
118
+ lobby: "st_fagans_interior_1k.hdr",
119
+ night: "dikhololo_night_1k.hdr",
120
+ park: "rooitou_park_1k.hdr",
121
+ studio: "studio_small_03_1k.hdr",
122
+ sunset: "venice_sunset_1k.hdr",
123
+ warehouse: "empty_warehouse_01_1k.hdr"
124
+ };
125
+
126
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
127
+ const isArray = (arr) => Array.isArray(arr);
128
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
129
+ function useEnvironment({
130
+ files = defaultFiles,
131
+ path = "",
132
+ preset = void 0,
133
+ colorSpace = void 0,
134
+ extensions
135
+ } = {}) {
136
+ if (preset) {
137
+ validatePreset(preset);
138
+ files = presetsObj[preset];
139
+ path = CUBEMAP_ROOT;
140
+ }
141
+ const multiFile = isArray(files);
142
+ const { extension, isCubemap } = getExtension(files);
143
+ const loader = getLoader$1(extension);
144
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
145
+ const renderer = useThree$1((state) => state.renderer);
146
+ useLayoutEffect(() => {
147
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
148
+ function clearGainmapTexture() {
149
+ useLoader$1.clear(loader, multiFile ? [files] : files);
150
+ }
151
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
152
+ }, [files, renderer.domElement]);
153
+ const loaderResult = useLoader$1(
154
+ loader,
155
+ multiFile ? [files] : files,
156
+ (loader2) => {
157
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
158
+ loader2.setRenderer?.(renderer);
159
+ }
160
+ loader2.setPath?.(path);
161
+ if (extensions) extensions(loader2);
162
+ }
163
+ );
164
+ let texture = multiFile ? (
165
+ // @ts-ignore
166
+ loaderResult[0]
167
+ ) : loaderResult;
168
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
169
+ texture = texture.renderTarget?.texture;
170
+ }
171
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
172
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
173
+ return texture;
174
+ }
175
+ const preloadDefaultOptions = {
176
+ files: defaultFiles,
177
+ path: "",
178
+ preset: void 0,
179
+ extensions: void 0
180
+ };
181
+ useEnvironment.preload = (preloadOptions) => {
182
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
183
+ let { files, path = "" } = options;
184
+ const { preset, extensions } = options;
185
+ if (preset) {
186
+ validatePreset(preset);
187
+ files = presetsObj[preset];
188
+ path = CUBEMAP_ROOT;
189
+ }
190
+ const { extension } = getExtension(files);
191
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
192
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
193
+ }
194
+ const loader = getLoader$1(extension);
195
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
196
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
197
+ loader2.setPath?.(path);
198
+ if (extensions) extensions(loader2);
199
+ });
200
+ };
201
+ const clearDefaultOptins = {
202
+ files: defaultFiles,
203
+ preset: void 0
204
+ };
205
+ useEnvironment.clear = (clearOptions) => {
206
+ const options = { ...clearDefaultOptins, ...clearOptions };
207
+ let { files } = options;
208
+ const { preset } = options;
209
+ if (preset) {
210
+ validatePreset(preset);
211
+ files = presetsObj[preset];
212
+ }
213
+ const { extension } = getExtension(files);
214
+ const loader = getLoader$1(extension);
215
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
216
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
217
+ };
218
+ function validatePreset(preset) {
219
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
220
+ }
221
+ function getExtension(files) {
222
+ const isCubemap = isArray(files) && files.length === 6;
223
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
224
+ const firstEntry = isArray(files) ? files[0] : files;
225
+ const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
226
+ return { extension, isCubemap, isGainmap };
227
+ }
228
+ function getLoader$1(extension) {
229
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
230
+ return loader;
231
+ }
232
+
233
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
234
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
235
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
236
+ sceneProps = {
237
+ backgroundBlurriness: 0,
238
+ backgroundIntensity: 1,
239
+ backgroundRotation: [0, 0, 0],
240
+ environmentIntensity: 1,
241
+ environmentRotation: [0, 0, 0],
242
+ ...sceneProps
243
+ };
244
+ const target = resolveScene(scene || defaultScene);
245
+ const oldbg = target.background;
246
+ const oldenv = target.environment;
247
+ const oldSceneProps = {
248
+ // @ts-ignore
249
+ backgroundBlurriness: target.backgroundBlurriness,
250
+ // @ts-ignore
251
+ backgroundIntensity: target.backgroundIntensity,
252
+ // @ts-ignore
253
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
254
+ // @ts-ignore
255
+ environmentIntensity: target.environmentIntensity,
256
+ // @ts-ignore
257
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
258
+ };
259
+ if (background !== "only") target.environment = texture;
260
+ if (background) target.background = texture;
261
+ applyProps$1(target, sceneProps);
262
+ return () => {
263
+ if (background !== "only") target.environment = oldenv;
264
+ if (background) target.background = oldbg;
265
+ applyProps$1(target, oldSceneProps);
266
+ };
267
+ }
268
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
269
+ const defaultScene = useThree$1((state) => state.scene);
270
+ React.useLayoutEffect(() => {
271
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
272
+ });
273
+ return null;
274
+ }
275
+ function EnvironmentCube({
276
+ background = false,
277
+ scene,
278
+ blur,
279
+ backgroundBlurriness,
280
+ backgroundIntensity,
281
+ backgroundRotation,
282
+ environmentIntensity,
283
+ environmentRotation,
284
+ ...rest
285
+ }) {
286
+ const texture = useEnvironment(rest);
287
+ const defaultScene = useThree$1((state) => state.scene);
288
+ React.useLayoutEffect(() => {
289
+ return setEnvProps(background, scene, defaultScene, texture, {
290
+ backgroundBlurriness: blur ?? backgroundBlurriness,
291
+ backgroundIntensity,
292
+ backgroundRotation,
293
+ environmentIntensity,
294
+ environmentRotation
295
+ });
296
+ });
297
+ React.useEffect(() => {
298
+ return () => {
299
+ texture.dispose();
300
+ };
301
+ }, [texture]);
302
+ return null;
303
+ }
304
+ function EnvironmentPortal({
305
+ children,
306
+ near = 0.1,
307
+ far = 1e3,
308
+ resolution = 256,
309
+ frames = 1,
310
+ map,
311
+ background = false,
312
+ blur,
313
+ backgroundBlurriness,
314
+ backgroundIntensity,
315
+ backgroundRotation,
316
+ environmentIntensity,
317
+ environmentRotation,
318
+ scene,
319
+ files,
320
+ path,
321
+ preset = void 0,
322
+ extensions
323
+ }) {
324
+ const gl = useThree$1((state) => state.gl);
325
+ const defaultScene = useThree$1((state) => state.scene);
326
+ const camera = React.useRef(null);
327
+ const [virtualScene] = React.useState(() => new Scene());
328
+ const fbo = React.useMemo(() => {
329
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
330
+ fbo2.texture.type = HalfFloatType;
331
+ return fbo2;
332
+ }, [resolution]);
333
+ React.useEffect(() => {
334
+ return () => {
335
+ fbo.dispose();
336
+ };
337
+ }, [fbo]);
338
+ React.useLayoutEffect(() => {
339
+ if (frames === 1) {
340
+ const autoClear = gl.autoClear;
341
+ gl.autoClear = true;
342
+ camera.current.update(gl, virtualScene);
343
+ gl.autoClear = autoClear;
344
+ }
345
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
346
+ backgroundBlurriness: blur ?? backgroundBlurriness,
347
+ backgroundIntensity,
348
+ backgroundRotation,
349
+ environmentIntensity,
350
+ environmentRotation
351
+ });
352
+ }, [children, virtualScene, fbo.texture, scene, defaultScene, background, frames, gl]);
353
+ let count = 1;
354
+ useFrame$1(() => {
355
+ if (frames === Infinity || count < frames) {
356
+ const autoClear = gl.autoClear;
357
+ gl.autoClear = true;
358
+ camera.current.update(gl, virtualScene);
359
+ gl.autoClear = autoClear;
360
+ count++;
361
+ }
362
+ });
363
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
364
+ /* @__PURE__ */ jsxs(Fragment, { children: [
365
+ children,
366
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
367
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
368
+ ] }),
369
+ virtualScene
370
+ ) });
371
+ }
372
+ function EnvironmentGround(props) {
373
+ const textureDefault = useEnvironment(props);
374
+ const texture = props.map || textureDefault;
375
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
376
+ React.useEffect(() => {
377
+ return () => {
378
+ textureDefault.dispose();
379
+ };
380
+ }, [textureDefault]);
381
+ const height = props.ground?.height ?? 15;
382
+ const radius = props.ground?.radius ?? 60;
383
+ const scale = props.ground?.scale ?? 1e3;
384
+ const args = React.useMemo(
385
+ () => [texture, height, radius],
386
+ [texture, height, radius]
387
+ );
388
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
389
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
390
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
391
+ ] });
392
+ }
393
+ function EnvironmentColor({ color, scene }) {
394
+ const defaultScene = useThree$1((state) => state.scene);
395
+ React.useLayoutEffect(() => {
396
+ if (color === void 0) return;
397
+ const target = resolveScene(scene || defaultScene);
398
+ const oldBg = target.background;
399
+ target.background = new Color(color);
400
+ return () => {
401
+ target.background = oldBg;
402
+ };
403
+ });
404
+ return null;
405
+ }
406
+ function EnvironmentDualSource(props) {
407
+ const { backgroundFiles, ...envProps } = props;
408
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
409
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
410
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
411
+ ] });
412
+ }
413
+ function Environment(props) {
414
+ if (props.color && !props.files && !props.preset && !props.map) {
415
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
416
+ }
417
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
418
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
419
+ }
420
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
421
+ }
422
+
49
423
  var __defProp$3 = Object.defineProperty;
50
424
  var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
51
425
  var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -225,7 +599,8 @@ function prepare(target, root, type, props) {
225
599
  object,
226
600
  eventCount: 0,
227
601
  handlers: {},
228
- isHidden: false
602
+ isHidden: false,
603
+ deferredRefs: []
229
604
  };
230
605
  if (object) object.__r3f = instance;
231
606
  }
@@ -274,7 +649,7 @@ function createOcclusionObserverNode(store, uniform) {
274
649
  let occlusionSetupPromise = null;
275
650
  function enableOcclusion(store) {
276
651
  const state = store.getState();
277
- const { internal, renderer, rootScene } = state;
652
+ const { internal, renderer } = state;
278
653
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
279
654
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
280
655
  if (!hasOcclusionSupport) {
@@ -437,6 +812,22 @@ function hasVisibilityHandlers(handlers) {
437
812
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
438
813
  }
439
814
 
815
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
816
+ function fromRef(ref) {
817
+ return { [FROM_REF]: ref };
818
+ }
819
+ function isFromRef(value) {
820
+ return value !== null && typeof value === "object" && FROM_REF in value;
821
+ }
822
+
823
+ const ONCE = Symbol.for("@react-three/fiber.once");
824
+ function once(...args) {
825
+ return { [ONCE]: args.length ? args : true };
826
+ }
827
+ function isOnce(value) {
828
+ return value !== null && typeof value === "object" && ONCE in value;
829
+ }
830
+
440
831
  const RESERVED_PROPS = [
441
832
  "children",
442
833
  "key",
@@ -507,7 +898,7 @@ function getMemoizedPrototype(root) {
507
898
  ctor = new root.constructor();
508
899
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
509
900
  }
510
- } catch (e) {
901
+ } catch {
511
902
  }
512
903
  return ctor;
513
904
  }
@@ -553,6 +944,25 @@ function applyProps(object, props) {
553
944
  continue;
554
945
  }
555
946
  if (value === void 0) continue;
947
+ if (isFromRef(value)) {
948
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
949
+ continue;
950
+ }
951
+ if (isOnce(value)) {
952
+ if (instance?.appliedOnce?.has(prop)) continue;
953
+ if (instance) {
954
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
955
+ instance.appliedOnce.add(prop);
956
+ }
957
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
958
+ const args = value[ONCE];
959
+ if (typeof targetRoot[targetKey] === "function") {
960
+ targetRoot[targetKey](...args === true ? [] : args);
961
+ } else if (args !== true && args.length > 0) {
962
+ targetRoot[targetKey] = args[0];
963
+ }
964
+ continue;
965
+ }
556
966
  let { root, key, target } = resolve(object, prop);
557
967
  if (target === void 0 && (typeof root !== "object" || root === null)) {
558
968
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -575,7 +985,7 @@ function applyProps(object, props) {
575
985
  else target.set(value);
576
986
  } else {
577
987
  root[key] = value;
578
- if (rootState && !rootState.linear && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
988
+ if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
579
989
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
580
990
  root[key].colorSpace = rootState.textureColorSpace;
581
991
  }
@@ -932,7 +1342,7 @@ function createPointerEvents(store) {
932
1342
  return {
933
1343
  priority: 1,
934
1344
  enabled: true,
935
- compute(event, state, previous) {
1345
+ compute(event, state) {
936
1346
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
937
1347
  state.raycaster.setFromCamera(state.pointer, state.camera);
938
1348
  },
@@ -1030,331 +1440,26 @@ function notifyAlpha({ message, link }) {
1030
1440
  }
1031
1441
  }
1032
1442
 
1033
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1034
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1035
- const createStore = (invalidate, advance) => {
1036
- const rootStore = createWithEqualityFn((set, get) => {
1037
- const position = new Vector3();
1038
- const defaultTarget = new Vector3();
1039
- const tempTarget = new Vector3();
1040
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1041
- const { width, height, top, left } = size;
1042
- const aspect = width / height;
1043
- if (target.isVector3) tempTarget.copy(target);
1044
- else tempTarget.set(...target);
1045
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1046
- if (isOrthographicCamera(camera)) {
1047
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1048
- } else {
1049
- const fov = camera.fov * Math.PI / 180;
1050
- const h = 2 * Math.tan(fov / 2) * distance;
1051
- const w = h * (width / height);
1052
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1053
- }
1054
- }
1055
- let performanceTimeout = void 0;
1056
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1057
- const pointer = new Vector2();
1058
- const rootState = {
1059
- set,
1060
- get,
1061
- // Mock objects that have to be configured
1062
- gl: null,
1063
- renderer: null,
1064
- camera: null,
1065
- frustum: new Frustum(),
1066
- autoUpdateFrustum: true,
1067
- raycaster: null,
1068
- events: { priority: 1, enabled: true, connected: false },
1069
- scene: null,
1070
- rootScene: null,
1071
- xr: null,
1072
- inspector: null,
1073
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1074
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1075
- legacy: false,
1076
- linear: false,
1077
- flat: false,
1078
- textureColorSpace: "srgb",
1079
- isLegacy: false,
1080
- webGPUSupported: false,
1081
- isNative: false,
1082
- controls: null,
1083
- pointer,
1084
- mouse: pointer,
1085
- frameloop: "always",
1086
- onPointerMissed: void 0,
1087
- onDragOverMissed: void 0,
1088
- onDropMissed: void 0,
1089
- performance: {
1090
- current: 1,
1091
- min: 0.5,
1092
- max: 1,
1093
- debounce: 200,
1094
- regress: () => {
1095
- const state2 = get();
1096
- if (performanceTimeout) clearTimeout(performanceTimeout);
1097
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1098
- performanceTimeout = setTimeout(
1099
- () => setPerformanceCurrent(get().performance.max),
1100
- state2.performance.debounce
1101
- );
1102
- }
1103
- },
1104
- size: { width: 0, height: 0, top: 0, left: 0 },
1105
- viewport: {
1106
- initialDpr: 0,
1107
- dpr: 0,
1108
- width: 0,
1109
- height: 0,
1110
- top: 0,
1111
- left: 0,
1112
- aspect: 0,
1113
- distance: 0,
1114
- factor: 0,
1115
- getCurrentViewport
1116
- },
1117
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1118
- setSize: (width, height, top, left) => {
1119
- const state2 = get();
1120
- if (width === void 0) {
1121
- set({ _sizeImperative: false });
1122
- if (state2._sizeProps) {
1123
- const { width: propW, height: propH } = state2._sizeProps;
1124
- if (propW !== void 0 || propH !== void 0) {
1125
- const currentSize = state2.size;
1126
- const newSize = {
1127
- width: propW ?? currentSize.width,
1128
- height: propH ?? currentSize.height,
1129
- top: currentSize.top,
1130
- left: currentSize.left
1131
- };
1132
- set((s) => ({
1133
- size: newSize,
1134
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1135
- }));
1136
- }
1137
- }
1138
- return;
1139
- }
1140
- const w = width;
1141
- const h = height ?? width;
1142
- const t = top ?? state2.size.top;
1143
- const l = left ?? state2.size.left;
1144
- const size = { width: w, height: h, top: t, left: l };
1145
- set((s) => ({
1146
- size,
1147
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1148
- _sizeImperative: true
1149
- }));
1150
- },
1151
- setDpr: (dpr) => set((state2) => {
1152
- const resolved = calculateDpr(dpr);
1153
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1154
- }),
1155
- setFrameloop: (frameloop = "always") => {
1156
- set(() => ({ frameloop }));
1157
- },
1158
- setError: (error) => set(() => ({ error })),
1159
- error: null,
1160
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1161
- uniforms: {},
1162
- nodes: {},
1163
- textures: /* @__PURE__ */ new Map(),
1164
- postProcessing: null,
1165
- passes: {},
1166
- _hmrVersion: 0,
1167
- _sizeImperative: false,
1168
- _sizeProps: null,
1169
- previousRoot: void 0,
1170
- internal: {
1171
- // Events
1172
- interaction: [],
1173
- hovered: /* @__PURE__ */ new Map(),
1174
- subscribers: [],
1175
- initialClick: [0, 0],
1176
- initialHits: [],
1177
- capturedMap: /* @__PURE__ */ new Map(),
1178
- lastEvent: React.createRef(),
1179
- // Visibility tracking (onFramed, onOccluded, onVisible)
1180
- visibilityRegistry: /* @__PURE__ */ new Map(),
1181
- // Occlusion system (WebGPU only)
1182
- occlusionEnabled: false,
1183
- occlusionObserver: null,
1184
- occlusionCache: /* @__PURE__ */ new Map(),
1185
- helperGroup: null,
1186
- // Updates
1187
- active: false,
1188
- frames: 0,
1189
- priority: 0,
1190
- subscribe: (ref, priority, store) => {
1191
- const internal = get().internal;
1192
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1193
- internal.subscribers.push({ ref, priority, store });
1194
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1195
- return () => {
1196
- const internal2 = get().internal;
1197
- if (internal2?.subscribers) {
1198
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1199
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1200
- }
1201
- };
1202
- },
1203
- // Renderer Storage (single source of truth)
1204
- actualRenderer: null,
1205
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1206
- scheduler: null
1207
- }
1208
- };
1209
- return rootState;
1210
- });
1211
- const state = rootStore.getState();
1212
- Object.defineProperty(state, "gl", {
1213
- get() {
1214
- const currentState = rootStore.getState();
1215
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1216
- const stack = new Error().stack || "";
1217
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1218
- if (!isInternalAccess) {
1219
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1220
- notifyDepreciated({
1221
- heading: "Accessing state.gl in WebGPU mode",
1222
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1223
- });
1224
- }
1225
- }
1226
- return currentState.internal.actualRenderer;
1227
- },
1228
- set(value) {
1229
- rootStore.getState().internal.actualRenderer = value;
1230
- },
1231
- enumerable: true,
1232
- configurable: true
1233
- });
1234
- Object.defineProperty(state, "renderer", {
1235
- get() {
1236
- return rootStore.getState().internal.actualRenderer;
1237
- },
1238
- set(value) {
1239
- rootStore.getState().internal.actualRenderer = value;
1240
- },
1241
- enumerable: true,
1242
- configurable: true
1243
- });
1244
- let oldScene = state.scene;
1245
- rootStore.subscribe(() => {
1246
- const currentState = rootStore.getState();
1247
- const { scene, rootScene, set } = currentState;
1248
- if (scene !== oldScene) {
1249
- oldScene = scene;
1250
- if (scene?.isScene && scene !== rootScene) {
1251
- set({ rootScene: scene });
1252
- }
1253
- }
1254
- });
1255
- let oldSize = state.size;
1256
- let oldDpr = state.viewport.dpr;
1257
- let oldCamera = state.camera;
1258
- rootStore.subscribe(() => {
1259
- const { camera, size, viewport, set, internal } = rootStore.getState();
1260
- const actualRenderer = internal.actualRenderer;
1261
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1262
- oldSize = size;
1263
- oldDpr = viewport.dpr;
1264
- updateCamera(camera, size);
1265
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1266
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1267
- actualRenderer.setSize(size.width, size.height, updateStyle);
1268
- }
1269
- if (camera !== oldCamera) {
1270
- oldCamera = camera;
1271
- const { rootScene } = rootStore.getState();
1272
- if (camera && rootScene && !camera.parent) {
1273
- rootScene.add(camera);
1274
- }
1275
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1276
- const currentState = rootStore.getState();
1277
- if (currentState.autoUpdateFrustum && camera) {
1278
- updateFrustum(camera, currentState.frustum);
1279
- }
1280
- }
1281
- });
1282
- rootStore.subscribe((state2) => invalidate(state2));
1283
- return rootStore;
1284
- };
1285
-
1286
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1287
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1288
- function getLoader(Proto) {
1289
- if (isConstructor$1(Proto)) {
1290
- let loader = memoizedLoaders.get(Proto);
1291
- if (!loader) {
1292
- loader = new Proto();
1293
- memoizedLoaders.set(Proto, loader);
1294
- }
1295
- return loader;
1296
- }
1297
- return Proto;
1298
- }
1299
- function loadingFn(extensions, onProgress) {
1300
- return function(Proto, input) {
1301
- const loader = getLoader(Proto);
1302
- if (extensions) extensions(loader);
1303
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1304
- return loader.loadAsync(input, onProgress).then((data) => {
1305
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1306
- return data;
1307
- });
1308
- }
1309
- return new Promise(
1310
- (res, reject) => loader.load(
1311
- input,
1312
- (data) => {
1313
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1314
- res(data);
1315
- },
1316
- onProgress,
1317
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1318
- )
1319
- );
1320
- };
1321
- }
1322
- function useLoader(loader, input, extensions, onProgress) {
1323
- const keys = Array.isArray(input) ? input : [input];
1324
- const fn = loadingFn(extensions, onProgress);
1325
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1326
- return Array.isArray(input) ? results : results[0];
1327
- }
1328
- useLoader.preload = function(loader, input, extensions, onProgress) {
1329
- const keys = Array.isArray(input) ? input : [input];
1330
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1331
- };
1332
- useLoader.clear = function(loader, input) {
1333
- const keys = Array.isArray(input) ? input : [input];
1334
- keys.forEach((key) => clear([loader, key]));
1335
- };
1336
- useLoader.loader = getLoader;
1337
-
1338
- var __defProp$2 = Object.defineProperty;
1339
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1340
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
1341
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1342
- class PhaseGraph {
1343
- constructor() {
1344
- /** Ordered list of phase nodes */
1345
- __publicField$2(this, "phases", []);
1346
- /** Quick lookup by name */
1347
- __publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
1348
- /** Cached ordered names (invalidated on changes) */
1349
- __publicField$2(this, "orderedNamesCache", null);
1350
- this.initializeDefaultPhases();
1351
- }
1352
- //* Initialization --------------------------------
1353
- initializeDefaultPhases() {
1354
- for (const name of DEFAULT_PHASES) {
1355
- const node = { name, isAutoGenerated: false };
1356
- this.phases.push(node);
1357
- this.phaseMap.set(name, node);
1443
+ var __defProp$2 = Object.defineProperty;
1444
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1445
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
1446
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1447
+ class PhaseGraph {
1448
+ constructor() {
1449
+ /** Ordered list of phase nodes */
1450
+ __publicField$2(this, "phases", []);
1451
+ /** Quick lookup by name */
1452
+ __publicField$2(this, "phaseMap", /* @__PURE__ */ new Map());
1453
+ /** Cached ordered names (invalidated on changes) */
1454
+ __publicField$2(this, "orderedNamesCache", null);
1455
+ this.initializeDefaultPhases();
1456
+ }
1457
+ //* Initialization --------------------------------
1458
+ initializeDefaultPhases() {
1459
+ for (const name of DEFAULT_PHASES) {
1460
+ const node = { name, isAutoGenerated: false };
1461
+ this.phases.push(node);
1462
+ this.phaseMap.set(name, node);
1358
1463
  }
1359
1464
  this.invalidateCache();
1360
1465
  }
@@ -2274,98 +2379,411 @@ const _Scheduler = class _Scheduler {
2274
2379
  this.triggerError(error instanceof Error ? error : new Error(String(error)));
2275
2380
  }
2276
2381
  }
2277
- }
2278
- //* Debug & Inspection Methods ================================
2279
- /**
2280
- * Get the total number of registered jobs across all roots.
2281
- * Includes both per-root jobs and global before/after jobs.
2282
- * @returns {number} Total job count
2283
- */
2284
- getJobCount() {
2285
- let count = 0;
2286
- for (const root of this.roots.values()) {
2287
- count += root.jobs.size;
2382
+ }
2383
+ //* Debug & Inspection Methods ================================
2384
+ /**
2385
+ * Get the total number of registered jobs across all roots.
2386
+ * Includes both per-root jobs and global before/after jobs.
2387
+ * @returns {number} Total job count
2388
+ */
2389
+ getJobCount() {
2390
+ let count = 0;
2391
+ for (const root of this.roots.values()) {
2392
+ count += root.jobs.size;
2393
+ }
2394
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2395
+ }
2396
+ /**
2397
+ * Get all registered job IDs across all roots.
2398
+ * Includes both per-root jobs and global before/after jobs.
2399
+ * @returns {string[]} Array of all job IDs
2400
+ */
2401
+ getJobIds() {
2402
+ const ids = [];
2403
+ for (const root of this.roots.values()) {
2404
+ ids.push(...root.jobs.keys());
2405
+ }
2406
+ ids.push(...this.globalBeforeJobs.keys());
2407
+ ids.push(...this.globalAfterJobs.keys());
2408
+ return ids;
2409
+ }
2410
+ /**
2411
+ * Get the number of registered roots (Canvas instances).
2412
+ * @returns {number} Number of registered roots
2413
+ */
2414
+ getRootCount() {
2415
+ return this.roots.size;
2416
+ }
2417
+ /**
2418
+ * Check if any user (non-system) jobs are registered in a specific phase.
2419
+ * Used by the default render job to know if a user has taken over rendering.
2420
+ *
2421
+ * @param phase The phase to check
2422
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2423
+ * @returns true if any user jobs exist in the phase
2424
+ */
2425
+ hasUserJobsInPhase(phase, rootId) {
2426
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2427
+ return rootsToCheck.some((root) => {
2428
+ if (!root) return false;
2429
+ for (const job of root.jobs.values()) {
2430
+ if (job.phase === phase && !job.system && job.enabled) return true;
2431
+ }
2432
+ return false;
2433
+ });
2434
+ }
2435
+ //* Utility Methods ================================
2436
+ /**
2437
+ * Generate a unique root ID for automatic root registration.
2438
+ * @returns {string} A unique root ID in the format 'root_N'
2439
+ */
2440
+ generateRootId() {
2441
+ return `root_${this.nextRootIndex++}`;
2442
+ }
2443
+ /**
2444
+ * Generate a unique job ID.
2445
+ * @returns {string} A unique job ID in the format 'job_N'
2446
+ * @private
2447
+ */
2448
+ generateJobId() {
2449
+ return `job_${this.nextJobIndex}`;
2450
+ }
2451
+ /**
2452
+ * Normalize before/after constraints to a Set.
2453
+ * Handles undefined, single string, or array inputs.
2454
+ * @param {string | string[] | undefined} value - The constraint value(s)
2455
+ * @returns {Set<string>} Normalized Set of constraint strings
2456
+ * @private
2457
+ */
2458
+ normalizeConstraints(value) {
2459
+ if (!value) return /* @__PURE__ */ new Set();
2460
+ if (Array.isArray(value)) return new Set(value);
2461
+ return /* @__PURE__ */ new Set([value]);
2462
+ }
2463
+ };
2464
+ //* Static State & Methods (Singleton Usage) ================================
2465
+ //* Cross-Bundle Singleton Key ==============================
2466
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2467
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2468
+ __publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2469
+ let Scheduler = _Scheduler;
2470
+ const getScheduler = () => Scheduler.get();
2471
+ if (hmrData) {
2472
+ hmrData.accept?.();
2473
+ }
2474
+
2475
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2476
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
2477
+ const createStore = (invalidate, advance) => {
2478
+ const rootStore = createWithEqualityFn((set, get) => {
2479
+ const position = new Vector3();
2480
+ const defaultTarget = new Vector3();
2481
+ const tempTarget = new Vector3();
2482
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2483
+ const { width, height, top, left } = size;
2484
+ const aspect = width / height;
2485
+ if (target.isVector3) tempTarget.copy(target);
2486
+ else tempTarget.set(...target);
2487
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2488
+ if (isOrthographicCamera(camera)) {
2489
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2490
+ } else {
2491
+ const fov = camera.fov * Math.PI / 180;
2492
+ const h = 2 * Math.tan(fov / 2) * distance;
2493
+ const w = h * (width / height);
2494
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2495
+ }
2496
+ }
2497
+ let performanceTimeout = void 0;
2498
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2499
+ const pointer = new Vector2();
2500
+ const rootState = {
2501
+ set,
2502
+ get,
2503
+ // Mock objects that have to be configured
2504
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2505
+ primaryStore: null,
2506
+ gl: null,
2507
+ renderer: null,
2508
+ camera: null,
2509
+ frustum: new Frustum(),
2510
+ autoUpdateFrustum: true,
2511
+ raycaster: null,
2512
+ events: { priority: 1, enabled: true, connected: false },
2513
+ scene: null,
2514
+ rootScene: null,
2515
+ xr: null,
2516
+ inspector: null,
2517
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2518
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2519
+ textureColorSpace: SRGBColorSpace,
2520
+ isLegacy: false,
2521
+ webGPUSupported: false,
2522
+ isNative: false,
2523
+ controls: null,
2524
+ pointer,
2525
+ mouse: pointer,
2526
+ frameloop: "always",
2527
+ onPointerMissed: void 0,
2528
+ onDragOverMissed: void 0,
2529
+ onDropMissed: void 0,
2530
+ performance: {
2531
+ current: 1,
2532
+ min: 0.5,
2533
+ max: 1,
2534
+ debounce: 200,
2535
+ regress: () => {
2536
+ const state2 = get();
2537
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2538
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2539
+ performanceTimeout = setTimeout(
2540
+ () => setPerformanceCurrent(get().performance.max),
2541
+ state2.performance.debounce
2542
+ );
2543
+ }
2544
+ },
2545
+ size: { width: 0, height: 0, top: 0, left: 0 },
2546
+ viewport: {
2547
+ initialDpr: 0,
2548
+ dpr: 0,
2549
+ width: 0,
2550
+ height: 0,
2551
+ top: 0,
2552
+ left: 0,
2553
+ aspect: 0,
2554
+ distance: 0,
2555
+ factor: 0,
2556
+ getCurrentViewport
2557
+ },
2558
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2559
+ setSize: (width, height, top, left) => {
2560
+ const state2 = get();
2561
+ if (width === void 0) {
2562
+ set({ _sizeImperative: false });
2563
+ if (state2._sizeProps) {
2564
+ const { width: propW, height: propH } = state2._sizeProps;
2565
+ if (propW !== void 0 || propH !== void 0) {
2566
+ const currentSize = state2.size;
2567
+ const newSize = {
2568
+ width: propW ?? currentSize.width,
2569
+ height: propH ?? currentSize.height,
2570
+ top: currentSize.top,
2571
+ left: currentSize.left
2572
+ };
2573
+ set((s) => ({
2574
+ size: newSize,
2575
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2576
+ }));
2577
+ getScheduler().invalidate();
2578
+ }
2579
+ }
2580
+ return;
2581
+ }
2582
+ const w = width;
2583
+ const h = height ?? width;
2584
+ const t = top ?? state2.size.top;
2585
+ const l = left ?? state2.size.left;
2586
+ const size = { width: w, height: h, top: t, left: l };
2587
+ set((s) => ({
2588
+ size,
2589
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2590
+ _sizeImperative: true
2591
+ }));
2592
+ getScheduler().invalidate();
2593
+ },
2594
+ setDpr: (dpr) => set((state2) => {
2595
+ const resolved = calculateDpr(dpr);
2596
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2597
+ }),
2598
+ setFrameloop: (frameloop = "always") => {
2599
+ set(() => ({ frameloop }));
2600
+ },
2601
+ setError: (error) => set(() => ({ error })),
2602
+ error: null,
2603
+ //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2604
+ uniforms: {},
2605
+ nodes: {},
2606
+ textures: /* @__PURE__ */ new Map(),
2607
+ postProcessing: null,
2608
+ passes: {},
2609
+ _hmrVersion: 0,
2610
+ _sizeImperative: false,
2611
+ _sizeProps: null,
2612
+ previousRoot: void 0,
2613
+ internal: {
2614
+ // Events
2615
+ interaction: [],
2616
+ hovered: /* @__PURE__ */ new Map(),
2617
+ subscribers: [],
2618
+ initialClick: [0, 0],
2619
+ initialHits: [],
2620
+ capturedMap: /* @__PURE__ */ new Map(),
2621
+ lastEvent: React.createRef(),
2622
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2623
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2624
+ // Occlusion system (WebGPU only)
2625
+ occlusionEnabled: false,
2626
+ occlusionObserver: null,
2627
+ occlusionCache: /* @__PURE__ */ new Map(),
2628
+ helperGroup: null,
2629
+ // Updates
2630
+ active: false,
2631
+ frames: 0,
2632
+ priority: 0,
2633
+ subscribe: (ref, priority, store) => {
2634
+ const internal = get().internal;
2635
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2636
+ internal.subscribers.push({ ref, priority, store });
2637
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2638
+ return () => {
2639
+ const internal2 = get().internal;
2640
+ if (internal2?.subscribers) {
2641
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2642
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2643
+ }
2644
+ };
2645
+ },
2646
+ // Renderer Storage (single source of truth)
2647
+ actualRenderer: null,
2648
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2649
+ scheduler: null
2650
+ }
2651
+ };
2652
+ return rootState;
2653
+ });
2654
+ const state = rootStore.getState();
2655
+ Object.defineProperty(state, "gl", {
2656
+ get() {
2657
+ const currentState = rootStore.getState();
2658
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2659
+ const stack = new Error().stack || "";
2660
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2661
+ if (!isInternalAccess) {
2662
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2663
+ notifyDepreciated({
2664
+ heading: "Accessing state.gl in WebGPU mode",
2665
+ body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
2666
+ });
2667
+ }
2668
+ }
2669
+ return currentState.internal.actualRenderer;
2670
+ },
2671
+ set(value) {
2672
+ rootStore.getState().internal.actualRenderer = value;
2673
+ },
2674
+ enumerable: true,
2675
+ configurable: true
2676
+ });
2677
+ Object.defineProperty(state, "renderer", {
2678
+ get() {
2679
+ return rootStore.getState().internal.actualRenderer;
2680
+ },
2681
+ set(value) {
2682
+ rootStore.getState().internal.actualRenderer = value;
2683
+ },
2684
+ enumerable: true,
2685
+ configurable: true
2686
+ });
2687
+ let oldScene = state.scene;
2688
+ rootStore.subscribe(() => {
2689
+ const currentState = rootStore.getState();
2690
+ const { scene, rootScene, set } = currentState;
2691
+ if (scene !== oldScene) {
2692
+ oldScene = scene;
2693
+ if (scene?.isScene && scene !== rootScene) {
2694
+ set({ rootScene: scene });
2695
+ }
2696
+ }
2697
+ });
2698
+ let oldSize = state.size;
2699
+ let oldDpr = state.viewport.dpr;
2700
+ let oldCamera = state.camera;
2701
+ rootStore.subscribe(() => {
2702
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2703
+ const actualRenderer = internal.actualRenderer;
2704
+ const canvasTarget = internal.canvasTarget;
2705
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2706
+ oldSize = size;
2707
+ oldDpr = viewport.dpr;
2708
+ updateCamera(camera, size);
2709
+ if (canvasTarget) {
2710
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2711
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && canvasTarget.domElement instanceof HTMLCanvasElement;
2712
+ canvasTarget.setSize(size.width, size.height, updateStyle);
2713
+ } else {
2714
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2715
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
2716
+ actualRenderer.setSize(size.width, size.height, updateStyle);
2717
+ }
2718
+ }
2719
+ if (camera !== oldCamera) {
2720
+ oldCamera = camera;
2721
+ const { rootScene } = rootStore.getState();
2722
+ if (camera && rootScene && !camera.parent) {
2723
+ rootScene.add(camera);
2724
+ }
2725
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2726
+ const currentState = rootStore.getState();
2727
+ if (currentState.autoUpdateFrustum && camera) {
2728
+ updateFrustum(camera, currentState.frustum);
2729
+ }
2288
2730
  }
2289
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2290
- }
2291
- /**
2292
- * Get all registered job IDs across all roots.
2293
- * Includes both per-root jobs and global before/after jobs.
2294
- * @returns {string[]} Array of all job IDs
2295
- */
2296
- getJobIds() {
2297
- const ids = [];
2298
- for (const root of this.roots.values()) {
2299
- ids.push(...root.jobs.keys());
2731
+ });
2732
+ rootStore.subscribe((state2) => invalidate(state2));
2733
+ return rootStore;
2734
+ };
2735
+
2736
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2737
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2738
+ function getLoader(Proto) {
2739
+ if (isConstructor$1(Proto)) {
2740
+ let loader = memoizedLoaders.get(Proto);
2741
+ if (!loader) {
2742
+ loader = new Proto();
2743
+ memoizedLoaders.set(Proto, loader);
2300
2744
  }
2301
- ids.push(...this.globalBeforeJobs.keys());
2302
- ids.push(...this.globalAfterJobs.keys());
2303
- return ids;
2304
- }
2305
- /**
2306
- * Get the number of registered roots (Canvas instances).
2307
- * @returns {number} Number of registered roots
2308
- */
2309
- getRootCount() {
2310
- return this.roots.size;
2311
- }
2312
- /**
2313
- * Check if any user (non-system) jobs are registered in a specific phase.
2314
- * Used by the default render job to know if a user has taken over rendering.
2315
- *
2316
- * @param phase The phase to check
2317
- * @param rootId Optional root ID to check (checks all roots if not provided)
2318
- * @returns true if any user jobs exist in the phase
2319
- */
2320
- hasUserJobsInPhase(phase, rootId) {
2321
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2322
- return rootsToCheck.some((root) => {
2323
- if (!root) return false;
2324
- for (const job of root.jobs.values()) {
2325
- if (job.phase === phase && !job.system && job.enabled) return true;
2326
- }
2327
- return false;
2328
- });
2329
- }
2330
- //* Utility Methods ================================
2331
- /**
2332
- * Generate a unique root ID for automatic root registration.
2333
- * @returns {string} A unique root ID in the format 'root_N'
2334
- */
2335
- generateRootId() {
2336
- return `root_${this.nextRootIndex++}`;
2337
- }
2338
- /**
2339
- * Generate a unique job ID.
2340
- * @returns {string} A unique job ID in the format 'job_N'
2341
- * @private
2342
- */
2343
- generateJobId() {
2344
- return `job_${this.nextJobIndex}`;
2345
- }
2346
- /**
2347
- * Normalize before/after constraints to a Set.
2348
- * Handles undefined, single string, or array inputs.
2349
- * @param {string | string[] | undefined} value - The constraint value(s)
2350
- * @returns {Set<string>} Normalized Set of constraint strings
2351
- * @private
2352
- */
2353
- normalizeConstraints(value) {
2354
- if (!value) return /* @__PURE__ */ new Set();
2355
- if (Array.isArray(value)) return new Set(value);
2356
- return /* @__PURE__ */ new Set([value]);
2745
+ return loader;
2357
2746
  }
2358
- };
2359
- //* Static State & Methods (Singleton Usage) ================================
2360
- //* Cross-Bundle Singleton Key ==============================
2361
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2362
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2363
- __publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2364
- let Scheduler = _Scheduler;
2365
- const getScheduler = () => Scheduler.get();
2366
- if (hmrData) {
2367
- hmrData.accept?.();
2747
+ return Proto;
2748
+ }
2749
+ function loadingFn(extensions, onProgress) {
2750
+ return function(Proto, input) {
2751
+ const loader = getLoader(Proto);
2752
+ if (extensions) extensions(loader);
2753
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2754
+ return loader.loadAsync(input, onProgress).then((data) => {
2755
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2756
+ return data;
2757
+ });
2758
+ }
2759
+ return new Promise(
2760
+ (res, reject) => loader.load(
2761
+ input,
2762
+ (data) => {
2763
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2764
+ res(data);
2765
+ },
2766
+ onProgress,
2767
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2768
+ )
2769
+ );
2770
+ };
2771
+ }
2772
+ function useLoader(loader, input, extensions, onProgress) {
2773
+ const keys = Array.isArray(input) ? input : [input];
2774
+ const fn = loadingFn(extensions, onProgress);
2775
+ const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
2776
+ return Array.isArray(input) ? results : results[0];
2368
2777
  }
2778
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2779
+ const keys = Array.isArray(input) ? input : [input];
2780
+ keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2781
+ };
2782
+ useLoader.clear = function(loader, input) {
2783
+ const keys = Array.isArray(input) ? input : [input];
2784
+ keys.forEach((key) => clear([loader, key]));
2785
+ };
2786
+ useLoader.loader = getLoader;
2369
2787
 
2370
2788
  function useFrame(callback, priorityOrOptions) {
2371
2789
  const store = React.useContext(context);
@@ -2546,6 +2964,9 @@ function useTexture(input, optionsOrOnLoad) {
2546
2964
  const textureCache = useThree((state) => state.textures);
2547
2965
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2548
2966
  const { onLoad, cache = false } = options;
2967
+ const onLoadRef = useRef(onLoad);
2968
+ onLoadRef.current = onLoad;
2969
+ const onLoadCalledForRef = useRef(null);
2549
2970
  const urls = useMemo(() => getUrls(input), [input]);
2550
2971
  const cachedResult = useMemo(() => {
2551
2972
  if (!cache) return null;
@@ -2556,9 +2977,13 @@ function useTexture(input, optionsOrOnLoad) {
2556
2977
  TextureLoader,
2557
2978
  IsObject(input) ? Object.values(input) : input
2558
2979
  );
2980
+ const inputKey = urls.join("\0");
2559
2981
  useLayoutEffect(() => {
2560
- if (!cachedResult) onLoad?.(loadedTextures);
2561
- }, [onLoad, cachedResult, loadedTextures]);
2982
+ if (cachedResult) return;
2983
+ if (onLoadCalledForRef.current === inputKey) return;
2984
+ onLoadCalledForRef.current = inputKey;
2985
+ onLoadRef.current?.(loadedTextures);
2986
+ }, [cachedResult, loadedTextures, inputKey]);
2562
2987
  useEffect(() => {
2563
2988
  if (cachedResult) return;
2564
2989
  if ("initTexture" in renderer) {
@@ -2725,14 +3150,31 @@ function useTextures() {
2725
3150
  }, [store]);
2726
3151
  }
2727
3152
 
2728
- function useRenderTarget(width, height, options) {
3153
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2729
3154
  const isLegacy = useThree((s) => s.isLegacy);
2730
3155
  const size = useThree((s) => s.size);
3156
+ let width;
3157
+ let height;
3158
+ let opts;
3159
+ if (typeof widthOrOptions === "object") {
3160
+ opts = widthOrOptions;
3161
+ } else if (typeof widthOrOptions === "number") {
3162
+ width = widthOrOptions;
3163
+ if (typeof heightOrOptions === "object") {
3164
+ height = widthOrOptions;
3165
+ opts = heightOrOptions;
3166
+ } else if (typeof heightOrOptions === "number") {
3167
+ height = heightOrOptions;
3168
+ opts = options;
3169
+ } else {
3170
+ height = widthOrOptions;
3171
+ }
3172
+ }
2731
3173
  return useMemo(() => {
2732
3174
  const w = width ?? size.width;
2733
3175
  const h = height ?? size.height;
2734
- return new RenderTarget(w, h, options);
2735
- }, [width, height, size.width, size.height, options, isLegacy]);
3176
+ return new RenderTarget(w, h, opts);
3177
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2736
3178
  }
2737
3179
 
2738
3180
  function useStore() {
@@ -2782,7 +3224,7 @@ function addTail(callback) {
2782
3224
  function invalidate(state, frames = 1, stackFrames = false) {
2783
3225
  getScheduler().invalidate(frames, stackFrames);
2784
3226
  }
2785
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3227
+ function advance(timestamp) {
2786
3228
  getScheduler().step(timestamp);
2787
3229
  }
2788
3230
 
@@ -14236,6 +14678,7 @@ function swapInstances() {
14236
14678
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14237
14679
  instance.object.__r3f = instance;
14238
14680
  setFiberRef(fiber, instance.object);
14681
+ delete instance.appliedOnce;
14239
14682
  applyProps(instance.object, instance.props);
14240
14683
  if (instance.props.attach) {
14241
14684
  attach(parent, instance);
@@ -14309,8 +14752,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14309
14752
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14310
14753
  if (isTailSibling) swapInstances();
14311
14754
  },
14312
- finalizeInitialChildren: () => false,
14313
- commitMount() {
14755
+ finalizeInitialChildren: (instance) => {
14756
+ for (const prop in instance.props) {
14757
+ if (isFromRef(instance.props[prop])) return true;
14758
+ }
14759
+ return false;
14760
+ },
14761
+ commitMount(instance) {
14762
+ const resolved = {};
14763
+ for (const prop in instance.props) {
14764
+ const value = instance.props[prop];
14765
+ if (isFromRef(value)) {
14766
+ const ref = value[FROM_REF];
14767
+ if (ref.current != null) resolved[prop] = ref.current;
14768
+ }
14769
+ }
14770
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14314
14771
  },
14315
14772
  getPublicInstance: (instance) => instance?.object,
14316
14773
  prepareForCommit: () => null,
@@ -14531,6 +14988,9 @@ function createRoot(canvas) {
14531
14988
  let resolve;
14532
14989
  pending = new Promise((_resolve) => resolve = _resolve);
14533
14990
  const {
14991
+ id: canvasId,
14992
+ primaryCanvas,
14993
+ scheduler: schedulerConfig,
14534
14994
  gl: glConfig,
14535
14995
  renderer: rendererConfig,
14536
14996
  size: propsSize,
@@ -14538,10 +14998,7 @@ function createRoot(canvas) {
14538
14998
  events,
14539
14999
  onCreated: onCreatedCallback,
14540
15000
  shadows = false,
14541
- linear = false,
14542
- flat = false,
14543
15001
  textureColorSpace = SRGBColorSpace,
14544
- legacy = false,
14545
15002
  orthographic = false,
14546
15003
  frameloop = "always",
14547
15004
  dpr = [1, 2],
@@ -14553,11 +15010,13 @@ function createRoot(canvas) {
14553
15010
  onDropMissed,
14554
15011
  autoUpdateFrustum = true,
14555
15012
  occlusion = false,
14556
- _sizeProps
15013
+ _sizeProps,
15014
+ forceEven
14557
15015
  } = props;
14558
15016
  const state = store.getState();
14559
15017
  const defaultGPUProps = {
14560
- canvas
15018
+ canvas,
15019
+ antialias: true
14561
15020
  };
14562
15021
  if (glConfig && !R3F_BUILD_LEGACY) {
14563
15022
  throw new Error(
@@ -14568,7 +15027,27 @@ function createRoot(canvas) {
14568
15027
  throw new Error("Cannot use both gl and renderer props at the same time");
14569
15028
  }
14570
15029
  let renderer = state.internal.actualRenderer;
14571
- if (!state.internal.actualRenderer) {
15030
+ if (primaryCanvas && !state.internal.actualRenderer) {
15031
+ const primary = await waitForPrimary(primaryCanvas);
15032
+ renderer = primary.renderer;
15033
+ state.internal.actualRenderer = renderer;
15034
+ const canvasTarget = new CanvasTarget(canvas);
15035
+ primary.store.setState((prev) => ({
15036
+ internal: { ...prev.internal, isMultiCanvas: true }
15037
+ }));
15038
+ state.set((prev) => ({
15039
+ webGPUSupported: primary.store.getState().webGPUSupported,
15040
+ renderer,
15041
+ primaryStore: primary.store,
15042
+ internal: {
15043
+ ...prev.internal,
15044
+ canvasTarget,
15045
+ isMultiCanvas: true,
15046
+ isSecondary: true,
15047
+ targetId: primaryCanvas
15048
+ }
15049
+ }));
15050
+ } else if (!state.internal.actualRenderer) {
14572
15051
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14573
15052
  if (!renderer.hasInitialized?.()) {
14574
15053
  await renderer.init();
@@ -14576,7 +15055,18 @@ function createRoot(canvas) {
14576
15055
  const backend = renderer.backend;
14577
15056
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14578
15057
  state.internal.actualRenderer = renderer;
14579
- state.set({ webGPUSupported: isWebGPUBackend, renderer });
15058
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
15059
+ if (canvasId && !state.internal.isSecondary) {
15060
+ const canvasTarget = new CanvasTarget(canvas);
15061
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
15062
+ state.set((prev) => ({
15063
+ internal: {
15064
+ ...prev.internal,
15065
+ canvasTarget,
15066
+ unregisterPrimary
15067
+ }
15068
+ }));
15069
+ }
14580
15070
  }
14581
15071
  let raycaster = state.raycaster;
14582
15072
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14585,6 +15075,7 @@ function createRoot(canvas) {
14585
15075
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14586
15076
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14587
15077
  }
15078
+ let tempCamera = state.camera;
14588
15079
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14589
15080
  lastCamera = cameraOptions;
14590
15081
  const isCamera = cameraOptions?.isCamera;
@@ -14604,6 +15095,7 @@ function createRoot(canvas) {
14604
15095
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14605
15096
  }
14606
15097
  state.set({ camera });
15098
+ tempCamera = camera;
14607
15099
  raycaster.camera = camera;
14608
15100
  }
14609
15101
  if (!state.scene) {
@@ -14621,7 +15113,7 @@ function createRoot(canvas) {
14621
15113
  rootScene: scene,
14622
15114
  internal: { ...prev.internal, container: scene }
14623
15115
  }));
14624
- const camera = state.camera;
15116
+ const camera = tempCamera;
14625
15117
  if (camera && !camera.parent) scene.add(camera);
14626
15118
  }
14627
15119
  if (events && !state.events.handlers) {
@@ -14638,6 +15130,9 @@ function createRoot(canvas) {
14638
15130
  if (_sizeProps !== void 0) {
14639
15131
  state.set({ _sizeProps });
14640
15132
  }
15133
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15134
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15135
+ }
14641
15136
  const size = computeInitialSize(canvas, propsSize);
14642
15137
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14643
15138
  const wasImperative = state._sizeImperative;
@@ -14667,7 +15162,7 @@ function createRoot(canvas) {
14667
15162
  const handleXRFrame = (timestamp, frame) => {
14668
15163
  const state2 = store.getState();
14669
15164
  if (state2.frameloop === "never") return;
14670
- advance(timestamp, true);
15165
+ advance(timestamp);
14671
15166
  };
14672
15167
  const actualRenderer = state.internal.actualRenderer;
14673
15168
  const handleSessionChange = () => {
@@ -14679,16 +15174,16 @@ function createRoot(canvas) {
14679
15174
  };
14680
15175
  const xr = {
14681
15176
  connect() {
14682
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14683
- const actualRenderer2 = renderer2 || gl;
14684
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14685
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15177
+ const { gl, renderer: renderer2 } = store.getState();
15178
+ const xrManager = (renderer2 || gl).xr;
15179
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15180
+ xrManager.addEventListener("sessionend", handleSessionChange);
14686
15181
  },
14687
15182
  disconnect() {
14688
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14689
- const actualRenderer2 = renderer2 || gl;
14690
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14691
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15183
+ const { gl, renderer: renderer2 } = store.getState();
15184
+ const xrManager = (renderer2 || gl).xr;
15185
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15186
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14692
15187
  }
14693
15188
  };
14694
15189
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14716,6 +15211,10 @@ function createRoot(canvas) {
14716
15211
  renderer.shadowMap.needsUpdate = true;
14717
15212
  }
14718
15213
  }
15214
+ if (!configured) {
15215
+ renderer.outputColorSpace = SRGBColorSpace;
15216
+ renderer.toneMapping = ACESFilmicToneMapping;
15217
+ }
14719
15218
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14720
15219
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14721
15220
  lastConfiguredProps.textureColorSpace = textureColorSpace;
@@ -14732,11 +15231,26 @@ function createRoot(canvas) {
14732
15231
  const scheduler = getScheduler();
14733
15232
  const rootId = state.internal.rootId;
14734
15233
  if (!rootId) {
14735
- const newRootId = scheduler.generateRootId();
15234
+ const newRootId = canvasId || scheduler.generateRootId();
14736
15235
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14737
15236
  getState: () => store.getState(),
14738
15237
  onError: (err) => store.getState().setError(err)
14739
15238
  });
15239
+ const unregisterCanvasTarget = scheduler.register(
15240
+ () => {
15241
+ const state2 = store.getState();
15242
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15243
+ const renderer2 = state2.internal.actualRenderer;
15244
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15245
+ }
15246
+ },
15247
+ {
15248
+ id: `${newRootId}_canvasTarget`,
15249
+ rootId: newRootId,
15250
+ phase: "start",
15251
+ system: true
15252
+ }
15253
+ );
14740
15254
  const unregisterFrustum = scheduler.register(
14741
15255
  () => {
14742
15256
  const state2 = store.getState();
@@ -14778,11 +15292,15 @@ function createRoot(canvas) {
14778
15292
  }
14779
15293
  },
14780
15294
  {
14781
- id: `${newRootId}_render`,
15295
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15296
+ id: canvasId || `${newRootId}_render`,
14782
15297
  rootId: newRootId,
14783
15298
  phase: "render",
14784
- system: true
15299
+ system: true,
14785
15300
  // Internal flag: this is a system job, not user-controlled
15301
+ // Apply scheduler config for render ordering and rate limiting
15302
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15303
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14786
15304
  }
14787
15305
  );
14788
15306
  state.set((state2) => ({
@@ -14791,6 +15309,7 @@ function createRoot(canvas) {
14791
15309
  rootId: newRootId,
14792
15310
  unregisterRoot: () => {
14793
15311
  unregisterRoot();
15312
+ unregisterCanvasTarget();
14794
15313
  unregisterFrustum();
14795
15314
  unregisterVisibility();
14796
15315
  unregisterRender();
@@ -14849,15 +15368,24 @@ function unmountComponentAtNode(canvas, callback) {
14849
15368
  const renderer = state.internal.actualRenderer;
14850
15369
  const unregisterRoot = state.internal.unregisterRoot;
14851
15370
  if (unregisterRoot) unregisterRoot();
15371
+ const unregisterPrimary = state.internal.unregisterPrimary;
15372
+ if (unregisterPrimary) unregisterPrimary();
15373
+ const canvasTarget = state.internal.canvasTarget;
15374
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14852
15375
  state.events.disconnect?.();
14853
15376
  cleanupHelperGroup(root.store);
14854
- renderer?.renderLists?.dispose?.();
14855
- renderer?.forceContextLoss?.();
14856
- if (renderer?.xr) state.xr.disconnect();
15377
+ if (state.isLegacy && renderer) {
15378
+ ;
15379
+ renderer.renderLists?.dispose?.();
15380
+ renderer.forceContextLoss?.();
15381
+ }
15382
+ if (!state.internal.isSecondary) {
15383
+ if (renderer?.xr) state.xr.disconnect();
15384
+ }
14857
15385
  dispose(state.scene);
14858
15386
  _roots.delete(canvas);
14859
15387
  if (callback) callback(canvas);
14860
- } catch (e) {
15388
+ } catch {
14861
15389
  }
14862
15390
  }, 500);
14863
15391
  }
@@ -14865,36 +15393,34 @@ function unmountComponentAtNode(canvas, callback) {
14865
15393
  }
14866
15394
  }
14867
15395
  function createPortal(children, container, state) {
14868
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
15396
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14869
15397
  }
14870
- function PortalWrapper({ children, container, state }) {
15398
+ function Portal({ children, container, state }) {
14871
15399
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14872
- const [resolvedContainer, setResolvedContainer] = useState(() => {
15400
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14873
15401
  if (isRef(container)) return container.current ?? null;
14874
15402
  return container;
14875
15403
  });
15404
+ const setResolvedContainer = useCallback(
15405
+ (newContainer) => {
15406
+ if (!newContainer || newContainer === resolvedContainer) return;
15407
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15408
+ },
15409
+ [resolvedContainer, _setResolvedContainer, isRef]
15410
+ );
14876
15411
  useMemo(() => {
14877
- if (isRef(container)) {
14878
- const current = container.current;
14879
- if (!current) {
14880
- queueMicrotask(() => {
14881
- const updated = container.current;
14882
- if (updated && updated !== resolvedContainer) {
14883
- setResolvedContainer(updated);
14884
- }
14885
- });
14886
- } else if (current !== resolvedContainer) {
14887
- setResolvedContainer(current);
14888
- }
14889
- } else if (container !== resolvedContainer) {
14890
- setResolvedContainer(container);
15412
+ if (isRef(container) && !container.current) {
15413
+ return queueMicrotask(() => {
15414
+ setResolvedContainer(container.current);
15415
+ });
14891
15416
  }
14892
- }, [container, resolvedContainer, isRef]);
15417
+ setResolvedContainer(container);
15418
+ }, [container, isRef, setResolvedContainer]);
14893
15419
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14894
15420
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14895
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15421
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14896
15422
  }
14897
- function Portal({ state = {}, children, container }) {
15423
+ function PortalInner({ state = {}, children, container }) {
14898
15424
  const { events, size, injectScene = true, ...rest } = state;
14899
15425
  const previousRoot = useStore();
14900
15426
  const [raycaster] = useState(() => new Raycaster());
@@ -14915,11 +15441,12 @@ function Portal({ state = {}, children, container }) {
14915
15441
  };
14916
15442
  }, [portalScene, container, injectScene]);
14917
15443
  const inject = useMutableCallback((rootState, injectState) => {
15444
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14918
15445
  let viewport = void 0;
14919
- if (injectState.camera && size) {
15446
+ if (injectState.camera && (size || injectState.size)) {
14920
15447
  const camera = injectState.camera;
14921
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14922
- if (camera !== rootState.camera) updateCamera(camera, size);
15448
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
15449
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14923
15450
  }
14924
15451
  return {
14925
15452
  // The intersect consists of the previous root state
@@ -14936,7 +15463,7 @@ function Portal({ state = {}, children, container }) {
14936
15463
  previousRoot,
14937
15464
  // Events, size and viewport can be overridden by the inject layer
14938
15465
  events: { ...rootState.events, ...injectState.events, ...events },
14939
- size: { ...rootState.size, ...size },
15466
+ size: resolvedSize,
14940
15467
  viewport: { ...rootState.viewport, ...viewport },
14941
15468
  // Layers are allowed to override events
14942
15469
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14970,15 +15497,13 @@ function CanvasImpl({
14970
15497
  fallback,
14971
15498
  resize,
14972
15499
  style,
15500
+ id,
14973
15501
  gl,
14974
- renderer,
15502
+ renderer: rendererProp,
14975
15503
  events = createPointerEvents,
14976
15504
  eventSource,
14977
15505
  eventPrefix,
14978
15506
  shadows,
14979
- linear,
14980
- flat,
14981
- legacy,
14982
15507
  orthographic,
14983
15508
  frameloop,
14984
15509
  dpr,
@@ -14993,10 +15518,43 @@ function CanvasImpl({
14993
15518
  hmr,
14994
15519
  width,
14995
15520
  height,
15521
+ background,
15522
+ forceEven,
14996
15523
  ...props
14997
15524
  }) {
15525
+ const { primaryCanvas, scheduler, ...rendererConfig } = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp) ? rendererProp : { primaryCanvas: void 0, scheduler: void 0 };
15526
+ const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
14998
15527
  React.useMemo(() => extend(THREE), []);
14999
15528
  const Bridge = useBridge();
15529
+ const backgroundProps = React.useMemo(() => {
15530
+ if (!background) return null;
15531
+ if (typeof background === "object" && !background.isColor) {
15532
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15533
+ return {
15534
+ ...rest,
15535
+ preset,
15536
+ files: envMap || files,
15537
+ backgroundFiles: backgroundMap,
15538
+ background: true
15539
+ };
15540
+ }
15541
+ if (typeof background === "number") {
15542
+ return { color: background, background: true };
15543
+ }
15544
+ if (typeof background === "string") {
15545
+ if (background in presetsObj) {
15546
+ return { preset: background, background: true };
15547
+ }
15548
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15549
+ return { files: background, background: true };
15550
+ }
15551
+ return { color: background, background: true };
15552
+ }
15553
+ if (background.isColor) {
15554
+ return { color: background, background: true };
15555
+ }
15556
+ return null;
15557
+ }, [background]);
15000
15558
  const hasInitialSizeRef = React.useRef(false);
15001
15559
  const measureConfig = React.useMemo(() => {
15002
15560
  if (!hasInitialSizeRef.current) {
@@ -15013,15 +15571,20 @@ function CanvasImpl({
15013
15571
  };
15014
15572
  }, [resize, hasInitialSizeRef.current]);
15015
15573
  const [containerRef, containerRect] = useMeasure(measureConfig);
15016
- const effectiveSize = React.useMemo(
15017
- () => ({
15018
- width: width ?? containerRect.width,
15019
- height: height ?? containerRect.height,
15574
+ const effectiveSize = React.useMemo(() => {
15575
+ let w = width ?? containerRect.width;
15576
+ let h = height ?? containerRect.height;
15577
+ if (forceEven) {
15578
+ w = Math.ceil(w / 2) * 2;
15579
+ h = Math.ceil(h / 2) * 2;
15580
+ }
15581
+ return {
15582
+ width: w,
15583
+ height: h,
15020
15584
  top: containerRect.top,
15021
15585
  left: containerRect.left
15022
- }),
15023
- [width, height, containerRect]
15024
- );
15586
+ };
15587
+ }, [width, height, containerRect, forceEven]);
15025
15588
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15026
15589
  hasInitialSizeRef.current = true;
15027
15590
  }
@@ -15061,14 +15624,14 @@ function CanvasImpl({
15061
15624
  async function run() {
15062
15625
  if (!effectActiveRef.current || !root.current) return;
15063
15626
  await root.current.configure({
15627
+ id,
15628
+ primaryCanvas,
15629
+ scheduler,
15064
15630
  gl,
15065
15631
  renderer,
15066
15632
  scene,
15067
15633
  events,
15068
15634
  shadows,
15069
- linear,
15070
- flat,
15071
- legacy,
15072
15635
  orthographic,
15073
15636
  frameloop,
15074
15637
  dpr,
@@ -15078,6 +15641,7 @@ function CanvasImpl({
15078
15641
  size: effectiveSize,
15079
15642
  // Store size props for reset functionality
15080
15643
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15644
+ forceEven,
15081
15645
  // Pass mutable reference to onPointerMissed so it's free to update
15082
15646
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15083
15647
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15101,7 +15665,10 @@ function CanvasImpl({
15101
15665
  });
15102
15666
  if (!effectActiveRef.current || !root.current) return;
15103
15667
  root.current.render(
15104
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15668
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
15669
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
15670
+ children ?? null
15671
+ ] }) }) })
15105
15672
  );
15106
15673
  }
15107
15674
  run();
@@ -15128,14 +15695,16 @@ function CanvasImpl({
15128
15695
  const canvas = canvasRef.current;
15129
15696
  if (!canvas) return;
15130
15697
  const handleHMR = () => {
15131
- const rootEntry = _roots.get(canvas);
15132
- if (rootEntry?.store) {
15133
- rootEntry.store.setState((state) => ({
15134
- nodes: {},
15135
- uniforms: {},
15136
- _hmrVersion: state._hmrVersion + 1
15137
- }));
15138
- }
15698
+ queueMicrotask(() => {
15699
+ const rootEntry = _roots.get(canvas);
15700
+ if (rootEntry?.store) {
15701
+ rootEntry.store.setState((state) => ({
15702
+ nodes: {},
15703
+ uniforms: {},
15704
+ _hmrVersion: state._hmrVersion + 1
15705
+ }));
15706
+ }
15707
+ });
15139
15708
  };
15140
15709
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15141
15710
  const hot = import.meta.hot;
@@ -15164,7 +15733,7 @@ function CanvasImpl({
15164
15733
  ...style
15165
15734
  },
15166
15735
  ...props,
15167
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15736
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, id, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15168
15737
  }
15169
15738
  );
15170
15739
  }
@@ -15237,6 +15806,22 @@ let ScopedStore = _ScopedStore;
15237
15806
  function createScopedStore(data) {
15238
15807
  return new ScopedStore(data);
15239
15808
  }
15809
+ function createLazyCreatorState(state) {
15810
+ let _uniforms = null;
15811
+ let _nodes = null;
15812
+ return Object.create(state, {
15813
+ uniforms: {
15814
+ get() {
15815
+ return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
15816
+ }
15817
+ },
15818
+ nodes: {
15819
+ get() {
15820
+ return _nodes ?? (_nodes = createScopedStore(state.nodes));
15821
+ }
15822
+ }
15823
+ });
15824
+ }
15240
15825
 
15241
15826
  function addTexture(set, key, value) {
15242
15827
  set((state) => {
@@ -15277,6 +15862,27 @@ function createTextureOperations(set) {
15277
15862
  removeMultiple: (keys) => removeTextures(set, keys)
15278
15863
  };
15279
15864
  }
15865
+ function extractTSLValue(value) {
15866
+ if (value === null || value === void 0) return value;
15867
+ if (typeof value !== "object") return value;
15868
+ const node = value;
15869
+ if (!node.isNode) return value;
15870
+ if (node.isConstNode) {
15871
+ return node.value;
15872
+ }
15873
+ if ("value" in node) {
15874
+ let extractedValue = node.value;
15875
+ if (typeof node.traverse === "function") {
15876
+ node.traverse((n) => {
15877
+ if (n.isConstNode) {
15878
+ extractedValue = n.value;
15879
+ }
15880
+ });
15881
+ }
15882
+ return extractedValue;
15883
+ }
15884
+ return value;
15885
+ }
15280
15886
  function vectorize(inObject) {
15281
15887
  if (inObject === null || inObject === void 0) return inObject;
15282
15888
  if (typeof inObject === "string") {
@@ -15289,6 +15895,9 @@ function vectorize(inObject) {
15289
15895
  }
15290
15896
  if (typeof inObject !== "object") return inObject;
15291
15897
  const obj = inObject;
15898
+ if (obj.isNode) {
15899
+ return extractTSLValue(inObject);
15900
+ }
15292
15901
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15293
15902
  if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15294
15903
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
@@ -15354,17 +15963,14 @@ function useUniforms(creatorOrScope, scope) {
15354
15963
  const rebuildUniforms = useCallback(
15355
15964
  (targetScope) => {
15356
15965
  store.setState((state) => {
15357
- let newUniforms = state.uniforms;
15966
+ let newUniforms = {};
15358
15967
  if (targetScope && targetScope !== "root") {
15359
15968
  const { [targetScope]: _, ...rest } = state.uniforms;
15360
15969
  newUniforms = rest;
15361
15970
  } else if (targetScope === "root") {
15362
- newUniforms = {};
15363
15971
  for (const [key, value] of Object.entries(state.uniforms)) {
15364
15972
  if (!isUniformNode$1(value)) newUniforms[key] = value;
15365
15973
  }
15366
- } else {
15367
- newUniforms = {};
15368
15974
  }
15369
15975
  return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15370
15976
  });
@@ -15372,20 +15978,26 @@ function useUniforms(creatorOrScope, scope) {
15372
15978
  [store]
15373
15979
  );
15374
15980
  const inputForMemoization = useMemo(() => {
15981
+ let raw = creatorOrScope;
15375
15982
  if (is.fun(creatorOrScope)) {
15376
- const state = store.getState();
15377
- const wrappedState = {
15378
- ...state,
15379
- uniforms: createScopedStore(state.uniforms),
15380
- nodes: createScopedStore(state.nodes)
15381
- };
15382
- return creatorOrScope(wrappedState);
15983
+ const wrappedState = createLazyCreatorState(store.getState());
15984
+ raw = creatorOrScope(wrappedState);
15985
+ }
15986
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
15987
+ const normalized = {};
15988
+ for (const [key, value] of Object.entries(raw)) {
15989
+ normalized[key] = vectorize(value);
15990
+ }
15991
+ return normalized;
15383
15992
  }
15384
- return creatorOrScope;
15993
+ return raw;
15385
15994
  }, [creatorOrScope, store]);
15386
15995
  const memoizedInput = useCompareMemoize(inputForMemoization);
15387
15996
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15388
15997
  const storeUniforms = useThree((s) => s.uniforms);
15998
+ const hmrVersion = useThree((s) => s._hmrVersion);
15999
+ const readerDep = isReader ? storeUniforms : null;
16000
+ const creatorDep = isReader ? null : hmrVersion;
15389
16001
  const uniforms = useMemo(() => {
15390
16002
  if (memoizedInput === void 0) {
15391
16003
  return storeUniforms;
@@ -15440,28 +16052,19 @@ function useUniforms(creatorOrScope, scope) {
15440
16052
  }
15441
16053
  }
15442
16054
  return result;
15443
- }, [
15444
- store,
15445
- memoizedInput,
15446
- scope,
15447
- // Only include storeUniforms in deps for reader modes to enable reactivity
15448
- isReader ? storeUniforms : null
15449
- ]);
16055
+ }, [store, memoizedInput, scope, readerDep, creatorDep]);
15450
16056
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15451
16057
  }
15452
16058
  function rebuildAllUniforms(store, scope) {
15453
16059
  store.setState((state) => {
15454
- let newUniforms = state.uniforms;
16060
+ let newUniforms = {};
15455
16061
  if (scope && scope !== "root") {
15456
16062
  const { [scope]: _, ...rest } = state.uniforms;
15457
16063
  newUniforms = rest;
15458
16064
  } else if (scope === "root") {
15459
- newUniforms = {};
15460
16065
  for (const [key, value] of Object.entries(state.uniforms)) {
15461
16066
  if (!isUniformNode$1(value)) newUniforms[key] = value;
15462
16067
  }
15463
- } else {
15464
- newUniforms = {};
15465
16068
  }
15466
16069
  return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
15467
16070
  });
@@ -15529,15 +16132,17 @@ function isSameThreeType(a, b) {
15529
16132
  }
15530
16133
 
15531
16134
  const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
16135
+ const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15532
16136
  function useUniform(name, value) {
15533
16137
  const store = useStore();
16138
+ const hmrVersion = useThree((s) => s._hmrVersion);
15534
16139
  return useMemo(() => {
15535
16140
  const state = store.getState();
15536
16141
  const set = store.setState;
15537
16142
  const existing = state.uniforms[name];
15538
16143
  if (existing && isUniformNode(existing)) {
15539
- if (value !== void 0) {
15540
- existing.value = value;
16144
+ if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
16145
+ existing.value = typeof value === "string" ? new Color(value) : value;
15541
16146
  }
15542
16147
  return existing;
15543
16148
  }
@@ -15546,7 +16151,24 @@ function useUniform(name, value) {
15546
16151
  `[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
15547
16152
  );
15548
16153
  }
15549
- const node = uniform(value);
16154
+ if (isUniformNode(value)) {
16155
+ const node2 = value;
16156
+ if (typeof node2.setName === "function") {
16157
+ node2.setName(name);
16158
+ }
16159
+ set((s) => ({
16160
+ uniforms: { ...s.uniforms, [name]: node2 }
16161
+ }));
16162
+ return node2;
16163
+ }
16164
+ let node;
16165
+ if (isTSLNode$1(value)) {
16166
+ node = uniform(value);
16167
+ } else if (typeof value === "string") {
16168
+ node = uniform(new Color(value));
16169
+ } else {
16170
+ node = uniform(value);
16171
+ }
15550
16172
  if (typeof node.setName === "function") {
15551
16173
  node.setName(name);
15552
16174
  }
@@ -15557,7 +16179,7 @@ function useUniform(name, value) {
15557
16179
  }
15558
16180
  }));
15559
16181
  return node;
15560
- }, [store, name]);
16182
+ }, [store, name, hmrVersion]);
15561
16183
  }
15562
16184
 
15563
16185
  const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
@@ -15621,6 +16243,9 @@ function useNodes(creatorOrScope, scope) {
15621
16243
  const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
15622
16244
  const storeNodes = useThree((s) => s.nodes);
15623
16245
  const hmrVersion = useThree((s) => s._hmrVersion);
16246
+ const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
16247
+ const readerDep = isReader ? storeNodes : null;
16248
+ const creatorDep = isReader ? null : hmrVersion;
15624
16249
  const nodes = useMemo(() => {
15625
16250
  if (creatorOrScope === void 0) {
15626
16251
  return storeNodes;
@@ -15633,11 +16258,7 @@ function useNodes(creatorOrScope, scope) {
15633
16258
  const state = store.getState();
15634
16259
  const set = store.setState;
15635
16260
  const creator = creatorOrScope;
15636
- const wrappedState = {
15637
- ...state,
15638
- uniforms: createScopedStore(state.uniforms),
15639
- nodes: createScopedStore(state.nodes)
15640
- };
16261
+ const wrappedState = createLazyCreatorState(state);
15641
16262
  const created = creator(wrappedState);
15642
16263
  const result = {};
15643
16264
  let hasNewNodes = false;
@@ -15647,7 +16268,7 @@ function useNodes(creatorOrScope, scope) {
15647
16268
  if (currentScope[name]) {
15648
16269
  result[name] = currentScope[name];
15649
16270
  } else {
15650
- if (typeof node.label === "function") node.setName(`${scope}.${name}`);
16271
+ node.setName?.(`${scope}.${name}`);
15651
16272
  result[name] = node;
15652
16273
  hasNewNodes = true;
15653
16274
  }
@@ -15667,7 +16288,7 @@ function useNodes(creatorOrScope, scope) {
15667
16288
  if (existing && isTSLNode(existing)) {
15668
16289
  result[name] = existing;
15669
16290
  } else {
15670
- if (typeof node.label === "function") node.setName(name);
16291
+ node.setName?.(name);
15671
16292
  result[name] = node;
15672
16293
  hasNewNodes = true;
15673
16294
  }
@@ -15676,15 +16297,7 @@ function useNodes(creatorOrScope, scope) {
15676
16297
  set((s) => ({ nodes: { ...s.nodes, ...result } }));
15677
16298
  }
15678
16299
  return result;
15679
- }, [
15680
- store,
15681
- typeof creatorOrScope === "string" ? creatorOrScope : scope,
15682
- // Only include storeNodes in deps for reader modes to enable reactivity
15683
- // Creator mode intentionally excludes it to avoid re-running creator on unrelated changes
15684
- isReader ? storeNodes : null,
15685
- // Include hmrVersion for creator modes to allow rebuildNodes() to bust the cache
15686
- isReader ? null : hmrVersion
15687
- ]);
16300
+ }, [store, scopeDep, readerDep, creatorDep]);
15688
16301
  return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
15689
16302
  }
15690
16303
  function rebuildAllNodes(store, scope) {
@@ -15736,15 +16349,11 @@ function useLocalNodes(creator) {
15736
16349
  const uniforms = useThree((s) => s.uniforms);
15737
16350
  const nodes = useThree((s) => s.nodes);
15738
16351
  const textures = useThree((s) => s.textures);
16352
+ const hmrVersion = useThree((s) => s._hmrVersion);
15739
16353
  return useMemo(() => {
15740
- const state = store.getState();
15741
- const wrappedState = {
15742
- ...state,
15743
- uniforms: createScopedStore(state.uniforms),
15744
- nodes: createScopedStore(state.nodes)
15745
- };
16354
+ const wrappedState = createLazyCreatorState(store.getState());
15746
16355
  return creator(wrappedState);
15747
- }, [store, creator, uniforms, nodes, textures]);
16356
+ }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15748
16357
  }
15749
16358
 
15750
16359
  function usePostProcessing(mainCB, setupCB) {
@@ -15842,4 +16451,4 @@ function usePostProcessing(mainCB, setupCB) {
15842
16451
 
15843
16452
  extend(THREE);
15844
16453
 
15845
- export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, rebuildAllNodes, rebuildAllUniforms, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };
16454
+ export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getScheduler, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllNodes, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };