carousel.rs revision 8a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6f
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma version(1)
18#pragma rs java_package_name(com.android.ex.carousel);
19#pragma rs set_reflect_license()
20
21#include "rs_graphics.rsh"
22
23typedef struct __attribute__((aligned(4))) Card {
24    // *** Update initCard if you add/remove fields here.
25    rs_allocation texture; // basic card texture
26    rs_allocation detailTexture; // screen-aligned detail texture
27    float2 detailTextureOffset; // offset to add, in screen coordinates
28    float2 detailLineOffset; // offset to add to detail line, in screen coordinates
29    float2 detailTexturePosition[2]; // screen coordinates of detail texture, computed at draw time
30    rs_mesh geometry;
31    rs_matrix4x4 matrix; // custom transform for this card/geometry
32    int textureState;  // whether or not the primary card texture is loaded.
33    int detailTextureState; // whether or not the detail for the card is loaded.
34    int geometryState; // whether or not geometry is loaded
35    int cardVisible; // not bool because of packing bug?
36    int detailVisible; // not bool because of packing bug?
37    int64_t textureTimeStamp; // time when this texture was last updated, in ms
38    int64_t detailTextureTimeStamp; // time when this texture was last updated, in ms
39    int64_t geometryTimeStamp; // time when the card itself was last updated, in ms
40} Card_t;
41
42typedef struct Ray_s {
43    float3 position;
44    float3 direction;
45} Ray;
46
47typedef struct Plane_s {
48    float3 point;
49    float3 normal;
50    float constant;
51} Plane;
52
53typedef struct Cylinder_s {
54    float3 center; // center of a y-axis-aligned infinite cylinder
55    float radius;
56} Cylinder;
57
58typedef struct PerspectiveCamera_s {
59    float3 from;
60    float3 at;
61    float3 up;
62    float  fov;
63    float  aspect;
64    float  near;
65    float  far;
66} PerspectiveCamera;
67
68typedef struct ProgramStore_s {
69    rs_program_store programStore;
70} ProgramStore_t;
71
72typedef struct FragmentShaderConstants_s {
73    float fadeAmount;
74    float overallAlpha;
75} FragmentShaderConstants;
76
77// Request states. Used for loading 3D object properties from the Java client.
78// Typical properties: texture, geometry and matrices.
79enum {
80    STATE_INVALID = 0, // item hasn't been loaded
81    STATE_LOADING, // we've requested an item but are waiting for it to load
82    STATE_STALE, // we have an old item, but should request an update
83    STATE_UPDATING, // we've requested an update, and will display the old one in the meantime
84    STATE_LOADED // item was delivered
85};
86
87// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
88enum {
89    /** Detail is centered vertically with respect to the card **/
90    CENTER_VERTICAL = 1,
91    /** Detail is aligned with the top edge of the carousel view **/
92    VIEW_TOP = 1 << 1,
93    /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
94    VIEW_BOTTOM = 1 << 2,
95    /** Detail is positioned above the card (not yet implemented) **/
96    ABOVE = 1 << 3,
97    /** Detail is positioned below the card **/
98    BELOW = 1 << 4,
99    /** Mask that selects those bits that control vertical alignment **/
100    VERTICAL_ALIGNMENT_MASK = 0xff,
101
102    /**
103     * Detail is centered horizontally with respect to either the top or bottom
104     * extent of the card, depending on whether the detail is above or below the card.
105     */
106    CENTER_HORIZONTAL = 1 << 8,
107    /**
108     * Detail is aligned with the left edge of either the top or the bottom of
109     * the card, depending on whether the detail is above or below the card.
110     */
111    LEFT = 1 << 9,
112    /**
113     * Detail is aligned with the right edge of either the top or the bottom of
114     * the card, depending on whether the detail is above or below the card.
115     * (not yet implemented)
116     */
117    RIGHT = 1 << 10,
118    /** Mask that selects those bits that control horizontal alignment **/
119    HORIZONTAL_ALIGNMENT_MASK = 0xff00,
120};
121
122// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
123static const int CMD_CARD_SELECTED = 100;
124static const int CMD_DETAIL_SELECTED = 105;
125static const int CMD_CARD_LONGPRESS = 110;
126static const int CMD_REQUEST_TEXTURE = 200;
127static const int CMD_INVALIDATE_TEXTURE = 210;
128static const int CMD_REQUEST_GEOMETRY = 300;
129static const int CMD_INVALIDATE_GEOMETRY = 310;
130static const int CMD_ANIMATION_STARTED = 400;
131static const int CMD_ANIMATION_FINISHED = 500;
132static const int CMD_REQUEST_DETAIL_TEXTURE = 600;
133static const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
134static const int CMD_PING = 1000;
135
136// Drag model *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
137static const int DRAG_MODEL_SCREEN_DELTA = 0; // Drag relative to x coordinate of motion vector
138static const int DRAG_MODEL_PLANE = 1; // Drag relative to projected point on plane of carousel
139static const int DRAG_MODEL_CYLINDER_INSIDE = 2; // Drag relative to point on inside of cylinder
140static const int DRAG_MODEL_CYLINDER_OUTSIDE = 3; // Drag relative to point on outside of cylinder
141
142// Constants
143static const int ANIMATION_DELAY_TIME = 100; // hold off scale animation until this time
144static const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
145static const float3 SELECTED_SCALE_FACTOR = { 0.0f, 0.0f, 0.0f }; // increase by this %
146static const float OVERSCROLL_SLOTS = 1.0f; // amount of allowed overscroll (in slots)
147
148// Debug flags
149const bool debugCamera = false; // dumps ray/camera coordinate stuff
150const bool debugSelection = false; // logs selection events
151const bool debugTextureLoading = false; // for debugging texture load/unload
152const bool debugGeometryLoading = false; // for debugging geometry load/unload
153const bool debugDetails = false; // for debugging detail texture geometry
154const bool debugRendering = false; // flashes display when the frame changes
155const bool debugRays = false; // shows visual depiction of hit tests, See renderWithRays().
156
157// Exported variables. These will be reflected to Java set_* variables.
158Card_t *cards; // array of cards to draw
159float startAngle; // position of initial card, in radians
160int slotCount; // number of positions where a card can be
161int cardCount; // number of cards in stack
162int programStoresCardCount; // number of program fragment stores
163int visibleSlotCount; // number of visible slots (for culling)
164int visibleDetailCount; // number of visible detail textures to show
165int prefetchCardCount; // how many cards to keep in memory
166int detailTextureAlignment; // How to align detail texture with respect to card
167bool drawRuler; // whether to draw a ruler from the card to the detail texture
168float radius; // carousel radius. Cards will be centered on a circle with this radius
169float cardRotation; // rotation of card in XY plane relative to Z=1
170bool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle
171float swaySensitivity; // how much to rotate cards in relation to the rotation velocity
172float frictionCoeff; // how much to slow down the carousel over time
173float dragFactor; // a scale factor for how sensitive the carousel is to user dragging
174int fadeInDuration; // amount of time (in ms) for smoothly switching out textures
175int cardCreationFadeDuration; // amount of time (in ms) to fade while initially showing a card
176float rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
177float detailFadeRate; // rate at which details fade as they move into the distance
178float4 backgroundColor;
179int rowCount;  // number of rows of cards in a given slot, default 1
180float rowSpacing;  // spacing between rows of cards
181bool firstCardTop; // set true for first card on top row when multiple rows used
182
183int dragModel = DRAG_MODEL_SCREEN_DELTA;
184int fillDirection; // the order in which to lay out cards: +1 for CCW (default), -1 for CW
185ProgramStore_t *programStoresCard;
186rs_program_store programStoreBackground;
187rs_program_store programStoreDetail;
188rs_program_fragment singleTextureFragmentProgram;
189rs_program_fragment singleTextureBlendingFragmentProgram;
190rs_program_fragment multiTextureFragmentProgram;
191rs_program_fragment multiTextureBlendingFragmentProgram;
192rs_program_vertex vertexProgram;
193rs_program_raster rasterProgram;
194rs_allocation defaultTexture; // shown when no other texture is assigned
195rs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
196rs_allocation backgroundTexture; // drawn behind everything, if set
197rs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
198rs_allocation detailLoadingTexture; // used when detail texture is loading
199rs_mesh defaultGeometry; // shown when no geometry is loaded
200rs_mesh loadingGeometry; // shown when geometry is loading
201rs_matrix4x4 defaultCardMatrix;
202rs_matrix4x4 projectionMatrix;
203rs_matrix4x4 modelviewMatrix;
204FragmentShaderConstants* shaderConstants;
205rs_sampler linearClamp;
206
207// Local variables
208static float bias; // rotation bias, in radians. Used for animation and dragging.
209static bool updateCamera;    // force a recompute of projection and lookat matrices
210static const float FLT_MAX = 1.0e37;
211static int animatedSelection = -1;
212static int currentFirstCard = -1;
213static int64_t touchTime = -1;  // time of first touch (see doStart())
214static float touchBias = 0.0f; // bias on first touch
215static float2 touchPosition; // position of first touch, as defined by last call to doStart(x,y)
216static float velocity = 0.0f;  // angular velocity in radians/s
217static bool overscroll = false; // whether we're in the overscroll animation
218static bool isDragging = false; // true while the user is dragging the carousel
219static float selectionRadius = 50.0f; // movement greater than this will result in no selection
220static bool enableSelection = false; // enabled until the user drags outside of selectionRadius
221
222// Default plane of the carousel. Used for angular motion estimation in view.
223static Plane carouselPlane = {
224       { 0.0f, 0.0f, 0.0f }, // point
225       { 0.0f, 1.0f, 0.0f }, // normal
226       0.0f // plane constant (= -dot(P, N))
227};
228
229static Cylinder carouselCylinder = {
230        {0.0f, 0.0f, 0.0f }, // center
231        1.0f // radius - update with carousel radius.
232};
233
234// Because allocations can't have 0 dimensions, we have to track whether or not
235// cards and program stores are valid separately.
236// TODO: Remove this dependency once allocations can have a zero dimension.
237static bool cardAllocationValid = false;
238static bool programStoresAllocationValid = false;
239
240// Default geometry when card.geometry is not set.
241static const float3 cardVertices[4] = {
242        { -1.0, -1.0, 0.0 },
243        { 1.0, -1.0, 0.0 },
244        { 1.0, 1.0, 0.0 },
245        {-1.0, 1.0, 0.0 }
246};
247
248// Default camera
249static PerspectiveCamera camera = {
250        {2,2,2}, // from
251        {0,0,0}, // at
252        {0,1,0}, // up
253        25.0f,   // field of view
254        1.0f,    // aspect
255        0.1f,    // near
256        100.0f   // far
257};
258
259// Forward references
260static int intersectGeometry(Ray* ray, float *bestTime);
261static int intersectDetailTexture(float x, float y, float2 *tapCoordinates);
262static bool __attribute__((overloadable))
263        makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
264static bool __attribute__((overloadable))
265        makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y);
266static float deltaTimeInSeconds(int64_t current);
267static bool rayPlaneIntersect(Ray* ray, Plane* plane, float* tout);
268static bool rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout);
269
270void init() {
271    // initializers currently have a problem when the variables are exported, so initialize
272    // globals here.
273    if (debugTextureLoading) rsDebug("Renderscript: init()", 0);
274    startAngle = 0.0f;
275    slotCount = 10;
276    visibleSlotCount = 1;
277    visibleDetailCount = 3;
278    bias = 0.0f;
279    radius = carouselCylinder.radius = 1.0f;
280    cardRotation = 0.0f;
281    cardsFaceTangent = false;
282    updateCamera = true;
283    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
284    cardAllocationValid = false;
285    programStoresAllocationValid = false;
286    cardCount = 0;
287    rowCount = 1;
288    rowSpacing = 0.0f;
289    firstCardTop = false;
290    fadeInDuration = 250;
291    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
292    detailFadeRate = 0.5f; // fade details over this many slot positions.
293    rsMatrixLoadIdentity(&defaultCardMatrix);
294}
295
296static void updateAllocationVars()
297{
298    // Cards
299    rs_allocation cardAlloc;
300    rsSetObject(&cardAlloc, rsGetAllocation(cards));
301    cardCount = (cardAllocationValid && rsIsObject(cardAlloc)) ? rsAllocationGetDimX(cardAlloc) : 0;
302
303    // Program stores
304    rs_allocation psAlloc;
305    rsSetObject(&psAlloc, rsGetAllocation(programStoresCard));
306    programStoresCardCount = (programStoresAllocationValid && rsIsObject(psAlloc) ?
307        rsAllocationGetDimX(psAlloc) : 0);
308}
309
310void setRadius(float rad)
311{
312    radius = carouselCylinder.radius = rad;
313}
314
315static void initCard(Card_t* card)
316{
317    // Object refs are always initilized cleared.
318    static const float2 zero = {0.0f, 0.0f};
319    card->detailTextureOffset = zero;
320    card->detailLineOffset = zero;
321    rsMatrixLoad(&card->matrix, &defaultCardMatrix);
322    card->textureState = STATE_INVALID;
323    card->detailTextureState = STATE_INVALID;
324    card->geometryState = STATE_INVALID;
325    card->cardVisible = false;
326    card->detailVisible = false;
327    card->textureTimeStamp = 0;
328    card->detailTextureTimeStamp = 0;
329    card->geometryTimeStamp = rsUptimeMillis();
330}
331
332void createCards(int start, int total)
333{
334    if (!cardAllocationValid) {
335        // If the allocation is invalid, it contains a single place-holder
336        // card that has not yet been initialized (see CarouselRS.createCards).
337        // Here we ensure that it is initialized when growing the total.
338        start = 0;
339    }
340    for (int k = start; k < total; k++) {
341        initCard(cards + k);
342    }
343
344    // Since allocations can't have 0-size, we track validity ourselves based on the call to
345    // this method.
346    cardAllocationValid = total > 0;
347
348    updateAllocationVars(cards);
349}
350
351// Computes an alpha value for a card using elapsed time and constant fadeInDuration
352static float getAnimatedAlpha(int64_t startTime, int64_t currentTime, int64_t duration)
353{
354    double timeElapsed = (double) (currentTime - startTime); // in ms
355    double alpha = duration > 0 ? (double) timeElapsed / duration : 1.0;
356    return min(1.0f, (float) alpha);
357}
358
359// Returns total angle for given number of slots
360static float wedgeAngle(float slots)
361{
362    return slots * 2.0f * M_PI / slotCount;
363}
364
365// Return angle of slot in position p.
366static float slotPosition(int p)
367{
368    return startAngle + wedgeAngle(p) * fillDirection;
369}
370
371// Return angle for card in position p.
372static float cardPosition(int p)
373{
374    return bias + slotPosition(p / rowCount);
375}
376
377// Return the lowest possible bias value, based on the fill direction
378static float minimumBias()
379{
380    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
381    return (fillDirection > 0) ?
382        -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)) :
383        wedgeAngle(0.0f);
384}
385
386// Return the highest possible bias value, based on the fill direction
387static float maximumBias()
388{
389    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
390    return (fillDirection > 0) ?
391        wedgeAngle(0.0f) :
392        max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
393}
394
395
396// convert from carousel rotation angle (in card slot units) to radians.
397static float carouselRotationAngleToRadians(float carouselRotationAngle)
398{
399    return -wedgeAngle(carouselRotationAngle);
400}
401
402// convert from radians to carousel rotation angle (in card slot units).
403static float radiansToCarouselRotationAngle(float angle)
404{
405    return -angle * slotCount / ( 2.0f * M_PI );
406}
407
408// Set basic camera properties:
409//    from - position of the camera in x,y,z
410//    at - target we're looking at - used to compute view direction
411//    up - a normalized vector indicating up (typically { 0, 1, 0})
412//
413// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
414void lookAt(float fromX, float fromY, float fromZ,
415        float atX, float atY, float atZ,
416        float upX, float upY, float upZ)
417{
418    camera.from.x = fromX;
419    camera.from.y = fromY;
420    camera.from.z = fromZ;
421    camera.at.x = atX;
422    camera.at.y = atY;
423    camera.at.z = atZ;
424    camera.up.x = upX;
425    camera.up.y = upY;
426    camera.up.z = upZ;
427    updateCamera = true;
428}
429
430// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
431static void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
432{
433    rsMatrixLoadIdentity(matrix);
434    float top = near * tan((float) (fovy * M_PI / 360.0f));
435    float bottom = -top;
436    float left = bottom * aspect;
437    float right = top * aspect;
438    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
439}
440
441// Construct a matrix based on eye point, center and up direction. Based on the
442// man page for gluLookat(). Up must be normalized.
443static void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
444{
445    float3 f = normalize(center - eye);
446    float3 s = normalize(cross(f, up));
447    float3 u = cross(s, f);
448    float m[16];
449    m[0] = s.x;
450    m[4] = s.y;
451    m[8] = s.z;
452    m[12] = 0.0f;
453    m[1] = u.x;
454    m[5] = u.y;
455    m[9] = u.z;
456    m[13] = 0.0f;
457    m[2] = -f.x;
458    m[6] = -f.y;
459    m[10] = -f.z;
460    m[14] = 0.0f;
461    m[3] = m[7] = m[11] = 0.0f;
462    m[15] = 1.0f;
463    rsMatrixLoad(matrix, m);
464    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
465}
466
467/*
468 * Returns true if a state represents a texture that is loaded enough to draw
469 */
470static bool textureEverLoaded(int state) {
471    return (state == STATE_LOADED) || (state == STATE_STALE) || (state == STATE_UPDATING);
472}
473
474void setTexture(int n, rs_allocation texture)
475{
476    if (n < 0 || n >= cardCount) return;
477    rsSetObject(&cards[n].texture, texture);
478    if (cards[n].textureState != STATE_STALE &&
479        cards[n].textureState != STATE_UPDATING) {
480        cards[n].textureTimeStamp = rsUptimeMillis();
481    }
482    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
483}
484
485void setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
486{
487    if (n < 0 || n >= cardCount) return;
488    rsSetObject(&cards[n].detailTexture, texture);
489    if (cards[n].detailTextureState != STATE_STALE &&
490        cards[n].detailTextureState != STATE_UPDATING) {
491        cards[n].detailTextureTimeStamp = rsUptimeMillis();
492    }
493    cards[n].detailTextureOffset.x = offx;
494    cards[n].detailTextureOffset.y = offy;
495    cards[n].detailLineOffset.x = loffx;
496    cards[n].detailLineOffset.y = loffy;
497    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
498}
499
500void invalidateTexture(int n, bool eraseCurrent)
501{
502    if (n < 0 || n >= cardCount) return;
503    if (eraseCurrent) {
504        cards[n].textureState = STATE_INVALID;
505        rsClearObject(&cards[n].texture);
506    } else {
507        cards[n].textureState =
508            textureEverLoaded(cards[n].textureState) ? STATE_STALE : STATE_INVALID;
509    }
510}
511
512void invalidateDetailTexture(int n, bool eraseCurrent)
513{
514    if (n < 0 || n >= cardCount) return;
515    if (eraseCurrent) {
516        cards[n].detailTextureState = STATE_INVALID;
517        rsClearObject(&cards[n].detailTexture);
518    } else {
519        cards[n].detailTextureState =
520            textureEverLoaded(cards[n].detailTextureState) ? STATE_STALE : STATE_INVALID;
521    }
522}
523
524void setGeometry(int n, rs_mesh geometry)
525{
526    if (n < 0 || n >= cardCount) return;
527    rsSetObject(&cards[n].geometry, geometry);
528    if (cards[n].geometry.p != 0)
529        cards[n].geometryState = STATE_LOADED;
530    else
531        cards[n].geometryState = STATE_INVALID;
532    cards[n].geometryTimeStamp = rsUptimeMillis();
533}
534
535void setMatrix(int n, rs_matrix4x4 matrix) {
536    if (n < 0 || n >= cardCount) return;
537    cards[n].matrix = matrix;
538}
539
540void setProgramStoresCard(int n, rs_program_store programStore)
541{
542    rsSetObject(&programStoresCard[n].programStore, programStore);
543    programStoresAllocationValid = true;
544}
545
546void setCarouselRotationAngle(float carouselRotationAngle) {
547    bias = carouselRotationAngleToRadians(carouselRotationAngle);
548}
549
550// Gets animated scale value for current selected card.
551// If card is currently being animated, returns true,  otherwise returns false.
552static bool getAnimatedScaleForSelected(float3* scale)
553{
554    const float3 one = { 1.0f, 1.0f, 1.0f };
555    int64_t dt = rsUptimeMillis() - touchTime;
556    if (dt >= ANIMATION_DELAY_TIME) {
557        float fraction = (float) (dt - ANIMATION_DELAY_TIME) / ANIMATION_SCALE_TIME;
558        fraction = min(fraction, 1.0f);
559        *scale = one + fraction * SELECTED_SCALE_FACTOR;
560    } else {
561        *scale = one;
562    }
563    return dt < (ANIMATION_DELAY_TIME + ANIMATION_SCALE_TIME); // still animating;
564}
565
566// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
567//    P(t) = 1 / (1 + e^(-t))
568// Parameter t: Any real number
569// Returns: A float in the range (0,1), with P(0.5)=0
570static float logistic(float t) {
571    return 1.f / (1.f + exp(-t));
572}
573
574static float getSwayAngleForVelocity(float v, bool enableSway)
575{
576    float sway = 0.0f;
577
578    if (enableSway) {
579        const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak
580        sway = range * (logistic(-v * swaySensitivity) - 0.5f);
581    }
582
583    return sway;
584}
585
586// Returns the vertical offset for a card in its slot,
587// depending on the number of rows configured.
588static float getVerticalOffsetForCard(int i) {
589   if (rowCount == 1) {
590       // fast path
591       return 0;
592   }
593   const float cardHeight = (cardVertices[3].y - cardVertices[0].y) *
594      rsMatrixGet(&defaultCardMatrix, 1, 1);
595   const float totalHeight = rowCount * (cardHeight + rowSpacing) - rowSpacing;
596   if (firstCardTop)
597      i = rowCount - (i % rowCount) - 1;
598   else
599      i = i % rowCount;
600   const float rowOffset = i * (cardHeight + rowSpacing);
601   return (cardHeight - totalHeight) / 2 + rowOffset;
602}
603
604/*
605 * Composes a matrix for the given card.
606 * matrix: The output matrix.
607 * i: The card we're getting the matrix for.
608 * enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
609 * enableCardMatrix: Whether to also consider the user-specified card matrix
610 *
611 * returns true if an animation is being applied to the given card
612 */
613static bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway, bool enableCardMatrix)
614{
615    float theta = cardPosition(i);
616    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
617    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
618    rsMatrixTranslate(matrix, radius, getVerticalOffsetForCard(i), 0);
619    float rotation = cardRotation + swayAngle;
620    if (!cardsFaceTangent) {
621      rotation -= theta;
622    }
623    rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0);
624    bool stillAnimating = false;
625    if (i == animatedSelection && enableSelection) {
626        float3 scale;
627        stillAnimating = getAnimatedScaleForSelected(&scale);
628        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
629    }
630    // TODO(jshuma): Instead of ignoring this matrix for the detail texture, use card bounding box
631    if (enableCardMatrix) {
632        rsMatrixLoadMultiply(matrix, matrix, &cards[i].matrix);
633    }
634    return stillAnimating;
635}
636
637/*
638 * Draws the requested mesh, with the appropriate program store in effect.
639 */
640static void drawMesh(rs_mesh mesh)
641{
642    if (programStoresCardCount == 1) {
643        // Draw the entire mesh, with the only available program store
644        rsgBindProgramStore(programStoresCard[0].programStore);
645        rsgDrawMesh(mesh);
646    } else {
647        // Draw each primitive in the mesh with the corresponding program store
648        for (int i=0; i<programStoresCardCount; ++i) {
649            if (programStoresCard[i].programStore.p != 0) {
650                rsgBindProgramStore(programStoresCard[i].programStore);
651                rsgDrawMesh(mesh, i);
652            }
653        }
654    }
655}
656
657/*
658 * Draws cards around the Carousel.
659 * Returns true if we're still animating any property of the cards (e.g. fades).
660 */
661static bool drawCards(int64_t currentTime)
662{
663    const float wedgeAngle = 2.0f * M_PI / slotCount;
664    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
665    bool stillAnimating = false;
666    for (int i = cardCount-1; i >= 0; i--) {
667        if (cards[i].cardVisible) {
668            // If this card was recently loaded, this will be < 1.0f until the animation completes
669            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime,
670                fadeInDuration);
671            float overallAlpha = getAnimatedAlpha(cards[i].geometryTimeStamp, currentTime,
672                cardCreationFadeDuration);
673            if (animatedAlpha < 1.0f || overallAlpha < 1.0f) {
674                stillAnimating = true;
675            }
676
677            // Compute fade out for cards in the distance
678            float positionAlpha;
679            if (rezInCardCount > 0.0f) {
680                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
681                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
682            } else {
683                positionAlpha = 1.0f;
684            }
685
686            // Set alpha for blending between the textures
687            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
688            shaderConstants->overallAlpha = overallAlpha;
689            rsgAllocationSyncAll(rsGetAllocation(shaderConstants));
690
691            // Bind the appropriate shader network.  If there's no alpha blend, then
692            // switch to single shader for better performance.
693            const int state = cards[i].textureState;
694            bool loaded = textureEverLoaded(state) && rsIsObject(cards[i].texture);
695            if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
696                if (overallAlpha < 1.0) {
697                    rsgBindProgramFragment(singleTextureBlendingFragmentProgram);
698                    rsgBindTexture(singleTextureBlendingFragmentProgram, 0,
699                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
700                            cards[i].texture : loadingTexture);
701                } else {
702                    rsgBindProgramFragment(singleTextureFragmentProgram);
703                    rsgBindTexture(singleTextureFragmentProgram, 0,
704                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
705                            cards[i].texture : loadingTexture);
706                }
707            } else {
708                if (overallAlpha < 1.0) {
709                    rsgBindProgramFragment(multiTextureBlendingFragmentProgram);
710                    rsgBindTexture(multiTextureBlendingFragmentProgram, 0, loadingTexture);
711                    rsgBindTexture(multiTextureBlendingFragmentProgram, 1, loaded ?
712                            cards[i].texture : loadingTexture);
713                } else {
714                    rsgBindProgramFragment(multiTextureFragmentProgram);
715                    rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
716                    rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
717                            cards[i].texture : loadingTexture);
718                }
719            }
720
721            // Draw geometry
722            rs_matrix4x4 matrix = modelviewMatrix;
723            stillAnimating |= getMatrixForCard(&matrix, i, true, true);
724            rsgProgramVertexLoadModelMatrix(&matrix);
725            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
726                drawMesh(cards[i].geometry);
727            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
728                drawMesh(loadingGeometry);
729            } else if (defaultGeometry.p != 0) {
730                drawMesh(defaultGeometry);
731            } else {
732                // Draw place-holder geometry
733                rsgBindProgramStore(programStoresCard[0].programStore);
734                rsgDrawQuad(
735                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
736                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
737                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
738                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
739            }
740        }
741    }
742    return stillAnimating;
743}
744
745/**
746 * Convert projection from normalized coordinates to pixel coordinates.
747 *
748 * @return True on success, false on failure.
749 */
750static bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) {
751    // This is probably cheaper than pre-multiplying with another matrix.
752    if (screenCoord->w == 0.0f) {
753        rsDebug("Bad transform while converting from normalized to pixel coordinates: ",
754            screenCoord);
755        return false;
756    }
757    *screenCoord *= 1.0f / screenCoord->w;
758    screenCoord->x += 1.0f;
759    screenCoord->y += 1.0f;
760    screenCoord->z += 1.0f;
761    screenCoord->x = round(screenCoord->x * 0.5f * width);
762    screenCoord->y = round(screenCoord->y * 0.5f * height);
763    screenCoord->z = - 0.5f * screenCoord->z;
764    return true;
765}
766
767/*
768 * Draws a screen-aligned card with the exact dimensions from the detail texture.
769 * This is used to display information about the object being displayed.
770 * Returns true if we're still animating any property of the cards (e.g. fades).
771 */
772static bool drawDetails(int64_t currentTime)
773{
774    const float width = rsgGetWidth();
775    const float height = rsgGetHeight();
776
777    bool stillAnimating = false;
778
779    // We'll be drawing in screen space, sampled on pixel centers
780    rs_matrix4x4 projection, model;
781    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
782    rsgProgramVertexLoadProjectionMatrix(&projection);
783    rsMatrixLoadIdentity(&model);
784    rsgProgramVertexLoadModelMatrix(&model);
785    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
786
787    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
788
789    // This can be done once...
790    rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture);
791
792    const float wedgeAngle = 2.0f * M_PI / slotCount;
793    // Angle where details start fading from 1.0f
794    const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle;
795    // Angle where detail alpha is 0.0f
796    const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
797
798    for (int i = cardCount-1; i >= 0; --i) {
799        if (cards[i].cardVisible) {
800            const int state = cards[i].detailTextureState;
801            const bool isLoaded = textureEverLoaded(state);
802            if (isLoaded && cards[i].detailTexture.p != 0) {
803                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
804
805                // Compute position in screen space of top corner or bottom corner of card
806                rsMatrixLoad(&model, &modelviewMatrix);
807                stillAnimating |= getMatrixForCard(&model, i, false, false);
808                rs_matrix4x4 matrix;
809                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
810
811                int indexLeft, indexRight;
812                float4 screenCoord;
813                if (detailTextureAlignment & BELOW) {
814                    indexLeft = 0;
815                    indexRight = 1;
816                } else {
817                    indexLeft = 3;
818                    indexRight = 2;
819                }
820                float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
821                float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
822                if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) {
823                    // this shouldn't happen
824                    rsDebug("Bad transform: ", screenCoord);
825                    continue;
826                }
827                if (detailTextureAlignment & CENTER_VERTICAL) {
828                    // If we're centering vertically, we'll need the other vertices too
829                    if (detailTextureAlignment & BELOW) {
830                        indexLeft = 3;
831                        indexRight = 2;
832                    } else {
833                        indexLeft = 0;
834                        indexRight = 1;
835                    }
836                    float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
837                    float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
838                    screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y
839                        + otherScreenLeft.y + otherScreenRight.y) / 4.;
840                }
841                (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
842                (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
843                if (debugDetails) {
844                    RS_DEBUG(screenCoordLeft);
845                    RS_DEBUG(screenCoordRight);
846                }
847                screenCoord = screenCoordLeft;
848                if (detailTextureAlignment & BELOW) {
849                    screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
850                } else if (detailTextureAlignment & CENTER_VERTICAL) {
851                    screenCoord.y -= round(rsAllocationGetDimY(cards[i].detailTexture) / 2.0f);
852                }
853                if (detailTextureAlignment & CENTER_HORIZONTAL) {
854                    screenCoord.x += round((screenCoordRight.x - screenCoordLeft.x) / 2.0f -
855                        rsAllocationGetDimX(cards[i].detailTexture) / 2.0f);
856                }
857
858                // Compute alpha for gradually fading in details. Applied to both line and
859                // detail texture. TODO: use a separate background texture for line.
860                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp,
861                    currentTime, fadeInDuration);
862                if (animatedAlpha < 1.0f) {
863                    stillAnimating = true;
864                }
865
866                // Compute alpha based on position. We fade cards quickly so they cannot overlap
867                float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i))
868                        / (endDetailFadeAngle - startDetailFadeAngle);
869                positionAlpha = max(0.0f, positionAlpha);
870                positionAlpha = min(1.0f, positionAlpha);
871
872                const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
873
874                if (blendedAlpha == 0.0f) {
875                    cards[i].detailVisible = false;
876                    continue; // nothing to draw
877                } else {
878                    cards[i].detailVisible = true;
879                }
880                if (blendedAlpha == 1.0f) {
881                    rsgBindProgramFragment(singleTextureFragmentProgram);
882                } else {
883                    rsgBindProgramFragment(multiTextureFragmentProgram);
884                }
885
886                // Set alpha for blending between the textures
887                shaderConstants->fadeAmount = blendedAlpha;
888                rsgAllocationSyncAll(rsGetAllocation(shaderConstants));
889
890                // Draw line from the card to the detail texture.
891                // The line is drawn from the top or bottom left of the card
892                // to either the top of the screen or the top of the detail
893                // texture, depending on detailTextureAlignment.
894                if (drawRuler) {
895                    float rulerTop;
896                    float rulerBottom;
897                    if (detailTextureAlignment & BELOW) {
898                        rulerTop = screenCoord.y;
899                        rulerBottom = 0;
900                    } else {
901                        rulerTop = height;
902                        rulerBottom = screenCoord.y;
903                    }
904                    const float halfWidth = lineWidth * 0.5f;
905                    const float x0 = trunc(cards[i].detailLineOffset.x + screenCoord.x - halfWidth);
906                    const float x1 = x0 + lineWidth;
907                    const float y0 = rulerBottom + yPadding;
908                    const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y;
909
910                    if (blendedAlpha == 1.0f) {
911                        rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture);
912                    } else {
913                        rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture);
914                    }
915                    rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
916                            x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
917                }
918
919                // Draw the detail texture next to it using the offsets provided.
920                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
921                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
922                const float offx = cards[i].detailTextureOffset.x;
923                const float offy = -cards[i].detailTextureOffset.y;
924                const float textureTop = (detailTextureAlignment & VIEW_TOP)
925                        ? height : screenCoord.y;
926                const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
927                const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
928                const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
929                const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
930                cards[i].detailTexturePosition[0].x = x0;
931                cards[i].detailTexturePosition[0].y = height - y1;
932                cards[i].detailTexturePosition[1].x = x1;
933                cards[i].detailTexturePosition[1].y = height - y0;
934
935                if (blendedAlpha == 1.0f) {
936                    rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
937                } else {
938                    rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture);
939                }
940                rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
941                        x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
942            }
943        }
944    }
945    return stillAnimating;
946}
947
948static void drawBackground()
949{
950    static bool toggle;
951    if (backgroundTexture.p != 0) {
952        rsgClearDepth(1.0f);
953        rs_matrix4x4 projection, model;
954        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
955        rsgProgramVertexLoadProjectionMatrix(&projection);
956        rsMatrixLoadIdentity(&model);
957        rsgProgramVertexLoadModelMatrix(&model);
958        rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture);
959        float z = -0.9999f;
960        rsgDrawQuad(
961            cardVertices[0].x, cardVertices[0].y, z,
962            cardVertices[1].x, cardVertices[1].y, z,
963            cardVertices[2].x, cardVertices[2].y, z,
964            cardVertices[3].x, cardVertices[3].y, z);
965        updateCamera = true; // we mucked with the matrix.
966    } else {
967        rsgClearDepth(1.0f);
968        if (debugRendering) { // for debugging - flash the screen so we know we're still rendering
969            rsgClearColor(toggle ? backgroundColor.x : 1.0f,
970                        toggle ? backgroundColor.y : 0.0f,
971                        toggle ? backgroundColor.z : 0.0f,
972                        backgroundColor.w);
973            toggle = !toggle;
974        } else {
975           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
976                   backgroundColor.w);
977       }
978    }
979}
980
981static void updateCameraMatrix(float width, float height)
982{
983    float aspect = width / height;
984    if (aspect != camera.aspect || updateCamera) {
985        camera.aspect = aspect;
986        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
987        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
988
989        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
990        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
991        updateCamera = false;
992    }
993}
994
995////////////////////////////////////////////////////////////////////////////////////////////////////
996// Behavior/Physics
997////////////////////////////////////////////////////////////////////////////////////////////////////
998static int64_t lastTime = 0L; // keep track of how much time has passed between frames
999static float lastAngle = 0.0f;
1000static float2 lastPosition;
1001static bool animating = false;
1002static float stopVelocity = 0.1f * M_PI / 180.0f; // slower than this: carousel stops
1003static float selectionVelocity = 15.0f * M_PI / 180.0f; // faster than this: tap won't select
1004static float velocityTracker;
1005static int velocityTrackerCount;
1006static float mass = 5.0f; // kg
1007
1008static const float G = 9.80f; // gravity constant, in m/s
1009static const float springConstant = 0.0f;
1010
1011// Computes a hit angle from the center of the carousel to a point on either a plane
1012// or on a cylinder. If neither is hit, returns false.
1013static bool hitAngle(float x, float y, float *angle)
1014{
1015    Ray ray;
1016    makeRayForPixelAt(&ray, &camera, x, y);
1017    float t = FLT_MAX;
1018    if (dragModel == DRAG_MODEL_PLANE && rayPlaneIntersect(&ray, &carouselPlane, &t)) {
1019        const float3 point = (ray.position + t*ray.direction);
1020        const float3 direction = point - carouselPlane.point;
1021        *angle = atan2(direction.x, direction.z);
1022        if (debugSelection) rsDebug("Plane Angle = ", degrees(*angle));
1023        return true;
1024    } else if ((dragModel == DRAG_MODEL_CYLINDER_INSIDE || dragModel == DRAG_MODEL_CYLINDER_OUTSIDE)
1025            && rayCylinderIntersect(&ray, &carouselCylinder, &t)) {
1026        const float3 point = (ray.position + t*ray.direction);
1027        const float3 direction = point - carouselCylinder.center;
1028        *angle = atan2(direction.x, direction.z);
1029        if (debugSelection) rsDebug("Cylinder Angle = ", degrees(*angle));
1030        return true;
1031    }
1032    return false;
1033}
1034
1035static float dragFunction(float x, float y)
1036{
1037    float result;
1038    float angle;
1039    if (hitAngle(x, y, &angle)) {
1040        result = angle - lastAngle;
1041        // Handle singularity where atan2 switches between +- PI
1042        if (result < -M_PI) {
1043            result += 2.0f * M_PI;
1044        } else if (result > M_PI) {
1045            result -= 2.0f * M_PI;
1046        }
1047        lastAngle = angle;
1048    } else {
1049        // If we didn't hit anything or drag model wasn't plane or cylinder, we use screen delta
1050        result = dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
1051    }
1052    return result;
1053}
1054
1055static float deltaTimeInSeconds(int64_t current)
1056{
1057    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
1058}
1059
1060static int doSelection(float x, float y)
1061{
1062    Ray ray;
1063    if (makeRayForPixelAt(&ray, &camera, x, y)) {
1064        float bestTime = FLT_MAX;
1065        return intersectGeometry(&ray, &bestTime);
1066    }
1067    return -1;
1068}
1069
1070static void sendAnimationStarted() {
1071    rsSendToClient(CMD_ANIMATION_STARTED);
1072}
1073
1074static void sendAnimationFinished() {
1075    float data[1];
1076    data[0] = radiansToCarouselRotationAngle(bias);
1077    rsSendToClient(CMD_ANIMATION_FINISHED, (int*) data, sizeof(data));
1078}
1079
1080void doStart(float x, float y, long eventTime)
1081{
1082    touchPosition = lastPosition = (float2) { x, y };
1083    lastAngle = hitAngle(x,y, &lastAngle) ? lastAngle : 0.0f;
1084    enableSelection = fabs(velocity) < selectionVelocity;
1085    velocity = 0.0f;
1086    velocityTracker = 0.0f;
1087    velocityTrackerCount = 0;
1088    touchTime = lastTime = eventTime;
1089    touchBias = bias;
1090    isDragging = true;
1091    overscroll = false;
1092    animatedSelection = doSelection(x, y); // used to provide visual feedback on touch
1093}
1094
1095void doStop(float x, float y, long eventTime)
1096{
1097    updateAllocationVars(cards);
1098
1099    if (enableSelection) {
1100        int data[3];
1101        int selection;
1102        float2 point;
1103
1104        if ((selection = intersectDetailTexture(x, y, &point)) != -1) {
1105            if (debugSelection) rsDebug("Selected detail texture on doStop():", selection);
1106            data[0] = selection;
1107            data[1] = point.x;
1108            data[2] = point.y;
1109            rsSendToClientBlocking(CMD_DETAIL_SELECTED, data, sizeof(data));
1110        }
1111        else if ((selection = doSelection(x, y))!= -1) {
1112            if (debugSelection) rsDebug("Selected item on doStop():", selection);
1113            data[0] = selection;
1114            rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
1115        }
1116        animating = false;
1117    } else {
1118        // TODO: move velocity tracking to Java
1119        velocity = velocityTrackerCount > 0 ?
1120                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
1121        if (fabs(velocity) > stopVelocity) {
1122            animating = true;
1123        }
1124    }
1125    enableSelection = false;
1126    lastTime = eventTime;
1127    isDragging = false;
1128}
1129
1130void doLongPress()
1131{
1132    int64_t currentTime = rsUptimeMillis();
1133    updateAllocationVars(cards);
1134    // Selection happens for most recent position detected in doMotion()
1135    if (enableSelection && animatedSelection != -1) {
1136        if (debugSelection) rsDebug("doLongPress(), selection = ", animatedSelection);
1137        int data[7];
1138        data[0] = animatedSelection;
1139        data[1] = lastPosition.x;
1140        data[2] = lastPosition.y;
1141        data[3] = cards[animatedSelection].detailTexturePosition[0].x;
1142        data[4] = cards[animatedSelection].detailTexturePosition[0].y;
1143        data[5] = cards[animatedSelection].detailTexturePosition[1].x;
1144        data[6] = cards[animatedSelection].detailTexturePosition[1].y;
1145        rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data));
1146        enableSelection = false;
1147    }
1148    lastTime = rsUptimeMillis();
1149}
1150
1151void doMotion(float x, float y, long eventTime)
1152{
1153    const float highBias = maximumBias();
1154    const float lowBias = minimumBias();
1155    float deltaOmega = dragFunction(x, y);
1156    if (!enableSelection) {
1157        bias += deltaOmega;
1158        bias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
1159                highBias + wedgeAngle(OVERSCROLL_SLOTS));
1160    }
1161    const float2 delta = (float2) { x, y } - touchPosition;
1162    float distance = sqrt(dot(delta, delta));
1163    bool inside = (distance < selectionRadius);
1164    enableSelection &= inside;
1165    lastPosition = (float2) { x, y };
1166    float dt = deltaTimeInSeconds(eventTime);
1167    if (dt > 0.0f) {
1168        float v = deltaOmega / dt;
1169        velocityTracker += v;
1170        velocityTrackerCount++;
1171    }
1172    velocity = velocityTrackerCount > 0 ?
1173                (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
1174    lastTime = eventTime;
1175}
1176
1177////////////////////////////////////////////////////////////////////////////////////////////////////
1178// Hit detection using ray casting.
1179////////////////////////////////////////////////////////////////////////////////////////////////////
1180static const float EPSILON = 1.0e-6f;
1181static const float tmin = 0.0f;
1182
1183static bool
1184rayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float* tout)
1185{
1186    float3 e1 = p1 - p0;
1187    float3 e2 = p2 - p0;
1188    float3 s1 = cross(ray->direction, e2);
1189
1190    float div = dot(s1, e1);
1191    if (div == 0.0f) return false;  // ray is parallel to plane.
1192
1193    float3 d = ray->position - p0;
1194    float invDiv = 1.0f / div;
1195
1196    float u = dot(d, s1) * invDiv;
1197    if (u < 0.0f || u > 1.0f) return false;
1198
1199    float3 s2 = cross(d, e1);
1200    float v = dot(ray->direction, s2) * invDiv;
1201    if ( v < 0.0f || (u+v) > 1.0f) return false;
1202
1203    float t = dot(e2, s2) * invDiv;
1204    if (t < tmin || t > *tout)
1205        return false;
1206    *tout = t;
1207    return true;
1208}
1209
1210////////////////////////////////////////////////////////////////////////////////////////////////////
1211// Computes ray/plane intersection. Returns false if no intersection found.
1212////////////////////////////////////////////////////////////////////////////////////////////////////
1213static bool
1214rayPlaneIntersect(Ray* ray, Plane* plane, float* tout)
1215{
1216    float denom = dot(ray->direction, plane->normal);
1217    if (fabs(denom) > EPSILON) {
1218        float t = - (plane->constant + dot(ray->position, plane->normal)) / denom;
1219        if (t > tmin && t < *tout) {
1220            *tout = t;
1221            return true;
1222        }
1223    }
1224    return false;
1225}
1226
1227////////////////////////////////////////////////////////////////////////////////////////////////////
1228// Computes ray/cylindr intersection. There are 0, 1 or 2 hits.
1229// Returns true and sets *tout to the closest point or
1230// returns false if no intersection found.
1231////////////////////////////////////////////////////////////////////////////////////////////////////
1232static bool
1233rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout)
1234{
1235    const float A = ray->direction.x * ray->direction.x + ray->direction.z * ray->direction.z;
1236    if (A < EPSILON) return false; // ray misses
1237
1238    // Compute quadratic equation coefficients
1239    const float B = 2.0f * (ray->direction.x * ray->position.x
1240            + ray->direction.z * ray->position.z);
1241    const float C = ray->position.x * ray->position.x
1242            + ray->position.z * ray->position.z
1243            - cylinder->radius * cylinder->radius;
1244    float disc = B*B - 4*A*C;
1245
1246    if (disc < 0.0f) return false; // ray misses
1247    disc = sqrt(disc);
1248    const float denom = 2.0f * A;
1249
1250    // Nearest point
1251    const float t1 = (-B - disc) / denom;
1252    if (dragModel == DRAG_MODEL_CYLINDER_OUTSIDE && t1 > tmin && t1 < *tout) {
1253        *tout = t1;
1254        return true;
1255    }
1256
1257    // Far point
1258    const float t2 = (-B + disc) / denom;
1259    if (dragModel == DRAG_MODEL_CYLINDER_INSIDE && t2 > tmin && t2 < *tout) {
1260        *tout = t2;
1261        return true;
1262    }
1263    return false;
1264}
1265
1266// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates.
1267// Note that the Y coordinate is opposite of GL rendering coordinates.
1268static bool __attribute__((overloadable))
1269makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y)
1270{
1271    if (debugCamera) {
1272        rsDebug("------ makeRay() -------", 0);
1273        rsDebug("Camera.from:", cam->from);
1274        rsDebug("Camera.at:", cam->at);
1275        rsDebug("Camera.dir:", normalize(cam->at - cam->from));
1276    }
1277
1278    // Vector math.  This has the potential to be much faster.
1279    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
1280    const float u = x / rsgGetWidth();
1281    const float v = 1.0f - (y / rsgGetHeight());
1282    const float aspect = (float) rsgGetWidth() / rsgGetHeight();
1283    const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f));
1284    float3 dir = normalize(cam->at - cam->from);
1285    float3 du = tanfov2 * normalize(cross(dir, cam->up));
1286    float3 dv = tanfov2 * normalize(cross(du, dir));
1287    du *= aspect;
1288    float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
1289    const float3 rayPoint = cam->from;
1290    const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
1291    if (debugCamera) {
1292        rsDebug("Ray direction (vector math) = ", rayDir);
1293    }
1294
1295    ray->position =  rayPoint;
1296    ray->direction = rayDir;
1297    return true;
1298}
1299
1300// Creates a ray for an Android pixel coordinate given a model view and projection matrix.
1301// Note that the Y coordinate is opposite of GL rendering coordinates.
1302static bool __attribute__((overloadable))
1303makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y)
1304{
1305    rs_matrix4x4 pm = *model;
1306    rsMatrixLoadMultiply(&pm, proj, model);
1307    if (!rsMatrixInverse(&pm)) {
1308        rsDebug("ERROR: SINGULAR PM MATRIX", 0);
1309        return false;
1310    }
1311    const float width = rsgGetWidth();
1312    const float height = rsgGetHeight();
1313    const float winx = 2.0f * x / width - 1.0f;
1314    const float winy = 2.0f * y / height - 1.0f;
1315
1316    float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
1317    float4 at = { winx, winy, 1.0f, 1.0f };
1318
1319    eye = rsMatrixMultiply(&pm, eye);
1320    eye *= 1.0f / eye.w;
1321
1322    at = rsMatrixMultiply(&pm, at);
1323    at *= 1.0f / at.w;
1324
1325    const float3 rayPoint = { eye.x, eye.y, eye.z };
1326    const float3 atPoint = { at.x, at.y, at.z };
1327    const float3 rayDir = normalize(atPoint - rayPoint);
1328    if (debugCamera) {
1329        rsDebug("winx: ", winx);
1330        rsDebug("winy: ", winy);
1331        rsDebug("Ray position (transformed) = ", eye);
1332        rsDebug("Ray direction (transformed) = ", rayDir);
1333    }
1334    ray->position =  rayPoint;
1335    ray->direction = rayDir;
1336    return true;
1337}
1338
1339static int intersectDetailTexture(float x, float y, float2 *tapCoordinates)
1340{
1341    for (int id = 0; id < cardCount; id++) {
1342        if (cards[id].detailVisible) {
1343            const int x0 = cards[id].detailTexturePosition[0].x;
1344            const int y0 = cards[id].detailTexturePosition[0].y;
1345            const int x1 = cards[id].detailTexturePosition[1].x;
1346            const int y1 = cards[id].detailTexturePosition[1].y;
1347            if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
1348                float2 point = { x - x0, y - y0 };
1349                *tapCoordinates = point;
1350                return id;
1351            }
1352        }
1353    }
1354    return -1;
1355}
1356
1357static int intersectGeometry(Ray* ray, float *bestTime)
1358{
1359    int hit = -1;
1360    for (int id = 0; id < cardCount; id++) {
1361        if (cards[id].cardVisible) {
1362            rs_matrix4x4 matrix;
1363            float3 p[4];
1364
1365            // Transform card vertices to world space
1366            rsMatrixLoadIdentity(&matrix);
1367            getMatrixForCard(&matrix, id, true, true);
1368            for (int vertex = 0; vertex < 4; vertex++) {
1369                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
1370                if (tmp.w != 0.0f) {
1371                    p[vertex].x = tmp.x;
1372                    p[vertex].y = tmp.y;
1373                    p[vertex].z = tmp.z;
1374                    p[vertex] *= 1.0f / tmp.w;
1375                } else {
1376                    rsDebug("Bad w coord: ", tmp);
1377                }
1378            }
1379
1380            // Intersect card geometry
1381            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
1382                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
1383                hit = id;
1384            }
1385        }
1386    }
1387    return hit;
1388}
1389
1390// This method computes the position of all the cards by updating bias based on a
1391// simple physics model.  If the cards are still in motion, returns true.
1392static bool doPhysics(float dt)
1393{
1394    const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
1395    const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
1396    dt /= N;
1397    for (int i = 0; i < N; i++) {
1398        // Force friction - always opposes motion
1399        const float Ff = -frictionCoeff * velocity;
1400
1401        // Restoring force to match cards with slots
1402        const float theta = startAngle + bias;
1403        const float dtheta = 2.0f * M_PI / slotCount;
1404        const float position = theta / dtheta;
1405        const float fraction = position - floor(position); // fractional position between slots
1406        float x;
1407        if (fraction > 0.5f) {
1408            x = - (1.0f - fraction);
1409        } else {
1410            x = fraction;
1411        }
1412        const float Fr = - springConstant * x;
1413
1414        // compute velocity
1415        const float momentum = mass * velocity + (Ff + Fr)*dt;
1416        velocity = momentum / mass;
1417        bias += velocity * dt;
1418    }
1419    return fabs(velocity) > stopVelocity;
1420}
1421
1422static float easeOut(float x)
1423{
1424    return x;
1425}
1426
1427// Computes the next value for bias using the current animation (physics or overscroll)
1428static bool updateNextPosition(int64_t currentTime)
1429{
1430    static const float biasMin = 1e-4f; // close enough if we're within this margin of result
1431
1432    float dt = deltaTimeInSeconds(currentTime);
1433
1434    if (dt <= 0.0f) {
1435        if (debugRendering) rsDebug("Time delta was <= 0", dt);
1436        return true;
1437    }
1438
1439    const float highBias = maximumBias();
1440    const float lowBias = minimumBias();
1441    bool stillAnimating = false;
1442    if (overscroll) {
1443        if (bias > highBias) {
1444            bias -= 4.0f * dt * easeOut((bias - highBias) * 2.0f);
1445            if (fabs(bias - highBias) < biasMin) {
1446                bias = highBias;
1447            } else {
1448                stillAnimating = true;
1449            }
1450        } else if (bias < lowBias) {
1451            bias += 4.0f * dt * easeOut((lowBias - bias) * 2.0f);
1452            if (fabs(bias - lowBias) < biasMin) {
1453                bias = lowBias;
1454            } else {
1455                stillAnimating = true;
1456            }
1457        } else {
1458            overscroll = false;
1459        }
1460    } else {
1461        stillAnimating = doPhysics(dt);
1462        overscroll = bias > highBias || bias < lowBias;
1463        if (overscroll) {
1464            velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
1465        }
1466    }
1467    float newbias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
1468            highBias + wedgeAngle(OVERSCROLL_SLOTS));
1469    if (newbias != bias) { // we clamped
1470        velocity = 0.0f;
1471        overscroll = true;
1472    }
1473    bias = newbias;
1474    return stillAnimating;
1475}
1476
1477// Cull cards based on visibility and visibleSlotCount.
1478// If visibleSlotCount is > 0, then only show those slots and cull the rest.
1479// Otherwise, it should cull based on bounds of geometry.
1480static int cullCards()
1481{
1482    // TODO(jshuma): Instead of fully fetching prefetchCardCount cards, make a distinction between
1483    // STATE_LOADED and a new STATE_PRELOADING, which will keep the textures loaded but will not
1484    // attempt to actually draw them.
1485    const int prefetchCardCountPerSide = prefetchCardCount / 2;
1486    const float thetaFirst = slotPosition(-prefetchCardCountPerSide);
1487    const float thetaSelected = slotPosition(0);
1488    const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f;
1489    const float thetaSelectedLow = thetaSelected - thetaHalfAngle;
1490    const float thetaSelectedHigh = thetaSelected + thetaHalfAngle;
1491    const float thetaLast = slotPosition(visibleSlotCount - 1 + prefetchCardCountPerSide);
1492
1493    int count = 0;
1494    for (int i = 0; i < cardCount; i++) {
1495        if (visibleSlotCount > 0) {
1496            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
1497            float p = cardPosition(i);
1498            if (p >= thetaFirst && p < thetaLast || p <= thetaFirst && p > thetaLast) {
1499                cards[i].cardVisible = true;
1500                // cards[i].detailVisible will be set at draw time
1501                count++;
1502            } else {
1503                cards[i].cardVisible = false;
1504                cards[i].detailVisible = false;
1505            }
1506        } else {
1507            // Cull the rest of the cards using bounding box of geometry.
1508            // TODO
1509            cards[i].cardVisible = true;
1510            // cards[i].detailVisible will be set at draw time
1511            count++;
1512        }
1513    }
1514    return count;
1515}
1516
1517// Request texture/geometry for items that have come into view
1518// or doesn't have a texture yet.
1519static void updateCardResources(int64_t currentTime)
1520{
1521    for (int i = cardCount-1; i >= 0; --i) {
1522        int data[1];
1523        if (cards[i].cardVisible) {
1524            if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp);
1525
1526            // request texture from client if not loaded
1527            if (cards[i].textureState == STATE_INVALID) {
1528                data[0] = i;
1529                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
1530                if (enqueued) {
1531                    cards[i].textureState = STATE_LOADING;
1532                } else {
1533                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
1534                }
1535            } else if (cards[i].textureState == STATE_STALE) {
1536                data[0] = i;
1537                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
1538                if (enqueued) {
1539                    cards[i].textureState = STATE_UPDATING;
1540                } else {
1541                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
1542                }
1543            }
1544            // request detail texture from client if not loaded
1545            if (cards[i].detailTextureState == STATE_INVALID) {
1546                data[0] = i;
1547                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
1548                if (enqueued) {
1549                    cards[i].detailTextureState = STATE_LOADING;
1550                } else {
1551                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
1552                }
1553            } else if (cards[i].detailTextureState == STATE_STALE) {
1554                data[0] = i;
1555                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
1556                if (enqueued) {
1557                    cards[i].detailTextureState = STATE_UPDATING;
1558                } else {
1559                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
1560                }
1561            }
1562            // request geometry from client if not loaded
1563            if (cards[i].geometryState == STATE_INVALID) {
1564                data[0] = i;
1565                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
1566                if (enqueued) {
1567                    cards[i].geometryState = STATE_LOADING;
1568                } else {
1569                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
1570                }
1571            }
1572        } else {
1573            // ask the host to remove the texture
1574            if (cards[i].textureState != STATE_INVALID) {
1575                data[0] = i;
1576                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
1577                if (enqueued) {
1578                    cards[i].textureState = STATE_INVALID;
1579                    cards[i].textureTimeStamp = currentTime;
1580                } else {
1581                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
1582                }
1583            }
1584            // ask the host to remove the detail texture
1585            if (cards[i].detailTextureState != STATE_INVALID) {
1586                data[0] = i;
1587                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
1588                if (enqueued) {
1589                    cards[i].detailTextureState = STATE_INVALID;
1590                    cards[i].detailTextureTimeStamp = currentTime;
1591                } else {
1592                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
1593                }
1594            }
1595            // ask the host to remove the geometry
1596            if (cards[i].geometryState != STATE_INVALID) {
1597                data[0] = i;
1598                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
1599                if (enqueued) {
1600                    cards[i].geometryState = STATE_INVALID;
1601                } else {
1602                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
1603                }
1604            }
1605        }
1606    }
1607}
1608
1609// Places dots on geometry to visually inspect that objects can be seen by rays.
1610// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
1611// card.
1612static void renderWithRays()
1613{
1614    const float w = rsgGetWidth();
1615    const float h = rsgGetHeight();
1616    const int skip = 8;
1617
1618    rsgProgramFragmentConstantColor(singleTextureFragmentProgram, 1.0f, 0.0f, 0.0f, 1.0f);
1619    for (int j = 0; j < (int) h; j+=skip) {
1620        float posY = (float) j;
1621        for (int i = 0; i < (int) w; i+=skip) {
1622            float posX = (float) i;
1623            Ray ray;
1624            if (makeRayForPixelAt(&ray, &camera, posX, posY)) {
1625                float bestTime = FLT_MAX;
1626                if (intersectGeometry(&ray, &bestTime) != -1) {
1627                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
1628                }
1629            }
1630        }
1631    }
1632}
1633
1634int root() {
1635    int64_t currentTime = rsUptimeMillis();
1636
1637    rsgBindProgramVertex(vertexProgram);
1638    rsgBindProgramRaster(rasterProgram);
1639    rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp);
1640    rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp);
1641    rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp);
1642
1643    updateAllocationVars(cards);
1644
1645    rsgBindProgramFragment(singleTextureFragmentProgram);
1646    // rsgClearDepth() currently follows the value of glDepthMask(), so it's disabled when
1647    // the mask is disabled. We may want to change the following to always draw w/o Z for
1648    // the background if we can guarantee the depth buffer will get cleared and
1649    // there's a performance advantage.
1650    rsgBindProgramStore(programStoreBackground);
1651    drawBackground();
1652
1653    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
1654
1655    bool stillAnimating = (currentTime - touchTime) <= ANIMATION_SCALE_TIME;
1656
1657    if (!isDragging && animating) {
1658        stillAnimating = updateNextPosition(currentTime);
1659    }
1660
1661    lastTime = currentTime;
1662
1663    cullCards();
1664
1665    updateCardResources(currentTime);
1666
1667    // Draw cards opaque only if requested, and always draw detail textures with blending.
1668    stillAnimating |= drawCards(currentTime);
1669    rsgBindProgramStore(programStoreDetail);
1670    stillAnimating |= drawDetails(currentTime);
1671
1672    if (stillAnimating != animating) {
1673        if (stillAnimating) {
1674            // we just started animating
1675            sendAnimationStarted();
1676        } else {
1677            // we were animating but stopped animating just now
1678            sendAnimationFinished();
1679        }
1680        animating = stillAnimating;
1681    }
1682
1683    if (debugRays) {
1684        renderWithRays();
1685    }
1686
1687    //rsSendToClient(CMD_PING);
1688
1689    return animating ? 1 : 0;
1690}
1691