carousel.rs revision 3adf712e636f67265da7a6ff425c87e63fc20884
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?
373adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t textureTimeStamp; // time when this texture was last updated, in ms
383adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t detailTextureTimeStamp; // time when this texture was last updated, in ms
393adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    int64_t geometryTimeStamp; // time when the card itself was last updated, in ms
405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Plane_s {
4843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 point;
4943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 normal;
5043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float constant;
5143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Plane;
5243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
5343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Cylinder_s {
5443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 center; // center of a y-axis-aligned infinite cylinder
5543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float radius;
5643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Cylinder;
5743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
68f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumatypedef struct ProgramStore_s {
69f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rs_program_store programStore;
70f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma} ProgramStore_t;
71f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
72420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millertypedef struct FragmentShaderConstants_s {
73420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    float fadeAmount;
743adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    float overallAlpha;
75420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller} FragmentShaderConstants;
76420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
826af401bca5f8854524d128e9df5700035fae1160Jim Shuma    STATE_STALE, // we have an old item, but should request an update
836af401bca5f8854524d128e9df5700035fae1160Jim Shuma    STATE_UPDATING, // we've requested an update, and will display the old one in the meantime
845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
874a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
884a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyenum {
894a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is centered vertically with respect to the card **/
904a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_VERTICAL = 1,
914a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the top edge of the carousel view **/
924a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_TOP = 1 << 1,
934a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
944a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_BOTTOM = 1 << 2,
954a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned above the card (not yet implemented) **/
964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    ABOVE = 1 << 3,
974a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned below the card **/
984a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    BELOW = 1 << 4,
994a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control vertical alignment **/
1004a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VERTICAL_ALIGNMENT_MASK = 0xff,
1014a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
1024a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1034a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is centered horizontally with respect to either the top or bottom
1044a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * extent of the card, depending on whether the detail is above or below the card.
1054a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1064a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_HORIZONTAL = 1 << 8,
1074a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1084a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the left edge of either the top or the bottom of
1094a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1104a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    LEFT = 1 << 9,
1124a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1134a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the right edge of either the top or the bottom of
1144a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1154a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * (not yet implemented)
1164a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1174a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    RIGHT = 1 << 10,
1184a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control horizontal alignment **/
1194a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    HORIZONTAL_ALIGNMENT_MASK = 0xff00,
1204a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney};
1214a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
1225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
1235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
1248fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic const int CMD_DETAIL_SELECTED = 105;
125594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumastatic const int CMD_CARD_LONGPRESS = 110;
1265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
1275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
1285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
1295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
1305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
1315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
1327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REQUEST_DETAIL_TEXTURE = 600;
1337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
1347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_PING = 1000;
1355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
136be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// Drag model *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
137be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_SCREEN_DELTA = 0; // Drag relative to x coordinate of motion vector
138be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_PLANE = 1; // Drag relative to projected point on plane of carousel
139be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_CYLINDER_INSIDE = 2; // Drag relative to point on inside of cylinder
140be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic const int DRAG_MODEL_CYLINDER_OUTSIDE = 3; // Drag relative to point on outside of cylinder
141be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
1425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
1431eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Millerstatic const int ANIMATION_DELAY_TIME = 100; // hold off scale animation until this time
1445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
1451eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.1f, 0.1f, 0.1f }; // increase by this %
14643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float OVERSCROLL_SLOTS = 1.0f; // amount of allowed overscroll (in slots)
1475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
1497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugCamera = false; // dumps ray/camera coordinate stuff
15043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugSelection = false; // logs selection events
1517cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugTextureLoading = false; // for debugging texture load/unload
1527cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugGeometryLoading = false; // for debugging geometry load/unload
1537c09ccce478100d75e4427d87866ff19d758ae7aJim Shumaconst bool debugDetails = false; // for debugging detail texture geometry
154b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerconst bool debugRendering = false; // flashes display when the frame changes
15543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugRays = false; // shows visual depiction of hit tests, See renderWithRays().
1565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
1585ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
1595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
1605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
1615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
162f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumaint programStoresCardCount; // number of program fragment stores
1635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
1647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerint visibleDetailCount; // number of visible detail textures to show
1654fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumaint prefetchCardCount; // how many cards to keep in memory
1664a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyint detailTextureAlignment; // How to align detail texture with respect to card
1677c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawRuler; // whether to draw a ruler from the card to the detail texture
1685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
1695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
17083d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinneybool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle
171c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat swaySensitivity; // how much to rotate cards in relation to the rotation velocity
172c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat frictionCoeff; // how much to slow down the carousel over time
173c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat dragFactor; // a scale factor for how sensitive the carousel is to user dragging
174420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerint fadeInDuration; // amount of time (in ms) for smoothly switching out textures
1753adf712e636f67265da7a6ff425c87e63fc20884Jim Shumaint cardCreationFadeDuration; // amount of time (in ms) to fade while initially showing a card
176420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
177a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerfloat detailFadeRate; // rate at which details fade as they move into the distance
17843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerfloat4 backgroundColor;
1790cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneyint rowCount;  // number of rows of cards in a given slot, default 1
1800cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneyfloat rowSpacing;  // spacing between rows of cards
1811a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilsonbool firstCardTop; // set true for first card on top row when multiple rows used
1820cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney
183be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerint dragModel = DRAG_MODEL_SCREEN_DELTA;
18414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumaint fillDirection; // the order in which to lay out cards: +1 for CCW (default), -1 for CW
185f664659f79399e92025e1dfe1ffbb682ff05613cJim ShumaProgramStore_t *programStoresCard;
186f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumars_program_store programStoreBackground;
187f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumars_program_store programStoreDetail;
188a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment singleTextureFragmentProgram;
1893adf712e636f67265da7a6ff425c87e63fc20884Jim Shumars_program_fragment singleTextureBlendingFragmentProgram;
190a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment multiTextureFragmentProgram;
1913adf712e636f67265da7a6ff425c87e63fc20884Jim Shumars_program_fragment multiTextureBlendingFragmentProgram;
1925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
1935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
1945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
1955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
1969afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerrs_allocation backgroundTexture; // drawn behind everything, if set
1977cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerrs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
198420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_allocation detailLoadingTexture; // used when detail texture is loading
1995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
2005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
20151dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinneyrs_matrix4x4 defaultCardMatrix;
2025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
2035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
204420b44b8b11ec1c309ea130e69a6876325dbfef9Jim MillerFragmentShaderConstants* shaderConstants;
205420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_sampler linearClamp;
2065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
2085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
2095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
2105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
21143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int animatedSelection = -1;
21243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int currentFirstCard = -1;
2135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t touchTime = -1;  // time of first touch (see doStart())
2145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
21543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float2 touchPosition; // position of first touch, as defined by last call to doStart(x,y)
216c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float velocity = 0.0f;  // angular velocity in radians/s
21743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool overscroll = false; // whether we're in the overscroll animation
21843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool isDragging = false; // true while the user is dragging the carousel
21943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float selectionRadius = 50.0f; // movement greater than this will result in no selection
22043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool enableSelection = false; // enabled until the user drags outside of selectionRadius
22143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
22243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Default plane of the carousel. Used for angular motion estimation in view.
22343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic Plane carouselPlane = {
22443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 0.0f, 0.0f }, // point
22543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 1.0f, 0.0f }, // normal
22643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       0.0f // plane constant (= -dot(P, N))
22743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller};
2285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
229be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic Cylinder carouselCylinder = {
230be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        {0.0f, 0.0f, 0.0f }, // center
231be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        1.0f // radius - update with carousel radius.
232be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller};
233be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
2343df59346f395434454d310b070fff195089fbaf1Jim Miller// Because allocations can't have 0 dimensions, we have to track whether or not
235f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma// cards and program stores are valid separately.
2363df59346f395434454d310b070fff195089fbaf1Jim Miller// TODO: Remove this dependency once allocations can have a zero dimension.
2373df59346f395434454d310b070fff195089fbaf1Jim Millerstatic bool cardAllocationValid = false;
238f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic bool programStoresAllocationValid = false;
2393df59346f395434454d310b070fff195089fbaf1Jim Miller
2405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
2415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
2425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
2435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
2445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
2455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
2465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
2495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
2505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
2515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
2525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
2535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
2545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
2555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
2565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
2575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
2605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
2618fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic int intersectDetailTexture(float x, float y, float2 *tapCoordinates);
262b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
263b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
264b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
265b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y);
2665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
267be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool rayPlaneIntersect(Ray* ray, Plane* plane, float* tout);
268be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout);
2695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
2715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
2725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
2732ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) rsDebug("Renderscript: init()", 0);
2745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
2755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
2765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
2777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    visibleDetailCount = 3;
2785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
279be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    radius = carouselCylinder.radius = 1.0f;
2805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
28183d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    cardsFaceTangent = false;
2825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
283b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
2843df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = false;
285f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresAllocationValid = false;
2863df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = 0;
2870cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rowCount = 1;
2880cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rowSpacing = 0.0f;
2891a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson    firstCardTop = false;
290420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    fadeInDuration = 250;
291420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
292a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    detailFadeRate = 0.5f; // fade details over this many slot positions.
29351dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinney    rsMatrixLoadIdentity(&defaultCardMatrix);
2945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
296f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic void updateAllocationVars()
2975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
299ed5cdfa293ec57cb14b98cdc3fa00ac5ec1c1ed4Stephen Hines    rs_allocation cardAlloc;
300f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsSetObject(&cardAlloc, rsGetAllocation(cards));
301dcfb45adbcf37de68920c181322aaa9e4e4b58d8Stephen Hines    cardCount = (cardAllocationValid && rsIsObject(cardAlloc)) ? rsAllocationGetDimX(cardAlloc) : 0;
302f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
303f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    // Program stores
304f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rs_allocation psAlloc;
305f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsSetObject(&psAlloc, rsGetAllocation(programStoresCard));
306f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresCardCount = (programStoresAllocationValid && rsIsObject(psAlloc) ?
307f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsAllocationGetDimX(psAlloc) : 0);
3085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
310be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millervoid setRadius(float rad)
311be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller{
312be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    radius = carouselCylinder.radius = rad;
313be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller}
314be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
315370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void initCard(Card_t* card)
3168b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
3176f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // Object refs are always initilized cleared.
3182ba04e061b52c488a154739379501dc833e39f79Jim Miller    static const float2 zero = {0.0f, 0.0f};
3192ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureOffset = zero;
3202ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailLineOffset = zero;
32151dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinney    rsMatrixLoad(&card->matrix, &defaultCardMatrix);
3222ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureState = STATE_INVALID;
3232ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureState = STATE_INVALID;
3242ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->geometryState = STATE_INVALID;
3258fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    card->cardVisible = false;
3268fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    card->detailVisible = false;
3272ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureTimeStamp = 0;
3282ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureTimeStamp = 0;
3293adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    card->geometryTimeStamp = rsUptimeMillis();
3302ba04e061b52c488a154739379501dc833e39f79Jim Miller}
3312ba04e061b52c488a154739379501dc833e39f79Jim Miller
3326f2cc8cf611860467315ecc542f71a225625eb1cJason Samsvoid createCards(int start, int total)
3332ba04e061b52c488a154739379501dc833e39f79Jim Miller{
3345b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney    if (!cardAllocationValid) {
3355b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // If the allocation is invalid, it contains a single place-holder
3365b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // card that has not yet been initialized (see CarouselRS.createCards).
3375b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        // Here we ensure that it is initialized when growing the total.
3385b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney        start = 0;
3395b54f405b4a1afcf57b5ccee2026a00a1004be20Bryan Mawhinney    }
3406f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    for (int k = start; k < total; k++) {
3416f2cc8cf611860467315ecc542f71a225625eb1cJason Sams        initCard(cards + k);
3422ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
3432ba04e061b52c488a154739379501dc833e39f79Jim Miller
3446f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // Since allocations can't have 0-size, we track validity ourselves based on the call to
3456f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    // this method.
3466f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    cardAllocationValid = total > 0;
3472ba04e061b52c488a154739379501dc833e39f79Jim Miller
3486f2cc8cf611860467315ecc542f71a225625eb1cJason Sams    updateAllocationVars(cards);
3495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
351420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller// Computes an alpha value for a card using elapsed time and constant fadeInDuration
3523adf712e636f67265da7a6ff425c87e63fc20884Jim Shumastatic float getAnimatedAlpha(int64_t startTime, int64_t currentTime, int64_t duration)
353420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller{
354420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double timeElapsed = (double) (currentTime - startTime); // in ms
3553adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    double alpha = duration > 0 ? (double) timeElapsed / duration : 1.0;
356420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return min(1.0f, (float) alpha);
357420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller}
358420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
3590cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Returns total angle for given number of slots
3600cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float wedgeAngle(float slots)
3615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3620cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    return slots * 2.0f * M_PI / slotCount;
3635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3650cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Return angle of slot in position p.
3660cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float slotPosition(int p)
3675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
36814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return startAngle + wedgeAngle(p) * fillDirection;
3695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3710cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Return angle for card in position p.
3720cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float cardPosition(int p)
373f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller{
3740cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    return bias + slotPosition(p / rowCount);
375f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller}
376f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller
37714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma// Return the lowest possible bias value, based on the fill direction
37814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumastatic float minimumBias()
37914d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma{
38014d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
38114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return (fillDirection > 0) ?
38214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)) :
38314d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        wedgeAngle(0.0f);
38414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma}
38514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
38614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma// Return the highest possible bias value, based on the fill direction
38714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shumastatic float maximumBias()
38814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma{
38914d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const int totalSlots = (cardCount + rowCount - 1) / rowCount;
39014d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    return (fillDirection > 0) ?
39114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        wedgeAngle(0.0f) :
39214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        max(0.0f, wedgeAngle(totalSlots - visibleDetailCount));
39314d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma}
39414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
39514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma
396a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from carousel rotation angle (in card slot units) to radians.
397a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float carouselRotationAngleToRadians(float carouselRotationAngle)
398a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
399a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -wedgeAngle(carouselRotationAngle);
400a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
401a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
402a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from radians to carousel rotation angle (in card slot units).
403a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float radiansToCarouselRotationAngle(float angle)
404a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
405a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -angle * slotCount / ( 2.0f * M_PI );
406a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
407a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
4085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
4095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
4105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
4115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
4125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
4135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
4145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
4155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
4165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
4175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
4195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
4205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
4215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
4225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
4235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
4245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
4255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
4265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
4275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
4285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
4315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
4325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
4345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
4365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
4375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
4385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
4395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
4425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
4435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
4445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
4465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
4475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
4485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
4495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
4505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
4515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
4525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
4535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
4545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
4555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
4565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
4575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
4585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
4595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
4605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
4615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
4625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
4635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
4645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
4655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
4685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4693df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
470c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].texture, texture);
4718441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (cards[n].textureState != STATE_STALE &&
4728441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureState != STATE_UPDATING) {
4738441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureTimeStamp = rsUptimeMillis();
4748441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    }
4757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
4767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
4777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
478b378af500b36226635b6343b1d5009ee9af44fc1Jim Millervoid setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
4797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
4803df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
481c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].detailTexture, texture);
4826af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (cards[n].detailTextureState != STATE_STALE &&
4836af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureState != STATE_UPDATING) {
4846af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureTimeStamp = rsUptimeMillis();
4856af401bca5f8854524d128e9df5700035fae1160Jim Shuma    }
4867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.x = offx;
4877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.y = offy;
488b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.x = loffx;
489b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.y = loffy;
4907cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
4916af401bca5f8854524d128e9df5700035fae1160Jim Shuma}
4926af401bca5f8854524d128e9df5700035fae1160Jim Shuma
4938441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkeyvoid invalidateTexture(int n, bool eraseCurrent)
4948441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey{
4958441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (n < 0 || n >= cardCount) return;
4968441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    if (eraseCurrent) {
4978441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureState = STATE_INVALID;
4988441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        rsClearObject(&cards[n].texture);
4998441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    } else {
5008441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey        cards[n].textureState = STATE_STALE;
5018441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey    }
5028441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey}
5038441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey
5046af401bca5f8854524d128e9df5700035fae1160Jim Shumavoid invalidateDetailTexture(int n, bool eraseCurrent)
5056af401bca5f8854524d128e9df5700035fae1160Jim Shuma{
5066af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (n < 0 || n >= cardCount) return;
5076af401bca5f8854524d128e9df5700035fae1160Jim Shuma    if (eraseCurrent) {
5086af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureState = STATE_INVALID;
5096af401bca5f8854524d128e9df5700035fae1160Jim Shuma        rsClearObject(&cards[n].detailTexture);
5106af401bca5f8854524d128e9df5700035fae1160Jim Shuma    } else {
5116af401bca5f8854524d128e9df5700035fae1160Jim Shuma        cards[n].detailTextureState = STATE_STALE;
5126af401bca5f8854524d128e9df5700035fae1160Jim Shuma    }
5135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
5165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5173df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
518c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].geometry, geometry);
5195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
5205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
5215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
5225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
5233adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma    cards[n].geometryTimeStamp = rsUptimeMillis();
5245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
526f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumavoid setProgramStoresCard(int n, rs_program_store programStore)
527f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma{
528f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsSetObject(&programStoresCard[n].programStore, programStore);
529f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    programStoresAllocationValid = true;
530f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma}
531f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
532a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichvoid setCarouselRotationAngle(float carouselRotationAngle) {
533a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    bias = carouselRotationAngleToRadians(carouselRotationAngle);
534198a060d650bc849ef0f25b597888fac9546803bJack Palevich}
535198a060d650bc849ef0f25b597888fac9546803bJack Palevich
5361eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller// Gets animated scale value for current selected card.
5371eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller// If card is currently being animated, returns true,  otherwise returns false.
5381eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Millerstatic bool getAnimatedScaleForSelected(float3* scale)
5395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float3 one = { 1.0f, 1.0f, 1.0f };
5411eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    int64_t dt = rsUptimeMillis() - touchTime;
5421eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    if (dt >= ANIMATION_DELAY_TIME) {
5431eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        float fraction = (float) (dt - ANIMATION_DELAY_TIME) / ANIMATION_SCALE_TIME;
5441eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        fraction = min(fraction, 1.0f);
5451eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        *scale = one + fraction * SELECTED_SCALE_FACTOR;
5461eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    } else {
5471eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        *scale = one;
5481eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    }
5491eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    return dt < (ANIMATION_DELAY_TIME + ANIMATION_SCALE_TIME); // still animating;
5505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
552c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
553c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma//    P(t) = 1 / (1 + e^(-t))
554c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Parameter t: Any real number
555c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Returns: A float in the range (0,1), with P(0.5)=0
556c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float logistic(float t) {
557af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao    return 1.f / (1.f + exp(-t));
558c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
559c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
5607c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic float getSwayAngleForVelocity(float v, bool enableSway)
561c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma{
5627c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float sway = 0.0f;
5637c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma
5647c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    if (enableSway) {
565d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak
566d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        sway = range * (logistic(-v * swaySensitivity) - 0.5f);
5677c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    }
568c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
569c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    return sway;
570c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
571c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
5720cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// Returns the vertical offset for a card in its slot,
5730cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney// depending on the number of rows configured.
5740cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinneystatic float getVerticalOffsetForCard(int i) {
5750cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   if (rowCount == 1) {
5760cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney       // fast path
5770cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney       return 0;
5780cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   }
5790cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   const float cardHeight = cardVertices[3].y - cardVertices[0].y;
5800cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   const float totalHeight = rowCount * (cardHeight + rowSpacing) - rowSpacing;
5811a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   if (firstCardTop)
5821a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson      i = rowCount - (i % rowCount) - 1;
5831a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   else
5841a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson      i = i % rowCount;
5851a5b4d109397ea175b5cbaa7490ca18e78eb040fSimon Wilson   const float rowOffset = i * (cardHeight + rowSpacing);
5860cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney   return (cardHeight - totalHeight) / 2 + rowOffset;
5870cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney}
5880cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney
5891eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller/*
5901eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * Composes a matrix for the given card.
5911eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * matrix: The output matrix.
5921eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * i: The card we're getting the matrix for.
5931eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
5941eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller *
5951eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller * returns true if an animation is being applied to the given card
5961eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller */
5971eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Millerstatic bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway)
5985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
6007c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
6015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
6020cec8afdb4f9d78adf88c9b9b41e993aef617beaBryan Mawhinney    rsMatrixTranslate(matrix, radius, getVerticalOffsetForCard(i), 0);
60383d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    float rotation = cardRotation + swayAngle;
60483d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    if (!cardsFaceTangent) {
60583d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney      rotation -= theta;
60683d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    }
60783d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0);
6081eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    bool stillAnimating = false;
60943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (i == animatedSelection && enableSelection) {
6101eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        float3 scale;
6111eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller        stillAnimating = getAnimatedScaleForSelected(&scale);
6125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
6135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
61451dd0196e4f3bd4086545f5bf30038ca9ad9ac27Bryan Mawhinney    rsMatrixLoadMultiply(matrix, &cards[i].matrix, matrix);
6151eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller    return stillAnimating;
6165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
618420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller/*
619f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma * Draws the requested mesh, with the appropriate program store in effect.
620f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma */
621f664659f79399e92025e1dfe1ffbb682ff05613cJim Shumastatic void drawMesh(rs_mesh mesh)
622f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma{
623f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    if (programStoresCardCount == 1) {
624f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        // Draw the entire mesh, with the only available program store
625f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsgBindProgramStore(programStoresCard[0].programStore);
626f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        rsgDrawMesh(mesh);
627f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    } else {
628f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        // Draw each primitive in the mesh with the corresponding program store
629f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        for (int i=0; i<programStoresCardCount; ++i) {
630f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma            if (programStoresCard[i].programStore.p != 0) {
631f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgBindProgramStore(programStoresCard[i].programStore);
632f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgDrawMesh(mesh, i);
633f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma            }
634f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma        }
635f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    }
636f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma}
637f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma
638f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma/*
639420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Draws cards around the Carousel.
640420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
641420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller */
642420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawCards(int64_t currentTime)
6435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
644420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
645420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
646420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
647420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    for (int i = cardCount-1; i >= 0; i--) {
6488fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
649420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // If this card was recently loaded, this will be < 1.0f until the animation completes
6503adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime,
6513adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                fadeInDuration);
6523adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            float overallAlpha = getAnimatedAlpha(cards[i].geometryTimeStamp, currentTime,
6533adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                cardCreationFadeDuration);
6543adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            if (animatedAlpha < 1.0f || overallAlpha < 1.0f) {
655420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                stillAnimating = true;
656420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
657420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
658420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Compute fade out for cards in the distance
659420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float positionAlpha;
660420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (rezInCardCount > 0.0f) {
661420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
662420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
663420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            } else {
664420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = 1.0f;
665420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
666420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
667420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Set alpha for blending between the textures
668420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
6693adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma            shaderConstants->overallAlpha = overallAlpha;
670420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
671420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
672b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // Bind the appropriate shader network.  If there's no alpha blend, then
673b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // switch to single shader for better performance.
6748441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey            const int state = cards[i].textureState;
6758441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey            const bool loaded = (state == STATE_LOADED) || (state == STATE_STALE) ||
6768441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                (state == STATE_UPDATING);
677b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
6783adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                if (overallAlpha < 1.0) {
6793adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(singleTextureBlendingFragmentProgram);
6803adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(singleTextureBlendingFragmentProgram, 0,
6813adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
6823adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
6833adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                } else {
6843adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(singleTextureFragmentProgram);
6853adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(singleTextureFragmentProgram, 0,
6863adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
6873adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
6883adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                }
6895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
6903adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                if (overallAlpha < 1.0) {
6913adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(multiTextureBlendingFragmentProgram);
6923adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureBlendingFragmentProgram, 0, loadingTexture);
6933adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureBlendingFragmentProgram, 1, loaded ?
6943adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
6953adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                } else {
6963adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindProgramFragment(multiTextureFragmentProgram);
6973adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
6983adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
6993adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                            cards[i].texture : loadingTexture);
7003adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                }
7015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
7045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
7051eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller            stillAnimating |= getMatrixForCard(&matrix, i, true);
7065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
7075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
708f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(cards[i].geometry);
7095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
710f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(loadingGeometry);
7115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
712f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                drawMesh(defaultGeometry);
7135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
7145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
715f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma                rsgBindProgramStore(programStoresCard[0].programStore);
7165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
7175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
7185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
7195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
7205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
7215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
724420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
7255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7274fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma/**
7284fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * Convert projection from normalized coordinates to pixel coordinates.
7294fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma *
7304fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * @return True on success, false on failure.
7314fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma */
7324fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumastatic bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) {
7334fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // This is probably cheaper than pre-multiplying with another matrix.
7344fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    if (screenCoord->w == 0.0f) {
7354fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        rsDebug("Bad transform while converting from normalized to pixel coordinates: ",
7364fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma            screenCoord);
7374fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        return false;
7384fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    }
7394fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    *screenCoord *= 1.0f / screenCoord->w;
7404fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x += 1.0f;
7414fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y += 1.0f;
7424fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z += 1.0f;
7434fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x = round(screenCoord->x * 0.5f * width);
7444fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y = round(screenCoord->y * 0.5f * height);
7454fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z = - 0.5f * screenCoord->z;
7464fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    return true;
7474fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma}
7484fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma
7497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller/*
7507cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * Draws a screen-aligned card with the exact dimensions from the detail texture.
7514a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney * This is used to display information about the object being displayed.
752420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
7537cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller */
754420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawDetails(int64_t currentTime)
7557cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
7567cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float width = rsgGetWidth();
7577cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float height = rsgGetHeight();
7587cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
759420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
760420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
7617cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // We'll be drawing in screen space, sampled on pixel centers
7627cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 projection, model;
7637cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
7647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadProjectionMatrix(&projection);
7657cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadIdentity(&model);
7667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadModelMatrix(&model);
7677cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
7687cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
7697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
7707cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
771420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // This can be done once...
772a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture);
773a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
774a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
775a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where details start fading from 1.0f
776a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle;
777a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where detail alpha is 0.0f
778a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
779420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
780a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
7818fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
7826af401bca5f8854524d128e9df5700035fae1160Jim Shuma            const int state = cards[i].detailTextureState;
7836af401bca5f8854524d128e9df5700035fae1160Jim Shuma            const bool isLoaded = (state == STATE_LOADED) || (state == STATE_STALE) ||
7846af401bca5f8854524d128e9df5700035fae1160Jim Shuma                (state == STATE_UPDATING);
7856af401bca5f8854524d128e9df5700035fae1160Jim Shuma            if (isLoaded && cards[i].detailTexture.p != 0) {
7867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
7877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
7884fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                // Compute position in screen space of top corner or bottom corner of card
789af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao                rsMatrixLoad(&model, &modelviewMatrix);
7901eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller                stillAnimating |= getMatrixForCard(&model, i, false);
7917cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rs_matrix4x4 matrix;
7927cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
793d443c88da4c7cf1947c12b26f111cb899cc8afe4Jim Miller
7944fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                int indexLeft, indexRight;
7954fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoord;
7964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
7974fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 0;
7984fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 1;
7994fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                } else {
8004fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 3;
8014fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 2;
8024fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
8034fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
8044fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
8054fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) {
8067cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    // this shouldn't happen
8077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    rsDebug("Bad transform: ", screenCoord);
8087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    continue;
8097cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
8104a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_VERTICAL) {
8114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    // If we're centering vertically, we'll need the other vertices too
8124a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
8134a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 3;
8144a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 2;
8154a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
8164a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 0;
8174a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 1;
8184a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
8194a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
8204a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
8214a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y
8224a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        + otherScreenLeft.y + otherScreenRight.y) / 4.;
8234a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                }
8244fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
8254fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
8264fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (debugDetails) {
8274fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordLeft);
8284fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordRight);
8294fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
8304fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                screenCoord = screenCoordLeft;
8314a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
8324fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
8334a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                } else if (detailTextureAlignment & CENTER_VERTICAL) {
834fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                    screenCoord.y -= round(rsAllocationGetDimY(cards[i].detailTexture) / 2.0f);
8354fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
8364a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_HORIZONTAL) {
837fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                    screenCoord.x += round((screenCoordRight.x - screenCoordLeft.x) / 2.0f -
838fe38385c1e5ce443adb962c066adeea185ad3d74Bryan Mawhinney                        rsAllocationGetDimX(cards[i].detailTexture) / 2.0f);
8394fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
8407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
841420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Compute alpha for gradually fading in details. Applied to both line and
842420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // detail texture. TODO: use a separate background texture for line.
8433adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp,
8443adf712e636f67265da7a6ff425c87e63fc20884Jim Shuma                    currentTime, fadeInDuration);
845420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                if (animatedAlpha < 1.0f) {
846420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    stillAnimating = true;
847420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                }
848420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
849a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                // Compute alpha based on position. We fade cards quickly so they cannot overlap
850a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i))
851a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                        / (endDetailFadeAngle - startDetailFadeAngle);
852a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = max(0.0f, positionAlpha);
853a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = min(1.0f, positionAlpha);
854a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
855a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
856a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
8578fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                if (blendedAlpha == 0.0f) {
8588fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    cards[i].detailVisible = false;
8598fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    continue; // nothing to draw
8608fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                } else {
8618fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                    cards[i].detailVisible = true;
8628fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                }
863b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
864b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(singleTextureFragmentProgram);
865b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
866b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(multiTextureFragmentProgram);
867b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
868a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
869420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Set alpha for blending between the textures
870a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                shaderConstants->fadeAmount = blendedAlpha;
871420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
872420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
8734a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // Draw line from the card to the detail texture.
8744a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // The line is drawn from the top or bottom left of the card
8754a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // to either the top of the screen or the top of the detail
8764a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // texture, depending on detailTextureAlignment.
8777c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (drawRuler) {
8784a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerTop;
8794a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerBottom;
8804a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
8814a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = screenCoord.y;
8824a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = 0;
8834a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
8844a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = height;
8854a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = screenCoord.y;
8864a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
8877c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float halfWidth = lineWidth * 0.5f;
8881eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller                    const float x0 = trunc(cards[i].detailLineOffset.x + screenCoord.x - halfWidth);
8891eccd028e704c15e842c1f23254d77a1a0a4cae0Jim Miller                    const float x1 = x0 + lineWidth;
890b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y0 = rulerBottom + yPadding;
891b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y;
892b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
893b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    if (blendedAlpha == 1.0f) {
894b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture);
895b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    } else {
896b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture);
897b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    }
898b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
899b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                            x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
9007c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
9017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
9027cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw the detail texture next to it using the offsets provided.
9037cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
9047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
9057cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offx = cards[i].detailTextureOffset.x;
9067cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offy = -cards[i].detailTextureOffset.y;
9074a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                const float textureTop = (detailTextureAlignment & VIEW_TOP)
9084a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        ? height : screenCoord.y;
909b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
910b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
911b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
912b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
9138fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[0].x = x0;
9148fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[0].y = height - y1;
9158fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[1].x = x1;
9168fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailTexturePosition[1].y = height - y0;
917b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
918b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
919b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
920b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
921b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture);
922b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
923b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
924b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
9257cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
9267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
9277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    }
928420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
9297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
9307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
9319afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerstatic void drawBackground()
9329afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller{
933b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    static bool toggle;
9349afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    if (backgroundTexture.p != 0) {
9359afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
9369afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rs_matrix4x4 projection, model;
9379afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
9389afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadProjectionMatrix(&projection);
9399afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadIdentity(&model);
9409afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadModelMatrix(&model);
941a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller        rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture);
9429afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        float z = -0.9999f;
9439afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgDrawQuad(
9449afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[0].x, cardVertices[0].y, z,
9459afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[1].x, cardVertices[1].y, z,
9469afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[2].x, cardVertices[2].y, z,
9479afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[3].x, cardVertices[3].y, z);
9489afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        updateCamera = true; // we mucked with the matrix.
9499afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    } else {
9509afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
951b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        if (debugRendering) { // for debugging - flash the screen so we know we're still rendering
952b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            rsgClearColor(toggle ? backgroundColor.x : 1.0f,
953b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.y : 0.0f,
954b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.z : 0.0f,
955b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        backgroundColor.w);
9569afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            toggle = !toggle;
957b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        } else {
9587cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
9597cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                   backgroundColor.w);
9609afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       }
9619afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    }
9629afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller}
9639afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller
9645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
9655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
9675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
9685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
9695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
9705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
9715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
9735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
9745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
9755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
9765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
9795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
9805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
9815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
982be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic float lastAngle = 0.0f;
9835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
9845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
985b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shumastatic float stopVelocity = 0.1f * M_PI / 180.0f; // slower than this: carousel stops
986b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shumastatic float selectionVelocity = 15.0f * M_PI / 180.0f; // faster than this: tap won't select
9875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityTracker;
9885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int velocityTrackerCount;
9895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
9905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
9925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
9935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
994be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// Computes a hit angle from the center of the carousel to a point on either a plane
995be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller// or on a cylinder. If neither is hit, returns false.
996be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Millerstatic bool hitAngle(float x, float y, float *angle)
997be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller{
998be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    Ray ray;
999be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    makeRayForPixelAt(&ray, &camera, x, y);
1000be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float t = FLT_MAX;
1001be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_PLANE && rayPlaneIntersect(&ray, &carouselPlane, &t)) {
1002be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 point = (ray.position + t*ray.direction);
1003be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 direction = point - carouselPlane.point;
1004be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        *angle = atan2(direction.x, direction.z);
1005be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (debugSelection) rsDebug("Plane Angle = ", degrees(*angle));
1006be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        return true;
1007be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    } else if ((dragModel == DRAG_MODEL_CYLINDER_INSIDE || dragModel == DRAG_MODEL_CYLINDER_OUTSIDE)
1008be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            && rayCylinderIntersect(&ray, &carouselCylinder, &t)) {
1009be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 point = (ray.position + t*ray.direction);
1010be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        const float3 direction = point - carouselCylinder.center;
1011be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        *angle = atan2(direction.x, direction.z);
1012be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (debugSelection) rsDebug("Cylinder Angle = ", degrees(*angle));
1013be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        return true;
1014be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    }
1015be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    return false;
1016be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller}
1017be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller
10185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
10195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1020be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float result;
1021be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    float angle;
1022be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (hitAngle(x, y, &angle)) {
1023be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        result = angle - lastAngle;
1024be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        // Handle singularity where atan2 switches between +- PI
1025be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        if (result < -M_PI) {
1026be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            result += 2.0f * M_PI;
1027be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        } else if (result > M_PI) {
1028be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller            result -= 2.0f * M_PI;
1029be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        }
1030be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        lastAngle = angle;
1031be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    } else {
1032be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        // If we didn't hit anything or drag model wasn't plane or cylinder, we use screen delta
1033be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller        result = dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
1034be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    }
1035be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    return result;
10365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
10395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
10415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1043370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic int doSelection(float x, float y)
10445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
1046b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (makeRayForPixelAt(&ray, &camera, x, y)) {
10475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
10485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
10495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
10515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1053370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void sendAnimationStarted() {
105443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    rsSendToClient(CMD_ANIMATION_STARTED);
105543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
105643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
1057370b177eb74cd8a7d9a2ab06a5ee8bb3ed25f74fStephen Hinesstatic void sendAnimationFinished() {
1058a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    float data[1];
1059a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    data[0] = radiansToCarouselRotationAngle(bias);
1060a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    rsSendToClient(CMD_ANIMATION_FINISHED, (int*) data, sizeof(data));
1061a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
1062a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
10631882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doStart(float x, float y, long eventTime)
10645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
106543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    touchPosition = lastPosition = (float2) { x, y };
1066be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    lastAngle = hitAngle(x,y, &lastAngle) ? lastAngle : 0.0f;
1067b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma    enableSelection = fabs(velocity) < selectionVelocity;
10685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
10695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTracker = 0.0f;
10705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTrackerCount = 0;
10711882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    touchTime = lastTime = eventTime;
10725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
107343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = true;
1074e26fadd96cff3251d7ed391e10ab9c372ce5e825Bryan Mawhinney    overscroll = false;
107543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    animatedSelection = doSelection(x, y); // used to provide visual feedback on touch
10765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10781882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doStop(float x, float y, long eventTime)
10795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10808b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
108143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
108243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (enableSelection) {
10838fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        int data[3];
10848fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        int selection;
10858fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        float2 point;
10868fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma
10878fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if ((selection = intersectDetailTexture(x, y, &point)) != -1) {
10888fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            if (debugSelection) rsDebug("Selected detail texture on doStop():", selection);
10898fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[0] = selection;
10908fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[1] = point.x;
10918fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            data[2] = point.y;
10928fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            rsSendToClientBlocking(CMD_DETAIL_SELECTED, data, sizeof(data));
10938fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        }
10948fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        else if ((selection = doSelection(x, y))!= -1) {
109543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (debugSelection) rsDebug("Selected item on doStop():", selection);
109643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            data[0] = selection;
109743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
109843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
109943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = false;
11005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
110143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // TODO: move velocity tracking to Java
11025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        velocity = velocityTrackerCount > 0 ?
11035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
1104b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma        if (fabs(velocity) > stopVelocity) {
11055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
11065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
11075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
110843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection = false;
11091882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    lastTime = eventTime;
111043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = false;
11115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1113594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumavoid doLongPress()
1114594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma{
1115594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    int64_t currentTime = rsUptimeMillis();
1116594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    updateAllocationVars(cards);
111743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Selection happens for most recent position detected in doMotion()
1118e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma    if (enableSelection && animatedSelection != -1) {
1119e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        if (debugSelection) rsDebug("doLongPress(), selection = ", animatedSelection);
1120e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        int data[7];
1121e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[0] = animatedSelection;
1122e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[1] = lastPosition.x;
1123e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[2] = lastPosition.y;
1124e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[3] = cards[animatedSelection].detailTexturePosition[0].x;
1125e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[4] = cards[animatedSelection].detailTexturePosition[0].y;
1126e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[5] = cards[animatedSelection].detailTexturePosition[1].x;
1127e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        data[6] = cards[animatedSelection].detailTexturePosition[1].y;
1128594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data));
1129e8cab95c5f73ddf6843d82793decc3adb4692860Jim Shuma        enableSelection = false;
1130594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    }
1131594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    lastTime = rsUptimeMillis();
1132594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma}
1133594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma
11341882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Millervoid doMotion(float x, float y, long eventTime)
11355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
113614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float highBias = maximumBias();
113714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float lowBias = minimumBias();
11385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
113943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (!enableSelection) {
114043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias += deltaOmega;
114114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        bias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
114214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma                highBias + wedgeAngle(OVERSCROLL_SLOTS));
114343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
114443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float2 delta = (float2) { x, y } - touchPosition;
114543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float distance = sqrt(dot(delta, delta));
114643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool inside = (distance < selectionRadius);
114743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection &= inside;
114843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastPosition = (float2) { x, y };
11491882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    float dt = deltaTimeInSeconds(eventTime);
11505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
11515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
115243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocityTracker += v;
115343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocityTrackerCount++;
11545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
1155c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    velocity = velocityTrackerCount > 0 ?
1156c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma                (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
11571882cebdc1b7b0551189ca33fb7cb77ef10c988bJim Miller    lastTime = eventTime;
11585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
11615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
11625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
116343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float EPSILON = 1.0e-6f;
116443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float tmin = 0.0f;
11655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
116743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float* tout)
11685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
11695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
11705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
11715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
11725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
11745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
11755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
11775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
11785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
11805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
11815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
11835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
11845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
11855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
11875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
11885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
11895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
11905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
11915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
119343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
119443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/plane intersection. Returns false if no intersection found.
119543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1196b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
119743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayPlaneIntersect(Ray* ray, Plane* plane, float* tout)
1198b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
119943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float denom = dot(ray->direction, plane->normal);
120043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (fabs(denom) > EPSILON) {
120143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float t = - (plane->constant + dot(ray->position, plane->normal)) / denom;
120243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (t > tmin && t < *tout) {
120343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            *tout = t;
120443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            return true;
120543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
120643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
120743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1208b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1209b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
121043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
121143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/cylindr intersection. There are 0, 1 or 2 hits.
121243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Returns true and sets *tout to the closest point or
121343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// returns false if no intersection found.
121443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1215b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
121643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout)
1217b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
121843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float A = ray->direction.x * ray->direction.x + ray->direction.z * ray->direction.z;
121943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (A < EPSILON) return false; // ray misses
122043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
122143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Compute quadratic equation coefficients
122243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float B = 2.0f * (ray->direction.x * ray->position.x
122343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->direction.z * ray->position.z);
122443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float C = ray->position.x * ray->position.x
122543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->position.z * ray->position.z
122643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            - cylinder->radius * cylinder->radius;
122743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float disc = B*B - 4*A*C;
122843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
122943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (disc < 0.0f) return false; // ray misses
123043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    disc = sqrt(disc);
123143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float denom = 2.0f * A;
123243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
123343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Nearest point
123443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t1 = (-B - disc) / denom;
1235be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_CYLINDER_OUTSIDE && t1 > tmin && t1 < *tout) {
123643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t1;
123743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
123843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
123943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
124043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Far point
124143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t2 = (-B + disc) / denom;
1242be5482f170e191aa98a3c2ecefdeaf936b7df412Jim Miller    if (dragModel == DRAG_MODEL_CYLINDER_INSIDE && t2 > tmin && t2 < *tout) {
124343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t2;
124443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
124543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
124643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1247b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1248b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1249b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates.
12505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1251b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1252b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y)
12535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
12545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
12555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
1256b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.from:", cam->from);
1257b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.at:", cam->at);
1258b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.dir:", normalize(cam->at - cam->from));
12595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
12625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
1263b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float u = x / rsgGetWidth();
1264b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float v = 1.0f - (y / rsgGetHeight());
1265b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float aspect = (float) rsgGetWidth() / rsgGetHeight();
1266b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f));
1267b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dir = normalize(cam->at - cam->from);
1268b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 du = tanfov2 * normalize(cross(dir, cam->up));
1269b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dv = tanfov2 * normalize(cross(du, dir));
1270b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    du *= aspect;
1271b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
1272b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = cam->from;
1273b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
1274b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1275b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (vector math) = ", rayDir);
12765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1278b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1279b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
1280b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return true;
1281b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1282b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1283b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a model view and projection matrix.
1284b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1285b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1286b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y)
1287b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
1288b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rs_matrix4x4 pm = *model;
1289b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsMatrixLoadMultiply(&pm, proj, model);
1290b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (!rsMatrixInverse(&pm)) {
1291b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("ERROR: SINGULAR PM MATRIX", 0);
1292b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        return false;
12935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
1294b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float width = rsgGetWidth();
1295b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float height = rsgGetHeight();
1296b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winx = 2.0f * x / width - 1.0f;
1297b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winy = 2.0f * y / height - 1.0f;
1298b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1299b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
1300b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 at = { winx, winy, 1.0f, 1.0f };
1301b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1302b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye = rsMatrixMultiply(&pm, eye);
1303b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye *= 1.0f / eye.w;
13045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1305b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at = rsMatrixMultiply(&pm, at);
1306b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at *= 1.0f / at.w;
1307b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1308b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = { eye.x, eye.y, eye.z };
1309b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 atPoint = { at.x, at.y, at.z };
1310b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(atPoint - rayPoint);
1311b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1312b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winx: ", winx);
1313b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winy: ", winy);
1314b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray position (transformed) = ", eye);
1315b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (transformed) = ", rayDir);
1316b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    }
1317b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1318b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
13195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
13205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
13215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13228fd40311898a9ec759a76f021642f43e617e38c4Jim Shumastatic int intersectDetailTexture(float x, float y, float2 *tapCoordinates)
13238fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma{
13248fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    for (int id = 0; id < cardCount; id++) {
13258fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[id].detailVisible) {
13268fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int x0 = cards[id].detailTexturePosition[0].x;
13278fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int y0 = cards[id].detailTexturePosition[0].y;
13288fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int x1 = cards[id].detailTexturePosition[1].x;
13298fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            const int y1 = cards[id].detailTexturePosition[1].y;
13308fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
13318fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                float2 point = { x - x0, y - y0 };
13328fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                *tapCoordinates = point;
13338fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                return id;
13348fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            }
13358fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        }
13368fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    }
13378fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma    return -1;
13388fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma}
13398fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma
13405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
13415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
13425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
13435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
13448fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[id].cardVisible) {
13455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
13465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
13475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
13495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
13507c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, id, true);
13515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
13525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
13535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
13545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
13555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
13565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
13575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
13585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
13595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
13605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
13645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
13655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
13665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
13675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
13695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
13705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
13715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
13725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
137443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// simple physics model.  If the cards are still in motion, returns true.
137543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool doPhysics(float dt)
137643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
137743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
137843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
137943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    dt /= N;
138043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    for (int i = 0; i < N; i++) {
138143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Force friction - always opposes motion
138243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Ff = -frictionCoeff * velocity;
138343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
138443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Restoring force to match cards with slots
138543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float theta = startAngle + bias;
138643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float dtheta = 2.0f * M_PI / slotCount;
138743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float position = theta / dtheta;
138843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float fraction = position - floor(position); // fractional position between slots
138943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float x;
139043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (fraction > 0.5f) {
139143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = - (1.0f - fraction);
139243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
139343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = fraction;
139443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
139543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Fr = - springConstant * x;
139643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
139743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // compute velocity
139843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float momentum = mass * velocity + (Ff + Fr)*dt;
139943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocity = momentum / mass;
140043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias += velocity * dt;
140143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
1402b2c785780ecbe79a5b7ba558b21985f956458c8cJim Shuma    return fabs(velocity) > stopVelocity;
140343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
140443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
140543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float easeOut(float x)
140643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
140743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return x;
140843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
140943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
141043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes the next value for bias using the current animation (physics or overscroll)
14115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
14125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
141343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    static const float biasMin = 1e-4f; // close enough if we're within this margin of result
14145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
141543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float dt = deltaTimeInSeconds(currentTime);
14165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
141743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (dt <= 0.0f) {
141843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (debugRendering) rsDebug("Time delta was <= 0", dt);
141943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
14205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
142214d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float highBias = maximumBias();
142314d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    const float lowBias = minimumBias();
142443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool stillAnimating = false;
142543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (overscroll) {
142614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        if (bias > highBias) {
142714d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            bias -= 4.0f * dt * easeOut((bias - highBias) * 2.0f);
142814d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            if (fabs(bias - highBias) < biasMin) {
142914d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma                bias = highBias;
143043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            } else {
143143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                stillAnimating = true;
143243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            }
143314d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        } else if (bias < lowBias) {
143414d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            bias += 4.0f * dt * easeOut((lowBias - bias) * 2.0f);
143514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            if (fabs(bias - lowBias) < biasMin) {
143614d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma                bias = lowBias;
143743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            } else {
143843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                stillAnimating = true;
143943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            }
144043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
144143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            overscroll = false;
144243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
144343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    } else {
144443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = doPhysics(dt);
144514d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma        overscroll = bias > highBias || bias < lowBias;
1446cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller        if (overscroll) {
1447cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller            velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
1448cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller        }
1449c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    }
145014d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma    float newbias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS),
145114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            highBias + wedgeAngle(OVERSCROLL_SLOTS));
145243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (newbias != bias) { // we clamped
145343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocity = 0.0f;
145443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        overscroll = true;
145543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
145643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bias = newbias;
145743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return stillAnimating;
14585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
14595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
14615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
14625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
14635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cullCards()
14645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
14654fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // TODO(jshuma): Instead of fully fetching prefetchCardCount cards, make a distinction between
14664fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // STATE_LOADED and a new STATE_PRELOADING, which will keep the textures loaded but will not
14674fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // attempt to actually draw them.
14684fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const int prefetchCardCountPerSide = prefetchCardCount / 2;
14694fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaFirst = slotPosition(-prefetchCardCountPerSide);
1470198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelected = slotPosition(0);
1471198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f;
1472198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedLow = thetaSelected - thetaHalfAngle;
1473198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedHigh = thetaSelected + thetaHalfAngle;
14744fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaLast = slotPosition(visibleSlotCount - 1 + prefetchCardCountPerSide);
14755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int count = 0;
14775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
14785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
14795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
14805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
148114d2c1ec52bb04b5120c2bfdd1a8811a238573ceJim Shuma            if (p >= thetaFirst && p < thetaLast || p <= thetaFirst && p > thetaLast) {
14828fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].cardVisible = true;
14838fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                // cards[i].detailVisible will be set at draw time
14845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                count++;
14855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
14868fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].cardVisible = false;
14878fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma                cards[i].detailVisible = false;
14885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
14895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
14905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
14915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
14928fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            cards[i].cardVisible = true;
14938fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma            // cards[i].detailVisible will be set at draw time
14945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            count++;
14955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
14965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return count;
14985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
14995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
15015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
1502420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic void updateCardResources(int64_t currentTime)
15035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1504a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
15055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
15068fd40311898a9ec759a76f021642f43e617e38c4Jim Shuma        if (cards[i].cardVisible) {
150743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp);
15082ba04e061b52c488a154739379501dc833e39f79Jim Miller
15095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request texture from client if not loaded
15105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_INVALID) {
15115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
15125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
15135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
15145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_LOADING;
15155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
15167cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
15177cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
15188441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey            } else if (cards[i].textureState == STATE_STALE) {
15198441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                data[0] = i;
15208441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
15218441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                if (enqueued) {
15228441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                    cards[i].textureState = STATE_UPDATING;
15238441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                } else {
15248441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
15258441b1025afe64748f9e3483baacee92171bbfa3Jeff Sharkey                }
15267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
15277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // request detail texture from client if not loaded
15287cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_INVALID) {
15297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
15307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
15317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
15327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_LOADING;
15337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
15347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
15355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
15366af401bca5f8854524d128e9df5700035fae1160Jim Shuma            } else if (cards[i].detailTextureState == STATE_STALE) {
15376af401bca5f8854524d128e9df5700035fae1160Jim Shuma                data[0] = i;
15386af401bca5f8854524d128e9df5700035fae1160Jim Shuma                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
15396af401bca5f8854524d128e9df5700035fae1160Jim Shuma                if (enqueued) {
15406af401bca5f8854524d128e9df5700035fae1160Jim Shuma                    cards[i].detailTextureState = STATE_UPDATING;
15416af401bca5f8854524d128e9df5700035fae1160Jim Shuma                } else {
15426af401bca5f8854524d128e9df5700035fae1160Jim Shuma                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
15436af401bca5f8854524d128e9df5700035fae1160Jim Shuma                }
15445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request geometry from client if not loaded
15465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_INVALID) {
15475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
15485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
15495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
15505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_LOADING;
15515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
15527cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
15535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
15545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
15565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
1557dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].textureState != STATE_INVALID) {
15585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
15595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
15605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
15615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
1562420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].textureTimeStamp = currentTime;
15635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
15647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
15657cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
15667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
15677cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // ask the host to remove the detail texture
15687cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState != STATE_INVALID) {
15697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
15707cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
15717cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
15727cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_INVALID;
1573420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].detailTextureTimeStamp = currentTime;
15747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
15757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
15765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
15775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
1579dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].geometryState != STATE_INVALID) {
15805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
15815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
15825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
15835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
15845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
15857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
15865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
15875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
15885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
15895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
15905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
15915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
15925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
15935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
15945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
15955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
15965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
15975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
15985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
15995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
16005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    color(1.0f, 0.0f, 0.0f, 1.0f);
16015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
16025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
16035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
16045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
16055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
1606b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (makeRayForPixelAt(&ray, &camera, posX, posY)) {
16075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
16085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
16095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
16105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
16115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
16125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
16135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
16145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
16155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
16175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
16185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
16205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
1621b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp);
1622b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp);
1623b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp);
16245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16258b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
16265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1627a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindProgramFragment(singleTextureFragmentProgram);
1628fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // rsgClearDepth() currently follows the value of glDepthMask(), so it's disabled when
1629fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // the mask is disabled. We may want to change the following to always draw w/o Z for
1630fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // the background if we can guarantee the depth buffer will get cleared and
1631fb179e7afd8f02be63061b478b0283e3085fc25fJim Miller    // there's a performance advantage.
1632f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsgBindProgramStore(programStoreBackground);
16339afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    drawBackground();
16345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
16365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
163743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool stillAnimating = (currentTime - touchTime) <= ANIMATION_SCALE_TIME;
163843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
163943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (!isDragging && animating) {
164043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = updateNextPosition(currentTime);
164143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
164243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
164343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastTime = currentTime;
16445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
16465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1647420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    updateCardResources(currentTime);
16485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1649bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    // Draw cards opaque only if requested, and always draw detail textures with blending.
1650bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawCards(currentTime);
1651f664659f79399e92025e1dfe1ffbb682ff05613cJim Shuma    rsgBindProgramStore(programStoreDetail);
1652bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawDetails(currentTime);
16535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
165443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (stillAnimating != animating) {
165543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (stillAnimating) {
165643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we just started animating
165743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationStarted();
165843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
165943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we were animating but stopped animating just now
166043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationFinished();
166143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
166243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = stillAnimating;
166343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
166443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
166543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (debugRays) {
16665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
16675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
16685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
16695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
16705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
167143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return animating ? 1 : 0;
16725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1673