@ericchen1990/pano-view 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -790,14 +790,25 @@ declare function TouchControls({ enabled, rotateSpeed, invert, pinchZoom, }: Tou
790
790
 
791
791
  type SphereProps = {
792
792
  src: string;
793
+ /**
794
+ * Low-resolution 2:1 equirectangular image shown while `src` loads.
795
+ * Relative paths resolve against the directory of `src`. Root-absolute and
796
+ * `http(s)` / `blob:` / `data:` URLs are used as-is. For krpano sphere
797
+ * scenes, copy `<preview url>`.
798
+ */
799
+ previewUrl: string;
793
800
  /** Horizontal image offset in degrees. */
794
801
  yawOffset?: number;
802
+ /** Called once the first paintable texture (preview or `src`) is ready. */
803
+ onReady?: () => void;
795
804
  onLoad?: (texture: Texture) => void;
796
805
  onError?: (error: unknown) => void;
806
+ /** Called when `previewUrl` cannot be loaded; `src` still continues. */
807
+ onPreviewError?: (error: unknown) => void;
797
808
  /** Keeps the source mounted for preloading without drawing it. */
798
809
  visible?: boolean;
799
810
  };
800
- declare function Sphere({ src, yawOffset, onLoad, onError, visible, }: SphereProps): react.JSX.Element | null;
811
+ declare function Sphere({ src, previewUrl, yawOffset, onReady, onLoad, onError, onPreviewError, visible, }: SphereProps): react.JSX.Element | null;
801
812
 
802
813
  type VideoPlaybackState = "playing" | "paused" | "ended" | "blocked";
803
814
  type VideoHotspotErrorEvent = {
@@ -1001,18 +1012,30 @@ type TileLoadError = {
1001
1012
  };
1002
1013
  type TileProps = {
1003
1014
  baseUrl: string;
1015
+ /**
1016
+ * Tile size followed by ascending cube-face sizes (`TILESIZE,LEVELSIZE1,...`).
1017
+ * For krpano output, copy `image.cube.multires` from the XML.
1018
+ */
1004
1019
  multires: string | TileMultiresConfig;
1005
- /** Defaults to `${baseUrl}/previews/cube-vertical.webp`. */
1006
- previewUrl?: string | null;
1020
+ /**
1021
+ * Preview atlas path. Relative paths resolve against `baseUrl`; root-absolute
1022
+ * and `http(s)` / `blob:` / `data:` URLs are used as-is. For krpano output,
1023
+ * copy `<preview url>` (often `preview.jpg` at the scene root). This package's
1024
+ * common cube atlas path is `previews/cube-vertical.webp`.
1025
+ */
1026
+ previewUrl: string;
1007
1027
  /**
1008
1028
  * Face order in the vertical preview atlas, from top to bottom. Defaults to
1009
- * `l/f/r/b/u/d`.
1029
+ * `l/f/r/b/u/d`, matching krpano `preview.striporder` `LFRBUD`.
1010
1030
  */
1011
1031
  previewFaceOrder?: readonly CubeFaceCode[];
1012
1032
  /**
1013
- * Relative to `baseUrl`; supports krpano cube-tile `%s` and `%l` placeholders,
1014
- * horizontal `%h`/`%x`/`%u`/`%c`, vertical `%v`/`%y`/`%w`/`%r`, and index padding
1015
- * such as `%0h` or `%00v`.
1033
+ * Path relative to `baseUrl`. Defaults to
1034
+ * `tiles/%s/l%l/%v/l%l_%s_%h_%v.webp`. For krpano output, copy `<cube url>`
1035
+ * from the XML; krpano templates often use `%v_%h` instead of this default
1036
+ * `%h_%v`, so omitting `urlTemplate` requests the wrong files. Supports
1037
+ * krpano cube-tile `%s` and `%l` placeholders, horizontal `%h`/`%x`/`%u`/`%c`,
1038
+ * vertical `%v`/`%y`/`%w`/`%r`, and index padding such as `%0h` or `%00v`.
1016
1039
  */
1017
1040
  urlTemplate?: string;
1018
1041
  /** Returns a path relative to `baseUrl`; takes precedence over `urlTemplate`. */
@@ -1040,6 +1063,7 @@ type SphereScene = {
1040
1063
  id: string;
1041
1064
  type: "sphere";
1042
1065
  src: string;
1066
+ previewUrl: string;
1043
1067
  yawOffset?: number;
1044
1068
  };
1045
1069
  type TileScene = Omit<TileProps, "loadMode" | "visible" | "onReady" | "onPreviewError" | "onLoadProgress" | "onTileError" | "onLevelChange"> & {
package/dist/index.d.ts CHANGED
@@ -790,14 +790,25 @@ declare function TouchControls({ enabled, rotateSpeed, invert, pinchZoom, }: Tou
790
790
 
791
791
  type SphereProps = {
792
792
  src: string;
793
+ /**
794
+ * Low-resolution 2:1 equirectangular image shown while `src` loads.
795
+ * Relative paths resolve against the directory of `src`. Root-absolute and
796
+ * `http(s)` / `blob:` / `data:` URLs are used as-is. For krpano sphere
797
+ * scenes, copy `<preview url>`.
798
+ */
799
+ previewUrl: string;
793
800
  /** Horizontal image offset in degrees. */
794
801
  yawOffset?: number;
802
+ /** Called once the first paintable texture (preview or `src`) is ready. */
803
+ onReady?: () => void;
795
804
  onLoad?: (texture: Texture) => void;
796
805
  onError?: (error: unknown) => void;
806
+ /** Called when `previewUrl` cannot be loaded; `src` still continues. */
807
+ onPreviewError?: (error: unknown) => void;
797
808
  /** Keeps the source mounted for preloading without drawing it. */
798
809
  visible?: boolean;
799
810
  };
800
- declare function Sphere({ src, yawOffset, onLoad, onError, visible, }: SphereProps): react.JSX.Element | null;
811
+ declare function Sphere({ src, previewUrl, yawOffset, onReady, onLoad, onError, onPreviewError, visible, }: SphereProps): react.JSX.Element | null;
801
812
 
802
813
  type VideoPlaybackState = "playing" | "paused" | "ended" | "blocked";
803
814
  type VideoHotspotErrorEvent = {
@@ -1001,18 +1012,30 @@ type TileLoadError = {
1001
1012
  };
1002
1013
  type TileProps = {
1003
1014
  baseUrl: string;
1015
+ /**
1016
+ * Tile size followed by ascending cube-face sizes (`TILESIZE,LEVELSIZE1,...`).
1017
+ * For krpano output, copy `image.cube.multires` from the XML.
1018
+ */
1004
1019
  multires: string | TileMultiresConfig;
1005
- /** Defaults to `${baseUrl}/previews/cube-vertical.webp`. */
1006
- previewUrl?: string | null;
1020
+ /**
1021
+ * Preview atlas path. Relative paths resolve against `baseUrl`; root-absolute
1022
+ * and `http(s)` / `blob:` / `data:` URLs are used as-is. For krpano output,
1023
+ * copy `<preview url>` (often `preview.jpg` at the scene root). This package's
1024
+ * common cube atlas path is `previews/cube-vertical.webp`.
1025
+ */
1026
+ previewUrl: string;
1007
1027
  /**
1008
1028
  * Face order in the vertical preview atlas, from top to bottom. Defaults to
1009
- * `l/f/r/b/u/d`.
1029
+ * `l/f/r/b/u/d`, matching krpano `preview.striporder` `LFRBUD`.
1010
1030
  */
1011
1031
  previewFaceOrder?: readonly CubeFaceCode[];
1012
1032
  /**
1013
- * Relative to `baseUrl`; supports krpano cube-tile `%s` and `%l` placeholders,
1014
- * horizontal `%h`/`%x`/`%u`/`%c`, vertical `%v`/`%y`/`%w`/`%r`, and index padding
1015
- * such as `%0h` or `%00v`.
1033
+ * Path relative to `baseUrl`. Defaults to
1034
+ * `tiles/%s/l%l/%v/l%l_%s_%h_%v.webp`. For krpano output, copy `<cube url>`
1035
+ * from the XML; krpano templates often use `%v_%h` instead of this default
1036
+ * `%h_%v`, so omitting `urlTemplate` requests the wrong files. Supports
1037
+ * krpano cube-tile `%s` and `%l` placeholders, horizontal `%h`/`%x`/`%u`/`%c`,
1038
+ * vertical `%v`/`%y`/`%w`/`%r`, and index padding such as `%0h` or `%00v`.
1016
1039
  */
1017
1040
  urlTemplate?: string;
1018
1041
  /** Returns a path relative to `baseUrl`; takes precedence over `urlTemplate`. */
@@ -1040,6 +1063,7 @@ type SphereScene = {
1040
1063
  id: string;
1041
1064
  type: "sphere";
1042
1065
  src: string;
1066
+ previewUrl: string;
1043
1067
  yawOffset?: number;
1044
1068
  };
1045
1069
  type TileScene = Omit<TileProps, "loadMode" | "visible" | "onReady" | "onPreviewError" | "onLoadProgress" | "onTileError" | "onLevelChange"> & {
package/dist/index.mjs CHANGED
@@ -6369,7 +6369,7 @@ var WebVR = forwardRef4(function WebVR2({
6369
6369
 
6370
6370
  // src/sphere.tsx
6371
6371
  import { useThree as useThree13 } from "@react-three/fiber";
6372
- import { useEffect as useEffect19, useRef as useRef19, useState as useState8 } from "react";
6372
+ import { useEffect as useEffect19, useMemo as useMemo12, useRef as useRef19, useState as useState8 } from "react";
6373
6373
  import {
6374
6374
  BackSide as BackSide2,
6375
6375
  LinearFilter,
@@ -6380,6 +6380,27 @@ import {
6380
6380
  TextureLoader
6381
6381
  } from "three";
6382
6382
 
6383
+ // src/asset-url.ts
6384
+ function isAbsoluteAssetUrl(url) {
6385
+ return url.startsWith("/") || url.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(url);
6386
+ }
6387
+ function resolveRelativeAssetUrl(baseUrl, relativePath) {
6388
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
6389
+ const normalizedPath = relativePath.replace(/^\/+/, "");
6390
+ return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
6391
+ }
6392
+ function resolveAssetUrl(baseUrl, path) {
6393
+ return isAbsoluteAssetUrl(path) ? path : resolveRelativeAssetUrl(baseUrl, path);
6394
+ }
6395
+ function resolveUrlAgainstFile(fileUrl, path) {
6396
+ if (isAbsoluteAssetUrl(path)) {
6397
+ return path;
6398
+ }
6399
+ const slash = fileUrl.lastIndexOf("/");
6400
+ const directory = slash === -1 ? "" : fileUrl.slice(0, slash);
6401
+ return resolveRelativeAssetUrl(directory, path);
6402
+ }
6403
+
6383
6404
  // src/pano-filter/pano-basic-material.tsx
6384
6405
  import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo11, useRef as useRef18 } from "react";
6385
6406
 
@@ -6596,25 +6617,22 @@ function PanoBasicMaterial({
6596
6617
 
6597
6618
  // src/sphere.tsx
6598
6619
  import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
6599
- function Sphere({
6600
- src,
6601
- yawOffset = 0,
6602
- onLoad,
6603
- onError,
6604
- visible = true
6605
- }) {
6606
- const gl = useThree13((state) => state.gl);
6620
+ function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
6607
6621
  const [texture, setTexture] = useState8(null);
6608
6622
  const onLoadRef = useRef19(onLoad);
6609
6623
  const onErrorRef = useRef19(onError);
6610
6624
  onLoadRef.current = onLoad;
6611
6625
  onErrorRef.current = onError;
6612
6626
  useEffect19(() => {
6627
+ if (!url) {
6628
+ setTexture(null);
6629
+ return;
6630
+ }
6613
6631
  let active = true;
6614
6632
  const loader = new TextureLoader();
6615
6633
  setTexture(null);
6616
6634
  loader.load(
6617
- src,
6635
+ url,
6618
6636
  (loadedTexture) => {
6619
6637
  if (!active) {
6620
6638
  loadedTexture.dispose();
@@ -6624,10 +6642,10 @@ function Sphere({
6624
6642
  loadedTexture.wrapS = RepeatWrapping;
6625
6643
  loadedTexture.repeat.x = -1;
6626
6644
  loadedTexture.offset.x = 1;
6627
- loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
6645
+ loadedTexture.anisotropy = anisotropy;
6628
6646
  loadedTexture.magFilter = LinearFilter;
6629
- loadedTexture.minFilter = LinearMipmapLinearFilter;
6630
- loadedTexture.generateMipmaps = true;
6647
+ loadedTexture.minFilter = generateMipmaps ? LinearMipmapLinearFilter : LinearFilter;
6648
+ loadedTexture.generateMipmaps = generateMipmaps;
6631
6649
  loadedTexture.needsUpdate = true;
6632
6650
  setTexture(loadedTexture);
6633
6651
  onLoadRef.current?.(loadedTexture);
@@ -6635,6 +6653,7 @@ function Sphere({
6635
6653
  void 0,
6636
6654
  (error) => {
6637
6655
  if (active) {
6656
+ setTexture(null);
6638
6657
  onErrorRef.current?.(error);
6639
6658
  }
6640
6659
  }
@@ -6642,14 +6661,58 @@ function Sphere({
6642
6661
  return () => {
6643
6662
  active = false;
6644
6663
  };
6645
- }, [gl, src]);
6664
+ }, [anisotropy, generateMipmaps, url]);
6646
6665
  useEffect19(
6647
6666
  () => () => {
6648
6667
  texture?.dispose();
6649
6668
  },
6650
6669
  [texture]
6651
6670
  );
6652
- if (!texture) {
6671
+ return texture;
6672
+ }
6673
+ function Sphere({
6674
+ src,
6675
+ previewUrl,
6676
+ yawOffset = 0,
6677
+ onReady,
6678
+ onLoad,
6679
+ onError,
6680
+ onPreviewError,
6681
+ visible = true
6682
+ }) {
6683
+ const gl = useThree13((state) => state.gl);
6684
+ const anisotropy = gl.capabilities.getMaxAnisotropy();
6685
+ const resolvedPreviewUrl = useMemo12(
6686
+ () => resolveUrlAgainstFile(src, previewUrl),
6687
+ [previewUrl, src]
6688
+ );
6689
+ const texture = useEquirectTexture(
6690
+ src,
6691
+ anisotropy,
6692
+ true,
6693
+ onLoad,
6694
+ onError
6695
+ );
6696
+ const previewTexture = useEquirectTexture(
6697
+ texture ? null : resolvedPreviewUrl,
6698
+ anisotropy,
6699
+ false,
6700
+ void 0,
6701
+ onPreviewError
6702
+ );
6703
+ const displayTexture = texture ?? previewTexture;
6704
+ const readyRef = useRef19(false);
6705
+ useEffect19(() => {
6706
+ readyRef.current = false;
6707
+ }, [resolvedPreviewUrl, src]);
6708
+ useEffect19(() => {
6709
+ if (!displayTexture || readyRef.current) {
6710
+ return;
6711
+ }
6712
+ readyRef.current = true;
6713
+ onReady?.();
6714
+ }, [displayTexture, onReady]);
6715
+ if (!displayTexture) {
6653
6716
  return null;
6654
6717
  }
6655
6718
  return /* @__PURE__ */ jsxs9(
@@ -6662,7 +6725,7 @@ function Sphere({
6662
6725
  /* @__PURE__ */ jsx15(
6663
6726
  PanoBasicMaterial,
6664
6727
  {
6665
- map: texture,
6728
+ map: displayTexture,
6666
6729
  side: BackSide2,
6667
6730
  toneMapped: false
6668
6731
  }
@@ -6679,7 +6742,7 @@ import {
6679
6742
  useContext as useContext19,
6680
6743
  useEffect as useEffect20,
6681
6744
  useLayoutEffect as useLayoutEffect3,
6682
- useMemo as useMemo12,
6745
+ useMemo as useMemo13,
6683
6746
  useRef as useRef20,
6684
6747
  useState as useState9
6685
6748
  } from "react";
@@ -6878,13 +6941,13 @@ function PanoVideo({
6878
6941
  const gl = useThree14((state) => state.gl);
6879
6942
  const host = useContext19(PanoVideoHostContext);
6880
6943
  const overlay = useContext19(PanoChromeOverlayContext);
6881
- const variants = useMemo12(
6944
+ const variants = useMemo13(
6882
6945
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
6883
6946
  [poster, src, variantsProp]
6884
6947
  );
6885
6948
  const tracks = tracksProp ?? EMPTY_TRACKS;
6886
6949
  const captionsEnabled = captions !== false;
6887
- const captionAppearance = useMemo12(
6950
+ const captionAppearance = useMemo13(
6888
6951
  () => resolvePanoVideoCaptionAppearance(
6889
6952
  typeof captions === "object" ? captions : void 0
6890
6953
  ),
@@ -6949,7 +7012,7 @@ function PanoVideo({
6949
7012
  onEndedRef.current = onEnded;
6950
7013
  onVariantChangeRef.current = onVariantChange;
6951
7014
  onTrackChangeRef.current = onTrackChange;
6952
- const resource = useMemo12(() => {
7015
+ const resource = useMemo13(() => {
6953
7016
  if (typeof document === "undefined") {
6954
7017
  return null;
6955
7018
  }
@@ -7062,7 +7125,7 @@ function PanoVideo({
7062
7125
  captionAppearance
7063
7126
  ]
7064
7127
  );
7065
- const controller = useMemo12(
7128
+ const controller = useMemo13(
7066
7129
  () => ({
7067
7130
  subscribe: (onStoreChange) => {
7068
7131
  const store = storeRef.current;
@@ -7429,7 +7492,7 @@ import {
7429
7492
  useCallback as useCallback10,
7430
7493
  useContext as useContext21,
7431
7494
  useEffect as useEffect25,
7432
- useMemo as useMemo17,
7495
+ useMemo as useMemo18,
7433
7496
  useRef as useRef24,
7434
7497
  useState as useState11
7435
7498
  } from "react";
@@ -7487,7 +7550,7 @@ function isSnapshotTransition(preset) {
7487
7550
 
7488
7551
  // src/scene-transitions/particle-overlay.tsx
7489
7552
  import { useFrame as useFrame9, useThree as useThree15 } from "@react-three/fiber";
7490
- import { useEffect as useEffect21, useMemo as useMemo13, useRef as useRef21 } from "react";
7553
+ import { useEffect as useEffect21, useMemo as useMemo14, useRef as useRef21 } from "react";
7491
7554
  import {
7492
7555
  DoubleSide as DoubleSide2,
7493
7556
  InstancedBufferAttribute,
@@ -7631,8 +7694,8 @@ function ParticleOverlay({
7631
7694
  const startRef = useRef21(null);
7632
7695
  const finishedRef = useRef21(false);
7633
7696
  const directionRef = useRef21(new Vector38());
7634
- const geometry = useMemo13(() => createParticleGeometry(), []);
7635
- const material = useMemo13(
7697
+ const geometry = useMemo14(() => createParticleGeometry(), []);
7698
+ const material = useMemo14(
7636
7699
  () => new ShaderMaterial2({
7637
7700
  transparent: true,
7638
7701
  depthTest: false,
@@ -7686,7 +7749,7 @@ function ParticleOverlay({
7686
7749
 
7687
7750
  // src/scene-transitions/snapshot-overlay.tsx
7688
7751
  import { useFrame as useFrame10, useThree as useThree16 } from "@react-three/fiber";
7689
- import { useEffect as useEffect22, useMemo as useMemo14, useRef as useRef22 } from "react";
7752
+ import { useEffect as useEffect22, useMemo as useMemo15, useRef as useRef22 } from "react";
7690
7753
  import { Vector3 as Vector39 } from "three";
7691
7754
 
7692
7755
  // src/scene-transitions/snapshot-shaders.ts
@@ -7979,7 +8042,7 @@ function SnapshotOverlay({
7979
8042
  const startRef = useRef22(null);
7980
8043
  const finishedRef = useRef22(false);
7981
8044
  const directionRef = useRef22(new Vector39());
7982
- const material = useMemo14(() => {
8045
+ const material = useMemo15(() => {
7983
8046
  if (!isSnapshotTransition(transition.preset)) {
7984
8047
  return null;
7985
8048
  }
@@ -8072,7 +8135,7 @@ import {
8072
8135
  createContext as createContext11,
8073
8136
  useContext as useContext20,
8074
8137
  useEffect as useEffect23,
8075
- useMemo as useMemo15
8138
+ useMemo as useMemo16
8076
8139
  } from "react";
8077
8140
 
8078
8141
  // src/tile/texture-manager.ts
@@ -8313,7 +8376,7 @@ function TileTextureManagerProvider({
8313
8376
  }) {
8314
8377
  const gl = useThree17((state) => state.gl);
8315
8378
  const anisotropy = gl.capabilities.getMaxAnisotropy();
8316
- const manager = useMemo15(
8379
+ const manager = useMemo16(
8317
8380
  () => new TileTextureManager({
8318
8381
  anisotropy,
8319
8382
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -8337,7 +8400,7 @@ import { useFrame as useFrame11, useThree as useThree18 } from "@react-three/fib
8337
8400
  import {
8338
8401
  memo,
8339
8402
  useEffect as useEffect24,
8340
- useMemo as useMemo16,
8403
+ useMemo as useMemo17,
8341
8404
  useRef as useRef23,
8342
8405
  useState as useState10
8343
8406
  } from "react";
@@ -8520,13 +8583,12 @@ function parseMultires(multires) {
8520
8583
  );
8521
8584
  return { tileSize, levels };
8522
8585
  }
8586
+ var DEFAULT_TILE_URL_TEMPLATE = "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
8523
8587
  function buildDefaultTileUrlTemplate() {
8524
- return "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
8588
+ return DEFAULT_TILE_URL_TEMPLATE;
8525
8589
  }
8526
8590
  function resolveRelativeTileUrl(baseUrl, relativePath) {
8527
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
8528
- const normalizedPath = relativePath.replace(/^\/+/, "");
8529
- return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
8591
+ return resolveRelativeAssetUrl(baseUrl, relativePath);
8530
8592
  }
8531
8593
  function resolveTemplateUrl(template, address) {
8532
8594
  return template.replace(/%s/g, address.face).replace(/%l/g, String(address.level)).replace(/%(0*)([hxucvywr])/g, (_match, zeroes, placeholder) => {
@@ -8598,7 +8660,7 @@ function PreviewFace({
8598
8660
  texture,
8599
8661
  faceOrder
8600
8662
  }) {
8601
- const geometry = useMemo16(() => {
8663
+ const geometry = useMemo17(() => {
8602
8664
  const nextGeometry = new PlaneGeometry4(
8603
8665
  PREVIEW_RADIUS * 2,
8604
8666
  PREVIEW_RADIUS * 2
@@ -8884,15 +8946,15 @@ function Tile({
8884
8946
  onLevelChange
8885
8947
  }) {
8886
8948
  const { camera, gl, size } = useThree18();
8887
- const multiresConfig = useMemo16(() => parseMultires(multires), [multires]);
8949
+ const multiresConfig = useMemo17(() => parseMultires(multires), [multires]);
8888
8950
  const { levels, tileSize } = multiresConfig;
8889
8951
  const multiresToken = `${tileSize}:${levels.join(",")}`;
8890
- const candidatesByLevel = useMemo16(
8952
+ const candidatesByLevel = useMemo17(
8891
8953
  () => buildTileCandidates(levels, tileSize),
8892
8954
  [levels, tileSize]
8893
8955
  );
8894
8956
  const anisotropy = gl.capabilities.getMaxAnisotropy();
8895
- const effectivePreviewUrl = previewUrl === void 0 ? `${baseUrl.replace(/\/$/, "")}/previews/cube-vertical.webp` : previewUrl;
8957
+ const effectivePreviewUrl = resolveAssetUrl(baseUrl, previewUrl);
8896
8958
  const previewTexture = usePreviewTexture(
8897
8959
  effectivePreviewUrl,
8898
8960
  anisotropy,
@@ -8905,7 +8967,7 @@ function Tile({
8905
8967
  progressRef.current = onLoadProgress;
8906
8968
  errorRef.current = onTileError;
8907
8969
  levelChangeRef.current = onLevelChange;
8908
- const ownedManager = useMemo16(
8970
+ const ownedManager = useMemo17(
8909
8971
  () => new TileTextureManager({
8910
8972
  anisotropy,
8911
8973
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -8948,7 +9010,7 @@ function Tile({
8948
9010
  onReady?.();
8949
9011
  }, [onReady, previewTexture]);
8950
9012
  const template = urlTemplate ?? buildDefaultTileUrlTemplate();
8951
- const resolveUrl = useMemo16(
9013
+ const resolveUrl = useMemo17(
8952
9014
  () => (address) => {
8953
9015
  const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
8954
9016
  return resolveRelativeTileUrl(baseUrl, relativePath);
@@ -9090,9 +9152,10 @@ function SceneSource({
9090
9152
  Sphere,
9091
9153
  {
9092
9154
  src: scene.src,
9155
+ previewUrl: scene.previewUrl,
9093
9156
  yawOffset: scene.yawOffset,
9094
9157
  visible,
9095
- onLoad: onReady,
9158
+ onReady,
9096
9159
  onError
9097
9160
  }
9098
9161
  );
@@ -9181,11 +9244,11 @@ function ScenesController({
9181
9244
  }) {
9182
9245
  const controlsRef = useContext21(PanoramaViewContext);
9183
9246
  const manager = useSharedTileTextureManager();
9184
- const transition = useMemo17(
9247
+ const transition = useMemo18(
9185
9248
  () => resolveTransition(transitionInput),
9186
9249
  [transitionInput]
9187
9250
  );
9188
- const sceneMap = useMemo17(
9251
+ const sceneMap = useMemo18(
9189
9252
  () => new Map(scenes.map((scene) => [scene.id, scene])),
9190
9253
  [scenes]
9191
9254
  );
@@ -9435,7 +9498,7 @@ import {
9435
9498
  useContext as useContext23,
9436
9499
  useEffect as useEffect27,
9437
9500
  useLayoutEffect as useLayoutEffect4,
9438
- useMemo as useMemo19,
9501
+ useMemo as useMemo20,
9439
9502
  useRef as useRef25,
9440
9503
  useState as useState13
9441
9504
  } from "react";
@@ -9453,7 +9516,7 @@ import {
9453
9516
  } from "three";
9454
9517
 
9455
9518
  // src/hotspot/hotspot-tooltip.tsx
9456
- import { useEffect as useEffect26, useMemo as useMemo18, useState as useState12 } from "react";
9519
+ import { useEffect as useEffect26, useMemo as useMemo19, useState as useState12 } from "react";
9457
9520
 
9458
9521
  // src/webvr/stereo-html.tsx
9459
9522
  import { Html } from "@react-three/drei";
@@ -9581,11 +9644,11 @@ function HotspotTooltip({
9581
9644
  setImageFailed(false);
9582
9645
  }, [content.image]);
9583
9646
  const showImage = Boolean(content.image) && !imageFailed;
9584
- const resolvedAppearance = useMemo18(
9647
+ const resolvedAppearance = useMemo19(
9585
9648
  () => resolveHotspotTooltipAppearance(appearance),
9586
9649
  [appearance]
9587
9650
  );
9588
- const bubbleStyle = useMemo18(
9651
+ const bubbleStyle = useMemo19(
9589
9652
  () => ({
9590
9653
  background: resolvedAppearance.background,
9591
9654
  border: resolvedAppearance.border,
@@ -9975,7 +10038,7 @@ function HotspotAnchor({
9975
10038
  const [hovered, setHovered] = useState13(false);
9976
10039
  const [dragging, setDragging] = useState13(false);
9977
10040
  const [pinned, setPinned] = useState13(false);
9978
- const tooltipContent = useMemo19(
10041
+ const tooltipContent = useMemo20(
9979
10042
  () => resolveHotspotTooltipContent(tooltip),
9980
10043
  [tooltip]
9981
10044
  );
@@ -9991,17 +10054,17 @@ function HotspotAnchor({
9991
10054
  scaledWidth,
9992
10055
  scaledHeight
9993
10056
  );
9994
- const worldPosition = useMemo19(
10057
+ const worldPosition = useMemo20(
9995
10058
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
9996
10059
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
9997
10060
  );
9998
- const surfaceQuaternion3 = useMemo19(
10061
+ const surfaceQuaternion3 = useMemo20(
9999
10062
  () => createSurfaceQuaternion(normalizedPosition),
10000
10063
  [normalizedPosition.pitch, normalizedPosition.yaw]
10001
10064
  );
10002
10065
  const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
10003
10066
  const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
10004
- const focusGeometry = useMemo19(() => {
10067
+ const focusGeometry = useMemo20(() => {
10005
10068
  const planeGeometry = new PlaneGeometry5(1, 1);
10006
10069
  const geometry = new EdgesGeometry(planeGeometry);
10007
10070
  planeGeometry.dispose();
@@ -10487,7 +10550,7 @@ function ImageHotspot({
10487
10550
  }
10488
10551
 
10489
10552
  // src/hotspot/polygon-hotspot.tsx
10490
- import { useEffect as useEffect29, useMemo as useMemo20, useRef as useRef28 } from "react";
10553
+ import { useEffect as useEffect29, useMemo as useMemo21, useRef as useRef28 } from "react";
10491
10554
  import {
10492
10555
  BufferGeometry,
10493
10556
  DoubleSide as DoubleSide5,
@@ -10832,14 +10895,14 @@ function PolygonHotspot({
10832
10895
  onInvalid,
10833
10896
  ...anchorProps
10834
10897
  }) {
10835
- const issues = useMemo20(() => validatePolygonVertices(vertices), [vertices]);
10898
+ const issues = useMemo21(() => validatePolygonVertices(vertices), [vertices]);
10836
10899
  const valid = issues.length === 0;
10837
- const center = useMemo20(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
10838
- const fillGeometry = useMemo20(
10900
+ const center = useMemo21(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
10901
+ const fillGeometry = useMemo21(
10839
10902
  () => valid && center ? makeFillGeometry(vertices, center) : null,
10840
10903
  [center, valid, vertices]
10841
10904
  );
10842
- const strokePoints = useMemo20(
10905
+ const strokePoints = useMemo21(
10843
10906
  () => valid && center ? makeStrokePositions(vertices, center) : null,
10844
10907
  [center, valid, vertices]
10845
10908
  );
@@ -10929,7 +10992,7 @@ function PolygonHotspot({
10929
10992
  }
10930
10993
 
10931
10994
  // src/hotspot/polyline-hotspot.tsx
10932
- import { useEffect as useEffect30, useMemo as useMemo21, useRef as useRef29 } from "react";
10995
+ import { useEffect as useEffect30, useMemo as useMemo22, useRef as useRef29 } from "react";
10933
10996
  import {
10934
10997
  MathUtils as MathUtils8,
10935
10998
  Matrix4 as Matrix44,
@@ -11053,10 +11116,10 @@ function PolylineHotspot({
11053
11116
  onInvalid,
11054
11117
  ...anchorProps
11055
11118
  }) {
11056
- const issues = useMemo21(() => validatePolylineVertices(vertices), [vertices]);
11119
+ const issues = useMemo22(() => validatePolylineVertices(vertices), [vertices]);
11057
11120
  const valid = issues.length === 0;
11058
- const center = useMemo21(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
11059
- const linePoints = useMemo21(
11121
+ const center = useMemo22(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
11122
+ const linePoints = useMemo22(
11060
11123
  () => valid && center ? makeLinePositions(vertices, center) : null,
11061
11124
  [center, valid, vertices]
11062
11125
  );