Bug 1100315 - Implemented fling curving by mapping physical velocity to a value using the bezier curve. r=kats
authorPrabhjyot Singh Sodhi <prabhjyotsingh95@gmail.com>
Mon, 12 Jan 2015 11:27:41 -0500
changeset 249120 dd9ba31c3cfc8e477c44ca8eb078afe3004bfff9
parent 249119 7536897c179d5ee387e16e38d786ecbea48c22b0
child 249121 7a776d0c87e6be3863a5ad389d13afff948a5a5a
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
bugs1100315
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 1100315 - Implemented fling curving by mapping physical velocity to a value using the bezier curve. r=kats
mobile/android/app/mobile.js
mobile/android/base/gfx/Axis.java
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -663,16 +663,25 @@ pref("ui.scrolling.negate_wheel_scrollY"
 // auto-detect based on reported hardware values
 pref("ui.scrolling.gamepad_dead_zone", 115);
 
 // Prefs for fling acceleration
 pref("ui.scrolling.fling_accel_interval", -1);
 pref("ui.scrolling.fling_accel_base_multiplier", -1);
 pref("ui.scrolling.fling_accel_supplemental_multiplier", -1);
 
+// Prefs for fling curving
+pref("ui.scrolling.fling_curve_function_x1", -1);
+pref("ui.scrolling.fling_curve_function_y1", -1);
+pref("ui.scrolling.fling_curve_function_x2", -1);
+pref("ui.scrolling.fling_curve_function_y2", -1);
+pref("ui.scrolling.fling_curve_threshold_velocity", -1);
+pref("ui.scrolling.fling_curve_max_velocity", -1);
+pref("ui.scrolling.fling_curve_newton_iterations", -1);
+
 // Enable accessibility mode if platform accessibility is enabled.
 pref("accessibility.accessfu.activate", 2);
 pref("accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement,Landmark,ListItem");
 // Active quicknav mode, index value of list from quicknav_modes
 pref("accessibility.accessfu.quicknav_index", 0);
 // Setting for an utterance order (0 - description first, 1 - description last).
 pref("accessibility.accessfu.utterance", 1);
 // Whether to skip images with empty alt text
--- a/mobile/android/base/gfx/Axis.java
+++ b/mobile/android/base/gfx/Axis.java
@@ -29,16 +29,23 @@ abstract class Axis {
     private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
     private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
     private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
     private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
     private static final String PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE = "ui.scrolling.min_scrollable_distance";
     private static final String PREF_FLING_ACCEL_INTERVAL = "ui.scrolling.fling_accel_interval";
     private static final String PREF_FLING_ACCEL_BASE_MULTIPLIER = "ui.scrolling.fling_accel_base_multiplier";
     private static final String PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = "ui.scrolling.fling_accel_supplemental_multiplier";
+    private static final String PREF_FLING_CURVE_FUNCTION_X1 = "ui.scrolling.fling_curve_function_x1";
+    private static final String PREF_FLING_CURVE_FUNCTION_Y1 = "ui.scrolling.fling_curve_function_y1";
+    private static final String PREF_FLING_CURVE_FUNCTION_X2 = "ui.scrolling.fling_curve_function_x2";
+    private static final String PREF_FLING_CURVE_FUNCTION_Y2 = "ui.scrolling.fling_curve_function_y2";
+    private static final String PREF_FLING_CURVE_THRESHOLD_VELOCITY = "ui.scrolling.fling_curve_threshold_velocity";
+    private static final String PREF_FLING_CURVE_MAXIMUM_VELOCITY = "ui.scrolling.fling_curve_max_velocity";
+    private static final String PREF_FLING_CURVE_NEWTON_ITERATIONS = "ui.scrolling.fling_curve_newton_iterations";
 
     // This fraction of velocity remains after every animation frame when the velocity is low.
     private static float FRICTION_SLOW;
     // This fraction of velocity remains after every animation frame when the velocity is high.
     private static float FRICTION_FAST;
     // Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST
     // to FRICTION_SLOW.
     private static float VELOCITY_THRESHOLD;
@@ -59,16 +66,37 @@ abstract class Axis {
     private static long FLING_ACCEL_INTERVAL;
 
     // The multiplication constant of the base velocity in case of accelerated scrolling.
     private static float FLING_ACCEL_BASE_MULTIPLIER;
 
     // The multiplication constant of the supplemental velocity in case of accelerated scrolling.
     private static float FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER;
 
+    // x co-ordinate of the second bezier control point
+    private static float FLING_CURVE_FUNCTION_X1;
+
+    // y co-ordinate of the second bezier control point
+    private static float FLING_CURVE_FUNCTION_Y1;
+
+    // x co-ordinate of the third bezier control point
+    private static float FLING_CURVE_FUNCTION_X2;
+
+    // y co-ordinate of the third bezier control point
+    private static float FLING_CURVE_FUNCTION_Y2;
+
+    // Minimum velocity for curve to be implemented i.e fling curving
+    private static float FLING_CURVE_THRESHOLD_VELOCITY;
+
+    // Maximum permitted velocity
+    private static float FLING_CURVE_MAXIMUM_VELOCITY;
+
+    // Number of iterations in the Newton-Raphson method
+    private static int FLING_CURVE_NEWTON_ITERATIONS;
+
     private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
         Integer value = (prefs == null ? null : prefs.get(prefName));
         return (value == null || value < 0 ? defaultValue : value) / 1000f;
     }
 
     private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
         Integer value = (prefs == null ? null : prefs.get(prefName));
         return (value == null || value < 0 ? defaultValue : value);
@@ -78,17 +106,24 @@ abstract class Axis {
         final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
                                  PREF_SCROLLING_FRICTION_SLOW,
                                  PREF_SCROLLING_MAX_EVENT_ACCELERATION,
                                  PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
                                  PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
                                  PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE,
                                  PREF_FLING_ACCEL_INTERVAL,
                                  PREF_FLING_ACCEL_BASE_MULTIPLIER,
-                                 PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER };
+                                 PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER,
+                                 PREF_FLING_CURVE_FUNCTION_X1,
+                                 PREF_FLING_CURVE_FUNCTION_Y1,
+                                 PREF_FLING_CURVE_FUNCTION_X2,
+                                 PREF_FLING_CURVE_FUNCTION_Y2,
+                                 PREF_FLING_CURVE_THRESHOLD_VELOCITY,
+                                 PREF_FLING_CURVE_MAXIMUM_VELOCITY,
+                                 PREF_FLING_CURVE_NEWTON_ITERATIONS };
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             Map<String, Integer> mPrefs = new HashMap<String, Integer>();
 
             @Override public void prefValue(String name, int value) {
                 mPrefs.put(name, value);
             }
 
@@ -115,16 +150,24 @@ abstract class Axis {
         VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
         MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, GeckoAppShell.getDpi() > 300 ? 100 : 40);
         OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
         SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
         MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
         FLING_ACCEL_INTERVAL = getIntPref(prefs, PREF_FLING_ACCEL_INTERVAL, 500);
         FLING_ACCEL_BASE_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_BASE_MULTIPLIER, 1000);
         FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER, 1000);
