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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7,6 +7,12 @@ const jsxRuntime = require('react/jsx-runtime');
7
7
  const React = require('react');
8
8
  const useMeasure = require('react-use-measure');
9
9
  const itsFine = require('its-fine');
10
+ const fiber = require('@react-three/fiber');
11
+ const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
12
+ const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
13
+ const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
14
+ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
15
+ const gainmapJs = require('@monogrid/gainmap-js');
10
16
  const Tb = require('scheduler');
11
17
  const traditional = require('zustand/traditional');
12
18
  const suspendReact = require('suspend-react');
@@ -57,6 +63,389 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
57
63
  WebGLRenderer: three.WebGLRenderer
58
64
  }, [webgpu__namespace]);
59
65
 
66
+ const primaryRegistry = /* @__PURE__ */ new Map();
67
+ const pendingSubscribers = /* @__PURE__ */ new Map();
68
+ function registerPrimary(id, renderer, store) {
69
+ if (primaryRegistry.has(id)) {
70
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
71
+ }
72
+ const entry = { renderer, store };
73
+ primaryRegistry.set(id, entry);
74
+ const subscribers = pendingSubscribers.get(id);
75
+ if (subscribers) {
76
+ subscribers.forEach((callback) => callback(entry));
77
+ pendingSubscribers.delete(id);
78
+ }
79
+ return () => {
80
+ const currentEntry = primaryRegistry.get(id);
81
+ if (currentEntry?.renderer === renderer) {
82
+ primaryRegistry.delete(id);
83
+ }
84
+ };
85
+ }
86
+ function getPrimary(id) {
87
+ return primaryRegistry.get(id);
88
+ }
89
+ function waitForPrimary(id, timeout = 5e3) {
90
+ const existing = primaryRegistry.get(id);
91
+ if (existing) {
92
+ return Promise.resolve(existing);
93
+ }
94
+ return new Promise((resolve, reject) => {
95
+ const timeoutId = setTimeout(() => {
96
+ const subscribers = pendingSubscribers.get(id);
97
+ if (subscribers) {
98
+ const index = subscribers.indexOf(callback);
99
+ if (index !== -1) subscribers.splice(index, 1);
100
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
101
+ }
102
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
103
+ }, timeout);
104
+ const callback = (entry) => {
105
+ clearTimeout(timeoutId);
106
+ resolve(entry);
107
+ };
108
+ if (!pendingSubscribers.has(id)) {
109
+ pendingSubscribers.set(id, []);
110
+ }
111
+ pendingSubscribers.get(id).push(callback);
112
+ });
113
+ }
114
+ function hasPrimary(id) {
115
+ return primaryRegistry.has(id);
116
+ }
117
+ function unregisterPrimary(id) {
118
+ primaryRegistry.delete(id);
119
+ }
120
+ function getPrimaryIds() {
121
+ return Array.from(primaryRegistry.keys());
122
+ }
123
+
124
+ const presetsObj = {
125
+ apartment: "lebombo_1k.hdr",
126
+ city: "potsdamer_platz_1k.hdr",
127
+ dawn: "kiara_1_dawn_1k.hdr",
128
+ forest: "forest_slope_1k.hdr",
129
+ lobby: "st_fagans_interior_1k.hdr",
130
+ night: "dikhololo_night_1k.hdr",
131
+ park: "rooitou_park_1k.hdr",
132
+ studio: "studio_small_03_1k.hdr",
133
+ sunset: "venice_sunset_1k.hdr",
134
+ warehouse: "empty_warehouse_01_1k.hdr"
135
+ };
136
+
137
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
138
+ const isArray = (arr) => Array.isArray(arr);
139
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
140
+ function useEnvironment({
141
+ files = defaultFiles,
142
+ path = "",
143
+ preset = void 0,
144
+ colorSpace = void 0,
145
+ extensions
146
+ } = {}) {
147
+ if (preset) {
148
+ validatePreset(preset);
149
+ files = presetsObj[preset];
150
+ path = CUBEMAP_ROOT;
151
+ }
152
+ const multiFile = isArray(files);
153
+ const { extension, isCubemap } = getExtension(files);
154
+ const loader = getLoader$1(extension);
155
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
156
+ const renderer = fiber.useThree((state) => state.renderer);
157
+ React.useLayoutEffect(() => {
158
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
159
+ function clearGainmapTexture() {
160
+ fiber.useLoader.clear(loader, multiFile ? [files] : files);
161
+ }
162
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
163
+ }, [extension, files, loader, multiFile, renderer.domElement]);
164
+ const loaderResult = fiber.useLoader(
165
+ loader,
166
+ multiFile ? [files] : files,
167
+ (loader2) => {
168
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
169
+ loader2.setRenderer?.(renderer);
170
+ }
171
+ loader2.setPath?.(path);
172
+ if (extensions) extensions(loader2);
173
+ }
174
+ );
175
+ let texture = multiFile ? (
176
+ // @ts-ignore
177
+ loaderResult[0]
178
+ ) : loaderResult;
179
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
180
+ texture = texture.renderTarget?.texture;
181
+ }
182
+ texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
183
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
184
+ return texture;
185
+ }
186
+ const preloadDefaultOptions = {
187
+ files: defaultFiles,
188
+ path: "",
189
+ preset: void 0,
190
+ extensions: void 0
191
+ };
192
+ useEnvironment.preload = (preloadOptions) => {
193
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
194
+ let { files, path = "" } = options;
195
+ const { preset, extensions } = options;
196
+ if (preset) {
197
+ validatePreset(preset);
198
+ files = presetsObj[preset];
199
+ path = CUBEMAP_ROOT;
200
+ }
201
+ const { extension } = getExtension(files);
202
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
203
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
204
+ }
205
+ const loader = getLoader$1(extension);
206
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
207
+ fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
208
+ loader2.setPath?.(path);
209
+ if (extensions) extensions(loader2);
210
+ });
211
+ };
212
+ const clearDefaultOptins = {
213
+ files: defaultFiles,
214
+ preset: void 0
215
+ };
216
+ useEnvironment.clear = (clearOptions) => {
217
+ const options = { ...clearDefaultOptins, ...clearOptions };
218
+ let { files } = options;
219
+ const { preset } = options;
220
+ if (preset) {
221
+ validatePreset(preset);
222
+ files = presetsObj[preset];
223
+ }
224
+ const { extension } = getExtension(files);
225
+ const loader = getLoader$1(extension);
226
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
227
+ fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
228
+ };
229
+ function validatePreset(preset) {
230
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
231
+ }
232
+ function getExtension(files) {
233
+ const isCubemap = isArray(files) && files.length === 6;
234
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
235
+ const firstEntry = isArray(files) ? files[0] : files;
236
+ 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();
237
+ return { extension, isCubemap, isGainmap };
238
+ }
239
+ function getLoader$1(extension) {
240
+ const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
241
+ return loader;
242
+ }
243
+
244
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
245
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
246
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
247
+ sceneProps = {
248
+ backgroundBlurriness: 0,
249
+ backgroundIntensity: 1,
250
+ backgroundRotation: [0, 0, 0],
251
+ environmentIntensity: 1,
252
+ environmentRotation: [0, 0, 0],
253
+ ...sceneProps
254
+ };
255
+ const target = resolveScene(scene || defaultScene);
256
+ const oldbg = target.background;
257
+ const oldenv = target.environment;
258
+ const oldSceneProps = {
259
+ // @ts-ignore
260
+ backgroundBlurriness: target.backgroundBlurriness,
261
+ // @ts-ignore
262
+ backgroundIntensity: target.backgroundIntensity,
263
+ // @ts-ignore
264
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
265
+ // @ts-ignore
266
+ environmentIntensity: target.environmentIntensity,
267
+ // @ts-ignore
268
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
269
+ };
270
+ if (background !== "only") target.environment = texture;
271
+ if (background) target.background = texture;
272
+ fiber.applyProps(target, sceneProps);
273
+ return () => {
274
+ if (background !== "only") target.environment = oldenv;
275
+ if (background) target.background = oldbg;
276
+ fiber.applyProps(target, oldSceneProps);
277
+ };
278
+ }
279
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
280
+ const defaultScene = fiber.useThree((state) => state.scene);
281
+ React__namespace.useLayoutEffect(() => {
282
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
283
+ });
284
+ return null;
285
+ }
286
+ function EnvironmentCube({
287
+ background = false,
288
+ scene,
289
+ blur,
290
+ backgroundBlurriness,
291
+ backgroundIntensity,
292
+ backgroundRotation,
293
+ environmentIntensity,
294
+ environmentRotation,
295
+ ...rest
296
+ }) {
297
+ const texture = useEnvironment(rest);
298
+ const defaultScene = fiber.useThree((state) => state.scene);
299
+ React__namespace.useLayoutEffect(() => {
300
+ return setEnvProps(background, scene, defaultScene, texture, {
301
+ backgroundBlurriness: blur ?? backgroundBlurriness,
302
+ backgroundIntensity,
303
+ backgroundRotation,
304
+ environmentIntensity,
305
+ environmentRotation
306
+ });
307
+ });
308
+ React__namespace.useEffect(() => {
309
+ return () => {
310
+ texture.dispose();
311
+ };
312
+ }, [texture]);
313
+ return null;
314
+ }
315
+ function EnvironmentPortal({
316
+ children,
317
+ near = 0.1,
318
+ far = 1e3,
319
+ resolution = 256,
320
+ frames = 1,
321
+ map,
322
+ background = false,
323
+ blur,
324
+ backgroundBlurriness,
325
+ backgroundIntensity,
326
+ backgroundRotation,
327
+ environmentIntensity,
328
+ environmentRotation,
329
+ scene,
330
+ files,
331
+ path,
332
+ preset = void 0,
333
+ extensions
334
+ }) {
335
+ const gl = fiber.useThree((state) => state.gl);
336
+ const defaultScene = fiber.useThree((state) => state.scene);
337
+ const camera = React__namespace.useRef(null);
338
+ const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
339
+ const fbo = React__namespace.useMemo(() => {
340
+ const fbo2 = new webgpu.WebGLCubeRenderTarget(resolution);
341
+ fbo2.texture.type = webgpu.HalfFloatType;
342
+ return fbo2;
343
+ }, [resolution]);
344
+ React__namespace.useEffect(() => {
345
+ return () => {
346
+ fbo.dispose();
347
+ };
348
+ }, [fbo]);
349
+ React__namespace.useLayoutEffect(() => {
350
+ if (frames === 1) {
351
+ const autoClear = gl.autoClear;
352
+ gl.autoClear = true;
353
+ camera.current.update(gl, virtualScene);
354
+ gl.autoClear = autoClear;
355
+ }
356
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
357
+ backgroundBlurriness: blur ?? backgroundBlurriness,
358
+ backgroundIntensity,
359
+ backgroundRotation,
360
+ environmentIntensity,
361
+ environmentRotation
362
+ });
363
+ }, [
364
+ children,
365
+ virtualScene,
366
+ fbo.texture,
367
+ scene,
368
+ defaultScene,
369
+ background,
370
+ frames,
371
+ gl,
372
+ blur,
373
+ backgroundBlurriness,
374
+ backgroundIntensity,
375
+ backgroundRotation,
376
+ environmentIntensity,
377
+ environmentRotation
378
+ ]);
379
+ let count = 1;
380
+ fiber.useFrame(() => {
381
+ if (frames === Infinity || count < frames) {
382
+ const autoClear = gl.autoClear;
383
+ gl.autoClear = true;
384
+ camera.current.update(gl, virtualScene);
385
+ gl.autoClear = autoClear;
386
+ count++;
387
+ }
388
+ });
389
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
390
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
391
+ children,
392
+ /* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
393
+ files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
394
+ ] }),
395
+ virtualScene
396
+ ) });
397
+ }
398
+ function EnvironmentGround(props) {
399
+ const textureDefault = useEnvironment(props);
400
+ const texture = props.map || textureDefault;
401
+ React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
402
+ React__namespace.useEffect(() => {
403
+ return () => {
404
+ textureDefault.dispose();
405
+ };
406
+ }, [textureDefault]);
407
+ const height = props.ground?.height ?? 15;
408
+ const radius = props.ground?.radius ?? 60;
409
+ const scale = props.ground?.scale ?? 1e3;
410
+ const args = React__namespace.useMemo(
411
+ () => [texture, height, radius],
412
+ [texture, height, radius]
413
+ );
414
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
415
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
416
+ /* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
417
+ ] });
418
+ }
419
+ function EnvironmentColor({ color, scene }) {
420
+ const defaultScene = fiber.useThree((state) => state.scene);
421
+ React__namespace.useLayoutEffect(() => {
422
+ if (color === void 0) return;
423
+ const target = resolveScene(scene || defaultScene);
424
+ const oldBg = target.background;
425
+ target.background = new webgpu.Color(color);
426
+ return () => {
427
+ target.background = oldBg;
428
+ };
429
+ });
430
+ return null;
431
+ }
432
+ function EnvironmentDualSource(props) {
433
+ const { backgroundFiles, ...envProps } = props;
434
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
435
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
436
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
437
+ ] });
438
+ }
439
+ function Environment(props) {
440
+ if (props.color && !props.files && !props.preset && !props.map) {
441
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
442
+ }
443
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
444
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
445
+ }
446
+ return props.ground ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props });
447
+ }
448
+
60
449
  var __defProp$2 = Object.defineProperty;
61
450
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
62
451
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -236,7 +625,8 @@ function prepare(target, root, type, props) {
236
625
  object,
237
626
  eventCount: 0,
238
627
  handlers: {},
239
- isHidden: false
628
+ isHidden: false,
629
+ deferredRefs: []
240
630
  };
241
631
  if (object) object.__r3f = instance;
242
632
  }
@@ -285,7 +675,7 @@ function createOcclusionObserverNode(store, uniform) {
285
675
  let occlusionSetupPromise = null;
286
676
  function enableOcclusion(store) {
287
677
  const state = store.getState();
288
- const { internal, renderer, rootScene } = state;
678
+ const { internal, renderer } = state;
289
679
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
290
680
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
291
681
  if (!hasOcclusionSupport) {
@@ -448,6 +838,22 @@ function hasVisibilityHandlers(handlers) {
448
838
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
449
839
  }
450
840
 
841
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
842
+ function fromRef(ref) {
843
+ return { [FROM_REF]: ref };
844
+ }
845
+ function isFromRef(value) {
846
+ return value !== null && typeof value === "object" && FROM_REF in value;
847
+ }
848
+
849
+ const ONCE = Symbol.for("@react-three/fiber.once");
850
+ function once(...args) {
851
+ return { [ONCE]: args.length ? args : true };
852
+ }
853
+ function isOnce(value) {
854
+ return value !== null && typeof value === "object" && ONCE in value;
855
+ }
856
+
451
857
  const RESERVED_PROPS = [
452
858
  "children",
453
859
  "key",
@@ -518,7 +924,7 @@ function getMemoizedPrototype(root) {
518
924
  ctor = new root.constructor();
519
925
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
520
926
  }
521
- } catch (e) {
927
+ } catch {
522
928
  }
523
929
  return ctor;
524
930
  }
@@ -564,6 +970,25 @@ function applyProps(object, props) {
564
970
  continue;
565
971
  }
566
972
  if (value === void 0) continue;
973
+ if (isFromRef(value)) {
974
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
975
+ continue;
976
+ }
977
+ if (isOnce(value)) {
978
+ if (instance?.appliedOnce?.has(prop)) continue;
979
+ if (instance) {
980
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
981
+ instance.appliedOnce.add(prop);
982
+ }
983
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
984
+ const args = value[ONCE];
985
+ if (typeof targetRoot[targetKey] === "function") {
986
+ targetRoot[targetKey](...args === true ? [] : args);
987
+ } else if (args !== true && args.length > 0) {
988
+ targetRoot[targetKey] = args[0];
989
+ }
990
+ continue;
991
+ }
567
992
  let { root, key, target } = resolve(object, prop);
568
993
  if (target === void 0 && (typeof root !== "object" || root === null)) {
569
994
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -586,7 +1011,10 @@ function applyProps(object, props) {
586
1011
  else target.set(value);
587
1012
  } else {
588
1013
  root[key] = value;
589
- 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
1014
+ if (key.endsWith("Node") && root.isMaterial) {
1015
+ root.needsUpdate = true;
1016
+ }
1017
+ if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
590
1018
  root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
591
1019
  root[key].colorSpace = rootState.textureColorSpace;
592
1020
  }
@@ -619,38 +1047,60 @@ function applyProps(object, props) {
619
1047
  return object;
620
1048
  }
621
1049
 
1050
+ const DEFAULT_POINTER_ID = 0;
1051
+ const XR_POINTER_ID_START = 1e3;
1052
+ function getPointerState(internal, pointerId) {
1053
+ let state = internal.pointerMap.get(pointerId);
1054
+ if (!state) {
1055
+ state = {
1056
+ hovered: /* @__PURE__ */ new Map(),
1057
+ captured: /* @__PURE__ */ new Map(),
1058
+ initialClick: [0, 0],
1059
+ initialHits: []
1060
+ };
1061
+ internal.pointerMap.set(pointerId, state);
1062
+ }
1063
+ return state;
1064
+ }
1065
+ function getPointerId(event) {
1066
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1067
+ }
622
1068
  function makeId(event) {
623
1069
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
624
1070
  }
