@react-three/fiber 10.0.0-canary.da7a768 → 10.0.0-canary.dc0ec15

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.
@@ -10,6 +10,7 @@ const scheduler = require('@pmndrs/scheduler');
10
10
  const Tb = require('scheduler');
11
11
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
12
12
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
13
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
13
14
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
14
15
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
15
16
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -323,7 +324,6 @@ function prepare(target, root, type, props) {
323
324
  }
324
325
  function invalidateInstance(instance) {
325
326
  if (!instance.parent) return;
326
- instance.props.onUpdate?.(instance.object);
327
327
  const state = instance.root?.getState?.();
328
328
  if (state && state.internal.frames === 0) state.invalidate();
329
329
  }
@@ -539,8 +539,10 @@ function hasVisibilityHandlers(handlers) {
539
539
  }
540
540
 
541
541
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
542
- function fromRef(ref) {
543
- return { [FROM_REF]: ref };
542
+ function fromRef(ref, transform) {
543
+ const marker = { [FROM_REF]: ref };
544
+ if (transform) marker.transform = transform;
545
+ return marker;
544
546
  }
545
547
  function isFromRef(value) {
546
548
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -554,6 +556,63 @@ function isOnce(value) {
554
556
  return value !== null && typeof value === "object" && ONCE in value;
555
557
  }
556
558
 
559
+ const shownNotices = /* @__PURE__ */ new Set();
560
+ function notifyDepreciated({ heading, body, link }) {
561
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
562
+ return;
563
+ }
564
+ if (shownNotices.has(heading)) return;
565
+ shownNotices.add(heading);
566
+ const caller = getCallerFrame();
567
+ if (caller) {
568
+ console.log();
569
+ }
570
+ const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
571
+ if (caller) {
572
+ console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
573
+ }
574
+ if (body || link) {
575
+ let message = "";
576
+ if (body) message += body;
577
+ if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
578
+ console.warn(`%c${message}`, "font-weight: bold;");
579
+ }
580
+ }
581
+ function getCallerFrame(depth = 3) {
582
+ const stack = new Error().stack;
583
+ if (!stack) return null;
584
+ const lines = stack.split("\n");
585
+ const frame = lines[depth];
586
+ if (!frame) return null;
587
+ let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
588
+ if (!match) {
589
+ match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
590
+ }
591
+ if (!match) return null;
592
+ const [, fn, url, line] = match;
593
+ return {
594
+ functionName: fn ?? "<anonymous>",
595
+ location: formatLocation(url, Number(line))
596
+ };
597
+ }
598
+ function formatLocation(url, line) {
599
+ const clean = url.split("?")[0];
600
+ const file = clean.split("/").pop() ?? clean;
601
+ return `${file}:${line}`;
602
+ }
603
+ function notifyAlpha({ message, link }) {
604
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
605
+ return;
606
+ }
607
+ if (shownNotices.has(message)) return;
608
+ shownNotices.add(message);
609
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
610
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
611
+ {
612
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
613
+ }
614
+ }
615
+
557
616
  const RESERVED_PROPS = [
558
617
  "children",
559
618
  "key",
@@ -563,7 +622,6 @@ const RESERVED_PROPS = [
563
622
  "dispose",
564
623
  "attach",
565
624
  "object",
566
- "onUpdate",
567
625
  // Behavior flags
568
626
  "dispose"
569
627
  ];
@@ -641,11 +699,9 @@ function diffProps(instance, newProps) {
641
699
  for (const prop in instance.props) {
642
700
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
643
701
  const { root, key } = resolve(instance.object, prop);
644
- if (root.constructor && root.constructor.length === 0) {
702
+ if (typeof root?.constructor === "function") {
645
703
  const ctor = getMemoizedPrototype(root);
646
704
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
647
- } else {
648
- changedProps[prop] = 0;
649
705
  }
650
706
  }
651
707
  return changedProps;
@@ -657,6 +713,13 @@ function applyProps(object, props) {
657
713
  for (const prop in props) {
658
714
  const value = props[prop];
659
715
  if (RESERVED_PROPS.includes(prop)) continue;
716
+ if (prop === "onUpdate") {
717
+ notifyDepreciated({
718
+ heading: "onUpdate is no longer an R3F prop",
719
+ 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.",
720
+ link: "https://docs.pmnd.rs/react-three-fiber/migration/v10#onupdate-removed"
721
+ });
722
+ }
660
723
  if (instance && EVENT_REGEX.test(prop)) {
661
724
  if (typeof value === "function") instance.handlers[prop] = value;
662
725
  else delete instance.handlers[prop];
@@ -731,377 +794,48 @@ function applyProps(object, props) {
731
794
  }
732
795
  }
733
796
  if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
734
- const object2 = instance.object;
735
- const index = rootState.internal.interaction.indexOf(object2);
736
- if (index > -1) rootState.internal.interaction.splice(index, 1);
737
- if (instance.eventCount && object2.raycast !== null) {
738
- rootState.internal.interaction.push(object2);
739
- }
740
- }
741
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
742
- const object2 = instance.object;
743
- const root = findInitialRoot(instance);
744
- const visibilityHandlers = {
745
- onFramed: instance.handlers.onFramed,
746
- onOccluded: instance.handlers.onOccluded,
747
- onVisible: instance.handlers.onVisible
748
- };
749
- if (hasVisibilityHandlers(visibilityHandlers)) {
750
- if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
751
- registerVisibility(root, object2, visibilityHandlers);
752
- }
753
- } else {
754
- unregisterVisibility(root, object2);
755
- }
756
- }
757
- if (instance && instance.props.attach === void 0) {
758
- if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
759
- else if (instance.object.isMaterial) instance.props.attach = "material";
760
- }
761
- if (instance) invalidateInstance(instance);
762
- return object;
763
- }
764
-
765
- const shownNotices = /* @__PURE__ */ new Set();
766
- function notifyDepreciated({ heading, body, link }) {
767
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
768
- return;
769
- }
770
- if (shownNotices.has(heading)) return;
771
- shownNotices.add(heading);
772
- const caller = getCallerFrame();
773
- if (caller) {
774
- console.log();
775
- }
776
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
777
- if (caller) {
778
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
779
- }
780
- if (body || link) {
781
- let message = "";
782
- if (body) message += body;
783
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
784
- console.warn(`%c${message}`, "font-weight: bold;");
785
- }
786
- }
787
- function getCallerFrame(depth = 3) {
788
- const stack = new Error().stack;
789
- if (!stack) return null;
790
- const lines = stack.split("\n");
791
- const frame = lines[depth];
792
- if (!frame) return null;
793
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
794
- if (!match) {
795
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
796
- }
797
- if (!match) return null;
798
- const [, fn, url, line] = match;
799
- return {
800
- functionName: fn ?? "<anonymous>",
801
- location: formatLocation(url, Number(line))
802
- };
803
- }
804
- function formatLocation(url, line) {
805
- const clean = url.split("?")[0];
806
- const file = clean.split("/").pop() ?? clean;
807
- return `${file}:${line}`;
808
- }
809
- function notifyAlpha({ message, link }) {
810
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
811
- return;
812
- }
813
- if (shownNotices.has(message)) return;
814
- shownNotices.add(message);
815
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
816
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
817
- {
818
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
819
- }
820
- }
821
-
822
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
823
- function isInternalRendererAccess(stack, callerDepth = 2) {
824
- if (!stack) return false;
825
- const caller = stack.split("\n")[callerDepth] ?? "";
826
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
827
- }
828
-
829
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
830
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
831
- const createStore = (invalidate, advance) => {
832
- const rootStore = traditional.createWithEqualityFn((set, get) => {
833
- const position = new webgpu.Vector3();
834
- const defaultTarget = new webgpu.Vector3();
835
- const tempTarget = new webgpu.Vector3();
836
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
837
- const { width, height, top, left } = size;
838
- const aspect = width / height;
839
- if (target.isVector3) tempTarget.copy(target);
840
- else tempTarget.set(...target);
841
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
842
- if (isOrthographicCamera(camera)) {
843
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
844
- } else {
845
- const fov = camera.fov * Math.PI / 180;
846
- const h = 2 * Math.tan(fov / 2) * distance;
847
- const w = h * (width / height);
848
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
849
- }
850
- }
851
- let performanceTimeout = void 0;
852
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
853
- const pointer = new webgpu.Vector2();
854
- const rootState = {
855
- set,
856
- get,
857
- // Mock objects that have to be configured
858
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
859
- primaryStore: null,
860
- gl: null,
861
- renderer: null,
862
- camera: null,
863
- frustum: new webgpu.Frustum(),
864
- autoUpdateFrustum: true,
865
- raycaster: null,
866
- events: {
867
- priority: 1,
868
- enabled: true,
869
- connected: false,
870
- frameTimedRaycasts: true,
871
- alwaysFireOnScroll: true,
872
- updateOnFrame: false
873
- },
874
- scene: null,
875
- rootScene: null,
876
- xr: null,
877
- inspector: null,
878
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
879
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
880
- textureColorSpace: webgpu.SRGBColorSpace,
881
- isLegacy: false,
882
- webGPUSupported: false,
883
- isNative: false,
884
- controls: null,
885
- pointer,
886
- mouse: pointer,
887
- frameloop: "always",
888
- onPointerMissed: void 0,
889
- onDragOverMissed: void 0,
890
- onDropMissed: void 0,
891
- performance: {
892
- current: 1,
893
- min: 0.5,
894
- max: 1,
895
- debounce: 200,
896
- regress: () => {
897
- const state2 = get();
898
- if (performanceTimeout) clearTimeout(performanceTimeout);
899
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
900
- performanceTimeout = setTimeout(
901
- () => setPerformanceCurrent(get().performance.max),
902
- state2.performance.debounce
903
- );
904
- }
905
- },
906
- size: { width: 0, height: 0, top: 0, left: 0 },
907
- viewport: {
908
- initialDpr: 0,
909
- dpr: 0,
910
- width: 0,
911
- height: 0,
912
- top: 0,
913
- left: 0,
914
- aspect: 0,
915
- distance: 0,
916
- factor: 0,
917
- getCurrentViewport
918
- },
919
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
920
- setSize: (width, height, top, left) => {
921
- const state2 = get();
922
- if (width === void 0) {
923
- set({ _sizeImperative: false });
924
- if (state2._sizeProps) {
925
- const { width: propW, height: propH } = state2._sizeProps;
926
- if (propW !== void 0 || propH !== void 0) {
927
- const currentSize = state2.size;
928
- const newSize = {
929
- width: propW ?? currentSize.width,
930
- height: propH ?? currentSize.height,
931
- top: currentSize.top,
932
- left: currentSize.left
933
- };
934
- set((s) => ({
935
- size: newSize,
936
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
937
- }));
938
- scheduler.getScheduler().invalidate();
939
- }
940
- }
941
- return;
942
- }
943
- const w = width;
944
- const h = height ?? width;
945
- const t = top ?? state2.size.top;
946
- const l = left ?? state2.size.left;
947
- const size = { width: w, height: h, top: t, left: l };
948
- set((s) => ({
949
- size,
950
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
951
- _sizeImperative: true
952
- }));
953
- scheduler.getScheduler().invalidate();
954
- },
955
- setDpr: (dpr) => set((state2) => {
956
- const resolved = calculateDpr(dpr);
957
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
958
- }),
959
- setFrameloop: (frameloop = "always") => {
960
- set(() => ({ frameloop }));
961
- scheduler.getScheduler().frameloop = frameloop;
962
- },
963
- setError: (error) => set(() => ({ error })),
964
- error: null,
965
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
966
- uniforms: {},
967
- nodes: {},
968
- buffers: {},
969
- gpuStorage: {},
970
- textures: /* @__PURE__ */ new Map(),
971
- _textureRefs: /* @__PURE__ */ new Map(),
972
- renderPipeline: null,
973
- passes: {},
974
- _hmrVersion: 0,
975
- _sizeImperative: false,
976
- _sizeProps: null,
977
- previousRoot: void 0,
978
- internal: {
979
- // Events
980
- interaction: [],
981
- subscribers: [],
982
- // Per-pointer state (new unified structure)
983
- pointerMap: /* @__PURE__ */ new Map(),
984
- pointerDirty: /* @__PURE__ */ new Map(),
985
- lastEvent: React__namespace.createRef(),
986
- // Deprecated but kept for backwards compatibility
987
- hovered: /* @__PURE__ */ new Map(),
988
- initialClick: [0, 0],
989
- initialHits: [],
990
- capturedMap: /* @__PURE__ */ new Map(),
991
- // Visibility tracking (onFramed, onOccluded, onVisible)
992
- visibilityRegistry: /* @__PURE__ */ new Map(),
993
- // Occlusion system (WebGPU only)
994
- occlusionEnabled: false,
995
- occlusionObserver: null,
996
- occlusionCache: /* @__PURE__ */ new Map(),
997
- helperGroup: null,
998
- // Updates
999
- active: false,
1000
- frames: 0,
1001
- priority: 0,
1002
- subscribe: (ref, priority, store) => {
1003
- const internal = get().internal;
1004
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1005
- internal.subscribers.push({ ref, priority, store });
1006
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1007
- return () => {
1008
- const internal2 = get().internal;
1009
- if (internal2?.subscribers) {
1010
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1011
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1012
- }
1013
- };
1014
- },
1015
- // Renderer Storage (single source of truth)
1016
- actualRenderer: null,
1017
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1018
- scheduler: null
1019
- }
1020
- };
1021
- return rootState;
1022
- });
1023
- const state = rootStore.getState();
1024
- Object.defineProperty(state, "gl", {
1025
- get() {
1026
- const currentState = rootStore.getState();
1027
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1028
- const stack = new Error().stack || "";
1029
- if (!isInternalRendererAccess(stack)) {
1030
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1031
- notifyDepreciated({
1032
- heading: "Accessing state.gl in WebGPU mode",
1033
- 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
1034
- });
1035
- }
1036
- }
1037
- return currentState.internal.actualRenderer;
1038
- },
1039
- set(value) {
1040
- rootStore.getState().internal.actualRenderer = value;
1041
- },
1042
- enumerable: true,
1043
- configurable: true
1044
- });
1045
- Object.defineProperty(state, "renderer", {
1046
- get() {
1047
- return rootStore.getState().internal.actualRenderer;
1048
- },
1049
- set(value) {
1050
- rootStore.getState().internal.actualRenderer = value;
1051
- },
1052
- enumerable: true,
1053
- configurable: true
1054
- });
1055
- let oldScene = state.scene;
1056
- rootStore.subscribe(() => {
1057
- const currentState = rootStore.getState();
1058
- const { scene, rootScene, set } = currentState;
1059
- if (scene !== oldScene) {
1060
- oldScene = scene;
1061
- if (scene?.isScene && scene !== rootScene) {
1062
- set({ rootScene: scene });
1063
- }
1064
- }
1065
- });
1066
- let oldSize = state.size;
1067
- let oldDpr = state.viewport.dpr;
1068
- let oldCamera = state.camera;
1069
- rootStore.subscribe(() => {
1070
- const { camera, size, viewport, set, internal } = rootStore.getState();
1071
- const actualRenderer = internal.actualRenderer;
1072
- const canvasTarget = internal.canvasTarget;
1073
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1074
- oldSize = size;
1075
- oldDpr = viewport.dpr;
1076
- updateCamera(camera, size);
1077
- if (internal.isSecondary && canvasTarget) {
1078
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1079
- canvasTarget.setSize(size.width, size.height, false);
1080
- } else {
1081
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1082
- actualRenderer.setSize(size.width, size.height, false);
1083
- if (canvasTarget) {
1084
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1085
- canvasTarget.setSize(size.width, size.height, false);
1086
- }
1087
- }
1088
- }
1089
- if (camera !== oldCamera) {
1090
- oldCamera = camera;
1091
- const { rootScene } = rootStore.getState();
1092
- if (camera && rootScene && !camera.parent) {
1093
- rootScene.add(camera);
1094
- }
1095
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1096
- const currentState = rootStore.getState();
1097
- if (currentState.autoUpdateFrustum && camera) {
1098
- updateFrustum(camera, currentState.frustum);
797
+ const object2 = instance.object;
798
+ const index = rootState.internal.interaction.indexOf(object2);
799
+ if (index > -1) rootState.internal.interaction.splice(index, 1);
800
+ if (instance.eventCount && object2.raycast !== null) {
801
+ rootState.internal.interaction.push(object2);
802
+ }
803
+ }
804
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
805
+ const object2 = instance.object;
806
+ const root = findInitialRoot(instance);
807
+ const visibilityHandlers = {
808
+ onFramed: instance.handlers.onFramed,
809
+ onOccluded: instance.handlers.onOccluded,
810
+ onVisible: instance.handlers.onVisible
811
+ };
812
+ if (hasVisibilityHandlers(visibilityHandlers)) {
813
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
814
+ registerVisibility(root, object2, visibilityHandlers);
1099
815
  }
816
+ } else {
817
+ unregisterVisibility(root, object2);
1100
818
  }
1101
- });
1102
- rootStore.subscribe((state2) => invalidate(state2));
1103
- return rootStore;
1104
- };
819
+ }
820
+ if (instance && instance.props.attach === void 0) {
821
+ if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
822
+ else if (instance.object.isMaterial) instance.props.attach = "material";
823
+ }
824
+ if (instance) invalidateInstance(instance);
825
+ return object;
826
+ }
827
+
828
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
829
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
830
+
831
+ function useStore() {
832
+ const store = React.useContext(context);
833
+ if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
834
+ return store;
835
+ }
836
+ function useThree$1(selector = (state) => state, equalityFn) {
837
+ return useStore()(selector, equalityFn);
838
+ }
1105
839
 
