@react-three/fiber 10.0.0-canary.4bc4a97 → 10.0.0-canary.4bf885a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color } from 'three/webgpu';
2
+ import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Vector3, Vector2, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color } from 'three/webgpu';
3
3
  import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
4
- import { Inspector } from 'three/addons/inspector/Inspector.js';
5
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
5
  import * as React from 'react';
7
- import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
6
+ import React__default, { useContext, useRef, useMemo, useLayoutEffect, useEffect, useImperativeHandle, useCallback, useState } from 'react';
8
7
  import useMeasure from 'react-use-measure';
9
8
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
10
9
  import { createWithEqualityFn } from 'zustand/traditional';
@@ -13,6 +12,7 @@ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
13
12
  import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
14
13
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
15
14
  import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
15
+ import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js';
16
16
  import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
17
17
  import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
18
18
  import { GainMapLoader } from '@monogrid/gainmap-js';
@@ -32,18 +32,22 @@ function _mergeNamespaces(n, m) {
32
32
 
33
33
  const R3F_BUILD_LEGACY = true;
34
34
  const R3F_BUILD_WEBGPU = true;
35
+ async function loadInspector() {
36
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
37
+ return Inspector;
38
+ }
35
39
 
36
40
  const THREE = /*#__PURE__*/_mergeNamespaces({
37
41
  __proto__: null,
38
42
  CubeRenderTarget: CubeRenderTarget,
39
43
  CubeRenderTargetCompat: CubeRenderTarget,
40
- Inspector: Inspector,
41
44
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
42
45
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
43
46
  RenderTargetCompat: RenderTarget,
44
47
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
45
48
  WebGLRenderTarget: WebGLRenderTarget,
46
- WebGLRenderer: WebGLRenderer
49
+ WebGLRenderer: WebGLRenderer,
50
+ loadInspector: loadInspector
47
51
  }, [webgpu]);
48
52
 
49
53
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -290,7 +294,6 @@ function prepare(target, root, type, props) {
290
294
  }
291
295
  function invalidateInstance(instance) {
292
296
  if (!instance.parent) return;
293
- instance.props.onUpdate?.(instance.object);
294
297
  const state = instance.root?.getState?.();
295
298
  if (state && state.internal.frames === 0) state.invalidate();
296
299
  }
@@ -506,8 +509,10 @@ function hasVisibilityHandlers(handlers) {
506
509
  }
507
510
 
508
511
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
509
- function fromRef(ref) {
510
- return { [FROM_REF]: ref };
512
+ function fromRef(ref, transform) {
513
+ const marker = { [FROM_REF]: ref };
514
+ if (transform) marker.transform = transform;
515
+ return marker;
511
516
  }
512
517
  function isFromRef(value) {
513
518
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -521,6 +526,63 @@ function isOnce(value) {
521
526
  return value !== null && typeof value === "object" && ONCE in value;
522
527
  }
523
528
 
529
+ const shownNotices = /* @__PURE__ */ new Set();
530
+ function notifyDepreciated({ heading, body, link }) {
531
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
532
+ return;
533
+ }
534
+ if (shownNotices.has(heading)) return;
535
+ shownNotices.add(heading);
536
+ const caller = getCallerFrame();
537
+ if (caller) {
538
+ console.log();
539
+ }
540
+ const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
541
+ if (caller) {
542
+ console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
543
+ }
544
+ if (body || link) {
545
+ let message = "";
546
+ if (body) message += body;
547
+ if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
548
+ console.warn(`%c${message}`, "font-weight: bold;");
549
+ }
550
+ }
551
+ function getCallerFrame(depth = 3) {
552
+ const stack = new Error().stack;
553
+ if (!stack) return null;
554
+ const lines = stack.split("\n");
555
+ const frame = lines[depth];
556
+ if (!frame) return null;
557
+ let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
558
+ if (!match) {
559
+ match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
560
+ }
561
+ if (!match) return null;
562
+ const [, fn, url, line] = match;
563
+ return {
564
+ functionName: fn ?? "<anonymous>",
565
+ location: formatLocation(url, Number(line))
566
+ };
567
+ }
568
+ function formatLocation(url, line) {
569
+ const clean = url.split("?")[0];
570
+ const file = clean.split("/").pop() ?? clean;
571
+ return `${file}:${line}`;
572
+ }
573
+ function notifyAlpha({ message, link }) {
574
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
575
+ return;
576
+ }
577
+ if (shownNotices.has(message)) return;
578
+ shownNotices.add(message);
579
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
580
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
581
+ {
582
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
583
+ }
584
+ }
585
+
524
586
  const RESERVED_PROPS = [
525
587
  "children",
526
588
  "key",
@@ -530,7 +592,6 @@ const RESERVED_PROPS = [
530
592
  "dispose",
531
593
  "attach",
532
594
  "object",
533
- "onUpdate",
534
595
  // Behavior flags
535
596
  "dispose"
536
597
  ];
@@ -608,11 +669,9 @@ function diffProps(instance, newProps) {
608
669
  for (const prop in instance.props) {
609
670
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
610
671
  const { root, key } = resolve(instance.object, prop);
611
- if (root.constructor && root.constructor.length === 0) {
672
+ if (typeof root?.constructor === "function") {
612
673
  const ctor = getMemoizedPrototype(root);
613
674
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
614
- } else {
615
- changedProps[prop] = 0;
616
675
  }
617
676
  }
618
677
  return changedProps;
@@ -624,6 +683,13 @@ function applyProps(object, props) {
624
683
  for (const prop in props) {
625
684
  const value = props[prop];
626
685
  if (RESERVED_PROPS.includes(prop)) continue;
686
+ if (prop === "onUpdate") {
687
+ notifyDepreciated({
688
+ heading: "onUpdate is no longer an R3F prop",
689
+ body: "v10 removed the onUpdate hook: R3F no longer calls it after prop updates. The value is assigned to the object by name. On a Texture that sets three's Texture.onUpdate, which the renderer calls after a GPU upload, not after prop changes. On a node it replaces the Node.onUpdate() method. On other objects it is never called.\n\nUse an effect keyed on the props that change, read the object in useFrame, or use a ref callback.",
690
+ link: "https://docs.pmnd.rs/react-three-fiber/migration/v10#onupdate-removed"
691
+ });
692
+ }
627
693
  if (instance && EVENT_REGEX.test(prop)) {
628
694
  if (typeof value === "function") instance.handlers[prop] = value;
629
695
  else delete instance.handlers[prop];
@@ -729,682 +795,378 @@ function applyProps(object, props) {
729
795
  return object;
730
796
  }
731
797
 
732
- const shownNotices = /* @__PURE__ */ new Set();
733
- function notifyDepreciated({ heading, body, link }) {
734
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
735
- return;
736
- }
737
- if (shownNotices.has(heading)) return;
738
- shownNotices.add(heading);
739
- const caller = getCallerFrame();
740
- if (caller) {
741
- console.log();
742
- }
743
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
744
- if (caller) {
745
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
746
- }
747
- if (body || link) {
748
- let message = "";
749
- if (body) message += body;
750
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
751
- console.warn(`%c${message}`, "font-weight: bold;");
752
- }
753
- }
754
- function getCallerFrame(depth = 3) {
755
- const stack = new Error().stack;
756
- if (!stack) return null;
757
- const lines = stack.split("\n");
758
- const frame = lines[depth];
759
- if (!frame) return null;
760
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
761
- if (!match) {
762
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
763
- }
764
- if (!match) return null;
765
- const [, fn, url, line] = match;
766
- return {
767
- functionName: fn ?? "<anonymous>",
768
- location: formatLocation(url, Number(line))
769
- };
798
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
799
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
800
+
801
+ function useStore() {
802
+ const store = useContext(context);
803
+ if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
804
+ return store;
770
805
  }
771
- function formatLocation(url, line) {
772
- const clean = url.split("?")[0];
773
- const file = clean.split("/").pop() ?? clean;
774
- return `${file}:${line}`;
806
+ function useThree(selector = (state) => state, equalityFn) {
807
+ return useStore()(selector, equalityFn);
775
808
  }
776
- function notifyAlpha({ message, link }) {
777
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
778
- return;
779
- }
780
- if (shownNotices.has(message)) return;
781
- shownNotices.add(message);
782
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
783
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
784
- {
785
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
809
+
810
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
811
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
812
+ function getLoader$1(Proto) {
813
+ if (isConstructor$1(Proto)) {
814
+ let loader = memoizedLoaders.get(Proto);
815
+ if (!loader) {
816
+ loader = new Proto();
817
+ memoizedLoaders.set(Proto, loader);
818
+ }
819
+ return loader;
786
820
  }
821
+ return Proto;
787
822
  }
788
-
789
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
790
- function isInternalRendererAccess(stack, callerDepth = 2) {
791
- if (!stack) return false;
792
- const caller = stack.split("\n")[callerDepth] ?? "";
793
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
823
+ function loadingFn(extensions, onProgress) {
824
+ return function(Proto, input) {
825
+ const loader = getLoader$1(Proto);
826
+ if (extensions) extensions(loader);
827
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
828
+ return loader.loadAsync(input, onProgress).then((data) => {
829
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
830
+ return data;
831
+ });
832
+ }
833
+ return new Promise(
834
+ (res, reject) => loader.load(
835
+ input,
836
+ (data) => {
837
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
838
+ res(data);
839
+ },
840
+ onProgress,
841
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
842
+ )
843
+ );
844
+ };
845
+ }
846
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
847
+ const inputs = Array.isArray(input) ? input : [input];
848
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
849
+ const fn = loadingFn(extensions, onProgress);
850
+ const results = cacheKeys.map(
851
+ (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
852
+ );
853
+ const stableRef = useRef(null);
854
+ const previous = stableRef.current;
855
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
856
+ stableRef.current = stable;
857
+ return Array.isArray(input) ? stable : stable[0];
794
858
  }
859
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
860
+ const inputs = Array.isArray(input) ? input : [input];
861
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
862
+ cacheKeys.forEach(
863
+ (key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
864
+ );
865
+ };
866
+ useLoader.clear = function(loader, input, cacheKey) {
867
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
868
+ cacheKeys.forEach((key) => clear([loader, key]));
869
+ };
870
+ useLoader.loader = getLoader$1;
795
871
 
796
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
797
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
798
- const createStore = (invalidate, advance) => {
799
- const rootStore = createWithEqualityFn((set, get) => {
800
- const position = new Vector3();
801
- const defaultTarget = new Vector3();
802
- const tempTarget = new Vector3();
803
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
804
- const { width, height, top, left } = size;
805
- const aspect = width / height;
806
- if (target.isVector3) tempTarget.copy(target);
807
- else tempTarget.set(...target);
808
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
809
- if (isOrthographicCamera(camera)) {
810
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
811
- } else {
812
- const fov = camera.fov * Math.PI / 180;
813
- const h = 2 * Math.tan(fov / 2) * distance;
814
- const w = h * (width / height);
815
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
872
+ function useFrame(callback, priorityOrOptions) {
873
+ const store = React.useContext(context);
874
+ const isInsideCanvas = store !== null;
875
+ const scheduler = getScheduler();
876
+ const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
877
+ id: priorityOrOptions.id,
878
+ phase: priorityOrOptions.phase,
879
+ priority: priorityOrOptions.priority,
880
+ fps: priorityOrOptions.fps,
881
+ drop: priorityOrOptions.drop,
882
+ enabled: priorityOrOptions.enabled,
883
+ before: priorityOrOptions.before,
884
+ after: priorityOrOptions.after
885
+ }) : "";
886
+ const options = React.useMemo(() => {
887
+ return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
888
+ }, [optionsKey]);
889
+ const reactId = React.useId();
890
+ const id = options.id ?? reactId;
891
+ const callbackRef = useMutableCallback(callback);
892
+ const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
893
+ useIsomorphicLayoutEffect(() => {
894
+ if (!callback) return;
895
+ if (isInsideCanvas) {
896
+ const state = store.getState();
897
+ const rootId = state.internal.rootId;
898
+ if (isLegacyPriority) {
899
+ state.internal.priority++;
900
+ let parentRoot = state.previousRoot;
901
+ while (parentRoot) {
902
+ const parentState = parentRoot.getState();
903
+ if (parentState?.internal) parentState.internal.priority++;
904
+ parentRoot = parentState?.previousRoot;
905
+ }
906
+ notifyDepreciated({
907
+ heading: "useFrame with numeric priority is deprecated",
908
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
909
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
910
+ });
816
911
  }
817
- }
818
- let performanceTimeout = void 0;
819
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
820
- const pointer = new Vector2();
821
- const rootState = {
822
- set,
823
- get,
824
- // Mock objects that have to be configured
825
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
826
- primaryStore: null,
827
- gl: null,
828
- renderer: null,
829
- camera: null,
830
- frustum: new Frustum(),
831
- autoUpdateFrustum: true,
832
- raycaster: null,
833
- events: {
834
- priority: 1,
835
- enabled: true,
836
- connected: false,
837
- frameTimedRaycasts: true,
838
- alwaysFireOnScroll: true,
839
- updateOnFrame: false
840
- },
841
- scene: null,
842
- rootScene: null,
843
- xr: null,
844
- inspector: null,
845
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
846
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
847
- textureColorSpace: SRGBColorSpace,
848
- isLegacy: false,
849
- webGPUSupported: false,
850
- isNative: false,
851
- controls: null,
852
- pointer,
853
- mouse: pointer,
854
- frameloop: "always",
855
- onPointerMissed: void 0,
856
- onDragOverMissed: void 0,
857
- onDropMissed: void 0,
858
- performance: {
859
- current: 1,
860
- min: 0.5,
861
- max: 1,
862
- debounce: 200,
863
- regress: () => {
864
- const state2 = get();
865
- if (performanceTimeout) clearTimeout(performanceTimeout);
866
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
867
- performanceTimeout = setTimeout(
868
- () => setPerformanceCurrent(get().performance.max),
869
- state2.performance.debounce
870
- );
871
- }
872
- },
873
- size: { width: 0, height: 0, top: 0, left: 0 },
874
- viewport: {
875
- initialDpr: 0,
876
- dpr: 0,
877
- width: 0,
878
- height: 0,
879
- top: 0,
880
- left: 0,
881
- aspect: 0,
882
- distance: 0,
883
- factor: 0,
884
- getCurrentViewport
885
- },
886
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
887
- setSize: (width, height, top, left) => {
888
- const state2 = get();
889
- if (width === void 0) {
890
- set({ _sizeImperative: false });
891
- if (state2._sizeProps) {
892
- const { width: propW, height: propH } = state2._sizeProps;
893
- if (propW !== void 0 || propH !== void 0) {
894
- const currentSize = state2.size;
895
- const newSize = {
896
- width: propW ?? currentSize.width,
897
- height: propH ?? currentSize.height,
898
- top: currentSize.top,
899
- left: currentSize.left
900
- };
901
- set((s) => ({
902
- size: newSize,
903
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
904
- }));
905
- getScheduler().invalidate();
912
+ const wrappedCallback = (frameState, delta) => {
913
+ const localState = store.getState();
914
+ const mergedState = {
915
+ ...localState,
916
+ time: frameState.time,
917
+ delta: frameState.delta,
918
+ elapsed: frameState.elapsed,
919
+ frame: frameState.frame
920
+ };
921
+ callbackRef.current?.(mergedState, delta);
922
+ };
923
+ const unregister = scheduler.register(wrappedCallback, {
924
+ id,
925
+ rootId,
926
+ ...options
927
+ });
928
+ return () => {
929
+ unregister();
930
+ if (isLegacyPriority) {
931
+ const currentState = store.getState();
932
+ if (currentState.internal) {
933
+ currentState.internal.priority--;
934
+ let parentRoot = currentState.previousRoot;
935
+ while (parentRoot) {
936
+ const parentState = parentRoot.getState();
937
+ if (parentState?.internal) parentState.internal.priority--;
938
+ parentRoot = parentState?.previousRoot;
906
939
  }
907
940
  }
908
- return;
909
941
  }
910
- const w = width;
911
- const h = height ?? width;
912
- const t = top ?? state2.size.top;
913
- const l = left ?? state2.size.left;
914
- const size = { width: w, height: h, top: t, left: l };
915
- set((s) => ({
916
- size,
917
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
918
- _sizeImperative: true
919
- }));
920
- getScheduler().invalidate();
942
+ };
943
+ } else {
944
+ return scheduler.register(
945
+ (state, delta) => {
946
+ const frameState = state;
947
+ if (!frameState.renderer) return;
948
+ callbackRef.current?.(frameState, delta);
949
+ },
950
+ { id, ...options }
951
+ );
952
+ }
953
+ }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
954
+ const isPaused = React.useSyncExternalStore(
955
+ // Subscribe function
956
+ React.useCallback(
957
+ (onStoreChange) => {
958
+ return getScheduler().subscribeJobState(id, onStoreChange);
921
959
  },
922
- setDpr: (dpr) => set((state2) => {
923
- const resolved = calculateDpr(dpr);
924
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
925
- }),
926
- setFrameloop: (frameloop = "always") => {
927
- set(() => ({ frameloop }));
928
- getScheduler().frameloop = frameloop;
960
+ [id]
961
+ ),
962
+ // getSnapshot function
963
+ React.useCallback(() => getScheduler().isJobPaused(id), [id]),
964
+ // getServerSnapshot function (SSR)
965
+ React.useCallback(() => false, [])
966
+ );
967
+ const controls = React.useMemo(() => {
968
+ const scheduler2 = getScheduler();
969
+ return {
970
+ /** The job's unique ID */
971
+ id,
972
+ /**
973
+ * Access to the global scheduler for frame loop control.
974
+ * Use for controlling the entire frame loop, adding phases, etc.
975
+ */
976
+ scheduler: scheduler2,
977
+ /**
978
+ * The root that currently owns this job. Resolve on access because ambient
979
+ * jobs can be adopted by a Canvas after the controls object is created.
980
+ */
981
+ get rootId() {
982
+ return getScheduler().getJobRootId(id);
929
983
  },
930
- setError: (error) => set(() => ({ error })),
931
- error: null,
932
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
933
- uniforms: {},
934
- nodes: {},
935
- buffers: {},
936
- gpuStorage: {},
937
- textures: /* @__PURE__ */ new Map(),
938
- _textureRefs: /* @__PURE__ */ new Map(),
939
- renderPipeline: null,
940
- passes: {},
941
- _hmrVersion: 0,
942
- _sizeImperative: false,
943
- _sizeProps: null,
944
- previousRoot: void 0,
945
- internal: {
946
- // Events
947
- interaction: [],
948
- subscribers: [],
949
- // Per-pointer state (new unified structure)
950
- pointerMap: /* @__PURE__ */ new Map(),
951
- pointerDirty: /* @__PURE__ */ new Map(),
952
- lastEvent: React.createRef(),
953
- // Deprecated but kept for backwards compatibility
954
- hovered: /* @__PURE__ */ new Map(),
955
- initialClick: [0, 0],
956
- initialHits: [],
957
- capturedMap: /* @__PURE__ */ new Map(),
958
- // Visibility tracking (onFramed, onOccluded, onVisible)
959
- visibilityRegistry: /* @__PURE__ */ new Map(),
960
- // Occlusion system (WebGPU only)
961
- occlusionEnabled: false,
962
- occlusionObserver: null,
963
- occlusionCache: /* @__PURE__ */ new Map(),
964
- helperGroup: null,
965
- // Updates
966
- active: false,
967
- frames: 0,
968
- priority: 0,
969
- subscribe: (ref, priority, store) => {
970
- const internal = get().internal;
971
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
972
- internal.subscribers.push({ ref, priority, store });
973
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
974
- return () => {
975
- const internal2 = get().internal;
976
- if (internal2?.subscribers) {
977
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
978
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
979
- }
980
- };
981
- },
982
- // Renderer Storage (single source of truth)
983
- actualRenderer: null,
984
- // Scheduler for useFrameNext (initialized in renderer.tsx)
985
- scheduler: null
986
- }
984
+ /**
985
+ * Manually step this job only.
986
+ * Bypasses FPS limiting - always runs.
987
+ * @param timestamp Optional timestamp (defaults to performance.now())
988
+ */
989
+ step: (timestamp) => {
990
+ getScheduler().stepJob(id, timestamp);
991
+ },
992
+ /**
993
+ * Manually step ALL jobs in the scheduler.
994
+ * Useful for frameloop='never' mode.
995
+ * @param timestamp Optional timestamp (defaults to performance.now())
996
+ */
997
+ stepAll: (timestamp) => {
998
+ getScheduler().step(timestamp);
999
+ },
1000
+ /**
1001
+ * Request frames for this job's owning root without waking sibling roots.
1002
+ */
1003
+ invalidate: (frames, stackFrames) => {
1004
+ const scheduler3 = getScheduler();
1005
+ const rootId = scheduler3.getJobRootId(id);
1006
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1007
+ },
1008
+ /**
1009
+ * Pause this job (set enabled=false).
1010
+ * Job remains registered but won't run.
1011
+ */
1012
+ pause: () => {
1013
+ getScheduler().pauseJob(id);
1014
+ },
1015
+ /**
1016
+ * Resume this job (set enabled=true).
1017
+ */
1018
+ resume: () => {
1019
+ getScheduler().resumeJob(id);
1020
+ },
1021
+ /**
1022
+ * Reactive paused state - automatically updates when pause/resume is called.
1023
+ * No need for forceUpdate() in your components.
1024
+ */
1025
+ isPaused
987
1026
  };
988
- return rootState;
989
- });
990
- const state = rootStore.getState();
991
- Object.defineProperty(state, "gl", {
992
- get() {
993
- const currentState = rootStore.getState();
994
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
995
- const stack = new Error().stack || "";
996
- if (!isInternalRendererAccess(stack)) {
997
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
998
- notifyDepreciated({
999
- heading: "Accessing state.gl in WebGPU mode",
1000
- 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
1001
- });
1002
- }
1003
- }
1004
- return currentState.internal.actualRenderer;
1005
- },
1006
- set(value) {
1007
- rootStore.getState().internal.actualRenderer = value;
1008
- },
1009
- enumerable: true,
1010
- configurable: true
1011
- });
1012
- Object.defineProperty(state, "renderer", {
1013
- get() {
1014
- return rootStore.getState().internal.actualRenderer;
1015
- },
1016
- set(value) {
1017
- rootStore.getState().internal.actualRenderer = value;
1018
- },
1019
- enumerable: true,
1020
- configurable: true
1021
- });
1022
- let oldScene = state.scene;
1023
- rootStore.subscribe(() => {
1024
- const currentState = rootStore.getState();
1025
- const { scene, rootScene, set } = currentState;
1026
- if (scene !== oldScene) {
1027
- oldScene = scene;
1028
- if (scene?.isScene && scene !== rootScene) {
1029
- set({ rootScene: scene });
1030
- }
1031
- }
1032
- });
1033
- let oldSize = state.size;
1034
- let oldDpr = state.viewport.dpr;
1035
- let oldCamera = state.camera;
1036
- rootStore.subscribe(() => {
1037
- const { camera, size, viewport, set, internal } = rootStore.getState();
1038
- const actualRenderer = internal.actualRenderer;
1039
- const canvasTarget = internal.canvasTarget;
1040
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1041
- oldSize = size;
1042
- oldDpr = viewport.dpr;
1043
- updateCamera(camera, size);
1044
- if (internal.isSecondary && canvasTarget) {
1045
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1046
- canvasTarget.setSize(size.width, size.height, false);
1047
- } else {
1048
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1049
- actualRenderer.setSize(size.width, size.height, false);
1050
- if (canvasTarget) {
1051
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1052
- canvasTarget.setSize(size.width, size.height, false);
1053
- }
1054
- }
1055
- }
1056
- if (camera !== oldCamera) {
1057
- oldCamera = camera;
1058
- const { rootScene } = rootStore.getState();
1059
- if (camera && rootScene && !camera.parent) {
1060
- rootScene.add(camera);
1061
- }
1062
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1063
- const currentState = rootStore.getState();
1064
- if (currentState.autoUpdateFrustum && camera) {
1065
- updateFrustum(camera, currentState.frustum);
1066
- }
1067
- }
1068
- });
1069
- rootStore.subscribe((state2) => invalidate(state2));
1070
- return rootStore;
1071
- };
1027
+ }, [id, isPaused]);
1028
+ return controls;
1029
+ }
1072
1030
 
1073
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1074
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1075
- function getLoader$1(Proto) {
1076
- if (isConstructor$1(Proto)) {
1077
- let loader = memoizedLoaders.get(Proto);
1078
- if (!loader) {
1079
- loader = new Proto();
1080
- memoizedLoaders.set(Proto, loader);
1031
+ const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1032
+ function getUrls(input) {
1033
+ if (typeof input === "string") return [input];
1034
+ if (Array.isArray(input)) return input;
1035
+ return Object.values(input);
1036
+ }
1037
+ function inputSignature(input) {
1038
+ if (typeof input === "string") return input;
1039
+ if (Array.isArray(input)) return input.join("\0");
1040
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1041
+ }
1042
+ function allUrlsCached(urls, textureCache) {
1043
+ return urls.every((url) => textureCache.has(url));
1044
+ }
1045
+ function buildFromCache(input, textureCache) {
1046
+ if (typeof input === "string") {
1047
+ return textureCache.get(input);
1048
+ } else if (Array.isArray(input)) {
1049
+ return input.map((url) => textureCache.get(url));
1050
+ } else {
1051
+ const result = {};
1052
+ const objInput = input;
1053
+ for (const key in objInput) {
1054
+ result[key] = textureCache.get(objInput[key]);
1081
1055
  }
1082
- return loader;
1056
+ return result;
1083
1057
  }
1084
- return Proto;
1085
1058
  }
1086
- function loadingFn(extensions, onProgress) {
1087
- return function(Proto, input) {
1088
- const loader = getLoader$1(Proto);
1089
- if (extensions) extensions(loader);
1090
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1091
- return loader.loadAsync(input, onProgress).then((data) => {
1092
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1093
- return data;
1059
+ function useTexture(input, optionsOrOnLoad) {
1060
+ const renderer = useThree((state) => state.internal.actualRenderer);
1061
+ const store = useStore();
1062
+ const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1063
+ const { onLoad, cache = true } = options;
1064
+ const onLoadRef = useRef(onLoad);
1065
+ onLoadRef.current = onLoad;
1066
+ const onLoadCalledForRef = useRef(null);
1067
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1068
+ const stableInput = useMemo(() => input, [inputKey]);
1069
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1070
+ const cachedResult = useMemo(() => {
1071
+ if (!cache) return null;
1072
+ const textures = store.getState().textures;
1073
+ if (!allUrlsCached(urls, textures)) return null;
1074
+ return buildFromCache(stableInput, textures);
1075
+ }, [cache, urls, stableInput, store]);
1076
+ const loadedTextures = useLoader(
1077
+ TextureLoader,
1078
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1079
+ );
1080
+ const mappedTextures = useMemo(() => {
1081
+ if (cachedResult) return cachedResult;
1082
+ if (IsObject(stableInput)) {
1083
+ const keyed = {};
1084
+ const textureArray = loadedTextures;
1085
+ let i = 0;
1086
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1087
+ return keyed;
1088
+ } else {
1089
+ return loadedTextures;
1090
+ }
1091
+ }, [stableInput, loadedTextures, cachedResult]);
1092
+ useLayoutEffect(() => {
1093
+ if (cachedResult) return;
1094
+ if (onLoadCalledForRef.current === inputKey) return;
1095
+ onLoadCalledForRef.current = inputKey;
1096
+ onLoadRef.current?.(mappedTextures);
1097
+ }, [cachedResult, mappedTextures, inputKey]);
1098
+ useEffect(() => {
1099
+ if (cachedResult) return;
1100
+ if ("initTexture" in renderer) {
1101
+ let textureArray = [];
1102
+ if (Array.isArray(loadedTextures)) {
1103
+ textureArray = loadedTextures;
1104
+ } else if (loadedTextures instanceof Texture$1) {
1105
+ textureArray = [loadedTextures];
1106
+ } else if (IsObject(loadedTextures)) {
1107
+ textureArray = Object.values(loadedTextures);
1108
+ }
1109
+ textureArray.forEach((texture) => {
1110
+ if (texture instanceof Texture$1) {
1111
+ renderer.initTexture(texture);
1112
+ }
1094
1113
  });
1095
1114
  }
1096
- return new Promise(
1097
- (res, reject) => loader.load(
1098
- input,
1099
- (data) => {
1100
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1101
- res(data);
1102
- },
1103
- onProgress,
1104
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1105
- )
1106
- );
1107
- };
1108
- }
1109
- function useLoader(loader, input, extensions, onProgress, cacheKey) {
1110
- const inputs = Array.isArray(input) ? input : [input];
1111
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1112
- const fn = loadingFn(extensions, onProgress);
1113
- const results = cacheKeys.map(
1114
- (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1115
- );
1116
- return Array.isArray(input) ? results : results[0];
1115
+ }, [renderer, loadedTextures, cachedResult]);
1116
+ useEffect(() => {
1117
+ if (!cache) return;
1118
+ const urlTextureMap = [];
1119
+ if (typeof stableInput === "string") {
1120
+ urlTextureMap.push([stableInput, mappedTextures]);
1121
+ } else if (Array.isArray(stableInput)) {
1122
+ const textureArray = mappedTextures;
1123
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1124
+ } else if (IsObject(stableInput)) {
1125
+ const textureRecord = mappedTextures;
1126
+ for (const key in stableInput) {
1127
+ const url = stableInput[key];
1128
+ urlTextureMap.push([url, textureRecord[key]]);
1129
+ }
1130
+ }
1131
+ store.setState((state) => {
1132
+ const refs = new Map(state._textureRefs);
1133
+ let textures = state.textures;
1134
+ let added = false;
1135
+ for (const [url, texture] of urlTextureMap) {
1136
+ if (!textures.has(url)) {
1137
+ if (!added) {
1138
+ textures = new Map(textures);
1139
+ added = true;
1140
+ }
1141
+ textures.set(url, texture);
1142
+ }
1143
+ refs.set(url, (refs.get(url) ?? 0) + 1);
1144
+ }
1145
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1146
+ });
1147
+ return () => store.setState((state) => {
1148
+ const refs = new Map(state._textureRefs);
1149
+ for (const [url] of urlTextureMap) {
1150
+ const next = (refs.get(url) ?? 0) - 1;
1151
+ if (next <= 0) refs.delete(url);
1152
+ else refs.set(url, next);
1153
+ }
1154
+ return { _textureRefs: refs };
1155
+ });
1156
+ }, [cache, stableInput, mappedTextures, store]);
1157
+ return mappedTextures;
1117
1158
  }
1118
- useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1119
- const inputs = Array.isArray(input) ? input : [input];
1120
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1121
- cacheKeys.forEach(
1122
- (key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
1123
- );
1124
- };
1125
- useLoader.clear = function(loader, input, cacheKey) {
1126
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1127
- cacheKeys.forEach((key) => clear([loader, key]));
1128
- };
1129
- useLoader.loader = getLoader$1;
1130
-
1131
- function useFrame(callback, priorityOrOptions) {
1132
- const store = React.useContext(context);
1133
- const isInsideCanvas = store !== null;
1134
- const scheduler = getScheduler();
1135
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1136
- id: priorityOrOptions.id,
1137
- phase: priorityOrOptions.phase,
1138
- priority: priorityOrOptions.priority,
1139
- fps: priorityOrOptions.fps,
1140
- drop: priorityOrOptions.drop,
1141
- enabled: priorityOrOptions.enabled,
1142
- before: priorityOrOptions.before,
1143
- after: priorityOrOptions.after
1144
- }) : "";
1145
- const options = React.useMemo(() => {
1146
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
1147
- }, [optionsKey]);
1148
- const reactId = React.useId();
1149
- const id = options.id ?? reactId;
1150
- const callbackRef = useMutableCallback(callback);
1151
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
1152
- useIsomorphicLayoutEffect(() => {
1153
- if (!callback) return;
1154
- if (isInsideCanvas) {
1155
- const state = store.getState();
1156
- const rootId = state.internal.rootId;
1157
- if (isLegacyPriority) {
1158
- state.internal.priority++;
1159
- let parentRoot = state.previousRoot;
1160
- while (parentRoot) {
1161
- const parentState = parentRoot.getState();
1162
- if (parentState?.internal) parentState.internal.priority++;
1163
- parentRoot = parentState?.previousRoot;
1164
- }
1165
- notifyDepreciated({
1166
- heading: "useFrame with numeric priority is deprecated",
1167
- body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
1168
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
1169
- });
1170
- }
1171
- const wrappedCallback = (frameState, delta) => {
1172
- const localState = store.getState();
1173
- const mergedState = {
1174
- ...localState,
1175
- time: frameState.time,
1176
- delta: frameState.delta,
1177
- elapsed: frameState.elapsed,
1178
- frame: frameState.frame
1179
- };
1180
- callbackRef.current?.(mergedState, delta);
1181
- };
1182
- const unregister = scheduler.register(wrappedCallback, {
1183
- id,
1184
- rootId,
1185
- ...options
1186
- });
1187
- return () => {
1188
- unregister();
1189
- if (isLegacyPriority) {
1190
- const currentState = store.getState();
1191
- if (currentState.internal) {
1192
- currentState.internal.priority--;
1193
- let parentRoot = currentState.previousRoot;
1194
- while (parentRoot) {
1195
- const parentState = parentRoot.getState();
1196
- if (parentState?.internal) parentState.internal.priority--;
1197
- parentRoot = parentState?.previousRoot;
1198
- }
1199
- }
1200
- }
1201
- };
1202
- } else {
1203
- return scheduler.register(
1204
- (state, delta) => {
1205
- const frameState = state;
1206
- if (!frameState.renderer) return;
1207
- callbackRef.current?.(frameState, delta);
1208
- },
1209
- { id, ...options }
1210
- );
1211
- }
1212
- }, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
1213
- const isPaused = React.useSyncExternalStore(
1214
- // Subscribe function
1215
- React.useCallback(
1216
- (onStoreChange) => {
1217
- return getScheduler().subscribeJobState(id, onStoreChange);
1218
- },
1219
- [id]
1220
- ),
1221
- // getSnapshot function
1222
- React.useCallback(() => getScheduler().isJobPaused(id), [id]),
1223
- // getServerSnapshot function (SSR)
1224
- React.useCallback(() => false, [])
1225
- );
1226
- const controls = React.useMemo(() => {
1227
- const scheduler2 = getScheduler();
1228
- return {
1229
- /** The job's unique ID */
1230
- id,
1231
- /**
1232
- * Access to the global scheduler for frame loop control.
1233
- * Use for controlling the entire frame loop, adding phases, etc.
1234
- */
1235
- scheduler: scheduler2,
1236
- /**
1237
- * Manually step this job only.
1238
- * Bypasses FPS limiting - always runs.
1239
- * @param timestamp Optional timestamp (defaults to performance.now())
1240
- */
1241
- step: (timestamp) => {
1242
- getScheduler().stepJob(id, timestamp);
1243
- },
1244
- /**
1245
- * Manually step ALL jobs in the scheduler.
1246
- * Useful for frameloop='never' mode.
1247
- * @param timestamp Optional timestamp (defaults to performance.now())
1248
- */
1249
- stepAll: (timestamp) => {
1250
- getScheduler().step(timestamp);
1251
- },
1252
- /**
1253
- * Pause this job (set enabled=false).
1254
- * Job remains registered but won't run.
1255
- */
1256
- pause: () => {
1257
- getScheduler().pauseJob(id);
1258
- },
1259
- /**
1260
- * Resume this job (set enabled=true).
1261
- */
1262
- resume: () => {
1263
- getScheduler().resumeJob(id);
1264
- },
1265
- /**
1266
- * Reactive paused state - automatically updates when pause/resume is called.
1267
- * No need for forceUpdate() in your components.
1268
- */
1269
- isPaused
1270
- };
1271
- }, [id, isPaused]);
1272
- return controls;
1273
- }
1274
-
1275
- const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1276
- function getUrls(input) {
1277
- if (typeof input === "string") return [input];
1278
- if (Array.isArray(input)) return input;
1279
- return Object.values(input);
1280
- }
1281
- function allUrlsCached(urls, textureCache) {
1282
- return urls.every((url) => textureCache.has(url));
1283
- }
1284
- function buildFromCache(input, textureCache) {
1285
- if (typeof input === "string") {
1286
- return textureCache.get(input);
1287
- } else if (Array.isArray(input)) {
1288
- return input.map((url) => textureCache.get(url));
1289
- } else {
1290
- const result = {};
1291
- const objInput = input;
1292
- for (const key in objInput) {
1293
- result[key] = textureCache.get(objInput[key]);
1294
- }
1295
- return result;
1296
- }
1297
- }
1298
- function useTexture(input, optionsOrOnLoad) {
1299
- const renderer = useThree((state) => state.internal.actualRenderer);
1300
- const store = useStore();
1301
- const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1302
- const { onLoad, cache = true } = options;
1303
- const onLoadRef = useRef(onLoad);
1304
- onLoadRef.current = onLoad;
1305
- const onLoadCalledForRef = useRef(null);
1306
- const urls = useMemo(() => getUrls(input), [input]);
1307
- const cachedResult = useMemo(() => {
1308
- if (!cache) return null;
1309
- const textures = store.getState().textures;
1310
- if (!allUrlsCached(urls, textures)) return null;
1311
- return buildFromCache(input, textures);
1312
- }, [cache, urls, input, store]);
1313
- const loadedTextures = useLoader(
1314
- TextureLoader,
1315
- IsObject(input) ? Object.values(input) : input
1316
- );
1317
- const inputKey = urls.join("\0");
1318
- useLayoutEffect(() => {
1319
- if (cachedResult) return;
1320
- if (onLoadCalledForRef.current === inputKey) return;
1321
- onLoadCalledForRef.current = inputKey;
1322
- onLoadRef.current?.(loadedTextures);
1323
- }, [cachedResult, loadedTextures, inputKey]);
1324
- useEffect(() => {
1325
- if (cachedResult) return;
1326
- if ("initTexture" in renderer) {
1327
- let textureArray = [];
1328
- if (Array.isArray(loadedTextures)) {
1329
- textureArray = loadedTextures;
1330
- } else if (loadedTextures instanceof Texture$1) {
1331
- textureArray = [loadedTextures];
1332
- } else if (IsObject(loadedTextures)) {
1333
- textureArray = Object.values(loadedTextures);
1334
- }
1335
- textureArray.forEach((texture) => {
1336
- if (texture instanceof Texture$1) {
1337
- renderer.initTexture(texture);
1338
- }
1339
- });
1340
- }
1341
- }, [renderer, loadedTextures, cachedResult]);
1342
- const mappedTextures = useMemo(() => {
1343
- if (cachedResult) return cachedResult;
1344
- if (IsObject(input)) {
1345
- const keyed = {};
1346
- const textureArray = loadedTextures;
1347
- let i = 0;
1348
- for (const key in input) keyed[key] = textureArray[i++];
1349
- return keyed;
1350
- } else {
1351
- return loadedTextures;
1352
- }
1353
- }, [input, loadedTextures, cachedResult]);
1354
- useEffect(() => {
1355
- if (!cache) return;
1356
- const urlTextureMap = [];
1357
- if (typeof input === "string") {
1358
- urlTextureMap.push([input, mappedTextures]);
1359
- } else if (Array.isArray(input)) {
1360
- const textureArray = mappedTextures;
1361
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1362
- } else if (IsObject(input)) {
1363
- const textureRecord = mappedTextures;
1364
- for (const key in input) {
1365
- const url = input[key];
1366
- urlTextureMap.push([url, textureRecord[key]]);
1367
- }
1368
- }
1369
- store.setState((state) => {
1370
- const refs = new Map(state._textureRefs);
1371
- let textures = state.textures;
1372
- let added = false;
1373
- for (const [url, texture] of urlTextureMap) {
1374
- if (!textures.has(url)) {
1375
- if (!added) {
1376
- textures = new Map(textures);
1377
- added = true;
1378
- }
1379
- textures.set(url, texture);
1380
- }
1381
- refs.set(url, (refs.get(url) ?? 0) + 1);
1382
- }
1383
- return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1384
- });
1385
- return () => store.setState((state) => {
1386
- const refs = new Map(state._textureRefs);
1387
- for (const [url] of urlTextureMap) {
1388
- const next = (refs.get(url) ?? 0) - 1;
1389
- if (next <= 0) refs.delete(url);
1390
- else refs.set(url, next);
1391
- }
1392
- return { _textureRefs: refs };
1393
- });
1394
- }, [cache, input, mappedTextures, store]);
1395
- return mappedTextures;
1396
- }
1397
- useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
1398
- useTexture.clear = (input) => useLoader.clear(TextureLoader, input);
1399
- const Texture = ({
1400
- children,
1401
- input,
1402
- onLoad,
1403
- cache
1404
- }) => {
1405
- const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1406
- const ret = useTexture(input, options);
1407
- return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
1159
+ useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
1160
+ useTexture.clear = (input) => useLoader.clear(TextureLoader, input);
1161
+ const Texture = ({
1162
+ children,
1163
+ input,
1164
+ onLoad,
1165
+ cache
1166
+ }) => {
1167
+ const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1168
+ const ret = useTexture(input, options);
1169
+ return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
1408
1170
  };
1409
1171
 
1410
1172
  function useTextures(selector) {
@@ -1554,7 +1316,7 @@ function useEnvironment({
1554
1316
  texture = texture.renderTarget?.texture;
1555
1317
  }
1556
1318
  texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
1557
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1319
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1558
1320
  return texture;
1559
1321
  }
1560
1322
  const preloadDefaultOptions = {
@@ -1607,22 +1369,15 @@ function getExtension(files) {
1607
1369
  const isCubemap = isArray(files) && files.length === 6;
1608
1370
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1609
1371
  const firstEntry = isArray(files) ? files[0] : files;
1610
- 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();
1372
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1373
+ const extension = isCubemap ? firstExtension === "hdr" ? "hdr-cube" : "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstExtension;
1611
1374
  return { extension, isCubemap, isGainmap };
1612
1375
  }
1613
1376
  function getLoader(extension) {
1614
- const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1377
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1615
1378
  return loader;
1616
1379
  }
1617
1380
 
1618
- function useStore() {
1619
- const store = useContext(context);
1620
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1621
- return store;
1622
- }
1623
- function useThree(selector = (state) => state, equalityFn) {
1624
- return useStore()(selector, equalityFn);
1625
- }
1626
1381
  function useInstanceHandle(ref) {
1627
1382
  const instance = useRef(null);
1628
1383
  useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1662,13 +1417,19 @@ function addTail(callback) {
1662
1417
  return getScheduler().onIdle(callback);
1663
1418
  }
1664
1419
  function invalidate(state, frames = 1, stackFrames = false) {
1665
- getScheduler().invalidate(frames, stackFrames);
1420
+ const rootId = state?.internal?.rootId;
1421
+ const scheduler = getScheduler();
1422
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1423
+ else scheduler.invalidate(frames, stackFrames);
1666
1424
  }
1667
- function advance(timestamp) {
1668
- getScheduler().step(timestamp);
1425
+ function advance(timestamp, runGlobalEffects, state) {
1426
+ const rootId = state?.internal?.rootId;
1427
+ const scheduler = getScheduler();
1428
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1429
+ else scheduler.step(timestamp);
1669
1430
  }
1670
1431
 
1671
- const version = "10.0.0-alpha.3";
1432
+ const version = "10.0.0-alpha.5";
1672
1433
  const packageData = {
1673
1434
  version: version};
1674
1435
 
@@ -13527,7 +13288,10 @@ function extend(objects) {
13527
13288
  catalogue[Component] = objects;
13528
13289
  return Component;
13529
13290
  } else {
13530
- Object.assign(catalogue, objects);
13291
+ for (const name in objects) {
13292
+ const object = objects[name];
13293
+ if (isConstructor(object)) catalogue[name] = object;
13294
+ }
13531
13295
  }
13532
13296
  }
13533
13297
  function validateInstance(type, props) {
@@ -13875,7 +13639,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13875
13639
  getRootHostContext: () => NO_CONTEXT,
13876
13640
  getChildHostContext: () => NO_CONTEXT,
13877
13641
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13878
- validateInstance(type, newProps);
13642
+ validateInstance(instance.type, newProps);
13879
13643
  let reconstruct = false;
13880
13644
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13881
13645
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13890,162 +13654,491 @@ const reconciler = /* @__PURE__ */ createReconciler({
13890
13654
  else delete instance.props.attach;
13891
13655
  if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13892
13656
  }
13893
- },
13894
- finalizeInitialChildren: (instance) => {
13895
- for (const prop in instance.props) {
13896
- if (isFromRef(instance.props[prop])) return true;
13657
+ },
13658
+ finalizeInitialChildren: (instance) => {
13659
+ for (const prop in instance.props) {
13660
+ if (isFromRef(instance.props[prop])) return true;
13661
+ }
13662
+ return false;
13663
+ },
13664
+ commitMount(instance) {
13665
+ const resolved = {};
13666
+ for (const prop in instance.props) {
13667
+ const value = instance.props[prop];
13668
+ if (isFromRef(value)) {
13669
+ const { [FROM_REF]: ref, transform } = value;
13670
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13671
+ }
13672
+ }
13673
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13674
+ },
13675
+ getPublicInstance: (instance) => instance?.object,
13676
+ prepareForCommit: () => null,
13677
+ preparePortalMount: (container) => {
13678
+ const target = container.getState().internal.container ?? container.getState().scene;
13679
+ return prepare(target, container, "", {});
13680
+ },
13681
+ // Reconstructed instances are swapped once all mutations are committed,
13682
+ // before layout effects run so refs point to the new objects
13683
+ resetAfterCommit: flushReconstructedInstances,
13684
+ shouldSetTextContent: () => false,
13685
+ clearContainer: () => false,
13686
+ hideInstance,
13687
+ unhideInstance,
13688
+ createTextInstance: handleTextInstance,
13689
+ hideTextInstance: handleTextInstance,
13690
+ unhideTextInstance: handleTextInstance,
13691
+ // Mirrors react-dom, which flushes discrete work in microtasks
13692
+ supportsMicrotasks: true,
13693
+ scheduleMicrotask,
13694
+ scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13695
+ cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13696
+ noTimeout: -1,
13697
+ getInstanceFromNode: () => null,
13698
+ beforeActiveInstanceBlur() {
13699
+ },
13700
+ afterActiveInstanceBlur() {
13701
+ },
13702
+ detachDeletedInstance() {
13703
+ },
13704
+ prepareScopeUpdate() {
13705
+ },
13706
+ getInstanceFromScope: () => null,
13707
+ shouldAttemptEagerTransition: () => false,
13708
+ trackSchedulerEvent: () => {
13709
+ },
13710
+ resolveEventType: () => null,
13711
+ resolveEventTimeStamp: () => -1.1,
13712
+ requestPostPaintCallback() {
13713
+ },
13714
+ maySuspendCommit: () => false,
13715
+ preloadInstance: () => true,
13716
+ // true indicates already loaded
13717
+ suspendInstance() {
13718
+ },
13719
+ waitForCommitToBeReady: () => null,
13720
+ NotPendingTransition: null,
13721
+ // The reconciler types use the internal ReactContext with all the hidden properties
13722
+ // so we have to cast from the public React.Context type
13723
+ HostTransitionContext: /* @__PURE__ */ React.createContext(
13724
+ null
13725
+ ),
13726
+ setCurrentUpdatePriority(newPriority) {
13727
+ currentUpdatePriority = newPriority;
13728
+ },
13729
+ getCurrentUpdatePriority() {
13730
+ return currentUpdatePriority;
13731
+ },
13732
+ resolveUpdatePriority() {
13733
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13734
+ const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13735
+ if (eventType === void 0) return r;
13736
+ return getEventPriority(eventType);
13737
+ },
13738
+ resetFormInstance() {
13739
+ },
13740
+ // @ts-ignore DefinitelyTyped is not up to date
13741
+ rendererPackageName: "@react-three/fiber",
13742
+ rendererVersion: packageData.version,
13743
+ // 19.2 Reconciler
13744
+ // https://github.com/facebook/react/pull/31975
13745
+ // https://github.com/facebook/react/pull/31999
13746
+ applyViewTransitionName(_instance, _name, _className) {
13747
+ },
13748
+ restoreViewTransitionName(_instance, _props) {
13749
+ },
13750
+ cancelViewTransitionName(_instance, _name, _props) {
13751
+ },
13752
+ cancelRootViewTransitionName(_rootContainer) {
13753
+ },
13754
+ restoreRootViewTransitionName(_rootContainer) {
13755
+ },
13756
+ InstanceMeasurement: null,
13757
+ measureInstance: (_instance) => null,
13758
+ wasInstanceInViewport: (_measurement) => true,
13759
+ hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
13760
+ hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
13761
+ // https://github.com/facebook/react/pull/32002
13762
+ // https://github.com/facebook/react/pull/34486
13763
+ suspendOnActiveViewTransition(_state, _container) {
13764
+ },
13765
+ // https://github.com/facebook/react/pull/32451
13766
+ // https://github.com/facebook/react/pull/32760
13767
+ startGestureTransition: () => null,
13768
+ startViewTransition: () => null,
13769
+ stopViewTransition(_transition) {
13770
+ },
13771
+ // https://github.com/facebook/react/pull/32038
13772
+ createViewTransitionInstance: (_name) => null,
13773
+ // https://github.com/facebook/react/pull/32379
13774
+ // https://github.com/facebook/react/pull/32786
13775
+ getCurrentGestureOffset(_provider) {
13776
+ throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
13777
+ },
13778
+ // https://github.com/facebook/react/pull/32500
13779
+ cloneMutableInstance(instance, _keepChildren) {
13780
+ return instance;
13781
+ },
13782
+ cloneMutableTextInstance(textInstance) {
13783
+ return textInstance;
13784
+ },
13785
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
13786
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
13787
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
13788
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
13789
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
13790
+ // Guarded by a test asserting a full render + unmount never reaches them.
13791
+ cloneRootViewTransitionContainer(_rootContainer) {
13792
+ },
13793
+ removeRootViewTransitionClone(_rootContainer, _clone) {
13794
+ },
13795
+ // https://github.com/facebook/react/pull/32465
13796
+ createFragmentInstance: (_fiber) => null,
13797
+ updateFragmentInstanceFiber(_fiber, _instance) {
13798
+ },
13799
+ commitNewChildToFragmentInstance(_child, _fragmentInstance) {
13800
+ },
13801
+ deleteChildFromFragmentInstance(_child, _fragmentInstance) {
13802
+ },
13803
+ // https://github.com/facebook/react/pull/32653
13804
+ measureClonedInstance: (_instance) => null,
13805
+ // https://github.com/facebook/react/pull/32819
13806
+ maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
13807
+ maySuspendCommitInSyncRender: (_type, _props) => false,
13808
+ // https://github.com/facebook/react/pull/34486
13809
+ startSuspendingCommit: () => null,
13810
+ // https://github.com/facebook/react/pull/34522
13811
+ getSuspendedCommitReason: (_state, _rootContainer) => null
13812
+ });
13813
+
13814
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
13815
+ function isInternalRendererAccess(stack, callerDepth = 2) {
13816
+ if (!stack) return false;
13817
+ const caller = stack.split("\n")[callerDepth] ?? "";
13818
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
13819
+ }
13820
+
13821
+ const createStore = (invalidate, advance) => {
13822
+ const rootStore = createWithEqualityFn((set, get) => {
13823
+ const position = new Vector3();
13824
+ const defaultTarget = new Vector3();
13825
+ const tempTarget = new Vector3();
13826
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
13827
+ const { width, height, top, left } = size;
13828
+ const aspect = width / height;
13829
+ if (target.isVector3) tempTarget.copy(target);
13830
+ else tempTarget.set(...target);
13831
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
13832
+ if (isOrthographicCamera(camera)) {
13833
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
13834
+ } else {
13835
+ const fov = camera.fov * Math.PI / 180;
13836
+ const h = 2 * Math.tan(fov / 2) * distance;
13837
+ const w = h * (width / height);
13838
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
13839
+ }
13840
+ }
13841
+ let performanceTimeout = void 0;
13842
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
13843
+ const pointer = new Vector2();
13844
+ const rootState = {
13845
+ set,
13846
+ get,
13847
+ // Mock objects that have to be configured
13848
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
13849
+ primaryStore: null,
13850
+ gl: null,
13851
+ renderer: null,
13852
+ camera: null,
13853
+ frustum: new Frustum(),
13854
+ autoUpdateFrustum: true,
13855
+ raycaster: null,
13856
+ events: {
13857
+ priority: 1,
13858
+ enabled: true,
13859
+ connected: false,
13860
+ frameTimedRaycasts: true,
13861
+ alwaysFireOnScroll: true,
13862
+ updateOnFrame: false
13863
+ },
13864
+ scene: null,
13865
+ rootScene: null,
13866
+ xr: null,
13867
+ inspector: null,
13868
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
13869
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
13870
+ textureColorSpace: SRGBColorSpace,
13871
+ isLegacy: false,
13872
+ webGPUSupported: false,
13873
+ isNative: false,
13874
+ controls: null,
13875
+ pointer,
13876
+ mouse: pointer,
13877
+ frameloop: "always",
13878
+ onPointerMissed: void 0,
13879
+ onDragOverMissed: void 0,
13880
+ onDropMissed: void 0,
13881
+ performance: {
13882
+ current: 1,
13883
+ min: 0.5,
13884
+ max: 1,
13885
+ debounce: 200,
13886
+ regress: () => {
13887
+ const state2 = get();
13888
+ if (performanceTimeout) clearTimeout(performanceTimeout);
13889
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
13890
+ performanceTimeout = setTimeout(
13891
+ () => setPerformanceCurrent(get().performance.max),
13892
+ state2.performance.debounce
13893
+ );
13894
+ }
13895
+ },
13896
+ size: { width: 0, height: 0, top: 0, left: 0 },
13897
+ viewport: {
13898
+ initialDpr: 0,
13899
+ dpr: 0,
13900
+ width: 0,
13901
+ height: 0,
13902
+ top: 0,
13903
+ left: 0,
13904
+ aspect: 0,
13905
+ distance: 0,
13906
+ factor: 0,
13907
+ getCurrentViewport
13908
+ },
13909
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
13910
+ setSize: (width, height, top, left) => {
13911
+ const state2 = get();
13912
+ if (width === void 0) {
13913
+ set({ _sizeImperative: false });
13914
+ if (state2._sizeProps) {
13915
+ const { width: propW, height: propH } = state2._sizeProps;
13916
+ if (propW !== void 0 || propH !== void 0) {
13917
+ const currentSize = state2.size;
13918
+ const newSize = {
13919
+ width: propW ?? currentSize.width,
13920
+ height: propH ?? currentSize.height,
13921
+ top: currentSize.top,
13922
+ left: currentSize.left
13923
+ };
13924
+ set((s) => ({
13925
+ size: newSize,
13926
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
13927
+ }));
13928
+ get().invalidate();
13929
+ }
13930
+ }
13931
+ return;
13932
+ }
13933
+ const w = width;
13934
+ const h = height ?? width;
13935
+ const t = top ?? state2.size.top;
13936
+ const l = left ?? state2.size.left;
13937
+ const size = { width: w, height: h, top: t, left: l };
13938
+ set((s) => ({
13939
+ size,
13940
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
13941
+ _sizeImperative: true
13942
+ }));
13943
+ get().invalidate();
13944
+ },
13945
+ setDpr: (dpr) => set((state2) => {
13946
+ const resolved = calculateDpr(dpr);
13947
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
13948
+ }),
13949
+ setFrameloop: (frameloop = "always") => {
13950
+ const rootId = get().internal.rootId;
13951
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
13952
+ set(() => ({ frameloop }));
13953
+ },
13954
+ setError: (error) => set(() => ({ error })),
13955
+ error: null,
13956
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
13957
+ uniforms: {},
13958
+ nodes: {},
13959
+ buffers: {},
13960
+ gpuStorage: {},
13961
+ textures: /* @__PURE__ */ new Map(),
13962
+ _textureRefs: /* @__PURE__ */ new Map(),
13963
+ renderPipeline: null,
13964
+ passes: {},
13965
+ _hmrVersion: 0,
13966
+ _sizeImperative: false,
13967
+ _sizeProps: null,
13968
+ previousRoot: void 0,
13969
+ internal: {
13970
+ // Events
13971
+ interaction: [],
13972
+ subscribers: [],
13973
+ // Per-pointer state (new unified structure)
13974
+ pointerMap: /* @__PURE__ */ new Map(),
13975
+ pointerDirty: /* @__PURE__ */ new Map(),
13976
+ lastEvent: React.createRef(),
13977
+ // Deprecated but kept for backwards compatibility
13978
+ hovered: /* @__PURE__ */ new Map(),
13979
+ initialClick: [0, 0],
13980
+ initialHits: [],
13981
+ capturedMap: /* @__PURE__ */ new Map(),
13982
+ // Visibility tracking (onFramed, onOccluded, onVisible)
13983
+ visibilityRegistry: /* @__PURE__ */ new Map(),
13984
+ // Occlusion system (WebGPU only)
13985
+ occlusionEnabled: false,
13986
+ occlusionObserver: null,
13987
+ occlusionCache: /* @__PURE__ */ new Map(),
13988
+ helperGroup: null,
13989
+ // Updates
13990
+ active: false,
13991
+ frames: 0,
13992
+ priority: 0,
13993
+ subscribe: (ref, priority, store) => {
13994
+ const internal = get().internal;
13995
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
13996
+ internal.subscribers.push({ ref, priority, store });
13997
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
13998
+ return () => {
13999
+ const internal2 = get().internal;
14000
+ if (internal2?.subscribers) {
14001
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
14002
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
14003
+ }
14004
+ };
14005
+ },
14006
+ // Renderer Storage (single source of truth)
14007
+ actualRenderer: null,
14008
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
14009
+ scheduler: null,
14010
+ // Replaces the default render call when set (see setRenderOverride)
14011
+ renderOverride: null
14012
+ }
14013
+ };
14014
+ return rootState;
14015
+ });
14016
+ const state = rootStore.getState();
14017
+ Object.defineProperty(state, "gl", {
14018
+ get() {
14019
+ const currentState = rootStore.getState();
14020
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
14021
+ const stack = new Error().stack || "";
14022
+ if (!isInternalRendererAccess(stack)) {
14023
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
14024
+ notifyDepreciated({
14025
+ heading: "Accessing state.gl in WebGPU mode",
14026
+ 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
14027
+ });
14028
+ }
14029
+ }
14030
+ return currentState.internal.actualRenderer;
14031
+ },
14032
+ set(value) {
14033
+ rootStore.getState().internal.actualRenderer = value;
14034
+ },
14035
+ enumerable: true,
14036
+ configurable: true
14037
+ });
14038
+ Object.defineProperty(state, "renderer", {
14039
+ get() {
14040
+ return rootStore.getState().internal.actualRenderer;
14041
+ },
14042
+ set(value) {
14043
+ rootStore.getState().internal.actualRenderer = value;
14044
+ },
14045
+ enumerable: true,
14046
+ configurable: true
14047
+ });
14048
+ let oldScene = state.scene;
14049
+ rootStore.subscribe(() => {
14050
+ const currentState = rootStore.getState();
14051
+ const { scene, rootScene, set } = currentState;
14052
+ if (scene !== oldScene) {
14053
+ oldScene = scene;
14054
+ if (scene?.isScene && scene !== rootScene) {
14055
+ set({ rootScene: scene });
14056
+ }
14057
+ }
14058
+ });
14059
+ let oldSize = state.size;
14060
+ let oldDpr = state.viewport.dpr;
14061
+ let oldCamera = state.camera;
14062
+ rootStore.subscribe(() => {
14063
+ const { camera, size, viewport, set, internal } = rootStore.getState();
14064
+ const actualRenderer = internal.actualRenderer;
14065
+ const canvasTarget = internal.canvasTarget;
14066
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
14067
+ oldSize = size;
14068
+ oldDpr = viewport.dpr;
14069
+ updateCamera(camera, size);
14070
+ const resizeTarget = canvasTarget ?? actualRenderer;
14071
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
14072
+ resizeTarget.setSize(size.width, size.height, false);
14073
+ const activeTarget = actualRenderer.getCanvasTarget?.();
14074
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
13897
14075
  }
13898
- return false;
13899
- },
13900
- commitMount(instance) {
13901
- const resolved = {};
13902
- for (const prop in instance.props) {
13903
- const value = instance.props[prop];
13904
- if (isFromRef(value)) {
13905
- const ref = value[FROM_REF];
13906
- if (ref.current != null) resolved[prop] = ref.current;
14076
+ if (camera !== oldCamera) {
14077
+ oldCamera = camera;
14078
+ const { rootScene } = rootStore.getState();
14079
+ if (camera && rootScene && !camera.parent) {
14080
+ rootScene.add(camera);
14081
+ }
14082
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
14083
+ const currentState = rootStore.getState();
14084
+ if (currentState.autoUpdateFrustum && camera) {
14085
+ updateFrustum(camera, currentState.frustum);
13907
14086
  }
13908
14087
  }
13909
- if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13910
- },
13911
- getPublicInstance: (instance) => instance?.object,
13912
- prepareForCommit: () => null,
13913
- preparePortalMount: (container) => {
13914
- const target = container.getState().internal.container ?? container.getState().scene;
13915
- return prepare(target, container, "", {});
13916
- },
13917
- // Reconstructed instances are swapped once all mutations are committed,
13918
- // before layout effects run so refs point to the new objects
13919
- resetAfterCommit: flushReconstructedInstances,
13920
- shouldSetTextContent: () => false,
13921
- clearContainer: () => false,
13922
- hideInstance,
13923
- unhideInstance,
13924
- createTextInstance: handleTextInstance,
13925
- hideTextInstance: handleTextInstance,
13926
- unhideTextInstance: handleTextInstance,
13927
- // Mirrors react-dom, which flushes discrete work in microtasks
13928
- supportsMicrotasks: true,
13929
- scheduleMicrotask,
13930
- scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13931
- cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13932
- noTimeout: -1,
13933
- getInstanceFromNode: () => null,
13934
- beforeActiveInstanceBlur() {
13935
- },
13936
- afterActiveInstanceBlur() {
13937
- },
13938
- detachDeletedInstance() {
13939
- },
13940
- prepareScopeUpdate() {
13941
- },
13942
- getInstanceFromScope: () => null,
13943
- shouldAttemptEagerTransition: () => false,
13944
- trackSchedulerEvent: () => {
13945
- },
13946
- resolveEventType: () => null,
13947
- resolveEventTimeStamp: () => -1.1,
13948
- requestPostPaintCallback() {
13949
- },
13950
- maySuspendCommit: () => false,
13951
- preloadInstance: () => true,
13952
- // true indicates already loaded
13953
- suspendInstance() {
13954
- },
13955
- waitForCommitToBeReady: () => null,
13956
- NotPendingTransition: null,
13957
- // The reconciler types use the internal ReactContext with all the hidden properties
13958
- // so we have to cast from the public React.Context type
13959
- HostTransitionContext: /* @__PURE__ */ React.createContext(
13960
- null
13961
- ),
13962
- setCurrentUpdatePriority(newPriority) {
13963
- currentUpdatePriority = newPriority;
13964
- },
13965
- getCurrentUpdatePriority() {
13966
- return currentUpdatePriority;
13967
- },
13968
- resolveUpdatePriority() {
13969
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13970
- const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13971
- if (eventType === void 0) return r;
13972
- return getEventPriority(eventType);
13973
- },
13974
- resetFormInstance() {
13975
- },
13976
- // @ts-ignore DefinitelyTyped is not up to date
13977
- rendererPackageName: "@react-three/fiber",
13978
- rendererVersion: packageData.version,
13979
- // 19.2 Reconciler
13980
- // https://github.com/facebook/react/pull/31975
13981
- // https://github.com/facebook/react/pull/31999
13982
- applyViewTransitionName(_instance, _name, _className) {
13983
- },
13984
- restoreViewTransitionName(_instance, _props) {
13985
- },
13986
- cancelViewTransitionName(_instance, _name, _props) {
13987
- },
13988
- cancelRootViewTransitionName(_rootContainer) {
13989
- },
13990
- restoreRootViewTransitionName(_rootContainer) {
13991
- },
13992
- InstanceMeasurement: null,
13993
- measureInstance: (_instance) => null,
13994
- wasInstanceInViewport: (_measurement) => true,
13995
- hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
13996
- hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
13997
- // https://github.com/facebook/react/pull/32002
13998
- // https://github.com/facebook/react/pull/34486
13999
- suspendOnActiveViewTransition(_state, _container) {
14000
- },
14001
- // https://github.com/facebook/react/pull/32451
14002
- // https://github.com/facebook/react/pull/32760
14003
- startGestureTransition: () => null,
14004
- startViewTransition: () => null,
14005
- stopViewTransition(_transition) {
14006
- },
14007
- // https://github.com/facebook/react/pull/32038
14008
- createViewTransitionInstance: (_name) => null,
14009
- // https://github.com/facebook/react/pull/32379
14010
- // https://github.com/facebook/react/pull/32786
14011
- getCurrentGestureOffset(_provider) {
14012
- throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
14013
- },
14014
- // https://github.com/facebook/react/pull/32500
14015
- cloneMutableInstance(instance, _keepChildren) {
14016
- return instance;
14017
- },
14018
- cloneMutableTextInstance(textInstance) {
14019
- return textInstance;
14020
- },
14021
- // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14022
- // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14023
- // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14024
- // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14025
- // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14026
- // Guarded by a test asserting a full render + unmount never reaches them.
14027
- cloneRootViewTransitionContainer(_rootContainer) {
14028
- },
14029
- removeRootViewTransitionClone(_rootContainer, _clone) {
14030
- },
14031
- // https://github.com/facebook/react/pull/32465
14032
- createFragmentInstance: (_fiber) => null,
14033
- updateFragmentInstanceFiber(_fiber, _instance) {
14034
- },
14035
- commitNewChildToFragmentInstance(_child, _fragmentInstance) {
14036
- },
14037
- deleteChildFromFragmentInstance(_child, _fragmentInstance) {
14038
- },
14039
- // https://github.com/facebook/react/pull/32653
14040
- measureClonedInstance: (_instance) => null,
14041
- // https://github.com/facebook/react/pull/32819
14042
- maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
14043
- maySuspendCommitInSyncRender: (_type, _props) => false,
14044
- // https://github.com/facebook/react/pull/34486
14045
- startSuspendingCommit: () => null,
14046
- // https://github.com/facebook/react/pull/34522
14047
- getSuspendedCommitReason: (_state, _rootContainer) => null
14048
- });
14088
+ });
14089
+ rootStore.subscribe((state2) => invalidate(state2));
14090
+ return rootStore;
14091
+ };
14092
+
14093
+ const R3F_EXTENSIONS = Symbol.for("@react-three/fiber.extensions");
14094
+ function getRegistry() {
14095
+ return globalThis[R3F_EXTENSIONS] ?? (globalThis[R3F_EXTENSIONS] = {
14096
+ extensions: /* @__PURE__ */ new Map(),
14097
+ roots: /* @__PURE__ */ new Set(),
14098
+ applied: /* @__PURE__ */ new WeakMap()
14099
+ });
14100
+ }
14101
+ function setupRoot(registry, store, extension) {
14102
+ let applied = registry.applied.get(store);
14103
+ if (!applied) registry.applied.set(store, applied = /* @__PURE__ */ new Map());
14104
+ if (applied.has(extension.name)) return;
14105
+ applied.set(extension.name, extension);
14106
+ const patch = extension.setup?.(store);
14107
+ if (patch) store.setState(patch);
14108
+ }
14109
+ function registerRootExtension(extension) {
14110
+ const registry = getRegistry();
14111
+ registry.extensions.set(extension.name, extension);
14112
+ for (const store of registry.roots) setupRoot(registry, store, extension);
14113
+ return () => {
14114
+ if (registry.extensions.get(extension.name) === extension) registry.extensions.delete(extension.name);
14115
+ };
14116
+ }
14117
+ function setRenderOverride(store, render) {
14118
+ store.getState().internal.renderOverride = render;
14119
+ }
14120
+ function attachRootExtensions(store) {
14121
+ const registry = getRegistry();
14122
+ registry.roots.add(store);
14123
+ for (const extension of registry.extensions.values()) setupRoot(registry, store, extension);
14124
+ }
14125
+ function current(registry, setUpBy) {
14126
+ return registry.extensions.get(setUpBy.name) ?? setUpBy;
14127
+ }
14128
+ function detachRootExtensions(store) {
14129
+ const registry = getRegistry();
14130
+ if (!registry.roots.delete(store)) return;
14131
+ const applied = registry.applied.get(store);
14132
+ registry.applied.delete(store);
14133
+ if (!applied) return;
14134
+ for (const setUpBy of applied.values()) current(registry, setUpBy).dispose?.(store);
14135
+ }
14136
+ function notifyRootExtensionsHmr(store) {
14137
+ const registry = getRegistry();
14138
+ const applied = registry.applied.get(store);
14139
+ if (!applied) return;
14140
+ for (const setUpBy of applied.values()) current(registry, setUpBy).hmr?.(store);
14141
+ }
14049
14142
 
14050
14143
  const isRenderer = (def) => !!def?.render;
14051
14144
  const _roots = /* @__PURE__ */ new Map();
@@ -14202,9 +14295,8 @@ function createRoot(canvas) {
14202
14295
  if (!renderer.hasInitialized?.()) {
14203
14296
  const size2 = computeInitialSize(canvas, propsSize);
14204
14297
  if (size2.width > 0 && size2.height > 0) {
14205
- const pixelRatio = calculateDpr(dpr);
14206
- canvas.width = size2.width * pixelRatio;
14207
- canvas.height = size2.height * pixelRatio;
14298
+ renderer.setPixelRatio(calculateDpr(dpr));
14299
+ renderer.setSize(size2.width, size2.height, false);
14208
14300
  }
14209
14301
  await renderer.init();
14210
14302
  }
@@ -14213,7 +14305,7 @@ function createRoot(canvas) {
14213
14305
  state.internal.actualRenderer = renderer;
14214
14306
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14215
14307
  if (canvasId && !state.internal.isSecondary) {
14216
- const canvasTarget = new CanvasTarget(canvas);
14308
+ const canvasTarget = renderer.getCanvasTarget?.();
14217
14309
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14218
14310
  state.set((prev) => ({
14219
14311
  internal: {
@@ -14327,7 +14419,7 @@ function createRoot(canvas) {
14327
14419
  const handleXRFrame = (timestamp, _frame) => {
14328
14420
  const state2 = store.getState();
14329
14421
  if (state2.frameloop === "never") return;
14330
- advance(timestamp);
14422
+ advance(timestamp, true, state2);
14331
14423
  };
14332
14424
  const actualRenderer = state.internal.actualRenderer;
14333
14425
  const handleSessionChange = () => {
@@ -14335,7 +14427,7 @@ function createRoot(canvas) {
14335
14427
  const renderer2 = state2.internal.actualRenderer;
14336
14428
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14337
14429
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14338
- if (!renderer2.xr.isPresenting) invalidate();
14430
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14339
14431
  };
14340
14432
  const xr = {
14341
14433
  connect() {
@@ -14417,14 +14509,23 @@ function createRoot(canvas) {
14417
14509
  const newRootId = canvasId || scheduler.generateRootId();
14418
14510
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14419
14511
  getState: () => store.getState(),
14420
- onError: (err) => store.getState().setError(err)
14512
+ onError: (err) => store.getState().setError(err),
14513
+ frameloop: store.getState().frameloop,
14514
+ before: schedulerConfig?.before,
14515
+ after: schedulerConfig?.after,
14516
+ order: schedulerConfig?.order
14421
14517
  });
14422
14518
  const unregisterCanvasTarget = scheduler.register(
14423
14519
  () => {
14424
14520
  const state2 = store.getState();
14425
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14521
+ const canvasTarget = state2.internal.canvasTarget;
14522
+ if (canvasTarget) {
14426
14523
  const renderer2 = state2.internal.actualRenderer;
14427
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14524
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14525
+ if (state2.internal.canvasTargetSizeDirty) {
14526
+ state2.internal.canvasTargetSizeDirty = false;
14527
+ renderer2.backend?.updateSize?.();
14528
+ }
14428
14529
  }
14429
14530
  },
14430
14531
  {
@@ -14480,7 +14581,8 @@ function createRoot(canvas) {
14480
14581
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14481
14582
  if (userHandlesRender || state2.internal.priority) return;
14482
14583
  try {
14483
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14584
+ const renderOverride = state2.internal.renderOverride;
14585
+ if (renderOverride) renderOverride();
14484
14586
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14485
14587
  } catch (error) {
14486
14588
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14493,8 +14595,7 @@ function createRoot(canvas) {
14493
14595
  phase: "render",
14494
14596
  system: true,
14495
14597
  // Internal flag: this is a system job, not user-controlled
14496
- // Apply scheduler config for render ordering and rate limiting
14497
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14598
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14498
14599
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14499
14600
  }
14500
14601
  );
@@ -14513,8 +14614,22 @@ function createRoot(canvas) {
14513
14614
  scheduler
14514
14615
  }
14515
14616
  }));
14617
+ } else {
14618
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14619
+ if (constraintsChanged) {
14620
+ scheduler.setRootConstraints(rootId, {
14621
+ before: schedulerConfig?.before,
14622
+ after: schedulerConfig?.after
14623
+ });
14624
+ }
14625
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14626
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14627
+ }
14516
14628
  }
14517
- scheduler.frameloop = frameloop;
14629
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14630
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14631
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14632
+ attachRootExtensions(store);
14518
14633
  onCreated = onCreatedCallback;
14519
14634
  configured = true;
14520
14635
  resolve();
@@ -14556,18 +14671,24 @@ function unmountComponentAtNode(canvas, callback) {
14556
14671
  const fiber = root?.fiber;
14557
14672
  if (fiber) {
14558
14673
  const state = root?.store.getState();
14559
- if (state) state.internal.active = false;
14674
+ if (state) {
14675
+ state.internal.active = false;
14676
+ const unregisterRoot = state.internal.unregisterRoot;
14677
+ if (unregisterRoot) {
14678
+ unregisterRoot();
14679
+ state.internal.unregisterRoot = void 0;
14680
+ }
14681
+ }
14560
14682
  reconciler.updateContainer(null, fiber, null, () => {
14683
+ if (root?.store) detachRootExtensions(root.store);
14561
14684
  if (state) {
14562
14685
  setTimeout(() => {
14563
14686
  try {
14564
14687
  const renderer = state.internal.actualRenderer;
14565
- const unregisterRoot = state.internal.unregisterRoot;
14566
- if (unregisterRoot) unregisterRoot();
14567
14688
  const unregisterPrimary = state.internal.unregisterPrimary;
14568
14689
  if (unregisterPrimary) unregisterPrimary();
14569
14690
  const canvasTarget = state.internal.canvasTarget;
14570
- if (canvasTarget?.dispose) canvasTarget.dispose();
14691
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14571
14692
  state.events.disconnect?.();
14572
14693
  cleanupHelperGroup(root.store);
14573
14694
  if (state.isLegacy && renderer) {
@@ -14708,16 +14829,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14708
14829
  const oldbg = target.background;
14709
14830
  const oldenv = target.environment;
14710
14831
  const oldSceneProps = {
14711
- // @ts-ignore
14712
14832
  backgroundBlurriness: target.backgroundBlurriness,
14713
- // @ts-ignore
14714
14833
  backgroundIntensity: target.backgroundIntensity,
14715
- // @ts-ignore
14716
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14717
- // @ts-ignore
14834
+ backgroundRotation: target.backgroundRotation.clone(),
14718
14835
  environmentIntensity: target.environmentIntensity,
14719
- // @ts-ignore
14720
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14836
+ environmentRotation: target.environmentRotation.clone()
14721
14837
  };
14722
14838
  if (background !== "only") target.environment = texture;
14723
14839
  if (background) target.background = texture;
@@ -14942,38 +15058,6 @@ function parseRendererConfig(rendererProp) {
14942
15058
  return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14943
15059
  }
14944
15060
 
14945
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14946
- const registries = /* @__PURE__ */ new WeakMap();
14947
- function getKindRegistry(store, kind) {
14948
- let registry = registries.get(store);
14949
- if (!registry) {
14950
- registry = {
14951
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14952
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14953
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14954
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14955
- };
14956
- registries.set(store, registry);
14957
- }
14958
- return registry[kind];
14959
- }
14960
- function invalidateResource(store, kind, scopeKey) {
14961
- const registry = getKindRegistry(store, kind);
14962
- {
14963
- registry.valid.clear();
14964
- registry.staged.clear();
14965
- }
14966
- }
14967
- function invalidateAllResources(store) {
14968
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14969
- }
14970
-
14971
- function clearHmrCaches(store) {
14972
- const primary = store.getState().primaryStore ?? store;
14973
- invalidateAllResources(primary);
14974
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14975
- }
14976
-
14977
15061
  function CanvasImpl({
14978
15062
  ref,
14979
15063
  children,
@@ -15011,9 +15095,9 @@ function CanvasImpl({
15011
15095
  React.useMemo(() => extend(THREE), []);
15012
15096
  const Bridge = useBridge();
15013
15097
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15014
- const hasInitialSizeRef = React.useRef(false);
15098
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15015
15099
  const measureConfig = React.useMemo(() => {
15016
- if (!hasInitialSizeRef.current) {
15100
+ if (!hasInitialSize) {
15017
15101
  return {
15018
15102
  ...resize,
15019
15103
  scroll: resize?.scroll ?? true,
@@ -15025,7 +15109,7 @@ function CanvasImpl({
15025
15109
  debounce: { scroll: 50, resize: 0 },
15026
15110
  ...resize
15027
15111
  };
15028
- }, [resize, hasInitialSizeRef.current]);
15112
+ }, [hasInitialSize, resize]);
15029
15113
  const [containerRef, containerRect] = useMeasure(measureConfig);
15030
15114
  const effectiveSize = React.useMemo(() => {
15031
15115
  let w = width ?? containerRect.width;
@@ -15041,9 +15125,7 @@ function CanvasImpl({
15041
15125
  left: containerRect.left
15042
15126
  };
15043
15127
  }, [width, height, containerRect, forceEven]);
15044
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15045
- hasInitialSizeRef.current = true;
15046
- }
15128
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15047
15129
  const canvasRef = React.useRef(null);
15048
15130
  const divRef = React.useRef(null);
15049
15131
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15157,6 +15239,7 @@ function CanvasImpl({
15157
15239
  const canvas = canvasRef.current;
15158
15240
  if (canvas) {
15159
15241
  return () => {
15242
+ if (canvas.isConnected) return;
15160
15243
  unmountComponentAtNode(canvas);
15161
15244
  root.current = null;
15162
15245
  };
@@ -15169,7 +15252,7 @@ function CanvasImpl({
15169
15252
  const handleHMR = () => {
15170
15253
  queueMicrotask(() => {
15171
15254
  const rootEntry = _roots.get(canvas);
15172
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15255
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15173
15256
  });
15174
15257
  };
15175
15258
  if (typeof import.meta !== "undefined" && import.meta.hot) {
@@ -15221,4 +15304,4 @@ function Canvas(props) {
15221
15304
 
15222
15305
  extend(THREE);
15223
15306
 
15224
- export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, reconciler, registerPrimary, removeInteractivity, resolve, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree, waitForPrimary };
15307
+ export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, reconciler, registerPrimary, registerRootExtension, removeInteractivity, resolve, setRenderOverride, swapInteractivity, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree, waitForPrimary };