625
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
626
- const captureData = captures.get(obj);
1071
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1072
+ const pointerState = internal.pointerMap.get(pointerId);
1073
+ if (!pointerState) return;
1074
+ const captureData = pointerState.captured.get(obj);
627
1075
  if (captureData) {
628
- captures.delete(obj);
629
- if (captures.size === 0) {
630
- capturedMap.delete(pointerId);
631
- captureData.target.releasePointerCapture(pointerId);
632
- }
1076
+ pointerState.captured.delete(obj);
1077
+ captureData.target.releasePointerCapture(pointerId);
633
1078
  }
634
1079
  }
635
1080
  function removeInteractivity(store, object) {
636
1081
  const { internal } = store.getState();
637
1082
  internal.interaction = internal.interaction.filter((o) => o !== object);
638
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
639
- internal.hovered.forEach((value, key) => {
640
- if (value.eventObject === object || value.object === object) {
641
- internal.hovered.delete(key);
1083
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1084
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1085
+ pointerState.hovered.forEach((value, key) => {
1086
+ if (value.eventObject === object || value.object === object) {
1087
+ pointerState.hovered.delete(key);
1088
+ }
1089
+ });
1090
+ if (pointerState.captured.has(object)) {
1091
+ releaseInternalPointerCapture(internal, object, pointerId);
642
1092
  }
643
- });
644
- internal.capturedMap.forEach((captures, pointerId) => {
645
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
646
- });
1093
+ }
647
1094
  unregisterVisibility(store, object);
648
1095
  }
