Bug 1448439 - Add an AndroidFlingPhysics class containing an implementation of Chrome's fling physics on Android. r=kats
MozReview-Commit-ID: 509Cl04rozm
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidFlingPhysics.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AndroidFlingPhysics.h"
+
+#include <cmath>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace layers {
+
+// The fling physics calculations implemented here are adapted from
+// Chrome's implementation of fling physics on Android:
+// https://cs.chromium.org/chromium/src/ui/events/android/scroller.cc?rcl=3ae3aaff927038a5c644926842cb0c31dea60c79
+
+static double ComputeDeceleration(float aFriction)
+{
+ const float kGravityEarth = 9.80665f;
+ return kGravityEarth // g (m/s^2)
+ * 39.37f // inch/meter
+ * 160.f // pixels/inch
+ * aFriction;
+}
+
+// == std::log(0.78f) / std::log(0.9f)
+const float kDecelerationRate = 2.3582018f;
+
+// Default friction constant in android.view.ViewConfiguration.
+const float kFlingFriction = 0.015f;
+
+// Tension lines cross at (kInflexion, 1).
+const float kInflexion = 0.35f;
+
+// Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd|
+// pixels or closer from the end.
+const float kThresholdForFlingEnd = 0.1;
+
+const float kTuningCoeff = ComputeDeceleration(0.84f);
+
+static double ComputeSplineDeceleration(ParentLayerCoord aVelocity)
+{
+ float velocityPerSec = aVelocity * 1000.0f;
+ return std::log(kInflexion * velocityPerSec / (kFlingFriction * kTuningCoeff));
+}
+
+static TimeDuration ComputeFlingDuration(ParentLayerCoord aVelocity)
+{
+ const double splineDecel = ComputeSplineDeceleration(aVelocity);
+ const double timeSeconds = std::exp(splineDecel / (kDecelerationRate - 1.0));
+ return TimeDuration::FromSeconds(timeSeconds);
+}
+
+static ParentLayerCoord ComputeFlingDistance(ParentLayerCoord aVelocity)
+{
+ const double splineDecel = ComputeSplineDeceleration(aVelocity);
+ return kFlingFriction * kTuningCoeff *
+ std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * splineDecel);
+}
+
+struct SplineConstants {
+public:
+ SplineConstants() {
+ const float kStartTension = 0.5f;
+ const float kEndTension = 1.0f;
+ const float kP1 = kStartTension * kInflexion;
+ const float kP2 = 1.0f - kEndTension * (1.0f - kInflexion);
+
+ float xMin = 0.0f;
+ for (int i = 0; i < kNumSamples; i++) {
+ const float alpha = static_cast<float>(i) / kNumSamples;
+
+ float xMax = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = xMin + (xMax - xMin) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x;
+ if (FuzzyEqualsAdditive(tx, alpha)) {
+ break;
+ }
+ if (tx > alpha) {
+ xMax = x;
+ } else {
+ xMin = x;
+ }
+ }
+ mSplinePositions[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x;
+ }
+ mSplinePositions[kNumSamples] = 1.0f;
+ }
+
+ void CalculateCoefficients(float aTime,
+ float* aOutDistanceCoef,
+ float* aOutVelocityCoef)
+ {
+ *aOutDistanceCoef = 1.0f;
+ *aOutVelocityCoef = 0.0f;
+ const int index = static_cast<int>(kNumSamples * aTime);
+ if (index < kNumSamples) {
+ const float tInf = static_cast<float>(index) / kNumSamples;
+ const float dInf = mSplinePositions[index];
+ const float tSup = static_cast<float>(index + 1) / kNumSamples;
+ const float dSup = mSplinePositions[index + 1];
+ *aOutVelocityCoef = (dSup - dInf) / (tSup - tInf);
+ *aOutDistanceCoef = dInf + (aTime - tInf) * *aOutVelocityCoef;
+ }
+ }
+private:
+ static const int kNumSamples = 100;
+ float mSplinePositions[kNumSamples + 1];
+};
+
+StaticAutoPtr<SplineConstants> gSplineConstants;
+
+/* static */ void AndroidFlingPhysics::InitializeGlobalState()
+{
+ gSplineConstants = new SplineConstants();
+ ClearOnShutdown(&gSplineConstants);
+}
+
+void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity)
+{
+ mVelocity = aStartingVelocity.Length();
+ mTargetDuration = ComputeFlingDuration(mVelocity);
+ MOZ_ASSERT(!mTargetDuration.IsZero());
+ mDurationSoFar = TimeDuration();
+ mLastPos = ParentLayerPoint();
+ mCurrentPos = ParentLayerPoint();
+ float coeffX = mVelocity == 0 ? 1.0f : aStartingVelocity.x / mVelocity;
+ float coeffY = mVelocity == 0 ? 1.0f : aStartingVelocity.y / mVelocity;
+ mTargetDistance = ComputeFlingDistance(mVelocity);
+ mTargetPos = ParentLayerPoint(mTargetDistance * coeffX,
+ mTargetDistance * coeffY);
+ const float hyp = mTargetPos.Length();
+ if (FuzzyEqualsAdditive(hyp, 0.0f)) {
+ mDeltaNorm = ParentLayerPoint(1, 1);
+ } else {
+ mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp);
+ }
+}
+void AndroidFlingPhysics::Sample(const TimeDuration& aDelta,
+ ParentLayerPoint* aOutVelocity,
+ ParentLayerPoint* aOutOffset)
+{
+ float newVelocity;
+ if (SampleImpl(aDelta, &newVelocity)) {
+ *aOutOffset = (mCurrentPos - mLastPos);
+ *aOutVelocity = ParentLayerPoint(mDeltaNorm.x * newVelocity,
+ mDeltaNorm.y * newVelocity);
+ mLastPos = mCurrentPos;
+ } else {
+ *aOutOffset = (mTargetPos - mLastPos);
+ *aOutVelocity = ParentLayerPoint();
+ }
+}
+
+bool AndroidFlingPhysics::SampleImpl(const TimeDuration& aDelta,
+ float* aOutVelocity)
+{
+ mDurationSoFar += aDelta;
+ if (mDurationSoFar >= mTargetDuration) {
+ return false;
+ }
+
+ const float timeRatio = mDurationSoFar.ToSeconds() / mTargetDuration.ToSeconds();
+ float distanceCoef = 1.0f;
+ float velocityCoef = 0.0f;
+ gSplineConstants->CalculateCoefficients(timeRatio, &distanceCoef, &velocityCoef);
+
+ // The caller expects the velocity in pixels per _millisecond_.
+ *aOutVelocity = velocityCoef * mTargetDistance / mTargetDuration.ToMilliseconds();
+
+ mCurrentPos = mTargetPos * distanceCoef;
+
+ ParentLayerPoint remainder = mTargetPos - mCurrentPos;
+ if (fabsf(remainder.x) < kThresholdForFlingEnd && fabsf(remainder.y) < kThresholdForFlingEnd) {
+ return false;
+ }
+
+ return true;
+}
+
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidFlingPhysics.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_AndroidFlingPhysics_h_
+#define mozilla_layers_AndroidFlingPhysics_h_
+
+#include "AsyncPanZoomController.h"
+#include "Units.h"
+#include "gfxPrefs.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace layers {
+
+class AndroidFlingPhysics {
+public:
+ void Init(const ParentLayerPoint& aVelocity);
+ void Sample(const TimeDuration& aDelta,
+ ParentLayerPoint* aOutVelocity,
+ ParentLayerPoint* aOutOffset);
+
+ static void InitializeGlobalState();
+private:
+ // Returns false if the animation should end.
+ bool SampleImpl(const TimeDuration& aDelta, float* aOutVelocity);
+
+ // Information pertaining to the current fling.
+ // This is initialized on each call to Init().
+ ParentLayerCoord mVelocity; // diagonal velocity (length of velocity vector)
+ TimeDuration mTargetDuration;
+ TimeDuration mDurationSoFar;
+ ParentLayerPoint mLastPos;
+ ParentLayerPoint mCurrentPos;
+ ParentLayerCoord mTargetDistance; // diagonal distance
+ ParentLayerPoint mTargetPos; // really a target *offset* relative to the
+ // start position, which we don't track
+ ParentLayerPoint mDeltaNorm; // mTargetPos with length normalized to 1
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_AndroidFlingPhysics_h_
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -45,16 +45,17 @@ class APZCTreeManager;
struct ScrollableLayerGuid;
class CompositorController;
class MetricsSharingController;
class GestureEventListener;
struct AsyncTransform;
class AsyncPanZoomAnimation;
class StackScrollerFlingAnimation;
template <typename FlingPhysics> class GenericFlingAnimation;
+class AndroidFlingPhysics;
class DesktopFlingPhysics;
class InputBlockState;
struct FlingHandoffState;
class TouchBlockState;
class PanGestureBlockState;
class OverscrollHandoffChain;
struct OverscrollHandoffState;
class StateChangeNotificationBlocker;
@@ -1159,16 +1160,17 @@ public:
ParentLayerPoint AttemptFling(const FlingHandoffState& aHandoffState);
ParentLayerPoint AdjustHandoffVelocityForOverscrollBehavior(ParentLayerPoint& aHandoffVelocity) const;
private:
friend class StackScrollerFlingAnimation;
friend class AutoscrollAnimation;
template <typename FlingPhysics> friend class GenericFlingAnimation;
+ friend class AndroidFlingPhysics;
friend class DesktopFlingPhysics;
friend class OverscrollAnimation;
friend class SmoothScrollAnimation;
friend class GenericScrollAnimation;
friend class WheelScrollAnimation;
friend class KeyboardScrollAnimation;
friend class GenericOverscrollEffect;
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -284,16 +284,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
'MacIOSurfaceHelpers.cpp',
'MacIOSurfaceImage.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
UNIFIED_SOURCES += [
'apz/src/AndroidAPZ.cpp',
'apz/src/AndroidDynamicToolbarAnimator.cpp',
+ 'apz/src/AndroidFlingPhysics.cpp',
]
EXPORTS.mozilla.layers += [
'apz/src/AndroidDynamicToolbarAnimator.h',
]
UNIFIED_SOURCES += [
'AnimationHelper.cpp',
'AnimationInfo.cpp',