Port ICS tilt-style overscroll from java to rs

Code follows the java implementation as closely as possible to make
comparisons and future bug fixes easy.

Bug: 5613515
Change-Id: I14db2f2d659be919b4b12e5a7fe53af7ec137467
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index c4dce91..e4dcc65 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -155,6 +155,13 @@
 static const int VELOCITY_HISTORY_MAX = 10; // # recent velocity samples used to calculate average
 static const int VISIBLE_SLOT_PADDING = 2;  // # slots to draw on either side of visible slots
 
+// Constants affecting tilt overscroll.  Some of these should be parameters.
+static const int TILT_SLOT_NUMBER = 5;
+static const float TILT_MIN_ANGLE = M_PI / 315.0f;
+static const float TILT_MAX_BIAS = M_PI / 8.0f;
+static const float TILT_MAX_ANGLE = M_PI / 8.0f;
+static const float MAX_DELTA_BIAS = 0.008f;
+
 // Debug flags
 const bool debugCamera = false; // dumps ray/camera coordinate stuff
 const bool debugSelection = false; // logs selection events
@@ -217,6 +224,7 @@
 
 // Local variables
 static float bias; // rotation bias, in radians. Used for animation and dragging.
+static float overscrollBias; // Track overscroll bias separately for tilt effect.
 static bool updateCamera;    // force a recompute of projection and lookat matrices
 static const float FLT_MAX = 1.0e37;
 static int animatedSelection = -1;
@@ -231,6 +239,7 @@
 static bool isDragging = false; // true while the user is dragging the carousel
 static float selectionRadius = 50.0f; // movement greater than this will result in no selection
 static bool enableSelection = false; // enabled until the user drags outside of selectionRadius
+static float tiltAngle = 0.0f;
 
 // Default plane of the carousel. Used for angular motion estimation in view.
 static Plane carouselPlane = {
@@ -280,6 +289,7 @@
 static bool rayPlaneIntersect(Ray* ray, Plane* plane, float* tout);
 static bool rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout);
 static void stopAutoscroll();
+static bool tiltOverscroll();
 
 void init() {
     // initializers currently have a problem when the variables are exported, so initialize
@@ -290,6 +300,8 @@
     visibleSlotCount = 1;
     visibleDetailCount = 3;
     bias = 0.0f;
+    overscrollBias = 0.0f;
+    tiltAngle = 0.0f;
     radius = carouselCylinder.radius = 1.0f;
     cardRotation = 0.0f;
     cardsFaceTangent = false;
@@ -613,6 +625,21 @@
     return sway;
 }
 
+static float getCardTiltAngle(int i) {
+    i /= rowCount;
+    int totalSlots = (cardCount + rowCount - 1) / rowCount;
+    float tiltSlotNumber = TILT_SLOT_NUMBER;
+    float deltaTilt = tiltAngle / tiltSlotNumber;
+    float cardTiltAngle = 0;
+    if (tiltAngle > 0 && i < tiltSlotNumber) {
+        // Overscroll for the front cards.
+        cardTiltAngle = deltaTilt * (tiltSlotNumber - i);
+    } else if (tiltAngle < 0 && i > (totalSlots - tiltSlotNumber)) {
+        cardTiltAngle = deltaTilt * (i - totalSlots + tiltSlotNumber + 1);
+    }
+    return cardTiltAngle;
+}
+
 // Returns the vertical offset for a card in its slot,
 // depending on the number of rows configured.
 static float getVerticalOffsetForCard(int i) {
@@ -646,7 +673,8 @@
     float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
     rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
     rsMatrixTranslate(matrix, radius, getVerticalOffsetForCard(i), 0);
-    float rotation = cardRotation + swayAngle;
+    float tiltAngle = getCardTiltAngle(i);
+    float rotation = cardRotation + swayAngle + tiltAngle;
     if (!cardsFaceTangent) {
       rotation -= theta;
     }
@@ -1121,6 +1149,9 @@
     touchBias = bias;
     isDragging = true;
     isOverScrolling = false;
+    tiltAngle = 0;
+    overscrollBias = bias;
+
     animatedSelection = doSelection(x, y); // used to provide visual feedback on touch
     stopAutoscroll();
 }
@@ -1200,11 +1231,12 @@
     const float highBias = maximumBias();
     const float lowBias = minimumBias();
     float deltaOmega = dragFunction(x, y);
-    if (!enableSelection) {
-        bias += deltaOmega;
-        bias = clamp(bias, lowBias - wedgeAngle(overscrollSlots),
-                highBias + wedgeAngle(overscrollSlots));
-    }
+    overscrollBias += deltaOmega;
+    overscrollBias = clamp(overscrollBias, lowBias - TILT_MAX_BIAS,
+            highBias + TILT_MAX_BIAS);
+    bias = clamp(overscrollBias, lowBias, highBias);
+    isOverScrolling = tiltOverscroll();
+
     const float2 delta = (float2) { x, y } - touchPosition;
     float distance = sqrt(dot(delta, delta));
     bool inside = (distance < selectionRadius);
@@ -1220,6 +1252,21 @@
     lastTime = eventTime;
 }
 
+bool tiltOverscroll() {
+    if (overscrollBias == bias) {
+        // No overscroll required.
+        return false;
+    }
+
+    // How much we deviate from the maximum bias.
+    float deltaBias = overscrollBias - bias;
+    // We clamped, that means we need overscroll.
+    tiltAngle = (deltaBias / TILT_MAX_BIAS)
+            * TILT_MAX_ANGLE * fillDirection;
+
+    return true;
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Autoscroll Interpolation
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1576,43 +1623,32 @@
         return true;
     }
 
-    const float highBias = maximumBias();
-    const float lowBias = minimumBias();
+    const float firstBias = maximumBias();
+    const float lastBias = minimumBias();
     bool stillAnimating = false;
     if (isOverScrolling) {
-        if (bias > highBias) {
-            bias -= 4.0f * dt * easeOut((bias - highBias) * 2.0f);
-            if (fabs(bias - highBias) < biasMin) {
-                bias = highBias;
-            } else {
-                stillAnimating = true;
-            }
-        } else if (bias < lowBias) {
-            bias += 4.0f * dt * easeOut((lowBias - bias) * 2.0f);
-            if (fabs(bias - lowBias) < biasMin) {
-                bias = lowBias;
-            } else {
-                stillAnimating = true;
-            }
+        if (tiltAngle > TILT_MIN_ANGLE) {
+            tiltAngle -= dt * TILT_MAX_ANGLE;
+            stillAnimating = true;
+        } else if (tiltAngle < -TILT_MIN_ANGLE) {
+            tiltAngle += dt * TILT_MAX_ANGLE;
+            stillAnimating = true;
         } else {
-            isOverScrolling = false;
+           isOverScrolling = false;
+           tiltAngle = false;
+           velocity = 0.0f;
         }
     } else if (isAutoScrolling) {
         stillAnimating = doAutoscroll(currentTime);
     } else {
         stillAnimating = doPhysics(dt);
-        isOverScrolling = bias > highBias || bias < lowBias;
+        isOverScrolling = tiltAngle != 0;
         if (isOverScrolling) {
             velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
+            stillAnimating = true;
         }
     }
-    float newbias = clamp(bias, lowBias - wedgeAngle(overscrollSlots),
-            highBias + wedgeAngle(overscrollSlots));
-    if (newbias != bias) { // we clamped
-        velocity = 0.0f;
-        isOverScrolling = true;
-    }
-    bias = newbias;
+    bias = clamp(bias, lastBias, firstBias);
     return stillAnimating;
 }