649
1096
  function createEvents(store) {
650
- function calculateDistance(event) {
1097
+ function calculateDistance(event, pointerId) {
651
1098
  const { internal } = store.getState();
652
- const dx = event.offsetX - internal.initialClick[0];
653
- const dy = event.offsetY - internal.initialClick[1];
1099
+ const pointerState = internal.pointerMap.get(pointerId);
1100
+ if (!pointerState) return 0;
1101
+ const [initialX, initialY] = pointerState.initialClick;
1102
+ const dx = event.offsetX - initialX;
1103
+ const dy = event.offsetY - initialY;
654
1104
  return Math.round(Math.sqrt(dx * dx + dy * dy));
655
1105
  }
656
1106
  function filterPointerEvents(objects) {
@@ -686,6 +1136,15 @@ function createEvents(store) {
686
1136
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
687
1137
  }
688
1138
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1139
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1140
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1141
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1142
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1143
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1144
+ if (aInteractivePriority !== bInteractivePriority) {
1145
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1146
+ }
1147
+ }
689
1148
  const aState = getRootState(a.object);
690
1149
  const bState = getRootState(b.object);
691
1150
  const aPriority = aState?.events?.priority ?? 1;
@@ -707,9 +1166,13 @@ function createEvents(store) {
707
1166
  eventObject = eventObject.parent;
708
1167
  }
709
1168
  }
710
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
711
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
712
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1169
+ if ("pointerId" in event) {
1170
+ const pointerId = event.pointerId;
1171
+ const pointerState = state.internal.pointerMap.get(pointerId);
1172
+ if (pointerState?.captured.size) {
1173
+ for (const captureData of pointerState.captured.values()) {
1174
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1175
+ }
713
1176
  }
714
1177
  }
715
1178
  return intersections;
@@ -722,27 +1185,25 @@ function createEvents(store) {
722
1185
  if (state) {
723
1186
  const { raycaster, pointer, camera, internal } = state;
724
1187
  const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
725
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1188
+ const hasPointerCapture = (id) => {
1189
+ const pointerState = internal.pointerMap.get(id);
1190
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1191
+ };
726
1192
  const setPointerCapture = (id) => {
727
1193
  const captureData = { intersection: hit, target: event.target };
728
- if (internal.capturedMap.has(id)) {
729
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
730
- } else {
731
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
732
- }
1194
+ const pointerState = getPointerState(internal, id);
1195
+ pointerState.captured.set(hit.eventObject, captureData);
733
1196
  event.target.setPointerCapture(id);
734
1197
  };
735
1198
  const releasePointerCapture = (id) => {
736
- const captures = internal.capturedMap.get(id);
737
- if (captures) {
738
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
739
- }
1199
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
740
1200
  };
741
1201
  const extractEventProps = {};
742
1202
  for (const prop in event) {
743
1203
  const property = event[prop];
744
1204
  if (typeof property !== "function") extractEventProps[prop] = property;
745
1205
  }
1206
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
746
1207
  const raycastEvent = {
747
1208
  ...hit,
748
1209
  ...extractEventProps,
@@ -753,18 +1214,19 @@ function createEvents(store) {
753
1214
  unprojectedPoint,
754
1215
  ray: raycaster.ray,
755
1216
  camera,
1217
+ pointerId: eventPointerId,
756
1218
  // Hijack stopPropagation, which just sets a flag
757
1219
  stopPropagation() {
758
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1220
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
759
1221
  if (
760
1222
  // ...if this pointer hasn't been captured
761
- !capturesForPointer || // ... or if the hit object is capturing the pointer
762
- capturesForPointer.has(hit.eventObject)
1223
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1224
+ pointerState.captured.has(hit.eventObject)
763
1225
  ) {
764
1226
  raycastEvent.stopped = localState.stopped = true;
765
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1227
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
766
1228
  const higher = intersections.slice(0, intersections.indexOf(hit));
767
- cancelPointer([...higher, hit]);
1229
+ cancelPointer([...higher, hit], eventPointerId);
768
1230
  }
769
1231
  }
770
1232
  },
@@ -780,15 +1242,18 @@ function createEvents(store) {
780
1242
  }
781
1243
  return intersections;
782
1244
  }
783
- function cancelPointer(intersections) {
1245
+ function cancelPointer(intersections, pointerId) {
784
1246
  const { internal } = store.getState();
785
- for (const hoveredObj of internal.hovered.values()) {
1247
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1248
+ const pointerState = internal.pointerMap.get(pid);
1249
+ if (!pointerState) return;
1250
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
786
1251
  if (!intersections.length || !intersections.find(
787
1252
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
788
1253
  )) {
789
1254
  const eventObject = hoveredObj.eventObject;
790
1255
  const instance = eventObject.__r3f;
791
- internal.hovered.delete(makeId(hoveredObj));
1256
+ pointerState.hovered.delete(hoveredId);
792
1257
  if (instance?.eventCount) {
793
1258
  const handlers = instance.handlers;
794
1259
  const data = { ...hoveredObj, intersections };
@@ -817,41 +1282,118 @@ function createEvents(store) {
817
1282
  instance?.handlers.onDropMissed?.(event);
818
1283
  }
819
1284
  }
1285
+ function cleanupPointer(pointerId) {
1286
+ const { internal } = store.getState();
1287
+ const pointerState = internal.pointerMap.get(pointerId);
1288
+ if (pointerState) {
1289
+ for (const [, hoveredObj] of pointerState.hovered) {
1290
+ const eventObject = hoveredObj.eventObject;
1291
+ const instance = eventObject.__r3f;
1292
+ if (instance?.eventCount) {
1293
+ const handlers = instance.handlers;
1294
+ const data = { ...hoveredObj, intersections: [] };
1295
+ handlers.onPointerOut?.(data);
1296
+ handlers.onPointerLeave?.(data);
1297
+ }
1298
+ }
1299
+ internal.pointerMap.delete(pointerId);
1300
+ }
1301
+ internal.pointerDirty.delete(pointerId);
1302
+ }
1303
+ function processDeferredPointer(event, pointerId) {
1304
+ const state = store.getState();
1305
+ const { internal } = state;
1306
+ if (!state.events.enabled) return;
1307
+ const filter = filterPointerEvents;
1308
+ const hits = intersect(event, filter);
1309
+ cancelPointer(hits, pointerId);
1310
+ function onIntersect(data) {
1311
+ const eventObject = data.eventObject;
1312
+ const instance = eventObject.__r3f;
1313
+ if (!instance?.eventCount) return;
1314
+ const handlers = instance.handlers;
1315
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1316
+ const id = makeId(data);
1317
+ const pointerState = getPointerState(internal, pointerId);
1318
+ const hoveredItem = pointerState.hovered.get(id);
1319
+ if (!hoveredItem) {
1320
+ pointerState.hovered.set(id, data);
1321
+ handlers.onPointerOver?.(data);
1322
+ handlers.onPointerEnter?.(data);
1323
+ } else if (hoveredItem.stopped) {
1324
+ data.stopPropagation();
1325
+ }
1326
+ }
1327
+ handlers.onPointerMove?.(data);
1328
+ }
1329
+ handleIntersects(hits, event, 0, onIntersect);
1330
+ }
820
1331
  function handlePointer(name) {
821
1332
  switch (name) {
822
1333
  case "onPointerLeave":
823
- case "onPointerCancel":
824
1334
  case "onDragLeave":
825
1335
  return () => cancelPointer([]);
1336
+ // Global cancel of these events
1337
+ case "onPointerCancel":
1338
+ return (event) => {
1339
+ const pointerId = getPointerId(event);
1340
+ cleanupPointer(pointerId);
1341
+ };
826
1342
  case "onLostPointerCapture":
827
1343
  return (event) => {
828
1344
  const { internal } = store.getState();
829
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1345
+ const pointerId = getPointerId(event);
1346
+ const pointerState = internal.pointerMap.get(pointerId);
1347
+ if (pointerState?.captured.size) {
830
1348
  requestAnimationFrame(() => {
831
- if (internal.capturedMap.has(event.pointerId)) {
832
- internal.capturedMap.delete(event.pointerId);
833
- cancelPointer([]);
1349
+ const pointerState2 = internal.pointerMap.get(pointerId);
1350
+ if (pointerState2?.captured.size) {
1351
+ pointerState2.captured.clear();
834
1352
  }
1353
+ cancelPointer([], pointerId);
835
1354
  });
836
1355
  }
837
1356
  };
838
1357
  }
839
1358
  return function handleEvent(event) {
840
1359
  const state = store.getState();
841
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1360
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1361
+ const pointerId = getPointerId(event);
842
1362
  internal.lastEvent.current = event;
843
- if (!state.events.enabled) return;
1363
+ if (!events.enabled) return;
844
1364
  const isPointerMove = name === "onPointerMove";
845
1365
  const isDragOver = name === "onDragOver";
846
1366
  const isDrop = name === "onDrop";
847
1367
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1368
+ const isPointerDown = name === "onPointerDown";
1369
+ const isPointerUp = name === "onPointerUp";
1370
+ const isWheel = name === "onWheel";
1371
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1372
+ if (isPointerMove && canDeferRaycasts) {
1373
+ events.compute?.(event, state);
1374
+ internal.pointerDirty.set(pointerId, event);
1375
+ return;
1376
+ }
1377
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1378
+ events.compute?.(event, state);
1379
+ internal.pointerDirty.set(pointerId, event);
1380
+ return;
1381
+ }
1382
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1383
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1384
+ internal.pointerDirty.delete(pointerId);
1385
+ processDeferredPointer(deferredEvent, pointerId);
1386
+ }
848
1387
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
849
1388
  const hits = intersect(event, filter);
850
- const delta = isClickEvent ? calculateDistance(event) : 0;
851
- if (name === "onPointerDown") {
852
- internal.initialClick = [event.offsetX, event.offsetY];
853
- internal.initialHits = hits.map((hit) => hit.eventObject);
854
- }
1389
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1390
+ if (isPointerDown) {
1391
+ const pointerState2 = getPointerState(internal, pointerId);
1392
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1393
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1394
+ }
1395
+ const pointerState = internal.pointerMap.get(pointerId);
1396
+ const initialHits = pointerState?.initialHits ?? [];
855
1397
  if (isClickEvent && !hits.length) {
856
1398
  if (delta <= 2) {
857
1399
  pointerMissed(event, internal.interaction);
@@ -866,7 +1408,9 @@ function createEvents(store) {
866
1408
  dropMissed(event, internal.interaction);
867
1409
  if (onDropMissed) onDropMissed(event);
868
1410
  }
869
- if (isPointerMove || isDragOver) cancelPointer(hits);
1411
+ if (isPointerMove || isDragOver) {
1412
+ cancelPointer(hits, pointerId);
1413
+ }
870
1414
  function onIntersect(data) {
871
1415
  const eventObject = data.eventObject;
872
1416
  const instance = eventObject.__r3f;
@@ -875,9 +1419,10 @@ function createEvents(store) {
875
1419
  if (isPointerMove) {
876
1420
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
877
1421
  const id = makeId(data);
878
- const hoveredItem = internal.hovered.get(id);
1422
+ const pointerState2 = getPointerState(internal, pointerId);
1423
+ const hoveredItem = pointerState2.hovered.get(id);
879
1424
  if (!hoveredItem) {
880
- internal.hovered.set(id, data);
1425
+ pointerState2.hovered.set(id, data);
881
1426
  handlers.onPointerOver?.(data);
882
1427
  handlers.onPointerEnter?.(data);
883
1428
  } else if (hoveredItem.stopped) {
@@ -887,9 +1432,10 @@ function createEvents(store) {
887
1432
  handlers.onPointerMove?.(data);
888
1433
  } else if (isDragOver) {
889
1434
  const id = makeId(data);
890
- const hoveredItem = internal.hovered.get(id);
1435
+ const pointerState2 = getPointerState(internal, pointerId);
1436
+ const hoveredItem = pointerState2.hovered.get(id);
891
1437
  if (!hoveredItem) {
892
- internal.hovered.set(id, data);
1438
+ pointerState2.hovered.set(id, data);
893
1439
  handlers.onDragOverEnter?.(data);
894
1440
  } else if (hoveredItem.stopped) {
895
1441
  data.stopPropagation();
@@ -900,18 +1446,18 @@ function createEvents(store) {
900
1446
  } else {
901
1447
  const handler = handlers[name];
902
1448
  if (handler) {
903
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1449
+ if (!isClickEvent || initialHits.includes(eventObject)) {
904
1450
  pointerMissed(
905
1451
  event,
906
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1452
+ internal.interaction.filter((object) => !initialHits.includes(object))
907
1453
  );
908
1454
  handler(data);
909
1455
  }
910
1456
  } else {
911
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1457
+ if (isClickEvent && initialHits.includes(eventObject)) {
912
1458
  pointerMissed(
913
1459
  event,
914
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1460
+ internal.interaction.filter((object) => !initialHits.includes(object))
915
1461
  );
916
1462
  }
917
1463
  }
@@ -920,7 +1466,15 @@ function createEvents(store) {
920
1466
  handleIntersects(hits, event, delta, onIntersect);
921
1467
  };
922
1468
  }
923
- return { handlePointer };
1469
+ function flushDeferredPointers() {
1470
+ const { internal, events } = store.getState();
1471
+ if (!events.frameTimedRaycasts) return;
1472
+ for (const [pointerId, event] of internal.pointerDirty) {
1473
+ processDeferredPointer(event, pointerId);
1474
+ }
1475
+ internal.pointerDirty.clear();
1476
+ }
1477
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
924
1478
  }
925
1479
  const DOM_EVENTS = {
926
1480
  onClick: ["click", false],
@@ -939,11 +1493,16 @@ const DOM_EVENTS = {
939
1493
  onLostPointerCapture: ["lostpointercapture", true]
940
1494
  };
941
1495
  function createPointerEvents(store) {
942
- const { handlePointer } = createEvents(store);
1496
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1497
+ let nextXRPointerId = XR_POINTER_ID_START;
1498
+ const xrPointers = /* @__PURE__ */ new Map();
943
1499
  return {
944
1500
  priority: 1,
945
1501
  enabled: true,
946
- compute(event, state, previous) {
1502
+ frameTimedRaycasts: true,
1503
+ alwaysFireOnScroll: true,
1504
+ updateOnFrame: false,
1505
+ compute(event, state) {
947
1506
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
948
1507
  state.raycaster.setFromCamera(state.pointer, state.camera);
949
1508
  },
@@ -952,11 +1511,33 @@ function createPointerEvents(store) {
952
1511
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
953
1512
  {}
954
1513
  ),
955
- update: () => {
1514
+ update: (pointerId) => {
1515
+ const { events, internal } = store.getState();
1516
+ if (!events.handlers) return;
1517
+ if (pointerId !== void 0) {
1518
+ const event = internal.pointerDirty.get(pointerId);
1519
+ if (event) {
1520
+ internal.pointerDirty.delete(pointerId);
1521
+ processDeferredPointer(event, pointerId);
1522
+ } else if (internal.lastEvent?.current) {
1523
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1524
+ }
1525
+ } else {
1526
+ flushDeferredPointers();
1527
+ if (internal.lastEvent?.current) {
1528
+ events.handlers.onPointerMove(internal.lastEvent.current);
1529
+ }
1530
+ }
1531
+ },
1532
+ flush: () => {
956
1533
  const { events, internal } = store.getState();
957
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1534
+ flushDeferredPointers();
1535
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1536
+ events.handlers.onPointerMove(internal.lastEvent.current);
1537
+ }
958
1538
  },
959
1539
  connect: (target) => {
1540
+ if (!target) return;
960
1541
  const { set, events } = store.getState();
961
1542
  events.disconnect?.();
962
1543
  set((state) => ({ events: { ...state.events, connected: target } }));
@@ -980,6 +1561,32 @@ function createPointerEvents(store) {
980
1561
  }
981
1562
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
982
1563
  }
1564
+ },
1565
+ registerPointer: (config) => {
1566
+ const pointerId = nextXRPointerId++;
1567
+ xrPointers.set(pointerId, config);
1568
+ const { internal } = store.getState();
1569
+ getPointerState(internal, pointerId);
1570
+ return pointerId;
1571
+ },
1572
+ unregisterPointer: (pointerId) => {
1573
+ xrPointers.delete(pointerId);
1574
+ const { internal } = store.getState();
1575
+ const pointerState = internal.pointerMap.get(pointerId);
1576
+ if (pointerState) {
1577
+ for (const [, hoveredObj] of pointerState.hovered) {
1578
+ const eventObject = hoveredObj.eventObject;
1579
+ const instance = eventObject.__r3f;
1580
+ if (instance?.eventCount) {
1581
+ const handlers = instance.handlers;
1582
+ const data = { ...hoveredObj, intersections: [] };
1583
+ handlers.onPointerOut?.(data);
1584
+ handlers.onPointerLeave?.(data);
1585
+ }
1586
+ }
1587
+ internal.pointerMap.delete(pointerId);
1588
+ }
1589
+ internal.pointerDirty.delete(pointerId);
983
1590
  }
984
1591
  };
985
1592
  }
@@ -1041,331 +1648,26 @@ function notifyAlpha({ message, link }) {
1041
1648
  }
1042
1649
  }
1043
1650
 
1044
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1045
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
1046
- const createStore = (invalidate, advance) => {
1047
- const rootStore = traditional.createWithEqualityFn((set, get) => {
1048
- const position = new webgpu.Vector3();
1049
- const defaultTarget = new webgpu.Vector3();
1050
- const tempTarget = new webgpu.Vector3();
1051
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1052
- const { width, height, top, left } = size;
1053
- const aspect = width / height;
1054
- if (target.isVector3) tempTarget.copy(target);
1055
- else tempTarget.set(...target);
1056
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1057
- if (isOrthographicCamera(camera)) {
1058
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1059
- } else {
1060
- const fov = camera.fov * Math.PI / 180;
1061
- const h = 2 * Math.tan(fov / 2) * distance;
1062
- const w = h * (width / height);
1063
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1064
- }
1065
- }
1066
- let performanceTimeout = void 0;
1067
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1068
- const pointer = new webgpu.Vector2();
1069
- const rootState = {
1070
- set,
1071
- get,
1072
- // Mock objects that have to be configured
1073
- gl: null,
1074
- renderer: null,
1075
- camera: null,
1076
- frustum: new webgpu.Frustum(),
1077
- autoUpdateFrustum: true,
1078
- raycaster: null,
1079
- events: { priority: 1, enabled: true, connected: false },
1080
- scene: null,
1081
- rootScene: null,
1082
- xr: null,
1083
- inspector: null,
1084
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1085
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1086
- legacy: false,
1087
- linear: false,
1088
- flat: false,
1089
- textureColorSpace: "srgb",
1090
- isLegacy: false,
1091
- webGPUSupported: false,
1092
- isNative: false,
1093
- controls: null,
1094
- pointer,
1095
- mouse: pointer,
1096
- frameloop: "always",
1097
- onPointerMissed: void 0,
1098
- onDragOverMissed: void 0,
1099
- onDropMissed: void 0,
1100
- performance: {
1101
- current: 1,
1102
- min: 0.5,
1103
- max: 1,
1104
- debounce: 200,
1105
- regress: () => {
1106
- const state2 = get();
1107
- if (performanceTimeout) clearTimeout(performanceTimeout);
1108
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1109
- performanceTimeout = setTimeout(
1110
- () => setPerformanceCurrent(get().performance.max),
1111
- state2.performance.debounce
1112
- );
1113
- }
1114
- },
1115
- size: { width: 0, height: 0, top: 0, left: 0 },
1116
- viewport: {
1117
- initialDpr: 0,
1118
- dpr: 0,
1119
- width: 0,
1120
- height: 0,
1121
- top: 0,
1122
- left: 0,
1123
- aspect: 0,
1124
- distance: 0,
1125
- factor: 0,
1126
- getCurrentViewport
1127
- },
1128
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1129
- setSize: (width, height, top, left) => {
1130
- const state2 = get();
1131
- if (width === void 0) {
1132
- set({ _sizeImperative: false });
1133
- if (state2._sizeProps) {
1134
- const { width: propW, height: propH } = state2._sizeProps;
1135
- if (propW !== void 0 || propH !== void 0) {
1136
- const currentSize = state2.size;
1137
- const newSize = {
1138
- width: propW ?? currentSize.width,
1139
- height: propH ?? currentSize.height,
1140
- top: currentSize.top,
1141
- left: currentSize.left
1142
- };
1143
- set((s) => ({
1144
- size: newSize,
1145
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1146
- }));
1147
- }
1148
- }
1149
- return;
1150
- }
1151
- const w = width;
1152
- const h = height ?? width;
1153
- const t = top ?? state2.size.top;
1154
- const l = left ?? state2.size.left;
1155
- const size = { width: w, height: h, top: t, left: l };
1156
- set((s) => ({
1157
- size,
1158
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1159
- _sizeImperative: true
1160
- }));
1161
- },
1162
- setDpr: (dpr) => set((state2) => {
1163
- const resolved = calculateDpr(dpr);
1164
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1165
- }),
1166
- setFrameloop: (frameloop = "always") => {
1167
- set(() => ({ frameloop }));
1168
- },
1169
- setError: (error) => set(() => ({ error })),
1170
- error: null,
1171
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1172
- uniforms: {},
1173
- nodes: {},
1174
- textures: /* @__PURE__ */ new Map(),
1175
- postProcessing: null,
1176
- passes: {},
1177
- _hmrVersion: 0,
1178
- _sizeImperative: false,
1179
- _sizeProps: null,
1180
- previousRoot: void 0,
1181
- internal: {
1182
- // Events
1183
- interaction: [],
1184
- hovered: /* @__PURE__ */ new Map(),
1185
- subscribers: [],
1186
- initialClick: [0, 0],
1187
- initialHits: [],
1188
- capturedMap: /* @__PURE__ */ new Map(),
1189
- lastEvent: React__namespace.createRef(),
1190
- // Visibility tracking (onFramed, onOccluded, onVisible)
1191
- visibilityRegistry: /* @__PURE__ */ new Map(),
1192
- // Occlusion system (WebGPU only)
1193
- occlusionEnabled: false,
1194
- occlusionObserver: null,
1195
- occlusionCache: /* @__PURE__ */ new Map(),
1196
- helperGroup: null,
1197
- // Updates
1198
- active: false,
1199
- frames: 0,
1200
- priority: 0,
1201
- subscribe: (ref, priority, store) => {
1202
- const internal = get().internal;
1203
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1204
- internal.subscribers.push({ ref, priority, store });
1205
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1206
- return () => {
1207
- const internal2 = get().internal;
1208
- if (internal2?.subscribers) {
1209
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1210
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1211
- }
1212
- };
1213
- },
1214
- // Renderer Storage (single source of truth)
1215
- actualRenderer: null,
1216
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1217
- scheduler: null
1218
- }
1219
- };
1220
- return rootState;
1221
- });
1222
- const state = rootStore.getState();
1223
- Object.defineProperty(state, "gl", {
1224
- get() {
1225
- const currentState = rootStore.getState();
1226
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1227
- const stack = new Error().stack || "";
1228
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1229
- if (!isInternalAccess) {
1230
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1231
- notifyDepreciated({
1232
- heading: "Accessing state.gl in WebGPU mode",
1233
- 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
1234
- });
1235
- }
1236
- }
1237
- return currentState.internal.actualRenderer;
1238
- },
1239
- set(value) {
1240
- rootStore.getState().internal.actualRenderer = value;
1241
- },
1242
- enumerable: true,
1243
- configurable: true
1244
- });
1245
- Object.defineProperty(state, "renderer", {
1246
- get() {
1247
- return rootStore.getState().internal.actualRenderer;
1248
- },
1249
- set(value) {
1250
- rootStore.getState().internal.actualRenderer = value;
1251
- },
1252
- enumerable: true,
1253
- configurable: true
1254
- });
1255
- let oldScene = state.scene;
1256
- rootStore.subscribe(() => {
1257
- const currentState = rootStore.getState();
1258
- const { scene, rootScene, set } = currentState;
1259
- if (scene !== oldScene) {
1260
- oldScene = scene;
1261
- if (scene?.isScene && scene !== rootScene) {
1262
- set({ rootScene: scene });
1263
- }
1264
- }
1265
- });
1266
- let oldSize = state.size;
1267
- let oldDpr = state.viewport.dpr;
1268
- let oldCamera = state.camera;
1269
- rootStore.subscribe(() => {
1270
- const { camera, size, viewport, set, internal } = rootStore.getState();
1271
- const actualRenderer = internal.actualRenderer;
1272
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1273
- oldSize = size;
1274
- oldDpr = viewport.dpr;
1275
- updateCamera(camera, size);
1276
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1277
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1278
- actualRenderer.setSize(size.width, size.height, updateStyle);
1279
- }
1280
- if (camera !== oldCamera) {
1281
- oldCamera = camera;
1282
- const { rootScene } = rootStore.getState();
1283
- if (camera && rootScene && !camera.parent) {
1284
- rootScene.add(camera);
1285
- }
1286
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1287
- const currentState = rootStore.getState();
1288
- if (currentState.autoUpdateFrustum && camera) {
1289
- updateFrustum(camera, currentState.frustum);
1290
- }
1291
- }
1292
- });
1293
- rootStore.subscribe((state2) => invalidate(state2));
1294
- return rootStore;
1295
- };
1296
-
1297
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1298
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1299
- function getLoader(Proto) {
1300
- if (isConstructor$1(Proto)) {
1301
- let loader = memoizedLoaders.get(Proto);
1302
- if (!loader) {
1303
- loader = new Proto();
1304
- memoizedLoaders.set(Proto, loader);
1305
- }
1306
- return loader;
1307
- }
1308
- return Proto;
1309
- }
1310
- function loadingFn(extensions, onProgress) {
1311
- return function(Proto, input) {
1312
- const loader = getLoader(Proto);
1313
- if (extensions) extensions(loader);
1314
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1315
- return loader.loadAsync(input, onProgress).then((data) => {
1316
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1317
- return data;
1318
- });
1319
- }
1320
- return new Promise(
1321
- (res, reject) => loader.load(
1322
- input,
1323
- (data) => {
1324
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1325
- res(data);
1326
- },
1327
- onProgress,
1328
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1329
- )
1330
- );
1331
- };
1332
- }
1333
- function useLoader(loader, input, extensions, onProgress) {
1334
- const keys = Array.isArray(input) ? input : [input];
1335
- const fn = loadingFn(extensions, onProgress);
1336
- const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
1337
- return Array.isArray(input) ? results : results[0];
1338
- }
1339
- useLoader.preload = function(loader, input, extensions, onProgress) {
1340
- const keys = Array.isArray(input) ? input : [input];
1341
- keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
1342
- };
1343
- useLoader.clear = function(loader, input) {
1344
- const keys = Array.isArray(input) ? input : [input];
1345
- keys.forEach((key) => suspendReact.clear([loader, key]));
1346
- };
1347
- useLoader.loader = getLoader;
1348
-
1349
- var __defProp$1 = Object.defineProperty;
1350
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1351
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1352
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1353
- class PhaseGraph {
1354
- constructor() {
1355
- /** Ordered list of phase nodes */
1356
- __publicField$1(this, "phases", []);
1357
- /** Quick lookup by name */
1358
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1359
- /** Cached ordered names (invalidated on changes) */
1360
- __publicField$1(this, "orderedNamesCache", null);
1361
- this.initializeDefaultPhases();
1362
- }
1363
- //* Initialization --------------------------------
1364
- initializeDefaultPhases() {
1365
- for (const name of DEFAULT_PHASES) {
1366
- const node = { name, isAutoGenerated: false };
1367
- this.phases.push(node);
1368
- this.phaseMap.set(name, node);
1651
+ var __defProp$1 = Object.defineProperty;
1652
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1653
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1654
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1655
+ class PhaseGraph {
1656
+ constructor() {
1657
+ /** Ordered list of phase nodes */
1658
+ __publicField$1(this, "phases", []);
1659
+ /** Quick lookup by name */
1660
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1661
+ /** Cached ordered names (invalidated on changes) */
1662
+ __publicField$1(this, "orderedNamesCache", null);
1663
+ this.initializeDefaultPhases();
1664
+ }
1665
+ //* Initialization --------------------------------
1666
+ initializeDefaultPhases() {
1667
+ for (const name of DEFAULT_PHASES) {
1668
+ const node = { name, isAutoGenerated: false };
1669
+ this.phases.push(node);
1670
+ this.phaseMap.set(name, node);
1369
1671
  }
1370
1672
  this.invalidateCache();
1371
1673
  }
@@ -1598,7 +1900,7 @@ function shouldRun(job, now) {
1598
1900
  const minInterval = 1e3 / job.fps;
1599
1901
  const lastRun = job.lastRun ?? 0;
1600
1902
  const elapsed = now - lastRun;
1601
- if (elapsed < minInterval) return false;
1903
+ if (elapsed < minInterval - 1) return false;
1602
1904
  if (job.drop) {
1603
1905
  job.lastRun = now;
1604
1906
  } else {
@@ -2267,116 +2569,444 @@ const _Scheduler = class _Scheduler {
2267
2569
  root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2268
2570
  root.needsRebuild = false;
2269
2571
  }
2270
- const providedState = root.getState?.() ?? {};
2271
- const frameState = {
2272
- ...providedState,
2273
- time: timestamp,
2274
- delta,
2275
- elapsed: this.loopState.elapsedTime / 1e3,
2276
- // Convert ms to seconds
2277
- frame: this.loopState.frameCount
2278
- };
2279
- for (const job of root.sortedJobs) {
2280
- if (!shouldRun(job, timestamp)) continue;
2281
- try {
2282
- job.callback(frameState, delta);
2283
- } catch (error) {
2284
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2285
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2572
+ const providedState = root.getState?.() ?? {};
2573
+ const frameState = {
2574
+ ...providedState,
2575
+ time: timestamp,
2576
+ delta,
2577
+ elapsed: this.loopState.elapsedTime / 1e3,
2578
+ // Convert ms to seconds
2579
+ frame: this.loopState.frameCount
2580
+ };
2581
+ for (const job of root.sortedJobs) {
2582
+ if (!shouldRun(job, timestamp)) continue;
2583
+ try {
2584
+ job.callback(frameState, delta);
2585
+ } catch (error) {
2586
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2587
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2588
+ }
2589
+ }
2590
+ }
2591
+ //* Debug & Inspection Methods ================================
2592
+ /**
2593
+ * Get the total number of registered jobs across all roots.
2594
+ * Includes both per-root jobs and global before/after jobs.
2595
+ * @returns {number} Total job count
2596
+ */
2597
+ getJobCount() {
2598
+ let count = 0;
2599
+ for (const root of this.roots.values()) {
2600
+ count += root.jobs.size;
2601
+ }
2602
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2603
+ }
2604
+ /**
2605
+ * Get all registered job IDs across all roots.
2606
+ * Includes both per-root jobs and global before/after jobs.
2607
+ * @returns {string[]} Array of all job IDs
2608
+ */
2609
+ getJobIds() {
2610
+ const ids = [];
2611
+ for (const root of this.roots.values()) {
2612
+ ids.push(...root.jobs.keys());
2613
+ }
2614
+ ids.push(...this.globalBeforeJobs.keys());
2615
+ ids.push(...this.globalAfterJobs.keys());
2616
+ return ids;
2617
+ }
2618
+ /**
2619
+ * Get the number of registered roots (Canvas instances).
2620
+ * @returns {number} Number of registered roots
2621
+ */
2622
+ getRootCount() {
2623
+ return this.roots.size;
2624
+ }
2625
+ /**
2626
+ * Check if any user (non-system) jobs are registered in a specific phase.
2627
+ * Used by the default render job to know if a user has taken over rendering.
2628
+ *
2629
+ * @param phase The phase to check
2630
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2631
+ * @returns true if any user jobs exist in the phase
2632
+ */
2633
+ hasUserJobsInPhase(phase, rootId) {
2634
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2635
+ return rootsToCheck.some((root) => {
2636
+ if (!root) return false;
2637
+ for (const job of root.jobs.values()) {
2638
+ if (job.phase === phase && !job.system && job.enabled) return true;
2639
+ }
2640
+ return false;
2641
+ });
2642
+ }
2643
+ //* Utility Methods ================================
2644
+ /**
2645
+ * Generate a unique root ID for automatic root registration.
2646
+ * @returns {string} A unique root ID in the format 'root_N'
2647
+ */
2648
+ generateRootId() {
2649
+ return `root_${this.nextRootIndex++}`;
2650
+ }
2651
+ /**
2652
+ * Generate a unique job ID.
2653
+ * @returns {string} A unique job ID in the format 'job_N'
2654
+ * @private
2655
+ */
2656
+ generateJobId() {
2657
+ return `job_${this.nextJobIndex}`;
2658
+ }
2659
+ /**
2660
+ * Normalize before/after constraints to a Set.
2661
+ * Handles undefined, single string, or array inputs.
2662
+ * @param {string | string[] | undefined} value - The constraint value(s)
2663
+ * @returns {Set<string>} Normalized Set of constraint strings
2664
+ * @private
2665
+ */
2666
+ normalizeConstraints(value) {
2667
+ if (!value) return /* @__PURE__ */ new Set();
2668
+ if (Array.isArray(value)) return new Set(value);
2669
+ return /* @__PURE__ */ new Set([value]);
2670
+ }
2671
+ };
2672
+ //* Static State & Methods (Singleton Usage) ================================
2673
+ //* Cross-Bundle Singleton Key ==============================
2674
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2675
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2676
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2677
+ let Scheduler = _Scheduler;
2678
+ const getScheduler = () => Scheduler.get();
2679
+ if (hmrData) {
2680
+ hmrData.accept?.();
2681
+ }
2682
+
2683
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2684
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
2685
+ const createStore = (invalidate, advance) => {
2686
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
2687
+ const position = new webgpu.Vector3();
2688
+ const defaultTarget = new webgpu.Vector3();
2689
+ const tempTarget = new webgpu.Vector3();
2690
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2691
+ const { width, height, top, left } = size;
2692
+ const aspect = width / height;
2693
+ if (target.isVector3) tempTarget.copy(target);
2694
+ else tempTarget.set(...target);
2695
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2696
+ if (isOrthographicCamera(camera)) {
2697
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2698
+ } else {
2699
+ const fov = camera.fov * Math.PI / 180;
2700
+ const h = 2 * Math.tan(fov / 2) * distance;
2701
+ const w = h * (width / height);
2702
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2703
+ }
2704
+ }
2705
+ let performanceTimeout = void 0;
2706
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2707
+ const pointer = new webgpu.Vector2();
2708
+ const rootState = {
2709
+ set,
2710
+ get,
2711
+ // Mock objects that have to be configured
2712
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2713
+ primaryStore: null,
2714
+ gl: null,
2715
+ renderer: null,
2716
+ camera: null,
2717
+ frustum: new webgpu.Frustum(),
2718
+ autoUpdateFrustum: true,
2719
+ raycaster: null,
2720
+ events: {
2721
+ priority: 1,
2722
+ enabled: true,
2723
+ connected: false,
2724
+ frameTimedRaycasts: true,
2725
+ alwaysFireOnScroll: true,
2726
+ updateOnFrame: false
2727
+ },
2728
+ scene: null,
2729
+ rootScene: null,
2730
+ xr: null,
2731
+ inspector: null,
2732
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2733
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2734
+ textureColorSpace: webgpu.SRGBColorSpace,
2735
+ isLegacy: false,
2736
+ webGPUSupported: false,
2737
+ isNative: false,
2738
+ controls: null,
2739
+ pointer,
2740
+ mouse: pointer,
2741
+ frameloop: "always",
2742
+ onPointerMissed: void 0,
2743
+ onDragOverMissed: void 0,
2744
+ onDropMissed: void 0,
2745
+ performance: {
2746
+ current: 1,
2747
+ min: 0.5,
2748
+ max: 1,
2749
+ debounce: 200,
2750
+ regress: () => {
2751
+ const state2 = get();
2752
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2753
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2754
+ performanceTimeout = setTimeout(
2755
+ () => setPerformanceCurrent(get().performance.max),
2756
+ state2.performance.debounce
2757
+ );
2758
+ }
2759
+ },
2760
+ size: { width: 0, height: 0, top: 0, left: 0 },
2761
+ viewport: {
2762
+ initialDpr: 0,
2763
+ dpr: 0,
2764
+ width: 0,
2765
+ height: 0,
2766
+ top: 0,
2767
+ left: 0,
2768
+ aspect: 0,
2769
+ distance: 0,
2770
+ factor: 0,
2771
+ getCurrentViewport
2772
+ },
2773
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2774
+ setSize: (width, height, top, left) => {
2775
+ const state2 = get();
2776
+ if (width === void 0) {
2777
+ set({ _sizeImperative: false });
2778
+ if (state2._sizeProps) {
2779
+ const { width: propW, height: propH } = state2._sizeProps;
2780
+ if (propW !== void 0 || propH !== void 0) {
2781
+ const currentSize = state2.size;
2782
+ const newSize = {
2783
+ width: propW ?? currentSize.width,
2784
+ height: propH ?? currentSize.height,
2785
+ top: currentSize.top,
2786
+ left: currentSize.left
2787
+ };
2788
+ set((s) => ({
2789
+ size: newSize,
2790
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2791
+ }));
2792
+ getScheduler().invalidate();
2793
+ }
2794
+ }
2795
+ return;
2796
+ }
2797
+ const w = width;
2798
+ const h = height ?? width;
2799
+ const t = top ?? state2.size.top;
2800
+ const l = left ?? state2.size.left;
2801
+ const size = { width: w, height: h, top: t, left: l };
2802
+ set((s) => ({
2803
+ size,
2804
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2805
+ _sizeImperative: true
2806
+ }));
2807
+ getScheduler().invalidate();
2808
+ },
2809
+ setDpr: (dpr) => set((state2) => {
2810
+ const resolved = calculateDpr(dpr);
2811
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2812
+ }),
2813
+ setFrameloop: (frameloop = "always") => {
2814
+ set(() => ({ frameloop }));
2815
+ },
2816
+ setError: (error) => set(() => ({ error })),
2817
+ error: null,
2818
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2819
+ uniforms: {},
2820
+ nodes: {},
2821
+ buffers: {},
2822
+ gpuStorage: {},
2823
+ textures: /* @__PURE__ */ new Map(),
2824
+ renderPipeline: null,
2825
+ passes: {},
2826
+ _hmrVersion: 0,
2827
+ _sizeImperative: false,
2828
+ _sizeProps: null,
2829
+ previousRoot: void 0,
2830
+ internal: {
2831
+ // Events
2832
+ interaction: [],
2833
+ subscribers: [],
2834
+ // Per-pointer state (new unified structure)
2835
+ pointerMap: /* @__PURE__ */ new Map(),
2836
+ pointerDirty: /* @__PURE__ */ new Map(),
2837
+ lastEvent: React__namespace.createRef(),
2838
+ // Deprecated but kept for backwards compatibility
2839
+ hovered: /* @__PURE__ */ new Map(),
2840
+ initialClick: [0, 0],
2841
+ initialHits: [],
2842
+ capturedMap: /* @__PURE__ */ new Map(),
2843
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2844
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2845
+ // Occlusion system (WebGPU only)
2846
+ occlusionEnabled: false,
2847
+ occlusionObserver: null,
2848
+ occlusionCache: /* @__PURE__ */ new Map(),
2849
+ helperGroup: null,
2850
+ // Updates
2851
+ active: false,
2852
+ frames: 0,
2853
+ priority: 0,
2854
+ subscribe: (ref, priority, store) => {
2855
+ const internal = get().internal;
2856
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2857
+ internal.subscribers.push({ ref, priority, store });
2858
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2859
+ return () => {
2860
+ const internal2 = get().internal;
2861
+ if (internal2?.subscribers) {
2862
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2863
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2864
+ }
2865
+ };
2866
+ },
2867
+ // Renderer Storage (single source of truth)
2868
+ actualRenderer: null,
2869
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2870
+ scheduler: null
2871
+ }
2872
+ };
2873
+ return rootState;
2874
+ });
2875
+ const state = rootStore.getState();
2876
+ Object.defineProperty(state, "gl", {
2877
+ get() {
2878
+ const currentState = rootStore.getState();
2879
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2880
+ const stack = new Error().stack || "";
2881
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2882
+ if (!isInternalAccess) {
2883
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2884
+ notifyDepreciated({
2885
+ heading: "Accessing state.gl in WebGPU mode",
2886
+ 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
2887
+ });
2888
+ }
2889
+ }
2890
+ return currentState.internal.actualRenderer;
2891
+ },
2892
+ set(value) {
2893
+ rootStore.getState().internal.actualRenderer = value;
2894
+ },
2895
+ enumerable: true,
2896
+ configurable: true
2897
+ });
2898
+ Object.defineProperty(state, "renderer", {
2899
+ get() {
2900
+ return rootStore.getState().internal.actualRenderer;
2901
+ },
2902
+ set(value) {
2903
+ rootStore.getState().internal.actualRenderer = value;
2904
+ },
2905
+ enumerable: true,
2906
+ configurable: true
2907
+ });
2908
+ let oldScene = state.scene;
2909
+ rootStore.subscribe(() => {
2910
+ const currentState = rootStore.getState();
2911
+ const { scene, rootScene, set } = currentState;
2912
+ if (scene !== oldScene) {
2913
+ oldScene = scene;
2914
+ if (scene?.isScene && scene !== rootScene) {
2915
+ set({ rootScene: scene });
2916
+ }
2917
+ }
2918
+ });
2919
+ let oldSize = state.size;
2920
+ let oldDpr = state.viewport.dpr;
2921
+ let oldCamera = state.camera;
2922
+ rootStore.subscribe(() => {
2923
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2924
+ const actualRenderer = internal.actualRenderer;
2925
+ const canvasTarget = internal.canvasTarget;
2926
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2927
+ oldSize = size;
2928
+ oldDpr = viewport.dpr;
2929
+ updateCamera(camera, size);
2930
+ if (internal.isSecondary && canvasTarget) {
2931
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2932
+ canvasTarget.setSize(size.width, size.height, false);
2933
+ } else {
2934
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2935
+ actualRenderer.setSize(size.width, size.height, false);
2936
+ if (canvasTarget) {
2937
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2938
+ canvasTarget.setSize(size.width, size.height, false);
2939
+ }
2940
+ }
2941
+ }
2942
+ if (camera !== oldCamera) {
2943
+ oldCamera = camera;
2944
+ const { rootScene } = rootStore.getState();
2945
+ if (camera && rootScene && !camera.parent) {
2946
+ rootScene.add(camera);
2947
+ }
2948
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2949
+ const currentState = rootStore.getState();
2950
+ if (currentState.autoUpdateFrustum && camera) {
2951
+ updateFrustum(camera, currentState.frustum);
2286
2952
  }
2287
2953
  }
2288
- }
2289
- //* Debug & Inspection Methods ================================
2290
- /**
2291
- * Get the total number of registered jobs across all roots.
2292
- * Includes both per-root jobs and global before/after jobs.
2293
- * @returns {number} Total job count
2294
- */
2295
- getJobCount() {
2296
- let count = 0;
2297
- for (const root of this.roots.values()) {
2298
- count += root.jobs.size;
2954
+ });
2955
+ rootStore.subscribe((state2) => invalidate(state2));
2956
+ return rootStore;
2957
+ };
2958
+
2959
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2960
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2961
+ function getLoader(Proto) {
2962
+ if (isConstructor$1(Proto)) {
2963
+ let loader = memoizedLoaders.get(Proto);
2964
+ if (!loader) {
2965
+ loader = new Proto();
2966
+ memoizedLoaders.set(Proto, loader);
2299
2967
  }
2300
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2968
+ return loader;
2301
2969
  }
2302
- /**
2303
- * Get all registered job IDs across all roots.
2304
- * Includes both per-root jobs and global before/after jobs.
2305
- * @returns {string[]} Array of all job IDs
2306
- */
2307
- getJobIds() {
2308
- const ids = [];
2309
- for (const root of this.roots.values()) {
2310
- ids.push(...root.jobs.keys());
2970
+ return Proto;
2971
+ }
2972
+ function loadingFn(extensions, onProgress) {
2973
+ return function(Proto, input) {
2974
+ const loader = getLoader(Proto);
2975
+ if (extensions) extensions(loader);
2976
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2977
+ return loader.loadAsync(input, onProgress).then((data) => {
2978
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2979
+ return data;
2980
+ });
2311
2981
  }
2312
- ids.push(...this.globalBeforeJobs.keys());
2313
- ids.push(...this.globalAfterJobs.keys());
2314
- return ids;
2315
- }
2316
- /**
2317
- * Get the number of registered roots (Canvas instances).
2318
- * @returns {number} Number of registered roots
2319
- */
2320
- getRootCount() {
2321
- return this.roots.size;
2322
- }
2323
- /**
2324
- * Check if any user (non-system) jobs are registered in a specific phase.
2325
- * Used by the default render job to know if a user has taken over rendering.
2326
- *
2327
- * @param phase The phase to check
2328
- * @param rootId Optional root ID to check (checks all roots if not provided)
2329
- * @returns true if any user jobs exist in the phase
2330
- */
2331
- hasUserJobsInPhase(phase, rootId) {
2332
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2333
- return rootsToCheck.some((root) => {
2334
- if (!root) return false;
2335
- for (const job of root.jobs.values()) {
2336
- if (job.phase === phase && !job.system && job.enabled) return true;
2337
- }
2338
- return false;
2339
- });
2340
- }
2341
- //* Utility Methods ================================
2342
- /**
2343
- * Generate a unique root ID for automatic root registration.
2344
- * @returns {string} A unique root ID in the format 'root_N'
2345
- */
2346
- generateRootId() {
2347
- return `root_${this.nextRootIndex++}`;
2348
- }
2349
- /**
2350
- * Generate a unique job ID.
2351
- * @returns {string} A unique job ID in the format 'job_N'
2352
- * @private
2353
- */
2354
- generateJobId() {
2355
- return `job_${this.nextJobIndex}`;
2356
- }
2357
- /**
2358
- * Normalize before/after constraints to a Set.
2359
- * Handles undefined, single string, or array inputs.
2360
- * @param {string | string[] | undefined} value - The constraint value(s)
2361
- * @returns {Set<string>} Normalized Set of constraint strings
2362
- * @private
2363
- */
2364
- normalizeConstraints(value) {
2365
- if (!value) return /* @__PURE__ */ new Set();
2366
- if (Array.isArray(value)) return new Set(value);
2367
- return /* @__PURE__ */ new Set([value]);
2368
- }
2369
- };
2370
- //* Static State & Methods (Singleton Usage) ================================
2371
- //* Cross-Bundle Singleton Key ==============================
2372
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2373
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2374
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2375
- let Scheduler = _Scheduler;
2376
- const getScheduler = () => Scheduler.get();
2377
- if (hmrData) {
2378
- hmrData.accept?.();
2982
+ return new Promise(
2983
+ (res, reject) => loader.load(
2984
+ input,
2985
+ (data) => {
2986
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2987
+ res(data);
2988
+ },
2989
+ onProgress,
2990
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2991
+ )
2992
+ );
2993
+ };
2994
+ }
2995
+ function useLoader(loader, input, extensions, onProgress) {
2996
+ const keys = Array.isArray(input) ? input : [input];
2997
+ const fn = loadingFn(extensions, onProgress);
2998
+ const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
2999
+ return Array.isArray(input) ? results : results[0];
2379
3000
  }
3001
+ useLoader.preload = function(loader, input, extensions, onProgress) {
3002
+ const keys = Array.isArray(input) ? input : [input];
3003
+ keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
3004
+ };
3005
+ useLoader.clear = function(loader, input) {
3006
+ const keys = Array.isArray(input) ? input : [input];
3007
+ keys.forEach((key) => suspendReact.clear([loader, key]));
3008
+ };
3009
+ useLoader.loader = getLoader;
2380
3010
 
2381
3011
  function useFrame(callback, priorityOrOptions) {
2382
3012
  const store = React__namespace.useContext(context);
@@ -2557,6 +3187,9 @@ function useTexture(input, optionsOrOnLoad) {
2557
3187
  const textureCache = useThree((state) => state.textures);
2558
3188
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2559
3189
  const { onLoad, cache = false } = options;
3190
+ const onLoadRef = React.useRef(onLoad);
3191
+ onLoadRef.current = onLoad;
3192
+ const onLoadCalledForRef = React.useRef(null);
2560
3193
  const urls = React.useMemo(() => getUrls(input), [input]);
2561
3194
  const cachedResult = React.useMemo(() => {
2562
3195
  if (!cache) return null;
@@ -2567,9 +3200,13 @@ function useTexture(input, optionsOrOnLoad) {
2567
3200
  webgpu.TextureLoader,
2568
3201
  IsObject(input) ? Object.values(input) : input
2569
3202
  );
3203
+ const inputKey = urls.join("\0");
2570
3204
  React.useLayoutEffect(() => {
2571
- if (!cachedResult) onLoad?.(loadedTextures);
2572
- }, [onLoad, cachedResult, loadedTextures]);
3205
+ if (cachedResult) return;
3206
+ if (onLoadCalledForRef.current === inputKey) return;
3207
+ onLoadCalledForRef.current = inputKey;
3208
+ onLoadRef.current?.(loadedTextures);
3209
+ }, [cachedResult, loadedTextures, inputKey]);
2573
3210
  React.useEffect(() => {
2574
3211
  if (cachedResult) return;
2575
3212
  if ("initTexture" in renderer) {
@@ -2736,16 +3373,33 @@ function useTextures() {
2736
3373
  }, [store]);
2737
3374
  }
2738
3375
 
2739
- function useRenderTarget(width, height, options) {
3376
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2740
3377
  const isLegacy = useThree((s) => s.isLegacy);
2741
3378
  const size = useThree((s) => s.size);
3379
+ let width;
3380
+ let height;
3381
+ let opts;
3382
+ if (typeof widthOrOptions === "object") {
3383
+ opts = widthOrOptions;
3384
+ } else if (typeof widthOrOptions === "number") {
3385
+ width = widthOrOptions;
3386
+ if (typeof heightOrOptions === "object") {
3387
+ height = widthOrOptions;
3388
+ opts = heightOrOptions;
3389
+ } else if (typeof heightOrOptions === "number") {
3390
+ height = heightOrOptions;
3391
+ opts = options;
3392
+ } else {
3393
+ height = widthOrOptions;
3394
+ }
3395
+ }
2742
3396
  return React.useMemo(() => {
2743
3397
  const w = width ?? size.width;
2744
3398
  const h = height ?? size.height;
2745
3399
  {
2746
- return isLegacy ? new three.WebGLRenderTarget(w, h, options) : new webgpu.RenderTarget(w, h, options);
3400
+ return isLegacy ? new three.WebGLRenderTarget(w, h, opts) : new webgpu.RenderTarget(w, h, opts);
2747
3401
  }
2748
- }, [width, height, size.width, size.height, options, isLegacy]);
3402
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2749
3403
  }
2750
3404
 
2751
3405
  function useStore() {
@@ -2795,7 +3449,7 @@ function addTail(callback) {
2795
3449
  function invalidate(state, frames = 1, stackFrames = false) {
2796
3450
  getScheduler().invalidate(frames, stackFrames);
2797
3451
  }
2798
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3452
+ function advance(timestamp) {
2799
3453
  getScheduler().step(timestamp);
2800
3454
  }
2801
3455
 
@@ -14249,6 +14903,7 @@ function swapInstances() {
14249
14903
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14250
14904
  instance.object.__r3f = instance;
14251
14905
  setFiberRef(fiber, instance.object);
14906
+ delete instance.appliedOnce;
14252
14907
  applyProps(instance.object, instance.props);
14253
14908
  if (instance.props.attach) {
14254
14909
  attach(parent, instance);
@@ -14322,8 +14977,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14322
14977
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14323
14978
  if (isTailSibling) swapInstances();
14324
14979
  },
14325
- finalizeInitialChildren: () => false,
14326
- commitMount() {
14980
+ finalizeInitialChildren: (instance) => {
14981
+ for (const prop in instance.props) {
14982
+ if (isFromRef(instance.props[prop])) return true;
14983
+ }
14984
+ return false;
14985
+ },
14986
+ commitMount(instance) {
14987
+ const resolved = {};
14988
+ for (const prop in instance.props) {
14989
+ const value = instance.props[prop];
14990
+ if (isFromRef(value)) {
14991
+ const ref = value[FROM_REF];
14992
+ if (ref.current != null) resolved[prop] = ref.current;
14993
+ }
14994
+ }
14995
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14327
14996
  },
14328
14997
  getPublicInstance: (instance) => instance?.object,
14329
14998
  prepareForCommit: () => null,
@@ -14544,6 +15213,9 @@ function createRoot(canvas) {
14544
15213
  let resolve;
14545
15214
  pending = new Promise((_resolve) => resolve = _resolve);
14546
15215
  const {
15216
+ id: canvasId,
15217
+ primaryCanvas,
15218
+ scheduler: schedulerConfig,
14547
15219
  gl: glConfig,
14548
15220
  renderer: rendererConfig,
14549
15221
  size: propsSize,
@@ -14551,10 +15223,6 @@ function createRoot(canvas) {
14551
15223
  events,
14552
15224
  onCreated: onCreatedCallback,
14553
15225
  shadows = false,
14554
- linear = false,
14555
- flat = false,
14556
- textureColorSpace = webgpu.SRGBColorSpace,
14557
- legacy = false,
14558
15226
  orthographic = false,
14559
15227
  frameloop = "always",
14560
15228
  dpr = [1, 2],
@@ -14566,8 +15234,10 @@ function createRoot(canvas) {
14566
15234
  onDropMissed,
14567
15235
  autoUpdateFrustum = true,
14568
15236
  occlusion = false,
14569
- _sizeProps
15237
+ _sizeProps,
15238
+ forceEven
14570
15239
  } = props;
15240
+ const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || webgpu.SRGBColorSpace;
14571
15241
  const state = store.getState();
14572
15242
  const defaultGLProps = {
14573
15243
  canvas,
@@ -14576,7 +15246,8 @@ function createRoot(canvas) {
14576
15246
  alpha: true
14577
15247
  };
14578
15248
  const defaultGPUProps = {
14579
- canvas
15249
+ canvas,
15250
+ antialias: true
14580
15251
  };
14581
15252
  const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU || !rendererConfig);
14582
15253
  if (glConfig && rendererConfig) {
@@ -14590,19 +15261,61 @@ function createRoot(canvas) {
14590
15261
  });
14591
15262
  }
14592
15263
  let renderer = state.internal.actualRenderer;
15264
+ if (primaryCanvas && wantsGL) {
15265
+ throw new Error(
15266
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15267
+ );
15268
+ }
14593
15269
  if (wantsGL && !state.internal.actualRenderer) {
14594
15270
  renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
14595
15271
  state.internal.actualRenderer = renderer;
14596
- state.set({ isLegacy: true, gl: renderer, renderer });
15272
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
15273
+ } else if (!wantsGL && primaryCanvas && !state.internal.actualRenderer) {
15274
+ const primary = await waitForPrimary(primaryCanvas);
15275
+ renderer = primary.renderer;
15276
+ state.internal.actualRenderer = renderer;
15277
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
15278
+ primary.store.setState((prev) => ({
15279
+ internal: { ...prev.internal, isMultiCanvas: true }
15280
+ }));
15281
+ state.set((prev) => ({
15282
+ webGPUSupported: primary.store.getState().webGPUSupported,
15283
+ renderer,
15284
+ primaryStore: primary.store,
15285
+ internal: {
15286
+ ...prev.internal,
15287
+ canvasTarget,
15288
+ isMultiCanvas: true,
15289
+ isSecondary: true,
15290
+ targetId: primaryCanvas
15291
+ }
15292
+ }));
14597
15293
  } else if (!wantsGL && !state.internal.actualRenderer) {
14598
15294
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
14599
15295
  if (!renderer.hasInitialized?.()) {
15296
+ const size2 = computeInitialSize(canvas, propsSize);
15297
+ if (size2.width > 0 && size2.height > 0) {
15298
+ const pixelRatio = calculateDpr(dpr);
15299
+ canvas.width = size2.width * pixelRatio;
15300
+ canvas.height = size2.height * pixelRatio;
15301
+ }
14600
15302
  await renderer.init();
14601
15303
  }
14602
15304
  const backend = renderer.backend;
14603
15305
  const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
14604
15306
  state.internal.actualRenderer = renderer;
14605
- state.set({ webGPUSupported: isWebGPUBackend, renderer });
15307
+ state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
15308
+ if (canvasId && !state.internal.isSecondary) {
15309
+ const canvasTarget = new webgpu.CanvasTarget(canvas);
15310
+ const unregisterPrimary = registerPrimary(canvasId, renderer, store);
15311
+ state.set((prev) => ({
15312
+ internal: {
15313
+ ...prev.internal,
15314
+ canvasTarget,
15315
+ unregisterPrimary
15316
+ }
15317
+ }));
15318
+ }
14606
15319
  }
14607
15320
  let raycaster = state.raycaster;
14608
15321
  if (!raycaster) state.set({ raycaster: raycaster = new webgpu.Raycaster() });
@@ -14611,6 +15324,7 @@ function createRoot(canvas) {
14611
15324
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14612
15325
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14613
15326
  }
15327
+ let tempCamera = state.camera;
14614
15328
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14615
15329
  lastCamera = cameraOptions;
14616
15330
  const isCamera = cameraOptions?.isCamera;
@@ -14630,6 +15344,7 @@ function createRoot(canvas) {
14630
15344
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14631
15345
  }
14632
15346
  state.set({ camera });
15347
+ tempCamera = camera;
14633
15348
  raycaster.camera = camera;
14634
15349
  }
14635
15350
  if (!state.scene) {
@@ -14647,7 +15362,7 @@ function createRoot(canvas) {
14647
15362
  rootScene: scene,
14648
15363
  internal: { ...prev.internal, container: scene }
14649
15364
  }));
14650
- const camera = state.camera;
15365
+ const camera = tempCamera;
14651
15366
  if (camera && !camera.parent) scene.add(camera);
14652
15367
  }
14653
15368
  if (events && !state.events.handlers) {
@@ -14664,6 +15379,9 @@ function createRoot(canvas) {
14664
15379
  if (_sizeProps !== void 0) {
14665
15380
  state.set({ _sizeProps });
14666
15381
  }
15382
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15383
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15384
+ }
14667
15385
  const size = computeInitialSize(canvas, propsSize);
14668
15386
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14669
15387
  const wasImperative = state._sizeImperative;
@@ -14690,10 +15408,10 @@ function createRoot(canvas) {
14690
15408
  lastConfiguredProps.performance = performance;
14691
15409
  }
14692
15410
  if (!state.xr) {
14693
- const handleXRFrame = (timestamp, frame) => {
15411
+ const handleXRFrame = (timestamp, _frame) => {
14694
15412
  const state2 = store.getState();
14695
15413
  if (state2.frameloop === "never") return;
14696
- advance(timestamp, true);
15414
+ advance(timestamp);
14697
15415
  };
14698
15416
  const actualRenderer = state.internal.actualRenderer;
14699
15417
  const handleSessionChange = () => {
@@ -14705,16 +15423,16 @@ function createRoot(canvas) {
14705
15423
  };
14706
15424
  const xr = {
14707
15425
  connect() {
14708
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14709
- const actualRenderer2 = renderer2 || gl;
14710
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14711
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15426
+ const { gl, renderer: renderer2 } = store.getState();
15427
+ const xrManager = (renderer2 || gl).xr;
15428
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15429
+ xrManager.addEventListener("sessionend", handleSessionChange);
14712
15430
  },
14713
15431
  disconnect() {
14714
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14715
- const actualRenderer2 = renderer2 || gl;
14716
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14717
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15432
+ const { gl, renderer: renderer2 } = store.getState();
15433
+ const xrManager = (renderer2 || gl).xr;
15434
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15435
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14718
15436
  }
14719
15437
  };
14720
15438
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14726,15 +15444,22 @@ function createRoot(canvas) {
14726
15444
  const oldType = renderer.shadowMap.type;
14727
15445
  renderer.shadowMap.enabled = !!shadows;
14728
15446
  if (is.boo(shadows)) {
14729
- renderer.shadowMap.type = webgpu.PCFSoftShadowMap;
15447
+ renderer.shadowMap.type = webgpu.PCFShadowMap;
14730
15448
  } else if (is.str(shadows)) {
15449
+ if (shadows === "soft") {
15450
+ notifyDepreciated({
15451
+ heading: 'shadows="soft" is deprecated',
15452
+ body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
15453
+ link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
15454
+ });
15455
+ }
14731
15456
  const types = {
14732
15457
  basic: webgpu.BasicShadowMap,
14733
15458
  percentage: webgpu.PCFShadowMap,
14734
- soft: webgpu.PCFSoftShadowMap,
15459
+ soft: webgpu.PCFShadowMap,
14735
15460
  variance: webgpu.VSMShadowMap
14736
15461
  };
14737
- renderer.shadowMap.type = types[shadows] ?? webgpu.PCFSoftShadowMap;
15462
+ renderer.shadowMap.type = types[shadows] ?? webgpu.PCFShadowMap;
14738
15463
  } else if (is.obj(shadows)) {
14739
15464
  Object.assign(renderer.shadowMap, shadows);
14740
15465
  }
@@ -14742,56 +15467,69 @@ function createRoot(canvas) {
14742
15467
  renderer.shadowMap.needsUpdate = true;
14743
15468
  }
14744
15469
  }
14745
- {
14746
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14747
- const linearChanged = linear !== lastConfiguredProps.linear;
14748
- const flatChanged = flat !== lastConfiguredProps.flat;
14749
- if (legacyChanged) {
14750
- if (legacy) {
14751
- notifyDepreciated({
14752
- heading: "Legacy Color Management",
14753
- body: "Legacy color management is deprecated and will be removed in a future version.",
14754
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
14755
- });
14756
- }
14757
- }
14758
- if (legacyChanged) {
14759
- webgpu.ColorManagement.enabled = !legacy;
14760
- lastConfiguredProps.legacy = legacy;
14761
- }
14762
- if (!configured || linearChanged) {
14763
- renderer.outputColorSpace = linear ? webgpu.LinearSRGBColorSpace : webgpu.SRGBColorSpace;
14764
- lastConfiguredProps.linear = linear;
14765
- }
14766
- if (!configured || flatChanged) {
14767
- renderer.toneMapping = flat ? webgpu.NoToneMapping : webgpu.ACESFilmicToneMapping;
14768
- lastConfiguredProps.flat = flat;
14769
- }
14770
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14771
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14772
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15470
+ if (!configured) {
15471
+ renderer.outputColorSpace = webgpu.SRGBColorSpace;
15472
+ renderer.toneMapping = webgpu.ACESFilmicToneMapping;
14773
15473
  }
14774
15474
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14775
15475
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14776
15476
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14777
15477
  }
15478
+ const r3fProps = ["textureColorSpace"];
15479
+ const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
15480
+ const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
14778
15481
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14779
- applyProps(renderer, glConfig);
15482
+ const glProps = {};
15483
+ for (const key in glConfig) {
15484
+ if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
15485
+ }
15486
+ applyProps(renderer, glProps);
14780
15487
  }
14781
15488
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14782
15489
  const currentRenderer = state.renderer;
14783
15490
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
14784
- applyProps(currentRenderer, rendererConfig);
15491
+ const rendererProps = {};
15492
+ for (const key in rendererConfig) {
15493
+ if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
15494
+ }
15495
+ applyProps(currentRenderer, rendererProps);
14785
15496
  }
14786
15497
  }
14787
15498
  const scheduler = getScheduler();
14788
15499
  const rootId = state.internal.rootId;
14789
15500
  if (!rootId) {
14790
- const newRootId = scheduler.generateRootId();
15501
+ const newRootId = canvasId || scheduler.generateRootId();
14791
15502
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14792
15503
  getState: () => store.getState(),
14793
15504
  onError: (err) => store.getState().setError(err)
14794
15505
  });
15506
+ const unregisterCanvasTarget = scheduler.register(
15507
+ () => {
15508
+ const state2 = store.getState();
15509
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15510
+ const renderer2 = state2.internal.actualRenderer;
15511
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15512
+ }
15513
+ },
15514
+ {
15515
+ id: `${newRootId}_canvasTarget`,
15516
+ rootId: newRootId,
15517
+ phase: "start",
15518
+ system: true
15519
+ }
15520
+ );
15521
+ const unregisterEventsFlush = scheduler.register(
15522
+ () => {
15523
+ const state2 = store.getState();
15524
+ state2.events.flush?.();
15525
+ },
15526
+ {
15527
+ id: `${newRootId}_events`,
15528
+ rootId: newRootId,
15529
+ phase: "input",
15530
+ system: true
15531
+ }
15532
+ );
14795
15533
  const unregisterFrustum = scheduler.register(
14796
15534
  () => {
14797
15535
  const state2 = store.getState();
@@ -14826,18 +15564,22 @@ function createRoot(canvas) {
14826
15564
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14827
15565
  if (userHandlesRender || state2.internal.priority) return;
14828
15566
  try {
14829
- if (state2.postProcessing?.render) state2.postProcessing.render();
15567
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
14830
15568
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14831
15569
  } catch (error) {
14832
15570
  state2.setError(error instanceof Error ? error : new Error(String(error)));
14833
15571
  }
14834
15572
  },
14835
15573
  {
14836
- id: `${newRootId}_render`,
15574
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15575
+ id: canvasId || `${newRootId}_render`,
14837
15576
  rootId: newRootId,
14838
15577
  phase: "render",
14839
- system: true
15578
+ system: true,
14840
15579
  // Internal flag: this is a system job, not user-controlled
15580
+ // Apply scheduler config for render ordering and rate limiting
15581
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15582
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14841
15583
  }
14842
15584
  );
14843
15585
  state.set((state2) => ({
@@ -14846,6 +15588,8 @@ function createRoot(canvas) {
14846
15588
  rootId: newRootId,
14847
15589
  unregisterRoot: () => {
14848
15590
  unregisterRoot();
15591
+ unregisterCanvasTarget();
15592
+ unregisterEventsFlush();
14849
15593
  unregisterFrustum();
14850
15594
  unregisterVisibility();
14851
15595
  unregisterRender();
@@ -14904,15 +15648,24 @@ function unmountComponentAtNode(canvas, callback) {
14904
15648
  const renderer = state.internal.actualRenderer;
14905
15649
  const unregisterRoot = state.internal.unregisterRoot;
14906
15650
  if (unregisterRoot) unregisterRoot();
15651
+ const unregisterPrimary = state.internal.unregisterPrimary;
15652
+ if (unregisterPrimary) unregisterPrimary();
15653
+ const canvasTarget = state.internal.canvasTarget;
15654
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14907
15655
  state.events.disconnect?.();
14908
15656
  cleanupHelperGroup(root.store);
14909
- renderer?.renderLists?.dispose?.();
14910
- renderer?.forceContextLoss?.();
14911
- if (renderer?.xr) state.xr.disconnect();
15657
+ if (state.isLegacy && renderer) {
15658
+ ;
15659
+ renderer.renderLists?.dispose?.();
15660
+ renderer.forceContextLoss?.();
15661
+ }
15662
+ if (!state.internal.isSecondary) {
15663
+ if (renderer?.xr) state.xr.disconnect();
15664
+ }
14912
15665
  dispose(state.scene);
14913
15666
  _roots.delete(canvas);
14914
15667
  if (callback) callback(canvas);
14915
- } catch (e) {
15668
+ } catch {
14916
15669
  }
14917
15670
  }, 500);
14918
15671
  }
@@ -14920,36 +15673,34 @@ function unmountComponentAtNode(canvas, callback) {
14920
15673
  }
14921
15674
  }
14922
15675
  function createPortal(children, container, state) {
14923
- return /* @__PURE__ */ jsxRuntime.jsx(PortalWrapper, { children, container, state });
15676
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
14924
15677
  }
14925
- function PortalWrapper({ children, container, state }) {
15678
+ function Portal({ children, container, state }) {
14926
15679
  const isRef = React.useCallback((obj) => obj && "current" in obj, []);
14927
- const [resolvedContainer, setResolvedContainer] = React.useState(() => {
15680
+ const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
14928
15681
  if (isRef(container)) return container.current ?? null;
14929
15682
  return container;
14930
15683
  });
15684
+ const setResolvedContainer = React.useCallback(
15685
+ (newContainer) => {
15686
+ if (!newContainer || newContainer === resolvedContainer) return;
15687
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15688
+ },
15689
+ [resolvedContainer, _setResolvedContainer, isRef]
15690
+ );
14931
15691
  React.useMemo(() => {
14932
- if (isRef(container)) {
14933
- const current = container.current;
14934
- if (!current) {
14935
- queueMicrotask(() => {
14936
- const updated = container.current;
14937
- if (updated && updated !== resolvedContainer) {
14938
- setResolvedContainer(updated);
14939
- }
14940
- });
14941
- } else if (current !== resolvedContainer) {
14942
- setResolvedContainer(current);
14943
- }
14944
- } else if (container !== resolvedContainer) {
14945
- setResolvedContainer(container);
15692
+ if (isRef(container) && !container.current) {
15693
+ return queueMicrotask(() => {
15694
+ setResolvedContainer(container.current);
15695
+ });
14946
15696
  }
14947
- }, [container, resolvedContainer, isRef]);
15697
+ setResolvedContainer(container);
15698
+ }, [container, isRef, setResolvedContainer]);
14948
15699
  if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
14949
15700
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14950
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15701
+ return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14951
15702
  }
14952
- function Portal({ state = {}, children, container }) {
15703
+ function PortalInner({ state = {}, children, container }) {
14953
15704
  const { events, size, injectScene = true, ...rest } = state;
14954
15705
  const previousRoot = useStore();
14955
15706
  const [raycaster] = React.useState(() => new webgpu.Raycaster());
@@ -14970,11 +15721,12 @@ function Portal({ state = {}, children, container }) {
14970
15721
  };
14971
15722
  }, [portalScene, container, injectScene]);
14972
15723
  const inject = useMutableCallback((rootState, injectState) => {
15724
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14973
15725
  let viewport = void 0;
14974
- if (injectState.camera && size) {
15726
+ if (injectState.camera && (size || injectState.size)) {
14975
15727
  const camera = injectState.camera;
14976
- viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), size);
14977
- if (camera !== rootState.camera) updateCamera(camera, size);
15728
+ viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
15729
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14978
15730
  }
14979
15731
  return {
14980
15732
  // The intersect consists of the previous root state
@@ -14991,7 +15743,7 @@ function Portal({ state = {}, children, container }) {
14991
15743
  previousRoot,
14992
15744
  // Events, size and viewport can be overridden by the inject layer
14993
15745
  events: { ...rootState.events, ...injectState.events, ...events },
14994
- size: { ...rootState.size, ...size },
15746
+ size: resolvedSize,
14995
15747
  viewport: { ...rootState.viewport, ...viewport },
14996
15748
  // Layers are allowed to override events
14997
15749
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -15003,9 +15755,13 @@ function Portal({ state = {}, children, container }) {
15003
15755
  const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
15004
15756
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
15005
15757
  onMutate(previousRoot.getState());
15006
- previousRoot.subscribe(onMutate);
15007
15758
  return store;
15008
15759
  }, [previousRoot, container]);
15760
+ useIsomorphicLayoutEffect(() => {
15761
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15762
+ const unsubscribe = previousRoot.subscribe(onMutate);
15763
+ return unsubscribe;
15764
+ }, [previousRoot, usePortalStore]);
15009
15765
  return (
15010
15766
  // @ts-ignore, reconciler types are not maintained
15011
15767
  /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
@@ -15025,15 +15781,13 @@ function CanvasImpl({
15025
15781
  fallback,
15026
15782
  resize,
15027
15783
  style,
15784
+ id,
15028
15785
  gl,
15029
- renderer,
15786
+ renderer: rendererProp,
15030
15787
  events = createPointerEvents,
15031
15788
  eventSource,
15032
15789
  eventPrefix,
15033
15790
  shadows,
15034
- linear,
15035
- flat,
15036
- legacy,
15037
15791
  orthographic,
15038
15792
  frameloop,
15039
15793
  dpr,
@@ -15048,10 +15802,53 @@ function CanvasImpl({
15048
15802
  hmr,
15049
15803
  width,
15050
15804
  height,
15805
+ background,
15806
+ forceEven,
15051
15807
  ...props
15052
15808
  }) {
15809
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15810
+ let primaryCanvas;
15811
+ let scheduler;
15812
+ let renderer;
15813
+ if (isRendererConfig) {
15814
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15815
+ primaryCanvas = pc;
15816
+ scheduler = sc;
15817
+ renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
15818
+ } else {
15819
+ renderer = rendererProp;
15820
+ }
15053
15821
  React__namespace.useMemo(() => extend(THREE), []);
15054
15822
  const Bridge = useBridge();
15823
+ const backgroundProps = React__namespace.useMemo(() => {
15824
+ if (!background) return null;
15825
+ if (typeof background === "object" && !background.isColor) {
15826
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15827
+ return {
15828
+ ...rest,
15829
+ preset,
15830
+ files: envMap || files,
15831
+ backgroundFiles: backgroundMap,
15832
+ background: true
15833
+ };
15834
+ }
15835
+ if (typeof background === "number") {
15836
+ return { color: background, background: true };
15837
+ }
15838
+ if (typeof background === "string") {
15839
+ if (background in presetsObj) {
15840
+ return { preset: background, background: true };
15841
+ }
15842
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15843
+ return { files: background, background: true };
15844
+ }
15845
+ return { color: background, background: true };
15846
+ }
15847
+ if (background.isColor) {
15848
+ return { color: background, background: true };
15849
+ }
15850
+ return null;
15851
+ }, [background]);
15055
15852
  const hasInitialSizeRef = React__namespace.useRef(false);
15056
15853
  const measureConfig = React__namespace.useMemo(() => {
15057
15854
  if (!hasInitialSizeRef.current) {
@@ -15068,15 +15865,20 @@ function CanvasImpl({
15068
15865
  };
15069
15866
  }, [resize, hasInitialSizeRef.current]);
15070
15867
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15071
- const effectiveSize = React__namespace.useMemo(
15072
- () => ({
15073
- width: width ?? containerRect.width,
15074
- height: height ?? containerRect.height,
15868
+ const effectiveSize = React__namespace.useMemo(() => {
15869
+ let w = width ?? containerRect.width;
15870
+ let h = height ?? containerRect.height;
15871
+ if (forceEven) {
15872
+ w = Math.ceil(w / 2) * 2;
15873
+ h = Math.ceil(h / 2) * 2;
15874
+ }
15875
+ return {
15876
+ width: w,
15877
+ height: h,
15075
15878
  top: containerRect.top,
15076
15879
  left: containerRect.left
15077
- }),
15078
- [width, height, containerRect]
15079
- );
15880
+ };
15881
+ }, [width, height, containerRect, forceEven]);
15080
15882
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15081
15883
  hasInitialSizeRef.current = true;
15082
15884
  }
@@ -15116,14 +15918,14 @@ function CanvasImpl({
15116
15918
  async function run() {
15117
15919
  if (!effectActiveRef.current || !root.current) return;
15118
15920
  await root.current.configure({
15921
+ id,
15922
+ primaryCanvas,
15923
+ scheduler,
15119
15924
  gl,
15120
15925
  renderer,
15121
15926
  scene,
15122
15927
  events,
15123
15928
  shadows,
15124
- linear,
15125
- flat,
15126
- legacy,
15127
15929
  orthographic,
15128
15930
  frameloop,
15129
15931
  dpr,
@@ -15133,6 +15935,7 @@ function CanvasImpl({
15133
15935
  size: effectiveSize,
15134
15936
  // Store size props for reset functionality
15135
15937
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15938
+ forceEven,
15136
15939
  // Pass mutable reference to onPointerMissed so it's free to update
15137
15940
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15138
15941
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15156,7 +15959,10 @@ function CanvasImpl({
15156
15959
  });
15157
15960
  if (!effectActiveRef.current || !root.current) return;
15158
15961
  root.current.render(
15159
- /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15962
+ /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
15963
+ backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
15964
+ children ?? null
15965
+ ] }) }) })
15160
15966
  );
15161
15967
  }
15162
15968
  run();
@@ -15183,20 +15989,22 @@ function CanvasImpl({
15183
15989
  const canvas = canvasRef.current;
15184
15990
  if (!canvas) return;
15185
15991
  const handleHMR = () => {
15186
- const rootEntry = _roots.get(canvas);
15187
- if (rootEntry?.store) {
15188
- rootEntry.store.setState((state) => ({
15189
- nodes: {},
15190
- uniforms: {},
15191
- _hmrVersion: state._hmrVersion + 1
15192
- }));
15193
- }
15992
+ queueMicrotask(() => {
15993
+ const rootEntry = _roots.get(canvas);
15994
+ if (rootEntry?.store) {
15995
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15996
+ rootEntry.store.setState((state) => ({
15997
+ nodes: {},
15998
+ uniforms: {},
15999
+ _hmrVersion: state._hmrVersion + 1
16000
+ }));
16001
+ }
16002
+ });
15194
16003
  };
15195
16004
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15196
16005
  const hot = undefined;
15197
16006
  hot.on("vite:afterUpdate", handleHMR);
15198
- return () => hot.dispose?.(() => {
15199
- });
16007
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15200
16008
  }
15201
16009
  if (typeof module !== "undefined" && module.hot) {
15202
16010
  const hot = module.hot;
@@ -15219,7 +16027,16 @@ function CanvasImpl({
15219
16027
  ...style
15220
16028
  },
15221
16029
  ...props,
15222
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
16030
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
16031
+ "canvas",
16032
+ {
16033
+ ref: canvasRef,
16034
+ id,
16035
+ className: "r3f-canvas",
16036
+ style: { display: "block", width: "100%", height: "100%" },
16037
+ children: fallback
16038
+ }
16039
+ ) })
15223
16040
  }
15224
16041
  );
15225
16042
  }
@@ -15231,8 +16048,15 @@ extend(THREE);
15231
16048
 
15232
16049
  exports.Block = Block;
15233
16050
  exports.Canvas = Canvas;
16051
+ exports.Environment = Environment;
16052
+ exports.EnvironmentCube = EnvironmentCube;
16053
+ exports.EnvironmentMap = EnvironmentMap;
16054
+ exports.EnvironmentPortal = EnvironmentPortal;
15234
16055
  exports.ErrorBoundary = ErrorBoundary;
16056
+ exports.FROM_REF = FROM_REF;
15235
16057
  exports.IsObject = IsObject;
16058
+ exports.ONCE = ONCE;
16059
+ exports.Portal = Portal;
15236
16060
  exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
15237
16061
  exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
15238
16062
  exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
@@ -15262,30 +16086,41 @@ exports.events = createPointerEvents;
15262
16086
  exports.extend = extend;
15263
16087
  exports.findInitialRoot = findInitialRoot;
15264
16088
  exports.flushSync = flushSync;
16089
+ exports.fromRef = fromRef;
15265
16090
  exports.getInstanceProps = getInstanceProps;
16091
+ exports.getPrimary = getPrimary;
16092
+ exports.getPrimaryIds = getPrimaryIds;
15266
16093
  exports.getRootState = getRootState;
15267
16094
  exports.getScheduler = getScheduler;
15268
16095
  exports.getUuidPrefix = getUuidPrefix;
15269
16096
  exports.hasConstructor = hasConstructor;
16097
+ exports.hasPrimary = hasPrimary;
15270
16098
  exports.invalidate = invalidate;
15271
16099
  exports.invalidateInstance = invalidateInstance;
15272
16100
  exports.is = is;
15273
16101
  exports.isColorRepresentation = isColorRepresentation;
15274
16102
  exports.isCopyable = isCopyable;
16103
+ exports.isFromRef = isFromRef;
15275
16104
  exports.isObject3D = isObject3D;
16105
+ exports.isOnce = isOnce;
15276
16106
  exports.isOrthographicCamera = isOrthographicCamera;
15277
16107
  exports.isRef = isRef;
15278
16108
  exports.isRenderer = isRenderer;
15279
16109
  exports.isTexture = isTexture;
15280
16110
  exports.isVectorLike = isVectorLike;
16111
+ exports.once = once;
15281
16112
  exports.prepare = prepare;
16113
+ exports.presetsObj = presetsObj;
15282
16114
  exports.reconciler = reconciler;
16115
+ exports.registerPrimary = registerPrimary;
15283
16116
  exports.removeInteractivity = removeInteractivity;
15284
16117
  exports.resolve = resolve;
15285
16118
  exports.unmountComponentAtNode = unmountComponentAtNode;
16119
+ exports.unregisterPrimary = unregisterPrimary;
15286
16120
  exports.updateCamera = updateCamera;
15287
16121
  exports.updateFrustum = updateFrustum;
15288
16122
  exports.useBridge = useBridge;
16123
+ exports.useEnvironment = useEnvironment;
15289
16124
  exports.useFrame = useFrame;
15290
16125
  exports.useGraph = useGraph;
15291
16126
  exports.useInstanceHandle = useInstanceHandle;
@@ -15297,3 +16132,4 @@ exports.useStore = useStore;
15297
16132
  exports.useTexture = useTexture;
15298
16133
  exports.useTextures = useTextures;
15299
16134
  exports.useThree = useThree;
16135
+ exports.waitForPrimary = waitForPrimary;