Bug 1113457 - Improve the approximation used to model spring physics during an overscroll animation, to avoid the approximation from diverging. r=kats
authorBotond Ballo <botond@mozilla.com>
Wed, 07 Jan 2015 16:45:29 -0500
changeset 248868 d65c9aa7d0adab4e7d23d7c68fc06873be3a91bf
parent 248867 40853b2ad7721b2844c9105c27b080390674869b
child 248869 c11c954fad7c6ab5ee3a628a294637a97d2681a6
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1113457
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1113457 - Improve the approximation used to model spring physics during an overscroll animation, to avoid the approximation from diverging. r=kats
gfx/layers/apz/src/Axis.cpp
gfx/layers/apz/src/Axis.h
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -192,17 +192,17 @@ void Axis::OverscrollBy(ParentLayerCoord
 ParentLayerCoord Axis::GetOverscroll() const {
   return mOverscroll;
 }
 
 bool Axis::IsInUnderscroll() const {
   return mInUnderscroll;
 }
 
-bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) {
+void Axis::StepOverscrollAnimation(double aStepDurationMilliseconds) {
   // Apply spring physics to the overscroll as time goes on.
   // Note: this method of sampling isn't perfectly smooth, as it assumes
   // a constant velocity over 'aDelta', instead of an accelerating velocity.
   // (The way we applying friction to flings has the same issue.)
   // Hooke's law with damping:
   //   F = -kx - bv
   // where
   //   k is a constant related to the stiffness of the spring
@@ -213,35 +213,55 @@ bool Axis::SampleOverscrollAnimation(con
   //   v is the velocity of the point at the end of the spring
   // See http://gafferongames.com/game-physics/spring-physics/
   const float kSpringStiffness = gfxPrefs::APZOverscrollSpringStiffness();
   const float kSpringFriction = gfxPrefs::APZOverscrollSpringFriction();
 
   // Apply spring force.
   float springForce = -1 * kSpringStiffness * mOverscroll;
   // Assume unit mass, so force = acceleration.
-  mVelocity += springForce * aDelta.ToMilliseconds();
+  mVelocity += springForce * aStepDurationMilliseconds;
 
   // Apply dampening.
-  mVelocity *= pow(double(1 - kSpringFriction), aDelta.ToMilliseconds());
+  mVelocity *= pow(double(1 - kSpringFriction), aStepDurationMilliseconds);
   AXIS_LOG("%p|%s sampled overscroll animation, leaving velocity at %f\n",
     mAsyncPanZoomController, Name(), mVelocity);
 
   // Adjust the amount of overscroll based on the velocity.
   // Note that we allow for oscillations. mInUnderscroll tracks whether
   // we are currently in a state where we have overshot and the spring is
   // displaced in the other direction.
   float oldOverscroll = mOverscroll;
-  mOverscroll += (mVelocity * aDelta.ToMilliseconds());
+  mOverscroll += (mVelocity * aStepDurationMilliseconds);
   bool signChange = (oldOverscroll * mOverscroll) < 0;
   if (signChange) {
     // If the sign of mOverscroll changed, we have either entered underscroll
     // or exited it.
     mInUnderscroll = !mInUnderscroll;
   }
+}
+
+bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) {
+  // We approximate the curve traced out by the velocity of the spring
+  // over time by breaking up the curve into small segments over which we
+  // consider the velocity to be constant. If the animation is sampled
+  // sufficiently often, then treating |aDelta| as a single segment of this
+  // sort would be fine, but the frequency at which the animation is sampled
+  // can be affected by external factors, and as the segment size grows larger,
+  // the approximation gets worse and the approximated curve can even diverge
+  // (i.e. oscillate forever, with displacements of increasing absolute value)!
+  // To avoid this, we break up |aDelta| into smaller segments of length 1 ms
+  // each, and a segment of any remaining fractional milliseconds.
+  double milliseconds = aDelta.ToMilliseconds();
+  int wholeMilliseconds = (int) aDelta.ToMilliseconds();
+  double fractionalMilliseconds = milliseconds - wholeMilliseconds;
+  for (int i = 0; i < wholeMilliseconds; ++i) {
+    StepOverscrollAnimation(1);
+  }
+  StepOverscrollAnimation(fractionalMilliseconds);
 
   // If both the velocity and the displacement fall below a threshold, stop
   // the animation so we don't continue doing tiny oscillations that aren't
   // noticeable.
   if (fabs(mOverscroll) < gfxPrefs::APZOverscrollStopDistanceThreshold() &&
       fabs(mVelocity) < gfxPrefs::APZOverscrollStopVelocityThreshold()) {
     // "Jump" to the at-rest state. The jump shouldn't be noticeable as the
     // velocity and overscroll are already low.
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -256,16 +256,19 @@ protected:
   nsTArray<std::pair<uint32_t, float> > mVelocityQueue;
 
   const FrameMetrics& GetFrameMetrics() const;
 
   // Adjust a requested overscroll amount for resistance, yielding a smaller
   // actual overscroll amount.
   ParentLayerCoord ApplyResistance(ParentLayerCoord aOverscroll) const;
 
+  // Helper function for SampleOverscrollAnimation().
+  void StepOverscrollAnimation(double aStepDurationMilliseconds);
+
   // Convert a velocity from global inches/ms into ParentLayerCoords/ms.
   float ToLocalVelocity(float aVelocityInchesPerMs) const;
 };
 
 class AxisX : public Axis {
 public:
   explicit AxisX(AsyncPanZoomController* mAsyncPanZoomController);
   virtual ParentLayerCoord GetPointOffset(const ParentLayerPoint& aPoint) const MOZ_OVERRIDE;