Allow clients to draw carousel cards clockwise

This consists of the following:
- Plumb the new fillDirection parameter
- Set the slotPosition (and consequently the cardPosition)
  at the appropriate points
- Compute motion and overscroll based not on first and last
  bias but on lowest and highest bias.
- Let cullCards() allow thetaFirst > thetaLast

Bug: 3177563
Change-Id: I2bb7b3f906726db5ced2ea5bad0e5395f5821d1d
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index d61e2a7..6a3f3a1 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -89,6 +89,7 @@
     private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap(
             new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444);
     private int mDragModel = CarouselRS.DRAG_MODEL_SCREEN_DELTA;
+    private int mFillDirection = CarouselRS.FILL_DIRECTION_CCW;
 
     public CarouselController() {
         boolean useDepthBuffer = true;
@@ -128,6 +129,7 @@
         setFrictionCoefficient(mFrictionCoefficient);
         setDragFactor(mDragFactor);
         setDragModel(mDragModel);
+        setFillDirection(mFillDirection);
         setLookAt(mEye, mAt, mUp);
         setRezInCardCount(mRezInCardCount);
         setFadeInDuration(mFadeInDuration);
@@ -552,6 +554,18 @@
         }
     }
 
+    /** Sets the direction to fill in cards around the carousel.
+     *
+     * @param direction Either {@link CarouselRS#FILL_DIRECTION_CCW} or
+     * {@link CarouselRS#FILL_DIRECTION_CW}.
+     */
+    public void setFillDirection(int direction) {
+        mFillDirection = direction;
+        if (mRenderScript != null) {
+            mRenderScript.setFillDirection(direction);
+        }
+    }
+
     public void setCardRotation(float cardRotation) {
         mCardRotation = cardRotation;
         if (mRenderScript != null) {
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index b59c0eb..5f99250 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -54,6 +54,9 @@
     public static final int DRAG_MODEL_CYLINDER_INSIDE = 2;
     public static final int DRAG_MODEL_CYLINDER_OUTSIDE = 3;
 
+    public static final int FILL_DIRECTION_CCW = +1;
+    public static final int FILL_DIRECTION_CW = -1;
+
     private static final String TAG = "CarouselRS";
     private static final int DEFAULT_SLOT_COUNT = 10;
     private static final boolean MIPMAP = false;
@@ -293,6 +296,10 @@
         mScript.set_dragModel(model);
     }
 
+    public void setFillDirection(int direction) {
+        mScript.set_fillDirection(direction);
+    }
+
     private void initVertexProgram() {
         ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
         mVertexProgram = pvb.create();
diff --git a/carousel/java/com/android/ex/carousel/CarouselView.java b/carousel/java/com/android/ex/carousel/CarouselView.java
index b995060..623fdfc 100644
--- a/carousel/java/com/android/ex/carousel/CarouselView.java
+++ b/carousel/java/com/android/ex/carousel/CarouselView.java
@@ -56,6 +56,10 @@
     // Drag relative to projected point on outside (near point) of cylinder centered around carousel
     public static final int DRAG_MODEL_CYLINDER_OUTSIDE = CarouselRS.DRAG_MODEL_CYLINDER_OUTSIDE;
 
+    // Draw cards counterclockwise around the carousel
+    public static final int FILL_DIRECTION_CCW = CarouselRS.FILL_DIRECTION_CCW;
+    // Draw cards clockwise around the carousel
+    public static final int FILL_DIRECTION_CW = CarouselRS.FILL_DIRECTION_CW;
 
     // Note: remember to update carousel.rs when changing the values below
     public static class DetailAlignment {
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 79798b0..73d6bc6 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -173,6 +173,7 @@
 float rowSpacing;  // spacing between rows of cards
 
 int dragModel = DRAG_MODEL_SCREEN_DELTA;
+int fillDirection; // the order in which to lay out cards: +1 for CCW (default), -1 for CW
 rs_program_store programStoreAlphaZ;
 rs_program_store programStoreAlphaNoZ;
 rs_program_store programStoreNoAlphaZ;
@@ -342,7 +343,7 @@
 // Return angle of slot in position p.
 static float slotPosition(int p)
 {
-    return startAngle + wedgeAngle(p);
+    return startAngle + wedgeAngle(p) * fillDirection;
 }
 
 // Return angle for card in position p.
@@ -351,6 +352,25 @@
     return bias + slotPosition(p / rowCount);
 }
 
+// Return the lowest possible bias value, based on the fill direction
+static float minimumBias()
+{
+    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
+    return (fillDirection > 0) ?
+        -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)) :
+        wedgeAngle(0.0f);
+}
+
+// Return the highest possible bias value, based on the fill direction
+static float maximumBias()
+{
+    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
+    return (fillDirection > 0) ?
+        wedgeAngle(0.0f) :
+        max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
+}
+
+
 // convert from carousel rotation angle (in card slot units) to radians.
 static float carouselRotationAngleToRadians(float carouselRotationAngle)
 {
@@ -1016,14 +1036,13 @@
 
 void doMotion(float x, float y, long eventTime)
 {
-    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
-    const float firstBias = wedgeAngle(0.0f);
-    const float lastBias = -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
+    const float highBias = maximumBias();
+    const float lowBias = minimumBias();
     float deltaOmega = dragFunction(x, y);
     if (!enableSelection) {
         bias += deltaOmega;
-        bias = clamp(bias, lastBias - wedgeAngle(OVERSCROLL_SLOTS),
-                firstBias + wedgeAngle(OVERSCROLL_SLOTS));
+        bias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
+                highBias + wedgeAngle(OVERSCROLL_SLOTS));
     }
     const float2 delta = (float2) { x, y } - touchPosition;
     float distance = sqrt(dot(delta, delta));
@@ -1303,22 +1322,21 @@
         return true;
     }
 
