@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/legacy.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  import * as three from 'three';
2
- import { WebGLRenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, ColorManagement, LinearSRGBColorSpace, NoToneMapping, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
- import { jsx, Fragment } from 'react/jsx-runtime';
2
+ import { WebGLRenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
5
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
6
6
  import useMeasure from 'react-use-measure';
7
7
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
8
+ import { useThree as useThree$1, useLoader as useLoader$1, useFrame as useFrame$1, createPortal as createPortal$1, applyProps as applyProps$1, extend as extend$1 } from '@react-three/fiber';
9
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
10
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
11
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
12
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
13
+ import { GainMapLoader } from '@monogrid/gainmap-js';
8
14
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
9
15
  import { createWithEqualityFn } from 'zustand/traditional';
10
16
  import { suspend, preload, clear } from 'suspend-react';
@@ -45,6 +51,389 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
45
51
  WebGPURenderer: WebGPURenderer
46
52
  }, [three]);
47
53
 
54
+ const primaryRegistry = /* @__PURE__ */ new Map();
55
+ const pendingSubscribers = /* @__PURE__ */ new Map();
56
+ function registerPrimary(id, renderer, store) {
57
+ if (primaryRegistry.has(id)) {
58
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
59
+ }
60
+ const entry = { renderer, store };
61
+ primaryRegistry.set(id, entry);
62
+ const subscribers = pendingSubscribers.get(id);
63
+ if (subscribers) {
64
+ subscribers.forEach((callback) => callback(entry));
65
+ pendingSubscribers.delete(id);
66
+ }
67
+ return () => {
68
+ const currentEntry = primaryRegistry.get(id);
69
+ if (currentEntry?.renderer === renderer) {
70
+ primaryRegistry.delete(id);
71
+ }
72
+ };
73
+ }
74
+ function getPrimary(id) {
75
+ return primaryRegistry.get(id);
76
+ }
77
+ function waitForPrimary(id, timeout = 5e3) {
78
+ const existing = primaryRegistry.get(id);
79
+ if (existing) {
80
+ return Promise.resolve(existing);
81
+ }
82
+ return new Promise((resolve, reject) => {
83
+ const timeoutId = setTimeout(() => {
84
+ const subscribers = pendingSubscribers.get(id);
85
+ if (subscribers) {
86
+ const index = subscribers.indexOf(callback);
87
+ if (index !== -1) subscribers.splice(index, 1);
88
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
89
+ }
90
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
91
+ }, timeout);
92
+ const callback = (entry) => {
93
+ clearTimeout(timeoutId);
94
+ resolve(entry);
95
+ };
96
+ if (!pendingSubscribers.has(id)) {
97
+ pendingSubscribers.set(id, []);
98
+ }
99
+ pendingSubscribers.get(id).push(callback);
100
+ });
101
+ }
102
+ function hasPrimary(id) {
103
+ return primaryRegistry.has(id);
104
+ }
105
+ function unregisterPrimary(id) {
106
+ primaryRegistry.delete(id);
107
+ }
108
+ function getPrimaryIds() {
109
+ return Array.from(primaryRegistry.keys());
110
+ }
111
+
112
+ const presetsObj = {
113
+ apartment: "lebombo_1k.hdr",
114
+ city: "potsdamer_platz_1k.hdr",
115
+ dawn: "kiara_1_dawn_1k.hdr",
116
+ forest: "forest_slope_1k.hdr",
117
+ lobby: "st_fagans_interior_1k.hdr",
118
+ night: "dikhololo_night_1k.hdr",
119
+ park: "rooitou_park_1k.hdr",
120
+ studio: "studio_small_03_1k.hdr",
121
+ sunset: "venice_sunset_1k.hdr",
122
+ warehouse: "empty_warehouse_01_1k.hdr"
123
+ };
124
+
125
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
126
+ const isArray = (arr) => Array.isArray(arr);
127
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
128
+ function useEnvironment({
129
+ files = defaultFiles,
130
+ path = "",
131
+ preset = void 0,
132
+ colorSpace = void 0,
133
+ extensions
134
+ } = {}) {
135
+ if (preset) {
136
+ validatePreset(preset);
137
+ files = presetsObj[preset];
138
+ path = CUBEMAP_ROOT;
139
+ }
140
+ const multiFile = isArray(files);
141
+ const { extension, isCubemap } = getExtension(files);
142
+ const loader = getLoader$1(extension);
143
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
144
+ const renderer = useThree$1((state) => state.renderer);
145
+ useLayoutEffect(() => {
146
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
147
+ function clearGainmapTexture() {
148
+ useLoader$1.clear(loader, multiFile ? [files] : files);
149
+ }
150
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
151
+ }, [extension, files, loader, multiFile, renderer.domElement]);
152
+ const loaderResult = useLoader$1(
153
+ loader,
154
+ multiFile ? [files] : files,
155
+ (loader2) => {
156
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
157
+ loader2.setRenderer?.(renderer);
158
+ }
159
+ loader2.setPath?.(path);
160
+ if (extensions) extensions(loader2);
161
+ }
162
+ );
163
+ let texture = multiFile ? (
164
+ // @ts-ignore
165
+ loaderResult[0]
166
+ ) : loaderResult;
167
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
168
+ texture = texture.renderTarget?.texture;
169
+ }
170
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
171
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
172
+ return texture;
173
+ }
174
+ const preloadDefaultOptions = {
175
+ files: defaultFiles,
176
+ path: "",
177
+ preset: void 0,
178
+ extensions: void 0
179
+ };
180
+ useEnvironment.preload = (preloadOptions) => {
181
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
182
+ let { files, path = "" } = options;
183
+ const { preset, extensions } = options;
184
+ if (preset) {
185
+ validatePreset(preset);
186
+ files = presetsObj[preset];
187
+ path = CUBEMAP_ROOT;
188
+ }
189
+ const { extension } = getExtension(files);
190
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
191
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
192
+ }
193
+ const loader = getLoader$1(extension);
194
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
195
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
196
+ loader2.setPath?.(path);
197
+ if (extensions) extensions(loader2);
198
+ });
199
+ };
200
+ const clearDefaultOptins = {
201
+ files: defaultFiles,
202
+ preset: void 0
203
+ };
204
+ useEnvironment.clear = (clearOptions) => {
205
+ const options = { ...clearDefaultOptins, ...clearOptions };
206
+ let { files } = options;
207
+ const { preset } = options;
208
+ if (preset) {
209
+ validatePreset(preset);
210
+ files = presetsObj[preset];
211
+ }
212
+ const { extension } = getExtension(files);
213
+ const loader = getLoader$1(extension);
214
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
215
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
216
+ };
217
+ function validatePreset(preset) {
218
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
219
+ }
220
+ function getExtension(files) {
221
+ const isCubemap = isArray(files) && files.length === 6;
222
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
223
+ const firstEntry = isArray(files) ? files[0] : files;
224
+ const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
225
+ return { extension, isCubemap, isGainmap };
226
+ }
227
+ function getLoader$1(extension) {
228
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
229
+ return loader;
230
+ }
231
+
232
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
233
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
234
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
235
+ sceneProps = {
236
+ backgroundBlurriness: 0,
237
+ backgroundIntensity: 1,
238
+ backgroundRotation: [0, 0, 0],
239
+ environmentIntensity: 1,
240
+ environmentRotation: [0, 0, 0],
241
+ ...sceneProps
242
+ };
243
+ const target = resolveScene(scene || defaultScene);
244
+ const oldbg = target.background;
245
+ const oldenv = target.environment;
246
+ const oldSceneProps = {
247
+ // @ts-ignore
248
+ backgroundBlurriness: target.backgroundBlurriness,
249
+ // @ts-ignore
250
+ backgroundIntensity: target.backgroundIntensity,
251
+ // @ts-ignore
252
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
253
+ // @ts-ignore
254
+ environmentIntensity: target.environmentIntensity,
255
+ // @ts-ignore
256
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
257
+ };
258
+ if (background !== "only") target.environment = texture;
259
+ if (background) target.background = texture;
260
+ applyProps$1(target, sceneProps);
261
+ return () => {
262
+ if (background !== "only") target.environment = oldenv;
263
+ if (background) target.background = oldbg;
264
+ applyProps$1(target, oldSceneProps);
265
+ };
266
+ }
267
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
268
+ const defaultScene = useThree$1((state) => state.scene);
269
+ React.useLayoutEffect(() => {
270
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
271
+ });
272
+ return null;
273
+ }
274
+ function EnvironmentCube({
275
+ background = false,
276
+ scene,
277
+ blur,
278
+ backgroundBlurriness,
279
+ backgroundIntensity,
280
+ backgroundRotation,
281
+ environmentIntensity,
282
+ environmentRotation,
283
+ ...rest
284
+ }) {
285
+ const texture = useEnvironment(rest);
286
+ const defaultScene = useThree$1((state) => state.scene);
287
+ React.useLayoutEffect(() => {
288
+ return setEnvProps(background, scene, defaultScene, texture, {
289
+ backgroundBlurriness: blur ?? backgroundBlurriness,
290
+ backgroundIntensity,
291
+ backgroundRotation,
292
+ environmentIntensity,
293
+ environmentRotation
294
+ });
295
+ });
296
+ React.useEffect(() => {
297
+ return () => {
298
+ texture.dispose();
299
+ };
300
+ }, [texture]);
301
+ return null;
302
+ }
303
+ function EnvironmentPortal({
304
+ children,
305
+ near = 0.1,
306
+ far = 1e3,
307
+ resolution = 256,
308
+ frames = 1,
309
+ map,
310
+ background = false,
311
+ blur,
312
+ backgroundBlurriness,
313
+ backgroundIntensity,
314
+ backgroundRotation,
315
+ environmentIntensity,
316
+ environmentRotation,
317
+ scene,
318
+ files,
319
+ path,
320
+ preset = void 0,
321
+ extensions
322
+ }) {
323
+ const gl = useThree$1((state) => state.gl);
324
+ const defaultScene = useThree$1((state) => state.scene);
325
+ const camera = React.useRef(null);
326
+ const [virtualScene] = React.useState(() => new Scene());
327
+ const fbo = React.useMemo(() => {
328
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
329
+ fbo2.texture.type = HalfFloatType;
330
+ return fbo2;
331
+ }, [resolution]);
332
+ React.useEffect(() => {
333
+ return () => {
334
+ fbo.dispose();
335
+ };
336
+ }, [fbo]);
337
+ React.useLayoutEffect(() => {
338
+ if (frames === 1) {
339
+ const autoClear = gl.autoClear;
340
+ gl.autoClear = true;
341
+ camera.current.update(gl, virtualScene);
342
+ gl.autoClear = autoClear;
343
+ }
344
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
345
+ backgroundBlurriness: blur ?? backgroundBlurriness,
346
+ backgroundIntensity,
347
+ backgroundRotation,
348
+ environmentIntensity,
349
+ environmentRotation
350
+ });
351
+ }, [
352
+ children,
353
+ virtualScene,
354
+ fbo.texture,
355
+ scene,
356
+ defaultScene,
357
+ background,
358
+ frames,
359
+ gl,
360
+ blur,
361
+ backgroundBlurriness,
362
+ backgroundIntensity,
363
+ backgroundRotation,
364
+ environmentIntensity,
365
+ environmentRotation
366
+ ]);
367
+ let count = 1;
368
+ useFrame$1(() => {
369
+ if (frames === Infinity || count < frames) {
370
+ const autoClear = gl.autoClear;
371
+ gl.autoClear = true;
372
+ camera.current.update(gl, virtualScene);
373
+ gl.autoClear = autoClear;
374
+ count++;
375
+ }
376
+ });
377
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
378
+ /* @__PURE__ */ jsxs(Fragment, { children: [
379
+ children,
380
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
381
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
382
+ ] }),
383
+ virtualScene
384
+ ) });
385
+ }
386
+ function EnvironmentGround(props) {
387
+ const textureDefault = useEnvironment(props);
388
+ const texture = props.map || textureDefault;
389
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
390
+ React.useEffect(() => {
391
+ return () => {
392
+ textureDefault.dispose();
393
+ };
394
+ }, [textureDefault]);
395
+ const height = props.ground?.height ?? 15;
396
+ const radius = props.ground?.radius ?? 60;
397
+ const scale = props.ground?.scale ?? 1e3;
398
+ const args = React.useMemo(
399
+ () => [texture, height, radius],
400
+ [texture, height, radius]
401
+ );
402
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
403
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
404
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
405
+ ] });
406
+ }
407
+ function EnvironmentColor({ color, scene }) {
408
+ const defaultScene = useThree$1((state) => state.scene);
409
+ React.useLayoutEffect(() => {
410
+ if (color === void 0) return;
411
+ const target = resolveScene(scene || defaultScene);
412
+ const oldBg = target.background;
413
+ target.background = new Color(color);
414
+ return () => {
415
+ target.background = oldBg;
416
+ };
417
+ });
418
+ return null;
419
+ }
420
+ function EnvironmentDualSource(props) {
421
+ const { backgroundFiles, ...envProps } = props;
422
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
423
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
424
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
425
+ ] });
426
+ }
427
+ function Environment(props) {
428
+ if (props.color && !props.files && !props.preset && !props.map) {
429
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
430
+ }
431
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
432
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
433
+ }
434
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
435
+ }
436
+
48
437
  var __defProp$2 = Object.defineProperty;
