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

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/index.mjs CHANGED
@@ -1,12 +1,18 @@
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, ColorManagement, LinearSRGBColorSpace, NoToneMapping, ACESFilmicToneMapping, WebGPURenderer } 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, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer } from 'three/webgpu';
3
3
  import { WebGLRenderTarget, WebGLRenderer } from 'three';
4
4
  import { Inspector } from 'three/addons/inspector/Inspector.js';
5
- import { jsx, Fragment } from 'react/jsx-runtime';
5
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
6
  import * as React from 'react';
7
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
7
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
8
8
  import useMeasure from 'react-use-measure';
9
9
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
10
+ 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';
11
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
12
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
13
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
14
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
15
+ import { GainMapLoader } from '@monogrid/gainmap-js';
10
16
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
11
17
  import { createWithEqualityFn } from 'zustand/traditional';
12
18
  import { suspend, preload, clear } from 'suspend-react';
@@ -36,6 +42,389 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
36
42
  WebGLRenderer: WebGLRenderer
37
43
  }, [webgpu]);
38
44
 
45
+ const primaryRegistry = /* @__PURE__ */ new Map();
46
+ const pendingSubscribers = /* @__PURE__ */ new Map();
47
+ function registerPrimary(id, renderer, store) {
48
+ if (primaryRegistry.has(id)) {
49
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
50
+ }
51
+ const entry = { renderer, store };
52
+ primaryRegistry.set(id, entry);
53
+ const subscribers = pendingSubscribers.get(id);
54
+ if (subscribers) {
55
+ subscribers.forEach((callback) => callback(entry));
56
+ pendingSubscribers.delete(id);
57
+ }
58
+ return () => {
59
+ const currentEntry = primaryRegistry.get(id);
60
+ if (currentEntry?.renderer === renderer) {
61
+ primaryRegistry.delete(id);
62
+ }
63
+ };
64
+ }
65
+ function getPrimary(id) {
66
+ return primaryRegistry.get(id);
67
+ }
68
+ function waitForPrimary(id, timeout = 5e3) {
69
+ const existing = primaryRegistry.get(id);
70
+ if (existing) {
71
+ return Promise.resolve(existing);
72
+ }
73
+ return new Promise((resolve, reject) => {
74
+ const timeoutId = setTimeout(() => {
75
+ const subscribers = pendingSubscribers.get(id);
76
+ if (subscribers) {
77
+ const index = subscribers.indexOf(callback);
78
+ if (index !== -1) subscribers.splice(index, 1);
79
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
80
+ }
81
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
82
+ }, timeout);
83
+ const callback = (entry) => {
84
+ clearTimeout(timeoutId);
85
+ resolve(entry);
86
+ };
87
+ if (!pendingSubscribers.has(id)) {
88
+ pendingSubscribers.set(id, []);
89
+ }
90
+ pendingSubscribers.get(id).push(callback);
91
+ });
92
+ }
93
+ function hasPrimary(id) {
94
+ return primaryRegistry.has(id);
95
+ }
96
+ function unregisterPrimary(id) {
97
+ primaryRegistry.delete(id);
98
+ }
99
+ function getPrimaryIds() {
100
+ return Array.from(primaryRegistry.keys());
101
+ }
102
+
103
+ const presetsObj = {
104
+ apartment: "lebombo_1k.hdr",
105
+ city: "potsdamer_platz_1k.hdr",
106
+ dawn: "kiara_1_dawn_1k.hdr",
107
+ forest: "forest_slope_1k.hdr",
108
+ lobby: "st_fagans_interior_1k.hdr",
109
+ night: "dikhololo_night_1k.hdr",
110
+ park: "rooitou_park_1k.hdr",
111
+ studio: "studio_small_03_1k.hdr",
112
+ sunset: "venice_sunset_1k.hdr",
113
+ warehouse: "empty_warehouse_01_1k.hdr"
114
+ };
115
+
116
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
117
+ const isArray = (arr) => Array.isArray(arr);
118
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
119
+ function useEnvironment({
120
+ files = defaultFiles,
121
+ path = "",
122
+ preset = void 0,
123
+ colorSpace = void 0,
124
+ extensions
125
+ } = {}) {
126
+ if (preset) {
127
+ validatePreset(preset);
128
+ files = presetsObj[preset];
129
+ path = CUBEMAP_ROOT;
130
+ }
131
+ const multiFile = isArray(files);
132
+ const { extension, isCubemap } = getExtension(files);
133
+ const loader = getLoader$1(extension);
134
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
135
+ const renderer = useThree$1((state) => state.renderer);
136
+ useLayoutEffect(() => {
137
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
138
+ function clearGainmapTexture() {
139
+ useLoader$1.clear(loader, multiFile ? [files] : files);
140
+ }
141
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
142
+ }, [extension, files, loader, multiFile, renderer.domElement]);
143
+ const loaderResult = useLoader$1(
144
+ loader,
145
+ multiFile ? [files] : files,
146
+ (loader2) => {
147
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
148
+ loader2.setRenderer?.(renderer);
149
+ }
150
+ loader2.setPath?.(path);
151
+ if (extensions) extensions(loader2);
152
+ }
153
+ );
154
+ let texture = multiFile ? (
155
+ // @ts-ignore
156
+ loaderResult[0]
157
+ ) : loaderResult;
158
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
159
+ texture = texture.renderTarget?.texture;
160
+ }
161
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
162
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
163
+ return texture;
164
+ }
165
+ const preloadDefaultOptions = {
166
+ files: defaultFiles,
167
+ path: "",
168
+ preset: void 0,
169
+ extensions: void 0
170
+ };
171
+ useEnvironment.preload = (preloadOptions) => {
172
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
173
+ let { files, path = "" } = options;
174
+ const { preset, extensions } = options;
175
+ if (preset) {
176
+ validatePreset(preset);
177
+ files = presetsObj[preset];
178
+ path = CUBEMAP_ROOT;
179
+ }
180
+ const { extension } = getExtension(files);
181
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
182
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
183
+ }
184
+ const loader = getLoader$1(extension);
185
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
186
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
187
+ loader2.setPath?.(path);
188
+ if (extensions) extensions(loader2);
189
+ });
190
+ };
191
+ const clearDefaultOptins = {
192
+ files: defaultFiles,
193
+ preset: void 0
194
+ };
195
+ useEnvironment.clear = (clearOptions) => {
196
+ const options = { ...clearDefaultOptins, ...clearOptions };
197
+ let { files } = options;
198
+ const { preset } = options;
199
+ if (preset) {
200
+ validatePreset(preset);
201
+ files = presetsObj[preset];
202
+ }
203
+ const { extension } = getExtension(files);
204
+ const loader = getLoader$1(extension);
205
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
206
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
207
+ };
208
+ function validatePreset(preset) {
209
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
210
+ }
211
+ function getExtension(files) {
212
+ const isCubemap = isArray(files) && files.length === 6;
213
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
214
+ const firstEntry = isArray(files) ? files[0] : files;
215
+ 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();
216
+ return { extension, isCubemap, isGainmap };
217
+ }
218
+ function getLoader$1(extension) {
219
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
220
+ return loader;
221
+ }
222
+
223
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
224
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
225
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
226
+ sceneProps = {
227
+ backgroundBlurriness: 0,
228
+ backgroundIntensity: 1,
229
+ backgroundRotation: [0, 0, 0],
230
+ environmentIntensity: 1,
231
+ environmentRotation: [0, 0, 0],
232
+ ...sceneProps
233
+ };
234
+ const target = resolveScene(scene || defaultScene);
235
+ const oldbg = target.background;
236
+ const oldenv = target.environment;
237
+ const oldSceneProps = {
238
+ // @ts-ignore
239
+ backgroundBlurriness: target.backgroundBlurriness,
240
+ // @ts-ignore
241
+ backgroundIntensity: target.backgroundIntensity,
242
+ // @ts-ignore
243
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
244
+ // @ts-ignore
245
+ environmentIntensity: target.environmentIntensity,
246
+ // @ts-ignore
247
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
248
+ };
249
+ if (background !== "only") target.environment = texture;
250
+ if (background) target.background = texture;
251
+ applyProps$1(target, sceneProps);
252
+ return () => {
253
+ if (background !== "only") target.environment = oldenv;
254
+ if (background) target.background = oldbg;
255
+ applyProps$1(target, oldSceneProps);
256
+ };
257
+ }
258
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
259
+ const defaultScene = useThree$1((state) => state.scene);
260
+ React.useLayoutEffect(() => {
261
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
262
+ });
263
+ return null;
264
+ }
265
+ function EnvironmentCube({
266
+ background = false,
267
+ scene,
268
+ blur,
269
+ backgroundBlurriness,
270
+ backgroundIntensity,
271
+ backgroundRotation,
272
+ environmentIntensity,
273
+ environmentRotation,
274
+ ...rest
275
+ }) {
276
+ const texture = useEnvironment(rest);
277
+ const defaultScene = useThree$1((state) => state.scene);
278
+ React.useLayoutEffect(() => {
279
+ return setEnvProps(background, scene, defaultScene, texture, {
280
+ backgroundBlurriness: blur ?? backgroundBlurriness,
281
+ backgroundIntensity,
282
+ backgroundRotation,
283
+ environmentIntensity,
284
+ environmentRotation
285
+ });
286
+ });
287
+ React.useEffect(() => {
288
+ return () => {
289
+ texture.dispose();
290
+ };
291
+ }, [texture]);
292
+ return null;
293
+ }
294
+ function EnvironmentPortal({
295
+ children,
296
+ near = 0.1,
297
+ far = 1e3,
298
+ resolution = 256,
299
+ frames = 1,
300
+ map,
301
+ background = false,
302
+ blur,
303
+ backgroundBlurriness,
304
+ backgroundIntensity,
305
+ backgroundRotation,
306
+ environmentIntensity,
307
+ environmentRotation,
308
+ scene,
309
+ files,
310
+ path,
311
+ preset = void 0,
312
+ extensions
313
+ }) {
314
+ const gl = useThree$1((state) => state.gl);
315
+ const defaultScene = useThree$1((state) => state.scene);
316
+ const camera = React.useRef(null);
317
+ const [virtualScene] = React.useState(() => new Scene());
318
+ const fbo = React.useMemo(() => {
319
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
320
+ fbo2.texture.type = HalfFloatType;
321
+ return fbo2;
322
+ }, [resolution]);
323
+ React.useEffect(() => {
324
+ return () => {
325
+ fbo.dispose();
326
+ };
327
+ }, [fbo]);
328
+ React.useLayoutEffect(() => {
329
+ if (frames === 1) {
330
+ const autoClear = gl.autoClear;
331
+ gl.autoClear = true;
332
+ camera.current.update(gl, virtualScene);
333
+ gl.autoClear = autoClear;
334
+ }
335
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
336
+ backgroundBlurriness: blur ?? backgroundBlurriness,
337
+ backgroundIntensity,
338
+ backgroundRotation,
339
+ environmentIntensity,
340
+ environmentRotation
341
+ });
342
+ }, [
343
+ children,
344
+ virtualScene,
345
+ fbo.texture,
346
+ scene,
347
+ defaultScene,
348
+ background,
349
+ frames,
350
+ gl,
351
+ blur,
352
+ backgroundBlurriness,
353
+ backgroundIntensity,
354
+ backgroundRotation,
355
+ environmentIntensity,
356
+ environmentRotation
357
+ ]);
358
+ let count = 1;
359
+ useFrame$1(() => {
360
+ if (frames === Infinity || count < frames) {
361
+ const autoClear = gl.autoClear;
362
+ gl.autoClear = true;
363
+ camera.current.update(gl, virtualScene);
364
+ gl.autoClear = autoClear;
365
+ count++;
366
+ }
367
+ });
368
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
369
+ /* @__PURE__ */ jsxs(Fragment, { children: [
370
+ children,
371
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
372
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
373
+ ] }),
374
+ virtualScene
375
+ ) });
376
+ }
377
+ function EnvironmentGround(props) {
378
+ const textureDefault = useEnvironment(props);
379
+ const texture = props.map || textureDefault;
380
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
381
+ React.useEffect(() => {
382
+ return () => {
383
+ textureDefault.dispose();
384
+ };
385
+ }, [textureDefault]);
386
+ const height = props.ground?.height ?? 15;
387
+ const radius = props.ground?.radius ?? 60;
388
+ const scale = props.ground?.scale ?? 1e3;
389
+ const args = React.useMemo(
390
+ () => [texture, height, radius],
391
+ [texture, height, radius]
392
+ );
393
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
394
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
395
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
396
+ ] });
397
+ }
398
+ function EnvironmentColor({ color, scene }) {
399
+ const defaultScene = useThree$1((state) => state.scene);
400
+ React.useLayoutEffect(() => {
401
+ if (color === void 0) return;
402
+ const target = resolveScene(scene || defaultScene);
403
+ const oldBg = target.background;
404
+ target.background = new Color(color);
405
+ return () => {
406
+ target.background = oldBg;
407
+ };
408
+ });
409
+ return null;
410
+ }
411
+ function EnvironmentDualSource(props) {
412
+ const { backgroundFiles, ...envProps } = props;
413
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
414
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
415
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
416
+ ] });
417
+ }
418
+ function Environment(props) {
419
+ if (props.color && !props.files && !props.preset && !props.map) {
420
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
421
+ }
422
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
423
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
424
+ }
425
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
426
+ }
427
+
39
428
  var __defProp$2 = Object.defineProperty;
