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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  import * as three from 'three';
2
- import { WebGLRenderTarget, 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, ColorManagement, LinearSRGBColorSpace, NoToneMapping, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
- import { jsx, Fragment } from 'react/jsx-runtime';
2
+ import { WebGLRenderTarget, 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, Raycaster, OrthographicCamera, PerspectiveCamera, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
5
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
6
6
  import useMeasure from 'react-use-measure';
7
7
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
8
+ 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';
9
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
10
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
11
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
12
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
13
+ import { GainMapLoader } from '@monogrid/gainmap-js';
8
14
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
9
15
  import { createWithEqualityFn } from 'zustand/traditional';
10
16
  import { suspend, preload, clear } from 'suspend-react';
@@ -45,6 +51,374 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
45
51
  WebGPURenderer: WebGPURenderer
46
52
  }, [three]);
47
53
 
54
+ const primaryRegistry = /* @__PURE__ */ new Map();
55
+ const pendingSubscribers = /* @__PURE__ */ new Map();
56
+ function registerPrimary(id, renderer, store) {
57
+ if (primaryRegistry.has(id)) {
58
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
59
+ }
60
+ const entry = { renderer, store };
61
+ primaryRegistry.set(id, entry);
62
+ const subscribers = pendingSubscribers.get(id);
63
+ if (subscribers) {
64
+ subscribers.forEach((callback) => callback(entry));
65
+ pendingSubscribers.delete(id);
66
+ }
67
+ return () => {
68
+ const currentEntry = primaryRegistry.get(id);
69
+ if (currentEntry?.renderer === renderer) {
70
+ primaryRegistry.delete(id);
71
+ }
72
+ };
73
+ }
74
+ function getPrimary(id) {
75
+ return primaryRegistry.get(id);
76
+ }
77
+ function waitForPrimary(id, timeout = 5e3) {
78
+ const existing = primaryRegistry.get(id);
79
+ if (existing) {
80
+ return Promise.resolve(existing);
81
+ }
82
+ return new Promise((resolve, reject) => {
83
+ const timeoutId = setTimeout(() => {
84
+ const subscribers = pendingSubscribers.get(id);
85
+ if (subscribers) {
86
+ const index = subscribers.indexOf(callback);
87
+ if (index !== -1) subscribers.splice(index, 1);
88
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
89
+ }
90
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
91
+ }, timeout);
92
+ const callback = (entry) => {
93
+ clearTimeout(timeoutId);
94
+ resolve(entry);
95
+ };
96
+ if (!pendingSubscribers.has(id)) {
97
+ pendingSubscribers.set(id, []);
98
+ }
99
+ pendingSubscribers.get(id).push(callback);
100
+ });
101
+ }
102
+ function hasPrimary(id) {
103
+ return primaryRegistry.has(id);
104
+ }
105
+ function unregisterPrimary(id) {
106
+ primaryRegistry.delete(id);
107
+ }
108
+ function getPrimaryIds() {
109
+ return Array.from(primaryRegistry.keys());
110
+ }
111
+
112
+ const presetsObj = {
113
+ apartment: "lebombo_1k.hdr",
114
+ city: "potsdamer_platz_1k.hdr",
115
+ dawn: "kiara_1_dawn_1k.hdr",
116
+ forest: "forest_slope_1k.hdr",
117
+ lobby: "st_fagans_interior_1k.hdr",
118
+ night: "dikhololo_night_1k.hdr",
119
+ park: "rooitou_park_1k.hdr",
120
+ studio: "studio_small_03_1k.hdr",
121
+ sunset: "venice_sunset_1k.hdr",
122
+ warehouse: "empty_warehouse_01_1k.hdr"
123
+ };
124
+
125
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
126
+ const isArray = (arr) => Array.isArray(arr);
127
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
128
+ function useEnvironment({
129
+ files = defaultFiles,
130
+ path = "",
131
+ preset = void 0,
132
+ colorSpace = void 0,
133
+ extensions
134
+ } = {}) {
135
+ if (preset) {
136
+ validatePreset(preset);
137
+ files = presetsObj[preset];
138
+ path = CUBEMAP_ROOT;
139
+ }
140
+ const multiFile = isArray(files);
141
+ const { extension, isCubemap } = getExtension(files);
142
+ const loader = getLoader$1(extension);
143
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
144
+ const renderer = useThree$1((state) => state.renderer);
145
+ useLayoutEffect(() => {
146
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
147
+ function clearGainmapTexture() {
148
+ useLoader$1.clear(loader, multiFile ? [files] : files);
149
+ }
150
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
151
+ }, [files, renderer.domElement]);
152
+ const loaderResult = useLoader$1(
153
+ loader,
154
+ multiFile ? [files] : files,
155
+ (loader2) => {
156
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
157
+ loader2.setRenderer?.(renderer);
158
+ }
159
+ loader2.setPath?.(path);
160
+ if (extensions) extensions(loader2);
161
+ }
162
+ );
163
+ let texture = multiFile ? (
164
+ // @ts-ignore
165
+ loaderResult[0]
166
+ ) : loaderResult;
167
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
168
+ texture = texture.renderTarget?.texture;
169
+ }
170
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
171
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
172
+ return texture;
173
+ }
174
+ const preloadDefaultOptions = {
175
+ files: defaultFiles,
176
+ path: "",
177
+ preset: void 0,
178
+ extensions: void 0
179
+ };
180
+ useEnvironment.preload = (preloadOptions) => {
181
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
182
+ let { files, path = "" } = options;
183
+ const { preset, extensions } = options;
184
+ if (preset) {
185
+ validatePreset(preset);
186
+ files = presetsObj[preset];
187
+ path = CUBEMAP_ROOT;
188
+ }
189
+ const { extension } = getExtension(files);
190
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
191
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
192
+ }
193
+ const loader = getLoader$1(extension);
194
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
195
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
196
+ loader2.setPath?.(path);
197
+ if (extensions) extensions(loader2);
198
+ });
199
+ };
200
+ const clearDefaultOptins = {
201
+ files: defaultFiles,
202
+ preset: void 0
203
+ };
204
+ useEnvironment.clear = (clearOptions) => {
205
+ const options = { ...clearDefaultOptins, ...clearOptions };
206
+ let { files } = options;
207
+ const { preset } = options;
208
+ if (preset) {
209
+ validatePreset(preset);
210
+ files = presetsObj[preset];
211
+ }
212
+ const { extension } = getExtension(files);
213
+ const loader = getLoader$1(extension);
214
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
215
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
216
+ };
217
+ function validatePreset(preset) {
218
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
219
+ }
220
+ function getExtension(files) {
221
+ const isCubemap = isArray(files) && files.length === 6;
222
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
223
+ const firstEntry = isArray(files) ? files[0] : files;
224
+ 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();
225
+ return { extension, isCubemap, isGainmap };
226
+ }
227
+ function getLoader$1(extension) {
228
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
229
+ return loader;
230
+ }
231
+
232
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
233
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
234
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
235
+ sceneProps = {
236
+ backgroundBlurriness: 0,
237
+ backgroundIntensity: 1,
238
+ backgroundRotation: [0, 0, 0],
239
+ environmentIntensity: 1,
240
+ environmentRotation: [0, 0, 0],
241
+ ...sceneProps
242
+ };
243
+ const target = resolveScene(scene || defaultScene);
244
+ const oldbg = target.background;
245
+ const oldenv = target.environment;
246
+ const oldSceneProps = {
247
+ // @ts-ignore
248
+ backgroundBlurriness: target.backgroundBlurriness,
249
+ // @ts-ignore
250
+ backgroundIntensity: target.backgroundIntensity,
251
+ // @ts-ignore
252
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
253
+ // @ts-ignore
254
+ environmentIntensity: target.environmentIntensity,
255
+ // @ts-ignore
256
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
257
+ };
258
+ if (background !== "only") target.environment = texture;
259
+ if (background) target.background = texture;
260
+ applyProps$1(target, sceneProps);
261
+ return () => {
262
+ if (background !== "only") target.environment = oldenv;
263
+ if (background) target.background = oldbg;
264
+ applyProps$1(target, oldSceneProps);
265
+ };
266
+ }
267
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
268
+ const defaultScene = useThree$1((state) => state.scene);
269
+ React.useLayoutEffect(() => {
270
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
271
+ });
272
+ return null;
273
+ }
274
+ function EnvironmentCube({
275
+ background = false,
276
+ scene,
277
+ blur,
278
+ backgroundBlurriness,
279
+ backgroundIntensity,
280
+ backgroundRotation,
281
+ environmentIntensity,
282
+ environmentRotation,
283
+ ...rest
284
+ }) {
285
+ const texture = useEnvironment(rest);
286
+ const defaultScene = useThree$1((state) => state.scene);
287
+ React.useLayoutEffect(() => {
288
+ return setEnvProps(background, scene, defaultScene, texture, {
289
+ backgroundBlurriness: blur ?? backgroundBlurriness,
290
+ backgroundIntensity,
291
+ backgroundRotation,
292
+ environmentIntensity,
293
+ environmentRotation
294
+ });
295
+ });
296
+ React.useEffect(() => {
297
+ return () => {
298
+ texture.dispose();
299
+ };
300
+ }, [texture]);
301
+ return null;
302
+ }
303
+ function EnvironmentPortal({
304
+ children,
305
+ near = 0.1,
306
+ far = 1e3,
307
+ resolution = 256,
308
+ frames = 1,
309
+ map,
310
+ background = false,
311
+ blur,
312
+ backgroundBlurriness,
313
+ backgroundIntensity,
314
+ backgroundRotation,
315
+ environmentIntensity,
316
+ environmentRotation,
317
+ scene,
318
+ files,
319
+ path,
320
+ preset = void 0,
321
+ extensions
322
+ }) {
323
+ const gl = useThree$1((state) => state.gl);
324
+ const defaultScene = useThree$1((state) => state.scene);
325
+ const camera = React.useRef(null);
326
+ const [virtualScene] = React.useState(() => new Scene());
327
+ const fbo = React.useMemo(() => {
328
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
329
+ fbo2.texture.type = HalfFloatType;
330
+ return fbo2;
331
+ }, [resolution]);
332
+ React.useEffect(() => {
333
+ return () => {
334
+ fbo.dispose();
335
+ };
336
+ }, [fbo]);
337
+ React.useLayoutEffect(() => {
338
+ if (frames === 1) {
339
+ const autoClear = gl.autoClear;
340
+ gl.autoClear = true;
341
+ camera.current.update(gl, virtualScene);
342
+ gl.autoClear = autoClear;
343
+ }
344
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
345
+ backgroundBlurriness: blur ?? backgroundBlurriness,
346
+ backgroundIntensity,
347
+ backgroundRotation,
348
+ environmentIntensity,
349
+ environmentRotation
350
+ });
351
+ }, [children, virtualScene, fbo.texture, scene, defaultScene, background, frames, gl]);
352
+ let count = 1;
353
+ useFrame$1(() => {
354
+ if (frames === Infinity || count < frames) {
355
+ const autoClear = gl.autoClear;
356
+ gl.autoClear = true;
357
+ camera.current.update(gl, virtualScene);
358
+ gl.autoClear = autoClear;
359
+ count++;
360
+ }
361
+ });
362
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
363
+ /* @__PURE__ */ jsxs(Fragment, { children: [
364
+ children,
365
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
366
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
367
+ ] }),
368
+ virtualScene
369
+ ) });
370
+ }
371
+ function EnvironmentGround(props) {
372
+ const textureDefault = useEnvironment(props);
373
+ const texture = props.map || textureDefault;
374
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
375
+ React.useEffect(() => {
376
+ return () => {
377
+ textureDefault.dispose();
378
+ };
379
+ }, [textureDefault]);
380
+ const height = props.ground?.height ?? 15;
381
+ const radius = props.ground?.radius ?? 60;
382
+ const scale = props.ground?.scale ?? 1e3;
383
+ const args = React.useMemo(
384
+ () => [texture, height, radius],
385
+ [texture, height, radius]
386
+ );
387
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
388
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
389
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
390
+ ] });
391
+ }
392
+ function EnvironmentColor({ color, scene }) {
393
+ const defaultScene = useThree$1((state) => state.scene);
394
+ React.useLayoutEffect(() => {
395
+ if (color === void 0) return;
396
+ const target = resolveScene(scene || defaultScene);
397
+ const oldBg = target.background;
398
+ target.background = new Color(color);
399
+ return () => {
400
+ target.background = oldBg;
401
+ };
402
+ });
403
+ return null;
404
+ }
405
+ function EnvironmentDualSource(props) {
406
+ const { backgroundFiles, ...envProps } = props;
407
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
408
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
409
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
410
+ ] });
411
+ }
412
+ function Environment(props) {
413
+ if (props.color && !props.files && !props.preset && !props.map) {
414
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
415
+ }
416
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
417
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
418
+ }
419
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
420
+ }
421
+
48
422
  var __defProp$2 = Object.defineProperty;
