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 223042 d65c9aa7d0adab4e7d23d7c68fc06873be3a91bf
parent 223041 40853b2ad7721b2844c9105c27b080390674869b
child 223043 c11c954fad7c6ab5ee3a628a294637a97d2681a6
push id13154
push userbballo@mozilla.com
push dateFri, 09 Jan 2015 22:31:04 +0000
treeherderb2g-inbound@d65c9aa7d0ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1113457
milestone37.0a1
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;