@ericchen1990/pano-view 3.6.1 → 3.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2842,7 +2842,12 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2842
2842
  }
2843
2843
  }
2844
2844
  markDirty();
2845
- if (applyOptions?.recordVelocity && !gyroControlsView) {
2845
+ const releaseVelocity = applyOptions?.velocity;
2846
+ if (!gyroControlsView && releaseVelocity && Number.isFinite(releaseVelocity.yaw) && Number.isFinite(releaseVelocity.pitch)) {
2847
+ yawVelocityRef.current = releaseVelocity.yaw;
2848
+ pitchVelocityRef.current = releaseVelocity.pitch;
2849
+ lastVelocitySampleAtRef.current = performance.now();
2850
+ } else if (applyOptions?.recordVelocity && !gyroControlsView) {
2846
2851
  const now = performance.now();
2847
2852
  const previousAt = lastVelocitySampleAtRef.current;
2848
2853
  const elapsed = previousAt === null ? 1 / 60 : Math.max((now - previousAt) / 1e3, 1 / 120);
@@ -3690,6 +3695,26 @@ import { useContext as useContext5, useEffect as useEffect5, useRef as useRef5 }
3690
3695
  import { Euler as Euler2, MathUtils as MathUtils2, PerspectiveCamera as PerspectiveCamera2, Vector3 } from "three";
3691
3696
  var DEFAULT_POINTER_DRAG_SPEED = 0.35;
3692
3697
  var POINTER_FOLLOW_TIME_SECONDS = 0.01;
3698
+ var MIN_VELOCITY_SAMPLE_SECONDS = 1 / 120;
3699
+ function pointerReleaseVelocity(samples, releasedAt, solve, maxAgeMs = POINTER_VELOCITY_MAX_AGE_MS) {
3700
+ if (samples.length < 2) return { yaw: 0, pitch: 0 };
3701
+ const newest = samples[samples.length - 1];
3702
+ if (releasedAt < newest.at || releasedAt - newest.at > maxAgeMs) return { yaw: 0, pitch: 0 };
3703
+ let oldest = newest;
3704
+ for (let index = samples.length - 1; index >= 0; index -= 1) {
3705
+ const sample = samples[index];
3706
+ if (releasedAt - sample.at <= maxAgeMs) oldest = sample;
3707
+ else break;
3708
+ }
3709
+ if (oldest === newest) return { yaw: 0, pitch: 0 };
3710
+ const elapsed = Math.max((newest.at - oldest.at) / 1e3, MIN_VELOCITY_SAMPLE_SECONDS);
3711
+ const from = solve(oldest);
3712
+ const to = solve(newest);
3713
+ return {
3714
+ yaw: normalizeViewYaw(to.yaw - from.yaw) / elapsed,
3715
+ pitch: (to.pitch - from.pitch) / elapsed
3716
+ };
3717
+ }
3693
3718
  var PointerDragAnchor = class {
3694
3719
  constructor(start, origin, rect, projection) {
3695
3720
  this.start = start;
@@ -3747,8 +3772,11 @@ function createPointerDrag(camera, element, runtime, source) {
3747
3772
  let lastPoint;
3748
3773
  let fresh = false;
3749
3774
  let lastFrameDelta = 1 / 60;
3775
+ let samples = [];
3776
+ let gestureSpeed = DEFAULT_POINTER_DRAG_SPEED;
3777
+ let gestureInvert = false;
3750
3778
  const key = (rect) => `${rect.left},${rect.top},${rect.width},${rect.height},${camera.projectionMatrix.elements.join(",")}`;
3751
- const start = (point) => {
3779
+ const place = (point) => {
3752
3780
  const handle = runtime();
3753
3781
  displayed = { ...point };
3754
3782
  lastPoint = { ...point };
@@ -3760,69 +3788,113 @@ function createPointerDrag(camera, element, runtime, source) {
3760
3788
  projectionKey = key(rect);
3761
3789
  anchor = new PointerDragAnchor(point, handle.getView(), rect, camera.clone());
3762
3790
  };
3763
- const apply = (point, previous, speed, invert, recordVelocity) => {
3791
+ const remember = (point, at) => {
3792
+ samples.push({ x: point.x, y: point.y, at });
3793
+ const cutoff = at - POINTER_VELOCITY_MAX_AGE_MS;
3794
+ while (samples.length > 2 && samples[0].at < cutoff) samples.shift();
3795
+ };
3796
+ const apply = (point, previous, speed, invert, recordVelocity, releaseVelocity) => {
3764
3797
  const handle = runtime();
3765
3798
  if (!handle) return;
3766
3799
  const rect = element.getBoundingClientRect();
3767
3800
  if (anchor && key(rect) !== projectionKey) {
3768
3801
  const queued = pending;
3769
- start(displayed ?? previous);
3802
+ place(displayed ?? previous);
3770
3803
  pending = queued;
3771
3804
  }
3805
+ const options = {
3806
+ recordVelocity: releaseVelocity ? false : recordVelocity,
3807
+ source,
3808
+ ...releaseVelocity ? { velocity: releaseVelocity } : {}
3809
+ };
3772
3810
  if (anchor) {
3773
3811
  handle.applyViewDelta(anchor.solve(point, speed, invert), {
3774
- pointerOrigin: anchor.origin,
3775
- recordVelocity,
3776
- source
3812
+ ...options,
3813
+ pointerOrigin: anchor.origin
3777
3814
  });
3778
3815
  } else {
3779
3816
  handle.applyViewDelta({
3780
3817
  yaw: -(point.x - previous.x) * 360 * speed / Math.max(rect.width, 1) * (invert ? -1 : 1),
3781
3818
  pitch: (point.y - previous.y) * 180 * speed / Math.max(rect.height, 1) * (invert ? -1 : 1)
3782
- }, { recordVelocity, source });
3819
+ }, options);
3783
3820
  }
3784
3821
  };
3822
+ const valid = () => revision === (runtime()?.getPointerDragState().revision ?? -1) && !runtime()?.isInteractionLocked();
3823
+ const clear = () => {
3824
+ anchor = void 0;
3825
+ revision = -1;
3826
+ pending = void 0;
3827
+ displayed = void 0;
3828
+ samples = [];
3829
+ };
3785
3830
  return {
3786
- start,
3787
- valid: () => revision === (runtime()?.getPointerDragState().revision ?? -1) && !runtime()?.isInteractionLocked(),
3788
- clear: () => {
3789
- anchor = void 0;
3790
- revision = -1;
3791
- pending = void 0;
3792
- displayed = void 0;
3831
+ start(point, at = performance.now()) {
3832
+ gestureSpeed = DEFAULT_POINTER_DRAG_SPEED;
3833
+ gestureInvert = false;
3834
+ samples = [];
3835
+ remember(point, at);
3836
+ place(point);
3793
3837
  },
3838
+ valid,
3839
+ clear,
3794
3840
  suspend: () => {
3795
3841
  anchor = void 0;
3796
3842
  pending = void 0;
3797
3843
  displayed = void 0;
3844
+ samples = [];
3798
3845
  },
3799
- move(point, previous, speed, invert) {
3846
+ move(point, previous, speed, invert, at = performance.now()) {
3800
3847
  fresh = true;
3848
+ gestureSpeed = speed;
3849
+ gestureInvert = invert;
3850
+ remember(point, at);
3801
3851
  pending = { point, previous: lastPoint ?? previous, speed, invert };
3802
3852
  },
3803
- flush(deltaSeconds = lastFrameDelta, immediate = false) {
3804
- lastFrameDelta = deltaSeconds;
3853
+ flush(deltaSeconds = lastFrameDelta, immediate = false, releaseVelocity) {
3854
+ if (deltaSeconds > 0) lastFrameDelta = deltaSeconds;
3805
3855
  if (!pending) return;
3806
3856
  const sample = pending;
3807
3857
  if (!anchor || immediate) {
3808
3858
  pending = void 0;
3809
- apply(sample.point, sample.previous, sample.speed, sample.invert, fresh);
3859
+ apply(sample.point, sample.previous, sample.speed, sample.invert, fresh, releaseVelocity);
3810
3860
  fresh = false;
3811
3861
  lastPoint = sample.point;
3812
3862
  displayed = sample.point;
3813
3863
  return;
3814
3864
  }
3865
+ const dt = deltaSeconds > 0 ? deltaSeconds : lastFrameDelta;
3815
3866
  const previousDisplay = displayed ?? sample.previous;
3816
3867
  const dx = sample.point.x - previousDisplay.x;
3817
3868
  const dy = sample.point.y - previousDisplay.y;
3818
3869
  const distance = Math.hypot(dx, dy);
3819
- const ratio = distance > 0.01 ? POINTER_FOLLOW_TIME_SECONDS / (POINTER_FOLLOW_TIME_SECONDS + deltaSeconds) : 0;
3870
+ const ratio = distance > 0.01 ? POINTER_FOLLOW_TIME_SECONDS / (POINTER_FOLLOW_TIME_SECONDS + dt) : 0;
3820
3871
  const point = { x: sample.point.x - dx * ratio, y: sample.point.y - dy * ratio };
3821
3872
  apply(point, sample.previous, sample.speed, sample.invert, fresh);
3822
3873
  fresh = false;
3823
3874
  lastPoint = sample.point;
3824
3875
  displayed = point;
3825
3876
  if (ratio === 0) pending = void 0;
3877
+ },
3878
+ release() {
3879
+ const handle = runtime();
3880
+ if (!handle || !valid()) {
3881
+ clear();
3882
+ return;
3883
+ }
3884
+ const newestAt = samples[samples.length - 1]?.at ?? 0;
3885
+ const velocity = pointerReleaseVelocity(samples, Math.max(performance.now(), newestAt), (point) => {
3886
+ if (anchor) return anchor.solve(point, gestureSpeed, gestureInvert);
3887
+ const origin = samples[0] ?? point;
3888
+ const rect = element.getBoundingClientRect();
3889
+ const sign = gestureInvert ? -1 : 1;
3890
+ return {
3891
+ yaw: -(point.x - origin.x) * 360 * gestureSpeed / Math.max(rect.width, 1) * sign,
3892
+ pitch: (point.y - origin.y) * 180 * gestureSpeed / Math.max(rect.height, 1) * sign
3893
+ };
3894
+ });
3895
+ if (pending) this.flush(lastFrameDelta, true, velocity);
3896
+ else handle.applyViewDelta({}, { velocity, source });
3897
+ clear();
3826
3898
  }
3827
3899
  };
3828
3900
  }
@@ -3838,6 +3910,9 @@ var BUTTON_FLAG = {
3838
3910
  function resolveSpeed2(value, fallback) {
3839
3911
  return Number.isFinite(value) && value >= 0 ? value : fallback;
3840
3912
  }
3913
+ function pointerAt(event) {
3914
+ return event.timeStamp > 0 ? event.timeStamp : performance.now();
3915
+ }
3841
3916
  function resolveButtons(buttons) {
3842
3917
  const list = buttons && buttons.length > 0 ? buttons : ["left"];
3843
3918
  return new Set(list.map((button) => BUTTON_FLAG[button]));
@@ -3924,7 +3999,7 @@ function MouseControls({
3924
3999
  });
3925
4000
  element.setPointerCapture?.(event.pointerId);
3926
4001
  syncActive();
3927
- drag.start({ x: event.clientX, y: event.clientY });
4002
+ drag.start({ x: event.clientX, y: event.clientY }, pointerAt(event));
3928
4003
  };
3929
4004
  const onPointerMove = (event) => {
3930
4005
  if (!isMouseLike(event)) {
@@ -3945,15 +4020,15 @@ function MouseControls({
3945
4020
  };
3946
4021
  pointersRef.current.set(event.pointerId, next);
3947
4022
  const speed = resolveSpeed2(optionsRef.current.rotateSpeed, DEFAULT_ROTATE_SPEED2);
3948
- drag.move(next, previous, speed, optionsRef.current.invert);
4023
+ drag.move(next, previous, speed, optionsRef.current.invert, pointerAt(event));
3949
4024
  };
3950
4025
  const releasePointer = (event) => {
3951
4026
  if (!pointersRef.current.has(event.pointerId)) {
3952
4027
  return;
3953
4028
  }
3954
- if (event.type === "pointerup") drag.flush();
4029
+ if (event.type === "pointerup") drag.release();
4030
+ else drag.clear();
3955
4031
  pointersRef.current.delete(event.pointerId);
3956
- drag.clear();
3957
4032
  if (element.hasPointerCapture?.(event.pointerId)) {
3958
4033
  element.releasePointerCapture?.(event.pointerId);
3959
4034
  }
@@ -5619,6 +5694,9 @@ function notifyScenesHost(host) {
5619
5694
  import { useFrame as useFrame6, useThree as useThree7 } from "@react-three/fiber";
5620
5695
  import { useContext as useContext13, useEffect as useEffect11, useRef as useRef10 } from "react";
5621
5696
  var DEFAULT_ROTATE_SPEED3 = 0.35;
5697
+ function pointerAt2(event) {
5698
+ return event.timeStamp > 0 ? event.timeStamp : performance.now();
5699
+ }
5622
5700
  function resolveSpeed3(value, fallback) {
5623
5701
  return Number.isFinite(value) && value >= 0 ? value : fallback;
5624
5702
  }
@@ -5687,7 +5765,7 @@ function TouchControls({
5687
5765
  });
5688
5766
  element.setPointerCapture?.(event.pointerId);
5689
5767
  syncActive();
5690
- drag.start({ x: event.clientX, y: event.clientY });
5768
+ drag.start({ x: event.clientX, y: event.clientY }, pointerAt2(event));
5691
5769
  if (pointersRef.current.size >= 2) {
5692
5770
  drag.suspend();
5693
5771
  const [first, second] = Array.from(pointersRef.current.values());
@@ -5734,15 +5812,16 @@ function TouchControls({
5734
5812
  return;
5735
5813
  }
5736
5814
  const speed = resolveSpeed3(optionsRef.current.rotateSpeed, DEFAULT_ROTATE_SPEED3);
5737
- drag.move(next, previous, speed, optionsRef.current.invert);
5815
+ drag.move(next, previous, speed, optionsRef.current.invert, pointerAt2(event));
5738
5816
  };
5739
5817
  const releasePointer = (event) => {
5740
5818
  if (!pointersRef.current.has(event.pointerId)) {
5741
5819
  return;
5742
5820
  }
5743
- if (event.type === "pointerup") drag.flush();
5821
+ const pinch = pointersRef.current.size >= 2;
5822
+ if (event.type === "pointerup" && !pinch) drag.release();
5823
+ else drag.clear();
5744
5824
  pointersRef.current.delete(event.pointerId);
5745
- drag.clear();
5746
5825
  if (element.hasPointerCapture?.(event.pointerId)) {
5747
5826
  element.releasePointerCapture?.(event.pointerId);
5748
5827
  }
@@ -5751,7 +5830,7 @@ function TouchControls({
5751
5830
  }
5752
5831
  syncActive(event.type !== "pointerup");
5753
5832
  const remaining = pointersRef.current.values().next().value;
5754
- if (remaining) drag.start(remaining);
5833
+ if (remaining) drag.start(remaining, pointerAt2(event));
5755
5834
  };
5756
5835
  element.addEventListener("pointerdown", onPointerDown);
5757
5836
  element.addEventListener("pointermove", onPointerMove);