@ericchen1990/pano-view 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -65,7 +65,18 @@ export function TileExample() {
65
65
  }
66
66
  ```
67
67
 
68
- The first `multires` value is the tile size. Remaining values are ascending cube-face sizes for `l1`, `l2`, and later levels. The default preview is `${baseUrl}/previews/cube-vertical.webp`, with faces stacked as `l/f/r/b/u/d`. Use `previewFaceOrder` when a preview atlas uses a different top-to-bottom order.
68
+ The first `multires` value is the tile size. Remaining values are ascending cube-face sizes for `l1`, `l2`, and later levels. The default preview is `${baseUrl}/previews/cube-vertical.webp`, with six square faces stacked from top to bottom as `l/f/r/b/u/d`. Use `previewFaceOrder` when an atlas uses a different order:
69
+
70
+ ```tsx
71
+ <Tile
72
+ baseUrl="/panoramas/room"
73
+ multires="512,1000,2000"
74
+ previewUrl="/panoramas/room/previews/cube-vertical.webp"
75
+ previewFaceOrder={["l", "f", "r", "b", "u", "d"]}
76
+ />
77
+ ```
78
+
79
+ `previewFaceOrder` must list the six face codes in their top-to-bottom atlas order: `f`, `r`, `b`, `l`, `u`, and `d`.
69
80
 
70
81
  During rapid rotation or zoom, loaded tiles remain visible while newly visible tiles use their parent level or the preview as a local fallback.
71
82
 
@@ -217,8 +228,8 @@ export function PlacementExample() {
217
228
 
218
229
  `normalizePanoPosition`, `normalizePanoYaw`, `clampPanoPitch`, and
219
230
  `vector3ToPanoPosition` are also exported for controlled authoring flows. Yaw
220
- is normalized to `[-180, 180)`. Pitch is clamped to `[-89.9, 89.9]` to avoid
221
- the spherical-coordinate singularity at the poles.
231
+ is normalized to `[-180, 180)`. Pitch is clamped to `[-90, 90]`; at either
232
+ pole, yaw is normalized to `0` because it does not identify a unique point.
222
233
 
223
234
  ## Shared hotspot contract and saved definitions
224
235
 
@@ -326,6 +337,9 @@ placement properties:
326
337
  - `scaleMode="fov"` (default) lets a hotspot grow when the user zooms in.
327
338
  `scaleMode="fixed"` compensates for FOV changes and keeps its screen size
328
339
  close to the size at `referenceFov` (default `75`).
340
+ - `rotation` rotates the hotspot around its own normal in degrees. `scale`
341
+ applies an overall positive multiplier to its angular width and height and
342
+ defaults to `1`.
329
343
 
330
344
  ```tsx
331
345
  <ImageHotspot
@@ -336,6 +350,8 @@ placement properties:
336
350
  height={6}
337
351
  mode="billboard"
338
352
  distance={14}
353
+ rotation={-12}
354
+ scale={1.25}
339
355
  scaleMode="fixed"
340
356
  src="/hotspots/callout.webp"
341
357
  />
package/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ var import_react4 = require("react");
52
52
  var import_fiber = require("@react-three/fiber");
53
53
  var import_react = require("react");
54
54
  var import_three = require("three");
55
- var MAX_PITCH = 89.9;
55
+ var MAX_PITCH = 90;
56
56
  var INERTIA_ROTATION_DAMPING = 8;
57
57
  var INERTIA_ZOOM_DAMPING = 12;
58
58
  var DEFAULT_ROTATE_DAMPING = 14;
@@ -578,9 +578,10 @@ var import_three3 = require("three");
578
578
  var import_three2 = require("three");
579
579
 
580
580
  // src/hotspot/types.ts
581
- var MAX_HOTSPOT_PITCH = 89.9;
581
+ var MAX_HOTSPOT_PITCH = 90;
582
582
 
583
583
  // src/hotspot/coordinates.ts
