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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
- const Inspector_js = require('three/addons/inspector/Inspector.js');
5
4
  const jsxRuntime = require('react/jsx-runtime');
6
5
  const React = require('react');
7
6
  const useMeasure = require('react-use-measure');
@@ -11,6 +10,7 @@ const scheduler = require('@pmndrs/scheduler');
11
10
  const Tb = require('scheduler');
12
11
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
13
12
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
13
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
14
14
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
15
15
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
16
16
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -54,6 +54,10 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
54
54
 
55
55
  const R3F_BUILD_LEGACY = false;
56
56
  const R3F_BUILD_WEBGPU = true;
57
+ async function loadInspector() {
58
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
59
+ return Inspector;
60
+ }
57
61
  const WebGLRenderer = class WebGLRenderer2 {
58
62
  constructor() {
59
63
  throw new Error(
@@ -67,13 +71,13 @@ const WebGLCubeRenderTarget = null;
67
71
  const THREE = /*#__PURE__*/_mergeNamespaces({
68
72
  __proto__: null,
69
73
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
70
- Inspector: Inspector_js.Inspector,
71
74
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
75
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
76
  RenderTargetCompat: webgpu.RenderTarget,
74
77
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
75
78
  WebGLRenderTarget: WebGLRenderTarget,
76
- WebGLRenderer: WebGLRenderer
79
+ WebGLRenderer: WebGLRenderer,
80
+ loadInspector: loadInspector
77
81
  }, [webgpu__namespace]);
78
82
 
79
83
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -320,7 +324,6 @@ function prepare(target, root, type, props) {
320
324
  }
321
325
  function invalidateInstance(instance) {
322
326
  if (!instance.parent) return;
323
- instance.props.onUpdate?.(instance.object);
324
327
  const state = instance.root?.getState?.();
325
328
  if (state && state.internal.frames === 0) state.invalidate();
326
329
  }
@@ -536,8 +539,10 @@ function hasVisibilityHandlers(handlers) {
536
539
  }
537
540
 
538
541
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
539
- function fromRef(ref) {
540
- return { [FROM_REF]: ref };
542
+ function fromRef(ref, transform) {
543
+ const marker = { [FROM_REF]: ref };
544
+ if (transform) marker.transform = transform;
545
+ return marker;
541
546
  }
542
547
  function isFromRef(value) {
543
548
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -551,6 +556,63 @@ function isOnce(value) {
551
556
  return value !== null && typeof value === "object" && ONCE in value;
552
557
  }
553
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
+
554
616
  const RESERVED_PROPS = [
555
617
  "children",
556
618
  "key",
@@ -560,7 +622,6 @@ const RESERVED_PROPS = [
560
622
  "dispose",
561
623
  "attach",
562
624
  "object",
563
- "onUpdate",
564
625
  // Behavior flags
565
626
  "dispose"
566
627
  ];
@@ -638,11 +699,9 @@ function diffProps(instance, newProps) {
638
699
  for (const prop in instance.props) {
639
700
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
640
701
  const { root, key } = resolve(instance.object, prop);
641
- if (root.constructor && root.constructor.length === 0) {
702
+ if (typeof root?.constructor === "function") {
642
703
  const ctor = getMemoizedPrototype(root);
643
704
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
644
- } else {
645
- changedProps[prop] = 0;
646
705
  }
647
706
  }
648
707
  return changedProps;
@@ -654,6 +713,13 @@ function applyProps(object, props) {
654
713
  for (const prop in props) {
655
714
  const value = props[prop];
656
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
+ }
657
723
  if (instance && EVENT_REGEX.test(prop)) {
658
724
  if (typeof value === "function") instance.handlers[prop] = value;
659
725
  else delete instance.handlers[prop];
@@ -725,380 +791,51 @@ function applyProps(object, props) {
725
791
  root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
726
792
  root[key].colorSpace = rootState.textureColorSpace;
727
793
  }
728
- }
729
- }
730
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
731
- const object2 = instance.object;
732
- const index = rootState.internal.interaction.indexOf(object2);
733
- if (index > -1) rootState.internal.interaction.splice(index, 1);
734
- if (instance.eventCount && object2.raycast !== null) {
735
- rootState.internal.interaction.push(object2);
736
- }
737
- }
738
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
739
- const object2 = instance.object;
740
- const root = findInitialRoot(instance);
741
- const visibilityHandlers = {
742
- onFramed: instance.handlers.onFramed,
743
- onOccluded: instance.handlers.onOccluded,
744
- onVisible: instance.handlers.onVisible
745
- };
746
- if (hasVisibilityHandlers(visibilityHandlers)) {
747
- if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
748
- registerVisibility(root, object2, visibilityHandlers);
749
- }
750
- } else {
751
- unregisterVisibility(root, object2);
752
- }
753
- }
754
- if (instance && instance.props.attach === void 0) {
755
- if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
756
- else if (instance.object.isMaterial) instance.props.attach = "material";
757
- }
758
- if (instance) invalidateInstance(instance);
759
- return object;
760
- }
761
-
762
- const shownNotices = /* @__PURE__ */ new Set();
763
- function notifyDepreciated({ heading, body, link }) {
764
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
765
- return;
766
- }
767
- if (shownNotices.has(heading)) return;
768
- shownNotices.add(heading);
769
- const caller = getCallerFrame();
770
- if (caller) {
771
- console.log();
772
- }
773
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
774
- if (caller) {
775
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
776
- }
777
- if (body || link) {
778
- let message = "";
779
- if (body) message += body;
780
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
781
- console.warn(`%c${message}`, "font-weight: bold;");
782
- }
783
- }
784
- function getCallerFrame(depth = 3) {
785
- const stack = new Error().stack;
786
- if (!stack) return null;
787
- const lines = stack.split("\n");
788
- const frame = lines[depth];
789
- if (!frame) return null;
790
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
791
- if (!match) {
792
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
793
- }
794
- if (!match) return null;
795
- const [, fn, url, line] = match;
796
- return {
797
- functionName: fn ?? "<anonymous>",
798
- location: formatLocation(url, Number(line))
799
- };
800
- }
801
- function formatLocation(url, line) {
802
- const clean = url.split("?")[0];
803
- const file = clean.split("/").pop() ?? clean;
804
- return `${file}:${line}`;
805
- }
806
- function notifyAlpha({ message, link }) {
807
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
808
- return;
809
- }
810
- if (shownNotices.has(message)) return;
811
- shownNotices.add(message);
812
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
813
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
814
- {
815
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
816
- }
817
- }
818
-
819
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
820
- function isInternalRendererAccess(stack, callerDepth = 2) {
821
- if (!stack) return false;
822
- const caller = stack.split("\n")[callerDepth] ?? "";
823
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
824
- }
825
-
826
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
827
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
828
- const createStore = (invalidate, advance) => {
829
- const rootStore = traditional.createWithEqualityFn((set, get) => {
830
- const position = new webgpu.Vector3();
831
- const defaultTarget = new webgpu.Vector3();
832
- const tempTarget = new webgpu.Vector3();
833
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
834
- const { width, height, top, left } = size;
835
- const aspect = width / height;
836
- if (target.isVector3) tempTarget.copy(target);
837
- else tempTarget.set(...target);
838
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
839
- if (isOrthographicCamera(camera)) {
840
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
841
- } else {
842
- const fov = camera.fov * Math.PI / 180;
843
- const h = 2 * Math.tan(fov / 2) * distance;
844
- const w = h * (width / height);
845
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
846
- }
847
- }
848
- let performanceTimeout = void 0;
849
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
850
- const pointer = new webgpu.Vector2();
851
- const rootState = {
852
- set,
853
- get,
854
- // Mock objects that have to be configured
855
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
856
- primaryStore: null,
857
- gl: null,
858
- renderer: null,
859
- camera: null,
860
- frustum: new webgpu.Frustum(),
861
- autoUpdateFrustum: true,
862
- raycaster: null,
863
- events: {
864
- priority: 1,
865
- enabled: true,
866
- connected: false,
867
- frameTimedRaycasts: true,
868
- alwaysFireOnScroll: true,
869
- updateOnFrame: false
870
- },
871
- scene: null,
872
- rootScene: null,
873
- xr: null,
874
- inspector: null,
875
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
876
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
877
- textureColorSpace: webgpu.SRGBColorSpace,
878
- isLegacy: false,
879
- webGPUSupported: false,
880
- isNative: false,
881
- controls: null,
882
- pointer,
883
- mouse: pointer,
884
- frameloop: "always",
885
- onPointerMissed: void 0,
886
- onDragOverMissed: void 0,
887
- onDropMissed: void 0,
888
- performance: {
889
- current: 1,
890
- min: 0.5,
891
- max: 1,
892
- debounce: 200,
893
- regress: () => {
894
- const state2 = get();
895
- if (performanceTimeout) clearTimeout(performanceTimeout);
896
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
897
- performanceTimeout = setTimeout(
898
- () => setPerformanceCurrent(get().performance.max),
899
- state2.performance.debounce
900
- );
901
- }
902
- },
903
- size: { width: 0, height: 0, top: 0, left: 0 },
904
- viewport: {
905
- initialDpr: 0,
906
- dpr: 0,
907
- width: 0,
908
- height: 0,
909
- top: 0,
910
- left: 0,
911
- aspect: 0,
912
- distance: 0,
913
- factor: 0,
914
- getCurrentViewport
915
- },
916
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
917
- setSize: (width, height, top, left) => {
918
- const state2 = get();
919
- if (width === void 0) {
920
- set({ _sizeImperative: false });
921
- if (state2._sizeProps) {
922
- const { width: propW, height: propH } = state2._sizeProps;
923
- if (propW !== void 0 || propH !== void 0) {
924
- const currentSize = state2.size;
925
- const newSize = {
926
- width: propW ?? currentSize.width,
927
- height: propH ?? currentSize.height,
928
- top: currentSize.top,
929
- left: currentSize.left
930
- };
931
- set((s) => ({
932
- size: newSize,
933
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
934
- }));
935
- scheduler.getScheduler().invalidate();
936
- }
937
- }
938
- return;
939
- }
940
- const w = width;
941
- const h = height ?? width;
942
- const t = top ?? state2.size.top;
943
- const l = left ?? state2.size.left;
944
- const size = { width: w, height: h, top: t, left: l };
945
- set((s) => ({
946
- size,
947
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
948
- _sizeImperative: true
949
- }));
950
- scheduler.getScheduler().invalidate();
951
- },
952
- setDpr: (dpr) => set((state2) => {
953
- const resolved = calculateDpr(dpr);
954
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
955
- }),
956
- setFrameloop: (frameloop = "always") => {
957
- set(() => ({ frameloop }));
958
- scheduler.getScheduler().frameloop = frameloop;
959
- },
960
- setError: (error) => set(() => ({ error })),
961
- error: null,
962
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
963
- uniforms: {},
964
- nodes: {},
965
- buffers: {},
966
- gpuStorage: {},
967
- textures: /* @__PURE__ */ new Map(),
968
- _textureRefs: /* @__PURE__ */ new Map(),
969
- renderPipeline: null,
970
- passes: {},
971
- _hmrVersion: 0,
972
- _sizeImperative: false,
973
- _sizeProps: null,
974
- previousRoot: void 0,
975
- internal: {
976
- // Events
977
- interaction: [],
978
- subscribers: [],
979
- // Per-pointer state (new unified structure)
980
- pointerMap: /* @__PURE__ */ new Map(),
981
- pointerDirty: /* @__PURE__ */ new Map(),
982
- lastEvent: React__namespace.createRef(),
983
- // Deprecated but kept for backwards compatibility
984
- hovered: /* @__PURE__ */ new Map(),
985
- initialClick: [0, 0],
986
- initialHits: [],
987
- capturedMap: /* @__PURE__ */ new Map(),
988
- // Visibility tracking (onFramed, onOccluded, onVisible)
989
- visibilityRegistry: /* @__PURE__ */ new Map(),
990
- // Occlusion system (WebGPU only)
991
- occlusionEnabled: false,
992
- occlusionObserver: null,
993
- occlusionCache: /* @__PURE__ */ new Map(),
994
- helperGroup: null,
995
- // Updates
996
- active: false,
997
- frames: 0,
998
- priority: 0,
999
- subscribe: (ref, priority, store) => {
1000
- const internal = get().internal;
1001
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1002
- internal.subscribers.push({ ref, priority, store });
1003
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1004
- return () => {
1005
- const internal2 = get().internal;
1006
- if (internal2?.subscribers) {
1007
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1008
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1009
- }
1010
- };
1011
- },
1012
- // Renderer Storage (single source of truth)
1013
- actualRenderer: null,
1014
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1015
- scheduler: null
1016
- }
1017
- };
1018
- return rootState;
1019
- });
1020
- const state = rootStore.getState();
1021
- Object.defineProperty(state, "gl", {
1022
- get() {
1023
- const currentState = rootStore.getState();
1024
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1025
- const stack = new Error().stack || "";
1026
- if (!isInternalRendererAccess(stack)) {
1027
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1028
- notifyDepreciated({
1029
- heading: "Accessing state.gl in WebGPU mode",
1030
- 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
1031
- });
1032
- }
1033
- }
1034
- return currentState.internal.actualRenderer;
1035
- },
1036
- set(value) {
1037
- rootStore.getState().internal.actualRenderer = value;
1038
- },
1039
- enumerable: true,
1040
- configurable: true
1041
- });
1042
- Object.defineProperty(state, "renderer", {
1043
- get() {
1044
- return rootStore.getState().internal.actualRenderer;
1045
- },
1046
- set(value) {
1047
- rootStore.getState().internal.actualRenderer = value;
1048
- },
1049
- enumerable: true,
1050
- configurable: true
1051
- });
1052
- let oldScene = state.scene;
1053
- rootStore.subscribe(() => {
1054
- const currentState = rootStore.getState();
1055
- const { scene, rootScene, set } = currentState;
1056
- if (scene !== oldScene) {
1057
- oldScene = scene;
1058
- if (scene?.isScene && scene !== rootScene) {
1059
- set({ rootScene: scene });
1060
- }
1061
- }
1062
- });
1063
- let oldSize = state.size;
1064
- let oldDpr = state.viewport.dpr;
1065
- let oldCamera = state.camera;
1066
- rootStore.subscribe(() => {
1067
- const { camera, size, viewport, set, internal } = rootStore.getState();
1068
- const actualRenderer = internal.actualRenderer;
1069
- const canvasTarget = internal.canvasTarget;
1070
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1071
- oldSize = size;
1072
- oldDpr = viewport.dpr;
1073
- updateCamera(camera, size);
1074
- if (internal.isSecondary && canvasTarget) {
1075
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1076
- canvasTarget.setSize(size.width, size.height, false);
1077
- } else {
1078
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1079
- actualRenderer.setSize(size.width, size.height, false);
1080
- if (canvasTarget) {
1081
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1082
- canvasTarget.setSize(size.width, size.height, false);
1083
- }
1084
- }
1085
- }
1086
- if (camera !== oldCamera) {
1087
- oldCamera = camera;
1088
- const { rootScene } = rootStore.getState();
1089
- if (camera && rootScene && !camera.parent) {
1090
- rootScene.add(camera);
1091
- }
1092
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1093
- const currentState = rootStore.getState();
1094
- if (currentState.autoUpdateFrustum && camera) {
1095
- updateFrustum(camera, currentState.frustum);
794
+ }
795
+ }
796
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
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);
1096
815
  }