1106
840
  const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1107
841
  const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
@@ -1146,7 +880,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1146
880
  const results = cacheKeys.map(
1147
881
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1148
882
  );
1149
- return Array.isArray(input) ? results : results[0];
883
+ const stableRef = React.useRef(null);
884
+ const previous = stableRef.current;
885
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
886
+ stableRef.current = stable;
887
+ return Array.isArray(input) ? stable : stable[0];
1150
888
  }
1151
889
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1152
890
  const inputs = Array.isArray(input) ? input : [input];
@@ -1266,6 +1004,13 @@ function useFrame$1(callback, priorityOrOptions) {
1266
1004
  * Use for controlling the entire frame loop, adding phases, etc.
1267
1005
  */
1268
1006
  scheduler: scheduler2,
1007
+ /**
1008
+ * The root that currently owns this job. Resolve on access because ambient
1009
+ * jobs can be adopted by a Canvas after the controls object is created.
1010
+ */
1011
+ get rootId() {
1012
+ return scheduler.getScheduler().getJobRootId(id);
1013
+ },
1269
1014
  /**
1270
1015
  * Manually step this job only.
1271
1016
  * Bypasses FPS limiting - always runs.
@@ -1282,6 +1027,14 @@ function useFrame$1(callback, priorityOrOptions) {
1282
1027
  stepAll: (timestamp) => {
1283
1028
  scheduler.getScheduler().step(timestamp);
1284
1029
  },
1030
+ /**
1031
+ * Request frames for this job's owning root without waking sibling roots.
1032
+ */
1033
+ invalidate: (frames, stackFrames) => {
1034
+ const scheduler3 = scheduler.getScheduler();
1035
+ const rootId = scheduler3.getJobRootId(id);
1036
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1037
+ },
1285
1038
  /**
1286
1039
  * Pause this job (set enabled=false).
1287
1040
  * Job remains registered but won't run.
@@ -1311,6 +1064,11 @@ function getUrls(input) {
1311
1064
  if (Array.isArray(input)) return input;
1312
1065
  return Object.values(input);
1313
1066
  }
1067
+ function inputSignature(input) {
1068
+ if (typeof input === "string") return input;
1069
+ if (Array.isArray(input)) return input.join("\0");
1070
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1071
+ }
1314
1072
  function allUrlsCached(urls, textureCache) {
1315
1073
  return urls.every((url) => textureCache.has(url));
1316
1074
  }
@@ -1336,24 +1094,37 @@ function useTexture(input, optionsOrOnLoad) {
1336
1094
  const onLoadRef = React.useRef(onLoad);
1337
1095
  onLoadRef.current = onLoad;
1338
1096
  const onLoadCalledForRef = React.useRef(null);
1339
- const urls = React.useMemo(() => getUrls(input), [input]);
1097
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1098
+ const stableInput = React.useMemo(() => input, [inputKey]);
1099
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1340
1100
  const cachedResult = React.useMemo(() => {
1341
1101
  if (!cache) return null;
1342
1102
  const textures = store.getState().textures;
1343
1103
  if (!allUrlsCached(urls, textures)) return null;
1344
- return buildFromCache(input, textures);
1345
- }, [cache, urls, input, store]);
1104
+ return buildFromCache(stableInput, textures);
1105
+ }, [cache, urls, stableInput, store]);
1346
1106
  const loadedTextures = useLoader(
1347
1107
  webgpu.TextureLoader,
1348
- IsObject(input) ? Object.values(input) : input
1108
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1349
1109
  );
1350
- const inputKey = urls.join("\0");
1110
+ const mappedTextures = React.useMemo(() => {
1111
+ if (cachedResult) return cachedResult;
1112
+ if (IsObject(stableInput)) {
1113
+ const keyed = {};
1114
+ const textureArray = loadedTextures;
1115
+ let i = 0;
1116
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1117
+ return keyed;
1118
+ } else {
1119
+ return loadedTextures;
1120
+ }
1121
+ }, [stableInput, loadedTextures, cachedResult]);
1351
1122
  React.useLayoutEffect(() => {
1352
1123
  if (cachedResult) return;
1353
1124
  if (onLoadCalledForRef.current === inputKey) return;
1354
1125
  onLoadCalledForRef.current = inputKey;
1355
- onLoadRef.current?.(loadedTextures);
1356
- }, [cachedResult, loadedTextures, inputKey]);
1126
+ onLoadRef.current?.(mappedTextures);
1127
+ }, [cachedResult, mappedTextures, inputKey]);
1357
1128
  React.useEffect(() => {
1358
1129
  if (cachedResult) return;
1359
1130
  if ("initTexture" in renderer) {
@@ -1372,30 +1143,18 @@ function useTexture(input, optionsOrOnLoad) {
1372
1143
  });
1373
1144
  }
1374
1145
  }, [renderer, loadedTextures, cachedResult]);
1375
- const mappedTextures = React.useMemo(() => {
1376
- if (cachedResult) return cachedResult;
1377
- if (IsObject(input)) {
1378
- const keyed = {};
1379
- const textureArray = loadedTextures;
1380
- let i = 0;
1381
- for (const key in input) keyed[key] = textureArray[i++];
1382
- return keyed;
1383
- } else {
1384
- return loadedTextures;
1385
- }
1386
- }, [input, loadedTextures, cachedResult]);
1387
1146
  React.useEffect(() => {
1388
1147
  if (!cache) return;
1389
1148
  const urlTextureMap = [];
1390
- if (typeof input === "string") {
1391
- urlTextureMap.push([input, mappedTextures]);
1392
- } else if (Array.isArray(input)) {
1149
+ if (typeof stableInput === "string") {
1150
+ urlTextureMap.push([stableInput, mappedTextures]);
1151
+ } else if (Array.isArray(stableInput)) {
1393
1152
  const textureArray = mappedTextures;
1394
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1395
- } else if (IsObject(input)) {
1153
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1154
+ } else if (IsObject(stableInput)) {
1396
1155
  const textureRecord = mappedTextures;
1397
- for (const key in input) {
1398
- const url = input[key];
1156
+ for (const key in stableInput) {
1157
+ const url = stableInput[key];
1399
1158
  urlTextureMap.push([url, textureRecord[key]]);
1400
1159
  }
1401
1160
  }
@@ -1424,7 +1183,7 @@ function useTexture(input, optionsOrOnLoad) {
1424
1183
  }
1425
1184
  return { _textureRefs: refs };
1426
1185
  });
1427
- }, [cache, input, mappedTextures, store]);
1186
+ }, [cache, stableInput, mappedTextures, store]);
1428
1187
  return mappedTextures;
1429
1188
  }
1430
1189
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1585,7 +1344,7 @@ function useEnvironment({
1585
1344
  texture = texture.renderTarget?.texture;
1586
1345
  }
1587
1346
  texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
1588
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1347
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1589
1348
  return texture;
1590
1349
  }
1591
1350
  const preloadDefaultOptions = {
@@ -1638,22 +1397,15 @@ function getExtension(files) {
1638
1397
  const isCubemap = isArray(files) && files.length === 6;
1639
1398
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1640
1399
  const firstEntry = isArray(files) ? files[0] : files;
1641
- 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();
1400
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1401
+ 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;
1642
1402
  return { extension, isCubemap, isGainmap };
1643
1403
  }
1644
1404
  function getLoader(extension) {
1645
- const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1405
+ const loader = extension === "cube" ? webgpu.CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader_js.HDRCubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1646
1406
  return loader;
1647
1407
  }
1648
1408
 
1649
- function useStore() {
1650
- const store = React.useContext(context);
1651
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1652
- return store;
1653
- }
1654
- function useThree$1(selector = (state) => state, equalityFn) {
1655
- return useStore()(selector, equalityFn);
1656
- }
1657
1409
  function useInstanceHandle(ref) {
1658
1410
  const instance = React.useRef(null);
1659
1411
  React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1693,22 +1445,28 @@ function addTail(callback) {
1693
1445
  return scheduler.getScheduler().onIdle(callback);
1694
1446
  }
1695
1447
  function invalidate(state, frames = 1, stackFrames = false) {
1696
- scheduler.getScheduler().invalidate(frames, stackFrames);
1448
+ const rootId = state?.internal?.rootId;
1449
+ const scheduler$1 = scheduler.getScheduler();
1450
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1451
+ else scheduler$1.invalidate(frames, stackFrames);
1697
1452
  }
1698
- function advance(timestamp) {
1699
- scheduler.getScheduler().step(timestamp);
1453
+ function advance(timestamp, runGlobalEffects, state) {
1454
+ const rootId = state?.internal?.rootId;
1455
+ const scheduler$1 = scheduler.getScheduler();
1456
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1457
+ else scheduler$1.step(timestamp);
1700
1458
  }
1701
1459
 
1702
- const version = "10.0.0-alpha.3";
1460
+ const version = "10.0.0-alpha.5";
1703
1461
  const packageData = {
1704
1462
  version: version};
1705
1463
 
1706
1464
  function Xb(Tt) {
1707
1465
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
1708
1466
  }
1709
- var Rm = { exports: {} }, Og = { exports: {} }, _b;
1467
+ var Rm = { exports: {} }, Og = { exports: {} }, _b$1;
1710
1468
  function Kb() {
1711
- return _b || (_b = 1, (function(Tt) {
1469
+ return _b$1 || (_b$1 = 1, (function(Tt) {
1712
1470
  Tt.exports = function(m) {
1713
1471
  function Yn(t, r, a, l) {
1714
1472
  return new uc(t, r, a, l);
@@ -13558,7 +13316,10 @@ function extend(objects) {
13558
13316
  catalogue[Component] = objects;
13559
13317
  return Component;
13560
13318
  } else {
13561
- Object.assign(catalogue, objects);
13319
+ for (const name in objects) {
13320
+ const object = objects[name];
13321
+ if (isConstructor(object)) catalogue[name] = object;
13322
+ }
13562
13323
  }
13563
13324
  }
13564
13325
  function validateInstance(type, props) {
@@ -13906,7 +13667,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13906
13667
  getRootHostContext: () => NO_CONTEXT,
13907
13668
  getChildHostContext: () => NO_CONTEXT,
13908
13669
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13909
- validateInstance(type, newProps);
13670
+ validateInstance(instance.type, newProps);
13910
13671
  let reconstruct = false;
13911
13672
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13912
13673
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13933,8 +13694,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13933
13694
  for (const prop in instance.props) {
13934
13695
  const value = instance.props[prop];
13935
13696
  if (isFromRef(value)) {
13936
- const ref = value[FROM_REF];
13937
- if (ref.current != null) resolved[prop] = ref.current;
13697
+ const { [FROM_REF]: ref, transform } = value;
13698
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13938
13699
  }
13939
13700
  }
13940
13701
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14078,6 +13839,335 @@ const reconciler = /* @__PURE__ */ createReconciler({
14078
13839
  getSuspendedCommitReason: (_state, _rootContainer) => null
14079
13840
  });
14080
13841
 
13842
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
13843
+ function isInternalRendererAccess(stack, callerDepth = 2) {
13844
+ if (!stack) return false;
13845
+ const caller = stack.split("\n")[callerDepth] ?? "";
13846
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
13847
+ }
13848
+
13849
+ const createStore = (invalidate, advance) => {
13850
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
13851
+ const position = new webgpu.Vector3();
13852
+ const defaultTarget = new webgpu.Vector3();
13853
+ const tempTarget = new webgpu.Vector3();
13854
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
13855
+ const { width, height, top, left } = size;
13856
+ const aspect = width / height;
13857
+ if (target.isVector3) tempTarget.copy(target);
13858
+ else tempTarget.set(...target);
13859
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
13860
+ if (isOrthographicCamera(camera)) {
13861
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
13862
+ } else {
13863
+ const fov = camera.fov * Math.PI / 180;
13864
+ const h = 2 * Math.tan(fov / 2) * distance;
13865
+ const w = h * (width / height);
13866
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
13867
+ }
13868
+ }
13869
+ let performanceTimeout = void 0;
13870
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
13871
+ const pointer = new webgpu.Vector2();
13872
+ const rootState = {
13873
+ set,
13874
+ get,
13875
+ // Mock objects that have to be configured
13876
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
13877
+ primaryStore: null,
13878
+ gl: null,
13879
+ renderer: null,
13880
+ camera: null,
13881
+ frustum: new webgpu.Frustum(),
13882
+ autoUpdateFrustum: true,
13883
+ raycaster: null,
13884
+ events: {
13885
+ priority: 1,
13886
+ enabled: true,
13887
+ connected: false,
13888
+ frameTimedRaycasts: true,
13889
+ alwaysFireOnScroll: true,
13890
+ updateOnFrame: false
13891
+ },
13892
+ scene: null,
13893
+ rootScene: null,
13894
+ xr: null,
13895
+ inspector: null,
13896
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
13897
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
13898
+ textureColorSpace: webgpu.SRGBColorSpace,
13899
+ isLegacy: false,
13900
+ webGPUSupported: false,
13901
+ isNative: false,
13902
+ controls: null,
13903
+ pointer,
13904
+ mouse: pointer,
13905
+ frameloop: "always",
13906
+ onPointerMissed: void 0,
13907
+ onDragOverMissed: void 0,
13908
+ onDropMissed: void 0,
13909
+ performance: {
13910
+ current: 1,
13911
+ min: 0.5,
13912
+ max: 1,
13913
+ debounce: 200,
13914
+ regress: () => {
13915
+ const state2 = get();
13916
+ if (performanceTimeout) clearTimeout(performanceTimeout);
13917
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
13918
+ performanceTimeout = setTimeout(
13919
+ () => setPerformanceCurrent(get().performance.max),
13920
+ state2.performance.debounce
13921
+ );
13922
+ }
13923
+ },
13924
+ size: { width: 0, height: 0, top: 0, left: 0 },
13925
+ viewport: {
13926
+ initialDpr: 0,
13927
+ dpr: 0,
13928
+ width: 0,
13929
+ height: 0,
13930
+ top: 0,
13931
+ left: 0,
13932
+ aspect: 0,
13933
+ distance: 0,
13934
+ factor: 0,
13935
+ getCurrentViewport
13936
+ },
13937
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
13938
+ setSize: (width, height, top, left) => {
13939
+ const state2 = get();
13940
+ if (width === void 0) {
13941
+ set({ _sizeImperative: false });
13942
+ if (state2._sizeProps) {
13943
+ const { width: propW, height: propH } = state2._sizeProps;
13944
+ if (propW !== void 0 || propH !== void 0) {
13945
+ const currentSize = state2.size;
13946
+ const newSize = {
13947
+ width: propW ?? currentSize.width,
13948
+ height: propH ?? currentSize.height,
13949
+ top: currentSize.top,
13950
+ left: currentSize.left
13951
+ };
13952
+ set((s) => ({
13953
+ size: newSize,
13954
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
13955
+ }));
13956
+ get().invalidate();
13957
+ }
13958
+ }
13959
+ return;
13960
+ }
13961
+ const w = width;
13962
+ const h = height ?? width;
13963
+ const t = top ?? state2.size.top;
13964
+ const l = left ?? state2.size.left;
13965
+ const size = { width: w, height: h, top: t, left: l };
13966
+ set((s) => ({
13967
+ size,
13968
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
13969
+ _sizeImperative: true
13970
+ }));
13971
+ get().invalidate();
13972
+ },
13973
+ setDpr: (dpr) => set((state2) => {
13974
+ const resolved = calculateDpr(dpr);
13975
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
13976
+ }),
13977
+ setFrameloop: (frameloop = "always") => {
13978
+ const rootId = get().internal.rootId;
13979
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
13980
+ set(() => ({ frameloop }));
13981
+ },
13982
+ setError: (error) => set(() => ({ error })),
13983
+ error: null,
13984
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
13985
+ uniforms: {},
13986
+ nodes: {},
13987
+ buffers: {},
13988
+ gpuStorage: {},
13989
+ textures: /* @__PURE__ */ new Map(),
13990
+ _textureRefs: /* @__PURE__ */ new Map(),
13991
+ renderPipeline: null,
13992
+ passes: {},
13993
+ _hmrVersion: 0,
13994
+ _sizeImperative: false,
13995
+ _sizeProps: null,
13996
+ previousRoot: void 0,
13997
+ internal: {
13998
+ // Events
13999
+ interaction: [],
14000
+ subscribers: [],
14001
+ // Per-pointer state (new unified structure)
14002
+ pointerMap: /* @__PURE__ */ new Map(),
14003
+ pointerDirty: /* @__PURE__ */ new Map(),
14004
+ lastEvent: React__namespace.createRef(),
14005
+ // Deprecated but kept for backwards compatibility
14006
+ hovered: /* @__PURE__ */ new Map(),
14007
+ initialClick: [0, 0],
14008
+ initialHits: [],
14009
+ capturedMap: /* @__PURE__ */ new Map(),
14010
+ // Visibility tracking (onFramed, onOccluded, onVisible)
14011
+ visibilityRegistry: /* @__PURE__ */ new Map(),
14012
+ // Occlusion system (WebGPU only)
14013
+ occlusionEnabled: false,
14014
+ occlusionObserver: null,
14015
+ occlusionCache: /* @__PURE__ */ new Map(),
14016
+ helperGroup: null,
14017
+ // Updates
14018
+ active: false,
14019
+ frames: 0,
14020
+ priority: 0,
14021
+ subscribe: (ref, priority, store) => {
14022
+ const internal = get().internal;
14023
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
14024
+ internal.subscribers.push({ ref, priority, store });
14025
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
14026
+ return () => {
14027
+ const internal2 = get().internal;
14028
+ if (internal2?.subscribers) {
14029
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
14030
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
14031
+ }
14032
+ };
14033
+ },
14034
+ // Renderer Storage (single source of truth)
14035
+ actualRenderer: null,
14036
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
14037
+ scheduler: null,
14038
+ // Replaces the default render call when set (see setRenderOverride)
14039
+ renderOverride: null
14040
+ }
14041
+ };
14042
+ return rootState;
14043
+ });
14044
+ const state = rootStore.getState();
14045
+ Object.defineProperty(state, "gl", {
14046
+ get() {
14047
+ const currentState = rootStore.getState();
14048
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
14049
+ const stack = new Error().stack || "";
14050
+ if (!isInternalRendererAccess(stack)) {
14051
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
14052
+ notifyDepreciated({
14053
+ heading: "Accessing state.gl in WebGPU mode",
14054
+ 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
14055
+ });
14056
+ }
14057
+ }
14058
+ return currentState.internal.actualRenderer;
14059
+ },
14060
+ set(value) {
14061
+ rootStore.getState().internal.actualRenderer = value;
14062
+ },
14063
+ enumerable: true,
14064
+ configurable: true
14065
+ });
14066
+ Object.defineProperty(state, "renderer", {
14067
+ get() {
14068
+ return rootStore.getState().internal.actualRenderer;
14069
+ },
14070
+ set(value) {
14071
+ rootStore.getState().internal.actualRenderer = value;
14072
+ },
14073
+ enumerable: true,
14074
+ configurable: true
14075
+ });
14076
+ let oldScene = state.scene;
14077
+ rootStore.subscribe(() => {
14078
+ const currentState = rootStore.getState();
14079
+ const { scene, rootScene, set } = currentState;
14080
+ if (scene !== oldScene) {
14081
+ oldScene = scene;
14082
+ if (scene?.isScene && scene !== rootScene) {
14083
+ set({ rootScene: scene });
14084
+ }
14085
+ }
14086
+ });
14087
+ let oldSize = state.size;
14088
+ let oldDpr = state.viewport.dpr;
14089
+ let oldCamera = state.camera;
14090
+ rootStore.subscribe(() => {
14091
+ const { camera, size, viewport, set, internal } = rootStore.getState();
14092
+ const actualRenderer = internal.actualRenderer;
14093
+ const canvasTarget = internal.canvasTarget;
14094
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
14095
+ oldSize = size;
14096
+ oldDpr = viewport.dpr;
14097
+ updateCamera(camera, size);
14098
+ const resizeTarget = canvasTarget ?? actualRenderer;
14099
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
14100
+ resizeTarget.setSize(size.width, size.height, false);
14101
+ const activeTarget = actualRenderer.getCanvasTarget?.();
14102
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
14103
+ }
14104
+ if (camera !== oldCamera) {
14105
+ oldCamera = camera;
14106
+ const { rootScene } = rootStore.getState();
14107
+ if (camera && rootScene && !camera.parent) {
14108
+ rootScene.add(camera);
14109
+ }
14110
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
14111
+ const currentState = rootStore.getState();
14112
+ if (currentState.autoUpdateFrustum && camera) {
14113
+ updateFrustum(camera, currentState.frustum);
14114
+ }
14115
+ }
14116
+ });
14117
+ rootStore.subscribe((state2) => invalidate(state2));
14118
+ return rootStore;
14119
+ };
14120
+
14121
+ const R3F_EXTENSIONS = Symbol.for("@react-three/fiber.extensions");
14122
+ function getRegistry() {
14123
+ return globalThis[R3F_EXTENSIONS] ?? (globalThis[R3F_EXTENSIONS] = {
14124
+ extensions: /* @__PURE__ */ new Map(),
14125
+ roots: /* @__PURE__ */ new Set(),
14126
+ applied: /* @__PURE__ */ new WeakMap()
14127
+ });
14128
+ }
14129
+ function setupRoot(registry, store, extension) {
14130
+ let applied = registry.applied.get(store);
14131
+ if (!applied) registry.applied.set(store, applied = /* @__PURE__ */ new Map());
14132
+ if (applied.has(extension.name)) return;
14133
+ applied.set(extension.name, extension);
14134
+ const patch = extension.setup?.(store);
14135
+ if (patch) store.setState(patch);
14136
+ }
14137
+ function registerRootExtension(extension) {
14138
+ const registry = getRegistry();
14139
+ registry.extensions.set(extension.name, extension);
14140
+ for (const store of registry.roots) setupRoot(registry, store, extension);
14141
+ return () => {
14142
+ if (registry.extensions.get(extension.name) === extension) registry.extensions.delete(extension.name);
14143
+ };
14144
+ }
14145
+ function setRenderOverride(store, render) {
14146
+ store.getState().internal.renderOverride = render;
14147
+ }
14148
+ function attachRootExtensions(store) {
14149
+ const registry = getRegistry();
14150
+ registry.roots.add(store);
14151
+ for (const extension of registry.extensions.values()) setupRoot(registry, store, extension);
14152
+ }
14153
+ function current(registry, setUpBy) {
14154
+ return registry.extensions.get(setUpBy.name) ?? setUpBy;
14155
+ }
14156
+ function detachRootExtensions(store) {
14157
+ const registry = getRegistry();
14158
+ if (!registry.roots.delete(store)) return;
14159
+ const applied = registry.applied.get(store);
14160
+ registry.applied.delete(store);
14161
+ if (!applied) return;
14162
+ for (const setUpBy of applied.values()) current(registry, setUpBy).dispose?.(store);
14163
+ }
14164
+ function notifyRootExtensionsHmr(store) {
14165
+ const registry = getRegistry();
14166
+ const applied = registry.applied.get(store);
14167
+ if (!applied) return;
14168
+ for (const setUpBy of applied.values()) current(registry, setUpBy).hmr?.(store);
14169
+ }
14170
+
14081
14171
  const isRenderer = (def) => !!def?.render;
