15ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller/*
25ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Copyright (C) 2010 The Android Open Source Project
35ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
45ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Licensed under the Apache License, Version 2.0 (the "License");
55ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * you may not use this file except in compliance with the License.
65ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * You may obtain a copy of the License at
75ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
85ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *      http://www.apache.org/licenses/LICENSE-2.0
95ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Unless required by applicable law or agreed to in writing, software
115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * distributed under the License is distributed on an "AS IS" BASIS,
125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * See the License for the specific language governing permissions and
145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * limitations under the License.
155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller */
165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma version(1)
185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs java_package_name(com.android.ex.carousel);
195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs set_reflect_license()
205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#include "rs_graphics.rsh"
225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct __attribute__((aligned(4))) Card {
246f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // *** Update initCard if you add/remove fields here.
257cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation texture; // basic card texture
267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation detailTexture; // screen-aligned detail texture
277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    float2 detailTextureOffset; // offset to add, in screen coordinates
28b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float2 detailLineOffset; // offset to add to detail line, in screen coordinates
298fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    float2 detailTexturePosition[2]; // screen coordinates of detail texture, computed at draw time
305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_mesh geometry;
317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 matrix; // custom transform for this card/geometry
327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int textureState;  // whether or not the primary card texture is loaded.
337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int detailTextureState; // whether or not the detail for the card is loaded.
345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int geometryState; // whether or not geometry is loaded
358fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    int cardVisible; // not bool because of packing bug?
368fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    int detailVisible; // not bool because of packing bug?
375ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    int shouldPrefetch; // not bool because of packing bug?
383adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t textureTimeStamp; // time when this texture was last updated, in ms
393adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t detailTextureTimeStamp; // time when this texture was last updated, in ms
403adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t geometryTimeStamp; // time when the card itself was last updated, in ms
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Plane_s {
4943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 point;
5043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 normal;
5143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float constant;
5243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Plane;
5343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
5443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Cylinder_s {
5543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 center; // center of a y-axis-aligned infinite cylinder
5643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float radius;
5743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Cylinder;
5843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
69f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumatypedef struct ProgramStore_s {
70f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rs_program_store programStore;
71f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma} ProgramStore_t;
72f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
73420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millertypedef struct FragmentShaderConstants_s {
74420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    float fadeAmount;
753adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    float overallAlpha;
76420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller} FragmentShaderConstants;
77420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
836af401bca5f8854524d128e9df5700035fae1160Jim Shuma    STATE_STALE, // we have an old item, but should request an update
846af401bca5f8854524d128e9df5700035fae1160Jim Shuma    STATE_UPDATING, // we've requested an update, and will display the old one in the meantime
855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
888debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry// Interpolation modes ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
898debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berryenum {
908debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    INTERPOLATION_LINEAR = 0,
918debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    INTERPOLATION_DECELERATE_QUADRATIC = 1,
928debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    INTERPOLATION_ACCELERATE_DECELERATE_CUBIC = 2,
938debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry};
948debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
954a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyenum {
974a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is centered vertically with respect to the card **/
984a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_VERTICAL = 1,
994a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the top edge of the carousel view **/
1004a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_TOP = 1 << 1,
1014a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
1024a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_BOTTOM = 1 << 2,
1034a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned above the card (not yet implemented) **/
1044a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    ABOVE = 1 << 3,
1054a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned below the card **/
1064a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    BELOW = 1 << 4,
1074a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control vertical alignment **/
1084a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VERTICAL_ALIGNMENT_MASK = 0xff,
1094a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
1104a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is centered horizontally with respect to either the top or bottom
1124a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * extent of the card, depending on whether the detail is above or below the card.
1134a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1144a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_HORIZONTAL = 1 << 8,
1154a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1164a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the left edge of either the top or the bottom of
1174a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1184a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1194a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    LEFT = 1 << 9,
1204a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1214a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the right edge of either the top or the bottom of
1224a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1234a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * (not yet implemented)
1244a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1254a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    RIGHT = 1 << 10,
1264a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control horizontal alignment **/
1274a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    HORIZONTAL_ALIGNMENT_MASK = 0xff00,
1284a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney};
1294a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
1305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
1315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
1328fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic const int CMD_DETAIL_SELECTED = 105;
133594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumastatic const int CMD_CARD_LONGPRESS = 110;
1345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
1355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
1365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
1375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
1385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
1395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
1407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REQUEST_DETAIL_TEXTURE = 600;
1417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
1427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_PING = 1000;
1435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
144be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// Drag model *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
145be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_SCREEN_DELTA = 0; // Drag relative to x coordinate of motion vector
146be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_PLANE = 1; // Drag relative to projected point on plane of carousel
147be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_CYLINDER_INSIDE = 2; // Drag relative to point on inside of cylinder
148be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_CYLINDER_OUTSIDE = 3; // Drag relative to point on outside of cylinder
149be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
1505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
151ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic const int ANIMATION_DELAY_TIME = 125; // hold off scale animation until this time
152ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic const int ANIMATION_SCALE_UP_TIME = 200; // Time it takes to animate selected card, in ms
1532cf7d5b78744e0d95951ddd631ca11904296ba7cJim Millerstatic const int ANIMATION_SCALE_DOWN_TIME = 200; // Time it takes to animate selected card, in ms
154ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.1f, 0.1f, 0.1f }; // increase by this %
15555b237bcd720774e27248f5fecf6c32a3f420a4cJim Millerstatic const int VELOCITY_HISTORY_MAX = 10; // # recent velocity samples used to calculate average
156b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinneystatic const int VISIBLE_SLOT_PADDING = 2;  // # slots to draw on either side of visible slots
1575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1585dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney// Constants affecting tilt overscroll.  Some of these should be parameters.
1595dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic const int TILT_SLOT_NUMBER = 5;
1605dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic const float TILT_MIN_ANGLE = M_PI / 315.0f;
1615dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic const float TILT_MAX_BIAS = M_PI / 8.0f;
1625dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic const float TILT_MAX_ANGLE = M_PI / 8.0f;
1635dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic const float MAX_DELTA_BIAS = 0.008f;
1645dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
1655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
1667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugCamera = false; // dumps ray/camera coordinate stuff
16743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugSelection = false; // logs selection events
1687cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugTextureLoading = false; // for debugging texture load/unload
1697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugGeometryLoading = false; // for debugging geometry load/unload
1707c09ccce478100d75e4427d87866ff19d758ae7aJim Shumaconst bool debugDetails = false; // for debugging detail texture geometry
171b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerconst bool debugRendering = false; // flashes display when the frame changes
17243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugRays = false; // shows visual depiction of hit tests, See renderWithRays().
1735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
1755ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
1765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
1775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
1785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
179f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumaint programStoresCardCount; // number of program fragment stores
1805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
1817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerint visibleDetailCount; // number of visible detail textures to show
1824fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumaint prefetchCardCount; // how many cards to keep in memory
1834a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyint detailTextureAlignment; // How to align detail texture with respect to card
1847c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawRuler; // whether to draw a ruler from the card to the detail texture
1855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
1865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
18783d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinneybool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle
188c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat swaySensitivity; // how much to rotate cards in relation to the rotation velocity
189c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat frictionCoeff; // how much to slow down the carousel over time
190c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat dragFactor; // a scale factor for how sensitive the carousel is to user dragging
191420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerint fadeInDuration; // amount of time (in ms) for smoothly switching out textures
1923adf712e636f67265da7a6ff425c87e63fc20884Jim Shumaint cardCreationFadeDuration; // amount of time (in ms) to fade while initially showing a card
193420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
194a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerfloat detailFadeRate; // rate at which details fade as they move into the distance
19543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerfloat4 backgroundColor;
1960cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneyint rowCount;  // number of rows of cards in a given slot, default 1
1970cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneyfloat rowSpacing;  // spacing between rows of cards
1981a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilsonbool firstCardTop; // set true for first card on top row when multiple rows used
1999afded4d212243e554c2695c4a2f90c13628e24bBryan Mawhinneyfloat overscrollSlots; // amount of allowed overscroll (in slots)
2000cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney
201be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerint dragModel = DRAG_MODEL_SCREEN_DELTA;
20214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumaint fillDirection; // the order in which to lay out cards: +1 for CCW (default), -1 for CW
203f664659f79399e92025e1dfe1ffbb682ff05613cJim ShumaProgramStore_t *programStoresCard;
204f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumars_program_store programStoreBackground;
205f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumars_program_store programStoreDetail;
206a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment singleTextureFragmentProgram;
2073adf712e636f67265da7a6ff425c87e63fc20884Jim Shumars_program_fragment singleTextureBlendingFragmentProgram;
208a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment multiTextureFragmentProgram;
2093adf712e636f67265da7a6ff425c87e63fc20884Jim Shumars_program_fragment multiTextureBlendingFragmentProgram;
2105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
2115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
2125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
2135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
2149afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerrs_allocation backgroundTexture; // drawn behind everything, if set
2157cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerrs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
216420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_allocation detailLoadingTexture; // used when detail texture is loading
2175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
2185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
21951dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinneyrs_matrix4x4 defaultCardMatrix;
2205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
2215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
222420b44b8b11ec1c309ea130e69a6876325dbfef9Jim MillerFragmentShaderConstants* shaderConstants;
223420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_sampler linearClamp;
2245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
2265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
2275dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic float overscrollBias; // Track overscroll bias separately for tilt effect.
2285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
2295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
23043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int animatedSelection = -1;
23143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int currentFirstCard = -1;
232ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic int64_t touchTime = -1; // time of first touch (see doStart())
233ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic int64_t releaseTime = 0L; // when touch was released
2345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
23543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float2 touchPosition; // position of first touch, as defined by last call to doStart(x,y)
236c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float velocity = 0.0f;  // angular velocity in radians/s
237ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic bool isOverScrolling = false; // whether we're in the overscroll animation
238ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Millerstatic bool isAutoScrolling = false; // whether we're in the autoscroll animation
23943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool isDragging = false; // true while the user is dragging the carousel
24043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float selectionRadius = 50.0f; // movement greater than this will result in no selection
24143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool enableSelection = false; // enabled until the user drags outside of selectionRadius
2425dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic float tiltAngle = 0.0f;
24343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
24443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Default plane of the carousel. Used for angular motion estimation in view.
24543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic Plane carouselPlane = {
24643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 0.0f, 0.0f }, // point
24743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 1.0f, 0.0f }, // normal
24843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       0.0f // plane constant (= -dot(P, N))
24943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller};
2505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
251be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic Cylinder carouselCylinder = {
252be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        {0.0f, 0.0f, 0.0f }, // center
253be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        1.0f // radius - update with carousel radius.
254be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller};
255be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
2563df59346f395434454d310b070fff195089fbaf1Jim Miller// Because allocations can't have 0 dimensions, we have to track whether or not
257f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma// cards and program stores are valid separately.
2583df59346f395434454d310b070fff195089fbaf1Jim Miller// TODO: Remove this dependency once allocations can have a zero dimension.
2593df59346f395434454d310b070fff195089fbaf1Jim Millerstatic bool cardAllocationValid = false;
260f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic bool programStoresAllocationValid = false;
2613df59346f395434454d310b070fff195089fbaf1Jim Miller
2625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
2635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
2645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
2655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
2665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
2675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
2685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
2715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
2725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
2735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
2745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
2755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
2765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
2775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
2785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
2795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
2825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
2838fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic int intersectDetailTexture(float x, float y, float2 *tapCoordinates);
284b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
285b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
286b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
287b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y);
2885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
289be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool rayPlaneIntersect(Ray* ray, Plane* plane, float* tout);
290be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout);
2918debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic void stopAutoscroll();
2925dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic bool tiltOverscroll();
2935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
2955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
2965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
2972ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) rsDebug("Renderscript: init()", 0);
2985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
2995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
3005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
3017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    visibleDetailCount = 3;
3025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
3035dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    overscrollBias = 0.0f;
3045dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    tiltAngle = 0.0f;
305be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    radius = carouselCylinder.radius = 1.0f;
3065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
30783d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    cardsFaceTangent = false;
3085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
309b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
3103df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = false;
311f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresAllocationValid = false;
3123df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = 0;
3130cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rowCount = 1;
3140cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rowSpacing = 0.0f;
3151a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson    firstCardTop = false;
316420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    fadeInDuration = 250;
317420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
318a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    detailFadeRate = 0.5f; // fade details over this many slot positions.
31951dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinney    rsMatrixLoadIdentity(&defaultCardMatrix);
3205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
322f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic void updateAllocationVars()
3235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
325ed5cdfa293ec57cb14b98cdc3fa00ac5ec1c1ed4Stephen Hines    rs_allocation cardAlloc;
32655392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    cardAlloc = rsGetAllocation(cards);
327dcfb45adbcf37de68920c181322aaa9e4e4b58d8Stephen Hines    cardCount = (cardAllocationValid && rsIsObject(cardAlloc)) ? rsAllocationGetDimX(cardAlloc) : 0;
328f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
329f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    // Program stores
330f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rs_allocation psAlloc;
33155392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    psAlloc = rsGetAllocation(programStoresCard);
332f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresCardCount = (programStoresAllocationValid && rsIsObject(psAlloc) ?
333f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsAllocationGetDimX(psAlloc) : 0);
3345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
336be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millervoid setRadius(float rad)
337be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller{
338be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    radius = carouselCylinder.radius = rad;
339be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller}
340be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
341370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void initCard(Card_t* card)
3428b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
3436f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // Object refs are always initilized cleared.
3442ba04e061b52c488a154739379501dc833e39f79Jim Miller    static const float2 zero = {0.0f, 0.0f};
3452ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureOffset = zero;
3462ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailLineOffset = zero;
34751dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinney    rsMatrixLoad(&card->matrix, &defaultCardMatrix);
3482ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureState = STATE_INVALID;
3492ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureState = STATE_INVALID;
3502ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->geometryState = STATE_INVALID;
3518fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    card->cardVisible = false;
3528fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    card->detailVisible = false;
3535ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    card->shouldPrefetch = false;
3542ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureTimeStamp = 0;
3552ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureTimeStamp = 0;
3563adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    card->geometryTimeStamp = rsUptimeMillis();
3572ba04e061b52c488a154739379501dc833e39f79Jim Miller}
3582ba04e061b52c488a154739379501dc833e39f79Jim Miller
3596f2cc8cf611860467315ecc542f71a225625eb1cJason Samsvoid createCards(int start, int total)
3602ba04e061b52c488a154739379501dc833e39f79Jim Miller{
3615b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney    if (!cardAllocationValid) {
3625b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // If the allocation is invalid, it contains a single place-holder
3635b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // card that has not yet been initialized (see CarouselRS.createCards).
3645b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // Here we ensure that it is initialized when growing the total.
3655b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        start = 0;
3665b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney    }
3676f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    for (int k = start; k < total; k++) {
3686f2cc8cf611860467315ecc542f71a225625eb1cJason Sams        initCard(cards + k);
3692ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
3702ba04e061b52c488a154739379501dc833e39f79Jim Miller
3716f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // Since allocations can't have 0-size, we track validity ourselves based on the call to
3726f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // this method.
3736f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    cardAllocationValid = total > 0;
3742ba04e061b52c488a154739379501dc833e39f79Jim Miller
375ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    updateAllocationVars();
3765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
378420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller// Computes an alpha value for a card using elapsed time and constant fadeInDuration
3793adf712e636f67265da7a6ff425c87e63fc20884Jim Shumastatic float getAnimatedAlpha(int64_t startTime, int64_t currentTime, int64_t duration)
380420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller{
381420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double timeElapsed = (double) (currentTime - startTime); // in ms
3823adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    double alpha = duration > 0 ? (double) timeElapsed / duration : 1.0;
383420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return min(1.0f, (float) alpha);
384420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller}
385420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
3860cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Returns total angle for given number of slots
3870cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float wedgeAngle(float slots)
3885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3890cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    return slots * 2.0f * M_PI / slotCount;
3905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3920cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Return angle of slot in position p.
3930cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float slotPosition(int p)
3945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
39514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return startAngle + wedgeAngle(p) * fillDirection;
3965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3980cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Return angle for card in position p.
3990cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float cardPosition(int p)
400f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller{
4010cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    return bias + slotPosition(p / rowCount);
402f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller}
403f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller
40414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma// Return the lowest possible bias value, based on the fill direction
40514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumastatic float minimumBias()
40614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma{
40714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
40814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return (fillDirection > 0) ?
40914d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)) :
41014d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        wedgeAngle(0.0f);
41114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma}
41214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
41314d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma// Return the highest possible bias value, based on the fill direction
41414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumastatic float maximumBias()
41514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma{
41614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
41714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return (fillDirection > 0) ?
41814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        wedgeAngle(0.0f) :
41914d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
42014d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma}
42114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
42214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
423a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from carousel rotation angle (in card slot units) to radians.
424a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float carouselRotationAngleToRadians(float carouselRotationAngle)
425a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
426a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -wedgeAngle(carouselRotationAngle);
427a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
428a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
429a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from radians to carousel rotation angle (in card slot units).
430a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float radiansToCarouselRotationAngle(float angle)
431a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
432a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -angle * slotCount / ( 2.0f * M_PI );
433a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
434a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
4365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
4375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
4385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
4395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
4405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
4415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
4425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
4435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
4445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
4465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
4475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
4485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
4495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
4505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
4515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
4525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
4535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
4545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
4555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
4585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
4595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
4615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
4625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
4635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
4645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
4655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
4665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
4695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
4705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
4715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
4735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
4745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
4755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
4765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
4775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
4785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
4795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
4805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
4815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
4825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
4835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
4845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
4855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
4865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
4875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
4885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
4895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
4905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
4915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
4925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4940de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma/*
4950de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma * Returns true if a state represents a texture that is loaded enough to draw
4960de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma */
4970de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shumastatic bool textureEverLoaded(int state) {
4980de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma    return (state == STATE_LOADED) || (state == STATE_STALE) || (state == STATE_UPDATING);
4990de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma}
5000de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma
5015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
5025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5033df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
50455392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    cards[n].texture = texture;
5058441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (cards[n].textureState != STATE_STALE &&
5068441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureState != STATE_UPDATING) {
5078441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureTimeStamp = rsUptimeMillis();
5088441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    }
5097cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
5107cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
5117cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
512b378af500b36226635b6343b1d5009ee9af44fc1Jim Millervoid setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
5137cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
5143df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
51555392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    cards[n].detailTexture = texture;
5166af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (cards[n].detailTextureState != STATE_STALE &&
5176af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureState != STATE_UPDATING) {
5186af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureTimeStamp = rsUptimeMillis();
5196af401bca5f8854524d128e9df5700035fae1160Jim Shuma    }
5207cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.x = offx;
5217cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.y = offy;
522b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.x = loffx;
523b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.y = loffy;
5247cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
5256af401bca5f8854524d128e9df5700035fae1160Jim Shuma}
5266af401bca5f8854524d128e9df5700035fae1160Jim Shuma
5278441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkeyvoid invalidateTexture(int n, bool eraseCurrent)
5288441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey{
5298441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (n < 0 || n >= cardCount) return;
5308441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (eraseCurrent) {
5318441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureState = STATE_INVALID;
5328441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        rsClearObject(&cards[n].texture);
5338441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    } else {
5340de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma        cards[n].textureState =
5350de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma            textureEverLoaded(cards[n].textureState) ? STATE_STALE : STATE_INVALID;
5368441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    }
5378441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey}
5388441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey
5396af401bca5f8854524d128e9df5700035fae1160Jim Shumavoid invalidateDetailTexture(int n, bool eraseCurrent)
5406af401bca5f8854524d128e9df5700035fae1160Jim Shuma{
5416af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (n < 0 || n >= cardCount) return;
5426af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (eraseCurrent) {
5436af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureState = STATE_INVALID;
5446af401bca5f8854524d128e9df5700035fae1160Jim Shuma        rsClearObject(&cards[n].detailTexture);
5456af401bca5f8854524d128e9df5700035fae1160Jim Shuma    } else {
5460de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma        cards[n].detailTextureState =
5470de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma            textureEverLoaded(cards[n].detailTextureState) ? STATE_STALE : STATE_INVALID;
5486af401bca5f8854524d128e9df5700035fae1160Jim Shuma    }
5495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
5525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5533df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
55455392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    cards[n].geometry = geometry;
5555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
5565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
5575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
5585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
5593adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    cards[n].geometryTimeStamp = rsUptimeMillis();
5605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
562c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shumavoid setMatrix(int n, rs_matrix4x4 matrix) {
563c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma    if (n < 0 || n >= cardCount) return;
564c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma    cards[n].matrix = matrix;
565c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma}
566c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma
567f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumavoid setProgramStoresCard(int n, rs_program_store programStore)
568f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma{
56955392759e4f1fd3b17799ef8bd75d959dcf3b0a7Stephen Hines    programStoresCard[n].programStore = programStore;
570f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresAllocationValid = true;
571f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma}
572f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
573a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichvoid setCarouselRotationAngle(float carouselRotationAngle) {
574a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    bias = carouselRotationAngleToRadians(carouselRotationAngle);
575198a060d650bc849ef0f25b597888fac9546803bJack Palevich}
576198a060d650bc849ef0f25b597888fac9546803bJack Palevich
5771eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller// Gets animated scale value for current selected card.
5781eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller// If card is currently being animated, returns true,  otherwise returns false.
5791eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Millerstatic bool getAnimatedScaleForSelected(float3* scale)
5805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
581ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    static const float3 one = { 1.0f, 1.0f, 1.0f };
582ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    static float fraction = 0.0f;
583ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    bool stillAnimating = false;
584ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    if (isDragging) {
585ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        // "scale up" animation
586ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        int64_t dt = rsUptimeMillis() - touchTime - ANIMATION_DELAY_TIME;
587ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        if (dt > 0L && enableSelection) {
588ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            float s = (float) dt / ANIMATION_SCALE_UP_TIME;
589ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            s = min(s, 1.0f);
590ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            fraction = max(s, fraction);
591ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        }
592ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        stillAnimating = dt < ANIMATION_SCALE_UP_TIME;
5931eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    } else {
594ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        // "scale down" animation
595ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        int64_t dt = rsUptimeMillis() - releaseTime;
596ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        if (dt < ANIMATION_SCALE_DOWN_TIME) {
597ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            float s = 1.0f - ((float) dt / ANIMATION_SCALE_DOWN_TIME);
598ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            fraction = min(s, fraction);
599ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            stillAnimating = true;
600ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        } else {
601ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller            fraction = 0.0f;
602ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        }
6031eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    }
604ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    *scale = one + fraction * SELECTED_SCALE_FACTOR;
605ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    return stillAnimating; // still animating;
6065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
608c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
609c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma//    P(t) = 1 / (1 + e^(-t))
610c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Parameter t: Any real number
611c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Returns: A float in the range (0,1), with P(0.5)=0
612c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float logistic(float t) {
613af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao    return 1.f / (1.f + exp(-t));
614c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
615c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
6167c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic float getSwayAngleForVelocity(float v, bool enableSway)
617c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma{
6187c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float sway = 0.0f;
6197c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma
6207c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    if (enableSway) {
621d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak
622d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        sway = range * (logistic(-v * swaySensitivity) - 0.5f);
6237c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    }
624c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
625c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    return sway;
626c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
627c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
6285dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneystatic float getCardTiltAngle(int i) {
6295dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    i /= rowCount;
6305dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    int totalSlots = (cardCount + rowCount - 1) / rowCount;
6315dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float tiltSlotNumber = TILT_SLOT_NUMBER;
6325dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float deltaTilt = tiltAngle / tiltSlotNumber;
6335dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float cardTiltAngle = 0;
6345dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    if (tiltAngle > 0 && i < tiltSlotNumber) {
6355dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        // Overscroll for the front cards.
6365dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        cardTiltAngle = deltaTilt * (tiltSlotNumber - i);
6375dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    } else if (tiltAngle < 0 && i > (totalSlots - tiltSlotNumber)) {
6385dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        cardTiltAngle = deltaTilt * (i - totalSlots + tiltSlotNumber + 1);
6395dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    }
6405dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    return cardTiltAngle;
6415dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney}
6425dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
6430cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Returns the vertical offset for a card in its slot,
6440cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// depending on the number of rows configured.
6450cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float getVerticalOffsetForCard(int i) {
6460cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   if (rowCount == 1) {
6470cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney       // fast path
6480cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney       return 0;
6490cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   }
650c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma   const float cardHeight = (cardVertices[3].y - cardVertices[0].y) *
6517d27aa4388936d7607407d25cc52d42e00f6567aJim Shuma      rsMatrixGet(&defaultCardMatrix, 1, 1);
6520cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   const float totalHeight = rowCount * (cardHeight + rowSpacing) - rowSpacing;
6531a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   if (firstCardTop)
6541a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson      i = rowCount - (i % rowCount) - 1;
6551a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   else
6561a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson      i = i % rowCount;
6571a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   const float rowOffset = i * (cardHeight + rowSpacing);
6580cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   return (cardHeight - totalHeight) / 2 + rowOffset;
6590cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney}
6600cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney
6611eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller/*
6621eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * Composes a matrix for the given card.
6631eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * matrix: The output matrix.
6641eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * i: The card we're getting the matrix for.
6651eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
666c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma * enableCardMatrix: Whether to also consider the user-specified card matrix
6671eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller *
6681eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * returns true if an animation is being applied to the given card
6691eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller */
670c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shumastatic bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway, bool enableCardMatrix)
6715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
6737c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
6745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
6750cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rsMatrixTranslate(matrix, radius, getVerticalOffsetForCard(i), 0);
6765dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float tiltAngle = getCardTiltAngle(i);
6775dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float rotation = cardRotation + swayAngle + tiltAngle;
67883d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    if (!cardsFaceTangent) {
67983d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney      rotation -= theta;
68083d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    }
68183d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0);
6821eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    bool stillAnimating = false;
683ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    if (i == animatedSelection) {
6841eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        float3 scale;
6851eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        stillAnimating = getAnimatedScaleForSelected(&scale);
6865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
6875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
688c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma    // TODO(jshuma): Instead of ignoring this matrix for the detail texture, use card bounding box
689c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma    if (enableCardMatrix) {
690c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma        rsMatrixLoadMultiply(matrix, matrix, &cards[i].matrix);
691c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma    }
6921eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    return stillAnimating;
6935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
695420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller/*
696f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma * Draws the requested mesh, with the appropriate program store in effect.
697f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma */
698f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic void drawMesh(rs_mesh mesh)
699f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma{
700f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    if (programStoresCardCount == 1) {
701f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        // Draw the entire mesh, with the only available program store
702f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsgBindProgramStore(programStoresCard[0].programStore);
703f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsgDrawMesh(mesh);
704f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    } else {
705f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        // Draw each primitive in the mesh with the corresponding program store
706f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        for (int i=0; i<programStoresCardCount; ++i) {
707f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma            if (programStoresCard[i].programStore.p != 0) {
708f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgBindProgramStore(programStoresCard[i].programStore);
709f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgDrawMesh(mesh, i);
710f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma            }
711f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        }
712f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    }
713f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma}
714f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
715f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma/*
716420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Draws cards around the Carousel.
717420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
718420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller */
719420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawCards(int64_t currentTime)
7205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
721420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
722420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
723420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
724420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    for (int i = cardCount-1; i >= 0; i--) {
7258fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
726420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // If this card was recently loaded, this will be < 1.0f until the animation completes
7273adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime,
7283adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                fadeInDuration);
7293adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            float overallAlpha = getAnimatedAlpha(cards[i].geometryTimeStamp, currentTime,
7303adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                cardCreationFadeDuration);
7313adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            if (animatedAlpha < 1.0f || overallAlpha < 1.0f) {
732420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                stillAnimating = true;
733420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
734420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
735420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Compute fade out for cards in the distance
736420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float positionAlpha;
737420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (rezInCardCount > 0.0f) {
738420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
739420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
740420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            } else {
741420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = 1.0f;
742420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
743420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
744420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Set alpha for blending between the textures
745420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
7463adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            shaderConstants->overallAlpha = overallAlpha;
7478a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6fJason Sams            rsgAllocationSyncAll(rsGetAllocation(shaderConstants));
748420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
749b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // Bind the appropriate shader network.  If there's no alpha blend, then
750b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // switch to single shader for better performance.
7518441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey            const int state = cards[i].textureState;
7520de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma            bool loaded = textureEverLoaded(state) && rsIsObject(cards[i].texture);
753b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
7543adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                if (overallAlpha < 1.0) {
7553adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(singleTextureBlendingFragmentProgram);
7563adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(singleTextureBlendingFragmentProgram, 0,
7573adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
7583adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
7593adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                } else {
7603adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(singleTextureFragmentProgram);
7613adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(singleTextureFragmentProgram, 0,
7623adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
7633adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
7643adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                }
7655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
7663adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                if (overallAlpha < 1.0) {
7673adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(multiTextureBlendingFragmentProgram);
7683adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureBlendingFragmentProgram, 0, loadingTexture);
7693adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureBlendingFragmentProgram, 1, loaded ?
7703adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
7713adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                } else {
7723adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(multiTextureFragmentProgram);
7733adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
7743adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
7753adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
7763adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                }
7775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
7805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
781c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma            stillAnimating |= getMatrixForCard(&matrix, i, true, true);
7825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
7835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
784f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(cards[i].geometry);
7855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
786f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(loadingGeometry);
7875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
788f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(defaultGeometry);
7895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
7905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
791f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgBindProgramStore(programStoresCard[0].programStore);
7925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
7935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
7945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
7955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
7965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
7975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
800420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
8015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8034fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma/**
8044fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * Convert projection from normalized coordinates to pixel coordinates.
8054fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma *
8064fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * @return True on success, false on failure.
8074fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma */
8084fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumastatic bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) {
8094fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // This is probably cheaper than pre-multiplying with another matrix.
8104fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    if (screenCoord->w == 0.0f) {
8114fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        rsDebug("Bad transform while converting from normalized to pixel coordinates: ",
8124fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma            screenCoord);
8134fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        return false;
8144fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    }
8154fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    *screenCoord *= 1.0f / screenCoord->w;
8164fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x += 1.0f;
8174fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y += 1.0f;
8184fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z += 1.0f;
8194fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x = round(screenCoord->x * 0.5f * width);
8204fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y = round(screenCoord->y * 0.5f * height);
8214fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z = - 0.5f * screenCoord->z;
8224fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    return true;
8234fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma}
8244fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma
8257cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller/*
8267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * Draws a screen-aligned card with the exact dimensions from the detail texture.
8274a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney * This is used to display information about the object being displayed.
828420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
8297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller */
830420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawDetails(int64_t currentTime)
8317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
8327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float width = rsgGetWidth();
8337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float height = rsgGetHeight();
8347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
835420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
836420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
8377cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // We'll be drawing in screen space, sampled on pixel centers
8387cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 projection, model;
8397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
8407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadProjectionMatrix(&projection);
8417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadIdentity(&model);
8427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadModelMatrix(&model);
8437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
8447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
8457cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
8467cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
847420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // This can be done once...
848a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture);
849a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
850a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
851a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where details start fading from 1.0f
852a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle;
853a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where detail alpha is 0.0f
854a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
855420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
856a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
8578fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
8586af401bca5f8854524d128e9df5700035fae1160Jim Shuma            const int state = cards[i].detailTextureState;
8590de20d1ebd3dc8e766f7f4f4dbc3f77dd7326e30Jim Shuma            const bool isLoaded = textureEverLoaded(state);
8606af401bca5f8854524d128e9df5700035fae1160Jim Shuma            if (isLoaded && cards[i].detailTexture.p != 0) {
8617cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
8627cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
8634fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                // Compute position in screen space of top corner or bottom corner of card
864af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao                rsMatrixLoad(&model, &modelviewMatrix);
865c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma                stillAnimating |= getMatrixForCard(&model, i, false, false);
8667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rs_matrix4x4 matrix;
8677cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
868d443c88da4c7cf1947c12b26f111cb899cc8afe4Jim Miller
8694fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                int indexLeft, indexRight;
8704fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoord;
8714a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
8724fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 0;
8734fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 1;
8744fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                } else {
8754fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 3;
8764fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 2;
8774fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
8784fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
8794fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
8804fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) {
8817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    // this shouldn't happen
8827cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    rsDebug("Bad transform: ", screenCoord);
8837cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    continue;
8847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
8854a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_VERTICAL) {
8864a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    // If we're centering vertically, we'll need the other vertices too
8874a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
8884a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 3;
8894a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 2;
8904a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
8914a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 0;
8924a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 1;
8934a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
8944a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
8954a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
8964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y
8974a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        + otherScreenLeft.y + otherScreenRight.y) / 4.;
8984a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                }
8994fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
9004fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
9014fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (debugDetails) {
9024fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordLeft);
9034fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordRight);
9044fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
9054fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                screenCoord = screenCoordLeft;
9064a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
9074fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
9084a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                } else if (detailTextureAlignment & CENTER_VERTICAL) {
909fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                    screenCoord.y -= round(rsAllocationGetDimY(cards[i].detailTexture) / 2.0f);
9104fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
9114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_HORIZONTAL) {
912fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                    screenCoord.x += round((screenCoordRight.x - screenCoordLeft.x) / 2.0f -
913fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                        rsAllocationGetDimX(cards[i].detailTexture) / 2.0f);
9144fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
9157cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
916420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Compute alpha for gradually fading in details. Applied to both line and
917420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // detail texture. TODO: use a separate background texture for line.
9183adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp,
9193adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    currentTime, fadeInDuration);
920420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                if (animatedAlpha < 1.0f) {
921420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    stillAnimating = true;
922420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                }
923420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
924a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                // Compute alpha based on position. We fade cards quickly so they cannot overlap
925a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i))
926a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                        / (endDetailFadeAngle - startDetailFadeAngle);
927a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = max(0.0f, positionAlpha);
928a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = min(1.0f, positionAlpha);
929a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
930a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
931a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
9328fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                if (blendedAlpha == 0.0f) {
9338fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    cards[i].detailVisible = false;
9348fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    continue; // nothing to draw
9358fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                } else {
9368fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    cards[i].detailVisible = true;
9378fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                }
938b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
939b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(singleTextureFragmentProgram);
940b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
941b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(multiTextureFragmentProgram);
942b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
943a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
944420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Set alpha for blending between the textures
945a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                shaderConstants->fadeAmount = blendedAlpha;
9468a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6fJason Sams                rsgAllocationSyncAll(rsGetAllocation(shaderConstants));
947420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
9484a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // Draw line from the card to the detail texture.
9494a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // The line is drawn from the top or bottom left of the card
9504a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // to either the top of the screen or the top of the detail
9514a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // texture, depending on detailTextureAlignment.
9527c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (drawRuler) {
9534a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerTop;
9544a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerBottom;
9554a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
9564a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = screenCoord.y;
9574a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = 0;
9584a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
9594a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = height;
9604a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = screenCoord.y;
9614a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
9627c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float halfWidth = lineWidth * 0.5f;
9631eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller                    const float x0 = trunc(cards[i].detailLineOffset.x + screenCoord.x - halfWidth);
9641eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller                    const float x1 = x0 + lineWidth;
965b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y0 = rulerBottom + yPadding;
966b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y;
967b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
968b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    if (blendedAlpha == 1.0f) {
969b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture);
970b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    } else {
971b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture);
972b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    }
973b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
974b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                            x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
9757c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
9767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
9777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw the detail texture next to it using the offsets provided.
9787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
9797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
9807cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offx = cards[i].detailTextureOffset.x;
9817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offy = -cards[i].detailTextureOffset.y;
9824a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                const float textureTop = (detailTextureAlignment & VIEW_TOP)
9834a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        ? height : screenCoord.y;
984b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
985b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
986b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
987b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
9888fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[0].x = x0;
9898fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[0].y = height - y1;
9908fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[1].x = x1;
9918fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[1].y = height - y0;
992b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
993b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
994b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
995b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
996b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture);
997b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
998b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
999b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
10007cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
10017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
10027cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    }
1003420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
10047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
10057cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
10069afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerstatic void drawBackground()
10079afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller{
1008b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    static bool toggle;
10099afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    if (backgroundTexture.p != 0) {
10109afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
10119afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rs_matrix4x4 projection, model;
10129afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
10139afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadProjectionMatrix(&projection);
10149afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadIdentity(&model);
10159afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadModelMatrix(&model);
1016a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller        rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture);
10179afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        float z = -0.9999f;
10189afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgDrawQuad(
10199afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[0].x, cardVertices[0].y, z,
10209afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[1].x, cardVertices[1].y, z,
10219afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[2].x, cardVertices[2].y, z,
10229afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[3].x, cardVertices[3].y, z);
10239afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        updateCamera = true; // we mucked with the matrix.
10249afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    } else {
10259afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
1026b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        if (debugRendering) { // for debugging - flash the screen so we know we're still rendering
1027b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            rsgClearColor(toggle ? backgroundColor.x : 1.0f,
1028b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.y : 0.0f,
1029b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.z : 0.0f,
1030b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        backgroundColor.w);
10319afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            toggle = !toggle;
1032b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        } else {
10337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
10347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                   backgroundColor.w);
10359afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       }
10369afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    }
10379afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller}
10389afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller
10395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
10405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
10425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
10435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
10445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
10455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
10465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
10485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
10495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
10505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
10545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
10555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
10565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
1057be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic float lastAngle = 0.0f;
10585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
10595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
1060b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shumastatic float stopVelocity = 0.1f * M_PI / 180.0f; // slower than this: carousel stops
1061b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shumastatic float selectionVelocity = 15.0f * M_PI / 180.0f; // faster than this: tap won't select
106255b237bcd720774e27248f5fecf6c32a3f420a4cJim Millerstatic float velocityHistory[VELOCITY_HISTORY_MAX];
106355b237bcd720774e27248f5fecf6c32a3f420a4cJim Millerstatic int velocityHistoryCount;
10645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
10655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
10675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
10685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1069be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// Computes a hit angle from the center of the carousel to a point on either a plane
1070be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// or on a cylinder. If neither is hit, returns false.
1071be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool hitAngle(float x, float y, float *angle)
1072be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller{
1073be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    Ray ray;
1074be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    makeRayForPixelAt(&ray, &camera, x, y);
1075be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float t = FLT_MAX;
1076be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_PLANE && rayPlaneIntersect(&ray, &carouselPlane, &t)) {
1077be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 point = (ray.position + t*ray.direction);
1078be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 direction = point - carouselPlane.point;
1079be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        *angle = atan2(direction.x, direction.z);
1080be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (debugSelection) rsDebug("Plane Angle = ", degrees(*angle));
1081be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        return true;
1082be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    } else if ((dragModel == DRAG_MODEL_CYLINDER_INSIDE || dragModel == DRAG_MODEL_CYLINDER_OUTSIDE)
1083be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            && rayCylinderIntersect(&ray, &carouselCylinder, &t)) {
1084be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 point = (ray.position + t*ray.direction);
1085be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 direction = point - carouselCylinder.center;
1086be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        *angle = atan2(direction.x, direction.z);
1087be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (debugSelection) rsDebug("Cylinder Angle = ", degrees(*angle));
1088be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        return true;
1089be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    }
1090be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    return false;
1091be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller}
1092be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
10935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
10945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1095be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float result;
1096be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float angle;
1097be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (hitAngle(x, y, &angle)) {
1098be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        result = angle - lastAngle;
1099be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        // Handle singularity where atan2 switches between +- PI
1100be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (result < -M_PI) {
1101be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            result += 2.0f * M_PI;
1102be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        } else if (result > M_PI) {
1103be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            result -= 2.0f * M_PI;
1104be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        }
1105be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        lastAngle = angle;
1106be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    } else {
1107be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        // If we didn't hit anything or drag model wasn't plane or cylinder, we use screen delta
1108be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        result = dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
1109be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    }
1110be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    return result;
11115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
11145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
11155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
11165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1118370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic int doSelection(float x, float y)
11195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
11205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
1121b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (makeRayForPixelAt(&ray, &camera, x, y)) {
11225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
11235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
11245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
11255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
11265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1128370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void sendAnimationStarted() {
112943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    rsSendToClient(CMD_ANIMATION_STARTED);
113043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
113143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
1132370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void sendAnimationFinished() {
1133a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    float data[1];
1134a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    data[0] = radiansToCarouselRotationAngle(bias);
1135a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    rsSendToClient(CMD_ANIMATION_FINISHED, (int*) data, sizeof(data));
1136a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
1137a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
11381882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doStart(float x, float y, long eventTime)
11395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
114043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    touchPosition = lastPosition = (float2) { x, y };
1141be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    lastAngle = hitAngle(x,y, &lastAngle) ? lastAngle : 0.0f;
1142b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma    enableSelection = fabs(velocity) < selectionVelocity;
11435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
114455b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    velocityHistory[0] = 0.0f;
114555b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    velocityHistoryCount = 0;
114655b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller
1147ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    releaseTime = lastTime; // used to disable scale down animation - any time in the past will do
11481882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    touchTime = lastTime = eventTime;
11495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
115043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = true;
1151ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    isOverScrolling = false;
11525dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    tiltAngle = 0;
11535dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    overscrollBias = bias;
11545dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
115543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    animatedSelection = doSelection(x, y); // used to provide visual feedback on touch
11568debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    stopAutoscroll();
11575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
115955b237bcd720774e27248f5fecf6c32a3f420a4cJim Millerstatic float computeAverageVelocityFromHistory()
116055b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller{
116155b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    if (velocityHistoryCount > 0) {
116255b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        const int count = min(VELOCITY_HISTORY_MAX, velocityHistoryCount);
116355b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        float vsum = 0.0f;
116455b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        for (int i = 0; i < count; i++) {
116555b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller            vsum += velocityHistory[i];
116655b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        }
116755b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        return vsum / count;
116855b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    } else {
116955b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        return 0.0f;
117055b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    }
117155b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller}
117255b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller
11731882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doStop(float x, float y, long eventTime)
11745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1175ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    updateAllocationVars();
1176ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller
1177ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    releaseTime = rsUptimeMillis();
117843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
117943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (enableSelection) {
11808fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        int data[3];
11818fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        int selection;
11828fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        float2 point;
11838fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma
11848fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if ((selection = intersectDetailTexture(x, y, &point)) != -1) {
11858fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            if (debugSelection) rsDebug("Selected detail texture on doStop():", selection);
11868fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[0] = selection;
11878fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[1] = point.x;
11888fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[2] = point.y;
11898fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            rsSendToClientBlocking(CMD_DETAIL_SELECTED, data, sizeof(data));
11908fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        }
11918fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        else if ((selection = doSelection(x, y))!= -1) {
119243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (debugSelection) rsDebug("Selected item on doStop():", selection);
119343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            data[0] = selection;
119443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
119543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
119643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = false;
11975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
119855b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        velocity = computeAverageVelocityFromHistory();
1199b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma        if (fabs(velocity) > stopVelocity) {
12005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
12015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
12025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
120343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection = false;
12041882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    lastTime = eventTime;
120543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = false;
12065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
12075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1208594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumavoid doLongPress()
1209594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma{
1210594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    int64_t currentTime = rsUptimeMillis();
1211ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    updateAllocationVars();
121243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Selection happens for most recent position detected in doMotion()
1213e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma    if (enableSelection && animatedSelection != -1) {
1214e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        if (debugSelection) rsDebug("doLongPress(), selection = ", animatedSelection);
1215e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        int data[7];
1216e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[0] = animatedSelection;
1217e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[1] = lastPosition.x;
1218e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[2] = lastPosition.y;
1219e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[3] = cards[animatedSelection].detailTexturePosition[0].x;
1220e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[4] = cards[animatedSelection].detailTexturePosition[0].y;
1221e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[5] = cards[animatedSelection].detailTexturePosition[1].x;
1222e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[6] = cards[animatedSelection].detailTexturePosition[1].y;
1223594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data));
1224e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        enableSelection = false;
1225594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    }
1226594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    lastTime = rsUptimeMillis();
1227594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma}
1228594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma
12291882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doMotion(float x, float y, long eventTime)
12305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
123114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float highBias = maximumBias();
123214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float lowBias = minimumBias();
12335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
12345dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    overscrollBias += deltaOmega;
12355dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    overscrollBias = clamp(overscrollBias, lowBias - TILT_MAX_BIAS,
12365dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            highBias + TILT_MAX_BIAS);
12375dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    bias = clamp(overscrollBias, lowBias, highBias);
12385dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    isOverScrolling = tiltOverscroll();
12395dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
124043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float2 delta = (float2) { x, y } - touchPosition;
124143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float distance = sqrt(dot(delta, delta));
124243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool inside = (distance < selectionRadius);
124343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection &= inside;
124443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastPosition = (float2) { x, y };
12451882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    float dt = deltaTimeInSeconds(eventTime);
12465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
12475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
124855b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        velocityHistory[velocityHistoryCount % VELOCITY_HISTORY_MAX] = v;
124955b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller        velocityHistoryCount++;
12505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
125155b237bcd720774e27248f5fecf6c32a3f420a4cJim Miller    velocity = computeAverageVelocityFromHistory();
12521882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    lastTime = eventTime;
12535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
12545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12555dac0398c2684197a4363f15cef292fee126038fBryan Mawhinneybool tiltOverscroll() {
12565dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    if (overscrollBias == bias) {
12575dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        // No overscroll required.
12585dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        return false;
12595dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    }
12605dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
12615dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    // How much we deviate from the maximum bias.
12625dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    float deltaBias = overscrollBias - bias;
12635dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    // We clamped, that means we need overscroll.
12645dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    tiltAngle = (deltaBias / TILT_MAX_BIAS)
12655dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            * TILT_MAX_ANGLE * fillDirection;
12665dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
12675dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    return true;
12685dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney}
12695dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney
12705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
12718debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry// Autoscroll Interpolation
12728debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry////////////////////////////////////////////////////////////////////////////////////////////////////
12738debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic int64_t autoscrollStartTime = 0L; //tracks when we actually started interpolating
12748debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic int64_t autoscrollDuration = 0L;  //in milli seconds
12758debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic int autoscrollInterpolationMode = INTERPOLATION_LINEAR;
12768debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
12778debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic float autoscrollStopAngle = 0.0f;
12788debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic float autoscrollStartAngle = 0.0f;
12798debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
12808debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berryvoid setCarouselRotationAngle2(
12818debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    float endAngle,
12828debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    int   milliseconds,
12838debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    int   interpolationMode,
12848debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    float maxAnimatedArc)
12858debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry{
12868debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    float actualStart = radiansToCarouselRotationAngle(bias);
12878debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
12888debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (maxAnimatedArc > 0) {
12898debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        //snap the current position to keep end - start under maxAnimatedArc
12908debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        if (actualStart <= endAngle) {
12918debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry            if (actualStart < endAngle - maxAnimatedArc) {
12928debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry                actualStart = endAngle - maxAnimatedArc;
12938debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry            }
12948debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        }
12958debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        else {
12968debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry            if (actualStart > endAngle + maxAnimatedArc) {
12978debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry                actualStart = endAngle + maxAnimatedArc;
12988debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry            }
12998debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        }
13008debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13018debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13028debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    animating = true;
1303ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    isAutoScrolling = true;
13048debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollDuration = milliseconds;
13058debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollInterpolationMode = interpolationMode;
13068debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollStartAngle = carouselRotationAngleToRadians(actualStart);
13078debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollStopAngle = carouselRotationAngleToRadians(endAngle);
13088debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13098debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    //Make sure the start and stop angles are in the allowed range
13108debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    const float highBias = maximumBias();
13118debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    const float lowBias  = minimumBias();
13128debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollStartAngle = clamp(autoscrollStartAngle, lowBias, highBias);
13138debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollStopAngle  = clamp(autoscrollStopAngle, lowBias, highBias);
13148debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13158debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    //stop other animation kinds
1316ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    isOverScrolling = false;
13178debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    velocity = 0.0f;
13188debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry}
13198debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13208debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic void stopAutoscroll()
13218debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry{
1322ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    isAutoScrolling = false;
13238debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    autoscrollStartTime = 0L; //reset for next time
13248debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry}
13258debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13268debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry// This method computes the position of all the cards by updating bias based on a
13278debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry// simple interpolation model.  If the cards are still in motion, returns true.
13288debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berrystatic bool doAutoscroll(float currentTime)
13298debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry{
13308debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (autoscrollDuration == 0L) {
13318debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        return false;
13328debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13338debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13348debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (autoscrollStartTime == 0L) {
13358debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        autoscrollStartTime = currentTime;
13368debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13378debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13388debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    const int64_t interpolationEndTime = autoscrollStartTime + autoscrollDuration;
13398debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13408debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    float timePos = (currentTime - autoscrollStartTime) / (float)autoscrollDuration;
13418debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (timePos > 1.0f) {
13428debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        timePos = 1.0f;
13438debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13448debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13458debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    float lambda = timePos; //default to linear
13468debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (autoscrollInterpolationMode == INTERPOLATION_DECELERATE_QUADRATIC) {
13478debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        lambda = 1.0f - (1.0f - timePos) * (1.0f - timePos);
13488debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13498debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    else if (autoscrollInterpolationMode == INTERPOLATION_ACCELERATE_DECELERATE_CUBIC) {
13508debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        lambda = timePos * timePos * (3 - 2 * timePos);
13518debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13528debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13538debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    bias = lambda * autoscrollStopAngle + (1.0 - lambda) * autoscrollStartAngle;
13548debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13558debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    if (currentTime > interpolationEndTime) {
13568debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        stopAutoscroll();
13578debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        return false;
13588debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13598debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    else {
13608debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        return true;
13618debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry    }
13628debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry}
13638debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry
13648debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry////////////////////////////////////////////////////////////////////////////////////////////////////
13655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
13665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
136743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float EPSILON = 1.0e-6f;
136843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float tmin = 0.0f;
13695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
137143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float* tout)
13725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
13735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
13745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
13755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
13765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
13785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
13795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
13815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
13825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
13845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
13855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
13875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
13885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
13895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
13915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
13925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
13935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
13945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
13955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
13965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
139743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
139843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/plane intersection. Returns false if no intersection found.
139943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1400b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
140143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayPlaneIntersect(Ray* ray, Plane* plane, float* tout)
1402b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
140343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float denom = dot(ray->direction, plane->normal);
140443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (fabs(denom) > EPSILON) {
140543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float t = - (plane->constant + dot(ray->position, plane->normal)) / denom;
140643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (t > tmin && t < *tout) {
140743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            *tout = t;
140843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            return true;
140943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
141043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
141143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1412b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1413b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
141443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
141543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/cylindr intersection. There are 0, 1 or 2 hits.
141643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Returns true and sets *tout to the closest point or
141743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// returns false if no intersection found.
141843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1419b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
142043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout)
1421b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
142243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float A = ray->direction.x * ray->direction.x + ray->direction.z * ray->direction.z;
142343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (A < EPSILON) return false; // ray misses
142443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
142543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Compute quadratic equation coefficients
142643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float B = 2.0f * (ray->direction.x * ray->position.x
142743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->direction.z * ray->position.z);
142843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float C = ray->position.x * ray->position.x
142943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->position.z * ray->position.z
143043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            - cylinder->radius * cylinder->radius;
143143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float disc = B*B - 4*A*C;
143243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
143343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (disc < 0.0f) return false; // ray misses
143443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    disc = sqrt(disc);
143543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float denom = 2.0f * A;
143643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
143743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Nearest point
143843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t1 = (-B - disc) / denom;
1439be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_CYLINDER_OUTSIDE && t1 > tmin && t1 < *tout) {
144043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t1;
144143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
144243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
144343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
144443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Far point
144543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t2 = (-B + disc) / denom;
1446be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_CYLINDER_INSIDE && t2 > tmin && t2 < *tout) {
144743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t2;
144843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
144943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
145043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1451b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1452b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1453b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates.
14545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1455b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1456b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y)
14575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
14585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
14595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
1460b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.from:", cam->from);
1461b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.at:", cam->at);
1462b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.dir:", normalize(cam->at - cam->from));
14635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
14665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
1467b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float u = x / rsgGetWidth();
1468b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float v = 1.0f - (y / rsgGetHeight());
1469b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float aspect = (float) rsgGetWidth() / rsgGetHeight();
1470b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f));
1471b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dir = normalize(cam->at - cam->from);
1472b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 du = tanfov2 * normalize(cross(dir, cam->up));
1473b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dv = tanfov2 * normalize(cross(du, dir));
1474b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    du *= aspect;
1475b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
1476b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = cam->from;
1477b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
1478b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1479b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (vector math) = ", rayDir);
14805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1482b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1483b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
1484b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return true;
1485b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1486b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1487b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a model view and projection matrix.
1488b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1489b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1490b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y)
1491b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
1492b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rs_matrix4x4 pm = *model;
1493b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsMatrixLoadMultiply(&pm, proj, model);
1494b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (!rsMatrixInverse(&pm)) {
1495b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("ERROR: SINGULAR PM MATRIX", 0);
1496b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        return false;
14975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
1498b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float width = rsgGetWidth();
1499b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float height = rsgGetHeight();
1500b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winx = 2.0f * x / width - 1.0f;
1501b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winy = 2.0f * y / height - 1.0f;
1502b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1503b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
1504b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 at = { winx, winy, 1.0f, 1.0f };
1505b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1506b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye = rsMatrixMultiply(&pm, eye);
1507b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye *= 1.0f / eye.w;
15085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1509b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at = rsMatrixMultiply(&pm, at);
1510b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at *= 1.0f / at.w;
1511b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1512b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = { eye.x, eye.y, eye.z };
1513b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 atPoint = { at.x, at.y, at.z };
1514b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(atPoint - rayPoint);
1515b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1516b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winx: ", winx);
1517b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winy: ", winy);
1518b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray position (transformed) = ", eye);
1519b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (transformed) = ", rayDir);
1520b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    }
1521b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1522b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
15235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
15245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
15255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15268fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic int intersectDetailTexture(float x, float y, float2 *tapCoordinates)
15278fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma{
15288fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    for (int id = 0; id < cardCount; id++) {
15298fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[id].detailVisible) {
15308fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int x0 = cards[id].detailTexturePosition[0].x;
15318fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int y0 = cards[id].detailTexturePosition[0].y;
15328fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int x1 = cards[id].detailTexturePosition[1].x;
15338fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int y1 = cards[id].detailTexturePosition[1].y;
15348fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
15358fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                float2 point = { x - x0, y - y0 };
15368fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                *tapCoordinates = point;
15378fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                return id;
15388fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            }
15398fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        }
15408fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    }
15418fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    return -1;
15428fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma}
15438fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma
15445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
15455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
15465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
15475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
15488fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[id].cardVisible) {
15495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
15505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
15515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
15535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
1554c2baf88a763ae0e3694c8a10c13f203db9aec363Jim Shuma            getMatrixForCard(&matrix, id, true, true);
15555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
15565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
15575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
15585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
15595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
15605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
15615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
15625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
15635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
15645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
15655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
15685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
15695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
15705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
15715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
15735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
15745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
15755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
15765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
157843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// simple physics model.  If the cards are still in motion, returns true.
157943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool doPhysics(float dt)
158043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
158143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
158243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
158343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    dt /= N;
158443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    for (int i = 0; i < N; i++) {
158543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Force friction - always opposes motion
158643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Ff = -frictionCoeff * velocity;
158743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
158843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Restoring force to match cards with slots
158943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float theta = startAngle + bias;
159043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float dtheta = 2.0f * M_PI / slotCount;
159143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float position = theta / dtheta;
159243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float fraction = position - floor(position); // fractional position between slots
159343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float x;
159443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (fraction > 0.5f) {
159543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = - (1.0f - fraction);
159643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
159743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = fraction;
159843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
159943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Fr = - springConstant * x;
160043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
160143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // compute velocity
160243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float momentum = mass * velocity + (Ff + Fr)*dt;
160343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocity = momentum / mass;
160443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias += velocity * dt;
160543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
1606b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma    return fabs(velocity) > stopVelocity;
160743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
160843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
160943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float easeOut(float x)
161043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
161143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return x;
161243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
161343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
16148debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry// Computes the next value for bias using the current animation (physics/overscroll/autoscrolling)
16155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
16165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
161743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    static const float biasMin = 1e-4f; // close enough if we're within this margin of result
16185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
161943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float dt = deltaTimeInSeconds(currentTime);
16205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
162143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (dt <= 0.0f) {
162243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (debugRendering) rsDebug("Time delta was <= 0", dt);
162343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
16245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
16255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16265dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    const float firstBias = maximumBias();
16275dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    const float lastBias = minimumBias();
162843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool stillAnimating = false;
1629ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    if (isOverScrolling) {
16305dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        if (tiltAngle > TILT_MIN_ANGLE) {
16315dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            tiltAngle -= dt * TILT_MAX_ANGLE;
16325dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            stillAnimating = true;
16335dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        } else if (tiltAngle < -TILT_MIN_ANGLE) {
16345dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            tiltAngle += dt * TILT_MAX_ANGLE;
16355dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            stillAnimating = true;
163643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
16375dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney           isOverScrolling = false;
16385dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney           tiltAngle = false;
16395dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney           velocity = 0.0f;
164043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
1641ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    } else if (isAutoScrolling) {
16428debeb8a0a785f0ad66bc75200cdb47c137602bcArnaud Berry        stillAnimating = doAutoscroll(currentTime);
164343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    } else {
164443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = doPhysics(dt);
16455dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney        isOverScrolling = tiltAngle != 0;
1646ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller        if (isOverScrolling) {
1647cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller            velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
16485dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney            stillAnimating = true;
1649cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller        }
1650c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    }
16515dac0398c2684197a4363f15cef292fee126038fBryan Mawhinney    bias = clamp(bias, lastBias, firstBias);
165243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return stillAnimating;
16535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
16545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
16565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
16575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
16585ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinneystatic void cullCards()
16595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1660b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    // Calculate the first and last angles of visible slots.  We include
1661b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    // VISIBLE_SLOT_PADDING slots on either side of visibleSlotCount to allow
1662b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    // cards to slide in / out at either side, and rely on the view frustrum
1663b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    // for accurate clipping.
1664b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    const float visibleFirst = slotPosition(-VISIBLE_SLOT_PADDING);
1665b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    const float visibleLast = slotPosition(visibleSlotCount + VISIBLE_SLOT_PADDING);
16665ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney
16675ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // We'll load but not draw prefetchCardCountPerSide cards
16685ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // from either side of the visible slots.
1669b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    const int prefetchCardCountPerSide = max(prefetchCardCount / 2, VISIBLE_SLOT_PADDING);
1670b4959ac31abdaf6ab7309c17f56fceaa1baabed7Bryan Mawhinney    const float prefetchFirst = slotPosition(-prefetchCardCountPerSide);
16715ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    const float prefetchLast = slotPosition(visibleSlotCount + prefetchCardCountPerSide);
16725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
16735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
16745ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            // If visibleSlotCount is specified then only show cards between visibleFirst and visibleLast
16755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
167646a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if ((p >= prefetchFirst && p < prefetchLast)
167746a02894a12775c16c4588a168c3cbc767a6f983Jim Miller                    || (p <= prefetchFirst && p > prefetchLast)) {
16785ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney                cards[i].shouldPrefetch = true;
167946a02894a12775c16c4588a168c3cbc767a6f983Jim Miller                cards[i].cardVisible = (p >= visibleFirst && p < visibleLast)
1680376a291b7a3016cc85501ee1c044629cce60e75cLogan Chien                        || (p <= visibleFirst && p > visibleLast);
16818fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                // cards[i].detailVisible will be set at draw time
16825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
16835ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney                cards[i].shouldPrefetch = false;
16848fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].cardVisible = false;
16858fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailVisible = false;
16865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
16875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
16885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
16895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
16908fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            cards[i].cardVisible = true;
16918fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            // cards[i].detailVisible will be set at draw time
16925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
16935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
16945ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney}
16955ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney
16965ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney// Request missing texture/geometry for a single card
16975ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinneystatic void requestCardResources(int i) {
16985ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp);
169946a02894a12775c16c4588a168c3cbc767a6f983Jim Miller    int data[1] = { i };
170046a02894a12775c16c4588a168c3cbc767a6f983Jim Miller
17015ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // request texture from client if not loaded
17025ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    if (cards[i].textureState == STATE_INVALID) {
170346a02894a12775c16c4588a168c3cbc767a6f983Jim Miller        if (debugTextureLoading) rsDebug("Requesting card because state is STATE_INVALID", i);
17045ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
17055ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (enqueued) {
17065ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            cards[i].textureState = STATE_LOADING;
17075ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        } else {
170846a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", i);
17095ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17105ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    } else if (cards[i].textureState == STATE_STALE) {
171146a02894a12775c16c4588a168c3cbc767a6f983Jim Miller        if (debugTextureLoading) rsDebug("Requesting card because state is STATE_STALE", i);
17125ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
17135ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (enqueued) {
17145ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            cards[i].textureState = STATE_UPDATING;
17155ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        } else {
171646a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", i);
17175ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17185ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    }
171946a02894a12775c16c4588a168c3cbc767a6f983Jim Miller
17205ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // request detail texture from client if not loaded
17215ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    if (cards[i].detailTextureState == STATE_INVALID) {
17225ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
17235ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (enqueued) {
17245ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            cards[i].detailTextureState = STATE_LOADING;
17255ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        } else {
172646a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", i);
17275ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17285ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    } else if (cards[i].detailTextureState == STATE_STALE) {
17295ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
17305ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (enqueued) {
17315ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            cards[i].detailTextureState = STATE_UPDATING;
17325ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        } else {
173346a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", i);
17345ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17355ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    }
173646a02894a12775c16c4588a168c3cbc767a6f983Jim Miller
17375ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // request geometry from client if not loaded
17385ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    if (cards[i].geometryState == STATE_INVALID) {
17395ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
17405ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (enqueued) {
17415ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            cards[i].geometryState = STATE_LOADING;
17425ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        } else {
174346a02894a12775c16c4588a168c3cbc767a6f983Jim Miller            if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", i);
17445ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17455ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    }
17465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
17475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
17485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
17495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
1750420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic void updateCardResources(int64_t currentTime)
17515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
17525ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // First process any visible cards
1753a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
17548fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
17555ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            requestCardResources(i);
17565ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        }
17575ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    }
17582ba04e061b52c488a154739379501dc833e39f79Jim Miller
17595ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    // Then the rest
17605ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney    for (int i = cardCount-1; i >= 0; --i) {
17615ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney        if (cards[i].cardVisible) {
17625ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            // already requested above
176346a02894a12775c16c4588a168c3cbc767a6f983Jim Miller        } else if (cards[i].shouldPrefetch) {
17645ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            requestCardResources(i);
17655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
17665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
17675ac1de00d4441748a4b183b5d406298cd18f2d27Bryan Mawhinney            int data[1];
1768dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].textureState != STATE_INVALID) {
17695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
17705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
17715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
17725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
1773420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].textureTimeStamp = currentTime;
17745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
17757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
17767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
17777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
17787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // ask the host to remove the detail texture
17797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState != STATE_INVALID) {
17807cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
17817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
17827cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
17837cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_INVALID;
1784420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].detailTextureTimeStamp = currentTime;
17857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
17867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
17875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
17885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
17895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
1790dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].geometryState != STATE_INVALID) {
17915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
17925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
17935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
17945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
17955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
17967cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
17975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
17985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
17995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
18005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
18015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
18025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
18045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
18055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
18065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
18075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
18085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
18095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
18105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
18118a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6fJason Sams
18128a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6fJason Sams    rsgProgramFragmentConstantColor(singleTextureFragmentProgram, 1.0f, 0.0f, 0.0f, 1.0f);
18135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
18145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
18155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
18165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
18175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
1818b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (makeRayForPixelAt(&ray, &camera, posX, posY)) {
18195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
18205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
18215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
18225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
18235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
18245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
18255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
18265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
18275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
18295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
18305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
18325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
1833b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp);
1834b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp);
1835b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp);
18365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1837ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    updateAllocationVars();
18385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1839a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindProgramFragment(singleTextureFragmentProgram);
1840fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // rsgClearDepth() currently follows the value of glDepthMask(), so it's disabled when
1841fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // the mask is disabled. We may want to change the following to always draw w/o Z for
1842fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // the background if we can guarantee the depth buffer will get cleared and
1843fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // there's a performance advantage.
1844f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsgBindProgramStore(programStoreBackground);
18459afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    drawBackground();
18465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
18485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1849ceae13b8f2ae7342506ecb4e4fcce956dbb12af7Jim Miller    bool stillAnimating = (currentTime - touchTime) <= ANIMATION_SCALE_UP_TIME;
185043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
185143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (!isDragging && animating) {
185243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = updateNextPosition(currentTime);
185343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
185443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
185543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastTime = currentTime;
18565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
18585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1859420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    updateCardResources(currentTime);
18605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1861bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    // Draw cards opaque only if requested, and always draw detail textures with blending.
1862bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawCards(currentTime);
1863f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsgBindProgramStore(programStoreDetail);
1864bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawDetails(currentTime);
18655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
186643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (stillAnimating != animating) {
186743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (stillAnimating) {
186843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we just started animating
186943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationStarted();
187043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
187143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we were animating but stopped animating just now
187243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationFinished();
187343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
187443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = stillAnimating;
187543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
187643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
187743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (debugRays) {
18785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
18795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
18805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
18815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
18825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
188343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return animating ? 1 : 0;
18845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1885