49
438
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
50
439
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -224,7 +613,8 @@ function prepare(target, root, type, props) {
224
613
  object,
225
614
  eventCount: 0,
226
615
  handlers: {},
227
- isHidden: false
616
+ isHidden: false,
617
+ deferredRefs: []
228
618
  };
229
619
  if (object) object.__r3f = instance;
230
620
  }
@@ -273,7 +663,7 @@ function createOcclusionObserverNode(store, uniform) {
273
663
  let occlusionSetupPromise = null;
274
664
  function enableOcclusion(store) {
275
665
  const state = store.getState();
276
- const { internal, renderer, rootScene } = state;
666
+ const { internal, renderer } = state;
277
667
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
278
668
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
279
669
  if (!hasOcclusionSupport) {
@@ -436,6 +826,22 @@ function hasVisibilityHandlers(handlers) {
436
826
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
437
827
  }
438
828
 
829
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
830
+ function fromRef(ref) {
831
+ return { [FROM_REF]: ref };
832
+ }
833
+ function isFromRef(value) {
834
+ return value !== null && typeof value === "object" && FROM_REF in value;
835
+ }
836
+
837
+ const ONCE = Symbol.for("@react-three/fiber.once");
838
+ function once(...args) {
839
+ return { [ONCE]: args.length ? args : true };
840
+ }
841
+ function isOnce(value) {
842
+ return value !== null && typeof value === "object" && ONCE in value;
843
+ }
844
+
439
845
  const RESERVED_PROPS = [
440
846
  "children",
441
847
  "key",
@@ -506,7 +912,7 @@ function getMemoizedPrototype(root) {
506
912
  ctor = new root.constructor();
507
913
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
508
914
  }
509
- } catch (e) {
915
+ } catch {
510
916
  }
511
917
  return ctor;
512
918
  }
@@ -552,6 +958,25 @@ function applyProps(object, props) {
552
958
  continue;
553
959
  }
554
960
  if (value === void 0) continue;
961
+ if (isFromRef(value)) {
962
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
963
+ continue;
964
+ }
965
+ if (isOnce(value)) {
966
+ if (instance?.appliedOnce?.has(prop)) continue;
967
+ if (instance) {
968
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
969
+ instance.appliedOnce.add(prop);
970
+ }
971
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
972
+ const args = value[ONCE];
973
+ if (typeof targetRoot[targetKey] === "function") {
974
+ targetRoot[targetKey](...args === true ? [] : args);
975
+ } else if (args !== true && args.length > 0) {
976
+ targetRoot[targetKey] = args[0];
977
+ }
978
+ continue;
979
+ }
555
980
  let { root, key, target } = resolve(object, prop);
556
981
  if (target === void 0 && (typeof root !== "object" || root === null)) {
557
982
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -574,7 +999,10 @@ function applyProps(object, props) {
574
999
  else target.set(value);
575
1000
  } else {
576
1001
  root[key] = value;
577
- if (rootState && !rootState.linear && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
1002
+ if (key.endsWith("Node") && root.isMaterial) {
1003
+ root.needsUpdate = true;
1004
+ }
1005
+ if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
578
1006
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
579
1007
  root[key].colorSpace = rootState.textureColorSpace;
580
1008
  }
@@ -607,38 +1035,60 @@ function applyProps(object, props) {
607
1035
  return object;
608
1036
  }
609
1037
 
1038
+ const DEFAULT_POINTER_ID = 0;
1039
+ const XR_POINTER_ID_START = 1e3;
1040
+ function getPointerState(internal, pointerId) {
1041
+ let state = internal.pointerMap.get(pointerId);
1042
+ if (!state) {
1043
+ state = {
1044
+ hovered: /* @__PURE__ */ new Map(),
1045
+ captured: /* @__PURE__ */ new Map(),
1046
+ initialClick: [0, 0],
1047
+ initialHits: []
1048
+ };
1049
+ internal.pointerMap.set(pointerId, state);
1050
+ }
1051
+ return state;
1052
+ }
1053
+ function getPointerId(event) {
1054
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1055
+ }
610
1056
  function makeId(event) {
611
1057
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
612
1058
  }
613
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
614
- const captureData = captures.get(obj);
1059
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1060
+ const pointerState = internal.pointerMap.get(pointerId);
1061
+ if (!pointerState) return;
1062
+ const captureData = pointerState.captured.get(obj);
615
1063
  if (captureData) {
616
- captures.delete(obj);
617
- if (captures.size === 0) {
618
- capturedMap.delete(pointerId);
619
- captureData.target.releasePointerCapture(pointerId);
620
- }
1064
+ pointerState.captured.delete(obj);
1065
+ captureData.target.releasePointerCapture(pointerId);
621
1066
  }
622
1067
  }
623
1068
  function removeInteractivity(store, object) {
624
1069
  const { internal } = store.getState();
625
1070
  internal.interaction = internal.interaction.filter((o) => o !== object);
626
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
627
- internal.hovered.forEach((value, key) => {
628
- if (value.eventObject === object || value.object === object) {
629
- internal.hovered.delete(key);
1071
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1072
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1073
+ pointerState.hovered.forEach((value, key) => {
1074
+ if (value.eventObject === object || value.object === object) {
1075
+ pointerState.hovered.delete(key);
1076
+ }
1077
+ });
1078
+ if (pointerState.captured.has(object)) {
1079
+ releaseInternalPointerCapture(internal, object, pointerId);
630
1080
  }
631
- });
632
- internal.capturedMap.forEach((captures, pointerId) => {
633
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
634
- });
1081
+ }
635
1082
  unregisterVisibility(store, object);
636
1083
  }
637
1084
  function createEvents(store) {
638
- function calculateDistance(event) {
1085
+ function calculateDistance(event, pointerId) {
639
1086
  const { internal } = store.getState();
640
- const dx = event.offsetX - internal.initialClick[0];
641
- const dy = event.offsetY - internal.initialClick[1];
1087
+ const pointerState = internal.pointerMap.get(pointerId);
1088
+ if (!pointerState) return 0;
1089
+ const [initialX, initialY] = pointerState.initialClick;
1090
+ const dx = event.offsetX - initialX;
1091
+ const dy = event.offsetY - initialY;
642
1092
  return Math.round(Math.sqrt(dx * dx + dy * dy));
643
1093
  }
644
1094
  function filterPointerEvents(objects) {
@@ -674,6 +1124,15 @@ function createEvents(store) {
674
1124
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
675
1125
  }
676
1126
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1127
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1128
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1129
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1130
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1131
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1132
+ if (aInteractivePriority !== bInteractivePriority) {
1133
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1134
+ }
1135
+ }
677
1136
  const aState = getRootState(a.object);
678
1137
  const bState = getRootState(b.object);
679
1138
  const aPriority = aState?.events?.priority ?? 1;
@@ -695,9 +1154,13 @@ function createEvents(store) {
695
1154
  eventObject = eventObject.parent;
696
1155
  }
697
1156
  }
698
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
699
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
700
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1157
+ if ("pointerId" in event) {
1158
+ const pointerId = event.pointerId;
1159
+ const pointerState = state.internal.pointerMap.get(pointerId);
1160
+ if (pointerState?.captured.size) {
1161
+ for (const captureData of pointerState.captured.values()) {
1162
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1163
+ }
701
1164
  }
702
1165
  }
703
1166
  return intersections;
@@ -710,27 +1173,25 @@ function createEvents(store) {
710
1173
  if (state) {
711
1174
  const { raycaster, pointer, camera, internal } = state;
712
1175
  const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
713
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1176
+ const hasPointerCapture = (id) => {
1177
+ const pointerState = internal.pointerMap.get(id);
1178
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1179
+ };
714
1180
  const setPointerCapture = (id) => {
715
1181
  const captureData = { intersection: hit, target: event.target };
716
- if (internal.capturedMap.has(id)) {
717
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
718
- } else {
719
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
720
- }
1182
+ const pointerState = getPointerState(internal, id);
1183
+ pointerState.captured.set(hit.eventObject, captureData);
721
1184
  event.target.setPointerCapture(id);
722
1185
  };
723
1186
  const releasePointerCapture = (id) => {
724
- const captures = internal.capturedMap.get(id);
725
- if (captures) {
726
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
727
- }
1187
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
728
1188
  };
729
1189
  const extractEventProps = {};
730
1190
  for (const prop in event) {
731
1191
  const property = event[prop];
732
1192
  if (typeof property !== "function") extractEventProps[prop] = property;
733
1193
  }
1194
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
734
1195
  const raycastEvent = {
735
1196
  ...hit,
736
1197
  ...extractEventProps,
@@ -741,18 +1202,19 @@ function createEvents(store) {
741
1202
  unprojectedPoint,
742
1203
  ray: raycaster.ray,
743
1204
  camera,
1205
+ pointerId: eventPointerId,
744
1206
  // Hijack stopPropagation, which just sets a flag
745
1207
  stopPropagation() {
746
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1208
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
747
1209
  if (
748
1210
  // ...if this pointer hasn't been captured
749
- !capturesForPointer || // ... or if the hit object is capturing the pointer
750
- capturesForPointer.has(hit.eventObject)
1211
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1212
+ pointerState.captured.has(hit.eventObject)
751
1213
  ) {
752
1214
  raycastEvent.stopped = localState.stopped = true;
753
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1215
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
754
1216
  const higher = intersections.slice(0, intersections.indexOf(hit));
755
- cancelPointer([...higher, hit]);
1217
+ cancelPointer([...higher, hit], eventPointerId);
756
1218
  }
757
1219
  }
758
1220
  },
@@ -768,15 +1230,18 @@ function createEvents(store) {
768
1230
  }
769
1231
  return intersections;
770
1232
  }
771
- function cancelPointer(intersections) {
1233
+ function cancelPointer(intersections, pointerId) {
772
1234
  const { internal } = store.getState();
773
- for (const hoveredObj of internal.hovered.values()) {
1235
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1236
+ const pointerState = internal.pointerMap.get(pid);
1237
+ if (!pointerState) return;
1238
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
774
1239
  if (!intersections.length || !intersections.find(
775
1240
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
776
1241
  )) {
777
1242
  const eventObject = hoveredObj.eventObject;
778
1243
  const instance = eventObject.__r3f;
779
- internal.hovered.delete(makeId(hoveredObj));
1244
+ pointerState.hovered.delete(hoveredId);
780
1245
  if (instance?.eventCount) {
781
1246
  const handlers = instance.handlers;
782
1247
  const data = { ...hoveredObj, intersections };
@@ -805,41 +1270,118 @@ function createEvents(store) {
805
1270
  instance?.handlers.onDropMissed?.(event);
806
1271
  }
807
1272
  }
1273
+ function cleanupPointer(pointerId) {
1274
+ const { internal } = store.getState();
1275
+ const pointerState = internal.pointerMap.get(pointerId);
1276
+ if (pointerState) {
1277
+ for (const [, hoveredObj] of pointerState.hovered) {
1278
+ const eventObject = hoveredObj.eventObject;
1279
+ const instance = eventObject.__r3f;
1280
+ if (instance?.eventCount) {
1281
+ const handlers = instance.handlers;
1282
+ const data = { ...hoveredObj, intersections: [] };
1283
+ handlers.onPointerOut?.(data);
1284
+ handlers.onPointerLeave?.(data);
1285
+ }
1286
+ }
1287
+ internal.pointerMap.delete(pointerId);
1288
+ }
1289
+ internal.pointerDirty.delete(pointerId);
1290
+ }
1291
+ function processDeferredPointer(event, pointerId) {
1292
+ const state = store.getState();
1293
+ const { internal } = state;
1294
+ if (!state.events.enabled) return;
1295
+ const filter = filterPointerEvents;
1296
+ const hits = intersect(event, filter);
1297
+ cancelPointer(hits, pointerId);
1298
+ function onIntersect(data) {
1299
+ const eventObject = data.eventObject;
1300
+ const instance = eventObject.__r3f;
1301
+ if (!instance?.eventCount) return;
1302
+ const handlers = instance.handlers;
1303
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1304
+ const id = makeId(data);
1305
+ const pointerState = getPointerState(internal, pointerId);
1306
+ const hoveredItem = pointerState.hovered.get(id);
1307
+ if (!hoveredItem) {
1308
+ pointerState.hovered.set(id, data);
1309
+ handlers.onPointerOver?.(data);
1310
+ handlers.onPointerEnter?.(data);
1311
+ } else if (hoveredItem.stopped) {
1312
+ data.stopPropagation();
1313
+ }
1314
+ }
1315
+ handlers.onPointerMove?.(data);
1316
+ }
1317
+ handleIntersects(hits, event, 0, onIntersect);
1318
+ }
808
1319
  function handlePointer(name) {
809
1320
  switch (name) {
810
1321
  case "onPointerLeave":
811
- case "onPointerCancel":
812
1322
  case "onDragLeave":
813
1323
  return () => cancelPointer([]);
1324
+ // Global cancel of these events
1325
+ case "onPointerCancel":
1326
+ return (event) => {
1327
+ const pointerId = getPointerId(event);
1328
+ cleanupPointer(pointerId);
1329
+ };
814
1330
  case "onLostPointerCapture":
815
1331
  return (event) => {
816
1332
  const { internal } = store.getState();
817
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1333
+ const pointerId = getPointerId(event);
1334
+ const pointerState = internal.pointerMap.get(pointerId);
1335
+ if (pointerState?.captured.size) {
818
1336
  requestAnimationFrame(() => {
819
- if (internal.capturedMap.has(event.pointerId)) {
820
- internal.capturedMap.delete(event.pointerId);
821
- cancelPointer([]);
1337
+ const pointerState2 = internal.pointerMap.get(pointerId);
1338
+ if (pointerState2?.captured.size) {
1339
+ pointerState2.captured.clear();
822
1340
  }
1341
+ cancelPointer([], pointerId);
823
1342
  });
824
1343
  }
825
1344
  };
826
1345
  }
827
1346
  return function handleEvent(event) {
828
1347
  const state = store.getState();
829
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1348
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1349
+ const pointerId = getPointerId(event);
830
1350
  internal.lastEvent.current = event;
831
- if (!state.events.enabled) return;
1351
+ if (!events.enabled) return;
832
1352
  const isPointerMove = name === "onPointerMove";
833
1353
  const isDragOver = name === "onDragOver";
834
1354
  const isDrop = name === "onDrop";
835
1355
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1356
+ const isPointerDown = name === "onPointerDown";
1357
+ const isPointerUp = name === "onPointerUp";
1358
+ const isWheel = name === "onWheel";
1359
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1360
+ if (isPointerMove && canDeferRaycasts) {
1361
+ events.compute?.(event, state);
1362
+ internal.pointerDirty.set(pointerId, event);
1363
+ return;
1364
+ }
1365
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1366
+ events.compute?.(event, state);
1367
+ internal.pointerDirty.set(pointerId, event);
1368
+ return;
1369
+ }
1370
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1371
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1372
+ internal.pointerDirty.delete(pointerId);
1373
+ processDeferredPointer(deferredEvent, pointerId);
1374
+ }
836
1375
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
837
1376
  const hits = intersect(event, filter);
838
- const delta = isClickEvent ? calculateDistance(event) : 0;
839
- if (name === "onPointerDown") {
840
- internal.initialClick = [event.offsetX, event.offsetY];
841
- internal.initialHits = hits.map((hit) => hit.eventObject);
842
- }
1377
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1378
+ if (isPointerDown) {
1379
+ const pointerState2 = getPointerState(internal, pointerId);
1380
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1381
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1382
+ }
1383
+ const pointerState = internal.pointerMap.get(pointerId);
1384
+ const initialHits = pointerState?.initialHits ?? [];
843
1385
  if (isClickEvent && !hits.length) {
844
1386
  if (delta <= 2) {
845
1387
  pointerMissed(event, internal.interaction);
@@ -854,7 +1396,9 @@ function createEvents(store) {
854
1396
  dropMissed(event, internal.interaction);
855
1397
  if (onDropMissed) onDropMissed(event);
856
1398
  }
857
- if (isPointerMove || isDragOver) cancelPointer(hits);
1399
+ if (isPointerMove || isDragOver) {
1400
+ cancelPointer(hits, pointerId);
1401
+ }
858
1402
  function onIntersect(data) {
859
1403
  const eventObject = data.eventObject;
860
1404
  const instance = eventObject.__r3f;
@@ -863,9 +1407,10 @@ function createEvents(store) {
863
1407
  if (isPointerMove) {
864
1408
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
865
1409
  const id = makeId(data);
866
- const hoveredItem = internal.hovered.get(id);
1410
+ const pointerState2 = getPointerState(internal, pointerId);
1411
+ const hoveredItem = pointerState2.hovered.get(id);
867
1412
  if (!hoveredItem) {
868
- internal.hovered.set(id, data);
1413
+ pointerState2.hovered.set(id, data);
869
1414
  handlers.onPointerOver?.(data);
870
1415
  handlers.onPointerEnter?.(data);
871
1416
  } else if (hoveredItem.stopped) {
@@ -875,9 +1420,10 @@ function createEvents(store) {
875
1420
  handlers.onPointerMove?.(data);
876
1421
  } else if (isDragOver) {
877
1422
  const id = makeId(data);
878
- const hoveredItem = internal.hovered.get(id);
1423
+ const pointerState2 = getPointerState(internal, pointerId);
1424
+ const hoveredItem = pointerState2.hovered.get(id);
879
1425
  if (!hoveredItem) {
880
- internal.hovered.set(id, data);
1426
+ pointerState2.hovered.set(id, data);
881
1427
  handlers.onDragOverEnter?.(data);
882
1428
  } else if (hoveredItem.stopped) {
883
1429
  data.stopPropagation();
@@ -888,18 +1434,18 @@ function createEvents(store) {
888
1434
  } else {
889
1435
  const handler = handlers[name];
890
1436
  if (handler) {
891
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1437
+ if (!isClickEvent || initialHits.includes(eventObject)) {
892
1438
  pointerMissed(
893
1439
  event,
894
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1440
+ internal.interaction.filter((object) => !initialHits.includes(object))
895
1441
  );
896
1442
  handler(data);
897
1443
  }
898
1444
  } else {
899
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1445
+ if (isClickEvent && initialHits.includes(eventObject)) {
900
1446
  pointerMissed(
901
1447
  event,
902
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1448
+ internal.interaction.filter((object) => !initialHits.includes(object))
903
1449
  );
904
1450
  }
905
1451
  }
@@ -908,7 +1454,15 @@ function createEvents(store) {
908
1454
  handleIntersects(hits, event, delta, onIntersect);
909
1455
  };
910
1456
  }
911
- return { handlePointer };
1457
+ function flushDeferredPointers() {
1458
+ const { internal, events } = store.getState();
1459
+ if (!events.frameTimedRaycasts) return;
1460
+ for (const [pointerId, event] of internal.pointerDirty) {
1461
+ processDeferredPointer(event, pointerId);
1462
+ }
1463
+ internal.pointerDirty.clear();
1464
+ }
1465
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
912
1466
  }
913
1467
  const DOM_EVENTS = {
914
1468
  onClick: ["click", false],
@@ -927,11 +1481,16 @@ const DOM_EVENTS = {
927
1481
  onLostPointerCapture: ["lostpointercapture", true]
928
1482
  };
929
1483
  function createPointerEvents(store) {
930
- const { handlePointer } = createEvents(store);
1484
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1485
+ let nextXRPointerId = XR_POINTER_ID_START;
1486
+ const xrPointers = /* @__PURE__ */ new Map();
931
1487
  return {
932
1488
  priority: 1,
933
1489
  enabled: true,
934
- compute(event, state, previous) {
1490
+ frameTimedRaycasts: true,
1491
+ alwaysFireOnScroll: true,
1492
+ updateOnFrame: false,
1493
+ compute(event, state) {
935
1494
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
936
1495
  state.raycaster.setFromCamera(state.pointer, state.camera);
937
1496
  },
@@ -940,11 +1499,33 @@ function createPointerEvents(store) {
940
1499
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
941
1500
  {}
942
1501
  ),
943
- update: () => {
1502
+ update: (pointerId) => {
1503
+ const { events, internal } = store.getState();
1504
+ if (!events.handlers) return;
1505
+ if (pointerId !== void 0) {
1506
+ const event = internal.pointerDirty.get(pointerId);
1507
+ if (event) {
1508
+ internal.pointerDirty.delete(pointerId);
1509
+ processDeferredPointer(event, pointerId);
1510
+ } else if (internal.lastEvent?.current) {
1511
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1512
+ }
1513
+ } else {
1514
+ flushDeferredPointers();
1515
+ if (internal.lastEvent?.current) {
1516
+ events.handlers.onPointerMove(internal.lastEvent.current);
1517
+ }
1518
+ }
1519
+ },
1520
+ flush: () => {
944
1521
  const { events, internal } = store.getState();
945
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1522
+ flushDeferredPointers();
1523
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1524
+ events.handlers.onPointerMove(internal.lastEvent.current);
1525
+ }
946
1526
  },
947
1527
  connect: (target) => {
1528
+ if (!target) return;
948
1529
  const { set, events } = store.getState();
949
1530
  events.disconnect?.();
950
1531
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -968,6 +1549,32 @@ function createPointerEvents(store) {
968
1549
  }
969
1550
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
970
1551
  }
1552
+ },
1553
+ registerPointer: (config) => {
1554
+ const pointerId = nextXRPointerId++;
1555
+ xrPointers.set(pointerId, config);
1556
+ const { internal } = store.getState();
1557
+ getPointerState(internal, pointerId);
1558
+ return pointerId;
1559
+ },
1560
+ unregisterPointer: (pointerId) => {
1561
+ xrPointers.delete(pointerId);
1562
+ const { internal } = store.getState();
1563
+ const pointerState = internal.pointerMap.get(pointerId);
1564
+ if (pointerState) {
1565
+ for (const [, hoveredObj] of pointerState.hovered) {
1566
+ const eventObject = hoveredObj.eventObject;
1567
+ const instance = eventObject.__r3f;
1568
+ if (instance?.eventCount) {
1569
+ const handlers = instance.handlers;
1570
+ const data = { ...hoveredObj, intersections: [] };
1571
+ handlers.onPointerOut?.(data);
1572
+ handlers.onPointerLeave?.(data);
1573
+ }
1574
+ }
1575
+ internal.pointerMap.delete(pointerId);
1576
+ }
1577
+ internal.pointerDirty.delete(pointerId);
971
1578
  }
972
1579
  };
973
1580
  }
@@ -1029,331 +1636,26 @@ function notifyAlpha({ message, link }) {
1029
1636
  }
1030
1637
  }
1031
1638
 
1032
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1033
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1034
- const createStore = (invalidate, advance) => {
1035
- const rootStore = createWithEqualityFn((set, get) => {
1036
- const position = new Vector3();
1037
- const defaultTarget = new Vector3();
1038
- const tempTarget = new Vector3();
1039
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1040
- const { width, height, top, left } = size;
1041
- const aspect = width / height;
1042
- if (target.isVector3) tempTarget.copy(target);
1043
- else tempTarget.set(...target);
1044
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1045
- if (isOrthographicCamera(camera)) {
1046
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1047
- } else {
1048
- const fov = camera.fov * Math.PI / 180;
1049
- const h = 2 * Math.tan(fov / 2) * distance;
1050
- const w = h * (width / height);
1051
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1052
- }
1053
- }
1054
- let performanceTimeout = void 0;
1055
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1056
- const pointer = new Vector2();
1057
- const rootState = {
1058
- set,
1059
- get,
1060
- // Mock objects that have to be configured
1061
- gl: null,
1062
- renderer: null,
1063
- camera: null,
1064
- frustum: new Frustum(),
1065
- autoUpdateFrustum: true,
1066
- raycaster: null,
1067
- events: { priority: 1, enabled: true, connected: false },
1068
- scene: null,
1069
- rootScene: null,
1070
- xr: null,
1071
- inspector: null,
1072
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1073
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1074
- legacy: false,
1075
- linear: false,
1076
- flat: false,
1077
- textureColorSpace: "srgb",
1078
- isLegacy: false,
1079
- webGPUSupported: false,
1080
- isNative: false,
1081
- controls: null,
1082
- pointer,
1083
- mouse: pointer,
1084
- frameloop: "always",
1085
- onPointerMissed: void 0,
1086
- onDragOverMissed: void 0,
1087
- onDropMissed: void 0,
1088
- performance: {
1089
- current: 1,
1090
- min: 0.5,
1091
- max: 1,
1092
- debounce: 200,
1093
- regress: () => {
1094
- const state2 = get();
1095
- if (performanceTimeout) clearTimeout(performanceTimeout);
1096
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1097
- performanceTimeout = setTimeout(
1098
- () => setPerformanceCurrent(get().performance.max),
1099
- state2.performance.debounce
1100
- );
1101
- }
1102
- },
1103
- size: { width: 0, height: 0, top: 0, left: 0 },
1104
- viewport: {
1105
- initialDpr: 0,
1106
- dpr: 0,
1107
- width: 0,
1108
- height: 0,
1109
- top: 0,
1110
- left: 0,
1111
- aspect: 0,
1112
- distance: 0,
1113
- factor: 0,
1114
- getCurrentViewport
1115
- },
1116
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1117
- setSize: (width, height, top, left) => {
1118
- const state2 = get();
1119
- if (width === void 0) {
1120
- set({ _sizeImperative: false });
1121
- if (state2._sizeProps) {
1122
- const { width: propW, height: propH } = state2._sizeProps;
1123
- if (propW !== void 0 || propH !== void 0) {
1124
- const currentSize = state2.size;
1125
- const newSize = {
1126
- width: propW ?? currentSize.width,
1127
- height: propH ?? currentSize.height,
1128
- top: currentSize.top,
1129
- left: currentSize.left
1130
- };
1131
- set((s) => ({
1132
- size: newSize,
1133
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1134
- }));
1135
- }
1136
- }
1137
- return;
1138
- }
1139
- const w = width;
1140
- const h = height ?? width;
1141
- const t = top ?? state2.size.top;
1142
- const l = left ?? state2.size.left;
1143
- const size = { width: w, height: h, top: t, left: l };
1144
- set((s) => ({
1145
- size,
1146
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1147
- _sizeImperative: true
1148
- }));
1149
- },
1150
- setDpr: (dpr) => set((state2) => {
1151
- const resolved = calculateDpr(dpr);
1152
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1153
- }),
1154
- setFrameloop: (frameloop = "always") => {
1155
- set(() => ({ frameloop }));
1156
- },
1157
- setError: (error) => set(() => ({ error })),
1158
- error: null,
1159
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1160
- uniforms: {},
1161
- nodes: {},
1162
- textures: /* @__PURE__ */ new Map(),
1163
- postProcessing: null,
1164
- passes: {},
1165
- _hmrVersion: 0,
1166
- _sizeImperative: false,
1167
- _sizeProps: null,
1168
- previousRoot: void 0,
1169
- internal: {
1170
- // Events
1171
- interaction: [],
1172
- hovered: /* @__PURE__ */ new Map(),
1173
- subscribers: [],
1174
- initialClick: [0, 0],
1175
- initialHits: [],
1176
- capturedMap: /* @__PURE__ */ new Map(),
1177
- lastEvent: React.createRef(),
1178
- // Visibility tracking (onFramed, onOccluded, onVisible)
1179
- visibilityRegistry: /* @__PURE__ */ new Map(),
1180
- // Occlusion system (WebGPU only)
1181
- occlusionEnabled: false,
1182
- occlusionObserver: null,
1183
- occlusionCache: /* @__PURE__ */ new Map(),
1184
- helperGroup: null,
1185
- // Updates
1186
- active: false,
1187
- frames: 0,
1188
- priority: 0,
1189
- subscribe: (ref, priority, store) => {
1190
- const internal = get().internal;
1191
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1192
- internal.subscribers.push({ ref, priority, store });
1193
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1194
- return () => {
1195
- const internal2 = get().internal;
1196
- if (internal2?.subscribers) {
1197
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1198
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1199
- }
1200
- };
1201
- },
1202
- // Renderer Storage (single source of truth)
1203
- actualRenderer: null,
1204
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1205
- scheduler: null
1206
- }
1207
- };
1208
- return rootState;
1209
- });
1210
- const state = rootStore.getState();
1211
- Object.defineProperty(state, "gl", {
1212
- get() {
1213
- const currentState = rootStore.getState();
1214
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1215
- const stack = new Error().stack || "";
1216
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1217
- if (!isInternalAccess) {
1218
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1219
- notifyDepreciated({
1220
- heading: "Accessing state.gl in WebGPU mode",
1221
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1222
- });
1223
- }
1224
- }
1225
- return currentState.internal.actualRenderer;
1226
- },
1227
- set(value) {
1228
- rootStore.getState().internal.actualRenderer = value;
1229
- },
1230
- enumerable: true,
1231
- configurable: true
1232
- });
1233
- Object.defineProperty(state, "renderer", {
1234
- get() {
1235
- return rootStore.getState().internal.actualRenderer;
1236
- },
1237
- set(value) {
1238
- rootStore.getState().internal.actualRenderer = value;
1239
- },
1240
- enumerable: true,
1241
- configurable: true
1242
- });
1243
- let oldScene = state.scene;
1244
- rootStore.subscribe(() => {
1245
- const currentState = rootStore.getState();
1246
- const { scene, rootScene, set } = currentState;
1247
- if (scene !== oldScene) {
1248
- oldScene = scene;
1249
- if (scene?.isScene && scene !== rootScene) {
1250
- set({ rootScene: scene });
1251
- }
1252
- }
1253
- });
1254
- let oldSize = state.size;
1255
- let oldDpr = state.viewport.dpr;
1256
- let oldCamera = state.camera;
1257
- rootStore.subscribe(() => {
1258
- const { camera, size, viewport, set, internal } = rootStore.getState();
1259
- const actualRenderer = internal.actualRenderer;
1260
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1261
- oldSize = size;
1262
- oldDpr = viewport.dpr;
1263
- updateCamera(camera, size);
1264
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1265
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1266
- actualRenderer.setSize(size.width, size.height, updateStyle);
1267
- }
1268
- if (camera !== oldCamera) {
1269
- oldCamera = camera;
1270
- const { rootScene } = rootStore.getState();
1271
- if (camera && rootScene && !camera.parent) {
1272
- rootScene.add(camera);
1273
- }
1274
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1275
- const currentState = rootStore.getState();
1276
- if (currentState.autoUpdateFrustum && camera) {
1277
- updateFrustum(camera, currentState.frustum);
1278
- }
1279
- }
1280
- });
1281
- rootStore.subscribe((state2) => invalidate(state2));
1282
- return rootStore;
1283
- };
1284
-
1285
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1286
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1287
- function getLoader(Proto) {
1288
- if (isConstructor$1(Proto)) {
1289
- let loader = memoizedLoaders.get(Proto);
1290
- if (!loader) {
1291
- loader = new Proto();
1292
- memoizedLoaders.set(Proto, loader);
1293
- }
1294
- return loader;
1295
- }
1296
- return Proto;
1297
- }
1298
- function loadingFn(extensions, onProgress) {
1299
- return function(Proto, input) {
1300
- const loader = getLoader(Proto);
1301
- if (extensions) extensions(loader);
1302
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1303
- return loader.loadAsync(input, onProgress).then((data) => {
1304
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1305
- return data;
1306
- });
1307
- }
1308
- return new Promise(
1309
- (res, reject) => loader.load(
1310
- input,
1311
- (data) => {
1312
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1313
- res(data);
1314
- },
1315
- onProgress,
1316
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1317
- )
1318
- );
1319
- };
1320
- }
1321
- function useLoader(loader, input, extensions, onProgress) {
1322
- const keys = Array.isArray(input) ? input : [input];
1323
- const fn = loadingFn(extensions, onProgress);
1324
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1325
- return Array.isArray(input) ? results : results[0];
1326
- }
1327
- useLoader.preload = function(loader, input, extensions, onProgress) {
1328
- const keys = Array.isArray(input) ? input : [input];
1329
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1330
- };
1331
- useLoader.clear = function(loader, input) {
1332
- const keys = Array.isArray(input) ? input : [input];
1333
- keys.forEach((key) => clear([loader, key]));
1334
- };
1335
- useLoader.loader = getLoader;
1336
-
1337
- var __defProp$1 = Object.defineProperty;
1338
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1339
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1340
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1341
- class PhaseGraph {
1342
- constructor() {
1343
- /** Ordered list of phase nodes */
1344
- __publicField$1(this, "phases", []);
1345
- /** Quick lookup by name */
1346
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1347
- /** Cached ordered names (invalidated on changes) */
1348
- __publicField$1(this, "orderedNamesCache", null);
1349
- this.initializeDefaultPhases();
1350
- }
1351
- //* Initialization --------------------------------
1352
- initializeDefaultPhases() {
1353
- for (const name of DEFAULT_PHASES) {
1354
- const node = { name, isAutoGenerated: false };
1355
- this.phases.push(node);
1356
- this.phaseMap.set(name, node);
1639
+ var __defProp$1 = Object.defineProperty;
1640
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1641
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1642
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1643
+ class PhaseGraph {
1644
+ constructor() {
1645
+ /** Ordered list of phase nodes */
1646
+ __publicField$1(this, "phases", []);
1647
+ /** Quick lookup by name */
1648
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1649
+ /** Cached ordered names (invalidated on changes) */
1650
+ __publicField$1(this, "orderedNamesCache", null);
1651
+ this.initializeDefaultPhases();
1652
+ }
1653
+ //* Initialization --------------------------------
1654
+ initializeDefaultPhases() {
1655
+ for (const name of DEFAULT_PHASES) {
1656
+ const node = { name, isAutoGenerated: false };
1657
+ this.phases.push(node);
1658
+ this.phaseMap.set(name, node);
1357
1659
  }
1358
1660
  this.invalidateCache();
1359
1661
  }
@@ -1586,7 +1888,7 @@ function shouldRun(job, now) {
1586
1888
  const minInterval = 1e3 / job.fps;
1587
1889
  const lastRun = job.lastRun ?? 0;
1588
1890
  const elapsed = now - lastRun;
1589
- if (elapsed < minInterval) return false;
1891
+ if (elapsed < minInterval - 1) return false;
1590
1892
  if (job.drop) {
1591
1893
  job.lastRun = now;
1592
1894
  } else {
@@ -2255,116 +2557,444 @@ const _Scheduler = class _Scheduler {
2255
2557
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2256
2558
  root.needsRebuild = false;
2257
2559
  }
2258
- const providedState = root.getState?.() ?? {};
2259
- const frameState = {
2260
- ...providedState,
2261
- time: timestamp,
2262
- delta,
2263
- elapsed: this.loopState.elapsedTime / 1e3,
2264
- // Convert ms to seconds
2265
- frame: this.loopState.frameCount
2266
- };
2267
- for (const job of root.sortedJobs) {
2268
- if (!shouldRun(job, timestamp)) continue;
2269
- try {
2270
- job.callback(frameState, delta);
2271
- } catch (error) {
2272
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2273
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2560
+ const providedState = root.getState?.() ?? {};
2561
+ const frameState = {
2562
+ ...providedState,
2563
+ time: timestamp,
2564
+ delta,
2565
+ elapsed: this.loopState.elapsedTime / 1e3,
2566
+ // Convert ms to seconds
2567
+ frame: this.loopState.frameCount
2568
+ };
2569
+ for (const job of root.sortedJobs) {
2570
+ if (!shouldRun(job, timestamp)) continue;
2571
+ try {
2572
+ job.callback(frameState, delta);
2573
+ } catch (error) {
2574
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2575
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2576
+ }
2577
+ }
2578
+ }
2579
+ //* Debug & Inspection Methods ================================
2580
+ /**
2581
+ * Get the total number of registered jobs across all roots.
2582
+ * Includes both per-root jobs and global before/after jobs.
2583
+ * @returns {number} Total job count
2584
+ */
2585
+ getJobCount() {
2586
+ let count = 0;
2587
+ for (const root of this.roots.values()) {
2588
+ count += root.jobs.size;
2589
+ }
2590
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2591
+ }
2592
+ /**
2593
+ * Get all registered job IDs across all roots.
2594
+ * Includes both per-root jobs and global before/after jobs.
2595
+ * @returns {string[]} Array of all job IDs
2596
+ */
2597
+ getJobIds() {
2598
+ const ids = [];
2599
+ for (const root of this.roots.values()) {
2600
+ ids.push(...root.jobs.keys());
2601
+ }
2602
+ ids.push(...this.globalBeforeJobs.keys());
2603
+ ids.push(...this.globalAfterJobs.keys());
2604
+ return ids;
2605
+ }
2606
+ /**
2607
+ * Get the number of registered roots (Canvas instances).
2608
+ * @returns {number} Number of registered roots
2609
+ */
2610
+ getRootCount() {
2611
+ return this.roots.size;
2612
+ }
2613
+ /**
2614
+ * Check if any user (non-system) jobs are registered in a specific phase.
2615
+ * Used by the default render job to know if a user has taken over rendering.
2616
+ *
2617
+ * @param phase The phase to check
2618
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2619
+ * @returns true if any user jobs exist in the phase
2620
+ */
2621
+ hasUserJobsInPhase(phase, rootId) {
2622
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2623
+ return rootsToCheck.some((root) => {
2624
+ if (!root) return false;
2625
+ for (const job of root.jobs.values()) {
2626
+ if (job.phase === phase && !job.system && job.enabled) return true;
2627
+ }
2628
+ return false;
2629
+ });
2630
+ }
2631
+ //* Utility Methods ================================
2632
+ /**
2633
+ * Generate a unique root ID for automatic root registration.
2634
+ * @returns {string} A unique root ID in the format 'root_N'
2635
+ */
2636
+ generateRootId() {
2637
+ return `root_${this.nextRootIndex++}`;
2638
+ }
2639
+ /**
2640
+ * Generate a unique job ID.
2641
+ * @returns {string} A unique job ID in the format 'job_N'
2642
+ * @private
2643
+ */
2644
+ generateJobId() {
2645
+ return `job_${this.nextJobIndex}`;
2646
+ }
2647
+ /**
2648
+ * Normalize before/after constraints to a Set.
2649
+ * Handles undefined, single string, or array inputs.
2650
+ * @param {string | string[] | undefined} value - The constraint value(s)
2651
+ * @returns {Set<string>} Normalized Set of constraint strings
2652
+ * @private
2653
+ */
2654
+ normalizeConstraints(value) {
2655
+ if (!value) return /* @__PURE__ */ new Set();
2656
+ if (Array.isArray(value)) return new Set(value);
2657
+ return /* @__PURE__ */ new Set([value]);
2658
+ }
2659
+ };
2660
+ //* Static State & Methods (Singleton Usage) ================================
2661
+ //* Cross-Bundle Singleton Key ==============================
2662
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2663
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2664
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2665
+ let Scheduler = _Scheduler;
2666
+ const getScheduler = () => Scheduler.get();
2667
+ if (hmrData) {
2668
+ hmrData.accept?.();
2669
+ }
2670
+
2671
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2672
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
2673
+ const createStore = (invalidate, advance) => {
2674
+ const rootStore = createWithEqualityFn((set, get) => {
2675
+ const position = new Vector3();
2676
+ const defaultTarget = new Vector3();
2677
+ const tempTarget = new Vector3();
2678
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2679
+ const { width, height, top, left } = size;
2680
+ const aspect = width / height;
2681
+ if (target.isVector3) tempTarget.copy(target);
2682
+ else tempTarget.set(...target);
2683
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2684
+ if (isOrthographicCamera(camera)) {
2685
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2686
+ } else {
2687
+ const fov = camera.fov * Math.PI / 180;
2688
+ const h = 2 * Math.tan(fov / 2) * distance;
2689
+ const w = h * (width / height);
2690
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2691
+ }
2692
+ }
2693
+ let performanceTimeout = void 0;
2694
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2695
+ const pointer = new Vector2();
2696
+ const rootState = {
2697
+ set,
2698
+ get,
2699
+ // Mock objects that have to be configured
2700
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2701
+ primaryStore: null,
2702
+ gl: null,
2703
+ renderer: null,
2704
+ camera: null,
2705
+ frustum: new Frustum(),
2706
+ autoUpdateFrustum: true,
2707
+ raycaster: null,
2708
+ events: {
2709
+ priority: 1,
2710
+ enabled: true,
2711
+ connected: false,
2712
+ frameTimedRaycasts: true,
2713
+ alwaysFireOnScroll: true,
2714
+ updateOnFrame: false
2715
+ },
2716
+ scene: null,
2717
+ rootScene: null,
2718
+ xr: null,
2719
+ inspector: null,
2720
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2721
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2722
+ textureColorSpace: SRGBColorSpace,
2723
+ isLegacy: false,
2724
+ webGPUSupported: false,
2725
+ isNative: false,
2726
+ controls: null,
2727
+ pointer,
2728
+ mouse: pointer,
2729
+ frameloop: "always",
2730
+ onPointerMissed: void 0,
2731
+ onDragOverMissed: void 0,
2732
+ onDropMissed: void 0,
2733
+ performance: {
2734
+ current: 1,
2735
+ min: 0.5,
2736
+ max: 1,
2737
+ debounce: 200,
2738
+ regress: () => {
2739
+ const state2 = get();
2740
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2741
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2742
+ performanceTimeout = setTimeout(
2743
+ () => setPerformanceCurrent(get().performance.max),
2744
+ state2.performance.debounce
2745
+ );
2746
+ }
2747
+ },
2748
+ size: { width: 0, height: 0, top: 0, left: 0 },
2749
+ viewport: {
2750
+ initialDpr: 0,
2751
+ dpr: 0,
2752
+ width: 0,
2753
+ height: 0,
2754
+ top: 0,
2755
+ left: 0,
2756
+ aspect: 0,
2757
+ distance: 0,
2758
+ factor: 0,
2759
+ getCurrentViewport
2760
+ },
2761
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2762
+ setSize: (width, height, top, left) => {
2763
+ const state2 = get();
2764
+ if (width === void 0) {
2765
+ set({ _sizeImperative: false });
2766
+ if (state2._sizeProps) {
2767
+ const { width: propW, height: propH } = state2._sizeProps;
2768
+ if (propW !== void 0 || propH !== void 0) {
2769
+ const currentSize = state2.size;
2770
+ const newSize = {
2771
+ width: propW ?? currentSize.width,
2772
+ height: propH ?? currentSize.height,
2773
+ top: currentSize.top,
2774
+ left: currentSize.left
2775
+ };
2776
+ set((s) => ({
2777
+ size: newSize,
2778
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2779
+ }));
2780
+ getScheduler().invalidate();
2781
+ }
2782
+ }
2783
+ return;
2784
+ }
2785
+ const w = width;
2786
+ const h = height ?? width;
2787
+ const t = top ?? state2.size.top;
2788
+ const l = left ?? state2.size.left;
2789
+ const size = { width: w, height: h, top: t, left: l };
2790
+ set((s) => ({
2791
+ size,
2792
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2793
+ _sizeImperative: true
2794
+ }));
2795
+ getScheduler().invalidate();
2796
+ },
2797
+ setDpr: (dpr) => set((state2) => {
2798
+ const resolved = calculateDpr(dpr);
2799
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2800
+ }),
2801
+ setFrameloop: (frameloop = "always") => {
2802
+ set(() => ({ frameloop }));
2803
+ },
2804
+ setError: (error) => set(() => ({ error })),
2805
+ error: null,
2806
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2807
+ uniforms: {},
2808
+ nodes: {},
2809
+ buffers: {},
2810
+ gpuStorage: {},
2811
+ textures: /* @__PURE__ */ new Map(),
2812
+ renderPipeline: null,
2813
+ passes: {},
2814
+ _hmrVersion: 0,
2815
+ _sizeImperative: false,
2816
+ _sizeProps: null,
2817
+ previousRoot: void 0,
2818
+ internal: {
2819
+ // Events
2820
+ interaction: [],
2821
+ subscribers: [],
2822
+ // Per-pointer state (new unified structure)
2823
+ pointerMap: /* @__PURE__ */ new Map(),
2824
+ pointerDirty: /* @__PURE__ */ new Map(),
2825
+ lastEvent: React.createRef(),
2826
+ // Deprecated but kept for backwards compatibility
2827
+ hovered: /* @__PURE__ */ new Map(),
2828
+ initialClick: [0, 0],
2829
+ initialHits: [],
2830
+ capturedMap: /* @__PURE__ */ new Map(),
2831
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2832
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2833
+ // Occlusion system (WebGPU only)
2834
+ occlusionEnabled: false,
2835
+ occlusionObserver: null,
2836
+ occlusionCache: /* @__PURE__ */ new Map(),
2837
+ helperGroup: null,
2838
+ // Updates
2839
+ active: false,
2840
+ frames: 0,
2841
+ priority: 0,
2842
+ subscribe: (ref, priority, store) => {
2843
+ const internal = get().internal;
2844
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2845
+ internal.subscribers.push({ ref, priority, store });
2846
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2847
+ return () => {
2848
+ const internal2 = get().internal;
2849
+ if (internal2?.subscribers) {
2850
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2851
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2852
+ }
2853
+ };
2854
+ },
2855
+ // Renderer Storage (single source of truth)
2856
+ actualRenderer: null,
2857
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2858
+ scheduler: null
2859
+ }
2860
+ };
2861
+ return rootState;
2862
+ });
2863
+ const state = rootStore.getState();
2864
+ Object.defineProperty(state, "gl", {
2865
+ get() {
2866
+ const currentState = rootStore.getState();
2867
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2868
+ const stack = new Error().stack || "";
2869
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2870
+ if (!isInternalAccess) {
2871
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2872
+ notifyDepreciated({
2873
+ heading: "Accessing state.gl in WebGPU mode",
2874
+ 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
2875
+ });
2876
+ }
2877
+ }
2878
+ return currentState.internal.actualRenderer;
2879
+ },
2880
+ set(value) {
2881
+ rootStore.getState().internal.actualRenderer = value;
2882
+ },
2883
+ enumerable: true,
2884
+ configurable: true
2885
+ });
2886
+ Object.defineProperty(state, "renderer", {
2887
+ get() {
2888
+ return rootStore.getState().internal.actualRenderer;
2889
+ },
2890
+ set(value) {
2891
+ rootStore.getState().internal.actualRenderer = value;
2892
+ },
2893
+ enumerable: true,
2894
+ configurable: true
2895
+ });
2896
+ let oldScene = state.scene;
2897
+ rootStore.subscribe(() => {
2898
+ const currentState = rootStore.getState();
2899
+ const { scene, rootScene, set } = currentState;
2900
+ if (scene !== oldScene) {
2901
+ oldScene = scene;
2902
+ if (scene?.isScene && scene !== rootScene) {
2903
+ set({ rootScene: scene });
2904
+ }
2905
+ }
2906
+ });
2907
+ let oldSize = state.size;
2908
+ let oldDpr = state.viewport.dpr;
2909
+ let oldCamera = state.camera;
2910
+ rootStore.subscribe(() => {
2911
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2912
+ const actualRenderer = internal.actualRenderer;
2913
+ const canvasTarget = internal.canvasTarget;
2914
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2915
+ oldSize = size;
2916
+ oldDpr = viewport.dpr;
2917
+ updateCamera(camera, size);
2918
+ if (internal.isSecondary && canvasTarget) {
2919
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2920
+ canvasTarget.setSize(size.width, size.height, false);
2921
+ } else {
2922
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2923
+ actualRenderer.setSize(size.width, size.height, false);
2924
+ if (canvasTarget) {
2925
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2926
+ canvasTarget.setSize(size.width, size.height, false);
2927
+ }
2274
2928
  }
2275
2929
  }
2276
- }
2277
- //* Debug & Inspection Methods ================================
2278
- /**
2279
- * Get the total number of registered jobs across all roots.
2280
- * Includes both per-root jobs and global before/after jobs.
2281
- * @returns {number} Total job count
2282
- */
2283
- getJobCount() {
2284
- let count = 0;
2285
- for (const root of this.roots.values()) {
2286
- count += root.jobs.size;
2930
+ if (camera !== oldCamera) {
2931
+ oldCamera = camera;
2932
+ const { rootScene } = rootStore.getState();
2933
+ if (camera && rootScene && !camera.parent) {
2934
+ rootScene.add(camera);
2935
+ }
2936
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2937
+ const currentState = rootStore.getState();
2938
+ if (currentState.autoUpdateFrustum && camera) {
2939
+ updateFrustum(camera, currentState.frustum);
2940
+ }
2287
2941
  }
2288
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2289
- }
2290
- /**
2291
- * Get all registered job IDs across all roots.
2292
- * Includes both per-root jobs and global before/after jobs.
2293
- * @returns {string[]} Array of all job IDs
2294
- */
2295
- getJobIds() {
2296
- const ids = [];
2297
- for (const root of this.roots.values()) {
2298
- ids.push(...root.jobs.keys());
2942
+ });
2943
+ rootStore.subscribe((state2) => invalidate(state2));
2944
+ return rootStore;
2945
+ };
2946
+
2947
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2948
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2949
+ function getLoader(Proto) {
2950
+ if (isConstructor$1(Proto)) {
2951
+ let loader = memoizedLoaders.get(Proto);
2952
+ if (!loader) {
2953
+ loader = new Proto();
2954
+ memoizedLoaders.set(Proto, loader);
2299
2955
  }
2300
- ids.push(...this.globalBeforeJobs.keys());
2301
- ids.push(...this.globalAfterJobs.keys());
2302
- return ids;
2303
- }
2304
- /**
2305
- * Get the number of registered roots (Canvas instances).
2306
- * @returns {number} Number of registered roots
2307
- */
2308
- getRootCount() {
2309
- return this.roots.size;
2310
- }
2311
- /**
2312
- * Check if any user (non-system) jobs are registered in a specific phase.
2313
- * Used by the default render job to know if a user has taken over rendering.
2314
- *
2315
- * @param phase The phase to check
2316
- * @param rootId Optional root ID to check (checks all roots if not provided)
2317
- * @returns true if any user jobs exist in the phase
2318
- */
2319
- hasUserJobsInPhase(phase, rootId) {
2320
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2321
- return rootsToCheck.some((root) => {
2322
- if (!root) return false;
2323
- for (const job of root.jobs.values()) {
2324
- if (job.phase === phase && !job.system && job.enabled) return true;
2325
- }
2326
- return false;
2327
- });
2328
- }
2329
- //* Utility Methods ================================
2330
- /**
2331
- * Generate a unique root ID for automatic root registration.
2332
- * @returns {string} A unique root ID in the format 'root_N'
2333
- */
2334
- generateRootId() {
2335
- return `root_${this.nextRootIndex++}`;
2336
- }
2337
- /**
2338
- * Generate a unique job ID.
2339
- * @returns {string} A unique job ID in the format 'job_N'
2340
- * @private
2341
- */
2342
- generateJobId() {
2343
- return `job_${this.nextJobIndex}`;
2344
- }
2345
- /**
2346
- * Normalize before/after constraints to a Set.
2347
- * Handles undefined, single string, or array inputs.
2348
- * @param {string | string[] | undefined} value - The constraint value(s)
2349
- * @returns {Set<string>} Normalized Set of constraint strings
2350
- * @private
2351
- */
2352
- normalizeConstraints(value) {
2353
- if (!value) return /* @__PURE__ */ new Set();
2354
- if (Array.isArray(value)) return new Set(value);
2355
- return /* @__PURE__ */ new Set([value]);
2956
+ return loader;
2356
2957
  }
2357
- };
2358
- //* Static State & Methods (Singleton Usage) ================================
2359
- //* Cross-Bundle Singleton Key ==============================
2360
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2361
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2362
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2363
- let Scheduler = _Scheduler;
2364
- const getScheduler = () => Scheduler.get();
2365
- if (hmrData) {
2366
- hmrData.accept?.();
2958
+ return Proto;
2959
+ }
2960
+ function loadingFn(extensions, onProgress) {
2961
+ return function(Proto, input) {
2962
+ const loader = getLoader(Proto);
2963
+ if (extensions) extensions(loader);
2964
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2965
+ return loader.loadAsync(input, onProgress).then((data) => {
2966
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2967
+ return data;
2968
+ });
2969
+ }
2970
+ return new Promise(
2971
+ (res, reject) => loader.load(
2972
+ input,
2973
+ (data) => {
2974
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2975
+ res(data);
2976
+ },
2977
+ onProgress,
2978
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2979
+ )
2980
+ );
2981
+ };
2982
+ }
2983
+ function useLoader(loader, input, extensions, onProgress) {
2984
+ const keys = Array.isArray(input) ? input : [input];
2985
+ const fn = loadingFn(extensions, onProgress);
2986
+ const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
2987
+ return Array.isArray(input) ? results : results[0];
2367
2988
  }
2989
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2990
+ const keys = Array.isArray(input) ? input : [input];
2991
+ keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2992
+ };
2993
+ useLoader.clear = function(loader, input) {
2994
+ const keys = Array.isArray(input) ? input : [input];
2995
+ keys.forEach((key) => clear([loader, key]));
2996
+ };
2997
+ useLoader.loader = getLoader;
2368
2998
 
2369
2999
  function useFrame(callback, priorityOrOptions) {
2370
3000
  const store = React.useContext(context);
@@ -2545,6 +3175,9 @@ function useTexture(input, optionsOrOnLoad) {
2545
3175
  const textureCache = useThree((state) => state.textures);
2546
3176
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2547
3177
  const { onLoad, cache = false } = options;
3178
+ const onLoadRef = useRef(onLoad);
3179
+ onLoadRef.current = onLoad;
3180
+ const onLoadCalledForRef = useRef(null);
2548
3181
  const urls = useMemo(() => getUrls(input), [input]);
2549
3182
  const cachedResult = useMemo(() => {
2550
3183
  if (!cache) return null;
@@ -2555,9 +3188,13 @@ function useTexture(input, optionsOrOnLoad) {
2555
3188
  TextureLoader,
2556
3189
  IsObject(input) ? Object.values(input) : input
2557
3190
  );
3191
+ const inputKey = urls.join("\0");
2558
3192
  useLayoutEffect(() => {
2559
- if (!cachedResult) onLoad?.(loadedTextures);
2560
- }, [onLoad, cachedResult, loadedTextures]);
3193
+ if (cachedResult) return;
3194
+ if (onLoadCalledForRef.current === inputKey) return;
3195
+ onLoadCalledForRef.current = inputKey;
3196
+ onLoadRef.current?.(loadedTextures);
3197
+ }, [cachedResult, loadedTextures, inputKey]);
2561
3198
  useEffect(() => {
2562
3199
  if (cachedResult) return;
2563
3200
  if ("initTexture" in renderer) {
@@ -2724,14 +3361,31 @@ function useTextures() {
2724
3361
  }, [store]);
2725
3362
  }
2726
3363
 
2727
- function useRenderTarget(width, height, options) {
3364
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2728
3365
  const isLegacy = useThree((s) => s.isLegacy);
2729
3366
  const size = useThree((s) => s.size);
3367
+ let width;
3368
+ let height;
3369
+ let opts;
3370
+ if (typeof widthOrOptions === "object") {
3371
+ opts = widthOrOptions;
3372
+ } else if (typeof widthOrOptions === "number") {
3373
+ width = widthOrOptions;
3374
+ if (typeof heightOrOptions === "object") {
3375
+ height = widthOrOptions;
3376
+ opts = heightOrOptions;
3377
+ } else if (typeof heightOrOptions === "number") {
3378
+ height = heightOrOptions;
3379
+ opts = options;
3380
+ } else {
3381
+ height = widthOrOptions;
3382
+ }
3383
+ }
2730
3384
  return useMemo(() => {
2731
3385
  const w = width ?? size.width;
2732
3386
  const h = height ?? size.height;
2733
- return new WebGLRenderTarget(w, h, options);
2734
- }, [width, height, size.width, size.height, options, isLegacy]);
3387
+ return new WebGLRenderTarget(w, h, opts);
3388
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2735
3389
  }
2736
3390
 
2737
3391
  function useStore() {
@@ -2781,7 +3435,7 @@ function addTail(callback) {
2781
3435
  function invalidate(state, frames = 1, stackFrames = false) {
2782
3436
  getScheduler().invalidate(frames, stackFrames);
2783
3437
  }
2784
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3438
+ function advance(timestamp) {
2785
3439
  getScheduler().step(timestamp);
2786
3440
  }
2787
3441
 
@@ -14235,6 +14889,7 @@ function swapInstances() {
14235
14889
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14236
14890
  instance.object.__r3f = instance;
14237
14891
  setFiberRef(fiber, instance.object);
14892
+ delete instance.appliedOnce;
14238
14893
  applyProps(instance.object, instance.props);
14239
14894
  if (instance.props.attach) {
14240
14895
  attach(parent, instance);
@@ -14308,8 +14963,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14308
14963
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14309
14964
  if (isTailSibling) swapInstances();
14310
14965
  },
14311
- finalizeInitialChildren: () => false,
14312
- commitMount() {
14966
+ finalizeInitialChildren: (instance) => {
14967
+ for (const prop in instance.props) {
14968
+ if (isFromRef(instance.props[prop])) return true;
14969
+ }
14970
+ return false;
14971
+ },
14972
+ commitMount(instance) {
14973
+ const resolved = {};
14974
+ for (const prop in instance.props) {
14975
+ const value = instance.props[prop];
14976
+ if (isFromRef(value)) {
14977
+ const ref = value[FROM_REF];
14978
+ if (ref.current != null) resolved[prop] = ref.current;
14979
+ }
14980
+ }
14981
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14313
14982
  },
14314
14983
  getPublicInstance: (instance) => instance?.object,
14315
14984
  prepareForCommit: () => null,
@@ -14530,6 +15199,9 @@ function createRoot(canvas) {
14530
15199
  let resolve;
14531
15200
  pending = new Promise((_resolve) => resolve = _resolve);
14532
15201
  const {
15202
+ id: canvasId,
15203
+ primaryCanvas,
15204
+ scheduler: schedulerConfig,
14533
15205
  gl: glConfig,
14534
15206
  renderer: rendererConfig,
14535
15207
  size: propsSize,
@@ -14537,10 +15209,6 @@ function createRoot(canvas) {
14537
15209
  events,
14538
15210
  onCreated: onCreatedCallback,
14539
15211
  shadows = false,
14540
- linear = false,
14541
- flat = false,
14542
- textureColorSpace = SRGBColorSpace,
14543
- legacy = false,
14544
15212
  orthographic = false,
14545
15213
  frameloop = "always",
14546
15214
  dpr = [1, 2],
@@ -14552,8 +15220,10 @@ function createRoot(canvas) {
14552
15220
  onDropMissed,
14553
15221
  autoUpdateFrustum = true,
14554
15222
  occlusion = false,
14555
- _sizeProps
15223
+ _sizeProps,
15224
+ forceEven
14556
15225
  } = props;
15226
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
14557
15227
  const state = store.getState();
14558
15228
  const defaultGLProps = {
14559
15229
  canvas,
@@ -14566,15 +15236,25 @@ function createRoot(canvas) {
14566
15236
  "WebGPURenderer (renderer prop) is not available in this build. Use @react-three/fiber or @react-three/fiber/webgpu instead."
14567
15237
  );
14568
15238
  }
14569
- (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
15239
+ const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14570
15240
  if (glConfig && rendererConfig) {
14571
15241
  throw new Error("Cannot use both gl and renderer props at the same time");
14572
15242
  }
14573
15243
  let renderer = state.internal.actualRenderer;
15244
+ if (primaryCanvas && !R3F_BUILD_WEBGPU) {
15245
+ throw new Error(
15246
+ "The `primaryCanvas` prop for multi-canvas rendering is only available with WebGPU. Use @react-three/fiber/webgpu instead."
15247
+ );
15248
+ }
15249
+ if (primaryCanvas && wantsGL) {
15250
+ throw new Error(
15251
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15252
+ );
15253
+ }
14574
15254
  if (!state.internal.actualRenderer) {
14575
15255
  renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14576
15256
  state.internal.actualRenderer = renderer;
14577
- state.set({ isLegacy: true, gl: renderer, renderer });
15257
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14578
15258
  }
14579
15259
  let raycaster = state.raycaster;
14580
15260
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
@@ -14583,6 +15263,7 @@ function createRoot(canvas) {
14583
15263
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14584
15264
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14585
15265
  }
15266
+ let tempCamera = state.camera;
14586
15267
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14587
15268
  lastCamera = cameraOptions;
14588
15269
  const isCamera = cameraOptions?.isCamera;
@@ -14602,6 +15283,7 @@ function createRoot(canvas) {
14602
15283
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14603
15284
  }
14604
15285
  state.set({ camera });
15286
+ tempCamera = camera;
14605
15287
  raycaster.camera = camera;
14606
15288
  }
14607
15289
  if (!state.scene) {
@@ -14619,7 +15301,7 @@ function createRoot(canvas) {
14619
15301
  rootScene: scene,
14620
15302
  internal: { ...prev.internal, container: scene }
14621
15303
  }));
14622
- const camera = state.camera;
15304
+ const camera = tempCamera;
14623
15305
  if (camera && !camera.parent) scene.add(camera);
14624
15306
  }
14625
15307
  if (events && !state.events.handlers) {
@@ -14636,6 +15318,9 @@ function createRoot(canvas) {
14636
15318
  if (_sizeProps !== void 0) {
14637
15319
  state.set({ _sizeProps });
14638
15320
  }
15321
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15322
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15323
+ }
14639
15324
  const size = computeInitialSize(canvas, propsSize);
14640
15325
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14641
15326
  const wasImperative = state._sizeImperative;
@@ -14662,10 +15347,10 @@ function createRoot(canvas) {
14662
15347
  lastConfiguredProps.performance = performance;
14663
15348
  }
14664
15349
  if (!state.xr) {
14665
- const handleXRFrame = (timestamp, frame) => {
15350
+ const handleXRFrame = (timestamp, _frame) => {
14666
15351
  const state2 = store.getState();
14667
15352
  if (state2.frameloop === "never") return;
14668
- advance(timestamp, true);
15353
+ advance(timestamp);
14669
15354
  };
14670
15355
  const actualRenderer = state.internal.actualRenderer;
14671
15356
  const handleSessionChange = () => {
@@ -14677,16 +15362,16 @@ function createRoot(canvas) {
14677
15362
  };
14678
15363
  const xr = {
14679
15364
  connect() {
14680
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14681
- const actualRenderer2 = renderer2 || gl;
14682
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14683
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15365
+ const { gl, renderer: renderer2 } = store.getState();
15366
+ const xrManager = (renderer2 || gl).xr;
15367
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15368
+ xrManager.addEventListener("sessionend", handleSessionChange);
14684
15369
  },
14685
15370
  disconnect() {
14686
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14687
- const actualRenderer2 = renderer2 || gl;
14688
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14689
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15371
+ const { gl, renderer: renderer2 } = store.getState();
15372
+ const xrManager = (renderer2 || gl).xr;
15373
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15374
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14690
15375
  }
14691
15376
  };
14692
15377
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14698,15 +15383,22 @@ function createRoot(canvas) {
14698
15383
  const oldType = renderer.shadowMap.type;
14699
15384
  renderer.shadowMap.enabled = !!shadows;
14700
15385
  if (is.boo(shadows)) {
14701
- renderer.shadowMap.type = PCFSoftShadowMap;
15386
+ renderer.shadowMap.type = PCFShadowMap;
14702
15387
  } else if (is.str(shadows)) {
15388
+ if (shadows === "soft") {
15389
+ notifyDepreciated({
15390
+ heading: 'shadows="soft" is deprecated',
15391
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
15392
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
15393
+ });
15394
+ }
14703
15395
  const types = {
14704
15396
  basic: BasicShadowMap,
14705
15397
  percentage: PCFShadowMap,
14706
- soft: PCFSoftShadowMap,
15398
+ soft: PCFShadowMap,
14707
15399
  variance: VSMShadowMap
14708
15400
  };
14709
- renderer.shadowMap.type = types[shadows] ?? PCFSoftShadowMap;
15401
+ renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
14710
15402
  } else if (is.obj(shadows)) {
14711
15403
  Object.assign(renderer.shadowMap, shadows);
14712
15404
  }
@@ -14714,47 +15406,69 @@ function createRoot(canvas) {
14714
15406
  renderer.shadowMap.needsUpdate = true;
14715
15407
  }
14716
15408
  }
14717
- {
14718
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14719
- const linearChanged = linear !== lastConfiguredProps.linear;
14720
- const flatChanged = flat !== lastConfiguredProps.flat;
14721
- if (legacyChanged) {
14722
- ColorManagement.enabled = !legacy;
14723
- lastConfiguredProps.legacy = legacy;
14724
- }
14725
- if (!configured || linearChanged) {
14726
- renderer.outputColorSpace = linear ? LinearSRGBColorSpace : SRGBColorSpace;
14727
- lastConfiguredProps.linear = linear;
14728
- }
14729
- if (!configured || flatChanged) {
14730
- renderer.toneMapping = flat ? NoToneMapping : ACESFilmicToneMapping;
14731
- lastConfiguredProps.flat = flat;
14732
- }
14733
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14734
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14735
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15409
+ if (!configured) {
15410
+ renderer.outputColorSpace = SRGBColorSpace;
15411
+ renderer.toneMapping = ACESFilmicToneMapping;
14736
15412
  }
14737
15413
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14738
15414
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14739
15415
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14740
15416
  }
15417
+ const r3fProps = ["textureColorSpace"];
15418
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
15419
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14741
15420
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14742
- applyProps(renderer, glConfig);
15421
+ const glProps = {};
15422
+ for (const key in glConfig) {
15423
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
15424
+ }
15425
+ applyProps(renderer, glProps);
14743
15426
  }
14744
15427
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14745
15428
  const currentRenderer = state.renderer;
14746
15429
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14747
- applyProps(currentRenderer, rendererConfig);
15430
+ const rendererProps = {};
15431
+ for (const key in rendererConfig) {
15432
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
15433
+ }
15434
+ applyProps(currentRenderer, rendererProps);
14748
15435
  }
14749
15436
  }
14750
15437
  const scheduler = getScheduler();
14751
15438
  const rootId = state.internal.rootId;
14752
15439
  if (!rootId) {
14753
- const newRootId = scheduler.generateRootId();
15440
+ const newRootId = canvasId || scheduler.generateRootId();
14754
15441
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14755
15442
  getState: () => store.getState(),
14756
15443
  onError: (err) => store.getState().setError(err)
14757
15444
  });
15445
+ const unregisterCanvasTarget = scheduler.register(
15446
+ () => {
15447
+ const state2 = store.getState();
15448
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15449
+ const renderer2 = state2.internal.actualRenderer;
15450
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15451
+ }
15452
+ },
15453
+ {
15454
+ id: `${newRootId}_canvasTarget`,
15455
+ rootId: newRootId,
15456
+ phase: "start",
15457
+ system: true
15458
+ }
15459
+ );
15460
+ const unregisterEventsFlush = scheduler.register(
15461
+ () => {
15462
+ const state2 = store.getState();
15463
+ state2.events.flush?.();
15464
+ },
15465
+ {
15466
+ id: `${newRootId}_events`,
15467
+ rootId: newRootId,
15468
+ phase: "input",
15469
+ system: true
15470
+ }
15471
+ );
14758
15472
  const unregisterFrustum = scheduler.register(
14759
15473
  () => {
14760
15474
  const state2 = store.getState();
@@ -14789,18 +15503,22 @@ function createRoot(canvas) {
14789
15503
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14790
15504
  if (userHandlesRender || state2.internal.priority) return;
14791
15505
  try {
14792
- if (state2.postProcessing?.render) state2.postProcessing.render();
15506
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14793
15507
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14794
15508
  } catch (error) {
14795
15509
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14796
15510
  }
14797
15511
  },
14798
15512
  {
14799
- id: `${newRootId}_render`,
15513
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15514
+ id: canvasId || `${newRootId}_render`,
14800
15515
  rootId: newRootId,
14801
15516
  phase: "render",
14802
- system: true
15517
+ system: true,
14803
15518
  // Internal flag: this is a system job, not user-controlled
15519
+ // Apply scheduler config for render ordering and rate limiting
15520
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15521
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14804
15522
  }
14805
15523
  );
14806
15524
  state.set((state2) => ({
@@ -14809,6 +15527,8 @@ function createRoot(canvas) {
14809
15527
  rootId: newRootId,
14810
15528
  unregisterRoot: () => {
14811
15529
  unregisterRoot();
15530
+ unregisterCanvasTarget();
15531
+ unregisterEventsFlush();
14812
15532
  unregisterFrustum();
14813
15533
  unregisterVisibility();
14814
15534
  unregisterRender();
@@ -14867,15 +15587,24 @@ function unmountComponentAtNode(canvas, callback) {
14867
15587
  const renderer = state.internal.actualRenderer;
14868
15588
  const unregisterRoot = state.internal.unregisterRoot;
14869
15589
  if (unregisterRoot) unregisterRoot();
15590
+ const unregisterPrimary = state.internal.unregisterPrimary;
15591
+ if (unregisterPrimary) unregisterPrimary();
15592
+ const canvasTarget = state.internal.canvasTarget;
15593
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14870
15594
  state.events.disconnect?.();
14871
15595
  cleanupHelperGroup(root.store);
14872
- renderer?.renderLists?.dispose?.();
14873
- renderer?.forceContextLoss?.();
14874
- if (renderer?.xr) state.xr.disconnect();
15596
+ if (state.isLegacy && renderer) {
15597
+ ;
15598
+ renderer.renderLists?.dispose?.();
15599
+ renderer.forceContextLoss?.();
15600
+ }
15601
+ if (!state.internal.isSecondary) {
15602
+ if (renderer?.xr) state.xr.disconnect();
15603
+ }
14875
15604
  dispose(state.scene);
14876
15605
  _roots.delete(canvas);
14877
15606
  if (callback) callback(canvas);
14878
- } catch (e) {
15607
+ } catch {
14879
15608
  }
14880
15609
  }, 500);
14881
15610
  }
@@ -14883,36 +15612,34 @@ function unmountComponentAtNode(canvas, callback) {
14883
15612
  }
14884
15613
  }
14885
15614
  function createPortal(children, container, state) {
14886
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
15615
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14887
15616
  }
14888
- function PortalWrapper({ children, container, state }) {
15617
+ function Portal({ children, container, state }) {
14889
15618
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14890
- const [resolvedContainer, setResolvedContainer] = useState(() => {
15619
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14891
15620
  if (isRef(container)) return container.current ?? null;
14892
15621
  return container;
14893
15622
  });
15623
+ const setResolvedContainer = useCallback(
15624
+ (newContainer) => {
15625
+ if (!newContainer || newContainer === resolvedContainer) return;
15626
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15627
+ },
15628
+ [resolvedContainer, _setResolvedContainer, isRef]
15629
+ );
14894
15630
  useMemo(() => {
14895
- if (isRef(container)) {
14896
- const current = container.current;
14897
- if (!current) {
14898
- queueMicrotask(() => {
14899
- const updated = container.current;
14900
- if (updated && updated !== resolvedContainer) {
14901
- setResolvedContainer(updated);
14902
- }
14903
- });
14904
- } else if (current !== resolvedContainer) {
14905
- setResolvedContainer(current);
14906
- }
14907
- } else if (container !== resolvedContainer) {
14908
- setResolvedContainer(container);
15631
+ if (isRef(container) && !container.current) {
15632
+ return queueMicrotask(() => {
15633
+ setResolvedContainer(container.current);
15634
+ });
14909
15635
  }
14910
- }, [container, resolvedContainer, isRef]);
15636
+ setResolvedContainer(container);
15637
+ }, [container, isRef, setResolvedContainer]);
14911
15638
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14912
15639
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14913
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15640
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14914
15641
  }
14915
- function Portal({ state = {}, children, container }) {
15642
+ function PortalInner({ state = {}, children, container }) {
14916
15643
  const { events, size, injectScene = true, ...rest } = state;
14917
15644
  const previousRoot = useStore();
14918
15645
  const [raycaster] = useState(() => new Raycaster());
@@ -14933,11 +15660,12 @@ function Portal({ state = {}, children, container }) {
14933
15660
  };
14934
15661
  }, [portalScene, container, injectScene]);
14935
15662
  const inject = useMutableCallback((rootState, injectState) => {
15663
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14936
15664
  let viewport = void 0;
14937
- if (injectState.camera && size) {
15665
+ if (injectState.camera && (size || injectState.size)) {
14938
15666
  const camera = injectState.camera;
14939
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14940
- if (camera !== rootState.camera) updateCamera(camera, size);
15667
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
15668
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14941
15669
  }
14942
15670
  return {
14943
15671
  // The intersect consists of the previous root state
@@ -14954,7 +15682,7 @@ function Portal({ state = {}, children, container }) {
14954
15682
  previousRoot,
14955
15683
  // Events, size and viewport can be overridden by the inject layer
14956
15684
  events: { ...rootState.events, ...injectState.events, ...events },
14957
- size: { ...rootState.size, ...size },
15685
+ size: resolvedSize,
14958
15686
  viewport: { ...rootState.viewport, ...viewport },
14959
15687
  // Layers are allowed to override events
14960
15688
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14966,9 +15694,13 @@ function Portal({ state = {}, children, container }) {
14966
15694
  const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
14967
15695
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
14968
15696
  onMutate(previousRoot.getState());
14969
- previousRoot.subscribe(onMutate);
14970
15697
  return store;
14971
15698
  }, [previousRoot, container]);
15699
+ useIsomorphicLayoutEffect(() => {
15700
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15701
+ const unsubscribe = previousRoot.subscribe(onMutate);
15702
+ return unsubscribe;
15703
+ }, [previousRoot, usePortalStore]);
14972
15704
  return (
14973
15705
  // @ts-ignore, reconciler types are not maintained
14974
15706
  /* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
@@ -14988,15 +15720,13 @@ function CanvasImpl({
14988
15720
  fallback,
14989
15721
  resize,
14990
15722
  style,
15723
+ id,
14991
15724
  gl,
14992
- renderer,
15725
+ renderer: rendererProp,
14993
15726
  events = createPointerEvents,
14994
15727
  eventSource,
14995
15728
  eventPrefix,
14996
15729
  shadows,
14997
- linear,
14998
- flat,
14999
- legacy,
15000
15730
  orthographic,
15001
15731
  frameloop,
15002
15732
  dpr,
@@ -15011,10 +15741,53 @@ function CanvasImpl({
15011
15741
  hmr,
15012
15742
  width,
15013
15743
  height,
15744
+ background,
15745
+ forceEven,
15014
15746
  ...props
15015
15747
  }) {
15748
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15749
+ let primaryCanvas;
15750
+ let scheduler;
15751
+ let renderer;
15752
+ if (isRendererConfig) {
15753
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15754
+ primaryCanvas = pc;
15755
+ scheduler = sc;
15756
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
15757
+ } else {
15758
+ renderer = rendererProp;
15759
+ }
15016
15760
  React.useMemo(() => extend(THREE), []);
15017
15761
  const Bridge = useBridge();
15762
+ const backgroundProps = React.useMemo(() => {
15763
+ if (!background) return null;
15764
+ if (typeof background === "object" && !background.isColor) {
15765
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15766
+ return {
15767
+ ...rest,
15768
+ preset,
15769
+ files: envMap || files,
15770
+ backgroundFiles: backgroundMap,
15771
+ background: true
15772
+ };
15773
+ }
15774
+ if (typeof background === "number") {
15775
+ return { color: background, background: true };
15776
+ }
15777
+ if (typeof background === "string") {
15778
+ if (background in presetsObj) {
15779
+ return { preset: background, background: true };
15780
+ }
15781
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15782
+ return { files: background, background: true };
15783
+ }
15784
+ return { color: background, background: true };
15785
+ }
15786
+ if (background.isColor) {
15787
+ return { color: background, background: true };
15788
+ }
15789
+ return null;
15790
+ }, [background]);
15018
15791
  const hasInitialSizeRef = React.useRef(false);
15019
15792
  const measureConfig = React.useMemo(() => {
15020
15793
  if (!hasInitialSizeRef.current) {
@@ -15031,15 +15804,20 @@ function CanvasImpl({
15031
15804
  };
15032
15805
  }, [resize, hasInitialSizeRef.current]);
15033
15806
  const [containerRef, containerRect] = useMeasure(measureConfig);
15034
- const effectiveSize = React.useMemo(
15035
- () => ({
15036
- width: width ?? containerRect.width,
15037
- height: height ?? containerRect.height,
15807
+ const effectiveSize = React.useMemo(() => {
15808
+ let w = width ?? containerRect.width;
15809
+ let h = height ?? containerRect.height;
15810
+ if (forceEven) {
15811
+ w = Math.ceil(w / 2) * 2;
15812
+ h = Math.ceil(h / 2) * 2;
15813
+ }
15814
+ return {
15815
+ width: w,
15816
+ height: h,
15038
15817
  top: containerRect.top,
15039
15818
  left: containerRect.left
15040
- }),
15041
- [width, height, containerRect]
15042
- );
15819
+ };
15820
+ }, [width, height, containerRect, forceEven]);
15043
15821
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15044
15822
  hasInitialSizeRef.current = true;
15045
15823
  }
@@ -15079,14 +15857,14 @@ function CanvasImpl({
15079
15857
  async function run() {
15080
15858
  if (!effectActiveRef.current || !root.current) return;
15081
15859
  await root.current.configure({
15860
+ id,
15861
+ primaryCanvas,
15862
+ scheduler,
15082
15863
  gl,
15083
15864
  renderer,
15084
15865
  scene,
15085
15866
  events,
15086
15867
  shadows,
15087
- linear,
15088
- flat,
15089
- legacy,
15090
15868
  orthographic,
15091
15869
  frameloop,
15092
15870
  dpr,
@@ -15096,6 +15874,7 @@ function CanvasImpl({
15096
15874
  size: effectiveSize,
15097
15875
  // Store size props for reset functionality
15098
15876
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15877
+ forceEven,
15099
15878
  // Pass mutable reference to onPointerMissed so it's free to update
15100
15879
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15101
15880
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15119,7 +15898,10 @@ function CanvasImpl({
15119
15898
  });
15120
15899
  if (!effectActiveRef.current || !root.current) return;
15121
15900
  root.current.render(
15122
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15901
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
15902
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
15903
+ children ?? null
15904
+ ] }) }) })
15123
15905
  );
15124
15906
  }
15125
15907
  run();
@@ -15146,20 +15928,22 @@ function CanvasImpl({
15146
15928
  const canvas = canvasRef.current;
15147
15929
  if (!canvas) return;
15148
15930
  const handleHMR = () => {
15149
- const rootEntry = _roots.get(canvas);
15150
- if (rootEntry?.store) {
15151
- rootEntry.store.setState((state) => ({
15152
- nodes: {},
15153
- uniforms: {},
15154
- _hmrVersion: state._hmrVersion + 1
15155
- }));
15156
- }
15931
+ queueMicrotask(() => {
15932
+ const rootEntry = _roots.get(canvas);
15933
+ if (rootEntry?.store) {
15934
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15935
+ rootEntry.store.setState((state) => ({
15936
+ nodes: {},
15937
+ uniforms: {},
15938
+ _hmrVersion: state._hmrVersion + 1
15939
+ }));
15940
+ }
15941
+ });
15157
15942
  };
15158
15943
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15159
15944
  const hot = import.meta.hot;
15160
15945
  hot.on("vite:afterUpdate", handleHMR);
15161
- return () => hot.dispose?.(() => {
15162
- });
15946
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15163
15947
  }
15164
15948
  if (typeof module !== "undefined" && module.hot) {
15165
15949
  const hot = module.hot;
@@ -15182,7 +15966,16 @@ function CanvasImpl({
15182
15966
  ...style
15183
15967
  },
15184
15968
  ...props,
15185
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15969
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
15970
+ "canvas",
15971
+ {
15972
+ ref: canvasRef,
15973
+ id,
15974
+ className: "r3f-canvas",
15975
+ style: { display: "block", width: "100%", height: "100%" },
15976
+ children: fallback
15977
+ }
15978
+ ) })
15186
15979
  }
15187
15980
  );
15188
15981
  }
@@ -15192,4 +15985,4 @@ function Canvas(props) {
15192
15985
 
15193
15986
  extend(THREE);
15194
15987
 
15195
- export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree };
15988
+ 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 };