816
+ } else {
817
+ unregisterVisibility(root, object2);
1097
818
  }
1098
- });
1099
- rootStore.subscribe((state2) => invalidate(state2));
1100
- return rootStore;
1101
- };
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
+ }
1102
839
 
1103
840
  const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1104
841
  const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
@@ -1143,7 +880,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1143
880
  const results = cacheKeys.map(
1144
881
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1145
882
  );
1146
- 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];
1147
888
  }
1148
889
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1149
890
  const inputs = Array.isArray(input) ? input : [input];
@@ -1158,7 +899,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1158
899
  };
1159
900
  useLoader.loader = getLoader$1;
1160
901
 
1161
- function useFrame(callback, priorityOrOptions) {
902
+ function useFrame$1(callback, priorityOrOptions) {
1162
903
  const store = React__namespace.useContext(context);
1163
904
  const isInsideCanvas = store !== null;
1164
905
  const scheduler$1 = scheduler.getScheduler();
@@ -1263,6 +1004,13 @@ function useFrame(callback, priorityOrOptions) {
1263
1004
  * Use for controlling the entire frame loop, adding phases, etc.
1264
1005
  */
1265
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
+ },
1266
1014
  /**
1267
1015
  * Manually step this job only.
1268
1016
  * Bypasses FPS limiting - always runs.
@@ -1279,6 +1027,14 @@ function useFrame(callback, priorityOrOptions) {
1279
1027
  stepAll: (timestamp) => {
1280
1028
  scheduler.getScheduler().step(timestamp);
1281
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
+ },
1282
1038
  /**
1283
1039
  * Pause this job (set enabled=false).
1284
1040
  * Job remains registered but won't run.
@@ -1308,6 +1064,11 @@ function getUrls(input) {
1308
1064
  if (Array.isArray(input)) return input;
1309
1065
  return Object.values(input);
1310
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
+ }
1311
1072
  function allUrlsCached(urls, textureCache) {
1312
1073
  return urls.every((url) => textureCache.has(url));
1313
1074
  }
@@ -1326,31 +1087,44 @@ function buildFromCache(input, textureCache) {
1326
1087
  }
1327
1088
  }
1328
1089
  function useTexture(input, optionsOrOnLoad) {
1329
- const renderer = useThree((state) => state.internal.actualRenderer);
1090
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1330
1091
  const store = useStore();
1331
1092
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1332
1093
  const { onLoad, cache = true } = options;
1333
1094
  const onLoadRef = React.useRef(onLoad);
1334
1095
  onLoadRef.current = onLoad;
1335
1096
  const onLoadCalledForRef = React.useRef(null);
1336
- 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]);
1337
1100
  const cachedResult = React.useMemo(() => {
1338
1101
  if (!cache) return null;
1339
1102
  const textures = store.getState().textures;
1340
1103
  if (!allUrlsCached(urls, textures)) return null;
1341
- return buildFromCache(input, textures);
1342
- }, [cache, urls, input, store]);
1104
+ return buildFromCache(stableInput, textures);
1105
+ }, [cache, urls, stableInput, store]);
1343
1106
  const loadedTextures = useLoader(
1344
1107
  webgpu.TextureLoader,
1345
- IsObject(input) ? Object.values(input) : input
1108
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1346
1109
  );
1347
- 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]);
1348
1122
  React.useLayoutEffect(() => {
1349
1123
  if (cachedResult) return;
1350
1124
  if (onLoadCalledForRef.current === inputKey) return;
1351
1125
  onLoadCalledForRef.current = inputKey;
1352
- onLoadRef.current?.(loadedTextures);
1353
- }, [cachedResult, loadedTextures, inputKey]);
1126
+ onLoadRef.current?.(mappedTextures);
1127
+ }, [cachedResult, mappedTextures, inputKey]);
1354
1128
  React.useEffect(() => {
1355
1129
  if (cachedResult) return;
1356
1130
  if ("initTexture" in renderer) {
@@ -1369,30 +1143,18 @@ function useTexture(input, optionsOrOnLoad) {
1369
1143
  });
1370
1144
  }
1371
1145
  }, [renderer, loadedTextures, cachedResult]);
1372
- const mappedTextures = React.useMemo(() => {
1373
- if (cachedResult) return cachedResult;
1374
- if (IsObject(input)) {
1375
- const keyed = {};
1376
- const textureArray = loadedTextures;
1377
- let i = 0;
1378
- for (const key in input) keyed[key] = textureArray[i++];
1379
- return keyed;
1380
- } else {
1381
- return loadedTextures;
1382
- }
1383
- }, [input, loadedTextures, cachedResult]);
1384
1146
  React.useEffect(() => {
1385
1147
  if (!cache) return;
1386
1148
  const urlTextureMap = [];
1387
- if (typeof input === "string") {
1388
- urlTextureMap.push([input, mappedTextures]);
1389
- } else if (Array.isArray(input)) {
1149
+ if (typeof stableInput === "string") {
1150
+ urlTextureMap.push([stableInput, mappedTextures]);
1151
+ } else if (Array.isArray(stableInput)) {
1390
1152
  const textureArray = mappedTextures;
1391
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1392
- } else if (IsObject(input)) {
1153
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1154
+ } else if (IsObject(stableInput)) {
1393
1155
  const textureRecord = mappedTextures;
1394
- for (const key in input) {
1395
- const url = input[key];
1156
+ for (const key in stableInput) {
1157
+ const url = stableInput[key];
1396
1158
  urlTextureMap.push([url, textureRecord[key]]);
1397
1159
  }
1398
1160
  }
@@ -1421,7 +1183,7 @@ function useTexture(input, optionsOrOnLoad) {
1421
1183
  }
1422
1184
  return { _textureRefs: refs };
1423
1185
  });
1424
- }, [cache, input, mappedTextures, store]);
1186
+ }, [cache, stableInput, mappedTextures, store]);
1425
1187
  return mappedTextures;
1426
1188
  }
1427
1189
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -1492,13 +1254,13 @@ function useTextures(selector) {
1492
1254
  };
1493
1255
  }, [store]);
1494
1256
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1495
- const selected = useThree(subscribe);
1257
+ const selected = useThree$1(subscribe);
1496
1258
  return selector ? selected : registry;
1497
1259
  }
1498
1260
 
1499
1261
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1500
- const isLegacy = useThree((s) => s.isLegacy);
1501
- const size = useThree((s) => s.size);
1262
+ const isLegacy = useThree$1((s) => s.isLegacy);
1263
+ const size = useThree$1((s) => s.size);
1502
1264
  let width;
1503
1265
  let height;
1504
1266
  let opts;
@@ -1555,7 +1317,7 @@ function useEnvironment({
1555
1317
  const { extension, isCubemap } = getExtension(files);
1556
1318
  const loader = getLoader(extension);
1557
1319
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1558
- const renderer = useThree((state) => state.renderer);
1320
+ const renderer = useThree$1((state) => state.renderer);
1559
1321
  React.useLayoutEffect(() => {
1560
1322
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1561
1323
  function clearGainmapTexture() {
@@ -1582,7 +1344,7 @@ function useEnvironment({
1582
1344
  texture = texture.renderTarget?.texture;
1583
1345
  }
1584
1346
  texture.mapping = isCubemap ? webgpu.CubeReflectionMapping : webgpu.EquirectangularReflectionMapping;
1585
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1347
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1586
1348
  return texture;
1587
1349
  }
1588
1350
  const preloadDefaultOptions = {
@@ -1635,22 +1397,15 @@ function getExtension(files) {
1635
1397
  const isCubemap = isArray(files) && files.length === 6;
1636
1398
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1637
1399
  const firstEntry = isArray(files) ? files[0] : files;
1638
- 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;
1639
1402
  return { extension, isCubemap, isGainmap };
1640
1403
  }
1641
1404
  function getLoader(extension) {
1642
- 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;
1643
1406
  return loader;
1644
1407
  }
1645
1408
 
1646
- function useStore() {
1647
- const store = React.useContext(context);
1648
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1649
- return store;
1650
- }
1651
- function useThree(selector = (state) => state, equalityFn) {
1652
- return useStore()(selector, equalityFn);
1653
- }
1654
1409
  function useInstanceHandle(ref) {
1655
1410
  const instance = React.useRef(null);
1656
1411
  React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1690,22 +1445,28 @@ function addTail(callback) {
1690
1445
  return scheduler.getScheduler().onIdle(callback);
1691
1446
  }
1692
1447
  function invalidate(state, frames = 1, stackFrames = false) {
1693
- 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);
1694
1452
  }
1695
- function advance(timestamp) {
1696
- 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);
1697
1458
  }
1698
1459
 
1699
- const version = "10.0.0-alpha.3";
1460
+ const version = "10.0.0-alpha.5";
1700
1461
  const packageData = {
1701
1462
  version: version};
1702
1463
 
1703
1464
  function Xb(Tt) {
1704
1465
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
1705
1466
  }
1706
- var Rm = { exports: {} }, Og = { exports: {} }, _b;
1467
+ var Rm = { exports: {} }, Og = { exports: {} }, _b$1;
1707
1468
  function Kb() {
1708
- return _b || (_b = 1, (function(Tt) {
1469
+ return _b$1 || (_b$1 = 1, (function(Tt) {
1709
1470
  Tt.exports = function(m) {
1710
1471
  function Yn(t, r, a, l) {
1711
1472
  return new uc(t, r, a, l);
@@ -13555,7 +13316,10 @@ function extend(objects) {
13555
13316
  catalogue[Component] = objects;
13556
13317
  return Component;
13557
13318
  } else {
13558
- Object.assign(catalogue, objects);
13319
+ for (const name in objects) {
13320
+ const object = objects[name];
13321
+ if (isConstructor(object)) catalogue[name] = object;
13322
+ }
13559
13323
  }
13560
13324
  }
13561
13325
  function validateInstance(type, props) {
@@ -13903,7 +13667,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13903
13667
  getRootHostContext: () => NO_CONTEXT,
13904
13668
  getChildHostContext: () => NO_CONTEXT,
13905
13669
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13906
- validateInstance(type, newProps);
13670
+ validateInstance(instance.type, newProps);
13907
13671
  let reconstruct = false;
13908
13672
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13909
13673
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13930,8 +13694,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13930
13694
  for (const prop in instance.props) {
13931
13695
  const value = instance.props[prop];
13932
13696
  if (isFromRef(value)) {
13933
- const ref = value[FROM_REF];
13934
- 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;
13935
13699
  }
13936
13700
  }
13937
13701
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14075,6 +13839,335 @@ const reconciler = /* @__PURE__ */ createReconciler({
14075
13839
  getSuspendedCommitReason: (_state, _rootContainer) => null
14076
13840
  });
14077
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
+
14078
14171
  const isRenderer = (def) => !!def?.render;
14079
14172
  const _roots = /* @__PURE__ */ new Map();
14080
14173
  const shallowLoose = { objects: "shallow", strict: false };
@@ -14212,9 +14305,8 @@ function createRoot(canvas) {
14212
14305
  if (!renderer.hasInitialized?.()) {
14213
14306
  const size2 = computeInitialSize(canvas, propsSize);
14214
14307
  if (size2.width > 0 && size2.height > 0) {
14215
- const pixelRatio = calculateDpr(dpr);
14216
- canvas.width = size2.width * pixelRatio;
14217
- canvas.height = size2.height * pixelRatio;
14308
+ renderer.setPixelRatio(calculateDpr(dpr));
14309
+ renderer.setSize(size2.width, size2.height, false);
14218
14310
  }
14219
14311
  await renderer.init();
14220
14312
  }
@@ -14223,7 +14315,7 @@ function createRoot(canvas) {
14223
14315
  state.internal.actualRenderer = renderer;
14224
14316
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14225
14317
  if (canvasId && !state.internal.isSecondary) {
14226
- const canvasTarget = new webgpu.CanvasTarget(canvas);
14318
+ const canvasTarget = renderer.getCanvasTarget?.();
14227
14319
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14228
14320
  state.set((prev) => ({
14229
14321
  internal: {
@@ -14337,7 +14429,7 @@ function createRoot(canvas) {
14337
14429
  const handleXRFrame = (timestamp, _frame) => {
14338
14430
  const state2 = store.getState();
14339
14431
  if (state2.frameloop === "never") return;
14340
- advance(timestamp);
14432
+ advance(timestamp, true, state2);
14341
14433
  };
14342
14434
  const actualRenderer = state.internal.actualRenderer;
14343
14435
  const handleSessionChange = () => {
@@ -14345,7 +14437,7 @@ function createRoot(canvas) {
14345
14437
  const renderer2 = state2.internal.actualRenderer;
14346
14438
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14347
14439
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14348
- if (!renderer2.xr.isPresenting) invalidate();
14440
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14349
14441
  };
14350
14442
  const xr = {
14351
14443
  connect() {
@@ -14427,14 +14519,23 @@ function createRoot(canvas) {
14427
14519
  const newRootId = canvasId || scheduler$1.generateRootId();
14428
14520
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14429
14521
  getState: () => store.getState(),
14430
- 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
14431
14527
  });
14432
14528
  const unregisterCanvasTarget = scheduler$1.register(
14433
14529
  () => {
14434
14530
  const state2 = store.getState();
14435
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14531
+ const canvasTarget = state2.internal.canvasTarget;
14532
+ if (canvasTarget) {
14436
14533
  const renderer2 = state2.internal.actualRenderer;
14437
- 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
+ }
14438
14539
  }
14439
14540
  },
14440
14541
  {
@@ -14490,7 +14591,8 @@ function createRoot(canvas) {
14490
14591
  const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14491
14592
  if (userHandlesRender || state2.internal.priority) return;
14492
14593
  try {
14493
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14594
+ const renderOverride = state2.internal.renderOverride;
14595
+ if (renderOverride) renderOverride();
14494
14596
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14495
14597
  } catch (error) {
14496
14598
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14503,8 +14605,7 @@ function createRoot(canvas) {
14503
14605
  phase: "render",
14504
14606
  system: true,
14505
14607
  // Internal flag: this is a system job, not user-controlled
14506
- // Apply scheduler config for render ordering and rate limiting
14507
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14608
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14508
14609
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14509
14610
  }
14510
14611
  );
@@ -14523,8 +14624,22 @@ function createRoot(canvas) {
14523
14624
  scheduler: scheduler$1
14524
14625
  }
14525
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
+ }
14526
14638
  }
14527
- scheduler$1.frameloop = frameloop;
14639
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14640
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14641
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14642
+ attachRootExtensions(store);
14528
14643
  onCreated = onCreatedCallback;
14529
14644
  configured = true;
14530
14645
  resolve();
@@ -14566,18 +14681,24 @@ function unmountComponentAtNode(canvas, callback) {
14566
14681
  const fiber = root?.fiber;
14567
14682
  if (fiber) {
14568
14683
  const state = root?.store.getState();
14569
- 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
+ }
14570
14692
  reconciler.updateContainer(null, fiber, null, () => {
14693
+ if (root?.store) detachRootExtensions(root.store);
14571
14694
  if (state) {
14572
14695
  setTimeout(() => {
14573
14696
  try {
14574
14697
  const renderer = state.internal.actualRenderer;
14575
- const unregisterRoot = state.internal.unregisterRoot;
14576
- if (unregisterRoot) unregisterRoot();
14577
14698
  const unregisterPrimary = state.internal.unregisterPrimary;
14578
14699
  if (unregisterPrimary) unregisterPrimary();
14579
14700
  const canvasTarget = state.internal.canvasTarget;
14580
- if (canvasTarget?.dispose) canvasTarget.dispose();
14701
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14581
14702
  state.events.disconnect?.();
14582
14703
  cleanupHelperGroup(root.store);
14583
14704
  if (state.isLegacy && renderer) {
@@ -14718,16 +14839,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14718
14839
  const oldbg = target.background;
14719
14840
  const oldenv = target.environment;
14720
14841
  const oldSceneProps = {
14721
- // @ts-ignore
14722
14842
  backgroundBlurriness: target.backgroundBlurriness,
14723
- // @ts-ignore
14724
14843
  backgroundIntensity: target.backgroundIntensity,
14725
- // @ts-ignore
14726
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14727
- // @ts-ignore
14844
+ backgroundRotation: target.backgroundRotation.clone(),
14728
14845
  environmentIntensity: target.environmentIntensity,
14729
- // @ts-ignore
14730
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14846
+ environmentRotation: target.environmentRotation.clone()
14731
14847
  };
14732
14848
  if (background !== "only") target.environment = texture;
14733
14849
  if (background) target.background = texture;
@@ -14739,7 +14855,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14739
14855
  };
14740
14856
  }
14741
14857
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14742
- const defaultScene = useThree((state) => state.scene);
14858
+ const defaultScene = useThree$1((state) => state.scene);
14743
14859
  React__namespace.useLayoutEffect(() => {
14744
14860
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14745
14861
  });
@@ -14757,7 +14873,7 @@ function EnvironmentCube({
14757
14873
  ...rest
14758
14874
  }) {
14759
14875
  const texture = useEnvironment(rest);
14760
- const defaultScene = useThree((state) => state.scene);
14876
+ const defaultScene = useThree$1((state) => state.scene);
14761
14877
  React__namespace.useLayoutEffect(() => {
14762
14878
  return setEnvProps(background, scene, defaultScene, texture, {
14763
14879
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14794,9 +14910,9 @@ function EnvironmentPortal({
14794
14910
  preset = void 0,
14795
14911
  extensions
14796
14912
  }) {
14797
- const renderer = useThree((state) => state.renderer);
14798
- const isLegacy = useThree((state) => state.isLegacy);
14799
- const defaultScene = useThree((state) => state.scene);
14913
+ const renderer = useThree$1((state) => state.renderer);
14914
+ const isLegacy = useThree$1((state) => state.isLegacy);
14915
+ const defaultScene = useThree$1((state) => state.scene);
14800
14916
  const camera = React__namespace.useRef(null);
14801
14917
  const [virtualScene] = React__namespace.useState(() => new webgpu.Scene());
14802
14918
  const fbo = React__namespace.useMemo(() => {
@@ -14840,7 +14956,7 @@ function EnvironmentPortal({
14840
14956
  environmentRotation
14841
14957
  ]);
14842
14958
  let count = 1;
14843
- useFrame(() => {
14959
+ useFrame$1(() => {
14844
14960
  if (frames === Infinity || count < frames) {
14845
14961
  const autoClear = renderer.autoClear;
14846
14962
  renderer.autoClear = true;
@@ -14880,7 +14996,7 @@ function EnvironmentGround(props) {
14880
14996
  ] });
14881
14997
  }
14882
14998
  function EnvironmentColor({ color, scene }) {
14883
- const defaultScene = useThree((state) => state.scene);
14999
+ const defaultScene = useThree$1((state) => state.scene);
14884
15000
  React__namespace.useLayoutEffect(() => {
14885
15001
  if (color === void 0) return;
14886
15002
  const target = resolveScene(scene || defaultScene);
@@ -14929,156 +15045,27 @@ function parseBackground(background) {
14929
15045
  return { preset: background, background: true };
14930
15046
  }
14931
15047
  if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
14932
- return { files: background, background: true };
14933
- }
14934
- return { color: background, background: true };
14935
- }
14936
- if (background.isColor) {
14937
- return { color: background, background: true };
14938
- }
14939
- return null;
14940
- }
14941
-
14942
- function parseRendererConfig(rendererProp) {
14943
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14944
- if (isRendererConfig) {
14945
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14946
- return {
14947
- primaryCanvas: pc,
14948
- scheduler: sc,
14949
- renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14950
- };
14951
- }
14952
- return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14953
- }
14954
-
14955
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14956
- const ROOT_SCOPE = "";
14957
- const registries = /* @__PURE__ */ new WeakMap();
14958
- function getKindRegistry(store, kind) {
14959
- let registry = registries.get(store);
14960
- if (!registry) {
14961
- registry = {
14962
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14963
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14964
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14965
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14966
- };
14967
- registries.set(store, registry);
14968
- }
14969
- return registry[kind];
14970
- }
14971
- function resolvePrimary$1(store) {
14972
- return store.getState().primaryStore ?? store;
14973
- }
14974
- function isScopeValid(store, kind, scopeKey) {
14975
- return getKindRegistry(store, kind).valid.has(scopeKey);
14976
- }
14977
- function peekStaged(store, kind, scopeKey) {
14978
- return getKindRegistry(store, kind).staged.get(scopeKey);
14979
- }
14980
- function stageEntries(store, kind, scopeKey, entries) {
14981
- const { staged } = getKindRegistry(store, kind);
14982
- const current = staged.get(scopeKey);
14983
- if (current) Object.assign(current, entries);
14984
- else staged.set(scopeKey, { ...entries });
14985
- }
14986
- function readCommittedScope(map, scopeKey, isLeaf) {
14987
- if (scopeKey === ROOT_SCOPE) {
14988
- const leaves = {};
14989
- for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14990
- return leaves;
14991
- }
14992
- const scopeData = map[scopeKey];
14993
- if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14994
- return {};
14995
- }
14996
- function withStagedOverlay(store, kind, committed) {
14997
- const { staged } = getKindRegistry(store, kind);
14998
- if (staged.size === 0) return committed;
14999
- const view = { ...committed };
15000
- for (const [scopeKey, entries] of staged) {
15001
- if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15002
- else view[scopeKey] = { ...view[scopeKey], ...entries };
15003
- }
15004
- return view;
15005
- }
15006
- function flushStagedScope(store, kind, scopeKey, isLeaf) {
15007
- const registry = getKindRegistry(store, kind);
15008
- const staged = registry.staged.get(scopeKey);
15009
- if (!staged) return;
15010
- registry.staged.delete(scopeKey);
15011
- const replace = !registry.valid.has(scopeKey);
15012
- store.setState((state) => {
15013
- const map = state[kind];
15014
- if (scopeKey === ROOT_SCOPE) {
15015
- if (!replace) return { [kind]: { ...map, ...staged } };
15016
- const next = {};
15017
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15018
- return { [kind]: { ...next, ...staged } };
15019
- }
15020
- const current = replace ? {} : map[scopeKey] ?? {};
15021
- return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15022
- });
15023
- registry.valid.add(scopeKey);
15024
- }
15025
- function invalidateResource(store, kind, scopeKey) {
15026
- const registry = getKindRegistry(store, kind);
15027
- if (scopeKey === void 0) {
15028
- registry.valid.clear();
15029
- registry.staged.clear();
15030
- } else {
15031
- registry.valid.delete(scopeKey);
15032
- registry.staged.delete(scopeKey);
15033
- }
15034
- }
15035
- function rebuildResource(store, kind, userScope) {
15036
- const primary = resolvePrimary$1(store);
15037
- invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15038
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15039
- }
15040
- function invalidateAllResources(store) {
15041
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15042
- }
15043
- function removeResourceEntries(store, kind, names, scope, isLeaf) {
15044
- const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15045
- if (staged) for (const name of names) delete staged[name];
15046
- store.setState((state) => {
15047
- const map = state[kind];
15048
- if (scope) {
15049
- const currentScope = { ...map[scope] };
15050
- for (const name of names) delete currentScope[name];
15051
- return { [kind]: { ...map, [scope]: currentScope } };
15052
- }
15053
- const next = { ...map };
15054
- for (const name of names) if (isLeaf(next[name])) delete next[name];
15055
- return { [kind]: next };
15056
- });
15057
- }
15058
- function clearResourceEntries(store, kind, scope, isLeaf) {
15059
- const registry = getKindRegistry(store, kind);
15060
- if (scope && scope !== "root") registry.staged.delete(scope);
15061
- else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15062
- else registry.staged.clear();
15063
- store.setState((state) => {
15064
- const map = state[kind];
15065
- if (scope && scope !== "root") {
15066
- const { [scope]: _, ...rest } = map;
15067
- return { [kind]: rest };
15068
- }
15069
- if (scope === "root") {
15070
- const next = {};
15071
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15072
- return { [kind]: next };
15048
+ return { files: background, background: true };
15073
15049
  }
15074
- return { [kind]: {} };
15075
- });
15050
+ return { color: background, background: true };
15051
+ }
15052
+ if (background.isColor) {
15053
+ return { color: background, background: true };
15054
+ }
15055
+ return null;
15076
15056
  }
15077
15057
 
15078
- function clearHmrCaches(store) {
15079
- const primary = store.getState().primaryStore ?? store;
15080
- invalidateAllResources(primary);
15081
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15058
+ function parseRendererConfig(rendererProp) {
15059
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15060
+ if (isRendererConfig) {
15061
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15062
+ return {
15063
+ primaryCanvas: pc,
15064
+ scheduler: sc,
15065
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
15066
+ };
15067
+ }
15068
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
15082
15069
  }
15083
15070
 
15084
15071
  function CanvasImpl({
@@ -15118,9 +15105,9 @@ function CanvasImpl({
15118
15105
  React__namespace.useMemo(() => extend(THREE), []);
15119
15106
  const Bridge = useBridge();
15120
15107
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
15121
- const hasInitialSizeRef = React__namespace.useRef(false);
15108
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
15122
15109
  const measureConfig = React__namespace.useMemo(() => {
15123
- if (!hasInitialSizeRef.current) {
15110
+ if (!hasInitialSize) {
15124
15111
  return {
15125
15112
  ...resize,
15126
15113
  scroll: resize?.scroll ?? true,
@@ -15132,7 +15119,7 @@ function CanvasImpl({
15132
15119
  debounce: { scroll: 50, resize: 0 },
15133
15120
  ...resize
15134
15121
  };
15135
- }, [resize, hasInitialSizeRef.current]);
15122
+ }, [hasInitialSize, resize]);
15136
15123
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15137
15124
  const effectiveSize = React__namespace.useMemo(() => {
15138
15125
  let w = width ?? containerRect.width;
@@ -15148,9 +15135,7 @@ function CanvasImpl({
15148
15135
  left: containerRect.left
15149
15136
  };
15150
15137
  }, [width, height, containerRect, forceEven]);
15151
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15152
- hasInitialSizeRef.current = true;
15153
- }
15138
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15154
15139
  const canvasRef = React__namespace.useRef(null);
15155
15140
  const divRef = React__namespace.useRef(null);
15156
15141
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15264,6 +15249,7 @@ function CanvasImpl({
15264
15249
  const canvas = canvasRef.current;
15265
15250
  if (canvas) {
15266
15251
  return () => {
15252
+ if (canvas.isConnected) return;
15267
15253
  unmountComponentAtNode(canvas);
15268
15254
  root.current = null;
15269
15255
  };
@@ -15276,7 +15262,7 @@ function CanvasImpl({
15276
15262
  const handleHMR = () => {
15277
15263
  queueMicrotask(() => {
15278
15264
  const rootEntry = _roots.get(canvas);
15279
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15265
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15280
15266
  });
15281
15267
  };
15282
15268
  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) {
@@ -15322,20 +15308,159 @@ function CanvasImpl({
15322
15308
  }
15323
15309
  );
15324
15310
  }
15325
- function Canvas(props) {
15311
+ function Canvas$1(props) {
15326
15312
  return /* @__PURE__ */ jsxRuntime.jsx(itsFine.FiberProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(CanvasImpl, { ...props }) });
15327
15313
  }
15328
15314
 
15315
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
15316
+ const ROOT_SCOPE = "";
15317
+ const registries = /* @__PURE__ */ new WeakMap();
15318
+ function getKindRegistry(store, kind) {
15319
+ let registry = registries.get(store);
15320
+ if (!registry) {
15321
+ registry = {
15322
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15323
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15324
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15325
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
15326
+ };
15327
+ registries.set(store, registry);
15328
+ }
15329
+ return registry[kind];
15330
+ }
15331
+ function resolvePrimary$1(store) {
15332
+ return store.getState().primaryStore ?? store;
15333
+ }
15334
+ function isScopeValid(store, kind, scopeKey) {
15335
+ return getKindRegistry(store, kind).valid.has(scopeKey);
15336
+ }
15337
+ function peekStaged(store, kind, scopeKey) {
15338
+ return getKindRegistry(store, kind).staged.get(scopeKey);
15339
+ }
15340
+ function stageEntries(store, kind, scopeKey, entries) {
15341
+ const { staged } = getKindRegistry(store, kind);
15342
+ const current = staged.get(scopeKey);
15343
+ if (current) Object.assign(current, entries);
15344
+ else staged.set(scopeKey, { ...entries });
15345
+ }
15346
+ function readCommittedScope(map, scopeKey, isLeaf) {
15347
+ if (scopeKey === ROOT_SCOPE) {
15348
+ const leaves = {};
15349
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
15350
+ return leaves;
15351
+ }
15352
+ const scopeData = map[scopeKey];
15353
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
15354
+ return {};
15355
+ }
15356
+ function withStagedOverlay(store, kind, committed) {
15357
+ const { staged } = getKindRegistry(store, kind);
15358
+ if (staged.size === 0) return committed;
15359
+ const view = { ...committed };
15360
+ for (const [scopeKey, entries] of staged) {
15361
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15362
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
15363
+ }
15364
+ return view;
15365
+ }
15366
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
15367
+ const registry = getKindRegistry(store, kind);
15368
+ const staged = registry.staged.get(scopeKey);
15369
+ if (!staged) return;
15370
+ registry.staged.delete(scopeKey);
15371
+ const replace = !registry.valid.has(scopeKey);
15372
+ store.setState((state) => {
15373
+ const map = state[kind];
15374
+ if (scopeKey === ROOT_SCOPE) {
15375
+ if (!replace) return { [kind]: { ...map, ...staged } };
15376
+ const next = {};
15377
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15378
+ return { [kind]: { ...next, ...staged } };
15379
+ }
15380
+ const current = replace ? {} : map[scopeKey] ?? {};
15381
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15382
+ });
15383
+ registry.valid.add(scopeKey);
15384
+ }
15385
+ function invalidateResource(store, kind, scopeKey) {
15386
+ const registry = getKindRegistry(store, kind);
15387
+ if (scopeKey === void 0) {
15388
+ registry.valid.clear();
15389
+ registry.staged.clear();
15390
+ } else {
15391
+ registry.valid.delete(scopeKey);
15392
+ registry.staged.delete(scopeKey);
15393
+ }
15394
+ }
15395
+ function rebuildResource(store, kind, userScope) {
15396
+ const primary = resolvePrimary$1(store);
15397
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15398
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15399
+ }
15400
+ function invalidateAllResources(store) {
15401
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15402
+ }
15403
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
15404
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15405
+ if (staged) for (const name of names) delete staged[name];
15406
+ store.setState((state) => {
15407
+ const map = state[kind];
15408
+ if (scope) {
15409
+ const currentScope = { ...map[scope] };
15410
+ for (const name of names) delete currentScope[name];
15411
+ return { [kind]: { ...map, [scope]: currentScope } };
15412
+ }
15413
+ const next = { ...map };
15414
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
15415
+ return { [kind]: next };
15416
+ });
15417
+ }
15418
+ function clearResourceEntries(store, kind, scope, isLeaf) {
15419
+ const registry = getKindRegistry(store, kind);
15420
+ if (scope && scope !== "root") registry.staged.delete(scope);
15421
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15422
+ else registry.staged.clear();
15423
+ store.setState((state) => {
15424
+ const map = state[kind];
15425
+ if (scope && scope !== "root") {
15426
+ const { [scope]: _, ...rest } = map;
15427
+ return { [kind]: rest };
15428
+ }
15429
+ if (scope === "root") {
15430
+ const next = {};
15431
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15432
+ return { [kind]: next };
15433
+ }
15434
+ return { [kind]: {} };
15435
+ });
15436
+ }
15437
+
15438
+ const isObjectLike = (value) => value !== null && (typeof value === "object" || typeof value === "function");
15439
+ const hasValidLegacyNodeMarkers = (value) => {
15440
+ const hasUuid = "uuid" in value;
15441
+ const hasNodeType = "nodeType" in value;
15442
+ const uuid = Reflect.get(value, "uuid");
15443
+ const nodeType = Reflect.get(value, "nodeType");
15444
+ return (hasUuid || hasNodeType) && (!hasUuid || uuid === void 0 || typeof uuid === "string") && (!hasNodeType || nodeType === void 0 || nodeType === null || typeof nodeType === "string");
15445
+ };
15446
+ const isUniformNode = (value) => isObjectLike(value) && "isUniformNode" in value && value.isUniformNode === true;
15447
+ const isTSLNode = (value) => isObjectLike(value) && (Reflect.get(value, "isNode") === true || Reflect.get(value, "shaderNode") !== void 0 && typeof Reflect.get(value, "id") === "number" || hasValidLegacyNodeMarkers(value));
15448
+ const isBufferLike = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView) || isObjectLike(value) && ("isBufferAttribute" in value || "isInterleavedBufferAttribute" in value || isTSLNode(value));
15449
+ const isStorageLike = (value) => isObjectLike(value) && ("isTexture" in value || "isData3DTexture" in value || isTSLNode(value));
15450
+
15329
15451
  var __defProp = Object.defineProperty;
15330
15452
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15331
15453
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15332
- var _a;
15454
+ var _a, _b;
15333
15455
  const INTERNAL_DATA = Symbol("ScopedStore.data");
15334
- _a = INTERNAL_DATA;
15456
+ const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
15457
+ _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
15335
15458
  const _ScopedStore = class _ScopedStore {
15336
- constructor(data) {
15459
+ constructor(data, isLeaf) {
15460
+ __publicField(this, _b);
15337
15461
  __publicField(this, _a);
15338
15462
  this[INTERNAL_DATA] = data;
15463
+ this[INTERNAL_IS_LEAF] = isLeaf;
15339
15464
  return new Proxy(this, {
15340
15465
  get(target, prop, receiver) {
15341
15466
  if (typeof prop === "string") {
@@ -15369,10 +15494,10 @@ const _ScopedStore = class _ScopedStore {
15369
15494
  * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15370
15495
  */
15371
15496
  scope(key) {
15372
- const data = this[INTERNAL_DATA][key];
15373
- return new _ScopedStore(
15374
- data && typeof data === "object" ? data : {}
15375
- );
15497
+ const value = this[INTERNAL_DATA][key];
15498
+ const isLeaf = this[INTERNAL_IS_LEAF];
15499
+ const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
15500
+ return new _ScopedStore(scope, isLeaf);
15376
15501
  }
15377
15502
  /**
15378
15503
  * Check if a key exists in the store.
@@ -15388,8 +15513,8 @@ const _ScopedStore = class _ScopedStore {
15388
15513
  }
15389
15514
  };
15390
15515
  let ScopedStore = _ScopedStore;
15391
- function createScopedStore(data) {
15392
- return new ScopedStore(data);
15516
+ function createScopedStore(data, isLeaf) {
15517
+ return new ScopedStore(data, isLeaf);
15393
15518
  }
15394
15519
  function createLazyCreatorState(state, store) {
15395
15520
  let _uniforms = null;
@@ -15400,22 +15525,22 @@ function createLazyCreatorState(state, store) {
15400
15525
  return Object.create(state, {
15401
15526
  uniforms: {
15402
15527
  get() {
15403
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15528
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
15404
15529
  }
15405
15530
  },
15406
15531
  nodes: {
15407
15532
  get() {
15408
- return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15533
+ return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
15409
15534
  }
15410
15535
  },
15411
15536
  buffers: {
15412
15537
  get() {
15413
- return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15538
+ return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
15414
15539
  }
15415
15540
  },
15416
15541
  gpuStorage: {
15417
15542
  get() {
15418
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15543
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
15419
15544
  }
15420
15545
  }
15421
15546
  });
@@ -15435,44 +15560,10 @@ function usePrimaryThree(selector = (state) => state, equalityFn) {
15435
15560
  return primary(selector, equalityFn);
15436
15561
  }
15437
15562
 
15438
- function addTexture(set, key, value) {
15439
- set((state) => {
15440
- const newMap = new Map(state.textures);
15441
- newMap.set(key, value);
15442
- return { textures: newMap };
15443
- });
15444
- }
15445
- function addTextures(set, items) {
15446
- set((state) => {
15447
- const newMap = new Map(state.textures);
15448
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
15449
- for (const [key, value] of entries) {
15450
- newMap.set(key, value);
15451
- }
15452
- return { textures: newMap };
15453
- });
15454
- }
15455
- function removeTexture(set, key) {
15456
- set((state) => {
15457
- const newMap = new Map(state.textures);
15458
- newMap.delete(key);
15459
- return { textures: newMap };
15460
- });
15461
- }
15462
- function removeTextures(set, keys) {
15463
- set((state) => {
15464
- const newMap = new Map(state.textures);
15465
- for (const key of keys) newMap.delete(key);
15466
- return { textures: newMap };
15467
- });
15468
- }
15469
- function createTextureOperations(set) {
15470
- return {
15471
- add: (key, value) => addTexture(set, key, value),
15472
- addMultiple: (items) => addTextures(set, items),
15473
- remove: (key) => removeTexture(set, key),
15474
- removeMultiple: (keys) => removeTextures(set, keys)
15475
- };
15563
+ function scopedNodeName(scope, name) {
15564
+ const raw = scope ? `${scope}_${name}` : name;
15565
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15566
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15476
15567
  }
15477
15568
  function extractTSLValue(value) {
15478
15569
  if (value === null || value === void 0) return value;
@@ -15511,7 +15602,7 @@ function vectorize(inObject) {
15511
15602
  return extractTSLValue(inObject);
15512
15603
  }
15513
15604
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15514
- if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15605
+ if (obj.isMatrix2 || obj.isMatrix3 || obj.isMatrix4) return inObject;
15515
15606
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15516
15607
  if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15517
15608
  const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
@@ -15538,8 +15629,26 @@ function useCompareMemoize(value, deep) {
15538
15629
  return ref.current;
15539
15630
  }
15540
15631
 
15632
+ function clearHmrCaches(store) {
15633
+ const primary = store.getState().primaryStore ?? store;
15634
+ invalidateAllResources(primary);
15635
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15636
+ }
15637
+
15638
+ let registered = false;
15639
+ function ensureTSLExtension() {
15640
+ if (registered) return;
15641
+ registered = true;
15642
+ registerRootExtension({
15643
+ name: "@react-three/tsl",
15644
+ // Resolves primaryStore itself, so every canvas sharing a renderer refreshes the same resources.
15645
+ hmr: clearHmrCaches
15646
+ });
15647
+ }
15648
+
15541
15649
  function useScopedResource(options) {
15542
15650
  const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15651
+ ensureTSLExtension();
15543
15652
  const scopeKey = scope ?? ROOT_SCOPE;
15544
15653
  const version = store((s) => s._hmrVersion);
15545
15654
  const entries = React.useMemo(() => {
@@ -15569,22 +15678,21 @@ function useScopedResource(options) {
15569
15678
  return entries;
15570
15679
  }
15571
15680
 
15572
- const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15573
15681
  function useUniforms(creatorOrScope, scope) {
15574
15682
  const store = usePrimaryStore();
15575
15683
  const removeUniforms2 = React.useCallback(
15576
- (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15684
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode),
15577
15685
  [store]
15578
15686
  );
15579
15687
  const clearUniforms = React.useCallback(
15580
- (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15688
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode),
15581
15689
  [store]
15582
15690
  );
15583
15691
  const rebuildUniforms = React.useCallback(
15584
15692
  (targetScope) => rebuildResource(store, "uniforms", targetScope),
15585
15693
  [store]
15586
15694
  );
15587
- const inputForMemoization = React.useMemo(() => {
15695
+ const processedInput = React.useMemo(() => {
15588
15696
  let raw = creatorOrScope;
15589
15697
  if (is.fun(creatorOrScope)) {
15590
15698
  const wrappedState = createLazyCreatorState(store.getState(), store);
@@ -15595,23 +15703,24 @@ function useUniforms(creatorOrScope, scope) {
15595
15703
  for (const [key, value] of Object.entries(raw)) {
15596
15704
  normalized[key] = vectorize(value);
15597
15705
  }
15598
- return normalized;
15706
+ return { candidates: raw, normalized };
15599
15707
  }
15600
- return raw;
15708
+ return { candidates: raw, normalized: raw };
15601
15709
  }, [creatorOrScope, store]);
15602
- const memoizedInput = useCompareMemoize(inputForMemoization);
15710
+ const memoizedInput = useCompareMemoize(processedInput.normalized);
15603
15711
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15604
15712
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15605
15713
  const created = useScopedResource({
15606
15714
  store,
15607
15715
  kind: "uniforms",
15608
15716
  scope: isReader ? void 0 : scope,
15609
- isLeaf: isUniformNode$1,
15717
+ isLeaf: isUniformNode,
15610
15718
  input: memoizedInput,
15611
15719
  create: () => {
15612
15720
  if (isReader) return {};
15613
- if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15614
- return memoizedInput;
15721
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
15722
+ throw new Error("Invalid uniform input");
15723
+ return processedInput.candidates;
15615
15724
  },
15616
15725
  prepare: (name, candidate) => createUniform(name, candidate, scope),
15617
15726
  reconcile: (existing, candidate) => {
@@ -15633,7 +15742,7 @@ function useUniforms(creatorOrScope, scope) {
15633
15742
  uniforms = storeUniforms;
15634
15743
  } else if (typeof memoizedInput === "string") {
15635
15744
  const scopeData = storeUniforms[memoizedInput];
15636
- uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15745
+ uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
15637
15746
  }
15638
15747
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15639
15748
  }
@@ -15648,7 +15757,7 @@ function removeUniforms(set, names, scope) {
15648
15757
  return { uniforms: { ...state.uniforms, [scope]: currentScope } };
15649
15758
  }
15650
15759
  const uniforms = { ...state.uniforms };
15651
- for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
15760
+ for (const name of names) if (isUniformNode(uniforms[name])) delete uniforms[name];
15652
15761
  return { uniforms };
15653
15762
  });
15654
15763
  }
@@ -15662,7 +15771,7 @@ function clearRootUniforms(set) {
15662
15771
  set((state) => {
15663
15772
  const uniforms = {};
15664
15773
  for (const [key, value] of Object.entries(state.uniforms)) {
15665
- if (!isUniformNode$1(value)) uniforms[key] = value;
15774
+ if (!isUniformNode(value)) uniforms[key] = value;
15666
15775
  }
15667
15776
  return { uniforms };
15668
15777
  });
@@ -15672,8 +15781,7 @@ function createUniform(inName, node, scope) {
15672
15781
  const inValue = vectorize(node);
15673
15782
  const newUniform = tsl.uniform(inValue);
15674
15783
  if (typeof newUniform.setName === "function") {
15675
- const name = scope ? `${scope}_${inName}` : inName;
15676
- newUniform.setName(name);
15784
+ newUniform.setName(scopedNodeName(scope, inName));
15677
15785
  }
15678
15786
  return newUniform;
15679
15787
  }
@@ -15699,12 +15807,10 @@ function hasEqualsMethod(value) {
15699
15807
  function isSameThreeType(a, b) {
15700
15808
  const obj_a = a;
15701
15809
  const obj_b = b;
15702
- 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;
15810
+ 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;
15703
15811
  }
15704
15812
 
15705
15813
  const uniformOf = tsl.uniform;
15706
- const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15707
- const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15708
15814
  function useUniform(name, value) {
15709
15815
  const store = useStore();
15710
15816
  const registered = useScopedResource({
@@ -15716,7 +15822,7 @@ function useUniform(name, value) {
15716
15822
  create: () => value === void 0 ? {} : { [name]: value },
15717
15823
  prepare: createNamedUniform,
15718
15824
  reconcile: (existing2) => {
15719
- if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15825
+ if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
15720
15826
  existing2.value = typeof value === "string" ? new webgpu.Color(value) : value;
15721
15827
  }
15722
15828
  }
@@ -15732,7 +15838,7 @@ function createNamedUniform(name, value) {
15732
15838
  let node;
15733
15839
  if (isUniformNode(value)) {
15734
15840
  node = value;
15735
- } else if (isTSLNode$1(value)) {
15841
+ } else if (isTSLNode(value)) {
15736
15842
  node = uniformOf(value);
15737
15843
  } else if (typeof value === "string") {
15738
15844
  node = tsl.uniform(new webgpu.Color(value));
@@ -15745,7 +15851,6 @@ function createNamedUniform(name, value) {
15745
15851
  return node;
15746
15852
  }
15747
15853
 
15748
- const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15749
15854
  function useNodes(creatorOrScope, scope) {
15750
15855
  const store = usePrimaryStore();
15751
15856
  const removeNodes2 = React.useCallback(
@@ -15772,7 +15877,8 @@ function useNodes(creatorOrScope, scope) {
15772
15877
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15773
15878
  },
15774
15879
  prepare: (name, node) => {
15775
- node.setName?.(scope ? `${scope}.${name}` : name);
15880
+ const setName = Reflect.get(node, "setName");
15881
+ if (typeof setName === "function") setName.call(node, scopedNodeName(scope, name));
15776
15882
  return node;
15777
15883
  }
15778
15884
  });
@@ -15827,13 +15933,6 @@ function useLocalNodes(creator) {
15827
15933
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15828
15934
  }
15829
15935
 
15830
- const isBufferLike = (value) => {
15831
- if (value === null || typeof value !== "object") return false;
15832
- if (ArrayBuffer.isView(value)) return true;
15833
- if ("isBufferAttribute" in value) return true;
15834
- if ("uuid" in value || "nodeType" in value) return true;
15835
- return false;
15836
- };
15837
15936
  const disposeBuffer = (buffer) => {
15838
15937
  if (buffer === null || typeof buffer !== "object") return;
15839
15938
  if ("dispose" in buffer && typeof buffer.dispose === "function") {
@@ -15880,9 +15979,8 @@ function useBuffers(creatorOrScope, scope) {
15880
15979
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15881
15980
  },
15882
15981
  prepare: (name, buffer) => {
15883
- if ("setName" in buffer && typeof buffer.setName === "function") {
15884
- buffer.setName(scope ? `${scope}.${name}` : name);
15885
- }
15982
+ const setName = Reflect.get(buffer, "setName");
15983
+ if (typeof setName === "function") setName.call(buffer, scopedNodeName(scope, name));
15886
15984
  return buffer;
15887
15985
  }
15888
15986
  });
@@ -15899,13 +15997,6 @@ function rebuildAllBuffers(store, scope) {
15899
15997
  rebuildResource(store, "buffers", scope);
15900
15998
  }
15901
15999
 
15902
- const isStorageLike = (value) => {
15903
- if (value === null || typeof value !== "object") return false;
15904
- if ("isTexture" in value) return true;
15905
- if ("isData3DTexture" in value) return true;
15906
- if ("uuid" in value || "nodeType" in value) return true;
15907
- return false;
15908
- };
15909
16000
  const disposeStorage = (storage) => {
15910
16001
  if (storage === null || typeof storage !== "object") return;
15911
16002
  if ("dispose" in storage && typeof storage.dispose === "function") {
@@ -15952,10 +16043,9 @@ function useGPUStorage(creatorOrScope, scope) {
15952
16043
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15953
16044
  },
15954
16045
  prepare: (name, storage) => {
15955
- const label = scope ? `${scope}.${name}` : name;
15956
- if ("setName" in storage && typeof storage.setName === "function") {
15957
- storage.setName(label);
15958
- }
16046
+ const label = scopedNodeName(scope, name);
16047
+ const setName = Reflect.get(storage, "setName");
16048
+ if (typeof setName === "function") setName.call(storage, label);
15959
16049
  if ("name" in storage && typeof storage.name === "string") {
15960
16050
  storage.name = label;
15961
16051
  }
@@ -15981,11 +16071,58 @@ function rebuildAllStorage(store, scope) {
15981
16071
  rebuildResource(store, "gpuStorage", scope);
15982
16072
  }
15983
16073
 
16074
+ function addTexture(set, key, value) {
16075
+ set((state) => {
16076
+ const newMap = new Map(state.textures);
16077
+ newMap.set(key, value);
16078
+ return { textures: newMap };
16079
+ });
16080
+ }
16081
+ function addTextures(set, items) {
16082
+ set((state) => {
16083
+ const newMap = new Map(state.textures);
16084
+ const entries = items instanceof Map ? items.entries() : Object.entries(items);
16085
+ for (const [key, value] of entries) {
16086
+ newMap.set(key, value);
16087
+ }
16088
+ return { textures: newMap };
16089
+ });
16090
+ }
16091
+ function removeTexture(set, key) {
16092
+ set((state) => {
16093
+ const newMap = new Map(state.textures);
16094
+ newMap.delete(key);
16095
+ return { textures: newMap };
16096
+ });
16097
+ }
16098
+ function removeTextures(set, keys) {
16099
+ set((state) => {
16100
+ const newMap = new Map(state.textures);
16101
+ for (const key of keys) newMap.delete(key);
16102
+ return { textures: newMap };
16103
+ });
16104
+ }
16105
+ function createTextureOperations(set) {
16106
+ return {
16107
+ add: (key, value) => addTexture(set, key, value),
16108
+ addMultiple: (items) => addTextures(set, items),
16109
+ remove: (key) => removeTexture(set, key),
16110
+ removeMultiple: (keys) => removeTextures(set, keys)
16111
+ };
16112
+ }
16113
+
16114
+ function renderThroughPipeline(store) {
16115
+ return () => {
16116
+ const pipeline = store.getState().renderPipeline;
16117
+ if (pipeline?.render) pipeline.render();
16118
+ };
16119
+ }
15984
16120
  function useRenderPipeline(mainCB, setupCB) {
15985
16121
  const store = useStore();
15986
- const { scene, camera, renderer, isLegacy } = useThree();
16122
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15987
16123
  const callbacksRanRef = React.useRef(false);
15988
16124
  const scenePassCacheRef = React.useRef(null);
16125
+ const forceScenePassRebuildRef = React.useRef(false);
15989
16126
  const mainCBRef = React.useRef(mainCB);
15990
16127
  const setupCBRef = React.useRef(setupCB);
15991
16128
  mainCBRef.current = mainCB;
@@ -15993,7 +16130,7 @@ function useRenderPipeline(mainCB, setupCB) {
15993
16130
  const [rebuildVersion, setRebuildVersion] = React.useState(0);
15994
16131
  React.useEffect(() => {
15995
16132
  callbacksRanRef.current = false;
15996
- scenePassCacheRef.current = null;
16133
+ forceScenePassRebuildRef.current = true;
15997
16134
  }, []);
15998
16135
  const clearPasses = React.useCallback(() => {
15999
16136
  store.setState({ passes: {} });
@@ -16003,12 +16140,15 @@ function useRenderPipeline(mainCB, setupCB) {
16003
16140
  renderPipeline: null,
16004
16141
  passes: {}
16005
16142
  });
16143
+ setRenderOverride(store, null);
16144
+ scenePassCacheRef.current?.scenePass.dispose();
16006
16145
  callbacksRanRef.current = false;
16007
16146
  scenePassCacheRef.current = null;
16147
+ forceScenePassRebuildRef.current = false;
16008
16148
  }, [store]);
16009
16149
  const rebuild = React.useCallback(() => {
16010
16150
  callbacksRanRef.current = false;
16011
- scenePassCacheRef.current = null;
16151
+ forceScenePassRebuildRef.current = true;
16012
16152
  setRebuildVersion((v) => v + 1);
16013
16153
  }, []);
16014
16154
  React.useLayoutEffect(() => {
@@ -16016,6 +16156,9 @@ function useRenderPipeline(mainCB, setupCB) {
16016
16156
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16017
16157
  }
16018
16158
  if (!renderer || !scene || !camera) return;
16159
+ if (!("isWebGPURenderer" in renderer)) {
16160
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16161
+ }
16019
16162
  const state = store.getState();
16020
16163
  const set = store.setState;
16021
16164
  try {
@@ -16026,21 +16169,34 @@ function useRenderPipeline(mainCB, setupCB) {
16026
16169
  pipeline = new webgpu.RenderPipeline(renderer);
16027
16170
  justCreatedPipeline = true;
16028
16171
  }
16029
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16172
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16173
+ let outgoingScenePass = null;
16030
16174
  let scenePass;
16031
16175
  if (cacheValid) {
16032
16176
  scenePass = scenePassCacheRef.current.scenePass;
16033
16177
  } else {
16178
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16034
16179
  scenePass = tsl.pass(scene, camera);
16035
16180
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16181
+ forceScenePassRebuildRef.current = false;
16036
16182
  }
16037
16183
  currentPasses.scenePass = scenePass;
16038
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16184
+ let outputNodeChanged = false;
16185
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16186
+ pipeline.outputNode = scenePass;
16187
+ outputNodeChanged = true;
16188
+ }
16039
16189
  set({ renderPipeline: pipeline, passes: currentPasses });
16190
+ setRenderOverride(store, renderThroughPipeline(store));
16040
16191
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16041
16192
  if (shouldRunCallbacks) {
16042
16193
  if (setupCBRef.current) {
16043
- const freshState = store.getState();
16194
+ const freshState = Object.assign({}, store.getState(), {
16195
+ renderPipeline: pipeline,
16196
+ passes: { ...currentPasses, scenePass },
16197
+ renderer,
16198
+ isLegacy: false
16199
+ });
16044
16200
  const setupResult = setupCBRef.current(freshState);
16045
16201
  if (setupResult && typeof setupResult === "object") {
16046
16202
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16048,33 +16204,38 @@ function useRenderPipeline(mainCB, setupCB) {
16048
16204
  }
16049
16205
  }
16050
16206
  if (mainCBRef.current) {
16051
- const freshState = store.getState();
16207
+ const freshState = Object.assign({}, store.getState(), {
16208
+ renderPipeline: pipeline,
16209
+ passes: { ...currentPasses, scenePass },
16210
+ renderer,
16211
+ isLegacy: false
16212
+ });
16052
16213
  const mainResult = mainCBRef.current(freshState);
16053
16214
  if (mainResult && typeof mainResult === "object") {
16054
16215
  currentPasses = { ...currentPasses, ...mainResult };
16055
16216
  set({ passes: currentPasses });
16056
16217
  }
16057
16218
  }
16219
+ outputNodeChanged = true;
16058
16220
  callbacksRanRef.current = true;
16059
16221
  }
16222
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16223
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16060
16224
  } catch (error) {
16061
16225
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16062
16226
  }
16063
16227
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16064
- const passes = useThree((s) => s.passes);
16065
- const renderPipeline = useThree((s) => s.renderPipeline);
16066
- return {
16067
- passes,
16068
- renderPipeline,
16069
- clearPasses,
16070
- reset,
16071
- rebuild,
16072
- // isReady indicates if RenderPipeline is configured and ready for rendering
16073
- isReady: renderPipeline !== null
16074
- };
16228
+ const passes = useThree$1((s) => s.passes);
16229
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16230
+ const actions = { passes, clearPasses, reset, rebuild };
16231
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16232
+ return { ...actions, isReady: false, renderPipeline: null };
16075
16233
  }
16076
16234
 
16077
16235
  extend(THREE);
16236
+ const useThree = useThree$1;
16237
+ const useFrame = useFrame$1;
16238
+ const Canvas = Canvas$1;
16078
16239
 
16079
16240
  exports.Scheduler = scheduler.Scheduler;
16080
16241
  exports.getScheduler = scheduler.getScheduler;
@@ -16152,10 +16313,12 @@ exports.rebuildAllStorage = rebuildAllStorage;
16152
16313
  exports.rebuildAllUniforms = rebuildAllUniforms;
16153
16314
  exports.reconciler = reconciler;
16154
16315
  exports.registerPrimary = registerPrimary;
16316
+ exports.registerRootExtension = registerRootExtension;
16155
16317
  exports.removeInteractivity = removeInteractivity;
16156
16318
  exports.removeNodes = removeNodes;
16157
16319
  exports.removeUniforms = removeUniforms;
16158
16320
  exports.resolve = resolve;
16321
+ exports.setRenderOverride = setRenderOverride;
16159
16322
  exports.swapInteractivity = swapInteractivity;
16160
16323
  exports.unmountComponentAtNode = unmountComponentAtNode;
16161
16324
  exports.unregisterPrimary = unregisterPrimary;