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

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