584
+ var POLE_EPSILON_DEGREES = 1e-6;
584
585
  function normalizePanoYaw(yaw) {
585
586
  if (!Number.isFinite(yaw)) {
586
587
  return 0;
@@ -591,12 +592,18 @@ function clampPanoPitch(pitch) {
591
592
  if (!Number.isFinite(pitch)) {
592
593
  return 0;
593
594
  }
594
- return import_three2.MathUtils.clamp(pitch, -MAX_HOTSPOT_PITCH, MAX_HOTSPOT_PITCH);
595
+ const clamped = import_three2.MathUtils.clamp(
596
+ pitch,
597
+ -MAX_HOTSPOT_PITCH,
598
+ MAX_HOTSPOT_PITCH
599
+ );
600
+ return Math.abs(clamped) >= MAX_HOTSPOT_PITCH - POLE_EPSILON_DEGREES ? Math.sign(clamped) * MAX_HOTSPOT_PITCH : clamped;
595
601
  }
596
602
  function normalizePanoPosition(position) {
603
+ const pitch = clampPanoPitch(position.pitch);
597
604
  return {
598
- yaw: normalizePanoYaw(position.yaw),
599
- pitch: clampPanoPitch(position.pitch)
605
+ yaw: Math.abs(pitch) === MAX_HOTSPOT_PITCH ? 0 : normalizePanoYaw(position.yaw),
606
+ pitch
600
607
  };
601
608
  }
602
609
  function panoPositionToVector3(position, radius = 1) {
@@ -724,7 +731,7 @@ var PanoView = (0, import_react4.forwardRef)(
724
731
  const normalizedInitialView = (0, import_react4.useMemo)(
725
732
  () => ({
726
733
  yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
727
- pitch: initialView?.pitch ?? DEFAULT_VIEW.pitch,
734
+ pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
728
735
  fov: Math.max(
729
736
  normalizedMinFov,
730
737
  Math.min(initialView?.fov ?? DEFAULT_VIEW.fov, normalizedMaxFov)
@@ -2449,6 +2456,9 @@ function angularSizeToWorldSize(size, radius) {
2449
2456
  const safeSize = Number.isFinite(size) ? import_three9.MathUtils.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
2450
2457
  return 2 * radius * Math.tan(import_three9.MathUtils.degToRad(safeSize) / 2);
2451
2458
  }
2459
+ function resolveScale(scale) {
2460
+ return Number.isFinite(scale) && scale > 0 ? scale : 1;
2461
+ }
2452
2462
  function resolveDistance(placement, distance, width, height) {
2453
2463
  const maximumDistance = maximumHotspotDistance(width, height);
2454
2464
  if (placement === "surface") {
@@ -2513,6 +2523,7 @@ function HotspotAnchor({
2513
2523
  position,
2514
2524
  width,
2515
2525
  height,
2526
+ scale = 1,
2516
2527
  useAngularScale = true,
2517
2528
  focusContent,
2518
2529
  mode = "billboard",
@@ -2541,7 +2552,15 @@ function HotspotAnchor({
2541
2552
  const normalizedPosition = normalizePanoPosition(position);
2542
2553
  const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
2543
2554
  const { orientation: orientation2, placement } = rendering;
2544
- const resolvedDistance = resolveDistance(placement, distance, width, height);
2555
+ const resolvedScale = resolveScale(scale);
2556
+ const scaledWidth = width * resolvedScale;
2557
+ const scaledHeight = height * resolvedScale;
2558
+ const resolvedDistance = resolveDistance(
2559
+ placement,
2560
+ distance,
2561
+ scaledWidth,
2562
+ scaledHeight
2563
+ );
2545
2564
  const worldPosition = (0, import_react9.useMemo)(
2546
2565
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
2547
2566
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
@@ -2550,8 +2569,8 @@ function HotspotAnchor({
2550
2569
  () => createSurfaceQuaternion(normalizedPosition),
2551
2570
  [normalizedPosition.pitch, normalizedPosition.yaw]
2552
2571
  );
2553
- const worldWidth = angularSizeToWorldSize(width, resolvedDistance);
2554
- const worldHeight = angularSizeToWorldSize(height, resolvedDistance);
2572
+ const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
2573
+ const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
2555
2574
  const focusGeometry = (0, import_react9.useMemo)(() => {
2556
2575
  const planeGeometry = new import_three9.PlaneGeometry(1, 1);
2557
2576
  const geometry = new import_three9.EdgesGeometry(planeGeometry);