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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.cjs CHANGED
@@ -5,6 +5,12 @@ const jsxRuntime = require('react/jsx-runtime');
5
5
  const React = require('react');
6
6
  const useMeasure = require('react-use-measure');
7
7
  const itsFine = require('its-fine');
8
+ const fiber = require('@react-three/fiber');
9
+ const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
10
+ const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
11
+ const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
12
+ const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
13
+ const gainmapJs = require('@monogrid/gainmap-js');
8
14
  const Tb = require('scheduler');
9
15
  const traditional = require('zustand/traditional');
10
16
  const suspendReact = require('suspend-react');
@@ -66,6 +72,374 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
66
72
  WebGPURenderer: WebGPURenderer
67
73
  }, [three__namespace]);
68
74
 
75
+ const primaryRegistry = /* @__PURE__ */ new Map();
76
+ const pendingSubscribers = /* @__PURE__ */ new Map();
77
+ function registerPrimary(id, renderer, store) {
78
+ if (primaryRegistry.has(id)) {
79
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
80
+ }
81
+ const entry = { renderer, store };
82
+ primaryRegistry.set(id, entry);
83
+ const subscribers = pendingSubscribers.get(id);
84
+ if (subscribers) {
85
+ subscribers.forEach((callback) => callback(entry));
86
+ pendingSubscribers.delete(id);
87
+ }
88
+ return () => {
89
+ const currentEntry = primaryRegistry.get(id);
90
+ if (currentEntry?.renderer === renderer) {
91
+ primaryRegistry.delete(id);
92
+ }
93
+ };
94
+ }
95
+ function getPrimary(id) {
96
+ return primaryRegistry.get(id);
97
+ }
98
+ function waitForPrimary(id, timeout = 5e3) {
99
+ const existing = primaryRegistry.get(id);
100
+ if (existing) {
101
+ return Promise.resolve(existing);
102
+ }
103
+ return new Promise((resolve, reject) => {
104
+ const timeoutId = setTimeout(() => {
105
+ const subscribers = pendingSubscribers.get(id);
106
+ if (subscribers) {
107
+ const index = subscribers.indexOf(callback);
108
+ if (index !== -1) subscribers.splice(index, 1);
109
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
110
+ }
111
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
112
+ }, timeout);
113
+ const callback = (entry) => {
114
+ clearTimeout(timeoutId);
115
+ resolve(entry);
116
+ };
117
+ if (!pendingSubscribers.has(id)) {
118
+ pendingSubscribers.set(id, []);
119
+ }
120
+ pendingSubscribers.get(id).push(callback);
121
+ });
122
+ }
123
+ function hasPrimary(id) {
124
+ return primaryRegistry.has(id);
125
+ }
126
+ function unregisterPrimary(id) {
127
+ primaryRegistry.delete(id);
128
+ }
129
+ function getPrimaryIds() {
130
+ return Array.from(primaryRegistry.keys());
131
+ }
132
+
133
+ const presetsObj = {
134
+ apartment: "lebombo_1k.hdr",
135
+ city: "potsdamer_platz_1k.hdr",
136
+ dawn: "kiara_1_dawn_1k.hdr",
137
+ forest: "forest_slope_1k.hdr",
138
+ lobby: "st_fagans_interior_1k.hdr",
139
+ night: "dikhololo_night_1k.hdr",
140
+ park: "rooitou_park_1k.hdr",
141
+ studio: "studio_small_03_1k.hdr",
142
+ sunset: "venice_sunset_1k.hdr",
143
+ warehouse: "empty_warehouse_01_1k.hdr"
144
+ };
145
+
146
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
147
+ const isArray = (arr) => Array.isArray(arr);
148
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
149
+ function useEnvironment({
150
+ files = defaultFiles,
151
+ path = "",
152
+ preset = void 0,
153
+ colorSpace = void 0,
154
+ extensions
155
+ } = {}) {
156
+ if (preset) {
157
+ validatePreset(preset);
158
+ files = presetsObj[preset];
159
+ path = CUBEMAP_ROOT;
160
+ }
161
+ const multiFile = isArray(files);
162
+ const { extension, isCubemap } = getExtension(files);
163
+ const loader = getLoader$1(extension);
164
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
165
+ const renderer = fiber.useThree((state) => state.renderer);
166
+ React.useLayoutEffect(() => {
167
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
168
+ function clearGainmapTexture() {
169
+ fiber.useLoader.clear(loader, multiFile ? [files] : files);
170
+ }
171
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
172
+ }, [files, renderer.domElement]);
173
+ const loaderResult = fiber.useLoader(
174
+ loader,
175
+ multiFile ? [files] : files,
176
+ (loader2) => {
177
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
178
+ loader2.setRenderer?.(renderer);
179
+ }
180
+ loader2.setPath?.(path);
181
+ if (extensions) extensions(loader2);
182
+ }
183
+ );
184
+ let texture = multiFile ? (
185
+ // @ts-ignore
186
+ loaderResult[0]
187
+ ) : loaderResult;
188
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
189
+ texture = texture.renderTarget?.texture;
190
+ }
191
+ texture.mapping = isCubemap ? three.CubeReflectionMapping : three.EquirectangularReflectionMapping;
192
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
193
+ return texture;
194
+ }
195
+ const preloadDefaultOptions = {
196
+ files: defaultFiles,
197
+ path: "",
198
+ preset: void 0,
199
+ extensions: void 0
200
+ };
201
+ useEnvironment.preload = (preloadOptions) => {
202
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
203
+ let { files, path = "" } = options;
204
+ const { preset, extensions } = options;
205
+ if (preset) {
206
+ validatePreset(preset);
207
+ files = presetsObj[preset];
208
+ path = CUBEMAP_ROOT;
209
+ }
210
+ const { extension } = getExtension(files);
211
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
212
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
213
+ }
214
+ const loader = getLoader$1(extension);
215
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
216
+ fiber.useLoader.preload(loader, isArray(files) ? [files] : files, (loader2) => {
217
+ loader2.setPath?.(path);
218
+ if (extensions) extensions(loader2);
219
+ });
220
+ };
221
+ const clearDefaultOptins = {
222
+ files: defaultFiles,
223
+ preset: void 0
224
+ };
225
+ useEnvironment.clear = (clearOptions) => {
226
+ const options = { ...clearDefaultOptins, ...clearOptions };
227
+ let { files } = options;
228
+ const { preset } = options;
229
+ if (preset) {
230
+ validatePreset(preset);
231
+ files = presetsObj[preset];
232
+ }
233
+ const { extension } = getExtension(files);
234
+ const loader = getLoader$1(extension);
235
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
236
+ fiber.useLoader.clear(loader, isArray(files) ? [files] : files);
237
+ };
238
+ function validatePreset(preset) {
239
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
240
+ }
241
+ function getExtension(files) {
242
+ const isCubemap = isArray(files) && files.length === 6;
243
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
244
+ const firstEntry = isArray(files) ? files[0] : files;
245
+ 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();
246
+ return { extension, isCubemap, isGainmap };
247
+ }
248
+ function getLoader$1(extension) {
249
+ const loader = extension === "cube" ? three.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
250
+ return loader;
251
+ }
252
+
253
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
254
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
255
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
256
+ sceneProps = {
257
+ backgroundBlurriness: 0,
258
+ backgroundIntensity: 1,
259
+ backgroundRotation: [0, 0, 0],
260
+ environmentIntensity: 1,
261
+ environmentRotation: [0, 0, 0],
262
+ ...sceneProps
263
+ };
264
+ const target = resolveScene(scene || defaultScene);
265
+ const oldbg = target.background;
266
+ const oldenv = target.environment;
267
+ const oldSceneProps = {
268
+ // @ts-ignore
269
+ backgroundBlurriness: target.backgroundBlurriness,
270
+ // @ts-ignore
271
+ backgroundIntensity: target.backgroundIntensity,
272
+ // @ts-ignore
273
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
274
+ // @ts-ignore
275
+ environmentIntensity: target.environmentIntensity,
276
+ // @ts-ignore
277
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
278
+ };
279
+ if (background !== "only") target.environment = texture;
280
+ if (background) target.background = texture;
281
+ fiber.applyProps(target, sceneProps);
282
+ return () => {
283
+ if (background !== "only") target.environment = oldenv;
284
+ if (background) target.background = oldbg;
285
+ fiber.applyProps(target, oldSceneProps);
286
+ };
287
+ }
288
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
289
+ const defaultScene = fiber.useThree((state) => state.scene);
290
+ React__namespace.useLayoutEffect(() => {
291
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
292
+ });
293
+ return null;
294
+ }
295
+ function EnvironmentCube({
296
+ background = false,
297
+ scene,
298
+ blur,
299
+ backgroundBlurriness,
300
+ backgroundIntensity,
301
+ backgroundRotation,
302
+ environmentIntensity,
303
+ environmentRotation,
304
+ ...rest
305
+ }) {
306
+ const texture = useEnvironment(rest);
307
+ const defaultScene = fiber.useThree((state) => state.scene);
308
+ React__namespace.useLayoutEffect(() => {
309
+ return setEnvProps(background, scene, defaultScene, texture, {
310
+ backgroundBlurriness: blur ?? backgroundBlurriness,
311
+ backgroundIntensity,
312
+ backgroundRotation,
313
+ environmentIntensity,
314
+ environmentRotation
315
+ });
316
+ });
317
+ React__namespace.useEffect(() => {
318
+ return () => {
319
+ texture.dispose();
320
+ };
321
+ }, [texture]);
322
+ return null;
323
+ }
324
+ function EnvironmentPortal({
325
+ children,
326
+ near = 0.1,
327
+ far = 1e3,
328
+ resolution = 256,
329
+ frames = 1,
330
+ map,
331
+ background = false,
332
+ blur,
333
+ backgroundBlurriness,
334
+ backgroundIntensity,
335
+ backgroundRotation,
336
+ environmentIntensity,
337
+ environmentRotation,
338
+ scene,
339
+ files,
340
+ path,
341
+ preset = void 0,
342
+ extensions
343
+ }) {
344
+ const gl = fiber.useThree((state) => state.gl);
345
+ const defaultScene = fiber.useThree((state) => state.scene);
346
+ const camera = React__namespace.useRef(null);
347
+ const [virtualScene] = React__namespace.useState(() => new three.Scene());
348
+ const fbo = React__namespace.useMemo(() => {
349
+ const fbo2 = new three.WebGLCubeRenderTarget(resolution);
350
+ fbo2.texture.type = three.HalfFloatType;
351
+ return fbo2;
352
+ }, [resolution]);
353
+ React__namespace.useEffect(() => {
354
+ return () => {
355
+ fbo.dispose();
356
+ };
357
+ }, [fbo]);
358
+ React__namespace.useLayoutEffect(() => {
359
+ if (frames === 1) {
360
+ const autoClear = gl.autoClear;
361
+ gl.autoClear = true;
362
+ camera.current.update(gl, virtualScene);
363
+ gl.autoClear = autoClear;
364
+ }
365
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
366
+ backgroundBlurriness: blur ?? backgroundBlurriness,
367
+ backgroundIntensity,
368
+ backgroundRotation,
369
+ environmentIntensity,
370
+ environmentRotation
371
+ });
372
+ }, [children, virtualScene, fbo.texture, scene, defaultScene, background, frames, gl]);
373
+ let count = 1;
374
+ fiber.useFrame(() => {
375
+ if (frames === Infinity || count < frames) {
376
+ const autoClear = gl.autoClear;
377
+ gl.autoClear = true;
378
+ camera.current.update(gl, virtualScene);
379
+ gl.autoClear = autoClear;
380
+ count++;
381
+ }
382
+ });
383
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fiber.createPortal(
384
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
385
+ children,
386
+ /* @__PURE__ */ jsxRuntime.jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
387
+ files || preset ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { background: true, map, extensions }) : null
388
+ ] }),
389
+ virtualScene
390
+ ) });
391
+ }
392
+ function EnvironmentGround(props) {
393
+ const textureDefault = useEnvironment(props);
394
+ const texture = props.map || textureDefault;
395
+ React__namespace.useMemo(() => fiber.extend({ GroundProjectedEnvImpl: GroundedSkybox_js.GroundedSkybox }), []);
396
+ React__namespace.useEffect(() => {
397
+ return () => {
398
+ textureDefault.dispose();
399
+ };
400
+ }, [textureDefault]);
401
+ const height = props.ground?.height ?? 15;
402
+ const radius = props.ground?.radius ?? 60;
403
+ const scale = props.ground?.scale ?? 1e3;
404
+ const args = React__namespace.useMemo(
405
+ () => [texture, height, radius],
406
+ [texture, height, radius]
407
+ );
408
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
409
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentMap, { ...props, map: texture }),
410
+ /* @__PURE__ */ jsxRuntime.jsx("groundProjectedEnvImpl", { args, scale })
411
+ ] });
412
+ }
413
+ function EnvironmentColor({ color, scene }) {
414
+ const defaultScene = fiber.useThree((state) => state.scene);
415
+ React__namespace.useLayoutEffect(() => {
416
+ if (color === void 0) return;
417
+ const target = resolveScene(scene || defaultScene);
418
+ const oldBg = target.background;
419
+ target.background = new three.Color(color);
420
+ return () => {
421
+ target.background = oldBg;
422
+ };
423
+ });
424
+ return null;
425
+ }
426
+ function EnvironmentDualSource(props) {
427
+ const { backgroundFiles, ...envProps } = props;
428
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
429
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...envProps, background: false }),
430
+ /* @__PURE__ */ jsxRuntime.jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
431
+ ] });
432
+ }
433
+ function Environment(props) {
434
+ if (props.color && !props.files && !props.preset && !props.map) {
435
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentColor, { ...props });
436
+ }
437
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
438
+ return /* @__PURE__ */ jsxRuntime.jsx(EnvironmentDualSource, { ...props });
439
+ }
440
+ 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 });
441
+ }
442
+
69
443
  var __defProp$2 = Object.defineProperty;