14082
14172
  const _roots = /* @__PURE__ */ new Map();
14083
14173
  const shallowLoose = { objects: "shallow", strict: false };
@@ -14215,9 +14305,8 @@ function createRoot(canvas) {
14215
14305
  if (!renderer.hasInitialized?.()) {
14216
14306
  const size2 = computeInitialSize(canvas, propsSize);
14217
14307
  if (size2.width > 0 && size2.height > 0) {
14218
- const pixelRatio = calculateDpr(dpr);
14219
- canvas.width = size2.width * pixelRatio;
14220
- canvas.height = size2.height * pixelRatio;
14308
+ renderer.setPixelRatio(calculateDpr(dpr));
14309
+ renderer.setSize(size2.width, size2.height, false);
14221
14310
  }
14222
14311
  await renderer.init();
14223
14312
  }
@@ -14226,7 +14315,7 @@ function createRoot(canvas) {
14226
14315
  state.internal.actualRenderer = renderer;
14227
14316
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14228
14317
  if (canvasId && !state.internal.isSecondary) {
14229
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14318
+ const canvasTarget = renderer.getCanvasTarget?.();
14230
14319
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14231
14320
  state.set((prev) => ({
14232
14321
  internal: {
@@ -14340,7 +14429,7 @@ function createRoot(canvas) {
14340
14429
  const handleXRFrame = (timestamp, _frame) => {
14341
14430
  const state2 = store.getState();
14342
14431
  if (state2.frameloop === "never") return;
14343
- advance(timestamp);
14432
+ advance(timestamp, true, state2);
14344
14433
  };
14345
14434
  const actualRenderer = state.internal.actualRenderer;
14346
14435
  const handleSessionChange = () => {
@@ -14348,7 +14437,7 @@ function createRoot(canvas) {
14348
14437
  const renderer2 = state2.internal.actualRenderer;
14349
14438
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14350
14439
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14351
- if (!renderer2.xr.isPresenting) invalidate();
14440
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14352
14441
  };
14353
14442
  const xr = {
14354
14443
  connect() {
@@ -14430,14 +14519,23 @@ function createRoot(canvas) {
14430
14519
  const newRootId = canvasId || scheduler$1.generateRootId();
14431
14520
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14432
14521
  getState: () => store.getState(),
14433
- onError: (err) => store.getState().setError(err)
14522
+ onError: (err) => store.getState().setError(err),
14523
+ frameloop: store.getState().frameloop,
14524
+ before: schedulerConfig?.before,
14525
+ after: schedulerConfig?.after,
14526
+ order: schedulerConfig?.order
14434
14527
  });
14435
14528
  const unregisterCanvasTarget = scheduler$1.register(
14436
14529
  () => {
14437
14530
  const state2 = store.getState();
14438
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14531
+ const canvasTarget = state2.internal.canvasTarget;
14532
+ if (canvasTarget) {
14439
14533
  const renderer2 = state2.internal.actualRenderer;
14440
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14534
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14535
+ if (state2.internal.canvasTargetSizeDirty) {
14536
+ state2.internal.canvasTargetSizeDirty = false;
14537
+ renderer2.backend?.updateSize?.();
14538
+ }
14441
14539
  }
14442
14540
  },
14443
14541
  {
@@ -14493,7 +14591,8 @@ function createRoot(canvas) {
14493
14591
  const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14494
14592
  if (userHandlesRender || state2.internal.priority) return;
14495
14593
  try {
14496
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14594
+ const renderOverride = state2.internal.renderOverride;
14595
+ if (renderOverride) renderOverride();
14497
14596
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14498
14597
  } catch (error) {
14499
14598
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14506,8 +14605,7 @@ function createRoot(canvas) {
14506
14605
  phase: "render",
14507
14606
  system: true,
14508
14607
  // Internal flag: this is a system job, not user-controlled
14509
- // Apply scheduler config for render ordering and rate limiting
14510
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14608
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14511
14609
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14512
14610
  }
14513
14611
  );
@@ -14526,8 +14624,22 @@ function createRoot(canvas) {
14526
14624
  scheduler: scheduler$1
14527
14625
  }
14528
14626
  }));
14627
+ } else {
14628
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14629
+ if (constraintsChanged) {
14630
+ scheduler$1.setRootConstraints(rootId, {
14631
+ before: schedulerConfig?.before,
14632
+ after: schedulerConfig?.after
14633
+ });
14634
+ }
14635
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14636
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14637
+ }
14529
14638
  }
14530
- scheduler$1.frameloop = frameloop;
14639
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14640
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14641
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14642
+ attachRootExtensions(store);
14531
14643
  onCreated = onCreatedCallback;
14532
14644
  configured = true;
14533
14645
  resolve();
@@ -14569,18 +14681,24 @@ function unmountComponentAtNode(canvas, callback) {
14569
14681
  const fiber = root?.fiber;
14570
14682
  if (fiber) {
14571
14683
  const state = root?.store.getState();
14572
- if (state) state.internal.active = false;
14684
+ if (state) {
14685
+ state.internal.active = false;
14686
+ const unregisterRoot = state.internal.unregisterRoot;
14687
+ if (unregisterRoot) {
14688
+ unregisterRoot();
14689
+ state.internal.unregisterRoot = void 0;
14690
+ }
14691
+ }
14573
14692
  reconciler.updateContainer(null, fiber, null, () => {
14693
+ if (root?.store) detachRootExtensions(root.store);
14574
14694
  if (state) {
14575
14695
  setTimeout(() => {
14576
14696
  try {
14577
14697
  const renderer = state.internal.actualRenderer;
14578
- const unregisterRoot = state.internal.unregisterRoot;
14579
- if (unregisterRoot) unregisterRoot();
14580
14698
  const unregisterPrimary = state.internal.unregisterPrimary;
14581
14699
  if (unregisterPrimary) unregisterPrimary();
14582
14700
  const canvasTarget = state.internal.canvasTarget;
14583
- if (canvasTarget?.dispose) canvasTarget.dispose();
14701
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14584
14702
  state.events.disconnect?.();
14585
14703
  cleanupHelperGroup(root.store);
14586
14704
  if (state.isLegacy && renderer) {
@@ -14650,11 +14768,24 @@ function PortalInner({ state = {}, children, container }) {
14650
14768
  }
14651
14769
  };
14652
14770
  }, [portalScene, container, injectScene]);
14771
+ const lastRootState = React.useRef(null);
14653
14772
  const inject = useMutableCallback((rootState, injectState) => {
14773
+ const followed = {};
14774
+ const lastRoot = lastRootState.current;
14775
+ if (lastRoot) {
14776
+ for (const key in rootState) {
14777
+ const field = key;
14778
+ const changed = rootState[field] !== lastRoot[field];
14779
+ const inherited = !(key in rest) && injectState[field] === lastRoot[field];
14780
+ if (changed && inherited) followed[field] = rootState[field];
14781
+ }
14782
+ }
14783
+ lastRootState.current = rootState;
14654
14784
  const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14655
14785
  let viewport = void 0;
14656
- if (injectState.camera && (size || injectState.size)) {
14657
- const camera = injectState.camera;
14786
+ const portalCamera = followed.camera ?? injectState.camera;
14787
+ if (portalCamera && (size || injectState.size)) {
14788
+ const camera = portalCamera;
14658
14789
  viewport = rootState.viewport.getCurrentViewport(camera, new webgpu.Vector3(), resolvedSize);
14659
14790
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14660
14791
  }
@@ -14663,6 +14794,7 @@ function PortalInner({ state = {}, children, container }) {
14663
14794
  // The intersect consists of the previous root state
14664
14795
  ...rootState,
14665
14796
  ...injectState,
14797
+ ...followed,
14666
14798
  // Portals have their own scene - always a real THREE.Scene (injected if needed)
14667
14799
  scene: portalScene,
14668
14800
  // rootScene always points to the actual THREE.Scene, even inside portals
@@ -14721,16 +14853,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14721
14853
  const oldbg = target.background;
14722
14854
  const oldenv = target.environment;
14723
14855
  const oldSceneProps = {
14724
- // @ts-ignore
14725
14856
  backgroundBlurriness: target.backgroundBlurriness,
14726
- // @ts-ignore
14727
14857
  backgroundIntensity: target.backgroundIntensity,
14728
- // @ts-ignore
14729
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14730
- // @ts-ignore
14858
+ backgroundRotation: target.backgroundRotation.clone(),
14731
14859
  environmentIntensity: target.environmentIntensity,
14732
- // @ts-ignore
14733
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14860
+ environmentRotation: target.environmentRotation.clone()
14734
14861
  };
14735
14862
  if (background !== "only") target.environment = texture;
14736
14863
  if (background) target.background = texture;
@@ -14937,151 +15064,22 @@ function parseBackground(background) {
14937
15064
  return { color: background, background: true };
14938
15065
  }
14939
15066
  if (background.isColor) {
14940
- return { color: background, background: true };
14941
- }
14942
- return null;
14943
- }
14944
-
14945
- function parseRendererConfig(rendererProp) {
14946
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14947
- if (isRendererConfig) {
14948
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14949
- return {
14950
- primaryCanvas: pc,
14951
- scheduler: sc,
14952
- renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14953
- };
14954
- }
14955
- return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14956
- }
14957
-
14958
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14959
- const ROOT_SCOPE = "";
14960
- const registries = /* @__PURE__ */ new WeakMap();
14961
- function getKindRegistry(store, kind) {
14962
- let registry = registries.get(store);
14963
- if (!registry) {
14964
- registry = {
14965
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14966
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14967
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14968
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14969
- };
14970
- registries.set(store, registry);
14971
- }
14972
- return registry[kind];
14973
- }
14974
- function resolvePrimary$1(store) {
14975
- return store.getState().primaryStore ?? store;
14976
- }
14977
- function isScopeValid(store, kind, scopeKey) {
14978
- return getKindRegistry(store, kind).valid.has(scopeKey);
14979
- }
14980
- function peekStaged(store, kind, scopeKey) {
14981
- return getKindRegistry(store, kind).staged.get(scopeKey);
14982
- }
14983
- function stageEntries(store, kind, scopeKey, entries) {
14984
- const { staged } = getKindRegistry(store, kind);
14985
- const current = staged.get(scopeKey);
14986
- if (current) Object.assign(current, entries);
14987
- else staged.set(scopeKey, { ...entries });
14988
- }
14989
- function readCommittedScope(map, scopeKey, isLeaf) {
14990
- if (scopeKey === ROOT_SCOPE) {
14991
- const leaves = {};
14992
- for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14993
- return leaves;
14994
- }
14995
- const scopeData = map[scopeKey];
14996
- if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14997
- return {};
14998
- }
14999
- function withStagedOverlay(store, kind, committed) {
15000
- const { staged } = getKindRegistry(store, kind);
15001
- if (staged.size === 0) return committed;
15002
- const view = { ...committed };
15003
- for (const [scopeKey, entries] of staged) {
15004
- if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15005
- else view[scopeKey] = { ...view[scopeKey], ...entries };
15006
- }
15007
- return view;
15008
- }
15009
- function flushStagedScope(store, kind, scopeKey, isLeaf) {
15010
- const registry = getKindRegistry(store, kind);
15011
- const staged = registry.staged.get(scopeKey);
15012
- if (!staged) return;
15013
- registry.staged.delete(scopeKey);
15014
- const replace = !registry.valid.has(scopeKey);
15015
- store.setState((state) => {
15016
- const map = state[kind];
15017
- if (scopeKey === ROOT_SCOPE) {
15018
- if (!replace) return { [kind]: { ...map, ...staged } };
15019
- const next = {};
15020
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15021
- return { [kind]: { ...next, ...staged } };
15022
- }
15023
- const current = replace ? {} : map[scopeKey] ?? {};
15024
- return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15025
- });
15026
- registry.valid.add(scopeKey);
15027
- }
15028
- function invalidateResource(store, kind, scopeKey) {
15029
- const registry = getKindRegistry(store, kind);
15030
- if (scopeKey === void 0) {
15031
- registry.valid.clear();
15032
- registry.staged.clear();
15033
- } else {
15034
- registry.valid.delete(scopeKey);
15035
- registry.staged.delete(scopeKey);
15036
- }
15037
- }
15038
- function rebuildResource(store, kind, userScope) {
15039
- const primary = resolvePrimary$1(store);
15040
- invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15041
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15042
- }
15043
- function invalidateAllResources(store) {
15044
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15045
- }
15046
- function removeResourceEntries(store, kind, names, scope, isLeaf) {
15047
- const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15048
- if (staged) for (const name of names) delete staged[name];
15049
- store.setState((state) => {
15050
- const map = state[kind];
15051
- if (scope) {
15052
- const currentScope = { ...map[scope] };
15053
- for (const name of names) delete currentScope[name];
15054
- return { [kind]: { ...map, [scope]: currentScope } };
15055
- }
15056
- const next = { ...map };
15057
- for (const name of names) if (isLeaf(next[name])) delete next[name];
15058
- return { [kind]: next };
15059
- });
15060
- }
15061
- function clearResourceEntries(store, kind, scope, isLeaf) {
15062
- const registry = getKindRegistry(store, kind);
15063
- if (scope && scope !== "root") registry.staged.delete(scope);
15064
- else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15065
- else registry.staged.clear();
15066
- store.setState((state) => {
15067
- const map = state[kind];
15068
- if (scope && scope !== "root") {
15069
- const { [scope]: _, ...rest } = map;
15070
- return { [kind]: rest };
15071
- }
15072
- if (scope === "root") {
15073
- const next = {};
15074
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15075
- return { [kind]: next };
15076
- }
15077
- return { [kind]: {} };
15078
- });
15067
+ return { color: background, background: true };
15068
+ }
15069
+ return null;
15079
15070
  }
15080
15071
 
15081
- function clearHmrCaches(store) {
15082
- const primary = store.getState().primaryStore ?? store;
15083
- invalidateAllResources(primary);
15084
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15072
+ function parseRendererConfig(rendererProp) {
15073
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15074
+ if (isRendererConfig) {
15075
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15076
+ return {
15077
+ primaryCanvas: pc,
15078
+ scheduler: sc,
15079
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
15080
+ };
15081
+ }
15082
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
15085
15083
  }
15086
15084
 
15087
15085
  function CanvasImpl({
@@ -15121,9 +15119,9 @@ function CanvasImpl({
15121
15119
  React__namespace.useMemo(() => extend(THREE), []);
15122
15120
  const Bridge = useBridge();
15123
15121
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15124
- const hasInitialSizeRef = React__namespace.useRef(false);
15122
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15125
15123
  const measureConfig = React__namespace.useMemo(() => {
15126
- if (!hasInitialSizeRef.current) {
15124
+ if (!hasInitialSize) {
15127
15125
  return {
15128
15126
  ...resize,
15129
15127
  scroll: resize?.scroll ?? true,
@@ -15135,7 +15133,7 @@ function CanvasImpl({
15135
15133
  debounce: { scroll: 50, resize: 0 },
15136
15134
  ...resize
15137
15135
  };
15138
- }, [resize, hasInitialSizeRef.current]);
15136
+ }, [hasInitialSize, resize]);
15139
15137
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15140
15138
  const effectiveSize = React__namespace.useMemo(() => {
15141
15139
  let w = width ?? containerRect.width;
@@ -15151,9 +15149,7 @@ function CanvasImpl({
15151
15149
  left: containerRect.left
15152
15150
  };
15153
15151
  }, [width, height, containerRect, forceEven]);
15154
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15155
- hasInitialSizeRef.current = true;
15156
- }
15152
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15157
15153
  const canvasRef = React__namespace.useRef(null);
15158
15154
  const divRef = React__namespace.useRef(null);
15159
15155
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15267,6 +15263,7 @@ function CanvasImpl({
15267
15263
  const canvas = canvasRef.current;
15268
15264
  if (canvas) {
15269
15265
  return () => {
15266
+ if (canvas.isConnected) return;
15270
15267
  unmountComponentAtNode(canvas);
15271
15268
  root.current = null;
15272
15269
  };
@@ -15279,7 +15276,7 @@ function CanvasImpl({
15279
15276
  const handleHMR = () => {
15280
15277
  queueMicrotask(() => {
15281
15278
  const rootEntry = _roots.get(canvas);
15282
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15279
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15283
15280
  });
15284
15281
  };
15285
15282
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
@@ -15325,20 +15322,159 @@ function CanvasImpl({
15325
15322
  }
15326
15323
  );
15327
15324
  }
15328
- function Canvas(props) {
15325
+ function Canvas$1(props) {
15329
15326
  return /* @__PURE__ */ jsxRuntime.jsx(itsFine.FiberProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(CanvasImpl, { ...props }) });
15330
15327
  }
15331
15328
 
15329
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
15330
+ const ROOT_SCOPE = "";
15331
+ const registries = /* @__PURE__ */ new WeakMap();
15332
+ function getKindRegistry(store, kind) {
15333
+ let registry = registries.get(store);
15334
+ if (!registry) {
15335
+ registry = {
15336
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15337
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15338
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15339
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
15340
+ };
15341
+ registries.set(store, registry);
15342
+ }
15343
+ return registry[kind];
15344
+ }
15345
+ function resolvePrimary$1(store) {
15346
+ return store.getState().primaryStore ?? store;
15347
+ }
15348
+ function isScopeValid(store, kind, scopeKey) {
15349
+ return getKindRegistry(store, kind).valid.has(scopeKey);
15350
+ }
15351
+ function peekStaged(store, kind, scopeKey) {
15352
+ return getKindRegistry(store, kind).staged.get(scopeKey);
15353
+ }
15354
+ function stageEntries(store, kind, scopeKey, entries) {
15355
+ const { staged } = getKindRegistry(store, kind);
15356
+ const current = staged.get(scopeKey);
15357
+ if (current) Object.assign(current, entries);
15358
+ else staged.set(scopeKey, { ...entries });
15359
+ }
15360
+ function readCommittedScope(map, scopeKey, isLeaf) {
15361
+ if (scopeKey === ROOT_SCOPE) {
15362
+ const leaves = {};
15363
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
15364
+ return leaves;
15365
+ }
15366
+ const scopeData = map[scopeKey];
15367
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
15368
+ return {};
15369
+ }
15370
+ function withStagedOverlay(store, kind, committed) {
15371
+ const { staged } = getKindRegistry(store, kind);
15372
+ if (staged.size === 0) return committed;
15373
+ const view = { ...committed };
15374
+ for (const [scopeKey, entries] of staged) {
15375
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15376
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
15377
+ }
15378
+ return view;
15379
+ }
15380
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
15381
+ const registry = getKindRegistry(store, kind);
15382
+ const staged = registry.staged.get(scopeKey);
15383
+ if (!staged) return;
15384
+ registry.staged.delete(scopeKey);
15385
+ const replace = !registry.valid.has(scopeKey);
15386
+ store.setState((state) => {
15387
+ const map = state[kind];
15388
+ if (scopeKey === ROOT_SCOPE) {
15389
+ if (!replace) return { [kind]: { ...map, ...staged } };
15390
+ const next = {};
15391
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15392
+ return { [kind]: { ...next, ...staged } };
15393
+ }
15394
+ const current = replace ? {} : map[scopeKey] ?? {};
15395
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15396
+ });
15397
+ registry.valid.add(scopeKey);
15398
+ }
15399
+ function invalidateResource(store, kind, scopeKey) {
15400
+ const registry = getKindRegistry(store, kind);
15401
+ if (scopeKey === void 0) {
15402
+ registry.valid.clear();
15403
+ registry.staged.clear();
15404
+ } else {
15405
+ registry.valid.delete(scopeKey);
15406
+ registry.staged.delete(scopeKey);
15407
+ }
15408
+ }
15409
+ function rebuildResource(store, kind, userScope) {
15410
+ const primary = resolvePrimary$1(store);
15411
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15412
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15413
+ }
15414
+ function invalidateAllResources(store) {
15415
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15416
+ }
15417
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
15418
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15419
+ if (staged) for (const name of names) delete staged[name];
15420
+ store.setState((state) => {
15421
+ const map = state[kind];
15422
+ if (scope) {
15423
+ const currentScope = { ...map[scope] };
15424
+ for (const name of names) delete currentScope[name];
15425
+ return { [kind]: { ...map, [scope]: currentScope } };
15426
+ }
15427
+ const next = { ...map };
15428
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
15429
+ return { [kind]: next };
15430
+ });
15431
+ }
15432
+ function clearResourceEntries(store, kind, scope, isLeaf) {
15433
+ const registry = getKindRegistry(store, kind);
15434
+ if (scope && scope !== "root") registry.staged.delete(scope);
15435
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15436
+ else registry.staged.clear();
15437
+ store.setState((state) => {
15438
+ const map = state[kind];
15439
+ if (scope && scope !== "root") {
15440
+ const { [scope]: _, ...rest } = map;
15441
+ return { [kind]: rest };
15442
+ }
15443
+ if (scope === "root") {
15444
+ const next = {};
15445
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15446
+ return { [kind]: next };
15447
+ }
15448
+ return { [kind]: {} };
15449
+ });
15450
+ }
15451
+
15452
+ const isObjectLike = (value) => value !== null && (typeof value === "object" || typeof value === "function");
15453
+ const hasValidLegacyNodeMarkers = (value) => {
15454
+ const hasUuid = "uuid" in value;
15455
+ const hasNodeType = "nodeType" in value;
15456
+ const uuid = Reflect.get(value, "uuid");
15457
+ const nodeType = Reflect.get(value, "nodeType");
15458
+ return (hasUuid || hasNodeType) && (!hasUuid || uuid === void 0 || typeof uuid === "string") && (!hasNodeType || nodeType === void 0 || nodeType === null || typeof nodeType === "string");
15459
+ };
15460
+ const isUniformNode = (value) => isObjectLike(value) && "isUniformNode" in value && value.isUniformNode === true;
15461
+ const isTSLNode = (value) => isObjectLike(value) && (Reflect.get(value, "isNode") === true || Reflect.get(value, "shaderNode") !== void 0 && typeof Reflect.get(value, "id") === "number" || hasValidLegacyNodeMarkers(value));
15462
+ const isBufferLike = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView) || isObjectLike(value) && ("isBufferAttribute" in value || "isInterleavedBufferAttribute" in value || isTSLNode(value));
15463
+ const isStorageLike = (value) => isObjectLike(value) && ("isTexture" in value || "isData3DTexture" in value || isTSLNode(value));
15464
+
15332
15465
  var __defProp = Object.defineProperty;
15333
15466
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15334
15467
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15335
- var _a;
15468
+ var _a, _b;
15336
15469
  const INTERNAL_DATA = Symbol("ScopedStore.data");
15337
- _a = INTERNAL_DATA;
15470
+ const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
15471
+ _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
15338
15472
  const _ScopedStore = class _ScopedStore {
15339
- constructor(data) {
15473
+ constructor(data, isLeaf) {
15474
+ __publicField(this, _b);
15340
15475
  __publicField(this, _a);
15341
15476
  this[INTERNAL_DATA] = data;
15477
+ this[INTERNAL_IS_LEAF] = isLeaf;
15342
15478
  return new Proxy(this, {
15343
15479
  get(target, prop, receiver) {
15344
15480
  if (typeof prop === "string") {
@@ -15372,10 +15508,10 @@ const _ScopedStore = class _ScopedStore {
15372
15508
  * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15373
15509
  */
15374
15510
  scope(key) {
15375
- const data = this[INTERNAL_DATA][key];
15376
- return new _ScopedStore(
15377
- data && typeof data === "object" ? data : {}
15378
- );
15511
+ const value = this[INTERNAL_DATA][key];
15512
+ const isLeaf = this[INTERNAL_IS_LEAF];
15513
+ const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
15514
+ return new _ScopedStore(scope, isLeaf);
15379
15515
  }
15380
15516
  /**
15381
15517
  * Check if a key exists in the store.
@@ -15391,8 +15527,8 @@ const _ScopedStore = class _ScopedStore {
15391
15527
  }
15392
15528
  };
15393
15529
  let ScopedStore = _ScopedStore;
15394
- function createScopedStore(data) {
15395
- return new ScopedStore(data);
15530
+ function createScopedStore(data, isLeaf) {
15531
+ return new ScopedStore(data, isLeaf);
15396
15532
  }
15397
15533
  function createLazyCreatorState(state, store) {
15398
15534
  let _uniforms = null;
@@ -15403,22 +15539,22 @@ function createLazyCreatorState(state, store) {
15403
15539
  return Object.create(state, {
15404
15540
  uniforms: {
15405
15541
  get() {
15406
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15542
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
15407
15543
  }
15408
15544
  },
15409
15545
  nodes: {
15410
15546
  get() {
15411
- return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15547
+ return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
15412
15548
  }
15413
15549
  },
15414
15550
  buffers: {
15415
15551
  get() {
15416
- return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15552
+ return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
15417
15553
  }
15418
15554
  },
15419
15555
  gpuStorage: {
15420
15556
  get() {
15421
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15557
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
15422
15558
  }
15423
15559
  }
15424
15560
  });
@@ -15438,44 +15574,10 @@ function usePrimaryThree(selector = (state) => state, equalityFn) {
15438
15574
  return primary(selector, equalityFn);
15439
15575
  }
15440
15576
 
15441
- function addTexture(set, key, value) {
15442
- set((state) => {
15443
- const newMap = new Map(state.textures);
15444
- newMap.set(key, value);
15445
- return { textures: newMap };
15446
- });
15447
- }
15448
- function addTextures(set, items) {
15449
- set((state) => {
15450
- const newMap = new Map(state.textures);
15451
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
15452
- for (const [key, value] of entries) {
15453
- newMap.set(key, value);
15454
- }
15455
- return { textures: newMap };
15456
- });
15457
- }
15458
- function removeTexture(set, key) {
15459
- set((state) => {
15460
- const newMap = new Map(state.textures);
15461
- newMap.delete(key);
15462
- return { textures: newMap };
15463
- });
15464
- }
15465
- function removeTextures(set, keys) {
15466
- set((state) => {
15467
- const newMap = new Map(state.textures);
15468
- for (const key of keys) newMap.delete(key);
15469
- return { textures: newMap };
15470
- });
15471
- }
15472
- function createTextureOperations(set) {
15473
- return {
15474
- add: (key, value) => addTexture(set, key, value),
15475
- addMultiple: (items) => addTextures(set, items),
15476
- remove: (key) => removeTexture(set, key),
15477
- removeMultiple: (keys) => removeTextures(set, keys)
15478
- };
15577
+ function scopedNodeName(scope, name) {
15578
+ const raw = scope ? `${scope}_${name}` : name;
15579
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15580
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15479
15581
  }
15480
15582
  function extractTSLValue(value) {
15481
15583
  if (value === null || value === void 0) return value;
@@ -15514,7 +15616,7 @@ function vectorize(inObject) {
15514
15616
  return extractTSLValue(inObject);
15515
15617
  }
15516
15618
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15517
- if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15619
+ if (obj.isMatrix2 || obj.isMatrix3 || obj.isMatrix4) return inObject;
15518
15620
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15519
15621
  if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15520
15622
  const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
@@ -15541,6 +15643,47 @@ function useCompareMemoize(value, deep) {
15541
15643
  return ref.current;
15542
15644
  }
15543
15645
 
15646
+ function clearHmrCaches(store) {
15647
+ const primary = store.getState().primaryStore ?? store;
15648
+ invalidateAllResources(primary);
15649
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15650
+ }
15651
+
15652
+ const SHARED_KEYS = ["uniforms", "nodes", "buffers", "gpuStorage", "_hmrVersion"];
15653
+ const following = /* @__PURE__ */ new WeakMap();
15654
+ function followPrimary(store) {
15655
+ const primary = store.getState().primaryStore;
15656
+ if (!primary || primary === store) return;
15657
+ const sync = () => {
15658
+ const source = primary.getState();
15659
+ const target = store.getState();
15660
+ let patch = null;
15661
+ for (const key of SHARED_KEYS) {
15662
+ if (target[key] !== source[key]) (patch ?? (patch = {}))[key] = source[key];
15663
+ }
15664
+ if (patch) store.setState(patch);
15665
+ };
15666
+ sync();
15667
+ following.set(store, primary.subscribe(sync));
15668
+ }
15669
+ function stopFollowing(store) {
15670
+ following.get(store)?.();
15671
+ following.delete(store);
15672
+ }
15673
+ let registered = false;
15674
+ function ensureTSLExtension() {
15675
+ if (registered) return;
15676
+ registered = true;
15677
+ registerRootExtension({
15678
+ name: "@react-three/tsl",
15679
+ setup: followPrimary,
15680
+ dispose: stopFollowing,
15681
+ // Resolves primaryStore itself, so every canvas sharing a renderer refreshes the same resources.
15682
+ hmr: clearHmrCaches
15683
+ });
15684
+ }
15685
+
15686
+ ensureTSLExtension();
15544
15687
  function useScopedResource(options) {
15545
15688
  const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15546
15689
  const scopeKey = scope ?? ROOT_SCOPE;
@@ -15572,22 +15715,21 @@ function useScopedResource(options) {
15572
15715
  return entries;
15573
15716
  }
15574
15717
 
15575
- const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15576
15718
  function useUniforms(creatorOrScope, scope) {
15577
15719
  const store = usePrimaryStore();
15578
15720
  const removeUniforms2 = React.useCallback(
15579
- (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15721
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode),
15580
15722
  [store]
15581
15723
  );
15582
15724
  const clearUniforms = React.useCallback(
15583
- (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15725
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode),
15584
15726
  [store]
15585
15727
  );
15586
15728
  const rebuildUniforms = React.useCallback(
15587
15729
  (targetScope) => rebuildResource(store, "uniforms", targetScope),
15588
15730
  [store]
15589
15731
  );
15590
- const inputForMemoization = React.useMemo(() => {
15732
+ const processedInput = React.useMemo(() => {
15591
15733
  let raw = creatorOrScope;
15592
15734
  if (is.fun(creatorOrScope)) {
15593
15735
  const wrappedState = createLazyCreatorState(store.getState(), store);
@@ -15598,23 +15740,24 @@ function useUniforms(creatorOrScope, scope) {
15598
15740
  for (const [key, value] of Object.entries(raw)) {
15599
15741
  normalized[key] = vectorize(value);
15600
15742
  }
15601
- return normalized;
15743
+ return { candidates: raw, normalized };
15602
15744
  }
15603
- return raw;
15745
+ return { candidates: raw, normalized: raw };
15604
15746
  }, [creatorOrScope, store]);
15605
- const memoizedInput = useCompareMemoize(inputForMemoization);
15747
+ const memoizedInput = useCompareMemoize(processedInput.normalized);
15606
15748
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15607
15749
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15608
15750
  const created = useScopedResource({
15609
15751
  store,
15610
15752
  kind: "uniforms",
15611
15753
  scope: isReader ? void 0 : scope,
15612
- isLeaf: isUniformNode$1,
15754
+ isLeaf: isUniformNode,
15613
15755
  input: memoizedInput,
15614
15756
  create: () => {
15615
15757
  if (isReader) return {};
15616
- if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15617
- return memoizedInput;
15758
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
15759
+ throw new Error("Invalid uniform input");
15760
+ return processedInput.candidates;
15618
15761
  },
15619
15762
  prepare: (name, candidate) => createUniform(name, candidate, scope),
15620
15763
  reconcile: (existing, candidate) => {
@@ -15636,7 +15779,7 @@ function useUniforms(creatorOrScope, scope) {
15636
15779
  uniforms = storeUniforms;
15637
15780
  } else if (typeof memoizedInput === "string") {
15638
15781
  const scopeData = storeUniforms[memoizedInput];
15639
- uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15782
+ uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
15640
15783
  }
15641
15784
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15642
15785
  }
@@ -15651,7 +15794,7 @@ function removeUniforms(set, names, scope) {
15651
15794
  return { uniforms: { ...state.uniforms, [scope]: currentScope } };
15652
15795
  }
15653
15796
  const uniforms = { ...state.uniforms };
15654
- for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
15797
+ for (const name of names) if (isUniformNode(uniforms[name])) delete uniforms[name];
15655
15798
  return { uniforms };
15656
15799
  });
15657
15800
  }
@@ -15665,7 +15808,7 @@ function clearRootUniforms(set) {
15665
15808
  set((state) => {
15666
15809
  const uniforms = {};
15667
15810
  for (const [key, value] of Object.entries(state.uniforms)) {
15668
- if (!isUniformNode$1(value)) uniforms[key] = value;
15811
+ if (!isUniformNode(value)) uniforms[key] = value;
15669
15812
  }
15670
15813
  return { uniforms };
15671
15814
  });
@@ -15675,8 +15818,7 @@ function createUniform(inName, node, scope) {
15675
15818
  const inValue = vectorize(node);
15676
15819
  const newUniform = tsl.uniform(inValue);
15677
15820
  if (typeof newUniform.setName === "function") {
15678
- const name = scope ? `${scope}_${inName}` : inName;
15679
- newUniform.setName(name);
15821
+ newUniform.setName(scopedNodeName(scope, inName));
15680
15822
  }
15681
15823
  return newUniform;
15682
15824
  }
@@ -15702,14 +15844,12 @@ function hasEqualsMethod(value) {
15702
15844
  function isSameThreeType(a, b) {
15703
15845
  const obj_a = a;
15704
15846
  const obj_b = b;
15705
- return obj_a.isColor && obj_b.isColor || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15847
+ return obj_a.isColor && obj_b.isColor || obj_a.isMatrix2 && obj_b.isMatrix2 || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15706
15848
  }
15707
15849
 
15708
15850
  const uniformOf = tsl.uniform;
15709
- const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15710
- const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15711
15851
  function useUniform(name, value) {
15712
- const store = useStore();
15852
+ const store = usePrimaryStore();
15713
15853
  const registered = useScopedResource({
15714
15854
  store,
15715
15855
  kind: "uniforms",
@@ -15719,7 +15859,7 @@ function useUniform(name, value) {
15719
15859
  create: () => value === void 0 ? {} : { [name]: value },
15720
15860
  prepare: createNamedUniform,
15721
15861
  reconcile: (existing2) => {
15722
- if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15862
+ if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
15723
15863
  existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
15724
15864
  }
15725
15865
  }
@@ -15735,7 +15875,7 @@ function createNamedUniform(name, value) {
15735
15875
  let node;
15736
15876
  if (isUniformNode(value)) {
15737
15877
  node = value;
15738
- } else if (isTSLNode$1(value)) {
15878
+ } else if (isTSLNode(value)) {
15739
15879
  node = uniformOf(value);
15740
15880
  } else if (typeof value === "string") {
15741
15881
  node = tsl.uniform(new webgpu.Color(value));
@@ -15748,7 +15888,6 @@ function createNamedUniform(name, value) {
15748
15888
  return node;
15749
15889
  }
15750
15890
 
15751
- const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15752
15891
  function useNodes(creatorOrScope, scope) {
15753
15892
  const store = usePrimaryStore();
15754
15893
  const removeNodes2 = React.useCallback(
@@ -15775,7 +15914,8 @@ function useNodes(creatorOrScope, scope) {
15775
15914
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15776
15915
  },
15777
15916
  prepare: (name, node) => {
15778
- node.setName?.(scope ? `${scope}.${name}` : name);
15917
+ const setName = Reflect.get(node, "setName");
15918
+ if (typeof setName === "function") setName.call(node, scopedNodeName(scope, name));
15779
15919
  return node;
15780
15920
  }
15781
15921
  });
@@ -15830,13 +15970,6 @@ function useLocalNodes(creator) {
15830
15970
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15831
15971
  }
15832
15972
 
15833
- const isBufferLike = (value) => {
15834
- if (value === null || typeof value !== "object") return false;
15835
- if (ArrayBuffer.isView(value)) return true;
15836
- if ("isBufferAttribute" in value) return true;
15837
- if ("uuid" in value || "nodeType" in value) return true;
15838
- return false;
15839
- };
15840
15973
  const disposeBuffer = (buffer) => {
15841
15974
  if (buffer === null || typeof buffer !== "object") return;
15842
15975
  if ("dispose" in buffer && typeof buffer.dispose === "function") {
@@ -15883,9 +16016,8 @@ function useBuffers(creatorOrScope, scope) {
15883
16016
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15884
16017
  },
15885
16018
  prepare: (name, buffer) => {
15886
- if ("setName" in buffer && typeof buffer.setName === "function") {
15887
- buffer.setName(scope ? `${scope}.${name}` : name);
15888
- }
16019
+ const setName = Reflect.get(buffer, "setName");
16020
+ if (typeof setName === "function") setName.call(buffer, scopedNodeName(scope, name));
15889
16021
  return buffer;
15890
16022
  }
15891
16023
  });
@@ -15902,13 +16034,6 @@ function rebuildAllBuffers(store, scope) {
15902
16034
  rebuildResource(store, "buffers", scope);
15903
16035
  }
15904
16036
 
15905
- const isStorageLike = (value) => {
15906
- if (value === null || typeof value !== "object") return false;
15907
- if ("isTexture" in value) return true;
15908
- if ("isData3DTexture" in value) return true;
15909
- if ("uuid" in value || "nodeType" in value) return true;
15910
- return false;
15911
- };
15912
16037
  const disposeStorage = (storage) => {
15913
16038
  if (storage === null || typeof storage !== "object") return;
15914
16039
  if ("dispose" in storage && typeof storage.dispose === "function") {
@@ -15955,10 +16080,9 @@ function useGPUStorage(creatorOrScope, scope) {
15955
16080
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15956
16081
  },
15957
16082
  prepare: (name, storage) => {
15958
- const label = scope ? `${scope}.${name}` : name;
15959
- if ("setName" in storage && typeof storage.setName === "function") {
15960
- storage.setName(label);
15961
- }
16083
+ const label = scopedNodeName(scope, name);
16084
+ const setName = Reflect.get(storage, "setName");
16085
+ if (typeof setName === "function") setName.call(storage, label);
15962
16086
  if ("name" in storage && typeof storage.name === "string") {
15963
16087
  storage.name = label;
15964
16088
  }
@@ -15984,11 +16108,58 @@ function rebuildAllStorage(store, scope) {
15984
16108
  rebuildResource(store, "gpuStorage", scope);
15985
16109
  }
15986
16110
 
16111
+ function addTexture(set, key, value) {
16112
+ set((state) => {
16113
+ const newMap = new Map(state.textures);
16114
+ newMap.set(key, value);
16115
+ return { textures: newMap };
16116
+ });
16117
+ }
16118
+ function addTextures(set, items) {
16119
+ set((state) => {
16120
+ const newMap = new Map(state.textures);
16121
+ const entries = items instanceof Map ? items.entries() : Object.entries(items);
16122
+ for (const [key, value] of entries) {
16123
+ newMap.set(key, value);
16124
+ }
16125
+ return { textures: newMap };
16126
+ });
16127
+ }
16128
+ function removeTexture(set, key) {
16129
+ set((state) => {
16130
+ const newMap = new Map(state.textures);
16131
+ newMap.delete(key);
16132
+ return { textures: newMap };
16133
+ });
16134
+ }
16135
+ function removeTextures(set, keys) {
16136
+ set((state) => {
16137
+ const newMap = new Map(state.textures);
16138
+ for (const key of keys) newMap.delete(key);
16139
+ return { textures: newMap };
16140
+ });
16141
+ }
16142
+ function createTextureOperations(set) {
16143
+ return {
16144
+ add: (key, value) => addTexture(set, key, value),
16145
+ addMultiple: (items) => addTextures(set, items),
16146
+ remove: (key) => removeTexture(set, key),
16147
+ removeMultiple: (keys) => removeTextures(set, keys)
16148
+ };
16149
+ }
16150
+
16151
+ function renderThroughPipeline(store) {
16152
+ return () => {
16153
+ const pipeline = store.getState().renderPipeline;
16154
+ if (pipeline?.render) pipeline.render();
16155
+ };
16156
+ }
15987
16157
  function useRenderPipeline(mainCB, setupCB) {
15988
16158
  const store = useStore();
15989
16159
  const { scene, camera, renderer, isLegacy } = useThree$1();
15990
16160
  const callbacksRanRef = React.useRef(false);
15991
16161
  const scenePassCacheRef = React.useRef(null);
16162
+ const forceScenePassRebuildRef = React.useRef(false);
15992
16163
  const mainCBRef = React.useRef(mainCB);
15993
16164
  const setupCBRef = React.useRef(setupCB);
15994
16165
  mainCBRef.current = mainCB;
@@ -15996,7 +16167,7 @@ function useRenderPipeline(mainCB, setupCB) {
15996
16167
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15997
16168
  React.useEffect(() => {
15998
16169
  callbacksRanRef.current = false;
15999
- scenePassCacheRef.current = null;
16170
+ forceScenePassRebuildRef.current = true;
16000
16171
  }, []);
16001
16172
  const clearPasses = React.useCallback(() => {
16002
16173
  store.setState({ passes: {} });
@@ -16006,12 +16177,15 @@ function useRenderPipeline(mainCB, setupCB) {
16006
16177
  renderPipeline: null,
16007
16178
  passes: {}
16008
16179
  });
16180
+ setRenderOverride(store, null);
16181
+ scenePassCacheRef.current?.scenePass.dispose();
16009
16182
  callbacksRanRef.current = false;
16010
16183
  scenePassCacheRef.current = null;
16184
+ forceScenePassRebuildRef.current = false;
16011
16185
  }, [store]);
16012
16186
  const rebuild = React.useCallback(() => {
16013
16187
  callbacksRanRef.current = false;
16014
- scenePassCacheRef.current = null;
16188
+ forceScenePassRebuildRef.current = true;
16015
16189
  setRebuildVersion((v) => v + 1);
16016
16190
  }, []);
16017
16191
  React.useLayoutEffect(() => {
@@ -16019,6 +16193,9 @@ function useRenderPipeline(mainCB, setupCB) {
16019
16193
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16020
16194
  }
16021
16195
  if (!renderer || !scene || !camera) return;
16196
+ if (!("isWebGPURenderer" in renderer)) {
16197
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16198
+ }
16022
16199
  const state = store.getState();
16023
16200
  const set = store.setState;
16024
16201
  try {
@@ -16029,21 +16206,34 @@ function useRenderPipeline(mainCB, setupCB) {
16029
16206
  pipeline = new webgpu.RenderPipeline(renderer);
16030
16207
  justCreatedPipeline = true;
16031
16208
  }
16032
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16209
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16210
+ let outgoingScenePass = null;
16033
16211
  let scenePass;
16034
16212
  if (cacheValid) {
16035
16213
  scenePass = scenePassCacheRef.current.scenePass;
16036
16214
  } else {
16215
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16037
16216
  scenePass = tsl.pass(scene, camera);
16038
16217
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16218
+ forceScenePassRebuildRef.current = false;
16039
16219
  }
16040
16220
  currentPasses.scenePass = scenePass;
16041
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16221
+ let outputNodeChanged = false;
16222
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16223
+ pipeline.outputNode = scenePass;
16224
+ outputNodeChanged = true;
16225
+ }
16042
16226
  set({ renderPipeline: pipeline, passes: currentPasses });
16227
+ setRenderOverride(store, renderThroughPipeline(store));
16043
16228
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16044
16229
  if (shouldRunCallbacks) {
16045
16230
  if (setupCBRef.current) {
16046
- const freshState = store.getState();
16231
+ const freshState = Object.assign({}, store.getState(), {
16232
+ renderPipeline: pipeline,
16233
+ passes: { ...currentPasses, scenePass },
16234
+ renderer,
16235
+ isLegacy: false
16236
+ });
16047
16237
  const setupResult = setupCBRef.current(freshState);
16048
16238
  if (setupResult && typeof setupResult === "object") {
16049
16239
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16051,35 +16241,38 @@ function useRenderPipeline(mainCB, setupCB) {
16051
16241
  }
16052
16242
  }
16053
16243
  if (mainCBRef.current) {
16054
- const freshState = store.getState();
16244
+ const freshState = Object.assign({}, store.getState(), {
16245
+ renderPipeline: pipeline,
16246
+ passes: { ...currentPasses, scenePass },
16247
+ renderer,
16248
+ isLegacy: false
16249
+ });
16055
16250
  const mainResult = mainCBRef.current(freshState);
16056
16251
  if (mainResult && typeof mainResult === "object") {
16057
16252
  currentPasses = { ...currentPasses, ...mainResult };
16058
16253
  set({ passes: currentPasses });
16059
16254
  }
16060
16255
  }
16256
+ outputNodeChanged = true;
16061
16257
  callbacksRanRef.current = true;
16062
16258
  }
16259
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16260
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16063
16261
  } catch (error) {
16064
16262
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16065
16263
  }
16066
16264
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16067
16265
  const passes = useThree$1((s) => s.passes);
16068
16266
  const renderPipeline = useThree$1((s) => s.renderPipeline);
16069
- return {
16070
- passes,
16071
- renderPipeline,
16072
- clearPasses,
16073
- reset,
16074
- rebuild,
16075
- // isReady indicates if RenderPipeline is configured and ready for rendering
16076
- isReady: renderPipeline !== null
16077
- };
16267
+ const actions = { passes, clearPasses, reset, rebuild };
16268
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16269
+ return { ...actions, isReady: false, renderPipeline: null };
16078
16270
  }
16079
16271
 
16080
16272
  extend(THREE);
16081
16273
  const useThree = useThree$1;
16082
16274
  const useFrame = useFrame$1;
16275
+ const Canvas = Canvas$1;
16083
16276
 
16084
16277
  exports.Scheduler = scheduler.Scheduler;
16085
16278
  exports.getScheduler = scheduler.getScheduler;
@@ -16157,10 +16350,12 @@ exports.rebuildAllStorage = rebuildAllStorage;
16157
16350
  exports.rebuildAllUniforms = rebuildAllUniforms;
16158
16351
  exports.reconciler = reconciler;
16159
16352
  exports.registerPrimary = registerPrimary;
16353
+ exports.registerRootExtension = registerRootExtension;
16160
16354
  exports.removeInteractivity = removeInteractivity;
16161
16355
  exports.removeNodes = removeNodes;
16162
16356
  exports.removeUniforms = removeUniforms;
16163
16357
  exports.resolve = resolve;
16358
+ exports.setRenderOverride = setRenderOverride;
16164
16359
  exports.swapInteractivity = swapInteractivity;
16165
16360
  exports.unmountComponentAtNode = unmountComponentAtNode;
16166
16361
  exports.unregisterPrimary = unregisterPrimary;