tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AndroidFlingPhysics.cpp (7754B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AndroidFlingPhysics.h"
      8 
      9 #include <cmath>
     10 
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/StaticPrefs_apz.h"
     13 #include "mozilla/StaticPtr.h"
     14 
     15 namespace mozilla {
     16 namespace layers {
     17 
     18 // The fling physics calculations implemented here are adapted from
     19 // Chrome's implementation of fling physics on Android:
     20 // https://cs.chromium.org/chromium/src/ui/events/android/scroller.cc?rcl=3ae3aaff927038a5c644926842cb0c31dea60c79
     21 
     22 static double ComputeDeceleration(float aDPI) {
     23  const float kFriction = 0.84f;
     24  const float kGravityEarth = 9.80665f;
     25  return kGravityEarth  // g (m/s^2)
     26         * 39.37f       // inch/meter
     27         * aDPI         // pixels/inch
     28         * kFriction;
     29 }
     30 
     31 // == std::log(0.78f) / std::log(0.9f)
     32 const float kDecelerationRate = 2.3582018f;
     33 
     34 // Default friction constant in android.view.ViewConfiguration.
     35 static float GetFlingFriction() {
     36  return StaticPrefs::apz_android_chrome_fling_physics_friction();
     37 }
     38 
     39 // Tension lines cross at (GetInflexion(), 1).
     40 static float GetInflexion() {
     41  // Clamp the inflexion to the range [0,1]. Values outside of this range
     42  // do not make sense in the physics model, and for negative values the
     43  // approximation used to compute the spline curve does not converge.
     44  const float inflexion =
     45      StaticPrefs::apz_android_chrome_fling_physics_inflexion();
     46  return std::clamp(inflexion, 0.f, 1.f);
     47 }
     48 
     49 // Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
     50 // pixels or closer from the end.
     51 static float GetThresholdForFlingEnd() {
     52  return StaticPrefs::apz_android_chrome_fling_physics_stop_threshold();
     53 }
     54 
     55 static double ComputeSplineDeceleration(ParentLayerCoord aVelocity,
     56                                        double aTuningCoeff) {
     57  float velocityPerSec = aVelocity * 1000.0f;
     58  return std::log(GetInflexion() * velocityPerSec /
     59                  (GetFlingFriction() * aTuningCoeff));
     60 }
     61 
     62 static TimeDuration ComputeFlingDuration(ParentLayerCoord aVelocity,
     63                                         double aTuningCoeff) {
     64  const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff);
     65  const double timeSeconds = std::exp(splineDecel / (kDecelerationRate - 1.0));
     66  return TimeDuration::FromSeconds(timeSeconds);
     67 }
     68 
     69 static ParentLayerCoord ComputeFlingDistance(ParentLayerCoord aVelocity,
     70                                             double aTuningCoeff) {
     71  const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff);
     72  return GetFlingFriction() * aTuningCoeff *
     73         std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * splineDecel);
     74 }
     75 
     76 struct SplineConstants {
     77 public:
     78  SplineConstants() {
     79    const float kStartTension = 0.5f;
     80    const float kEndTension = 1.0f;
     81    const float kP1 = kStartTension * GetInflexion();
     82    const float kP2 = 1.0f - kEndTension * (1.0f - GetInflexion());
     83 
     84    float xMin = 0.0f;
     85    for (int i = 0; i < kNumSamples; i++) {
     86      const float alpha = static_cast<float>(i) / kNumSamples;
     87 
     88      float xMax = 1.0f;
     89      float x, tx, coef;
     90      // While the inflexion can be overridden by the user, it's clamped to
     91      // [0,1]. For values in this range, the approximation algorithm below
     92      // should converge in < 20 iterations. For good measure, we impose an
     93      // iteration limit as well.
     94      static const int sIterationLimit = 100;
     95      int iterations = 0;
     96      while (iterations++ < sIterationLimit) {
     97        x = xMin + (xMax - xMin) / 2.0f;
     98        coef = 3.0f * x * (1.0f - x);
     99        tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
    100        if (FuzzyEqualsAdditive(tx, alpha)) {
    101          break;
    102        }
    103        if (tx > alpha) {
    104          xMax = x;
    105        } else {
    106          xMin = x;
    107        }
    108      }
    109      mSplinePositions[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
    110    }
    111    mSplinePositions[kNumSamples] = 1.0f;
    112  }
    113 
    114  void CalculateCoefficients(float aTime, float* aOutDistanceCoef,
    115                             float* aOutVelocityCoef) {
    116    *aOutDistanceCoef = 1.0f;
    117    *aOutVelocityCoef = 0.0f;
    118    const int index = static_cast<int>(kNumSamples * aTime);
    119    if (index < kNumSamples) {
    120      const float tInf = static_cast<float>(index) / kNumSamples;
    121      const float dInf = mSplinePositions[index];
    122      const float tSup = static_cast<float>(index + 1) / kNumSamples;
    123      const float dSup = mSplinePositions[index + 1];
    124      *aOutVelocityCoef = (dSup - dInf) / (tSup - tInf);
    125      *aOutDistanceCoef = dInf + (aTime - tInf) * *aOutVelocityCoef;
    126    }
    127  }
    128 
    129 private:
    130  static const int kNumSamples = 100;
    131  float mSplinePositions[kNumSamples + 1];
    132 };
    133 
    134 StaticAutoPtr<SplineConstants> gSplineConstants;
    135 
    136 /* static */
    137 void AndroidFlingPhysics::InitializeGlobalState() {
    138  gSplineConstants = new SplineConstants();
    139  ClearOnShutdown(&gSplineConstants);
    140 }
    141 
    142 void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity,
    143                               float aPLPPI) {
    144  mVelocity = aStartingVelocity.Length();
    145  // We should not have created a fling animation if there is no velocity.
    146  MOZ_ASSERT(mVelocity != 0.0f);
    147  const double tuningCoeff = ComputeDeceleration(aPLPPI);
    148  mTargetDuration = ComputeFlingDuration(mVelocity, tuningCoeff);
    149  MOZ_ASSERT(!mTargetDuration.IsZero());
    150  mDurationSoFar = TimeDuration();
    151  mLastPos = ParentLayerPoint();
    152  mCurrentPos = ParentLayerPoint();
    153  float coeffX =
    154      mVelocity == 0 ? 1.0f : aStartingVelocity.x.value / mVelocity.value;
    155  float coeffY =
    156      mVelocity == 0 ? 1.0f : aStartingVelocity.y.value / mVelocity.value;
    157  mTargetDistance = ComputeFlingDistance(mVelocity, tuningCoeff);
    158  mTargetPos =
    159      ParentLayerPoint(mTargetDistance * coeffX, mTargetDistance * coeffY);
    160  const float hyp = mTargetPos.Length();
    161  if (FuzzyEqualsAdditive(hyp, 0.0f)) {
    162    mDeltaNorm = ParentLayerPoint(1, 1);
    163  } else {
    164    mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp);
    165  }
    166 }
    167 void AndroidFlingPhysics::Sample(const TimeDuration& aDelta,
    168                                 ParentLayerPoint* aOutVelocity,
    169                                 ParentLayerPoint* aOutOffset) {
    170  float newVelocity;
    171  if (SampleImpl(aDelta, &newVelocity)) {
    172    *aOutOffset = (mCurrentPos - mLastPos);
    173    *aOutVelocity = ParentLayerPoint(mDeltaNorm.x * newVelocity,
    174                                     mDeltaNorm.y * newVelocity);
    175    mLastPos = mCurrentPos;
    176  } else {
    177    *aOutOffset = (mTargetPos - mLastPos);
    178    *aOutVelocity = ParentLayerPoint();
    179  }
    180 }
    181 
    182 bool AndroidFlingPhysics::SampleImpl(const TimeDuration& aDelta,
    183                                     float* aOutVelocity) {
    184  mDurationSoFar += aDelta;
    185  if (mDurationSoFar >= mTargetDuration) {
    186    return false;
    187  }
    188 
    189  const float timeRatio =
    190      mDurationSoFar.ToSeconds() / mTargetDuration.ToSeconds();
    191  float distanceCoef = 1.0f;
    192  float velocityCoef = 0.0f;
    193  gSplineConstants->CalculateCoefficients(timeRatio, &distanceCoef,
    194                                          &velocityCoef);
    195 
    196  // The caller expects the velocity in pixels per _millisecond_.
    197  *aOutVelocity =
    198      velocityCoef * mTargetDistance / mTargetDuration.ToMilliseconds();
    199 
    200  mCurrentPos = mTargetPos * distanceCoef;
    201 
    202  ParentLayerPoint remainder = mTargetPos - mCurrentPos;
    203  const float threshold = GetThresholdForFlingEnd();
    204  if (fabsf(remainder.x) < threshold && fabsf(remainder.y) < threshold) {
    205    return false;
    206  }
    207 
    208  return true;
    209 }
    210 
    211 }  // namespace layers
    212 }  // namespace mozilla