70
444
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
71
445
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -245,7 +619,8 @@ function prepare(target, root, type, props) {
245
619
  object,
246
620
  eventCount: 0,
247
621
  handlers: {},
248
- isHidden: false
622
+ isHidden: false,
623
+ deferredRefs: []
249
624
  };
250
625
  if (object) object.__r3f = instance;
251
626
  }
@@ -294,7 +669,7 @@ function createOcclusionObserverNode(store, uniform) {
294
669
  let occlusionSetupPromise = null;
295
670
  function enableOcclusion(store) {
296
671
  const state = store.getState();
297
- const { internal, renderer, rootScene } = state;
672
+ const { internal, renderer } = state;
298
673
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
299
674
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
300
675
  if (!hasOcclusionSupport) {
@@ -457,6 +832,22 @@ function hasVisibilityHandlers(handlers) {
457
832
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
458
833
  }
459
834
 
835
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
836
+ function fromRef(ref) {
837
+ return { [FROM_REF]: ref };
838
+ }
839
+ function isFromRef(value) {
840
+ return value !== null && typeof value === "object" && FROM_REF in value;
841
+ }
842
+
843
+ const ONCE = Symbol.for("@react-three/fiber.once");
844
+ function once(...args) {
845
+ return { [ONCE]: args.length ? args : true };
846
+ }
847
+ function isOnce(value) {
848
+ return value !== null && typeof value === "object" && ONCE in value;
849
+ }
850
+
460
851
  const RESERVED_PROPS = [
461
852
  "children",
462
853
  "key",
@@ -527,7 +918,7 @@ function getMemoizedPrototype(root) {
527
918
  ctor = new root.constructor();
528
919
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
529
920
  }
530
- } catch (e) {
921
+ } catch {
531
922
  }
532
923
  return ctor;
533
924
  }
@@ -573,6 +964,25 @@ function applyProps(object, props) {
573
964
  continue;
574
965
  }
575
966
  if (value === void 0) continue;
967
+ if (isFromRef(value)) {
968
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
969
+ continue;
970
+ }
971
+ if (isOnce(value)) {
972
+ if (instance?.appliedOnce?.has(prop)) continue;
973
+ if (instance) {
974
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
975
+ instance.appliedOnce.add(prop);
976
+ }
977
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
978
+ const args = value[ONCE];
979
+ if (typeof targetRoot[targetKey] === "function") {
980
+ targetRoot[targetKey](...args === true ? [] : args);
981
+ } else if (args !== true && args.length > 0) {
982
+ targetRoot[targetKey] = args[0];
983
+ }
984
+ continue;
985
+ }
576
986
  let { root, key, target } = resolve(object, prop);
577
987
  if (target === void 0 && (typeof root !== "object" || root === null)) {
578
988
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -595,7 +1005,7 @@ function applyProps(object, props) {
595
1005
  else target.set(value);
596
1006
  } else {
597
1007
  root[key] = value;
598
- 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
1008
+ if (rootState && rootState.renderer?.outputColorSpace === three.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
599
1009
  root[key].format === three.RGBAFormat && root[key].type === three.UnsignedByteType) {
600
1010
  root[key].colorSpace = rootState.textureColorSpace;
601
1011
  }
@@ -952,7 +1362,7 @@ function createPointerEvents(store) {
952
1362
  return {
953
1363
  priority: 1,
954
1364
  enabled: true,
955
- compute(event, state, previous) {
1365
+ compute(event, state) {
956
1366
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
957
1367
  state.raycaster.setFromCamera(state.pointer, state.camera);
958
1368
  },
@@ -1050,331 +1460,26 @@ function notifyAlpha({ message, link }) {
1050
1460
  }
1051
1461
  }
1052
1462
 
1053
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1054
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
1055
- const createStore = (invalidate, advance) => {
1056
- const rootStore = traditional.createWithEqualityFn((set, get) => {
1057
- const position = new three.Vector3();
1058
- const defaultTarget = new three.Vector3();
1059
- const tempTarget = new three.Vector3();
1060
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1061
- const { width, height, top, left } = size;
1062
- const aspect = width / height;
1063
- if (target.isVector3) tempTarget.copy(target);
1064
- else tempTarget.set(...target);
1065
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1066
- if (isOrthographicCamera(camera)) {
1067
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1068
- } else {
1069
- const fov = camera.fov * Math.PI / 180;
1070
- const h = 2 * Math.tan(fov / 2) * distance;
1071
- const w = h * (width / height);
1072
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1073
- }
1074
- }
1075
- let performanceTimeout = void 0;
1076
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1077
- const pointer = new three.Vector2();
1078
- const rootState = {
1079
- set,
1080
- get,
1081
- // Mock objects that have to be configured
1082
- gl: null,
1083
- renderer: null,
1084
- camera: null,
1085
- frustum: new three.Frustum(),
1086
- autoUpdateFrustum: true,
1087
- raycaster: null,
1088
- events: { priority: 1, enabled: true, connected: false },
1089
- scene: null,
1090
- rootScene: null,
1091
- xr: null,
1092
- inspector: null,
1093
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1094
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1095
- legacy: false,
1096
- linear: false,
1097
- flat: false,
1098
- textureColorSpace: "srgb",
1099
- isLegacy: false,
1100
- webGPUSupported: false,
1101
- isNative: false,
1102
- controls: null,
1103
- pointer,
1104
- mouse: pointer,
1105
- frameloop: "always",
1106
- onPointerMissed: void 0,
1107
- onDragOverMissed: void 0,
1108
- onDropMissed: void 0,
1109
- performance: {
1110
- current: 1,
1111
- min: 0.5,
1112
- max: 1,
1113
- debounce: 200,
1114
- regress: () => {
1115
- const state2 = get();
1116
- if (performanceTimeout) clearTimeout(performanceTimeout);
1117
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1118
- performanceTimeout = setTimeout(
1119
- () => setPerformanceCurrent(get().performance.max),
1120
- state2.performance.debounce
1121
- );
1122
- }
1123
- },
1124
- size: { width: 0, height: 0, top: 0, left: 0 },
1125
- viewport: {
1126
- initialDpr: 0,
1127
- dpr: 0,
1128
- width: 0,
1129
- height: 0,
1130
- top: 0,
1131
- left: 0,
1132
- aspect: 0,
1133
- distance: 0,
1134
- factor: 0,
1135
- getCurrentViewport
1136
- },
1137
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1138
- setSize: (width, height, top, left) => {
1139
- const state2 = get();
1140
- if (width === void 0) {
1141
- set({ _sizeImperative: false });
1142
- if (state2._sizeProps) {
1143
- const { width: propW, height: propH } = state2._sizeProps;
1144
- if (propW !== void 0 || propH !== void 0) {
1145
- const currentSize = state2.size;
1146
- const newSize = {
1147
- width: propW ?? currentSize.width,
1148
- height: propH ?? currentSize.height,
1149
- top: currentSize.top,
1150
- left: currentSize.left
1151
- };
1152
- set((s) => ({
1153
- size: newSize,
1154
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
1155
- }));
1156
- }
1157
- }
1158
- return;
1159
- }
1160
- const w = width;
1161
- const h = height ?? width;
1162
- const t = top ?? state2.size.top;
1163
- const l = left ?? state2.size.left;
1164
- const size = { width: w, height: h, top: t, left: l };
1165
- set((s) => ({
1166
- size,
1167
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
1168
- _sizeImperative: true
1169
- }));
1170
- },
1171
- setDpr: (dpr) => set((state2) => {
1172
- const resolved = calculateDpr(dpr);
1173
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1174
- }),
1175
- setFrameloop: (frameloop = "always") => {
1176
- set(() => ({ frameloop }));
1177
- },
1178
- setError: (error) => set(() => ({ error })),
1179
- error: null,
1180
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1181
- uniforms: {},
1182
- nodes: {},
1183
- textures: /* @__PURE__ */ new Map(),
1184
- postProcessing: null,
1185
- passes: {},
1186
- _hmrVersion: 0,
1187
- _sizeImperative: false,
1188
- _sizeProps: null,
1189
- previousRoot: void 0,
1190
- internal: {
1191
- // Events
1192
- interaction: [],
1193
- hovered: /* @__PURE__ */ new Map(),
1194
- subscribers: [],
1195
- initialClick: [0, 0],
1196
- initialHits: [],
1197
- capturedMap: /* @__PURE__ */ new Map(),
1198
- lastEvent: React__namespace.createRef(),
1199
- // Visibility tracking (onFramed, onOccluded, onVisible)
1200
- visibilityRegistry: /* @__PURE__ */ new Map(),
1201
- // Occlusion system (WebGPU only)
1202
- occlusionEnabled: false,
1203
- occlusionObserver: null,
1204
- occlusionCache: /* @__PURE__ */ new Map(),
1205
- helperGroup: null,
1206
- // Updates
1207
- active: false,
1208
- frames: 0,
1209
- priority: 0,
1210
- subscribe: (ref, priority, store) => {
1211
- const internal = get().internal;
1212
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1213
- internal.subscribers.push({ ref, priority, store });
1214
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1215
- return () => {
1216
- const internal2 = get().internal;
1217
- if (internal2?.subscribers) {
1218
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1219
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1220
- }
1221
- };
1222
- },
1223
- // Renderer Storage (single source of truth)
1224
- actualRenderer: null,
1225
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1226
- scheduler: null
1227
- }
1228
- };
1229
- return rootState;
1230
- });
1231
- const state = rootStore.getState();
1232
- Object.defineProperty(state, "gl", {
1233
- get() {
1234
- const currentState = rootStore.getState();
1235
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1236
- const stack = new Error().stack || "";
1237
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1238
- if (!isInternalAccess) {
1239
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1240
- notifyDepreciated({
1241
- heading: "Accessing state.gl in WebGPU mode",
1242
- 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
1243
- });
1244
- }
1245
- }
1246
- return currentState.internal.actualRenderer;
1247
- },
1248
- set(value) {
1249
- rootStore.getState().internal.actualRenderer = value;
1250
- },
1251
- enumerable: true,
1252
- configurable: true
1253
- });
1254
- Object.defineProperty(state, "renderer", {
1255
- get() {
1256
- return rootStore.getState().internal.actualRenderer;
1257
- },
1258
- set(value) {
1259
- rootStore.getState().internal.actualRenderer = value;
1260
- },
1261
- enumerable: true,
1262
- configurable: true
1263
- });
1264
- let oldScene = state.scene;
1265
- rootStore.subscribe(() => {
1266
- const currentState = rootStore.getState();
1267
- const { scene, rootScene, set } = currentState;
1268
- if (scene !== oldScene) {
1269
- oldScene = scene;
1270
- if (scene?.isScene && scene !== rootScene) {
1271
- set({ rootScene: scene });
1272
- }
1273
- }
1274
- });
1275
- let oldSize = state.size;
1276
- let oldDpr = state.viewport.dpr;
1277
- let oldCamera = state.camera;
1278
- rootStore.subscribe(() => {
1279
- const { camera, size, viewport, set, internal } = rootStore.getState();
1280
- const actualRenderer = internal.actualRenderer;
1281
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1282
- oldSize = size;
1283
- oldDpr = viewport.dpr;
1284
- updateCamera(camera, size);
1285
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1286
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1287
- actualRenderer.setSize(size.width, size.height, updateStyle);
1288
- }
1289
- if (camera !== oldCamera) {
1290
- oldCamera = camera;
1291
- const { rootScene } = rootStore.getState();
1292
- if (camera && rootScene && !camera.parent) {
1293
- rootScene.add(camera);
1294
- }
1295
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1296
- const currentState = rootStore.getState();
1297
- if (currentState.autoUpdateFrustum && camera) {
1298
- updateFrustum(camera, currentState.frustum);
1299
- }
1300
- }
1301
- });
1302
- rootStore.subscribe((state2) => invalidate(state2));
1303
- return rootStore;
1304
- };
1305
-
1306
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1307
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1308
- function getLoader(Proto) {
1309
- if (isConstructor$1(Proto)) {
1310
- let loader = memoizedLoaders.get(Proto);
1311
- if (!loader) {
1312
- loader = new Proto();
1313
- memoizedLoaders.set(Proto, loader);
1314
- }
1315
- return loader;
1316
- }
1317
- return Proto;
1318
- }
1319
- function loadingFn(extensions, onProgress) {
1320
- return function(Proto, input) {
1321
- const loader = getLoader(Proto);
1322
- if (extensions) extensions(loader);
1323
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1324
- return loader.loadAsync(input, onProgress).then((data) => {
1325
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1326
- return data;
1327
- });
1328
- }
1329
- return new Promise(
1330
- (res, reject) => loader.load(
1331
- input,
1332
- (data) => {
1333
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1334
- res(data);
1335
- },
1336
- onProgress,
1337
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1338
- )
1339
- );
1340
- };
1341
- }
1342
- function useLoader(loader, input, extensions, onProgress) {
1343
- const keys = Array.isArray(input) ? input : [input];
1344
- const fn = loadingFn(extensions, onProgress);
1345
- const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
1346
- return Array.isArray(input) ? results : results[0];
1347
- }
1348
- useLoader.preload = function(loader, input, extensions, onProgress) {
1349
- const keys = Array.isArray(input) ? input : [input];
1350
- keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
1351
- };
1352
- useLoader.clear = function(loader, input) {
1353
- const keys = Array.isArray(input) ? input : [input];
1354
- keys.forEach((key) => suspendReact.clear([loader, key]));
1355
- };
1356
- useLoader.loader = getLoader;
1357
-
1358
- var __defProp$1 = Object.defineProperty;
1359
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1360
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1361
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1362
- class PhaseGraph {
1363
- constructor() {
1364
- /** Ordered list of phase nodes */
1365
- __publicField$1(this, "phases", []);
1366
- /** Quick lookup by name */
1367
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1368
- /** Cached ordered names (invalidated on changes) */
1369
- __publicField$1(this, "orderedNamesCache", null);
1370
- this.initializeDefaultPhases();
1371
- }
1372
- //* Initialization --------------------------------
1373
- initializeDefaultPhases() {
1374
- for (const name of DEFAULT_PHASES) {
1375
- const node = { name, isAutoGenerated: false };
1376
- this.phases.push(node);
1377
- this.phaseMap.set(name, node);
1463
+ var __defProp$1 = Object.defineProperty;
1464
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1465
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1466
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1467
+ class PhaseGraph {
1468
+ constructor() {
1469
+ /** Ordered list of phase nodes */
1470
+ __publicField$1(this, "phases", []);
1471
+ /** Quick lookup by name */
1472
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1473
+ /** Cached ordered names (invalidated on changes) */
1474
+ __publicField$1(this, "orderedNamesCache", null);
1475
+ this.initializeDefaultPhases();
1476
+ }
1477
+ //* Initialization --------------------------------
1478
+ initializeDefaultPhases() {
1479
+ for (const name of DEFAULT_PHASES) {
1480
+ const node = { name, isAutoGenerated: false };
1481
+ this.phases.push(node);
1482
+ this.phaseMap.set(name, node);
1378
1483
  }
1379
1484
  this.invalidateCache();
1380
1485
  }
@@ -2260,132 +2365,445 @@ const _Scheduler = class _Scheduler {
2260
2365
  console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2261
2366
  }
2262
2367
  }
2263
- }
2264
- /**
2265
- * Execute all jobs for a single root in sorted order.
2266
- * Rebuilds sorted job list if needed, then dispatches each job.
2267
- * Errors are caught and propagated via triggerError.
2268
- * @param {RootEntry} root - The root entry to tick
2269
- * @param {number} timestamp - RAF timestamp in milliseconds
2270
- * @param {number} delta - Time since last frame in seconds
2271
- * @returns {void}
2272
- * @private
2273
- */
2274
- tickRoot(root, timestamp, delta) {
2275
- if (root.needsRebuild) {
2276
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2277
- root.needsRebuild = false;
2368
+ }
2369
+ /**
2370
+ * Execute all jobs for a single root in sorted order.
2371
+ * Rebuilds sorted job list if needed, then dispatches each job.
2372
+ * Errors are caught and propagated via triggerError.
2373
+ * @param {RootEntry} root - The root entry to tick
2374
+ * @param {number} timestamp - RAF timestamp in milliseconds
2375
+ * @param {number} delta - Time since last frame in seconds
2376
+ * @returns {void}
2377
+ * @private
2378
+ */
2379
+ tickRoot(root, timestamp, delta) {
2380
+ if (root.needsRebuild) {
2381
+ root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2382
+ root.needsRebuild = false;
2383
+ }
2384
+ const providedState = root.getState?.() ?? {};
2385
+ const frameState = {
2386
+ ...providedState,
2387
+ time: timestamp,
2388
+ delta,
2389
+ elapsed: this.loopState.elapsedTime / 1e3,
2390
+ // Convert ms to seconds
2391
+ frame: this.loopState.frameCount
2392
+ };
2393
+ for (const job of root.sortedJobs) {
2394
+ if (!shouldRun(job, timestamp)) continue;
2395
+ try {
2396
+ job.callback(frameState, delta);
2397
+ } catch (error) {
2398
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2399
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2400
+ }
2401
+ }
2402
+ }
2403
+ //* Debug & Inspection Methods ================================
2404
+ /**
2405
+ * Get the total number of registered jobs across all roots.
2406
+ * Includes both per-root jobs and global before/after jobs.
2407
+ * @returns {number} Total job count
2408
+ */
2409
+ getJobCount() {
2410
+ let count = 0;
2411
+ for (const root of this.roots.values()) {
2412
+ count += root.jobs.size;
2413
+ }
2414
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2415
+ }
2416
+ /**
2417
+ * Get all registered job IDs across all roots.
2418
+ * Includes both per-root jobs and global before/after jobs.
2419
+ * @returns {string[]} Array of all job IDs
2420
+ */
2421
+ getJobIds() {
2422
+ const ids = [];
2423
+ for (const root of this.roots.values()) {
2424
+ ids.push(...root.jobs.keys());
2425
+ }
2426
+ ids.push(...this.globalBeforeJobs.keys());
2427
+ ids.push(...this.globalAfterJobs.keys());
2428
+ return ids;
2429
+ }
2430
+ /**
2431
+ * Get the number of registered roots (Canvas instances).
2432
+ * @returns {number} Number of registered roots
2433
+ */
2434
+ getRootCount() {
2435
+ return this.roots.size;
2436
+ }
2437
+ /**
2438
+ * Check if any user (non-system) jobs are registered in a specific phase.
2439
+ * Used by the default render job to know if a user has taken over rendering.
2440
+ *
2441
+ * @param phase The phase to check
2442
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2443
+ * @returns true if any user jobs exist in the phase
2444
+ */
2445
+ hasUserJobsInPhase(phase, rootId) {
2446
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2447
+ return rootsToCheck.some((root) => {
2448
+ if (!root) return false;
2449
+ for (const job of root.jobs.values()) {
2450
+ if (job.phase === phase && !job.system && job.enabled) return true;
2451
+ }
2452
+ return false;
2453
+ });
2454
+ }
2455
+ //* Utility Methods ================================
2456
+ /**
2457
+ * Generate a unique root ID for automatic root registration.
2458
+ * @returns {string} A unique root ID in the format 'root_N'
2459
+ */
2460
+ generateRootId() {
2461
+ return `root_${this.nextRootIndex++}`;
2462
+ }
2463
+ /**
2464
+ * Generate a unique job ID.
2465
+ * @returns {string} A unique job ID in the format 'job_N'
2466
+ * @private
2467
+ */
2468
+ generateJobId() {
2469
+ return `job_${this.nextJobIndex}`;
2470
+ }
2471
+ /**
2472
+ * Normalize before/after constraints to a Set.
2473
+ * Handles undefined, single string, or array inputs.
2474
+ * @param {string | string[] | undefined} value - The constraint value(s)
2475
+ * @returns {Set<string>} Normalized Set of constraint strings
2476
+ * @private
2477
+ */
2478
+ normalizeConstraints(value) {
2479
+ if (!value) return /* @__PURE__ */ new Set();
2480
+ if (Array.isArray(value)) return new Set(value);
2481
+ return /* @__PURE__ */ new Set([value]);
2482
+ }
2483
+ };
2484
+ //* Static State & Methods (Singleton Usage) ================================
2485
+ //* Cross-Bundle Singleton Key ==============================
2486
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2487
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2488
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2489
+ let Scheduler = _Scheduler;
2490
+ const getScheduler = () => Scheduler.get();
2491
+ if (hmrData) {
2492
+ hmrData.accept?.();
2493
+ }
2494
+
2495
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2496
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
2497
+ const createStore = (invalidate, advance) => {
2498
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
2499
+ const position = new three.Vector3();
2500
+ const defaultTarget = new three.Vector3();
2501
+ const tempTarget = new three.Vector3();
2502
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2503
+ const { width, height, top, left } = size;
2504
+ const aspect = width / height;
2505
+ if (target.isVector3) tempTarget.copy(target);
2506
+ else tempTarget.set(...target);
2507
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2508
+ if (isOrthographicCamera(camera)) {
2509
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2510
+ } else {
2511
+ const fov = camera.fov * Math.PI / 180;
2512
+ const h = 2 * Math.tan(fov / 2) * distance;
2513
+ const w = h * (width / height);
2514
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2515
+ }
2516
+ }
2517
+ let performanceTimeout = void 0;
2518
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2519
+ const pointer = new three.Vector2();
2520
+ const rootState = {
2521
+ set,
2522
+ get,
2523
+ // Mock objects that have to be configured
2524
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2525
+ primaryStore: null,
2526
+ gl: null,
2527
+ renderer: null,
2528
+ camera: null,
2529
+ frustum: new three.Frustum(),
2530
+ autoUpdateFrustum: true,
2531
+ raycaster: null,
2532
+ events: { priority: 1, enabled: true, connected: false },
2533
+ scene: null,
2534
+ rootScene: null,
2535
+ xr: null,
2536
+ inspector: null,
2537
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2538
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2539
+ textureColorSpace: three.SRGBColorSpace,
2540
+ isLegacy: false,
2541
+ webGPUSupported: false,
2542
+ isNative: false,
2543
+ controls: null,
2544
+ pointer,
2545
+ mouse: pointer,
2546
+ frameloop: "always",
2547
+ onPointerMissed: void 0,
2548
+ onDragOverMissed: void 0,
2549
+ onDropMissed: void 0,
2550
+ performance: {
2551
+ current: 1,
2552
+ min: 0.5,
2553
+ max: 1,
2554
+ debounce: 200,
2555
+ regress: () => {
2556
+ const state2 = get();
2557
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2558
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2559
+ performanceTimeout = setTimeout(
2560
+ () => setPerformanceCurrent(get().performance.max),
2561
+ state2.performance.debounce
2562
+ );
2563
+ }
2564
+ },
2565
+ size: { width: 0, height: 0, top: 0, left: 0 },
2566
+ viewport: {
2567
+ initialDpr: 0,
2568
+ dpr: 0,
2569
+ width: 0,
2570
+ height: 0,
2571
+ top: 0,
2572
+ left: 0,
2573
+ aspect: 0,
2574
+ distance: 0,
2575
+ factor: 0,
2576
+ getCurrentViewport
2577
+ },
2578
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2579
+ setSize: (width, height, top, left) => {
2580
+ const state2 = get();
2581
+ if (width === void 0) {
2582
+ set({ _sizeImperative: false });
2583
+ if (state2._sizeProps) {
2584
+ const { width: propW, height: propH } = state2._sizeProps;
2585
+ if (propW !== void 0 || propH !== void 0) {
2586
+ const currentSize = state2.size;
2587
+ const newSize = {
2588
+ width: propW ?? currentSize.width,
2589
+ height: propH ?? currentSize.height,
2590
+ top: currentSize.top,
2591
+ left: currentSize.left
2592
+ };
2593
+ set((s) => ({
2594
+ size: newSize,
2595
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2596
+ }));
2597
+ getScheduler().invalidate();
2598
+ }
2599
+ }
2600
+ return;
2601
+ }
2602
+ const w = width;
2603
+ const h = height ?? width;
2604
+ const t = top ?? state2.size.top;
2605
+ const l = left ?? state2.size.left;
2606
+ const size = { width: w, height: h, top: t, left: l };
2607
+ set((s) => ({
2608
+ size,
2609
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2610
+ _sizeImperative: true
2611
+ }));
2612
+ getScheduler().invalidate();
2613
+ },
2614
+ setDpr: (dpr) => set((state2) => {
2615
+ const resolved = calculateDpr(dpr);
2616
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2617
+ }),
2618
+ setFrameloop: (frameloop = "always") => {
2619
+ set(() => ({ frameloop }));
2620
+ },
2621
+ setError: (error) => set(() => ({ error })),
2622
+ error: null,
2623
+ //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2624
+ uniforms: {},
2625
+ nodes: {},
2626
+ textures: /* @__PURE__ */ new Map(),
2627
+ postProcessing: null,
2628
+ passes: {},
2629
+ _hmrVersion: 0,
2630
+ _sizeImperative: false,
2631
+ _sizeProps: null,
2632
+ previousRoot: void 0,
2633
+ internal: {
2634
+ // Events
2635
+ interaction: [],
2636
+ hovered: /* @__PURE__ */ new Map(),
2637
+ subscribers: [],
2638
+ initialClick: [0, 0],
2639
+ initialHits: [],
2640
+ capturedMap: /* @__PURE__ */ new Map(),
2641
+ lastEvent: React__namespace.createRef(),
2642
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2643
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2644
+ // Occlusion system (WebGPU only)
2645
+ occlusionEnabled: false,
2646
+ occlusionObserver: null,
2647
+ occlusionCache: /* @__PURE__ */ new Map(),
2648
+ helperGroup: null,
2649
+ // Updates
2650
+ active: false,
2651
+ frames: 0,
2652
+ priority: 0,
2653
+ subscribe: (ref, priority, store) => {
2654
+ const internal = get().internal;
2655
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2656
+ internal.subscribers.push({ ref, priority, store });
2657
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2658
+ return () => {
2659
+ const internal2 = get().internal;
2660
+ if (internal2?.subscribers) {
2661
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2662
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2663
+ }
2664
+ };
2665
+ },
2666
+ // Renderer Storage (single source of truth)
2667
+ actualRenderer: null,
2668
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2669
+ scheduler: null
2670
+ }
2671
+ };
2672
+ return rootState;
2673
+ });
2674
+ const state = rootStore.getState();
2675
+ Object.defineProperty(state, "gl", {
2676
+ get() {
2677
+ const currentState = rootStore.getState();
2678
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2679
+ const stack = new Error().stack || "";
2680
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2681
+ if (!isInternalAccess) {
2682
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2683
+ notifyDepreciated({
2684
+ heading: "Accessing state.gl in WebGPU mode",
2685
+ 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
2686
+ });
2687
+ }
2688
+ }
2689
+ return currentState.internal.actualRenderer;
2690
+ },
2691
+ set(value) {
2692
+ rootStore.getState().internal.actualRenderer = value;
2693
+ },
2694
+ enumerable: true,
2695
+ configurable: true
2696
+ });
2697
+ Object.defineProperty(state, "renderer", {
2698
+ get() {
2699
+ return rootStore.getState().internal.actualRenderer;
2700
+ },
2701
+ set(value) {
2702
+ rootStore.getState().internal.actualRenderer = value;
2703
+ },
2704
+ enumerable: true,
2705
+ configurable: true
2706
+ });
2707
+ let oldScene = state.scene;
2708
+ rootStore.subscribe(() => {
2709
+ const currentState = rootStore.getState();
2710
+ const { scene, rootScene, set } = currentState;
2711
+ if (scene !== oldScene) {
2712
+ oldScene = scene;
2713
+ if (scene?.isScene && scene !== rootScene) {
2714
+ set({ rootScene: scene });
2715
+ }
2716
+ }
2717
+ });
2718
+ let oldSize = state.size;
2719
+ let oldDpr = state.viewport.dpr;
2720
+ let oldCamera = state.camera;
2721
+ rootStore.subscribe(() => {
2722
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2723
+ const actualRenderer = internal.actualRenderer;
2724
+ const canvasTarget = internal.canvasTarget;
2725
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2726
+ oldSize = size;
2727
+ oldDpr = viewport.dpr;
2728
+ updateCamera(camera, size);
2729
+ if (canvasTarget) {
2730
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2731
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && canvasTarget.domElement instanceof HTMLCanvasElement;
2732
+ canvasTarget.setSize(size.width, size.height, updateStyle);
2733
+ } else {
2734
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2735
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
2736
+ actualRenderer.setSize(size.width, size.height, updateStyle);
2737
+ }
2278
2738
  }
2279
- const providedState = root.getState?.() ?? {};
2280
- const frameState = {
2281
- ...providedState,
2282
- time: timestamp,
2283
- delta,
2284
- elapsed: this.loopState.elapsedTime / 1e3,
2285
- // Convert ms to seconds
2286
- frame: this.loopState.frameCount
2287
- };
2288
- for (const job of root.sortedJobs) {
2289
- if (!shouldRun(job, timestamp)) continue;
2290
- try {
2291
- job.callback(frameState, delta);
2292
- } catch (error) {
2293
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2294
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2739
+ if (camera !== oldCamera) {
2740
+ oldCamera = camera;
2741
+ const { rootScene } = rootStore.getState();
2742
+ if (camera && rootScene && !camera.parent) {
2743
+ rootScene.add(camera);
2744
+ }
2745
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2746
+ const currentState = rootStore.getState();
2747
+ if (currentState.autoUpdateFrustum && camera) {
2748
+ updateFrustum(camera, currentState.frustum);
2295
2749
  }
2296
2750
  }
2297
- }
2298
- //* Debug & Inspection Methods ================================
2299
- /**
2300
- * Get the total number of registered jobs across all roots.
2301
- * Includes both per-root jobs and global before/after jobs.
2302
- * @returns {number} Total job count
2303
- */
2304
- getJobCount() {
2305
- let count = 0;
2306
- for (const root of this.roots.values()) {
2307
- count += root.jobs.size;
2751
+ });
2752
+ rootStore.subscribe((state2) => invalidate(state2));
2753
+ return rootStore;
2754
+ };
2755
+
2756
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2757
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2758
+ function getLoader(Proto) {
2759
+ if (isConstructor$1(Proto)) {
2760
+ let loader = memoizedLoaders.get(Proto);
2761
+ if (!loader) {
2762
+ loader = new Proto();
2763
+ memoizedLoaders.set(Proto, loader);
2308
2764
  }
2309
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2765
+ return loader;
2310
2766
  }
2311
- /**
2312
- * Get all registered job IDs across all roots.
2313
- * Includes both per-root jobs and global before/after jobs.
2314
- * @returns {string[]} Array of all job IDs
2315
- */
2316
- getJobIds() {
2317
- const ids = [];
2318
- for (const root of this.roots.values()) {
2319
- ids.push(...root.jobs.keys());
2767
+ return Proto;
2768
+ }
2769
+ function loadingFn(extensions, onProgress) {
2770
+ return function(Proto, input) {
2771
+ const loader = getLoader(Proto);
2772
+ if (extensions) extensions(loader);
2773
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2774
+ return loader.loadAsync(input, onProgress).then((data) => {
2775
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2776
+ return data;
2777
+ });
2320
2778
  }
2321
- ids.push(...this.globalBeforeJobs.keys());
2322
- ids.push(...this.globalAfterJobs.keys());
2323
- return ids;
2324
- }
2325
- /**
2326
- * Get the number of registered roots (Canvas instances).
2327
- * @returns {number} Number of registered roots
2328
- */
2329
- getRootCount() {
2330
- return this.roots.size;
2331
- }
2332
- /**
2333
- * Check if any user (non-system) jobs are registered in a specific phase.
2334
- * Used by the default render job to know if a user has taken over rendering.
2335
- *
2336
- * @param phase The phase to check
2337
- * @param rootId Optional root ID to check (checks all roots if not provided)
2338
- * @returns true if any user jobs exist in the phase
2339
- */
2340
- hasUserJobsInPhase(phase, rootId) {
2341
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2342
- return rootsToCheck.some((root) => {
2343
- if (!root) return false;
2344
- for (const job of root.jobs.values()) {
2345
- if (job.phase === phase && !job.system && job.enabled) return true;
2346
- }
2347
- return false;
2348
- });
2349
- }
2350
- //* Utility Methods ================================
2351
- /**
2352
- * Generate a unique root ID for automatic root registration.
2353
- * @returns {string} A unique root ID in the format 'root_N'
2354
- */
2355
- generateRootId() {
2356
- return `root_${this.nextRootIndex++}`;
2357
- }
2358
- /**
2359
- * Generate a unique job ID.
2360
- * @returns {string} A unique job ID in the format 'job_N'
2361
- * @private
2362
- */
2363
- generateJobId() {
2364
- return `job_${this.nextJobIndex}`;
2365
- }
2366
- /**
2367
- * Normalize before/after constraints to a Set.
2368
- * Handles undefined, single string, or array inputs.
2369
- * @param {string | string[] | undefined} value - The constraint value(s)
2370
- * @returns {Set<string>} Normalized Set of constraint strings
2371
- * @private
2372
- */
2373
- normalizeConstraints(value) {
2374
- if (!value) return /* @__PURE__ */ new Set();
2375
- if (Array.isArray(value)) return new Set(value);
2376
- return /* @__PURE__ */ new Set([value]);
2377
- }
2378
- };
2379
- //* Static State & Methods (Singleton Usage) ================================
2380
- //* Cross-Bundle Singleton Key ==============================
2381
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2382
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2383
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2384
- let Scheduler = _Scheduler;
2385
- const getScheduler = () => Scheduler.get();
2386
- if (hmrData) {
2387
- hmrData.accept?.();
2779
+ return new Promise(
2780
+ (res, reject) => loader.load(
2781
+ input,
2782
+ (data) => {
2783
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2784
+ res(data);
2785
+ },
2786
+ onProgress,
2787
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2788
+ )
2789
+ );
2790
+ };
2791
+ }
2792
+ function useLoader(loader, input, extensions, onProgress) {
2793
+ const keys = Array.isArray(input) ? input : [input];
2794
+ const fn = loadingFn(extensions, onProgress);
2795
+ const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
2796
+ return Array.isArray(input) ? results : results[0];
2388
2797
  }
2798
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2799
+ const keys = Array.isArray(input) ? input : [input];
2800
+ keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
2801
+ };
2802
+ useLoader.clear = function(loader, input) {
2803
+ const keys = Array.isArray(input) ? input : [input];
2804
+ keys.forEach((key) => suspendReact.clear([loader, key]));
2805
+ };
2806
+ useLoader.loader = getLoader;
2389
2807
 
2390
2808
  function useFrame(callback, priorityOrOptions) {
2391
2809
  const store = React__namespace.useContext(context);
@@ -2566,6 +2984,9 @@ function useTexture(input, optionsOrOnLoad) {
2566
2984
  const textureCache = useThree((state) => state.textures);
2567
2985
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2568
2986
  const { onLoad, cache = false } = options;
2987
+ const onLoadRef = React.useRef(onLoad);
2988
+ onLoadRef.current = onLoad;
2989
+ const onLoadCalledForRef = React.useRef(null);
2569
2990
  const urls = React.useMemo(() => getUrls(input), [input]);
2570
2991
  const cachedResult = React.useMemo(() => {
2571
2992
  if (!cache) return null;
@@ -2576,9 +2997,13 @@ function useTexture(input, optionsOrOnLoad) {
2576
2997
  three.TextureLoader,
2577
2998
  IsObject(input) ? Object.values(input) : input
2578
2999
  );
3000
+ const inputKey = urls.join("\0");
2579
3001
  React.useLayoutEffect(() => {
2580
- if (!cachedResult) onLoad?.(loadedTextures);
2581
- }, [onLoad, cachedResult, loadedTextures]);
3002
+ if (cachedResult) return;
3003
+ if (onLoadCalledForRef.current === inputKey) return;
3004
+ onLoadCalledForRef.current = inputKey;
3005
+ onLoadRef.current?.(loadedTextures);
3006
+ }, [cachedResult, loadedTextures, inputKey]);
2582
3007
  React.useEffect(() => {
2583
3008
  if (cachedResult) return;
2584
3009
  if ("initTexture" in renderer) {
@@ -2745,14 +3170,31 @@ function useTextures() {
2745
3170
  }, [store]);
2746
3171
  }
2747
3172
 
2748
- function useRenderTarget(width, height, options) {
3173
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2749
3174
  const isLegacy = useThree((s) => s.isLegacy);
2750
3175
  const size = useThree((s) => s.size);
3176
+ let width;
3177
+ let height;
3178
+ let opts;
3179
+ if (typeof widthOrOptions === "object") {
3180
+ opts = widthOrOptions;
3181
+ } else if (typeof widthOrOptions === "number") {
3182
+ width = widthOrOptions;
3183
+ if (typeof heightOrOptions === "object") {
3184
+ height = widthOrOptions;
3185
+ opts = heightOrOptions;
3186
+ } else if (typeof heightOrOptions === "number") {
3187
+ height = heightOrOptions;
3188
+ opts = options;
3189
+ } else {
3190
+ height = widthOrOptions;
3191
+ }
3192
+ }
2751
3193
  return React.useMemo(() => {
2752
3194
  const w = width ?? size.width;
2753
3195
  const h = height ?? size.height;
2754
- return new three.WebGLRenderTarget(w, h, options);
2755
- }, [width, height, size.width, size.height, options, isLegacy]);
3196
+ return new three.WebGLRenderTarget(w, h, opts);
3197
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2756
3198
  }
2757
3199
 
2758
3200
  function useStore() {
@@ -2802,7 +3244,7 @@ function addTail(callback) {
2802
3244
  function invalidate(state, frames = 1, stackFrames = false) {
2803
3245
  getScheduler().invalidate(frames, stackFrames);
2804
3246
  }
2805
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3247
+ function advance(timestamp) {
2806
3248
  getScheduler().step(timestamp);
2807
3249
  }
2808
3250
 
@@ -14256,6 +14698,7 @@ function swapInstances() {
14256
14698
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14257
14699
  instance.object.__r3f = instance;
14258
14700
  setFiberRef(fiber, instance.object);
14701
+ delete instance.appliedOnce;
14259
14702
  applyProps(instance.object, instance.props);
14260
14703
  if (instance.props.attach) {
14261
14704
  attach(parent, instance);
@@ -14329,8 +14772,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14329
14772
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14330
14773
  if (isTailSibling) swapInstances();
14331
14774
  },
14332
- finalizeInitialChildren: () => false,
14333
- commitMount() {
14775
+ finalizeInitialChildren: (instance) => {
14776
+ for (const prop in instance.props) {
14777
+ if (isFromRef(instance.props[prop])) return true;
14778
+ }
14779
+ return false;
14780
+ },
14781
+ commitMount(instance) {
14782
+ const resolved = {};
14783
+ for (const prop in instance.props) {
14784
+ const value = instance.props[prop];
14785
+ if (isFromRef(value)) {
14786
+ const ref = value[FROM_REF];
14787
+ if (ref.current != null) resolved[prop] = ref.current;
14788
+ }
14789
+ }
14790
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14334
14791
  },
14335
14792
  getPublicInstance: (instance) => instance?.object,
14336
14793
  prepareForCommit: () => null,
@@ -14551,6 +15008,9 @@ function createRoot(canvas) {
14551
15008
  let resolve;
14552
15009
  pending = new Promise((_resolve) => resolve = _resolve);
14553
15010
  const {
15011
+ id: canvasId,
15012
+ primaryCanvas,
15013
+ scheduler: schedulerConfig,
14554
15014
  gl: glConfig,
14555
15015
  renderer: rendererConfig,
14556
15016
  size: propsSize,
@@ -14558,10 +15018,7 @@ function createRoot(canvas) {
14558
15018
  events,
14559
15019
  onCreated: onCreatedCallback,
14560
15020
  shadows = false,
14561
- linear = false,
14562
- flat = false,
14563
15021
  textureColorSpace = three.SRGBColorSpace,
14564
- legacy = false,
14565
15022
  orthographic = false,
14566
15023
  frameloop = "always",
14567
15024
  dpr = [1, 2],
@@ -14573,7 +15030,8 @@ function createRoot(canvas) {
14573
15030
  onDropMissed,
14574
15031
  autoUpdateFrustum = true,
14575
15032
  occlusion = false,
14576
- _sizeProps
15033
+ _sizeProps,
15034
+ forceEven
14577
15035
  } = props;
14578
15036
  const state = store.getState();
14579
15037
  const defaultGLProps = {
@@ -14587,15 +15045,25 @@ function createRoot(canvas) {
14587
15045
  "WebGPURenderer (renderer prop) is not available in this build. Use @react-three/fiber or @react-three/fiber/webgpu instead."
14588
15046
  );
14589
15047
  }
14590
- (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
15048
+ const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14591
15049
  if (glConfig && rendererConfig) {
14592
15050
  throw new Error("Cannot use both gl and renderer props at the same time");
14593
15051
  }
14594
15052
  let renderer = state.internal.actualRenderer;
15053
+ if (primaryCanvas && !R3F_BUILD_WEBGPU) {
15054
+ throw new Error(
15055
+ "The `primaryCanvas` prop for multi-canvas rendering is only available with WebGPU. Use @react-three/fiber/webgpu instead."
15056
+ );
15057
+ }
15058
+ if (primaryCanvas && wantsGL) {
15059
+ throw new Error(
15060
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15061
+ );
15062
+ }
14595
15063
  if (!state.internal.actualRenderer) {
14596
15064
  renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
14597
15065
  state.internal.actualRenderer = renderer;
14598
- state.set({ isLegacy: true, gl: renderer, renderer });
15066
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14599
15067
  }
14600
15068
  let raycaster = state.raycaster;
14601
15069
  if (!raycaster) state.set({ raycaster: raycaster = new three.Raycaster() });
@@ -14604,6 +15072,7 @@ function createRoot(canvas) {
14604
15072
  if (!is.equ(params, raycaster.params, shallowLoose)) {
14605
15073
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
14606
15074
  }
15075
+ let tempCamera = state.camera;
14607
15076
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14608
15077
  lastCamera = cameraOptions;
14609
15078
  const isCamera = cameraOptions?.isCamera;
@@ -14623,6 +15092,7 @@ function createRoot(canvas) {
14623
15092
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14624
15093
  }
14625
15094
  state.set({ camera });
15095
+ tempCamera = camera;
14626
15096
  raycaster.camera = camera;
14627
15097
  }
14628
15098
  if (!state.scene) {
@@ -14640,7 +15110,7 @@ function createRoot(canvas) {
14640
15110
  rootScene: scene,
14641
15111
  internal: { ...prev.internal, container: scene }
14642
15112
  }));
14643
- const camera = state.camera;
15113
+ const camera = tempCamera;
14644
15114
  if (camera && !camera.parent) scene.add(camera);
14645
15115
  }
14646
15116
  if (events && !state.events.handlers) {
@@ -14657,6 +15127,9 @@ function createRoot(canvas) {
14657
15127
  if (_sizeProps !== void 0) {
14658
15128
  state.set({ _sizeProps });
14659
15129
  }
15130
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15131
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15132
+ }
14660
15133
  const size = computeInitialSize(canvas, propsSize);
14661
15134
  if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
14662
15135
  const wasImperative = state._sizeImperative;
@@ -14686,7 +15159,7 @@ function createRoot(canvas) {
14686
15159
  const handleXRFrame = (timestamp, frame) => {
14687
15160
  const state2 = store.getState();
14688
15161
  if (state2.frameloop === "never") return;
14689
- advance(timestamp, true);
15162
+ advance(timestamp);
14690
15163
  };
14691
15164
  const actualRenderer = state.internal.actualRenderer;
14692
15165
  const handleSessionChange = () => {
@@ -14698,16 +15171,16 @@ function createRoot(canvas) {
14698
15171
  };
14699
15172
  const xr = {
14700
15173
  connect() {
14701
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14702
- const actualRenderer2 = renderer2 || gl;
14703
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14704
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15174
+ const { gl, renderer: renderer2 } = store.getState();
15175
+ const xrManager = (renderer2 || gl).xr;
15176
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15177
+ xrManager.addEventListener("sessionend", handleSessionChange);
14705
15178
  },
14706
15179
  disconnect() {
14707
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14708
- const actualRenderer2 = renderer2 || gl;
14709
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14710
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15180
+ const { gl, renderer: renderer2 } = store.getState();
15181
+ const xrManager = (renderer2 || gl).xr;
15182
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15183
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14711
15184
  }
14712
15185
  };
14713
15186
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14735,25 +15208,9 @@ function createRoot(canvas) {
14735
15208
  renderer.shadowMap.needsUpdate = true;
14736
15209
  }
14737
15210
  }
14738
- {
14739
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14740
- const linearChanged = linear !== lastConfiguredProps.linear;
14741
- const flatChanged = flat !== lastConfiguredProps.flat;
14742
- if (legacyChanged) {
14743
- three.ColorManagement.enabled = !legacy;
14744
- lastConfiguredProps.legacy = legacy;
14745
- }
14746
- if (!configured || linearChanged) {
14747
- renderer.outputColorSpace = linear ? three.LinearSRGBColorSpace : three.SRGBColorSpace;
14748
- lastConfiguredProps.linear = linear;
14749
- }
14750
- if (!configured || flatChanged) {
14751
- renderer.toneMapping = flat ? three.NoToneMapping : three.ACESFilmicToneMapping;
14752
- lastConfiguredProps.flat = flat;
14753
- }
14754
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14755
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14756
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15211
+ if (!configured) {
15212
+ renderer.outputColorSpace = three.SRGBColorSpace;
15213
+ renderer.toneMapping = three.ACESFilmicToneMapping;
14757
15214
  }
14758
15215
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14759
15216
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
@@ -14771,11 +15228,26 @@ function createRoot(canvas) {
14771
15228
  const scheduler = getScheduler();
14772
15229
  const rootId = state.internal.rootId;
14773
15230
  if (!rootId) {
14774
- const newRootId = scheduler.generateRootId();
15231
+ const newRootId = canvasId || scheduler.generateRootId();
14775
15232
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14776
15233
  getState: () => store.getState(),
14777
15234
  onError: (err) => store.getState().setError(err)
14778
15235
  });
15236
+ const unregisterCanvasTarget = scheduler.register(
15237
+ () => {
15238
+ const state2 = store.getState();
15239
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15240
+ const renderer2 = state2.internal.actualRenderer;
15241
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15242
+ }
15243
+ },
15244
+ {
15245
+ id: `${newRootId}_canvasTarget`,
15246
+ rootId: newRootId,
15247
+ phase: "start",
15248
+ system: true
15249
+ }
15250
+ );
14779
15251
  const unregisterFrustum = scheduler.register(
14780
15252
  () => {
14781
15253
  const state2 = store.getState();
@@ -14817,11 +15289,15 @@ function createRoot(canvas) {
14817
15289
  }
14818
15290
  },
14819
15291
  {
14820
- id: `${newRootId}_render`,
15292
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15293
+ id: canvasId || `${newRootId}_render`,
14821
15294
  rootId: newRootId,
14822
15295
  phase: "render",
14823
- system: true
15296
+ system: true,
14824
15297
  // Internal flag: this is a system job, not user-controlled
15298
+ // Apply scheduler config for render ordering and rate limiting
15299
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15300
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14825
15301
  }
14826
15302
  );
14827
15303
  state.set((state2) => ({
@@ -14830,6 +15306,7 @@ function createRoot(canvas) {
14830
15306
  rootId: newRootId,
14831
15307
  unregisterRoot: () => {
14832
15308
  unregisterRoot();
15309
+ unregisterCanvasTarget();
14833
15310
  unregisterFrustum();
14834
15311
  unregisterVisibility();
14835
15312
  unregisterRender();
@@ -14888,15 +15365,24 @@ function unmountComponentAtNode(canvas, callback) {
14888
15365
  const renderer = state.internal.actualRenderer;
14889
15366
  const unregisterRoot = state.internal.unregisterRoot;
14890
15367
  if (unregisterRoot) unregisterRoot();
15368
+ const unregisterPrimary = state.internal.unregisterPrimary;
15369
+ if (unregisterPrimary) unregisterPrimary();
15370
+ const canvasTarget = state.internal.canvasTarget;
15371
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14891
15372
  state.events.disconnect?.();
14892
15373
  cleanupHelperGroup(root.store);
14893
- renderer?.renderLists?.dispose?.();
14894
- renderer?.forceContextLoss?.();
14895
- if (renderer?.xr) state.xr.disconnect();
15374
+ if (state.isLegacy && renderer) {
15375
+ ;
15376
+ renderer.renderLists?.dispose?.();
15377
+ renderer.forceContextLoss?.();
15378
+ }
15379
+ if (!state.internal.isSecondary) {
15380
+ if (renderer?.xr) state.xr.disconnect();
15381
+ }
14896
15382
  dispose(state.scene);
14897
15383
  _roots.delete(canvas);
14898
15384
  if (callback) callback(canvas);
14899
- } catch (e) {
15385
+ } catch {
14900
15386
  }
14901
15387
  }, 500);
14902
15388
  }
@@ -14904,36 +15390,34 @@ function unmountComponentAtNode(canvas, callback) {
14904
15390
  }
14905
15391
  }
14906
15392
  function createPortal(children, container, state) {
14907
- return /* @__PURE__ */ jsxRuntime.jsx(PortalWrapper, { children, container, state });
15393
+ return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container, state });
14908
15394
  }
14909
- function PortalWrapper({ children, container, state }) {
15395
+ function Portal({ children, container, state }) {
14910
15396
  const isRef = React.useCallback((obj) => obj && "current" in obj, []);
14911
- const [resolvedContainer, setResolvedContainer] = React.useState(() => {
15397
+ const [resolvedContainer, _setResolvedContainer] = React.useState(() => {
14912
15398
  if (isRef(container)) return container.current ?? null;
14913
15399
  return container;
14914
15400
  });
15401
+ const setResolvedContainer = React.useCallback(
15402
+ (newContainer) => {
15403
+ if (!newContainer || newContainer === resolvedContainer) return;
15404
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15405
+ },
15406
+ [resolvedContainer, _setResolvedContainer, isRef]
15407
+ );
14915
15408
  React.useMemo(() => {
14916
- if (isRef(container)) {
14917
- const current = container.current;
14918
- if (!current) {
14919
- queueMicrotask(() => {
14920
- const updated = container.current;
14921
- if (updated && updated !== resolvedContainer) {
14922
- setResolvedContainer(updated);
14923
- }
14924
- });
14925
- } else if (current !== resolvedContainer) {
14926
- setResolvedContainer(current);
14927
- }
14928
- } else if (container !== resolvedContainer) {
14929
- setResolvedContainer(container);
15409
+ if (isRef(container) && !container.current) {
15410
+ return queueMicrotask(() => {
15411
+ setResolvedContainer(container.current);
15412
+ });
14930
15413
  }
14931
- }, [container, resolvedContainer, isRef]);
15414
+ setResolvedContainer(container);
15415
+ }, [container, isRef, setResolvedContainer]);
14932
15416
  if (!resolvedContainer) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
14933
15417
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14934
- return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15418
+ return /* @__PURE__ */ jsxRuntime.jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14935
15419
  }
14936
- function Portal({ state = {}, children, container }) {
15420
+ function PortalInner({ state = {}, children, container }) {
14937
15421
  const { events, size, injectScene = true, ...rest } = state;
14938
15422
  const previousRoot = useStore();
14939
15423
  const [raycaster] = React.useState(() => new three.Raycaster());
@@ -14954,11 +15438,12 @@ function Portal({ state = {}, children, container }) {
14954
15438
  };
14955
15439
  }, [portalScene, container, injectScene]);
14956
15440
  const inject = useMutableCallback((rootState, injectState) => {
15441
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14957
15442
  let viewport = void 0;
14958
- if (injectState.camera && size) {
15443
+ if (injectState.camera && (size || injectState.size)) {
14959
15444
  const camera = injectState.camera;
14960
- viewport = rootState.viewport.getCurrentViewport(camera, new three.Vector3(), size);
14961
- if (camera !== rootState.camera) updateCamera(camera, size);
15445
+ viewport = rootState.viewport.getCurrentViewport(camera, new three.Vector3(), resolvedSize);
15446
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14962
15447
  }
14963
15448
  return {
14964
15449
  // The intersect consists of the previous root state
@@ -14975,7 +15460,7 @@ function Portal({ state = {}, children, container }) {
14975
15460
  previousRoot,
14976
15461
  // Events, size and viewport can be overridden by the inject layer
14977
15462
  events: { ...rootState.events, ...injectState.events, ...events },
14978
- size: { ...rootState.size, ...size },
15463
+ size: resolvedSize,
14979
15464
  viewport: { ...rootState.viewport, ...viewport },
14980
15465
  // Layers are allowed to override events
14981
15466
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -15009,15 +15494,13 @@ function CanvasImpl({
15009
15494
  fallback,
15010
15495
  resize,
15011
15496
  style,
15497
+ id,
15012
15498
  gl,
15013
- renderer,
15499
+ renderer: rendererProp,
15014
15500
  events = createPointerEvents,
15015
15501
  eventSource,
15016
15502
  eventPrefix,
15017
15503
  shadows,
15018
- linear,
15019
- flat,
15020
- legacy,
15021
15504
  orthographic,
15022
15505
  frameloop,
15023
15506
  dpr,
@@ -15032,10 +15515,43 @@ function CanvasImpl({
15032
15515
  hmr,
15033
15516
  width,
15034
15517
  height,
15518
+ background,
15519
+ forceEven,
15035
15520
  ...props
15036
15521
  }) {
15522
+ const { primaryCanvas, scheduler, ...rendererConfig } = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp) ? rendererProp : { primaryCanvas: void 0, scheduler: void 0 };
15523
+ const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
15037
15524
  React__namespace.useMemo(() => extend(THREE), []);
15038
15525
  const Bridge = useBridge();
15526
+ const backgroundProps = React__namespace.useMemo(() => {
15527
+ if (!background) return null;
15528
+ if (typeof background === "object" && !background.isColor) {
15529
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15530
+ return {
15531
+ ...rest,
15532
+ preset,
15533
+ files: envMap || files,
15534
+ backgroundFiles: backgroundMap,
15535
+ background: true
15536
+ };
15537
+ }
15538
+ if (typeof background === "number") {
15539
+ return { color: background, background: true };
15540
+ }
15541
+ if (typeof background === "string") {
15542
+ if (background in presetsObj) {
15543
+ return { preset: background, background: true };
15544
+ }
15545
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15546
+ return { files: background, background: true };
15547
+ }
15548
+ return { color: background, background: true };
15549
+ }
15550
+ if (background.isColor) {
15551
+ return { color: background, background: true };
15552
+ }
15553
+ return null;
15554
+ }, [background]);
15039
15555
  const hasInitialSizeRef = React__namespace.useRef(false);
15040
15556
  const measureConfig = React__namespace.useMemo(() => {
15041
15557
  if (!hasInitialSizeRef.current) {
@@ -15052,15 +15568,20 @@ function CanvasImpl({
15052
15568
  };
15053
15569
  }, [resize, hasInitialSizeRef.current]);
15054
15570
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15055
- const effectiveSize = React__namespace.useMemo(
15056
- () => ({
15057
- width: width ?? containerRect.width,
15058
- height: height ?? containerRect.height,
15571
+ const effectiveSize = React__namespace.useMemo(() => {
15572
+ let w = width ?? containerRect.width;
15573
+ let h = height ?? containerRect.height;
15574
+ if (forceEven) {
15575
+ w = Math.ceil(w / 2) * 2;
15576
+ h = Math.ceil(h / 2) * 2;
15577
+ }
15578
+ return {
15579
+ width: w,
15580
+ height: h,
15059
15581
  top: containerRect.top,
15060
15582
  left: containerRect.left
15061
- }),
15062
- [width, height, containerRect]
15063
- );
15583
+ };
15584
+ }, [width, height, containerRect, forceEven]);
15064
15585
  if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15065
15586
  hasInitialSizeRef.current = true;
15066
15587
  }
@@ -15100,14 +15621,14 @@ function CanvasImpl({
15100
15621
  async function run() {
15101
15622
  if (!effectActiveRef.current || !root.current) return;
15102
15623
  await root.current.configure({
15624
+ id,
15625
+ primaryCanvas,
15626
+ scheduler,
15103
15627
  gl,
15104
15628
  renderer,
15105
15629
  scene,
15106
15630
  events,
15107
15631
  shadows,
15108
- linear,
15109
- flat,
15110
- legacy,
15111
15632
  orthographic,
15112
15633
  frameloop,
15113
15634
  dpr,
@@ -15117,6 +15638,7 @@ function CanvasImpl({
15117
15638
  size: effectiveSize,
15118
15639
  // Store size props for reset functionality
15119
15640
  _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15641
+ forceEven,
15120
15642
  // Pass mutable reference to onPointerMissed so it's free to update
15121
15643
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15122
15644
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15140,7 +15662,10 @@ function CanvasImpl({
15140
15662
  });
15141
15663
  if (!effectActiveRef.current || !root.current) return;
15142
15664
  root.current.render(
15143
- /* @__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 }) }) })
15665
+ /* @__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: [
15666
+ backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
15667
+ children ?? null
15668
+ ] }) }) })
15144
15669
  );
15145
15670
  }
15146
15671
  run();
@@ -15167,14 +15692,16 @@ function CanvasImpl({
15167
15692
  const canvas = canvasRef.current;
15168
15693
  if (!canvas) return;
15169
15694
  const handleHMR = () => {
15170
- const rootEntry = _roots.get(canvas);
15171
- if (rootEntry?.store) {
15172
- rootEntry.store.setState((state) => ({
15173
- nodes: {},
15174
- uniforms: {},
15175
- _hmrVersion: state._hmrVersion + 1
15176
- }));
15177
- }
15695
+ queueMicrotask(() => {
15696
+ const rootEntry = _roots.get(canvas);
15697
+ if (rootEntry?.store) {
15698
+ rootEntry.store.setState((state) => ({
15699
+ nodes: {},
15700
+ uniforms: {},
15701
+ _hmrVersion: state._hmrVersion + 1
15702
+ }));
15703
+ }
15704
+ });
15178
15705
  };
15179
15706
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('legacy.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15180
15707
  const hot = undefined;
@@ -15203,7 +15730,7 @@ function CanvasImpl({
15203
15730
  ...style
15204
15731
  },
15205
15732
  ...props,
15206
- 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 }) })
15733
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, id, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15207
15734
  }
15208
15735
  );
15209
15736
  }
@@ -15215,8 +15742,15 @@ extend(THREE);
15215
15742
 
15216
15743
  exports.Block = Block;
15217
15744
  exports.Canvas = Canvas;
15745
+ exports.Environment = Environment;
15746
+ exports.EnvironmentCube = EnvironmentCube;
15747
+ exports.EnvironmentMap = EnvironmentMap;
15748
+ exports.EnvironmentPortal = EnvironmentPortal;
15218
15749
  exports.ErrorBoundary = ErrorBoundary;
15750
+ exports.FROM_REF = FROM_REF;
15219
15751
  exports.IsObject = IsObject;
15752
+ exports.ONCE = ONCE;
15753
+ exports.Portal = Portal;
15220
15754
  exports.R3F_BUILD_LEGACY = R3F_BUILD_LEGACY;
15221
15755
  exports.R3F_BUILD_WEBGPU = R3F_BUILD_WEBGPU;
15222
15756
  exports.REACT_INTERNAL_PROPS = REACT_INTERNAL_PROPS;
@@ -15246,30 +15780,41 @@ exports.events = createPointerEvents;
15246
15780
  exports.extend = extend;
15247
15781
  exports.findInitialRoot = findInitialRoot;
15248
15782
  exports.flushSync = flushSync;
15783
+ exports.fromRef = fromRef;
15249
15784
  exports.getInstanceProps = getInstanceProps;
15785
+ exports.getPrimary = getPrimary;
15786
+ exports.getPrimaryIds = getPrimaryIds;
15250
15787
  exports.getRootState = getRootState;
15251
15788
  exports.getScheduler = getScheduler;
15252
15789
  exports.getUuidPrefix = getUuidPrefix;
15253
15790
  exports.hasConstructor = hasConstructor;
15791
+ exports.hasPrimary = hasPrimary;
15254
15792
  exports.invalidate = invalidate;
15255
15793
  exports.invalidateInstance = invalidateInstance;
15256
15794
  exports.is = is;
15257
15795
  exports.isColorRepresentation = isColorRepresentation;
15258
15796
  exports.isCopyable = isCopyable;
15797
+ exports.isFromRef = isFromRef;
15259
15798
  exports.isObject3D = isObject3D;
15799
+ exports.isOnce = isOnce;
15260
15800
  exports.isOrthographicCamera = isOrthographicCamera;
15261
15801
  exports.isRef = isRef;
15262
15802
  exports.isRenderer = isRenderer;
15263
15803
  exports.isTexture = isTexture;
15264
15804
  exports.isVectorLike = isVectorLike;
15805
+ exports.once = once;
15265
15806
  exports.prepare = prepare;
15807
+ exports.presetsObj = presetsObj;
15266
15808
  exports.reconciler = reconciler;
15809
+ exports.registerPrimary = registerPrimary;
15267
15810
  exports.removeInteractivity = removeInteractivity;
15268
15811
  exports.resolve = resolve;
15269
15812
  exports.unmountComponentAtNode = unmountComponentAtNode;
15813
+ exports.unregisterPrimary = unregisterPrimary;
15270
15814
  exports.updateCamera = updateCamera;
15271
15815
  exports.updateFrustum = updateFrustum;
15272
15816
  exports.useBridge = useBridge;
15817
+ exports.useEnvironment = useEnvironment;
15273
15818
  exports.useFrame = useFrame;
15274
15819
  exports.useGraph = useGraph;
15275
15820
  exports.useInstanceHandle = useInstanceHandle;
@@ -15281,3 +15826,4 @@ exports.useStore = useStore;
15281
15826
  exports.useTexture = useTexture;
15282
15827
  exports.useTextures = useTextures;
15283
15828
  exports.useThree = useThree;
15829
+ exports.waitForPrimary = waitForPrimary;