Allow specifying per-card matrices

Set a per-card matrix through the newly expanded
TextureParameters mechanism.

This also changes the behavior of the card matrix so that
it is applied locally to the card, rather than being
applied to the whole carousel. And the detail texture is
no longer drawn with the card matrix.

Bug: 3206251
Change-Id: I5119bc97b136d32cf9d4e29e7aa3751334cc68c1
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index 4b774aa..7516250 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -156,9 +156,10 @@
     }
 
     /**
-     * Load A3D file from resource.  If resId == 0, will clear geometry for this item.
-     * @param n
-     * @param resId
+     * Set the geometry to show for a given slot.
+     * @param n The card to set the geometry for
+     * @param mesh The geometry for that item
+     * @see {@link #setDefaultGeometry}
      */
     public void setGeometryForItem(int n, Mesh mesh) {
         if (mRenderScript != null) {
@@ -167,6 +168,31 @@
     }
 
     /**
+     * Load A3D file from resource. If resId == 0, will clear geometry for this item.
+     * @param n The card to set the geometry for
+     * @param resId The resource ID for the geometry for that item
+     * @see {@link #setDefaultGeometry}
+     */
+    public void setGeometryForItem(int n, int resId) {
+        if (mRenderScript != null) {
+            Mesh mesh = mRenderScript.loadGeometry(resId);
+            mRenderScript.setGeometry(n, mesh);
+        }
+    }
+
+    /**
+     * Set the matrix for the specified card
+     * @param n The card to set the matrix for
+     * @param matrix The matrix to use
+     * @see {@link #setDefaultGeometry}
+     */
+    public void setMatrixForItem(int n, float[] matrix) {
+        if (mRenderScript != null) {
+            mRenderScript.setMatrix(n, matrix);
+        }
+    }
+
+    /**
      * Set the number of slots around the Carousel. Basically equivalent to the poles horses
      * might attach to on a real Carousel.
      *
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index 3de1037..7431acc 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -324,7 +324,7 @@
         mScript.set_fillDirection(direction);
     }
 
-    public void setDefaultCardMatrix(float[] matrix) {
+    private Matrix4f matrixFromFloat(float[] matrix) {
         int dimensions;
         if (matrix == null || matrix.length == 0) {
           dimensions = 0;
@@ -342,7 +342,12 @@
                 rsMatrix.set(i, j, matrix[i*dimensions + j]);
             }
         }
-        mScript.set_defaultCardMatrix(rsMatrix);
+
+        return rsMatrix;
+    }
+
+    public void setDefaultCardMatrix(float[] matrix) {
+        mScript.set_defaultCardMatrix(matrixFromFloat(matrix));
     }
 
     private void initVertexProgram() {
@@ -667,16 +672,27 @@
         return allocation;
     }
 
+    private ScriptField_Card.Item getOrCreateCard(int n) {
+        ScriptField_Card.Item item;
+        try {
+            item = mCards.get(n);
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+            item = null;
+        }
+        if (item == null) {
+            if (DBG) Log.v(TAG, "getOrCreateItem(): no item at index " + n);
+            item = new ScriptField_Card.Item();
+        }
+        return item;
+    }
+
     public void setTexture(int n, Bitmap bitmap)
     {
         if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
 
         synchronized(this) {
-            ScriptField_Card.Item item = mCards.get(n);
-            if (item == null) {
-                if (DBG) Log.v(TAG, "setTexture(): no item at index " + n);
-                item = new ScriptField_Card.Item();
-            }
+            ScriptField_Card.Item item = getOrCreateCard(n);
             if (bitmap != null) {
                 item.texture = allocationFromPool(n, bitmap, MIPMAP);
             } else {
@@ -695,11 +711,7 @@
         if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
 
         synchronized(this) {
-            ScriptField_Card.Item item = mCards.get(n);
-            if (item == null) {
-                if (DBG) Log.v(TAG, "setDetailTexture(): no item at index " + n);
-                item = new ScriptField_Card.Item();
-            }
+            ScriptField_Card.Item item = getOrCreateCard(n);
             float width = 0.0f;
             float height = 0.0f;
             if (bitmap != null) {
@@ -770,11 +782,7 @@
 
         synchronized(this) {
             final boolean mipmap = false;
-            ScriptField_Card.Item item = mCards.get(n);
-            if (item == null) {
-                if (DBG) Log.v(TAG, "setGeometry(): no item at index " + n);
-                item = new ScriptField_Card.Item();
-            }
+            ScriptField_Card.Item item = getOrCreateCard(n);
             if (geometry != null) {
                 item.geometry = geometry;
             } else {
@@ -789,6 +797,23 @@
         }
     }
 
+    public void setMatrix(int n, float[] matrix) {
+        if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
+
+        synchronized(this) {
+            final boolean mipmap = false;
+            ScriptField_Card.Item item = getOrCreateCard(n);
+            if (matrix != null) {
+                item.matrix = matrixFromFloat(matrix);
+            } else {
+                if (DBG) Log.v(TAG, "unloading matrix " + n);
+                item.matrix = null;
+            }
+            mCards.set(item, n, false);
+            mScript.invoke_setMatrix(n, item.matrix);
+        }
+    }
+
     public void setBackgroundColor(Float4 color) {
         mScript.set_backgroundColor(color);
     }
diff --git a/carousel/java/com/android/ex/carousel/CarouselView.java b/carousel/java/com/android/ex/carousel/CarouselView.java
index 572f172..6fd0f20 100644
--- a/carousel/java/com/android/ex/carousel/CarouselView.java
+++ b/carousel/java/com/android/ex/carousel/CarouselView.java
@@ -201,6 +201,15 @@
     }
 
     /**
+     * Set the matrix for a given item.
+     * @param n
+     * @param matrix the requested matrix; null to just use the default
+     */
+    public void setMatrixForItem(int n, float[] matrix) {
+        mController.setMatrixForItem(n, matrix);
+    }
+
+    /**
      * Set the number of slots around the Carousel. Basically equivalent to the poles horses
      * might attach to on a real Carousel.
      *
diff --git a/carousel/java/com/android/ex/carousel/CarouselViewHelper.java b/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
index 5fc74ac..ef05ba9 100644
--- a/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
+++ b/carousel/java/com/android/ex/carousel/CarouselViewHelper.java
@@ -8,6 +8,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.renderscript.Matrix4f;
 import android.renderscript.Mesh;
 import android.util.Log;
 
@@ -24,6 +25,7 @@
     private static final int SET_TEXTURE_N = 1;
     private static final int SET_DETAIL_TEXTURE_N = 2;
     private static final int SET_GEOMETRY_N = 3;
+    private static final int SET_MATRIX_N = 4;
 
     // This is an ordered list of base message ids to allow removal of a single item from the
     // list for a particular card. The implementation currently supports up to a million cards.
@@ -41,8 +43,9 @@
     private Handler mSyncHandler; // Synchronous handler for interacting with UI elements.
 
     public static class TextureParameters {
-        public TextureParameters(Matrix _matrix) { matrix = _matrix; }
-        public Matrix matrix;
+        public TextureParameters() { matrix = new Matrix4f(); }
+        public TextureParameters(Matrix4f _matrix) { matrix = _matrix; }
+        public Matrix4f matrix;
     };
 
     public static class DetailTextureParameters {
@@ -108,6 +111,12 @@
                 if (bitmap != null) {
                     mSyncHandler.obtainMessage(SET_TEXTURE_N, id, 0, bitmap).sendToTarget();
                 }
+
+                TextureParameters params = getTextureParameters(id);
+                if (params != null) {
+                    mSyncHandler.obtainMessage(SET_MATRIX_N, id, 0,
+                            params.matrix.getArray()).sendToTarget();
+                }
             } else if (msg.what < REQUEST_GEOMETRY_N) {
                 // REQUEST_DETAIL_TEXTURE_N
                 final Bitmap bitmap = getDetailTexture(id);
@@ -150,6 +159,10 @@
                 case SET_GEOMETRY_N:
                     mCarouselView.setGeometryForItem(id, (Mesh) msg.obj);
                     break;
+
+                case SET_MATRIX_N:
+                    mCarouselView.setMatrixForItem(id, (float[]) msg.obj);
+                    break;
             }
         }
     };
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 5c9ae5b..efccbd0 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -523,6 +523,11 @@
     cards[n].geometryTimeStamp = rsUptimeMillis();
 }
 
+void setMatrix(int n, rs_matrix4x4 matrix) {
+    if (n < 0 || n >= cardCount) return;
+    cards[n].matrix = matrix;
+}
+
 void setProgramStoresCard(int n, rs_program_store programStore)
 {
     rsSetObject(&programStoresCard[n].programStore, programStore);
@@ -576,7 +581,8 @@
        // fast path
        return 0;
    }
-   const float cardHeight = cardVertices[3].y - cardVertices[0].y;
+   const float cardHeight = (cardVertices[3].y - cardVertices[0].y) *
+      rsMatrixGet(&cards[i].matrix, 1, 1);
    const float totalHeight = rowCount * (cardHeight + rowSpacing) - rowSpacing;
    if (firstCardTop)
       i = rowCount - (i % rowCount) - 1;
@@ -591,10 +597,11 @@
  * matrix: The output matrix.
  * i: The card we're getting the matrix for.
  * enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
+ * enableCardMatrix: Whether to also consider the user-specified card matrix
  *
  * returns true if an animation is being applied to the given card
  */
-static bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway)
+static bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway, bool enableCardMatrix)
 {
     float theta = cardPosition(i);
     float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
@@ -611,7 +618,10 @@
         stillAnimating = getAnimatedScaleForSelected(&scale);
         rsMatrixScale(matrix, scale.x, scale.y, scale.z);
     }