49
423
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
50
424
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -224,7 +598,8 @@ function prepare(target, root, type, props) {
224
598
  object,
225
599
  eventCount: 0,
226
600
  handlers: {},
227
- isHidden: false
601
+ isHidden: false,
602
+ deferredRefs: []
228
603
  };
229
604
  if (object) object.__r3f = instance;
230
605
  }
@@ -273,7 +648,7 @@ function createOcclusionObserverNode(store, uniform) {
273
648
  let occlusionSetupPromise = null;
274
649
  function enableOcclusion(store) {
275
650
  const state = store.getState();
276
- const { internal, renderer, rootScene } = state;
651
+ const { internal, renderer } = state;
277
652
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
278
653
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
279
654
  if (!hasOcclusionSupport) {
@@ -436,6 +811,22 @@ function hasVisibilityHandlers(handlers) {
436
811
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
437
812
  }
438
813
 
814
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
815
+ function fromRef(ref) {
816
+ return { [FROM_REF]: ref };
817
+ }
818
+ function isFromRef(value) {
819
+ return value !== null && typeof value === "object" && FROM_REF in value;
820
+ }
821
+
822
+ const ONCE = Symbol.for("@react-three/fiber.once");
823
+ function once(...args) {
824
+ return { [ONCE]: args.length ? args : true };
825
+ }
826
+ function isOnce(value) {
827
+ return value !== null && typeof value === "object" && ONCE in value;
828
+ }
829
+
439
830
  const RESERVED_PROPS = [
440
831
  "children",
441
832
  "key",
@@ -506,7 +897,7 @@ function getMemoizedPrototype(root) {
506
897
  ctor = new root.constructor();
507
898
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
508
899
  }
509
- } catch (e) {
900
+ } catch {
510
901
  }
511
902
  return ctor;
512
903
  }
@@ -552,6 +943,25 @@ function applyProps(object, props) {
552
943
  continue;
553
944
  }
554
945
  if (value === void 0) continue;
946
+ if (isFromRef(value)) {
947
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
948
+ continue;
949
+ }
950
+ if (isOnce(value)) {
951
+ if (instance?.appliedOnce?.has(prop)) continue;
952
+ if (instance) {
953
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
954
+ instance.appliedOnce.add(prop);
955
+ }
956
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
957
+ const args = value[ONCE];
958
+ if (typeof targetRoot[targetKey] === "function") {
959
+ targetRoot[targetKey](...args === true ? [] : args);
960
+ } else if (args !== true && args.length > 0) {
961
+ targetRoot[targetKey] = args[0];
962
+ }
963
+ continue;
964
+ }
555
965
  let { root, key, target } = resolve(object, prop);
556
966
  if (target === void 0 && (typeof root !== "object" || root === null)) {
557
967
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -574,7 +984,7 @@ function applyProps(object, props) {
574
984
  else target.set(value);
575
985
  } else {
576
986
  root[key] = value;
577
- 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
987
+ 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
578
988
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
579
989
  root[key].colorSpace = rootState.textureColorSpace;
580
990
  }
@@ -931,7 +1341,7 @@ function createPointerEvents(store) {
931
1341
  return {
932
1342
  priority: 1,
933
1343
  enabled: true,
934
- compute(event, state, previous) {
1344
+ compute(event, state) {
935
1345
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
936
1346
  state.raycaster.setFromCamera(state.pointer, state.camera);
937
1347
  },
@@ -1029,331 +1439,26 @@ function notifyAlpha({ message, link }) {
1029
1439
  }
1030
1440
  }
1031
1441
 
1032
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1033
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1034
- const createStore = (invalidate, advance) => {
1035
- const rootStore = createWithEqualityFn((set, get) => {
1036
- const position = new Vector3();
1037
- const defaultTarget = new Vector3();
1038
- const tempTarget = new Vector3();
1039
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1040
- const { width, height, top, left } = size;
1041
- const aspect = width / height;
1042
- if (target.isVector3) tempTarget.copy(target);
1043
- else tempTarget.set(...target);
1044
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1045
- if (isOrthographicCamera(camera)) {
1046
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1047
- } else {
1048
- const fov = camera.fov * Math.PI / 180;
1049
- const h = 2 * Math.tan(fov / 2) * distance;
1050
- const w = h * (width / height);
1051
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1052
- }
1053
- }
1054
- let performanceTimeout = void 0;
1055
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1056
- const pointer = new Vector2();
1057
- const rootState = {
1058
- set,
1059
- get,
1060
- // Mock objects that have to be configured
1061
- gl: null,
1062
- renderer: null,
1063
- camera: null,
1064
- frustum: new Frustum(),
1065
- autoUpdateFrustum: true,
1066
- raycaster: null,
1067
- events: { priority: 1, enabled: true, connected: false },
1068
- scene: null,
1069
- rootScene: null,
1070
- xr: null,
1071
- inspector: null,
1072
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1073
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1074
- legacy: false,
1075
- linear: false,
1076
- flat: false,
1077
- textureColorSpace: "srgb",
1078
- isLegacy: false,
1079
- webGPUSupported: false,
1080
- isNative: false,
1081
- controls: null,
1082
- pointer,
1083
- mouse: pointer,
1084
- frameloop: "always",
1085
- onPointerMissed: void 0,
1086
- onDragOverMissed: void 0,
1087
- onDropMissed: void 0,
1088
- performance: {
1089
- current: 1,
1090
- min: 0.5,
1091
- max: 1,
1092
- debounce: 200,
1093
- regress: () => {
1094
- const state2 = get();
1095
- if (performanceTimeout) clearTimeout(performanceTimeout);
1096
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1097
- performanceTimeout = setTimeout(
1098
- () => setPerformanceCurrent(get().performance.max),
1099
- state2.performance.debounce
1100
- );
1101
- }
1102
- },
1103
- size: { width: 0, height: 0, top: 0, left: 0 },
1104
- viewport: {
1105
- initialDpr: 0,
1106
- dpr: 0,
1107
- width: 0,
1108
- height: 0,
1109
- top: 0,
1110
- left: 0,
1111
- aspect: 0,
1112
- distance: 0,
1113
- factor: 0,
1114
- getCurrentViewport
1115
- },
1116
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1117
- setSize: (width, height, top, left) => {
1118
- const state2 = get();
1119
- if (width === void 0) {
1120
- set({ _sizeImperative: false });
1121
- if (state2._sizeProps) {
1122
- const { width: propW, height: propH } = state2._sizeProps;
1123
- if (propW !== void 0 || propH !== void 0) {
1124
- const currentSize = state2.size;
1125
- const newSize = {
1126
- width: propW ?? currentSize.width,
1127
- height: propH ?? currentSize.height,
1128
- top: currentSize.top,
1129
- left: currentSize.left
1130
- };
1131
- set((s) => ({
1132
- size: newSize,
1133
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1134
- }));
1135
- }
1136
- }
1137
- return;
1138
- }
1139
- const w = width;
1140
- const h = height ?? width;
1141
- const t = top ?? state2.size.top;
1142
- const l = left ?? state2.size.left;
1143
- const size = { width: w, height: h, top: t, left: l };
1144
- set((s) => ({
1145
- size,
1146
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1147
- _sizeImperative: true
1148
- }));
1149
- },
1150
- setDpr: (dpr) => set((state2) => {
1151
- const resolved = calculateDpr(dpr);
1152
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1153
- }),
1154
- setFrameloop: (frameloop = "always") => {
1155
- set(() => ({ frameloop }));
1156
- },
1157
- setError: (error) => set(() => ({ error })),
1158
- error: null,
1159
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1160
- uniforms: {},
1161
- nodes: {},
1162
- textures: /* @__PURE__ */ new Map(),
1163
- postProcessing: null,
1164
- passes: {},
1165
- _hmrVersion: 0,
1166
- _sizeImperative: false,
1167
- _sizeProps: null,
1168
- previousRoot: void 0,
1169
- internal: {
1170
- // Events
1171
- interaction: [],
1172
- hovered: /* @__PURE__ */ new Map(),
1173
- subscribers: [],
1174
- initialClick: [0, 0],
1175
- initialHits: [],
1176
- capturedMap: /* @__PURE__ */ new Map(),
1177
- lastEvent: React.createRef(),
1178
- // Visibility tracking (onFramed, onOccluded, onVisible)
1179
- visibilityRegistry: /* @__PURE__ */ new Map(),
1180
- // Occlusion system (WebGPU only)
1181
- occlusionEnabled: false,
1182
- occlusionObserver: null,
1183
- occlusionCache: /* @__PURE__ */ new Map(),
1184
- helperGroup: null,
1185
- // Updates
1186
- active: false,
1187
- frames: 0,
1188
- priority: 0,
1189
- subscribe: (ref, priority, store) => {
1190
- const internal = get().internal;
1191
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1192
- internal.subscribers.push({ ref, priority, store });
1193
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1194
- return () => {
1195
- const internal2 = get().internal;
1196
- if (internal2?.subscribers) {
1197
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1198
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1199
- }
1200
- };
1201
- },
1202
- // Renderer Storage (single source of truth)
1203
- actualRenderer: null,
1204
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1205
- scheduler: null
1206
- }
1207
- };
1208
- return rootState;
1209
- });
1210
- const state = rootStore.getState();
1211
- Object.defineProperty(state, "gl", {
1212
- get() {
1213
- const currentState = rootStore.getState();
1214
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1215
- const stack = new Error().stack || "";
1216
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1217
- if (!isInternalAccess) {
1218
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1219
- notifyDepreciated({
1220
- heading: "Accessing state.gl in WebGPU mode",
1221
- 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
1222
- });
1223
- }
1224
- }
1225
- return currentState.internal.actualRenderer;
1226
- },
1227
- set(value) {
1228
- rootStore.getState().internal.actualRenderer = value;
1229
- },
1230
- enumerable: true,
1231
- configurable: true
1232
- });
1233
- Object.defineProperty(state, "renderer", {
1234
- get() {
1235
- return rootStore.getState().internal.actualRenderer;
1236
- },
1237
- set(value) {
1238
- rootStore.getState().internal.actualRenderer = value;
1239
- },
1240
- enumerable: true,
1241
- configurable: true
1242
- });
1243
- let oldScene = state.scene;
1244
- rootStore.subscribe(() => {
1245
- const currentState = rootStore.getState();
1246
- const { scene, rootScene, set } = currentState;
1247
- if (scene !== oldScene) {
1248
- oldScene = scene;
1249
- if (scene?.isScene && scene !== rootScene) {
1250
- set({ rootScene: scene });
1251
- }
1252
- }
1253
- });
1254
- let oldSize = state.size;
1255
- let oldDpr = state.viewport.dpr;
1256
- let oldCamera = state.camera;
1257
- rootStore.subscribe(() => {
1258
- const { camera, size, viewport, set, internal } = rootStore.getState();
1259
- const actualRenderer = internal.actualRenderer;
1260
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1261
- oldSize = size;
1262
- oldDpr = viewport.dpr;
1263
- updateCamera(camera, size);
1264
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1265
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1266
- actualRenderer.setSize(size.width, size.height, updateStyle);
1267
- }
1268
- if (camera !== oldCamera) {
1269
- oldCamera = camera;
1270
- const { rootScene } = rootStore.getState();
1271
- if (camera && rootScene && !camera.parent) {
1272
- rootScene.add(camera);
1273
- }
1274
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1275
- const currentState = rootStore.getState();
1276
- if (currentState.autoUpdateFrustum && camera) {
1277
- updateFrustum(camera, currentState.frustum);
1278
- }
1279
- }
1280
- });
1281
- rootStore.subscribe((state2) => invalidate(state2));
1282
- return rootStore;
1283
- };
1284
-
1285
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1286
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1287
- function getLoader(Proto) {
1288
- if (isConstructor$1(Proto)) {
1289
- let loader = memoizedLoaders.get(Proto);
1290
- if (!loader) {
1291
- loader = new Proto();
1292
- memoizedLoaders.set(Proto, loader);
1293
- }
1294
- return loader;
1295
- }
1296
- return Proto;
1297
- }
1298
- function loadingFn(extensions, onProgress) {
1299
- return function(Proto, input) {
1300
- const loader = getLoader(Proto);
1301
- if (extensions) extensions(loader);
1302
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1303
- return loader.loadAsync(input, onProgress).then((data) => {
1304
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1305
- return data;
1306
- });
1307
- }
1308
- return new Promise(
1309
- (res, reject) => loader.load(
1310
- input,
1311
- (data) => {
1312
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1313
- res(data);
1314
- },
1315
- onProgress,
1316
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1317
- )
1318
- );
1319
- };
1320
- }
1321
- function useLoader(loader, input, extensions, onProgress) {
1322
- const keys = Array.isArray(input) ? input : [input];
1323
- const fn = loadingFn(extensions, onProgress);
1324
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1325
- return Array.isArray(input) ? results : results[0];
1326
- }
1327
- useLoader.preload = function(loader, input, extensions, onProgress) {
1328
- const keys = Array.isArray(input) ? input : [input];
1329
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1330
- };
1331
- useLoader.clear = function(loader, input) {
1332
- const keys = Array.isArray(input) ? input : [input];
1333
- keys.forEach((key) => clear([loader, key]));
1334
- };
1335
- useLoader.loader = getLoader;
1336
-
1337
- var __defProp$1 = Object.defineProperty;
1338
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1339
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1340
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1341
- class PhaseGraph {
1342
- constructor() {
1343
- /** Ordered list of phase nodes */
1344
- __publicField$1(this, "phases", []);
1345
- /** Quick lookup by name */
1346
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1347
- /** Cached ordered names (invalidated on changes) */
1348
- __publicField$1(this, "orderedNamesCache", null);
1349
- this.initializeDefaultPhases();
1350
- }
1351
- //* Initialization --------------------------------
1352
- initializeDefaultPhases() {
1353
- for (const name of DEFAULT_PHASES) {
1354
- const node = { name, isAutoGenerated: false };
1355
- this.phases.push(node);
1356
- this.phaseMap.set(name, node);
1442
+ var __defProp$1 = Object.defineProperty;
1443
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1444
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1445
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1446
+ class PhaseGraph {
1447
+ constructor() {
1448
+ /** Ordered list of phase nodes */
1449
+ __publicField$1(this, "phases", []);
1450
+ /** Quick lookup by name */
1451
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1452
+ /** Cached ordered names (invalidated on changes) */
1453
+ __publicField$1(this, "orderedNamesCache", null);
1454
+ this.initializeDefaultPhases();
1455
+ }
1456
+ //* Initialization --------------------------------
1457
+ initializeDefaultPhases() {
1458
+ for (const name of DEFAULT_PHASES) {
1459
+ const node = { name, isAutoGenerated: false };
1460
+ this.phases.push(node);
1461
+ this.phaseMap.set(name, node);
1357
1462
  }
1358
1463
  this.invalidateCache();
1359
1464
  }
@@ -2239,132 +2344,445 @@ const _Scheduler = class _Scheduler {
2239
2344
  console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2240
2345
  }
2241
2346
  }
2242
- }
2243
- /**
2244
- * Execute all jobs for a single root in sorted order.
2245
- * Rebuilds sorted job list if needed, then dispatches each job.
2246
- * Errors are caught and propagated via triggerError.
2247
- * @param {RootEntry} root - The root entry to tick
2248
- * @param {number} timestamp - RAF timestamp in milliseconds
2249
- * @param {number} delta - Time since last frame in seconds
2250
- * @returns {void}
2251
- * @private
2252
- */
2253
- tickRoot(root, timestamp, delta) {
2254
- if (root.needsRebuild) {
2255
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2256
- root.needsRebuild = false;
2347
+ }
2348
+ /**
2349
+ * Execute all jobs for a single root in sorted order.
2350
+ * Rebuilds sorted job list if needed, then dispatches each job.
2351
+ * Errors are caught and propagated via triggerError.
2352
+ * @param {RootEntry} root - The root entry to tick
2353
+ * @param {number} timestamp - RAF timestamp in milliseconds
2354
+ * @param {number} delta - Time since last frame in seconds
2355
+ * @returns {void}
2356
+ * @private
2357
+ */
2358
+ tickRoot(root, timestamp, delta) {
2359
+ if (root.needsRebuild) {
2360
+ root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2361
+ root.needsRebuild = false;
2362
+ }
2363
+ const providedState = root.getState?.() ?? {};
2364
+ const frameState = {
2365
+ ...providedState,
2366
+ time: timestamp,
2367
+ delta,
2368
+ elapsed: this.loopState.elapsedTime / 1e3,
2369
+ // Convert ms to seconds
2370
+ frame: this.loopState.frameCount
2371
+ };
2372
+ for (const job of root.sortedJobs) {
2373
+ if (!shouldRun(job, timestamp)) continue;
2374
+ try {
2375
+ job.callback(frameState, delta);
2376
+ } catch (error) {
2377
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2378
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2379
+ }
2380
+ }
2381
+ }
2382
+ //* Debug & Inspection Methods ================================
2383
+ /**
2384
+ * Get the total number of registered jobs across all roots.
2385
+ * Includes both per-root jobs and global before/after jobs.
2386
+ * @returns {number} Total job count
2387
+ */
2388
+ getJobCount() {
2389
+ let count = 0;
2390
+ for (const root of this.roots.values()) {
2391
+ count += root.jobs.size;
2392
+ }
2393
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2394
+ }
2395
+ /**
2396
+ * Get all registered job IDs across all roots.
2397
+ * Includes both per-root jobs and global before/after jobs.
2398
+ * @returns {string[]} Array of all job IDs
2399
+ */
2400
+ getJobIds() {
2401
+ const ids = [];
2402
+ for (const root of this.roots.values()) {
2403
+ ids.push(...root.jobs.keys());
2404
+ }
2405
+ ids.push(...this.globalBeforeJobs.keys());
2406
+ ids.push(...this.globalAfterJobs.keys());
2407
+ return ids;
2408
+ }
2409
+ /**
2410
+ * Get the number of registered roots (Canvas instances).
2411
+ * @returns {number} Number of registered roots
2412
+ */
2413
+ getRootCount() {
2414
+ return this.roots.size;
2415
+ }
2416
+ /**
2417
+ * Check if any user (non-system) jobs are registered in a specific phase.
2418
+ * Used by the default render job to know if a user has taken over rendering.
2419
+ *
2420
+ * @param phase The phase to check
2421
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2422
+ * @returns true if any user jobs exist in the phase
2423
+ */
2424
+ hasUserJobsInPhase(phase, rootId) {
2425
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2426
+ return rootsToCheck.some((root) => {
2427
+ if (!root) return false;
2428
+ for (const job of root.jobs.values()) {
2429
+ if (job.phase === phase && !job.system && job.enabled) return true;
2430
+ }
2431
+ return false;
2432
+ });
2433
+ }
2434
+ //* Utility Methods ================================
2435
+ /**
2436
+ * Generate a unique root ID for automatic root registration.
2437
+ * @returns {string} A unique root ID in the format 'root_N'
2438
+ */
2439
+ generateRootId() {
2440
+ return `root_${this.nextRootIndex++}`;
2441
+ }
2442
+ /**
2443
+ * Generate a unique job ID.
2444
+ * @returns {string} A unique job ID in the format 'job_N'
2445
+ * @private
2446
+ */
2447
+ generateJobId() {
2448
+ return `job_${this.nextJobIndex}`;
2449
+ }
2450
+ /**
2451
+ * Normalize before/after constraints to a Set.
2452
+ * Handles undefined, single string, or array inputs.
2453
+ * @param {string | string[] | undefined} value - The constraint value(s)
2454
+ * @returns {Set<string>} Normalized Set of constraint strings
2455
+ * @private
2456
+ */
2457
+ normalizeConstraints(value) {
2458
+ if (!value) return /* @__PURE__ */ new Set();
2459
+ if (Array.isArray(value)) return new Set(value);
2460
+ return /* @__PURE__ */ new Set([value]);
2461
+ }
2462
+ };
2463
+ //* Static State & Methods (Singleton Usage) ================================
2464
+ //* Cross-Bundle Singleton Key ==============================
2465
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2466
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2467
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2468
+ let Scheduler = _Scheduler;
2469
+ const getScheduler = () => Scheduler.get();
2470
+ if (hmrData) {
2471
+ hmrData.accept?.();
2472
+ }
2473
+
2474
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2475
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
2476
+ const createStore = (invalidate, advance) => {
2477
+ const rootStore = createWithEqualityFn((set, get) => {
2478
+ const position = new Vector3();
2479
+ const defaultTarget = new Vector3();
2480
+ const tempTarget = new Vector3();
2481
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2482
+ const { width, height, top, left } = size;
2483
+ const aspect = width / height;
2484
+ if (target.isVector3) tempTarget.copy(target);
2485
+ else tempTarget.set(...target);
2486
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2487
+ if (isOrthographicCamera(camera)) {
2488
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2489
+ } else {
2490
+ const fov = camera.fov * Math.PI / 180;
2491
+ const h = 2 * Math.tan(fov / 2) * distance;
2492
+ const w = h * (width / height);
2493
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2494
+ }
2495
+ }
2496
+ let performanceTimeout = void 0;
2497
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2498
+ const pointer = new Vector2();
2499
+ const rootState = {
2500
+ set,
2501
+ get,
2502
+ // Mock objects that have to be configured
2503
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2504
+ primaryStore: null,
2505
+ gl: null,
2506
+ renderer: null,
2507
+ camera: null,
2508
+ frustum: new Frustum(),
2509
+ autoUpdateFrustum: true,
2510
+ raycaster: null,
2511
+ events: { priority: 1, enabled: true, connected: false },
2512
+ scene: null,
2513
+ rootScene: null,
2514
+ xr: null,
2515
+ inspector: null,
2516
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2517
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2518
+ textureColorSpace: SRGBColorSpace,
2519
+ isLegacy: false,
2520
+ webGPUSupported: false,
2521
+ isNative: false,
2522
+ controls: null,
2523
+ pointer,
2524
+ mouse: pointer,
2525
+ frameloop: "always",
2526
+ onPointerMissed: void 0,
2527
+ onDragOverMissed: void 0,
2528
+ onDropMissed: void 0,
2529
+ performance: {
2530
+ current: 1,
2531
+ min: 0.5,
2532
+ max: 1,
2533
+ debounce: 200,
2534
+ regress: () => {
2535
+ const state2 = get();
2536
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2537
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2538
+ performanceTimeout = setTimeout(
2539
+ () => setPerformanceCurrent(get().performance.max),
2540
+ state2.performance.debounce
2541
+ );
2542
+ }
2543
+ },
2544
+ size: { width: 0, height: 0, top: 0, left: 0 },
2545
+ viewport: {
2546
+ initialDpr: 0,
2547
+ dpr: 0,
2548
+ width: 0,
2549
+ height: 0,
2550
+ top: 0,
2551
+ left: 0,
2552
+ aspect: 0,
2553
+ distance: 0,
2554
+ factor: 0,
2555
+ getCurrentViewport
2556
+ },
2557
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2558
+ setSize: (width, height, top, left) => {
2559
+ const state2 = get();
2560
+ if (width === void 0) {
2561
+ set({ _sizeImperative: false });
2562
+ if (state2._sizeProps) {
2563
+ const { width: propW, height: propH } = state2._sizeProps;
2564
+ if (propW !== void 0 || propH !== void 0) {
2565
+ const currentSize = state2.size;
2566
+ const newSize = {
2567
+ width: propW ?? currentSize.width,
2568
+ height: propH ?? currentSize.height,
2569
+ top: currentSize.top,
2570
+ left: currentSize.left
2571
+ };
2572
+ set((s) => ({
2573
+ size: newSize,
2574
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2575
+ }));
2576
+ getScheduler().invalidate();
2577
+ }
2578
+ }
2579
+ return;
2580
+ }
2581
+ const w = width;
2582
+ const h = height ?? width;
2583
+ const t = top ?? state2.size.top;
2584
+ const l = left ?? state2.size.left;
2585
+ const size = { width: w, height: h, top: t, left: l };
2586
+ set((s) => ({
2587
+ size,
2588
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2589
+ _sizeImperative: true
2590
+ }));
2591
+ getScheduler().invalidate();
2592
+ },
2593
+ setDpr: (dpr) => set((state2) => {
2594
+ const resolved = calculateDpr(dpr);
2595
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2596
+ }),
2597
+ setFrameloop: (frameloop = "always") => {
2598
+ set(() => ({ frameloop }));
2599
+ },
2600
+ setError: (error) => set(() => ({ error })),
2601
+ error: null,
2602
+ //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2603
+ uniforms: {},
2604
+ nodes: {},
2605
+ textures: /* @__PURE__ */ new Map(),
2606
+ postProcessing: null,
2607
+ passes: {},
2608
+ _hmrVersion: 0,
2609
+ _sizeImperative: false,
2610
+ _sizeProps: null,
2611
+ previousRoot: void 0,
2612
+ internal: {
2613
+ // Events
2614
+ interaction: [],
2615
+ hovered: /* @__PURE__ */ new Map(),
2616
+ subscribers: [],
2617
+ initialClick: [0, 0],
2618
+ initialHits: [],
2619
+ capturedMap: /* @__PURE__ */ new Map(),
2620
+ lastEvent: React.createRef(),
2621
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2622
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2623
+ // Occlusion system (WebGPU only)
2624
+ occlusionEnabled: false,
2625
+ occlusionObserver: null,
2626
+ occlusionCache: /* @__PURE__ */ new Map(),
2627
+ helperGroup: null,
2628
+ // Updates
2629
+ active: false,
2630
+ frames: 0,
2631
+ priority: 0,
2632
+ subscribe: (ref, priority, store) => {
2633
+ const internal = get().internal;
2634
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2635
+ internal.subscribers.push({ ref, priority, store });
2636
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2637
+ return () => {
2638
+ const internal2 = get().internal;
2639
+ if (internal2?.subscribers) {
2640
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2641
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2642
+ }
2643
+ };
2644
+ },
2645
+ // Renderer Storage (single source of truth)
2646
+ actualRenderer: null,
2647
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2648
+ scheduler: null
2649
+ }
2650
+ };
2651
+ return rootState;
2652
+ });
2653
+ const state = rootStore.getState();
2654
+ Object.defineProperty(state, "gl", {
2655
+ get() {
2656
+ const currentState = rootStore.getState();
2657
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2658
+ const stack = new Error().stack || "";
2659
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2660
+ if (!isInternalAccess) {
2661
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2662
+ notifyDepreciated({
2663
+ heading: "Accessing state.gl in WebGPU mode",
2664
+ 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
2665
+ });
2666
+ }
2667
+ }
2668
+ return currentState.internal.actualRenderer;
2669
+ },
2670
+ set(value) {
2671
+ rootStore.getState().internal.actualRenderer = value;
2672
+ },
2673
+ enumerable: true,
2674
+ configurable: true
2675
+ });
2676
+ Object.defineProperty(state, "renderer", {
2677
+ get() {
2678
+ return rootStore.getState().internal.actualRenderer;
2679
+ },
2680
+ set(value) {
2681
+ rootStore.getState().internal.actualRenderer = value;
2682
+ },
2683
+ enumerable: true,
2684
+ configurable: true
2685
+ });
2686
+ let oldScene = state.scene;
2687
+ rootStore.subscribe(() => {
2688
+ const currentState = rootStore.getState();
2689
+ const { scene, rootScene, set } = currentState;
2690
+ if (scene !== oldScene) {
2691
+ oldScene = scene;
2692
+ if (scene?.isScene && scene !== rootScene) {
2693
+ set({ rootScene: scene });
2694
+ }
2695
+ }
2696
+ });
2697
+ let oldSize = state.size;
2698
+ let oldDpr = state.viewport.dpr;
2699
+ let oldCamera = state.camera;
2700
+ rootStore.subscribe(() => {
2701
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2702
+ const actualRenderer = internal.actualRenderer;
2703
+ const canvasTarget = internal.canvasTarget;
2704
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2705
+ oldSize = size;
2706
+ oldDpr = viewport.dpr;
2707
+ updateCamera(camera, size);
2708
+ if (canvasTarget) {
2709
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2710
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && canvasTarget.domElement instanceof HTMLCanvasElement;
2711
+ canvasTarget.setSize(size.width, size.height, updateStyle);
2712
+ } else {
2713
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2714
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
2715
+ actualRenderer.setSize(size.width, size.height, updateStyle);
2716
+ }
2257
2717
  }
2258
- const providedState = root.getState?.() ?? {};
2259
- const frameState = {
2260
- ...providedState,
2261
- time: timestamp,
2262
- delta,
2263
- elapsed: this.loopState.elapsedTime / 1e3,
2264
- // Convert ms to seconds
2265
- frame: this.loopState.frameCount
2266
- };
2267
- for (const job of root.sortedJobs) {
2268
- if (!shouldRun(job, timestamp)) continue;
2269
- try {
2270
- job.callback(frameState, delta);
2271
- } catch (error) {
2272
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2273
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2718
+ if (camera !== oldCamera) {
2719
+ oldCamera = camera;
2720
+ const { rootScene } = rootStore.getState();
2721
+ if (camera && rootScene && !camera.parent) {
2722
+ rootScene.add(camera);
2723
+ }
2724
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2725
+ const currentState = rootStore.getState();
2726
+ if (currentState.autoUpdateFrustum && camera) {
2727
+ updateFrustum(camera, currentState.frustum);
2274
2728
  }
2275
2729
  }
2276
- }
2277
- //* Debug & Inspection Methods ================================
2278
- /**
2279
- * Get the total number of registered jobs across all roots.
2280
- * Includes both per-root jobs and global before/after jobs.
2281
- * @returns {number} Total job count
2282
- */
2283
- getJobCount() {
2284
- let count = 0;
2285
- for (const root of this.roots.values()) {
2286
- count += root.jobs.size;
2730
+ });
2731
+ rootStore.subscribe((state2) => invalidate(state2));
2732
+ return rootStore;
2733
+ };
2734
+
2735
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2736
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2737
+ function getLoader(Proto) {
2738
+ if (isConstructor$1(Proto)) {
2739
+ let loader = memoizedLoaders.get(Proto);
2740
+ if (!loader) {
2741
+ loader = new Proto();
2742
+ memoizedLoaders.set(Proto, loader);
2287
2743
  }
2288
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2744
+ return loader;
2289
2745
  }
2290
- /**
2291
- * Get all registered job IDs across all roots.
2292
- * Includes both per-root jobs and global before/after jobs.
2293
- * @returns {string[]} Array of all job IDs
2294
- */
2295
- getJobIds() {
2296
- const ids = [];
2297
- for (const root of this.roots.values()) {
2298
- ids.push(...root.jobs.keys());
2746
+ return Proto;
2747
+ }
2748
+ function loadingFn(extensions, onProgress) {
2749
+ return function(Proto, input) {
2750
+ const loader = getLoader(Proto);
2751
+ if (extensions) extensions(loader);
2752
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2753
+ return loader.loadAsync(input, onProgress).then((data) => {
2754
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2755
+ return data;
2756
+ });
2299
2757
  }
2300
- ids.push(...this.globalBeforeJobs.keys());
2301
- ids.push(...this.globalAfterJobs.keys());
2302
- return ids;
2303
- }
2304
- /**
2305
- * Get the number of registered roots (Canvas instances).
2306
- * @returns {number} Number of registered roots
2307
- */
2308
- getRootCount() {
2309
- return this.roots.size;
2310
- }
2311
- /**
2312
- * Check if any user (non-system) jobs are registered in a specific phase.
2313
- * Used by the default render job to know if a user has taken over rendering.
2314
- *
2315
- * @param phase The phase to check
2316
- * @param rootId Optional root ID to check (checks all roots if not provided)
2317
- * @returns true if any user jobs exist in the phase
2318
- */
2319
- hasUserJobsInPhase(phase, rootId) {
2320
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2321
- return rootsToCheck.some((root) => {
2322
- if (!root) return false;
2323
- for (const job of root.jobs.values()) {
2324
- if (job.phase === phase && !job.system && job.enabled) return true;
2325
- }
2326
- return false;
2327
- });
2328
- }
2329
- //* Utility Methods ================================
2330
- /**
2331
- * Generate a unique root ID for automatic root registration.
2332
- * @returns {string} A unique root ID in the format 'root_N'
2333
- */
2334
- generateRootId() {
2335
- return `root_${this.nextRootIndex++}`;
2336
- }
2337
- /**
2338
- * Generate a unique job ID.
2339
- * @returns {string} A unique job ID in the format 'job_N'
2340
- * @private
2341
- */
2342
- generateJobId() {
2343
- return `job_${this.nextJobIndex}`;
2344
- }
2345
- /**
2346
- * Normalize before/after constraints to a Set.
2347
- * Handles undefined, single string, or array inputs.
2348
- * @param {string | string[] | undefined} value - The constraint value(s)
2349
- * @returns {Set<string>} Normalized Set of constraint strings
2350
- * @private
2351
- */
2352
- normalizeConstraints(value) {
2353
- if (!value) return /* @__PURE__ */ new Set();
2354
- if (Array.isArray(value)) return new Set(value);
2355
- return /* @__PURE__ */ new Set([value]);
2356
- }
2357
- };
2358
- //* Static State & Methods (Singleton Usage) ================================
2359
- //* Cross-Bundle Singleton Key ==============================
2360
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2361
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2362
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2363
- let Scheduler = _Scheduler;
2364
- const getScheduler = () => Scheduler.get();
2365
- if (hmrData) {
2366
- hmrData.accept?.();
2758
+ return new Promise(
2759
+ (res, reject) => loader.load(
2760
+ input,
2761
+ (data) => {
2762
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2763
+ res(data);
2764
+ },
2765
+ onProgress,
2766
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2767
+ )
2768
+ );
2769
+ };
2770
+ }
2771
+ function useLoader(loader, input, extensions, onProgress) {
2772
+ const keys = Array.isArray(input) ? input : [input];
2773
+ const fn = loadingFn(extensions, onProgress);
2774
+ const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
2775
+ return Array.isArray(input) ? results : results[0];
2367
2776
  }
2777
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2778
+ const keys = Array.isArray(input) ? input : [input];
2779
+ keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2780
+ };
2781
+ useLoader.clear = function(loader, input) {
2782
+ const keys = Array.isArray(input) ? input : [input];
2783
+ keys.forEach((key) => clear([loader, key]));
2784
+ };
2785
+ useLoader.loader = getLoader;
2368
2786
 
2369
2787
  function useFrame(callback, priorityOrOptions) {
2370
2788
  const store = React.useContext(context);
@@ -2545,6 +2963,9 @@ function useTexture(input, optionsOrOnLoad) {
2545
2963
  const textureCache = useThree((state) => state.textures);
2546
2964
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2547
2965
  const { onLoad, cache = false } = options;
2966
+ const onLoadRef = useRef(onLoad);
2967
+ onLoadRef.current = onLoad;
2968
+ const onLoadCalledForRef = useRef(null);
2548
2969
  const urls = useMemo(() => getUrls(input), [input]);
2549
2970
  const cachedResult = useMemo(() => {
2550
2971
  if (!cache) return null;
@@ -2555,9 +2976,13 @@ function useTexture(input, optionsOrOnLoad) {
2555
2976
  TextureLoader,
2556
2977
  IsObject(input) ? Object.values(input) : input
2557
2978
  );
2979
+ const inputKey = urls.join("\0");
2558
2980
  useLayoutEffect(() => {
2559
- if (!cachedResult) onLoad?.(loadedTextures);
2560
- }, [onLoad, cachedResult, loadedTextures]);
2981
+ if (cachedResult) return;
2982
+ if (onLoadCalledForRef.current === inputKey) return;
2983
+ onLoadCalledForRef.current = inputKey;
2984
+ onLoadRef.current?.(loadedTextures);
2985
+ }, [cachedResult, loadedTextures, inputKey]);
2561
2986
  useEffect(() => {
2562
2987
  if (cachedResult) return;
2563
2988
  if ("initTexture" in renderer) {
@@ -2724,14 +3149,31 @@ function useTextures() {
2724
3149
  }, [store]);
2725
3150
  }
2726
3151
 
2727
- function useRenderTarget(width, height, options) {
3152
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2728
3153
  const isLegacy = useThree((s) => s.isLegacy);
2729
3154
  const size = useThree((s) => s.size);
3155
+ let width;
3156
+ let height;
3157
+ let opts;
3158
+ if (typeof widthOrOptions === "object") {
3159
+ opts = widthOrOptions;
3160
+ } else if (typeof widthOrOptions === "number") {
3161
+ width = widthOrOptions;
3162
+ if (typeof heightOrOptions === "object") {
3163
+ height = widthOrOptions;
3164
+ opts = heightOrOptions;
3165
+ } else if (typeof heightOrOptions === "number") {
3166
+ height = heightOrOptions;
3167
+ opts = options;
3168
+ } else {
3169
+ height = widthOrOptions;
3170
+ }
3171
+ }
2730
3172
  return useMemo(() => {
2731
3173
  const w = width ?? size.width;
2732
3174
  const h = height ?? size.height;
2733
- return new WebGLRenderTarget(w, h, options);
2734
- }, [width, height, size.width, size.height, options, isLegacy]);
3175
+ return new WebGLRenderTarget(w, h, opts);
3176
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2735
3177
  }
2736
3178
 
2737
3179
  function useStore() {
@@ -2781,7 +3223,7 @@ function addTail(callback) {
2781
3223
  function invalidate(state, frames = 1, stackFrames = false) {
2782
3224
  getScheduler().invalidate(frames, stackFrames);
2783
3225
  }
2784
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3226
+ function advance(timestamp) {
2785
3227
  getScheduler().step(timestamp);
2786
3228
  }
2787
3229
 
@@ -14235,6 +14677,7 @@ function swapInstances() {
14235
14677
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14236
14678
  instance.object.__r3f = instance;
14237
14679
  setFiberRef(fiber, instance.object);
14680
+ delete instance.appliedOnce;
14238
14681
  applyProps(instance.object, instance.props);
14239
14682
  if (instance.props.attach) {
14240
14683
  attach(parent, instance);
@@ -14308,8 +14751,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14308
14751
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14309
14752
  if (isTailSibling) swapInstances();
14310
14753
  },
14311
- finalizeInitialChildren: () => false,
14312
- commitMount() {
14754
+ finalizeInitialChildren: (instance) => {
14755
+ for (const prop in instance.props) {
14756
+ if (isFromRef(instance.props[prop])) return true;
14757
+ }
14758
+ return false;
14759
+ },
14760
+ commitMount(instance) {
14761
+ const resolved = {};
14762
+ for (const prop in instance.props) {
14763
+ const value = instance.props[prop];
14764
+ if (isFromRef(value)) {
14765
+ const ref = value[FROM_REF];
14766
+ if (ref.current != null) resolved[prop] = ref.current;
14767
+ }
14768
+ }
14769
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14313
14770
  },
14314
14771
  getPublicInstance: (instance) => instance?.object,
14315
14772
  prepareForCommit: () => null,
@@ -14530,6 +14987,9 @@ function createRoot(canvas) {
14530
14987
  let resolve;
14531
14988
  pending = new Promise((_resolve) => resolve = _resolve);
14532
14989
  const {
14990
+ id: canvasId,
14991
+ primaryCanvas,
14992
+ scheduler: schedulerConfig,
14533
14993
  gl: glConfig,
14534
14994
  renderer: rendererConfig,
14535
14995
  size: propsSize,
@@ -14537,10 +14997,7 @@ function createRoot(canvas) {
14537
14997
  events,
14538
14998
  onCreated: onCreatedCallback,
14539
14999
  shadows = false,
14540
- linear = false,
14541
- flat = false,
14542
15000
  textureColorSpace = SRGBColorSpace,
14543
- legacy = false,
14544
15001
  orthographic = false,
14545
15002
  frameloop = "always",
14546
15003
  dpr = [1, 2],
@@ -14552,7 +15009,8 @@ function createRoot(canvas) {
14552
15009
  onDropMissed,
14553
15010
  autoUpdateFrustum = true,
14554
15011
  occlusion = false,
14555
- _sizeProps
15012
+ _sizeProps,
15013
+ forceEven
14556
15014
  } = props;
14557
15015
  const state = store.getState();
14558
15016
  const defaultGLProps = {
@@ -14566,15 +15024,25 @@ function createRoot(canvas) {
14566
15024
  "WebGPURenderer (renderer prop) is not available in this build. Use @react-three/fiber or @react-three/fiber/webgpu instead."
14567
15025
  );
14568
15026
  }
14569
- (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
15027
+ const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14570
15028
  if (glConfig && rendererConfig) {
14571
15029
  throw new Error("Cannot use both gl and renderer props at the same time");
14572
15030
  }
14573
15031
  let renderer = state.internal.actualRenderer;
15032
+ if (primaryCanvas && !R3F_BUILD_WEBGPU) {
15033
+ throw new Error(
15034
+ "The `primaryCanvas` prop for multi-canvas rendering is only available with WebGPU. Use @react-three/fiber/webgpu instead."
15035
+ );
15036
+ }
15037
+ if (primaryCanvas && wantsGL) {
15038
+ throw new Error(
15039
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15040
+ );
15041
+ }
14574
15042
  if (!state.internal.actualRenderer) {
14575
15043
  renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14576
15044
  state.internal.actualRenderer = renderer;
14577
- state.set({ isLegacy: true, gl: renderer, renderer });
15045
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14578
15046
  }
14579
15047
  let raycaster = state.raycaster;
14580
15048
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14583,6 +15051,7 @@ function createRoot(canvas) {
14583
15051
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14584
15052
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14585
15053
  }
15054
+ let tempCamera = state.camera;
14586
15055
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14587
15056
  lastCamera = cameraOptions;
14588
15057
  const isCamera = cameraOptions?.isCamera;
@@ -14602,6 +15071,7 @@ function createRoot(canvas) {
14602
15071
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14603
15072
  }
14604
15073
  state.set({ camera });
15074
+ tempCamera = camera;
14605
15075
  raycaster.camera = camera;
14606
15076
  }
14607
15077
  if (!state.scene) {
@@ -14619,7 +15089,7 @@ function createRoot(canvas) {
14619
15089
  rootScene: scene,
14620
15090
  internal: { ...prev.internal, container: scene }
14621
15091
  }));
14622
- const camera = state.camera;
15092
+ const camera = tempCamera;
14623
15093
  if (camera && !camera.parent) scene.add(camera);
14624
15094
  }
14625
15095
  if (events && !state.events.handlers) {
@@ -14636,6 +15106,9 @@ function createRoot(canvas) {
14636
15106
  if (_sizeProps !== void 0) {
14637
15107
  state.set({ _sizeProps });
14638
15108
  }
15109
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15110
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15111
+ }
14639
15112
  const size = computeInitialSize(canvas, propsSize);
14640
15113
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14641
15114
  const wasImperative = state._sizeImperative;
@@ -14665,7 +15138,7 @@ function createRoot(canvas) {
14665
15138
  const handleXRFrame = (timestamp, frame) => {
14666
15139
  const state2 = store.getState();
14667
15140
  if (state2.frameloop === "never") return;
14668
- advance(timestamp, true);
15141
+ advance(timestamp);
14669
15142
  };
14670
15143
  const actualRenderer = state.internal.actualRenderer;
14671
15144
  const handleSessionChange = () => {
@@ -14677,16 +15150,16 @@ function createRoot(canvas) {
14677
15150
  };
14678
15151
  const xr = {
14679
15152
  connect() {
14680
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14681
- const actualRenderer2 = renderer2 || gl;
14682
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14683
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15153
+ const { gl, renderer: renderer2 } = store.getState();
15154
+ const xrManager = (renderer2 || gl).xr;
15155
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15156
+ xrManager.addEventListener("sessionend", handleSessionChange);
14684
15157
  },
14685
15158
  disconnect() {
14686
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14687
- const actualRenderer2 = renderer2 || gl;
14688
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14689
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15159
+ const { gl, renderer: renderer2 } = store.getState();
15160
+ const xrManager = (renderer2 || gl).xr;
15161
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15162
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14690
15163
  }
14691
15164
  };
14692
15165
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14714,25 +15187,9 @@ function createRoot(canvas) {
14714
15187
  renderer.shadowMap.needsUpdate = true;
14715
15188
  }
14716
15189
  }
14717
- {
14718
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14719
- const linearChanged = linear !== lastConfiguredProps.linear;
14720
- const flatChanged = flat !== lastConfiguredProps.flat;
14721
- if (legacyChanged) {
14722
- ColorManagement.enabled = !legacy;
14723
- lastConfiguredProps.legacy = legacy;
14724
- }
14725
- if (!configured || linearChanged) {
14726
- renderer.outputColorSpace = linear ? LinearSRGBColorSpace : SRGBColorSpace;
14727
- lastConfiguredProps.linear = linear;
14728
- }
14729
- if (!configured || flatChanged) {
14730
- renderer.toneMapping = flat ? NoToneMapping : ACESFilmicToneMapping;
14731
- lastConfiguredProps.flat = flat;
14732
- }
14733
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14734
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14735
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15190
+ if (!configured) {
15191
+ renderer.outputColorSpace = SRGBColorSpace;
15192
+ renderer.toneMapping = ACESFilmicToneMapping;
14736
15193
  }
14737
15194
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14738
15195
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
@@ -14750,11 +15207,26 @@ function createRoot(canvas) {
14750
15207
  const scheduler = getScheduler();
14751
15208
  const rootId = state.internal.rootId;
14752
15209
  if (!rootId) {
14753
- const newRootId = scheduler.generateRootId();
15210
+ const newRootId = canvasId || scheduler.generateRootId();
14754
15211
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14755
15212
  getState: () => store.getState(),
14756
15213
  onError: (err) => store.getState().setError(err)
14757
15214
  });
15215
+ const unregisterCanvasTarget = scheduler.register(
15216
+ () => {
15217
+ const state2 = store.getState();
15218
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15219
+ const renderer2 = state2.internal.actualRenderer;
15220
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15221
+ }
15222
+ },
15223
+ {
15224
+ id: `${newRootId}_canvasTarget`,
15225
+ rootId: newRootId,
15226
+ phase: "start",
15227
+ system: true
15228
+ }
15229
+ );
14758
15230
  const unregisterFrustum = scheduler.register(
14759
15231
  () => {
14760
15232
  const state2 = store.getState();
@@ -14796,11 +15268,15 @@ function createRoot(canvas) {
14796
15268
  }
14797
15269
  },
14798
15270
  {
14799
- id: `${newRootId}_render`,
15271
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15272
+ id: canvasId || `${newRootId}_render`,
14800
15273
  rootId: newRootId,
14801
15274
  phase: "render",
14802
- system: true
15275
+ system: true,
14803
15276
  // Internal flag: this is a system job, not user-controlled
15277
+ // Apply scheduler config for render ordering and rate limiting
15278
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15279
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14804
15280
  }
14805
15281
  );
14806
15282
  state.set((state2) => ({
@@ -14809,6 +15285,7 @@ function createRoot(canvas) {
14809
15285
  rootId: newRootId,
14810
15286
  unregisterRoot: () => {
14811
15287
  unregisterRoot();
15288
+ unregisterCanvasTarget();
14812
15289
  unregisterFrustum();
14813
15290
  unregisterVisibility();
14814
15291
  unregisterRender();
@@ -14867,15 +15344,24 @@ function unmountComponentAtNode(canvas, callback) {
14867
15344
  const renderer = state.internal.actualRenderer;
14868
15345
  const unregisterRoot = state.internal.unregisterRoot;
14869
15346
  if (unregisterRoot) unregisterRoot();
15347
+ const unregisterPrimary = state.internal.unregisterPrimary;
15348
+ if (unregisterPrimary) unregisterPrimary();
15349
+ const canvasTarget = state.internal.canvasTarget;
15350
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14870
15351
  state.events.disconnect?.();
14871
15352
  cleanupHelperGroup(root.store);
14872
- renderer?.renderLists?.dispose?.();
14873
- renderer?.forceContextLoss?.();
14874
- if (renderer?.xr) state.xr.disconnect();
15353
+ if (state.isLegacy && renderer) {
15354
+ ;
15355
+ renderer.renderLists?.dispose?.();
15356
+ renderer.forceContextLoss?.();
15357
+ }
15358
+ if (!state.internal.isSecondary) {
15359
+ if (renderer?.xr) state.xr.disconnect();
15360
+ }
14875
15361
  dispose(state.scene);
14876
15362
  _roots.delete(canvas);
14877
15363
  if (callback) callback(canvas);
14878
- } catch (e) {
15364
+ } catch {
14879
15365
  }
14880
15366
  }, 500);
14881
15367
  }
@@ -14883,36 +15369,34 @@ function unmountComponentAtNode(canvas, callback) {
14883
15369
  }
14884
15370
  }
14885
15371
  function createPortal(children, container, state) {
14886
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
15372
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14887
15373
  }
14888
- function PortalWrapper({ children, container, state }) {
15374
+ function Portal({ children, container, state }) {
14889
15375
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14890
- const [resolvedContainer, setResolvedContainer] = useState(() => {
15376
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14891
15377
  if (isRef(container)) return container.current ?? null;
14892
15378
  return container;
14893
15379
  });
15380
+ const setResolvedContainer = useCallback(
15381
+ (newContainer) => {
15382
+ if (!newContainer || newContainer === resolvedContainer) return;
15383
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15384
+ },
15385
+ [resolvedContainer, _setResolvedContainer, isRef]
15386
+ );
14894
15387
  useMemo(() => {
14895
- if (isRef(container)) {
14896
- const current = container.current;
14897
- if (!current) {
14898
- queueMicrotask(() => {
14899
- const updated = container.current;
14900
- if (updated && updated !== resolvedContainer) {
14901
- setResolvedContainer(updated);
14902
- }
14903
- });
14904
- } else if (current !== resolvedContainer) {
14905
- setResolvedContainer(current);
14906
- }
14907
- } else if (container !== resolvedContainer) {
14908
- setResolvedContainer(container);
15388
+ if (isRef(container) && !container.current) {
15389
+ return queueMicrotask(() => {
15390
+ setResolvedContainer(container.current);
15391
+ });
14909
15392
  }
14910
- }, [container, resolvedContainer, isRef]);
15393
+ setResolvedContainer(container);
15394
+ }, [container, isRef, setResolvedContainer]);
14911
15395
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14912
15396
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14913
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15397
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14914
15398
  }
14915
- function Portal({ state = {}, children, container }) {
15399
+ function PortalInner({ state = {}, children, container }) {
14916
15400
  const { events, size, injectScene = true, ...rest } = state;
14917
15401
  const previousRoot = useStore();
14918
15402
  const [raycaster] = useState(() => new Raycaster());
@@ -14933,11 +15417,12 @@ function Portal({ state = {}, children, container }) {
14933
15417
  };
14934
15418
  }, [portalScene, container, injectScene]);
14935
15419
  const inject = useMutableCallback((rootState, injectState) => {
15420
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14936
15421
  let viewport = void 0;
14937
- if (injectState.camera && size) {
15422
+ if (injectState.camera && (size || injectState.size)) {
14938
15423
  const camera = injectState.camera;
14939
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14940
- if (camera !== rootState.camera) updateCamera(camera, size);
15424
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
15425
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14941
15426
  }
14942
15427
  return {
14943
15428
  // The intersect consists of the previous root state
@@ -14954,7 +15439,7 @@ function Portal({ state = {}, children, container }) {
14954
15439
  previousRoot,
14955
15440
  // Events, size and viewport can be overridden by the inject layer
14956
15441
  events: { ...rootState.events, ...injectState.events, ...events },
14957
- size: { ...rootState.size, ...size },
15442
+ size: resolvedSize,
14958
15443
  viewport: { ...rootState.viewport, ...viewport },
14959
15444
  // Layers are allowed to override events
14960
15445
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14988,15 +15473,13 @@ function CanvasImpl({
14988
15473
  fallback,
14989
15474
  resize,
14990
15475
  style,
15476
+ id,
14991
15477
  gl,
14992
- renderer,
15478
+ renderer: rendererProp,
14993
15479
  events = createPointerEvents,
14994
15480
  eventSource,
14995
15481
  eventPrefix,
14996
15482
  shadows,
14997
- linear,
14998
- flat,
14999
- legacy,
15000
15483
  orthographic,
15001
15484
  frameloop,
15002
15485
  dpr,
@@ -15011,10 +15494,43 @@ function CanvasImpl({
15011
15494
  hmr,
15012
15495
  width,
15013
15496
  height,
15497
+ background,
15498
+ forceEven,
15014
15499
  ...props
15015
15500
  }) {
15501
+ 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 };
15502
+ const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
15016
15503
  React.useMemo(() => extend(THREE), []);
15017
15504
  const Bridge = useBridge();
15505
+ const backgroundProps = React.useMemo(() => {
15506
+ if (!background) return null;
15507
+ if (typeof background === "object" && !background.isColor) {
15508
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15509
+ return {
15510
+ ...rest,
15511
+ preset,
15512
+ files: envMap || files,
15513
+ backgroundFiles: backgroundMap,
15514
+ background: true
15515
+ };
15516
+ }
15517
+ if (typeof background === "number") {
15518
+ return { color: background, background: true };
15519
+ }
15520
+ if (typeof background === "string") {
15521
+ if (background in presetsObj) {
15522
+ return { preset: background, background: true };
15523
+ }
15524
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15525
+ return { files: background, background: true };
15526
+ }
15527
+ return { color: background, background: true };
15528
+ }
15529
+ if (background.isColor) {
15530
+ return { color: background, background: true };
15531
+ }
15532
+ return null;
15533
+ }, [background]);
15018
15534
  const hasInitialSizeRef = React.useRef(false);
15019
15535
  const measureConfig = React.useMemo(() => {
15020
15536
  if (!hasInitialSizeRef.current) {
@@ -15031,15 +15547,20 @@ function CanvasImpl({
15031
15547
  };
15032
15548
  }, [resize, hasInitialSizeRef.current]);
15033
15549
  const [containerRef, containerRect] = useMeasure(measureConfig);
15034
- const effectiveSize = React.useMemo(
15035
- () => ({
15036
- width: width ?? containerRect.width,
15037
- height: height ?? containerRect.height,
15550
+ const effectiveSize = React.useMemo(() => {
15551
+ let w = width ?? containerRect.width;
15552
+ let h = height ?? containerRect.height;
15553
+ if (forceEven) {
15554
+ w = Math.ceil(w / 2) * 2;
15555
+ h = Math.ceil(h / 2) * 2;
15556
+ }
15557
+ return {
15558
+ width: w,
15559
+ height: h,
15038
15560
  top: containerRect.top,
15039
15561
  left: containerRect.left
15040
- }),
15041
- [width, height, containerRect]
15042
- );
15562
+ };
15563
+ }, [width, height, containerRect, forceEven]);
15043
15564
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15044
15565
  hasInitialSizeRef.current = true;
15045
15566
  }
@@ -15079,14 +15600,14 @@ function CanvasImpl({
15079
15600
  async function run() {
15080
15601
  if (!effectActiveRef.current || !root.current) return;
15081
15602
  await root.current.configure({
15603
+ id,
15604
+ primaryCanvas,
15605
+ scheduler,
15082
15606
  gl,
15083
15607
  renderer,
15084
15608
  scene,
15085
15609
  events,
15086
15610
  shadows,
15087
- linear,
15088
- flat,
15089
- legacy,
15090
15611
  orthographic,
15091
15612
  frameloop,
15092
15613
  dpr,
@@ -15096,6 +15617,7 @@ function CanvasImpl({
15096
15617
  size: effectiveSize,
15097
15618
  // Store size props for reset functionality
15098
15619
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15620
+ forceEven,
15099
15621
  // Pass mutable reference to onPointerMissed so it's free to update
15100
15622
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15101
15623
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15119,7 +15641,10 @@ function CanvasImpl({
15119
15641
  });
15120
15642
  if (!effectActiveRef.current || !root.current) return;
15121
15643
  root.current.render(
15122
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15644
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
15645
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
15646
+ children ?? null
15647
+ ] }) }) })
15123
15648
  );
15124
15649
  }
15125
15650
  run();
@@ -15146,14 +15671,16 @@ function CanvasImpl({
15146
15671
  const canvas = canvasRef.current;
15147
15672
  if (!canvas) return;
15148
15673
  const handleHMR = () => {
15149
- const rootEntry = _roots.get(canvas);
15150
- if (rootEntry?.store) {
15151
- rootEntry.store.setState((state) => ({
15152
- nodes: {},
15153
- uniforms: {},
15154
- _hmrVersion: state._hmrVersion + 1
15155
- }));
15156
- }
15674
+ queueMicrotask(() => {
15675
+ const rootEntry = _roots.get(canvas);
15676
+ if (rootEntry?.store) {
15677
+ rootEntry.store.setState((state) => ({
15678
+ nodes: {},
15679
+ uniforms: {},
15680
+ _hmrVersion: state._hmrVersion + 1
15681
+ }));
15682
+ }
15683
+ });
15157
15684
  };
15158
15685
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15159
15686
  const hot = import.meta.hot;
@@ -15182,7 +15709,7 @@ function CanvasImpl({
15182
15709
  ...style
15183
15710
  },
15184
15711
  ...props,
15185
- 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 }) })
15712
+ 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 }) })
15186
15713
  }
15187
15714
  );
15188
15715
  }
@@ -15192,4 +15719,4 @@ function Canvas(props) {
15192
15719
 
15193
15720
  extend(THREE);
15194
15721
 
15195
- 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, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, 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, reconciler, removeInteractivity, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree };
15722
+ 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, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, 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, reconciler, registerPrimary, removeInteractivity, resolve, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree, waitForPrimary };