-    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
-    const float firstBias = wedgeAngle(0.0f);
-    const float lastBias = -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
+    const float highBias = maximumBias();
+    const float lowBias = minimumBias();
     bool stillAnimating = false;
     if (overscroll) {
-        if (bias > firstBias) {
-            bias -= 4.0f * dt * easeOut((bias - firstBias) * 2.0f);
-            if (fabs(bias - firstBias) < biasMin) {
-                bias = firstBias;
+        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 < lastBias) {
-            bias += 4.0f * dt * easeOut((lastBias - bias) * 2.0f);
-            if (fabs(bias - lastBias) < biasMin) {
-                bias = lastBias;
+        } else if (bias < lowBias) {
+            bias += 4.0f * dt * easeOut((lowBias - bias) * 2.0f);
+            if (fabs(bias - lowBias) < biasMin) {
+                bias = lowBias;
             } else {
                 stillAnimating = true;
             }
@@ -1327,13 +1345,13 @@
         }
     } else {
         stillAnimating = doPhysics(dt);
-        overscroll = bias > firstBias || bias < lastBias;
+        overscroll = bias > highBias || bias < lowBias;
         if (overscroll) {
             velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
         }
     }
-    float newbias = clamp(bias, lastBias - wedgeAngle(OVERSCROLL_SLOTS),
-            firstBias + wedgeAngle(OVERSCROLL_SLOTS));
+    float newbias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
+            highBias + wedgeAngle(OVERSCROLL_SLOTS));
     if (newbias != bias) { // we clamped
         velocity = 0.0f;
         overscroll = true;
@@ -1363,7 +1381,7 @@
         if (visibleSlotCount > 0) {
             // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
             float p = cardPosition(i);
-            if (p >= thetaFirst && p < thetaLast) {
+            if (p >= thetaFirst && p < thetaLast || p <= thetaFirst && p > thetaLast) {
                 cards[i].cardVisible = true;
                 // cards[i].detailVisible will be set at draw time
                 count++;