-    rsMatrixLoadMultiply(matrix, &cards[i].matrix, matrix);
+    // TODO(jshuma): Instead of ignoring this matrix for the detail texture, use card bounding box
+    if (enableCardMatrix) {
+        rsMatrixLoadMultiply(matrix, matrix, &cards[i].matrix);
+    }
     return stillAnimating;
 }
 
@@ -702,7 +712,7 @@
 
             // Draw geometry
             rs_matrix4x4 matrix = modelviewMatrix;
-            stillAnimating |= getMatrixForCard(&matrix, i, true);
+            stillAnimating |= getMatrixForCard(&matrix, i, true, true);
             rsgProgramVertexLoadModelMatrix(&matrix);
             if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
                 drawMesh(cards[i].geometry);
@@ -787,7 +797,7 @@
 
                 // Compute position in screen space of top corner or bottom corner of card
                 rsMatrixLoad(&model, &modelviewMatrix);
-                stillAnimating |= getMatrixForCard(&model, i, false);
+                stillAnimating |= getMatrixForCard(&model, i, false, false);
                 rs_matrix4x4 matrix;
                 rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
 
@@ -1347,7 +1357,7 @@
 
             // Transform card vertices to world space
             rsMatrixLoadIdentity(&matrix);
-            getMatrixForCard(&matrix, id, true);
+            getMatrixForCard(&matrix, id, true, true);
             for (int vertex = 0; vertex < 4; vertex++) {
                 float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
                 if (tmp.w != 0.0f) {