40
429
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
41
430
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -215,7 +604,8 @@ function prepare(target, root, type, props) {
215
604
  object,
216
605
  eventCount: 0,
217
606
  handlers: {},
218
- isHidden: false
607
+ isHidden: false,
608
+ deferredRefs: []
219
609
  };
220
610
  if (object) object.__r3f = instance;
221
611
  }
@@ -264,7 +654,7 @@ function createOcclusionObserverNode(store, uniform) {
264
654
  let occlusionSetupPromise = null;
265
655
  function enableOcclusion(store) {
266
656
  const state = store.getState();
267
- const { internal, renderer, rootScene } = state;
657
+ const { internal, renderer } = state;
268
658
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
269
659
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
270
660
  if (!hasOcclusionSupport) {
@@ -427,6 +817,22 @@ function hasVisibilityHandlers(handlers) {
427
817
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
428
818
  }
429
819
 
820
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
821
+ function fromRef(ref) {
822
+ return { [FROM_REF]: ref };
823
+ }
824
+ function isFromRef(value) {
825
+ return value !== null && typeof value === "object" && FROM_REF in value;
826
+ }
827
+
828
+ const ONCE = Symbol.for("@react-three/fiber.once");
829
+ function once(...args) {
830
+ return { [ONCE]: args.length ? args : true };
831
+ }
832
+ function isOnce(value) {
833
+ return value !== null && typeof value === "object" && ONCE in value;
834
+ }
835
+
430
836
  const RESERVED_PROPS = [
431
837
  "children",
432
838
  "key",
@@ -497,7 +903,7 @@ function getMemoizedPrototype(root) {
497
903
  ctor = new root.constructor();
498
904
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
499
905
  }
500
- } catch (e) {
906
+ } catch {
501
907
  }
502
908
  return ctor;
503
909
  }
@@ -543,6 +949,25 @@ function applyProps(object, props) {
543
949
  continue;
544
950
  }
545
951
  if (value === void 0) continue;
952
+ if (isFromRef(value)) {
953
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
954
+ continue;
955
+ }
956
+ if (isOnce(value)) {
957
+ if (instance?.appliedOnce?.has(prop)) continue;
958
+ if (instance) {
959
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
960
+ instance.appliedOnce.add(prop);
961
+ }
962
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
963
+ const args = value[ONCE];
964
+ if (typeof targetRoot[targetKey] === "function") {
965
+ targetRoot[targetKey](...args === true ? [] : args);
966
+ } else if (args !== true && args.length > 0) {
967
+ targetRoot[targetKey] = args[0];
968
+ }
969
+ continue;
970
+ }
546
971
  let { root, key, target } = resolve(object, prop);
547
972
  if (target === void 0 && (typeof root !== "object" || root === null)) {
548
973
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -565,7 +990,10 @@ function applyProps(object, props) {
565
990
  else target.set(value);
566
991
  } else {
567
992
  root[key] = value;
568
- 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
993
+ if (key.endsWith("Node") && root.isMaterial) {
994
+ root.needsUpdate = true;
995
+ }
996
+ 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
569
997
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
570
998
  root[key].colorSpace = rootState.textureColorSpace;
571
999
  }
@@ -598,38 +1026,60 @@ function applyProps(object, props) {
598
1026
  return object;
599
1027
  }
600
1028
 
1029
+ const DEFAULT_POINTER_ID = 0;
1030
+ const XR_POINTER_ID_START = 1e3;
1031
+ function getPointerState(internal, pointerId) {
1032
+ let state = internal.pointerMap.get(pointerId);
1033
+ if (!state) {
1034
+ state = {
1035
+ hovered: /* @__PURE__ */ new Map(),
1036
+ captured: /* @__PURE__ */ new Map(),
1037
+ initialClick: [0, 0],
1038
+ initialHits: []
1039
+ };
1040
+ internal.pointerMap.set(pointerId, state);
1041
+ }
1042
+ return state;
1043
+ }
1044
+ function getPointerId(event) {
1045
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1046
+ }
601
1047
  function makeId(event) {
602
1048
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
603
1049
  }
604
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
605
- const captureData = captures.get(obj);
1050
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1051
+ const pointerState = internal.pointerMap.get(pointerId);
1052
+ if (!pointerState) return;
1053
+ const captureData = pointerState.captured.get(obj);
606
1054
  if (captureData) {
607
- captures.delete(obj);
608
- if (captures.size === 0) {
609
- capturedMap.delete(pointerId);
610
- captureData.target.releasePointerCapture(pointerId);
611
- }
1055
+ pointerState.captured.delete(obj);
1056
+ captureData.target.releasePointerCapture(pointerId);
612
1057
  }
613
1058
  }
614
1059
  function removeInteractivity(store, object) {
615
1060
  const { internal } = store.getState();
616
1061
  internal.interaction = internal.interaction.filter((o) => o !== object);
617
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
618
- internal.hovered.forEach((value, key) => {
619
- if (value.eventObject === object || value.object === object) {
620
- internal.hovered.delete(key);
1062
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1063
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1064
+ pointerState.hovered.forEach((value, key) => {
1065
+ if (value.eventObject === object || value.object === object) {
1066
+ pointerState.hovered.delete(key);
1067
+ }
1068
+ });
1069
+ if (pointerState.captured.has(object)) {
1070
+ releaseInternalPointerCapture(internal, object, pointerId);
621
1071
  }
622
- });
623
- internal.capturedMap.forEach((captures, pointerId) => {
624
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
625
- });
1072
+ }
626
1073
  unregisterVisibility(store, object);
627
1074
  }
628
1075
  function createEvents(store) {
629
- function calculateDistance(event) {
1076
+ function calculateDistance(event, pointerId) {
630
1077
  const { internal } = store.getState();
631
- const dx = event.offsetX - internal.initialClick[0];
632
- const dy = event.offsetY - internal.initialClick[1];
1078
+ const pointerState = internal.pointerMap.get(pointerId);
1079
+ if (!pointerState) return 0;
1080
+ const [initialX, initialY] = pointerState.initialClick;
1081
+ const dx = event.offsetX - initialX;
1082
+ const dy = event.offsetY - initialY;
633
1083
  return Math.round(Math.sqrt(dx * dx + dy * dy));
634
1084
  }
635
1085
  function filterPointerEvents(objects) {
@@ -665,6 +1115,15 @@ function createEvents(store) {
665
1115
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
666
1116
  }
667
1117
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1118
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1119
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1120
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1121
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1122
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1123
+ if (aInteractivePriority !== bInteractivePriority) {
1124
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1125
+ }
1126
+ }
668
1127
  const aState = getRootState(a.object);
669
1128
  const bState = getRootState(b.object);
670
1129
  const aPriority = aState?.events?.priority ?? 1;
@@ -686,9 +1145,13 @@ function createEvents(store) {
686
1145
  eventObject = eventObject.parent;
687
1146
  }
688
1147
  }
689
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
690
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
691
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1148
+ if ("pointerId" in event) {
1149
+ const pointerId = event.pointerId;
1150
+ const pointerState = state.internal.pointerMap.get(pointerId);
1151
+ if (pointerState?.captured.size) {
1152
+ for (const captureData of pointerState.captured.values()) {
1153
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1154
+ }
692
1155
  }
693
1156
  }
694
1157
  return intersections;
@@ -701,27 +1164,25 @@ function createEvents(store) {
701
1164
  if (state) {
702
1165
  const { raycaster, pointer, camera, internal } = state;
703
1166
  const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
704
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1167
+ const hasPointerCapture = (id) => {
1168
+ const pointerState = internal.pointerMap.get(id);
1169
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1170
+ };
705
1171
  const setPointerCapture = (id) => {
706
1172
  const captureData = { intersection: hit, target: event.target };
707
- if (internal.capturedMap.has(id)) {
708
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
709
- } else {
710
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
711
- }
1173
+ const pointerState = getPointerState(internal, id);
1174
+ pointerState.captured.set(hit.eventObject, captureData);
712
1175
  event.target.setPointerCapture(id);
713
1176
  };
714
1177
  const releasePointerCapture = (id) => {
715
- const captures = internal.capturedMap.get(id);
716
- if (captures) {
717
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
718
- }
1178
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
719
1179
  };
720
1180
  const extractEventProps = {};
721
1181
  for (const prop in event) {
722
1182
  const property = event[prop];
723
1183
  if (typeof property !== "function") extractEventProps[prop] = property;
724
1184
  }
1185
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
725
1186
  const raycastEvent = {
726
1187
  ...hit,
727
1188
  ...extractEventProps,
@@ -732,18 +1193,19 @@ function createEvents(store) {
732
1193
  unprojectedPoint,
733
1194
  ray: raycaster.ray,
734
1195
  camera,
1196
+ pointerId: eventPointerId,
735
1197
  // Hijack stopPropagation, which just sets a flag
736
1198
  stopPropagation() {
737
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1199
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
738
1200
  if (
739
1201
  // ...if this pointer hasn't been captured
740
- !capturesForPointer || // ... or if the hit object is capturing the pointer
741
- capturesForPointer.has(hit.eventObject)
1202
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1203
+ pointerState.captured.has(hit.eventObject)
742
1204
  ) {
743
1205
  raycastEvent.stopped = localState.stopped = true;
744
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1206
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
745
1207
  const higher = intersections.slice(0, intersections.indexOf(hit));
746
- cancelPointer([...higher, hit]);
1208
+ cancelPointer([...higher, hit], eventPointerId);
747
1209
  }
748
1210
  }
749
1211
  },
@@ -759,15 +1221,18 @@ function createEvents(store) {
759
1221
  }
760
1222
  return intersections;
761
1223
  }
762
- function cancelPointer(intersections) {
1224
+ function cancelPointer(intersections, pointerId) {
763
1225
  const { internal } = store.getState();
764
- for (const hoveredObj of internal.hovered.values()) {
1226
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1227
+ const pointerState = internal.pointerMap.get(pid);
1228
+ if (!pointerState) return;
1229
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
765
1230
  if (!intersections.length || !intersections.find(
766
1231
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
767
1232
  )) {
768
1233
  const eventObject = hoveredObj.eventObject;
769
1234
  const instance = eventObject.__r3f;
770
- internal.hovered.delete(makeId(hoveredObj));
1235
+ pointerState.hovered.delete(hoveredId);
771
1236
  if (instance?.eventCount) {
772
1237
  const handlers = instance.handlers;
773
1238
  const data = { ...hoveredObj, intersections };
@@ -796,41 +1261,118 @@ function createEvents(store) {
796
1261
  instance?.handlers.onDropMissed?.(event);
797
1262
  }
798
1263
  }
1264
+ function cleanupPointer(pointerId) {
1265
+ const { internal } = store.getState();
1266
+ const pointerState = internal.pointerMap.get(pointerId);
1267
+ if (pointerState) {
1268
+ for (const [, hoveredObj] of pointerState.hovered) {
1269
+ const eventObject = hoveredObj.eventObject;
1270
+ const instance = eventObject.__r3f;
1271
+ if (instance?.eventCount) {
1272
+ const handlers = instance.handlers;
1273
+ const data = { ...hoveredObj, intersections: [] };
1274
+ handlers.onPointerOut?.(data);
1275
+ handlers.onPointerLeave?.(data);
1276
+ }
1277
+ }
1278
+ internal.pointerMap.delete(pointerId);
1279
+ }
1280
+ internal.pointerDirty.delete(pointerId);
1281
+ }
1282
+ function processDeferredPointer(event, pointerId) {
1283
+ const state = store.getState();
1284
+ const { internal } = state;
1285
+ if (!state.events.enabled) return;
1286
+ const filter = filterPointerEvents;
1287
+ const hits = intersect(event, filter);
1288
+ cancelPointer(hits, pointerId);
1289
+ function onIntersect(data) {
1290
+ const eventObject = data.eventObject;
1291
+ const instance = eventObject.__r3f;
1292
+ if (!instance?.eventCount) return;
1293
+ const handlers = instance.handlers;
1294
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1295
+ const id = makeId(data);
1296
+ const pointerState = getPointerState(internal, pointerId);
1297
+ const hoveredItem = pointerState.hovered.get(id);
1298
+ if (!hoveredItem) {
1299
+ pointerState.hovered.set(id, data);
1300
+ handlers.onPointerOver?.(data);
1301
+ handlers.onPointerEnter?.(data);
1302
+ } else if (hoveredItem.stopped) {
1303
+ data.stopPropagation();
1304
+ }
1305
+ }
1306
+ handlers.onPointerMove?.(data);
1307
+ }
1308
+ handleIntersects(hits, event, 0, onIntersect);
1309
+ }
799
1310
  function handlePointer(name) {
800
1311
  switch (name) {
801
1312
  case "onPointerLeave":
802
- case "onPointerCancel":
803
1313
  case "onDragLeave":
804
1314
  return () => cancelPointer([]);
1315
+ // Global cancel of these events
1316
+ case "onPointerCancel":
1317
+ return (event) => {
1318
+ const pointerId = getPointerId(event);
1319
+ cleanupPointer(pointerId);
1320
+ };
805
1321
  case "onLostPointerCapture":
806
1322
  return (event) => {
807
1323
  const { internal } = store.getState();
808
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1324
+ const pointerId = getPointerId(event);
1325
+ const pointerState = internal.pointerMap.get(pointerId);
1326
+ if (pointerState?.captured.size) {
809
1327
  requestAnimationFrame(() => {
810
- if (internal.capturedMap.has(event.pointerId)) {
811
- internal.capturedMap.delete(event.pointerId);
812
- cancelPointer([]);
1328
+ const pointerState2 = internal.pointerMap.get(pointerId);
1329
+ if (pointerState2?.captured.size) {
1330
+ pointerState2.captured.clear();
813
1331
  }
1332
+ cancelPointer([], pointerId);
814
1333
  });
815
1334
  }
816
1335
  };
817
1336
  }
818
1337
  return function handleEvent(event) {
819
1338
  const state = store.getState();
820
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1339
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1340
+ const pointerId = getPointerId(event);
821
1341
  internal.lastEvent.current = event;
822
- if (!state.events.enabled) return;
1342
+ if (!events.enabled) return;
823
1343
  const isPointerMove = name === "onPointerMove";
824
1344
  const isDragOver = name === "onDragOver";
825
1345
  const isDrop = name === "onDrop";
826
1346
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1347
+ const isPointerDown = name === "onPointerDown";
1348
+ const isPointerUp = name === "onPointerUp";
1349
+ const isWheel = name === "onWheel";
1350
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1351
+ if (isPointerMove && canDeferRaycasts) {
1352
+ events.compute?.(event, state);
1353
+ internal.pointerDirty.set(pointerId, event);
1354
+ return;
1355
+ }
1356
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1357
+ events.compute?.(event, state);
1358
+ internal.pointerDirty.set(pointerId, event);
1359
+ return;
1360
+ }
1361
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1362
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1363
+ internal.pointerDirty.delete(pointerId);
1364
+ processDeferredPointer(deferredEvent, pointerId);
1365
+ }
827
1366
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
828
1367
  const hits = intersect(event, filter);
829
- const delta = isClickEvent ? calculateDistance(event) : 0;
830
- if (name === "onPointerDown") {
831
- internal.initialClick = [event.offsetX, event.offsetY];
832
- internal.initialHits = hits.map((hit) => hit.eventObject);
833
- }
1368
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1369
+ if (isPointerDown) {
1370
+ const pointerState2 = getPointerState(internal, pointerId);
1371
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1372
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1373
+ }
1374
+ const pointerState = internal.pointerMap.get(pointerId);
1375
+ const initialHits = pointerState?.initialHits ?? [];
834
1376
  if (isClickEvent && !hits.length) {
835
1377
  if (delta <= 2) {
836
1378
  pointerMissed(event, internal.interaction);
@@ -845,7 +1387,9 @@ function createEvents(store) {
845
1387
  dropMissed(event, internal.interaction);
846
1388
  if (onDropMissed) onDropMissed(event);
847
1389
  }
848
- if (isPointerMove || isDragOver) cancelPointer(hits);
1390
+ if (isPointerMove || isDragOver) {
1391
+ cancelPointer(hits, pointerId);
1392
+ }
849
1393
  function onIntersect(data) {
850
1394
  const eventObject = data.eventObject;
851
1395
  const instance = eventObject.__r3f;
@@ -854,9 +1398,10 @@ function createEvents(store) {
854
1398
  if (isPointerMove) {
855
1399
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
856
1400
  const id = makeId(data);
857
- const hoveredItem = internal.hovered.get(id);
1401
+ const pointerState2 = getPointerState(internal, pointerId);
1402
+ const hoveredItem = pointerState2.hovered.get(id);
858
1403
  if (!hoveredItem) {
859
- internal.hovered.set(id, data);
1404
+ pointerState2.hovered.set(id, data);
860
1405
  handlers.onPointerOver?.(data);
861
1406
  handlers.onPointerEnter?.(data);
862
1407
  } else if (hoveredItem.stopped) {
@@ -866,9 +1411,10 @@ function createEvents(store) {
866
1411
  handlers.onPointerMove?.(data);
867
1412
  } else if (isDragOver) {
868
1413
  const id = makeId(data);
869
- const hoveredItem = internal.hovered.get(id);
1414
+ const pointerState2 = getPointerState(internal, pointerId);
1415
+ const hoveredItem = pointerState2.hovered.get(id);
870
1416
  if (!hoveredItem) {
871
- internal.hovered.set(id, data);
1417
+ pointerState2.hovered.set(id, data);
872
1418
  handlers.onDragOverEnter?.(data);
873
1419
  } else if (hoveredItem.stopped) {
874
1420
  data.stopPropagation();
@@ -879,18 +1425,18 @@ function createEvents(store) {
879
1425
  } else {
880
1426
  const handler = handlers[name];
881
1427
  if (handler) {
882
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1428
+ if (!isClickEvent || initialHits.includes(eventObject)) {
883
1429
  pointerMissed(
884
1430
  event,
885
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1431
+ internal.interaction.filter((object) => !initialHits.includes(object))
886
1432
  );
887
1433
  handler(data);
888
1434
  }
889
1435
  } else {
890
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1436
+ if (isClickEvent && initialHits.includes(eventObject)) {
891
1437
  pointerMissed(
892
1438
  event,
893
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1439
+ internal.interaction.filter((object) => !initialHits.includes(object))
894
1440
  );
895
1441
  }
896
1442
  }
@@ -899,7 +1445,15 @@ function createEvents(store) {
899
1445
  handleIntersects(hits, event, delta, onIntersect);
900
1446
  };
901
1447
  }
902
- return { handlePointer };
1448
+ function flushDeferredPointers() {
1449
+ const { internal, events } = store.getState();
1450
+ if (!events.frameTimedRaycasts) return;
1451
+ for (const [pointerId, event] of internal.pointerDirty) {
1452
+ processDeferredPointer(event, pointerId);
1453
+ }
1454
+ internal.pointerDirty.clear();
1455
+ }
1456
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
903
1457
  }
904
1458
  const DOM_EVENTS = {
905
1459
  onClick: ["click", false],
@@ -918,11 +1472,16 @@ const DOM_EVENTS = {
918
1472
  onLostPointerCapture: ["lostpointercapture", true]
919
1473
  };
920
1474
  function createPointerEvents(store) {
921
- const { handlePointer } = createEvents(store);
1475
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1476
+ let nextXRPointerId = XR_POINTER_ID_START;
1477
+ const xrPointers = /* @__PURE__ */ new Map();
922
1478
  return {
923
1479
  priority: 1,
924
1480
  enabled: true,
925
- compute(event, state, previous) {
1481
+ frameTimedRaycasts: true,
1482
+ alwaysFireOnScroll: true,
1483
+ updateOnFrame: false,
1484
+ compute(event, state) {
926
1485
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
927
1486
  state.raycaster.setFromCamera(state.pointer, state.camera);
928
1487
  },
@@ -931,11 +1490,33 @@ function createPointerEvents(store) {
931
1490
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
932
1491
  {}
933
1492
  ),
934
- update: () => {
1493
+ update: (pointerId) => {
1494
+ const { events, internal } = store.getState();
1495
+ if (!events.handlers) return;
1496
+ if (pointerId !== void 0) {
1497
+ const event = internal.pointerDirty.get(pointerId);
1498
+ if (event) {
1499
+ internal.pointerDirty.delete(pointerId);
1500
+ processDeferredPointer(event, pointerId);
1501
+ } else if (internal.lastEvent?.current) {
1502
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1503
+ }
1504
+ } else {
1505
+ flushDeferredPointers();
1506
+ if (internal.lastEvent?.current) {
1507
+ events.handlers.onPointerMove(internal.lastEvent.current);
1508
+ }
1509
+ }
1510
+ },
1511
+ flush: () => {
935
1512
  const { events, internal } = store.getState();
936
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1513
+ flushDeferredPointers();
1514
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1515
+ events.handlers.onPointerMove(internal.lastEvent.current);
1516
+ }
937
1517
  },
938
1518
  connect: (target) => {
1519
+ if (!target) return;
939
1520
  const { set, events } = store.getState();
940
1521
  events.disconnect?.();
941
1522
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -959,6 +1540,32 @@ function createPointerEvents(store) {
959
1540
  }
960
1541
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
961
1542
  }
1543
+ },
1544
+ registerPointer: (config) => {
1545
+ const pointerId = nextXRPointerId++;
1546
+ xrPointers.set(pointerId, config);
1547
+ const { internal } = store.getState();
1548
+ getPointerState(internal, pointerId);
1549
+ return pointerId;
1550
+ },
1551
+ unregisterPointer: (pointerId) => {
1552
+ xrPointers.delete(pointerId);
1553
+ const { internal } = store.getState();
1554
+ const pointerState = internal.pointerMap.get(pointerId);
1555
+ if (pointerState) {
1556
+ for (const [, hoveredObj] of pointerState.hovered) {
1557
+ const eventObject = hoveredObj.eventObject;
1558
+ const instance = eventObject.__r3f;
1559
+ if (instance?.eventCount) {
1560
+ const handlers = instance.handlers;
1561
+ const data = { ...hoveredObj, intersections: [] };
1562
+ handlers.onPointerOut?.(data);
1563
+ handlers.onPointerLeave?.(data);
1564
+ }
1565
+ }
1566
+ internal.pointerMap.delete(pointerId);
1567
+ }
1568
+ internal.pointerDirty.delete(pointerId);
962
1569
  }
963
1570
  };
964
1571
  }
@@ -1020,331 +1627,26 @@ function notifyAlpha({ message, link }) {
1020
1627
  }
1021
1628
  }
1022
1629
 
1023
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1024
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1025
- const createStore = (invalidate, advance) => {
1026
- const rootStore = createWithEqualityFn((set, get) => {
1027
- const position = new Vector3();
1028
- const defaultTarget = new Vector3();
1029
- const tempTarget = new Vector3();
1030
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1031
- const { width, height, top, left } = size;
1032
- const aspect = width / height;
1033
- if (target.isVector3) tempTarget.copy(target);
1034
- else tempTarget.set(...target);
1035
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1036
- if (isOrthographicCamera(camera)) {
1037
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1038
- } else {
1039
- const fov = camera.fov * Math.PI / 180;
1040
- const h = 2 * Math.tan(fov / 2) * distance;
1041
- const w = h * (width / height);
1042
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1043
- }
1044
- }
1045
- let performanceTimeout = void 0;
1046
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1047
- const pointer = new Vector2();
1048
- const rootState = {
1049
- set,
1050
- get,
1051
- // Mock objects that have to be configured
1052
- gl: null,
1053
- renderer: null,
1054
- camera: null,
1055
- frustum: new Frustum(),
1056
- autoUpdateFrustum: true,
1057
- raycaster: null,
1058
- events: { priority: 1, enabled: true, connected: false },
1059
- scene: null,
1060
- rootScene: null,
1061
- xr: null,
1062
- inspector: null,
1063
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1064
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1065
- legacy: false,
1066
- linear: false,
1067
- flat: false,
1068
- textureColorSpace: "srgb",
1069
- isLegacy: false,
1070
- webGPUSupported: false,
1071
- isNative: false,
1072
- controls: null,
1073
- pointer,
1074
- mouse: pointer,
1075
- frameloop: "always",
1076
- onPointerMissed: void 0,
1077
- onDragOverMissed: void 0,
1078
- onDropMissed: void 0,
1079
- performance: {
1080
- current: 1,
1081
- min: 0.5,
1082
- max: 1,
1083
- debounce: 200,
1084
- regress: () => {
1085
- const state2 = get();
1086
- if (performanceTimeout) clearTimeout(performanceTimeout);
1087
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1088
- performanceTimeout = setTimeout(
1089
- () => setPerformanceCurrent(get().performance.max),
1090
- state2.performance.debounce
1091
- );
1092
- }
1093
- },
1094
- size: { width: 0, height: 0, top: 0, left: 0 },
1095
- viewport: {
1096
- initialDpr: 0,
1097
- dpr: 0,
1098
- width: 0,
1099
- height: 0,
1100
- top: 0,
1101
- left: 0,
1102
- aspect: 0,
1103
- distance: 0,
1104
- factor: 0,
1105
- getCurrentViewport
1106
- },
1107
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1108
- setSize: (width, height, top, left) => {
1109
- const state2 = get();
1110
- if (width === void 0) {
1111
- set({ _sizeImperative: false });
1112
- if (state2._sizeProps) {
1113
- const { width: propW, height: propH } = state2._sizeProps;
1114
- if (propW !== void 0 || propH !== void 0) {
1115
- const currentSize = state2.size;
1116
- const newSize = {
1117
- width: propW ?? currentSize.width,
1118
- height: propH ?? currentSize.height,
1119
- top: currentSize.top,
1120
- left: currentSize.left
1121
- };
1122
- set((s) => ({
1123
- size: newSize,
1124
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1125
- }));
1126
- }
1127
- }
1128
- return;
1129
- }
1130
- const w = width;
1131
- const h = height ?? width;
1132
- const t = top ?? state2.size.top;
1133
- const l = left ?? state2.size.left;
1134
- const size = { width: w, height: h, top: t, left: l };
1135
- set((s) => ({
1136
- size,
1137
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1138
- _sizeImperative: true
1139
- }));
1140
- },
1141
- setDpr: (dpr) => set((state2) => {
1142
- const resolved = calculateDpr(dpr);
1143
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1144
- }),
1145
- setFrameloop: (frameloop = "always") => {
1146
- set(() => ({ frameloop }));
1147
- },
1148
- setError: (error) => set(() => ({ error })),
1149
- error: null,
1150
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1151
- uniforms: {},
1152
- nodes: {},
1153
- textures: /* @__PURE__ */ new Map(),
1154
- postProcessing: null,
1155
- passes: {},
1156
- _hmrVersion: 0,
1157
- _sizeImperative: false,
1158
- _sizeProps: null,
1159
- previousRoot: void 0,
1160
- internal: {
1161
- // Events
1162
- interaction: [],
1163
- hovered: /* @__PURE__ */ new Map(),
1164
- subscribers: [],
1165
- initialClick: [0, 0],
1166
- initialHits: [],
1167
- capturedMap: /* @__PURE__ */ new Map(),
1168
- lastEvent: React.createRef(),
1169
- // Visibility tracking (onFramed, onOccluded, onVisible)
1170
- visibilityRegistry: /* @__PURE__ */ new Map(),
1171
- // Occlusion system (WebGPU only)
1172
- occlusionEnabled: false,
1173
- occlusionObserver: null,
1174
- occlusionCache: /* @__PURE__ */ new Map(),
1175
- helperGroup: null,
1176
- // Updates
1177
- active: false,
1178
- frames: 0,
1179
- priority: 0,
1180
- subscribe: (ref, priority, store) => {
1181
- const internal = get().internal;
1182
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1183
- internal.subscribers.push({ ref, priority, store });
1184
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1185
- return () => {
1186
- const internal2 = get().internal;
1187
- if (internal2?.subscribers) {
1188
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1189
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1190
- }
1191
- };
1192
- },
1193
- // Renderer Storage (single source of truth)
1194
- actualRenderer: null,
1195
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1196
- scheduler: null
1197
- }
1198
- };
1199
- return rootState;
1200
- });
1201
- const state = rootStore.getState();
1202
- Object.defineProperty(state, "gl", {
1203
- get() {
1204
- const currentState = rootStore.getState();
1205
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1206
- const stack = new Error().stack || "";
1207
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1208
- if (!isInternalAccess) {
1209
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1210
- notifyDepreciated({
1211
- heading: "Accessing state.gl in WebGPU mode",
1212
- 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
1213
- });
1214
- }
1215
- }
1216
- return currentState.internal.actualRenderer;
1217
- },
1218
- set(value) {
1219
- rootStore.getState().internal.actualRenderer = value;
1220
- },
1221
- enumerable: true,
1222
- configurable: true
1223
- });
1224
- Object.defineProperty(state, "renderer", {
1225
- get() {
1226
- return rootStore.getState().internal.actualRenderer;
1227
- },
1228
- set(value) {
1229
- rootStore.getState().internal.actualRenderer = value;
1230
- },
1231
- enumerable: true,
1232
- configurable: true
1233
- });
1234
- let oldScene = state.scene;
1235
- rootStore.subscribe(() => {
1236
- const currentState = rootStore.getState();
1237
- const { scene, rootScene, set } = currentState;
1238
- if (scene !== oldScene) {
1239
- oldScene = scene;
1240
- if (scene?.isScene && scene !== rootScene) {
1241
- set({ rootScene: scene });
1242
- }
1243
- }
1244
- });
1245
- let oldSize = state.size;
1246
- let oldDpr = state.viewport.dpr;
1247
- let oldCamera = state.camera;
1248
- rootStore.subscribe(() => {
1249
- const { camera, size, viewport, set, internal } = rootStore.getState();
1250
- const actualRenderer = internal.actualRenderer;
1251
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1252
- oldSize = size;
1253
- oldDpr = viewport.dpr;
1254
- updateCamera(camera, size);
1255
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1256
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1257
- actualRenderer.setSize(size.width, size.height, updateStyle);
1258
- }
1259
- if (camera !== oldCamera) {
1260
- oldCamera = camera;
1261
- const { rootScene } = rootStore.getState();
1262
- if (camera && rootScene && !camera.parent) {
1263
- rootScene.add(camera);
1264
- }
1265
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1266
- const currentState = rootStore.getState();
1267
- if (currentState.autoUpdateFrustum && camera) {
1268
- updateFrustum(camera, currentState.frustum);
1269
- }
1270
- }
1271
- });
1272
- rootStore.subscribe((state2) => invalidate(state2));
1273
- return rootStore;
1274
- };
1275
-
1276
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1277
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1278
- function getLoader(Proto) {
1279
- if (isConstructor$1(Proto)) {
1280
- let loader = memoizedLoaders.get(Proto);
1281
- if (!loader) {
1282
- loader = new Proto();
1283
- memoizedLoaders.set(Proto, loader);
1284
- }
1285
- return loader;
1286
- }
1287
- return Proto;
1288
- }
1289
- function loadingFn(extensions, onProgress) {
1290
- return function(Proto, input) {
1291
- const loader = getLoader(Proto);
1292
- if (extensions) extensions(loader);
1293
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1294
- return loader.loadAsync(input, onProgress).then((data) => {
1295
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1296
- return data;
1297
- });
1298
- }
1299
- return new Promise(
1300
- (res, reject) => loader.load(
1301
- input,
1302
- (data) => {
1303
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1304
- res(data);
1305
- },
1306
- onProgress,
1307
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1308
- )
1309
- );
1310
- };
1311
- }
1312
- function useLoader(loader, input, extensions, onProgress) {
1313
- const keys = Array.isArray(input) ? input : [input];
1314
- const fn = loadingFn(extensions, onProgress);
1315
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1316
- return Array.isArray(input) ? results : results[0];
1317
- }
1318
- useLoader.preload = function(loader, input, extensions, onProgress) {
1319
- const keys = Array.isArray(input) ? input : [input];
1320
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1321
- };
1322
- useLoader.clear = function(loader, input) {
1323
- const keys = Array.isArray(input) ? input : [input];
1324
- keys.forEach((key) => clear([loader, key]));
1325
- };
1326
- useLoader.loader = getLoader;
1327
-
1328
- var __defProp$1 = Object.defineProperty;
1329
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1330
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1331
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1332
- class PhaseGraph {
1333
- constructor() {
1334
- /** Ordered list of phase nodes */
1335
- __publicField$1(this, "phases", []);
1336
- /** Quick lookup by name */
1337
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1338
- /** Cached ordered names (invalidated on changes) */
1339
- __publicField$1(this, "orderedNamesCache", null);
1340
- this.initializeDefaultPhases();
1341
- }
1342
- //* Initialization --------------------------------
1343
- initializeDefaultPhases() {
1344
- for (const name of DEFAULT_PHASES) {
1345
- const node = { name, isAutoGenerated: false };
1346
- this.phases.push(node);
1347
- this.phaseMap.set(name, node);
1630
+ var __defProp$1 = Object.defineProperty;
1631
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1632
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1633
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1634
+ class PhaseGraph {
1635
+ constructor() {
1636
+ /** Ordered list of phase nodes */
1637
+ __publicField$1(this, "phases", []);
1638
+ /** Quick lookup by name */
1639
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1640
+ /** Cached ordered names (invalidated on changes) */
1641
+ __publicField$1(this, "orderedNamesCache", null);
1642
+ this.initializeDefaultPhases();
1643
+ }
1644
+ //* Initialization --------------------------------
1645
+ initializeDefaultPhases() {
1646
+ for (const name of DEFAULT_PHASES) {
1647
+ const node = { name, isAutoGenerated: false };
1648
+ this.phases.push(node);
1649
+ this.phaseMap.set(name, node);
1348
1650
  }
1349
1651
  this.invalidateCache();
1350
1652
  }
@@ -1577,7 +1879,7 @@ function shouldRun(job, now) {
1577
1879
  const minInterval = 1e3 / job.fps;
1578
1880
  const lastRun = job.lastRun ?? 0;
1579
1881
  const elapsed = now - lastRun;
1580
- if (elapsed < minInterval) return false;
1882
+ if (elapsed < minInterval - 1) return false;
1581
1883
  if (job.drop) {
1582
1884
  job.lastRun = now;
1583
1885
  } else {
@@ -2246,116 +2548,444 @@ const _Scheduler = class _Scheduler {
2246
2548
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2247
2549
  root.needsRebuild = false;
2248
2550
  }
2249
- const providedState = root.getState?.() ?? {};
2250
- const frameState = {
2251
- ...providedState,
2252
- time: timestamp,
2253
- delta,
2254
- elapsed: this.loopState.elapsedTime / 1e3,
2255
- // Convert ms to seconds
2256
- frame: this.loopState.frameCount
2257
- };
2258
- for (const job of root.sortedJobs) {
2259
- if (!shouldRun(job, timestamp)) continue;
2260
- try {
2261
- job.callback(frameState, delta);
2262
- } catch (error) {
2263
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2264
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2551
+ const providedState = root.getState?.() ?? {};
2552
+ const frameState = {
2553
+ ...providedState,
2554
+ time: timestamp,
2555
+ delta,
2556
+ elapsed: this.loopState.elapsedTime / 1e3,
2557
+ // Convert ms to seconds
2558
+ frame: this.loopState.frameCount
2559
+ };
2560
+ for (const job of root.sortedJobs) {
2561
+ if (!shouldRun(job, timestamp)) continue;
2562
+ try {
2563
+ job.callback(frameState, delta);
2564
+ } catch (error) {
2565
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2566
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2567
+ }
2568
+ }
2569
+ }
2570
+ //* Debug & Inspection Methods ================================
2571
+ /**
2572
+ * Get the total number of registered jobs across all roots.
2573
+ * Includes both per-root jobs and global before/after jobs.
2574
+ * @returns {number} Total job count
2575
+ */
2576
+ getJobCount() {
2577
+ let count = 0;
2578
+ for (const root of this.roots.values()) {
2579
+ count += root.jobs.size;
2580
+ }
2581
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2582
+ }
2583
+ /**
2584
+ * Get all registered job IDs across all roots.
2585
+ * Includes both per-root jobs and global before/after jobs.
2586
+ * @returns {string[]} Array of all job IDs
2587
+ */
2588
+ getJobIds() {
2589
+ const ids = [];
2590
+ for (const root of this.roots.values()) {
2591
+ ids.push(...root.jobs.keys());
2592
+ }
2593
+ ids.push(...this.globalBeforeJobs.keys());
2594
+ ids.push(...this.globalAfterJobs.keys());
2595
+ return ids;
2596
+ }
2597
+ /**
2598
+ * Get the number of registered roots (Canvas instances).
2599
+ * @returns {number} Number of registered roots
2600
+ */
2601
+ getRootCount() {
2602
+ return this.roots.size;
2603
+ }
2604
+ /**
2605
+ * Check if any user (non-system) jobs are registered in a specific phase.
2606
+ * Used by the default render job to know if a user has taken over rendering.
2607
+ *
2608
+ * @param phase The phase to check
2609
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2610
+ * @returns true if any user jobs exist in the phase
2611
+ */
2612
+ hasUserJobsInPhase(phase, rootId) {
2613
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2614
+ return rootsToCheck.some((root) => {
2615
+ if (!root) return false;
2616
+ for (const job of root.jobs.values()) {
2617
+ if (job.phase === phase && !job.system && job.enabled) return true;
2618
+ }
2619
+ return false;
2620
+ });
2621
+ }
2622
+ //* Utility Methods ================================
2623
+ /**
2624
+ * Generate a unique root ID for automatic root registration.
2625
+ * @returns {string} A unique root ID in the format 'root_N'
2626
+ */
2627
+ generateRootId() {
2628
+ return `root_${this.nextRootIndex++}`;
2629
+ }
2630
+ /**
2631
+ * Generate a unique job ID.
2632
+ * @returns {string} A unique job ID in the format 'job_N'
2633
+ * @private
2634
+ */
2635
+ generateJobId() {
2636
+ return `job_${this.nextJobIndex}`;
2637
+ }
2638
+ /**
2639
+ * Normalize before/after constraints to a Set.
2640
+ * Handles undefined, single string, or array inputs.
2641
+ * @param {string | string[] | undefined} value - The constraint value(s)
2642
+ * @returns {Set<string>} Normalized Set of constraint strings
2643
+ * @private
2644
+ */
2645
+ normalizeConstraints(value) {
2646
+ if (!value) return /* @__PURE__ */ new Set();
2647
+ if (Array.isArray(value)) return new Set(value);
2648
+ return /* @__PURE__ */ new Set([value]);
2649
+ }
2650
+ };
2651
+ //* Static State & Methods (Singleton Usage) ================================
2652
+ //* Cross-Bundle Singleton Key ==============================
2653
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2654
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2655
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2656
+ let Scheduler = _Scheduler;
2657
+ const getScheduler = () => Scheduler.get();
2658
+ if (hmrData) {
2659
+ hmrData.accept?.();
2660
+ }
2661
+
2662
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2663
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
2664
+ const createStore = (invalidate, advance) => {
2665
+ const rootStore = createWithEqualityFn((set, get) => {
2666
+ const position = new Vector3();
2667
+ const defaultTarget = new Vector3();
2668
+ const tempTarget = new Vector3();
2669
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2670
+ const { width, height, top, left } = size;
2671
+ const aspect = width / height;
2672
+ if (target.isVector3) tempTarget.copy(target);
2673
+ else tempTarget.set(...target);
2674
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2675
+ if (isOrthographicCamera(camera)) {
2676
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2677
+ } else {
2678
+ const fov = camera.fov * Math.PI / 180;
2679
+ const h = 2 * Math.tan(fov / 2) * distance;
2680
+ const w = h * (width / height);
2681
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2682
+ }
2683
+ }
2684
+ let performanceTimeout = void 0;
2685
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2686
+ const pointer = new Vector2();
2687
+ const rootState = {
2688
+ set,
2689
+ get,
2690
+ // Mock objects that have to be configured
2691
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2692
+ primaryStore: null,
2693
+ gl: null,
2694
+ renderer: null,
2695
+ camera: null,
2696
+ frustum: new Frustum(),
2697
+ autoUpdateFrustum: true,
2698
+ raycaster: null,
2699
+ events: {
2700
+ priority: 1,
2701
+ enabled: true,
2702
+ connected: false,
2703
+ frameTimedRaycasts: true,
2704
+ alwaysFireOnScroll: true,
2705
+ updateOnFrame: false
2706
+ },
2707
+ scene: null,
2708
+ rootScene: null,
2709
+ xr: null,
2710
+ inspector: null,
2711
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2712
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2713
+ textureColorSpace: SRGBColorSpace,
2714
+ isLegacy: false,
2715
+ webGPUSupported: false,
2716
+ isNative: false,
2717
+ controls: null,
2718
+ pointer,
2719
+ mouse: pointer,
2720
+ frameloop: "always",
2721
+ onPointerMissed: void 0,
2722
+ onDragOverMissed: void 0,
2723
+ onDropMissed: void 0,
2724
+ performance: {
2725
+ current: 1,
2726
+ min: 0.5,
2727
+ max: 1,
2728
+ debounce: 200,
2729
+ regress: () => {
2730
+ const state2 = get();
2731
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2732
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2733
+ performanceTimeout = setTimeout(
2734
+ () => setPerformanceCurrent(get().performance.max),
2735
+ state2.performance.debounce
2736
+ );
2737
+ }
2738
+ },
2739
+ size: { width: 0, height: 0, top: 0, left: 0 },
2740
+ viewport: {
2741
+ initialDpr: 0,
2742
+ dpr: 0,
2743
+ width: 0,
2744
+ height: 0,
2745
+ top: 0,
2746
+ left: 0,
2747
+ aspect: 0,
2748
+ distance: 0,
2749
+ factor: 0,
2750
+ getCurrentViewport
2751
+ },
2752
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2753
+ setSize: (width, height, top, left) => {
2754
+ const state2 = get();
2755
+ if (width === void 0) {
2756
+ set({ _sizeImperative: false });
2757
+ if (state2._sizeProps) {
2758
+ const { width: propW, height: propH } = state2._sizeProps;
2759
+ if (propW !== void 0 || propH !== void 0) {
2760
+ const currentSize = state2.size;
2761
+ const newSize = {
2762
+ width: propW ?? currentSize.width,
2763
+ height: propH ?? currentSize.height,
2764
+ top: currentSize.top,
2765
+ left: currentSize.left
2766
+ };
2767
+ set((s) => ({
2768
+ size: newSize,
2769
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2770
+ }));
2771
+ getScheduler().invalidate();
2772
+ }
2773
+ }
2774
+ return;
2775
+ }
2776
+ const w = width;
2777
+ const h = height ?? width;
2778
+ const t = top ?? state2.size.top;
2779
+ const l = left ?? state2.size.left;
2780
+ const size = { width: w, height: h, top: t, left: l };
2781
+ set((s) => ({
2782
+ size,
2783
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2784
+ _sizeImperative: true
2785
+ }));
2786
+ getScheduler().invalidate();
2787
+ },
2788
+ setDpr: (dpr) => set((state2) => {
2789
+ const resolved = calculateDpr(dpr);
2790
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2791
+ }),
2792
+ setFrameloop: (frameloop = "always") => {
2793
+ set(() => ({ frameloop }));
2794
+ },
2795
+ setError: (error) => set(() => ({ error })),
2796
+ error: null,
2797
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2798
+ uniforms: {},
2799
+ nodes: {},
2800
+ buffers: {},
2801
+ gpuStorage: {},
2802
+ textures: /* @__PURE__ */ new Map(),
2803
+ renderPipeline: null,
2804
+ passes: {},
2805
+ _hmrVersion: 0,
2806
+ _sizeImperative: false,
2807
+ _sizeProps: null,
2808
+ previousRoot: void 0,
2809
+ internal: {
2810
+ // Events
2811
+ interaction: [],
2812
+ subscribers: [],
2813
+ // Per-pointer state (new unified structure)
2814
+ pointerMap: /* @__PURE__ */ new Map(),
2815
+ pointerDirty: /* @__PURE__ */ new Map(),
2816
+ lastEvent: React.createRef(),
2817
+ // Deprecated but kept for backwards compatibility
2818
+ hovered: /* @__PURE__ */ new Map(),
2819
+ initialClick: [0, 0],
2820
+ initialHits: [],
2821
+ capturedMap: /* @__PURE__ */ new Map(),
2822
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2823
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2824
+ // Occlusion system (WebGPU only)
2825
+ occlusionEnabled: false,
2826
+ occlusionObserver: null,
2827
+ occlusionCache: /* @__PURE__ */ new Map(),
2828
+ helperGroup: null,
2829
+ // Updates
2830
+ active: false,
2831
+ frames: 0,
2832
+ priority: 0,
2833
+ subscribe: (ref, priority, store) => {
2834
+ const internal = get().internal;
2835
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2836
+ internal.subscribers.push({ ref, priority, store });
2837
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2838
+ return () => {
2839
+ const internal2 = get().internal;
2840
+ if (internal2?.subscribers) {
2841
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2842
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2843
+ }
2844
+ };
2845
+ },
2846
+ // Renderer Storage (single source of truth)
2847
+ actualRenderer: null,
2848
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2849
+ scheduler: null
2850
+ }
2851
+ };
2852
+ return rootState;
2853
+ });
2854
+ const state = rootStore.getState();
2855
+ Object.defineProperty(state, "gl", {
2856
+ get() {
2857
+ const currentState = rootStore.getState();
2858
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2859
+ const stack = new Error().stack || "";
2860
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2861
+ if (!isInternalAccess) {
2862
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2863
+ notifyDepreciated({
2864
+ heading: "Accessing state.gl in WebGPU mode",
2865
+ 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
2866
+ });
2867
+ }
2868
+ }
2869
+ return currentState.internal.actualRenderer;
2870
+ },
2871
+ set(value) {
2872
+ rootStore.getState().internal.actualRenderer = value;
2873
+ },
2874
+ enumerable: true,
2875
+ configurable: true
2876
+ });
2877
+ Object.defineProperty(state, "renderer", {
2878
+ get() {
2879
+ return rootStore.getState().internal.actualRenderer;
2880
+ },
2881
+ set(value) {
2882
+ rootStore.getState().internal.actualRenderer = value;
2883
+ },
2884
+ enumerable: true,
2885
+ configurable: true
2886
+ });
2887
+ let oldScene = state.scene;
2888
+ rootStore.subscribe(() => {
2889
+ const currentState = rootStore.getState();
2890
+ const { scene, rootScene, set } = currentState;
2891
+ if (scene !== oldScene) {
2892
+ oldScene = scene;
2893
+ if (scene?.isScene && scene !== rootScene) {
2894
+ set({ rootScene: scene });
2895
+ }
2896
+ }
2897
+ });
2898
+ let oldSize = state.size;
2899
+ let oldDpr = state.viewport.dpr;
2900
+ let oldCamera = state.camera;
2901
+ rootStore.subscribe(() => {
2902
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2903
+ const actualRenderer = internal.actualRenderer;
2904
+ const canvasTarget = internal.canvasTarget;
2905
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2906
+ oldSize = size;
2907
+ oldDpr = viewport.dpr;
2908
+ updateCamera(camera, size);
2909
+ if (internal.isSecondary && canvasTarget) {
2910
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2911
+ canvasTarget.setSize(size.width, size.height, false);
2912
+ } else {
2913
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2914
+ actualRenderer.setSize(size.width, size.height, false);
2915
+ if (canvasTarget) {
2916
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2917
+ canvasTarget.setSize(size.width, size.height, false);
2918
+ }
2919
+ }
2920
+ }
2921
+ if (camera !== oldCamera) {
2922
+ oldCamera = camera;
2923
+ const { rootScene } = rootStore.getState();
2924
+ if (camera && rootScene && !camera.parent) {
2925
+ rootScene.add(camera);
2926
+ }
2927
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2928
+ const currentState = rootStore.getState();
2929
+ if (currentState.autoUpdateFrustum && camera) {
2930
+ updateFrustum(camera, currentState.frustum);
2265
2931
  }
2266
2932
  }
2267
- }
2268
- //* Debug & Inspection Methods ================================
2269
- /**
2270
- * Get the total number of registered jobs across all roots.
2271
- * Includes both per-root jobs and global before/after jobs.
2272
- * @returns {number} Total job count
2273
- */
2274
- getJobCount() {
2275
- let count = 0;
2276
- for (const root of this.roots.values()) {
2277
- count += root.jobs.size;
2933
+ });
2934
+ rootStore.subscribe((state2) => invalidate(state2));
2935
+ return rootStore;
2936
+ };
2937
+
2938
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2939
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2940
+ function getLoader(Proto) {
2941
+ if (isConstructor$1(Proto)) {
2942
+ let loader = memoizedLoaders.get(Proto);
2943
+ if (!loader) {
2944
+ loader = new Proto();
2945
+ memoizedLoaders.set(Proto, loader);
2278
2946
  }
2279
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2947
+ return loader;
2280
2948
  }
2281
- /**
2282
- * Get all registered job IDs across all roots.
2283
- * Includes both per-root jobs and global before/after jobs.
2284
- * @returns {string[]} Array of all job IDs
2285
- */
2286
- getJobIds() {
2287
- const ids = [];
2288
- for (const root of this.roots.values()) {
2289
- ids.push(...root.jobs.keys());
2949
+ return Proto;
2950
+ }
2951
+ function loadingFn(extensions, onProgress) {
2952
+ return function(Proto, input) {
2953
+ const loader = getLoader(Proto);
2954
+ if (extensions) extensions(loader);
2955
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2956
+ return loader.loadAsync(input, onProgress).then((data) => {
2957
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2958
+ return data;
2959
+ });
2290
2960
  }
2291
- ids.push(...this.globalBeforeJobs.keys());
2292
- ids.push(...this.globalAfterJobs.keys());
2293
- return ids;
2294
- }
2295
- /**
2296
- * Get the number of registered roots (Canvas instances).
2297
- * @returns {number} Number of registered roots
2298
- */
2299
- getRootCount() {
2300
- return this.roots.size;
2301
- }
2302
- /**
2303
- * Check if any user (non-system) jobs are registered in a specific phase.
2304
- * Used by the default render job to know if a user has taken over rendering.
2305
- *
2306
- * @param phase The phase to check
2307
- * @param rootId Optional root ID to check (checks all roots if not provided)
2308
- * @returns true if any user jobs exist in the phase
2309
- */
2310
- hasUserJobsInPhase(phase, rootId) {
2311
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2312
- return rootsToCheck.some((root) => {
2313
- if (!root) return false;
2314
- for (const job of root.jobs.values()) {
2315
- if (job.phase === phase && !job.system && job.enabled) return true;
2316
- }
2317
- return false;
2318
- });
2319
- }
2320
- //* Utility Methods ================================
2321
- /**
2322
- * Generate a unique root ID for automatic root registration.
2323
- * @returns {string} A unique root ID in the format 'root_N'
2324
- */
2325
- generateRootId() {
2326
- return `root_${this.nextRootIndex++}`;
2327
- }
2328
- /**
2329
- * Generate a unique job ID.
2330
- * @returns {string} A unique job ID in the format 'job_N'
2331
- * @private
2332
- */
2333
- generateJobId() {
2334
- return `job_${this.nextJobIndex}`;
2335
- }
2336
- /**
2337
- * Normalize before/after constraints to a Set.
2338
- * Handles undefined, single string, or array inputs.
2339
- * @param {string | string[] | undefined} value - The constraint value(s)
2340
- * @returns {Set<string>} Normalized Set of constraint strings
2341
- * @private
2342
- */
2343
- normalizeConstraints(value) {
2344
- if (!value) return /* @__PURE__ */ new Set();
2345
- if (Array.isArray(value)) return new Set(value);
2346
- return /* @__PURE__ */ new Set([value]);
2347
- }
2348
- };
2349
- //* Static State & Methods (Singleton Usage) ================================
2350
- //* Cross-Bundle Singleton Key ==============================
2351
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2352
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2353
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2354
- let Scheduler = _Scheduler;
2355
- const getScheduler = () => Scheduler.get();
2356
- if (hmrData) {
2357
- hmrData.accept?.();
2961
+ return new Promise(
2962
+ (res, reject) => loader.load(
2963
+ input,
2964
+ (data) => {
2965
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2966
+ res(data);
2967
+ },
2968
+ onProgress,
2969
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2970
+ )
2971
+ );
2972
+ };
2973
+ }
2974
+ function useLoader(loader, input, extensions, onProgress) {
2975
+ const keys = Array.isArray(input) ? input : [input];
2976
+ const fn = loadingFn(extensions, onProgress);
2977
+ const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
2978
+ return Array.isArray(input) ? results : results[0];
2358
2979
  }
2980
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2981
+ const keys = Array.isArray(input) ? input : [input];
2982
+ keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2983
+ };
2984
+ useLoader.clear = function(loader, input) {
2985
+ const keys = Array.isArray(input) ? input : [input];
2986
+ keys.forEach((key) => clear([loader, key]));
2987
+ };
2988
+ useLoader.loader = getLoader;
2359
2989
 
2360
2990
  function useFrame(callback, priorityOrOptions) {
2361
2991
  const store = React.useContext(context);
@@ -2536,6 +3166,9 @@ function useTexture(input, optionsOrOnLoad) {
2536
3166
  const textureCache = useThree((state) => state.textures);
2537
3167
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2538
3168
  const { onLoad, cache = false } = options;
3169
+ const onLoadRef = useRef(onLoad);
3170
+ onLoadRef.current = onLoad;
3171
+ const onLoadCalledForRef = useRef(null);
2539
3172
  const urls = useMemo(() => getUrls(input), [input]);
2540
3173
  const cachedResult = useMemo(() => {
2541
3174
  if (!cache) return null;
@@ -2546,9 +3179,13 @@ function useTexture(input, optionsOrOnLoad) {
2546
3179
  TextureLoader,
2547
3180
  IsObject(input) ? Object.values(input) : input
2548
3181
  );
3182
+ const inputKey = urls.join("\0");
2549
3183
  useLayoutEffect(() => {
2550
- if (!cachedResult) onLoad?.(loadedTextures);
2551
- }, [onLoad, cachedResult, loadedTextures]);
3184
+ if (cachedResult) return;
3185
+ if (onLoadCalledForRef.current === inputKey) return;
3186
+ onLoadCalledForRef.current = inputKey;
3187
+ onLoadRef.current?.(loadedTextures);
3188
+ }, [cachedResult, loadedTextures, inputKey]);
2552
3189
  useEffect(() => {
2553
3190
  if (cachedResult) return;
2554
3191
  if ("initTexture" in renderer) {
@@ -2715,16 +3352,33 @@ function useTextures() {
2715
3352
  }, [store]);
2716
3353
  }
2717
3354
 
2718
- function useRenderTarget(width, height, options) {
3355
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2719
3356
  const isLegacy = useThree((s) => s.isLegacy);
2720
3357
  const size = useThree((s) => s.size);
3358
+ let width;
3359
+ let height;
3360
+ let opts;
3361
+ if (typeof widthOrOptions === "object") {
3362
+ opts = widthOrOptions;
3363
+ } else if (typeof widthOrOptions === "number") {
3364
+ width = widthOrOptions;
3365
+ if (typeof heightOrOptions === "object") {
3366
+ height = widthOrOptions;
3367
+ opts = heightOrOptions;
3368
+ } else if (typeof heightOrOptions === "number") {
3369
+ height = heightOrOptions;
3370
+ opts = options;
3371
+ } else {
3372
+ height = widthOrOptions;
3373
+ }
3374
+ }
2721
3375
  return useMemo(() => {
2722
3376
  const w = width ?? size.width;
2723
3377
  const h = height ?? size.height;
2724
3378
  {
2725
- return isLegacy ? new WebGLRenderTarget(w, h, options) : new RenderTarget(w, h, options);
3379
+ return isLegacy ? new WebGLRenderTarget(w, h, opts) : new RenderTarget(w, h, opts);
2726
3380
  }
2727
- }, [width, height, size.width, size.height, options, isLegacy]);
3381
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2728
3382
  }
2729
3383
 
2730
3384
  function useStore() {
@@ -2774,7 +3428,7 @@ function addTail(callback) {
2774
3428
  function invalidate(state, frames = 1, stackFrames = false) {
2775
3429
  getScheduler().invalidate(frames, stackFrames);
2776
3430
  }
2777
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3431
+ function advance(timestamp) {
2778
3432
  getScheduler().step(timestamp);
2779
3433
  }
2780
3434
 
@@ -14228,6 +14882,7 @@ function swapInstances() {
14228
14882
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14229
14883
  instance.object.__r3f = instance;
14230
14884
  setFiberRef(fiber, instance.object);
14885
+ delete instance.appliedOnce;
14231
14886
  applyProps(instance.object, instance.props);
14232
14887
  if (instance.props.attach) {
14233
14888
  attach(parent, instance);
@@ -14301,8 +14956,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14301
14956
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14302
14957
  if (isTailSibling) swapInstances();
14303
14958
  },
14304
- finalizeInitialChildren: () => false,
14305
- commitMount() {
14959
+ finalizeInitialChildren: (instance) => {
14960
+ for (const prop in instance.props) {
14961
+ if (isFromRef(instance.props[prop])) return true;
14962
+ }
14963
+ return false;
14964
+ },
14965
+ commitMount(instance) {
14966
+ const resolved = {};
14967
+ for (const prop in instance.props) {
14968
+ const value = instance.props[prop];
14969
+ if (isFromRef(value)) {
14970
+ const ref = value[FROM_REF];
14971
+ if (ref.current != null) resolved[prop] = ref.current;
14972
+ }
14973
+ }
14974
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14306
14975
  },
14307
14976
  getPublicInstance: (instance) => instance?.object,
14308
14977
  prepareForCommit: () => null,
@@ -14523,6 +15192,9 @@ function createRoot(canvas) {
14523
15192
  let resolve;
14524
15193
  pending = new Promise((_resolve) => resolve = _resolve);
14525
15194
  const {
15195
+ id: canvasId,
15196
+ primaryCanvas,
15197
+ scheduler: schedulerConfig,
14526
15198
  gl: glConfig,
14527
15199
  renderer: rendererConfig,
14528
15200
  size: propsSize,
@@ -14530,10 +15202,6 @@ function createRoot(canvas) {
14530
15202
  events,
14531
15203
  onCreated: onCreatedCallback,
14532
15204
  shadows = false,
14533
- linear = false,
14534
- flat = false,
14535
- textureColorSpace = SRGBColorSpace,
14536
- legacy = false,
14537
15205
  orthographic = false,
14538
15206
  frameloop = "always",
14539
15207
  dpr = [1, 2],
@@ -14545,8 +15213,10 @@ function createRoot(canvas) {
14545
15213
  onDropMissed,
14546
15214
  autoUpdateFrustum = true,
14547
15215
  occlusion = false,
14548
- _sizeProps
15216
+ _sizeProps,
15217
+ forceEven
14549
15218
  } = props;
15219
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
14550
15220
  const state = store.getState();
14551
15221
  const defaultGLProps = {
14552
15222
  canvas,
@@ -14555,7 +15225,8 @@ function createRoot(canvas) {
14555
15225
  alpha: true
14556
15226
  };
14557
15227
  const defaultGPUProps = {
14558
- canvas
15228
+ canvas,
15229
+ antialias: true
14559
15230
  };
14560
15231
  const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU || !rendererConfig);
14561
15232
  if (glConfig && rendererConfig) {
@@ -14569,19 +15240,61 @@ function createRoot(canvas) {
14569
15240
  });
14570
15241
  }
14571
15242
  let renderer = state.internal.actualRenderer;
15243
+ if (primaryCanvas && wantsGL) {
15244
+ throw new Error(
15245
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15246
+ );
15247
+ }
14572
15248
  if (wantsGL && !state.internal.actualRenderer) {
14573
15249
  renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14574
15250
  state.internal.actualRenderer = renderer;
14575
- state.set({ isLegacy: true, gl: renderer, renderer });
15251
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
15252
+ } else if (!wantsGL && primaryCanvas && !state.internal.actualRenderer) {
15253
+ const primary = await waitForPrimary(primaryCanvas);
15254
+ renderer = primary.renderer;
15255
+ state.internal.actualRenderer = renderer;
15256
+ const canvasTarget = new CanvasTarget(canvas);
15257
+ primary.store.setState((prev) => ({
15258
+ internal: { ...prev.internal, isMultiCanvas: true }
15259
+ }));
15260
+ state.set((prev) => ({
15261
+ webGPUSupported: primary.store.getState().webGPUSupported,
15262
+ renderer,
15263
+ primaryStore: primary.store,
15264
+ internal: {
15265
+ ...prev.internal,
15266
+ canvasTarget,
15267
+ isMultiCanvas: true,
15268
+ isSecondary: true,
15269
+ targetId: primaryCanvas
15270
+ }
15271
+ }));
14576
15272
  } else if (!wantsGL && !state.internal.actualRenderer) {
14577
15273
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
14578
15274
  if (!renderer.hasInitialized?.()) {
15275
+ const size2 = computeInitialSize(canvas, propsSize);
15276
+ if (size2.width > 0 && size2.height > 0) {
15277
+ const pixelRatio = calculateDpr(dpr);
15278
+ canvas.width = size2.width * pixelRatio;
15279
+ canvas.height = size2.height * pixelRatio;
15280
+ }
14579
15281
  await renderer.init();
14580
15282
  }
14581
15283
  const backend = renderer.backend;
14582
15284
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14583
15285
  state.internal.actualRenderer = renderer;
14584
- state.set({ webGPUSupported: isWebGPUBackend, renderer });
15286
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
15287
+ if (canvasId && !state.internal.isSecondary) {
15288
+ const canvasTarget = new CanvasTarget(canvas);
15289
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
15290
+ state.set((prev) => ({
15291
+ internal: {
15292
+ ...prev.internal,
15293
+ canvasTarget,
15294
+ unregisterPrimary
15295
+ }
15296
+ }));
15297
+ }
14585
15298
  }
14586
15299
  let raycaster = state.raycaster;
14587
15300
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14590,6 +15303,7 @@ function createRoot(canvas) {
14590
15303
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14591
15304
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14592
15305
  }
15306
+ let tempCamera = state.camera;
14593
15307
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14594
15308
  lastCamera = cameraOptions;
14595
15309
  const isCamera = cameraOptions?.isCamera;
@@ -14609,6 +15323,7 @@ function createRoot(canvas) {
14609
15323
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14610
15324
  }
14611
15325
  state.set({ camera });
15326
+ tempCamera = camera;
14612
15327
  raycaster.camera = camera;
14613
15328
  }
14614
15329
  if (!state.scene) {
@@ -14626,7 +15341,7 @@ function createRoot(canvas) {
14626
15341
  rootScene: scene,
14627
15342
  internal: { ...prev.internal, container: scene }
14628
15343
  }));
14629
- const camera = state.camera;
15344
+ const camera = tempCamera;
14630
15345
  if (camera && !camera.parent) scene.add(camera);
14631
15346
  }
14632
15347
  if (events && !state.events.handlers) {
@@ -14643,6 +15358,9 @@ function createRoot(canvas) {
14643
15358
  if (_sizeProps !== void 0) {
14644
15359
  state.set({ _sizeProps });
14645
15360
  }
15361
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15362
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15363
+ }
14646
15364
  const size = computeInitialSize(canvas, propsSize);
14647
15365
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14648
15366
  const wasImperative = state._sizeImperative;
@@ -14669,10 +15387,10 @@ function createRoot(canvas) {
14669
15387
  lastConfiguredProps.performance = performance;
14670
15388
  }
14671
15389
  if (!state.xr) {
14672
- const handleXRFrame = (timestamp, frame) => {
15390
+ const handleXRFrame = (timestamp, _frame) => {
14673
15391
  const state2 = store.getState();
14674
15392
  if (state2.frameloop === "never") return;
14675
- advance(timestamp, true);
15393
+ advance(timestamp);
14676
15394
  };
14677
15395
  const actualRenderer = state.internal.actualRenderer;
14678
15396
  const handleSessionChange = () => {
@@ -14684,16 +15402,16 @@ function createRoot(canvas) {
14684
15402
  };
14685
15403
  const xr = {
14686
15404
  connect() {
14687
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14688
- const actualRenderer2 = renderer2 || gl;
14689
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14690
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15405
+ const { gl, renderer: renderer2 } = store.getState();
15406
+ const xrManager = (renderer2 || gl).xr;
15407
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15408
+ xrManager.addEventListener("sessionend", handleSessionChange);
14691
15409
  },
14692
15410
  disconnect() {
14693
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14694
- const actualRenderer2 = renderer2 || gl;
14695
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14696
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15411
+ const { gl, renderer: renderer2 } = store.getState();
15412
+ const xrManager = (renderer2 || gl).xr;
15413
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15414
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14697
15415
  }
14698
15416
  };
14699
15417
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14705,15 +15423,22 @@ function createRoot(canvas) {
14705
15423
  const oldType = renderer.shadowMap.type;
14706
15424
  renderer.shadowMap.enabled = !!shadows;
14707
15425
  if (is.boo(shadows)) {
14708
- renderer.shadowMap.type = PCFSoftShadowMap;
15426
+ renderer.shadowMap.type = PCFShadowMap;
14709
15427
  } else if (is.str(shadows)) {
15428
+ if (shadows === "soft") {
15429
+ notifyDepreciated({
15430
+ heading: 'shadows="soft" is deprecated',
15431
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
15432
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
15433
+ });
15434
+ }
14710
15435
  const types = {
14711
15436
  basic: BasicShadowMap,
14712
15437
  percentage: PCFShadowMap,
14713
- soft: PCFSoftShadowMap,
15438
+ soft: PCFShadowMap,
14714
15439
  variance: VSMShadowMap
14715
15440
  };
14716
- renderer.shadowMap.type = types[shadows] ?? PCFSoftShadowMap;
15441
+ renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
14717
15442
  } else if (is.obj(shadows)) {
14718
15443
  Object.assign(renderer.shadowMap, shadows);
14719
15444
  }
@@ -14721,56 +15446,69 @@ function createRoot(canvas) {
14721
15446
  renderer.shadowMap.needsUpdate = true;
14722
15447
  }
14723
15448
  }
14724
- {
14725
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14726
- const linearChanged = linear !== lastConfiguredProps.linear;
14727
- const flatChanged = flat !== lastConfiguredProps.flat;
14728
- if (legacyChanged) {
14729
- if (legacy) {
14730
- notifyDepreciated({
14731
- heading: "Legacy Color Management",
14732
- body: "Legacy color management is deprecated and will be removed in a future version.",
14733
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
14734
- });
14735
- }
14736
- }
14737
- if (legacyChanged) {
14738
- ColorManagement.enabled = !legacy;
14739
- lastConfiguredProps.legacy = legacy;
14740
- }
14741
- if (!configured || linearChanged) {
14742
- renderer.outputColorSpace = linear ? LinearSRGBColorSpace : SRGBColorSpace;
14743
- lastConfiguredProps.linear = linear;
14744
- }
14745
- if (!configured || flatChanged) {
14746
- renderer.toneMapping = flat ? NoToneMapping : ACESFilmicToneMapping;
14747
- lastConfiguredProps.flat = flat;
14748
- }
14749
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14750
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14751
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15449
+ if (!configured) {
15450
+ renderer.outputColorSpace = SRGBColorSpace;
15451
+ renderer.toneMapping = ACESFilmicToneMapping;
14752
15452
  }
14753
15453
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14754
15454
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14755
15455
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14756
15456
  }
15457
+ const r3fProps = ["textureColorSpace"];
15458
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
15459
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14757
15460
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14758
- applyProps(renderer, glConfig);
15461
+ const glProps = {};
15462
+ for (const key in glConfig) {
15463
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
15464
+ }
15465
+ applyProps(renderer, glProps);
14759
15466
  }
14760
15467
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14761
15468
  const currentRenderer = state.renderer;
14762
15469
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14763
- applyProps(currentRenderer, rendererConfig);
15470
+ const rendererProps = {};
15471
+ for (const key in rendererConfig) {
15472
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
15473
+ }
15474
+ applyProps(currentRenderer, rendererProps);
14764
15475
  }
14765
15476
  }
14766
15477
  const scheduler = getScheduler();
14767
15478
  const rootId = state.internal.rootId;
14768
15479
  if (!rootId) {
14769
- const newRootId = scheduler.generateRootId();
15480
+ const newRootId = canvasId || scheduler.generateRootId();
14770
15481
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14771
15482
  getState: () => store.getState(),
14772
15483
  onError: (err) => store.getState().setError(err)
14773
15484
  });
15485
+ const unregisterCanvasTarget = scheduler.register(
15486
+ () => {
15487
+ const state2 = store.getState();
15488
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15489
+ const renderer2 = state2.internal.actualRenderer;
15490
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15491
+ }
15492
+ },
15493
+ {
15494
+ id: `${newRootId}_canvasTarget`,
15495
+ rootId: newRootId,
15496
+ phase: "start",
15497
+ system: true
15498
+ }
15499
+ );
15500
+ const unregisterEventsFlush = scheduler.register(
15501
+ () => {
15502
+ const state2 = store.getState();
15503
+ state2.events.flush?.();
15504
+ },
15505
+ {
15506
+ id: `${newRootId}_events`,
15507
+ rootId: newRootId,
15508
+ phase: "input",
15509
+ system: true
15510
+ }
15511
+ );
14774
15512
  const unregisterFrustum = scheduler.register(
14775
15513
  () => {
14776
15514
  const state2 = store.getState();
@@ -14805,18 +15543,22 @@ function createRoot(canvas) {
14805
15543
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14806
15544
  if (userHandlesRender || state2.internal.priority) return;
14807
15545
  try {
14808
- if (state2.postProcessing?.render) state2.postProcessing.render();
15546
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14809
15547
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14810
15548
  } catch (error) {
14811
15549
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14812
15550
  }
14813
15551
  },
14814
15552
  {
14815
- id: `${newRootId}_render`,
15553
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15554
+ id: canvasId || `${newRootId}_render`,
14816
15555
  rootId: newRootId,
14817
15556
  phase: "render",
14818
- system: true
15557
+ system: true,
14819
15558
  // Internal flag: this is a system job, not user-controlled
15559
+ // Apply scheduler config for render ordering and rate limiting
15560
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15561
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14820
15562
  }
14821
15563
  );
14822
15564
  state.set((state2) => ({
@@ -14825,6 +15567,8 @@ function createRoot(canvas) {
14825
15567
  rootId: newRootId,
14826
15568
  unregisterRoot: () => {
14827
15569
  unregisterRoot();
15570
+ unregisterCanvasTarget();
15571
+ unregisterEventsFlush();
14828
15572
  unregisterFrustum();
14829
15573
  unregisterVisibility();
14830
15574
  unregisterRender();
@@ -14883,15 +15627,24 @@ function unmountComponentAtNode(canvas, callback) {
14883
15627
  const renderer = state.internal.actualRenderer;
14884
15628
  const unregisterRoot = state.internal.unregisterRoot;
14885
15629
  if (unregisterRoot) unregisterRoot();
15630
+ const unregisterPrimary = state.internal.unregisterPrimary;
15631
+ if (unregisterPrimary) unregisterPrimary();
15632
+ const canvasTarget = state.internal.canvasTarget;
15633
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14886
15634
  state.events.disconnect?.();
14887
15635
  cleanupHelperGroup(root.store);
14888
- renderer?.renderLists?.dispose?.();
14889
- renderer?.forceContextLoss?.();
14890
- if (renderer?.xr) state.xr.disconnect();
15636
+ if (state.isLegacy && renderer) {
15637
+ ;
15638
+ renderer.renderLists?.dispose?.();
15639
+ renderer.forceContextLoss?.();
15640
+ }
15641
+ if (!state.internal.isSecondary) {
15642
+ if (renderer?.xr) state.xr.disconnect();
15643
+ }
14891
15644
  dispose(state.scene);
14892
15645
  _roots.delete(canvas);
14893
15646
  if (callback) callback(canvas);
14894
- } catch (e) {
15647
+ } catch {
14895
15648
  }
14896
15649
  }, 500);
14897
15650
  }
@@ -14899,36 +15652,34 @@ function unmountComponentAtNode(canvas, callback) {
14899
15652
  }
14900
15653
  }
14901
15654
  function createPortal(children, container, state) {
14902
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
15655
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14903
15656
  }
14904
- function PortalWrapper({ children, container, state }) {
15657
+ function Portal({ children, container, state }) {
14905
15658
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14906
- const [resolvedContainer, setResolvedContainer] = useState(() => {
15659
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14907
15660
  if (isRef(container)) return container.current ?? null;
14908
15661
  return container;
14909
15662
  });
15663
+ const setResolvedContainer = useCallback(
15664
+ (newContainer) => {
15665
+ if (!newContainer || newContainer === resolvedContainer) return;
15666
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15667
+ },
15668
+ [resolvedContainer, _setResolvedContainer, isRef]
15669
+ );
14910
15670
  useMemo(() => {
14911
- if (isRef(container)) {
14912
- const current = container.current;
14913
- if (!current) {
14914
- queueMicrotask(() => {
14915
- const updated = container.current;
14916
- if (updated && updated !== resolvedContainer) {
14917
- setResolvedContainer(updated);
14918
- }
14919
- });
14920
- } else if (current !== resolvedContainer) {
14921
- setResolvedContainer(current);
14922
- }
14923
- } else if (container !== resolvedContainer) {
14924
- setResolvedContainer(container);
15671
+ if (isRef(container) && !container.current) {
15672
+ return queueMicrotask(() => {
15673
+ setResolvedContainer(container.current);
15674
+ });
14925
15675
  }
14926
- }, [container, resolvedContainer, isRef]);
15676
+ setResolvedContainer(container);
15677
+ }, [container, isRef, setResolvedContainer]);
14927
15678
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14928
15679
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14929
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15680
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14930
15681
  }
14931
- function Portal({ state = {}, children, container }) {
15682
+ function PortalInner({ state = {}, children, container }) {
14932
15683
  const { events, size, injectScene = true, ...rest } = state;
14933
15684
  const previousRoot = useStore();
14934
15685
  const [raycaster] = useState(() => new Raycaster());
@@ -14949,11 +15700,12 @@ function Portal({ state = {}, children, container }) {
14949
15700
  };
14950
15701
  }, [portalScene, container, injectScene]);
14951
15702
  const inject = useMutableCallback((rootState, injectState) => {
15703
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14952
15704
  let viewport = void 0;
14953
- if (injectState.camera && size) {
15705
+ if (injectState.camera && (size || injectState.size)) {
14954
15706
  const camera = injectState.camera;
14955
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14956
- if (camera !== rootState.camera) updateCamera(camera, size);
15707
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
15708
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14957
15709
  }
14958
15710
  return {
14959
15711
  // The intersect consists of the previous root state
@@ -14970,7 +15722,7 @@ function Portal({ state = {}, children, container }) {
14970
15722
  previousRoot,
14971
15723
  // Events, size and viewport can be overridden by the inject layer
14972
15724
  events: { ...rootState.events, ...injectState.events, ...events },
14973
- size: { ...rootState.size, ...size },
15725
+ size: resolvedSize,
14974
15726
  viewport: { ...rootState.viewport, ...viewport },
14975
15727
  // Layers are allowed to override events
14976
15728
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14982,9 +15734,13 @@ function Portal({ state = {}, children, container }) {
14982
15734
  const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
14983
15735
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
14984
15736
  onMutate(previousRoot.getState());
14985
- previousRoot.subscribe(onMutate);
14986
15737
  return store;
14987
15738
  }, [previousRoot, container]);
15739
+ useIsomorphicLayoutEffect(() => {
15740
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15741
+ const unsubscribe = previousRoot.subscribe(onMutate);
15742
+ return unsubscribe;
15743
+ }, [previousRoot, usePortalStore]);
14988
15744
  return (
14989
15745
  // @ts-ignore, reconciler types are not maintained
14990
15746
  /* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
@@ -15004,15 +15760,13 @@ function CanvasImpl({
15004
15760
  fallback,
15005
15761
  resize,
15006
15762
  style,
15763
+ id,
15007
15764
  gl,
15008
- renderer,
15765
+ renderer: rendererProp,
15009
15766
  events = createPointerEvents,
15010
15767
  eventSource,
15011
15768
  eventPrefix,
15012
15769
  shadows,
15013
- linear,
15014
- flat,
15015
- legacy,
15016
15770
  orthographic,
15017
15771
  frameloop,
15018
15772
  dpr,
@@ -15027,10 +15781,53 @@ function CanvasImpl({
15027
15781
  hmr,
15028
15782
  width,
15029
15783
  height,
15784
+ background,
15785
+ forceEven,
15030
15786
  ...props
15031
15787
  }) {
15788
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15789
+ let primaryCanvas;
15790
+ let scheduler;
15791
+ let renderer;
15792
+ if (isRendererConfig) {
15793
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15794
+ primaryCanvas = pc;
15795
+ scheduler = sc;
15796
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
15797
+ } else {
15798
+ renderer = rendererProp;
15799
+ }
15032
15800
  React.useMemo(() => extend(THREE), []);
15033
15801
  const Bridge = useBridge();
15802
+ const backgroundProps = React.useMemo(() => {
15803
+ if (!background) return null;
15804
+ if (typeof background === "object" && !background.isColor) {
15805
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15806
+ return {
15807
+ ...rest,
15808
+ preset,
15809
+ files: envMap || files,
15810
+ backgroundFiles: backgroundMap,
15811
+ background: true
15812
+ };
15813
+ }
15814
+ if (typeof background === "number") {
15815
+ return { color: background, background: true };
15816
+ }
15817
+ if (typeof background === "string") {
15818
+ if (background in presetsObj) {
15819
+ return { preset: background, background: true };
15820
+ }
15821
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15822
+ return { files: background, background: true };
15823
+ }
15824
+ return { color: background, background: true };
15825
+ }
15826
+ if (background.isColor) {
15827
+ return { color: background, background: true };
15828
+ }
15829
+ return null;
15830
+ }, [background]);
15034
15831
  const hasInitialSizeRef = React.useRef(false);
15035
15832
  const measureConfig = React.useMemo(() => {
15036
15833
  if (!hasInitialSizeRef.current) {
@@ -15047,15 +15844,20 @@ function CanvasImpl({
15047
15844
  };
15048
15845
  }, [resize, hasInitialSizeRef.current]);
15049
15846
  const [containerRef, containerRect] = useMeasure(measureConfig);
15050
- const effectiveSize = React.useMemo(
15051
- () => ({
15052
- width: width ?? containerRect.width,
15053
- height: height ?? containerRect.height,
15847
+ const effectiveSize = React.useMemo(() => {
15848
+ let w = width ?? containerRect.width;
15849
+ let h = height ?? containerRect.height;
15850
+ if (forceEven) {
15851
+ w = Math.ceil(w / 2) * 2;
15852
+ h = Math.ceil(h / 2) * 2;
15853
+ }
15854
+ return {
15855
+ width: w,
15856
+ height: h,
15054
15857
  top: containerRect.top,
15055
15858
  left: containerRect.left
15056
- }),
15057
- [width, height, containerRect]
15058
- );
15859
+ };
15860
+ }, [width, height, containerRect, forceEven]);
15059
15861
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15060
15862
  hasInitialSizeRef.current = true;
15061
15863
  }
@@ -15095,14 +15897,14 @@ function CanvasImpl({
15095
15897
  async function run() {
15096
15898
  if (!effectActiveRef.current || !root.current) return;
15097
15899
  await root.current.configure({
15900
+ id,
15901
+ primaryCanvas,
15902
+ scheduler,
15098
15903
  gl,
15099
15904
  renderer,
15100
15905
  scene,
15101
15906
  events,
15102
15907
  shadows,
15103
- linear,
15104
- flat,
15105
- legacy,
15106
15908
  orthographic,
15107
15909
  frameloop,
15108
15910
  dpr,
@@ -15112,6 +15914,7 @@ function CanvasImpl({
15112
15914
  size: effectiveSize,
15113
15915
  // Store size props for reset functionality
15114
15916
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15917
+ forceEven,
15115
15918
  // Pass mutable reference to onPointerMissed so it's free to update
15116
15919
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15117
15920
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15135,7 +15938,10 @@ function CanvasImpl({
15135
15938
  });
15136
15939
  if (!effectActiveRef.current || !root.current) return;
15137
15940
  root.current.render(
15138
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15941
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
15942
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
15943
+ children ?? null
15944
+ ] }) }) })
15139
15945
  );
15140
15946
  }
15141
15947
  run();
@@ -15162,20 +15968,22 @@ function CanvasImpl({
15162
15968
  const canvas = canvasRef.current;
15163
15969
  if (!canvas) return;
15164
15970
  const handleHMR = () => {
15165
- const rootEntry = _roots.get(canvas);
15166
- if (rootEntry?.store) {
15167
- rootEntry.store.setState((state) => ({
15168
- nodes: {},
15169
- uniforms: {},
15170
- _hmrVersion: state._hmrVersion + 1
15171
- }));
15172
- }
15971
+ queueMicrotask(() => {
15972
+ const rootEntry = _roots.get(canvas);
15973
+ if (rootEntry?.store) {
15974
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15975
+ rootEntry.store.setState((state) => ({
15976
+ nodes: {},
15977
+ uniforms: {},
15978
+ _hmrVersion: state._hmrVersion + 1
15979
+ }));
15980
+ }
15981
+ });
15173
15982
  };
15174
15983
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15175
15984
  const hot = import.meta.hot;
15176
15985
  hot.on("vite:afterUpdate", handleHMR);
15177
- return () => hot.dispose?.(() => {
15178
- });
15986
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15179
15987
  }
15180
15988
  if (typeof module !== "undefined" && module.hot) {
15181
15989
  const hot = module.hot;
@@ -15198,7 +16006,16 @@ function CanvasImpl({
15198
16006
  ...style
15199
16007
  },
15200
16008
  ...props,
15201
- 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 }) })
16009
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
16010
+ "canvas",
16011
+ {
16012
+ ref: canvasRef,
16013
+ id,
16014
+ className: "r3f-canvas",
16015
+ style: { display: "block", width: "100%", height: "100%" },
16016
+ children: fallback
16017
+ }
16018
+ ) })
15202
16019
  }
15203
16020
  );
15204
16021
  }
@@ -15208,4 +16025,4 @@ function Canvas(props) {
15208
16025
 
15209
16026
  extend(THREE);
15210
16027
 
15211
- 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 };
16028
+ 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 };