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