+        FLING_CURVE_FUNCTION_X1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X1, 410);
+        FLING_CURVE_FUNCTION_Y1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y1, 0);
+        FLING_CURVE_FUNCTION_X2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X2, 800);
+        FLING_CURVE_FUNCTION_Y2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y2, 1000);
+        FLING_CURVE_THRESHOLD_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_THRESHOLD_VELOCITY, 30);
+        FLING_CURVE_MAXIMUM_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_MAXIMUM_VELOCITY, 70);
+        FLING_CURVE_NEWTON_ITERATIONS = getIntPref(prefs, PREF_FLING_CURVE_NEWTON_ITERATIONS, 5);
+
         Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
                 + MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
     }
 
     static {
         // set the scrolling parameters to default values on startup
         setPrefs(null);
     }
@@ -207,19 +250,68 @@ abstract class Axis {
     void setScrollingDisabled(boolean disabled) {
         mScrollingDisabled = disabled;
     }
 
     void saveTouchPos() {
         mLastTouchPos = mTouchPos;
     }
 
+    // Calculates and return the slope of the curve at given parameter t
+    float getSlope(float t) {
+        float y1 = FLING_CURVE_FUNCTION_Y1;
+        float y2 = FLING_CURVE_FUNCTION_Y2;
+
+        return (3 * y1)
+             + t * (6 * y2 - 12 * y1)
+             + t * t * (9 * y1 - 9 * y2 + 3);
+    }
+
+    // Calculates and returns the value of the bezier curve with the given parameter t and control points p1 and p2
+    float cubicBezier(float p1, float p2, float t) {
+        return (3 * t * (1-t) * (1-t) * p1)
+             + (3 * t * t * (1-t) * p2)
+             + (t * t * t);
+    }
+
+    // Responsible for mapping the physical velocity to a the velocity obtained after applying bezier curve (with control points (X1,Y1) and (X2,Y2))
+    float flingCurve(float By) {
+        int ni = FLING_CURVE_NEWTON_ITERATIONS;
+        float[] guess = new float[ni];
+        float y1 = FLING_CURVE_FUNCTION_Y1;
+        float y2 = FLING_CURVE_FUNCTION_Y2;
+        guess[0] = By;
+
+        for (int i = 1; i < ni; i++) {
+            guess[i] = guess[i-1] - (cubicBezier(y1, y2, guess[i-1]) - By) / getSlope(guess[i-1]);
+        }
+        // guess[4] is the final approximate root the cubic equation.
+        float t = guess[4];
+
+        float x1 = FLING_CURVE_FUNCTION_X1;
+        float x2 = FLING_CURVE_FUNCTION_X2;
+        return cubicBezier(x1, x2, t);
+    }
+
     void updateWithTouchAt(float pos, float timeDelta) {
+        float curveVelocityThreshold = FLING_CURVE_THRESHOLD_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
+        float maxVelocity = FLING_CURVE_MAXIMUM_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
+
         float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
 
+        if (Math.abs(newVelocity) > curveVelocityThreshold && Math.abs(newVelocity) < maxVelocity) {
+            float sign = Math.signum(newVelocity);
+            newVelocity = newVelocity * sign;
+            float scale = maxVelocity - curveVelocityThreshold;
+            float functInp = (newVelocity - curveVelocityThreshold) / scale;
+            float functOut = flingCurve(functInp);
+            newVelocity = functOut * scale + curveVelocityThreshold;
+            newVelocity = newVelocity * sign;
+        }
+
         mRecentVelocities[mRecentVelocityCount % FLING_VELOCITY_POINTS] = newVelocity;
         mRecentVelocityCount++;
 
         // If there's a direction change, or current velocity is very low,
         // allow setting of the velocity outright. Otherwise, use the current
         // velocity and a maximum change factor to set the new velocity.
         boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
         boolean directionChange = (mVelocity > 0) != (newVelocity > 0);