carousel.rs revision 4a8736e22d7b40ab9dfa3fbd8a10de92144912b3
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 {
248b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    // *** Update copyCard 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
295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_mesh geometry;
307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 matrix; // custom transform for this card/geometry
317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int textureState;  // whether or not the primary card texture is loaded.
327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int detailTextureState; // whether or not the detail for the card is loaded.
335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int geometryState; // whether or not geometry is loaded
345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int visible; // not bool because of packing bug?
3543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    int64_t textureTimeStamp; // time when this texture was last updated, in seconds
3643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    int64_t detailTextureTimeStamp; // time when this texture was last updated, in seconds
375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Plane_s {
4543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 point;
4643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 normal;
4743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float constant;
4843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Plane;
4943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
5043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millertypedef struct Cylinder_s {
5143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float3 center; // center of a y-axis-aligned infinite cylinder
5243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float radius;
5343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller} Cylinder;
5443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
65420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millertypedef struct FragmentShaderConstants_s {
66420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    float fadeAmount;
67420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller} FragmentShaderConstants;
68420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
774a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java ***
784a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyenum {
794a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is centered vertically with respect to the card **/
804a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_VERTICAL = 1,
814a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the top edge of the carousel view **/
824a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_TOP = 1 << 1,
834a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
844a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VIEW_BOTTOM = 1 << 2,
854a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned above the card (not yet implemented) **/
864a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    ABOVE = 1 << 3,
874a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Detail is positioned below the card **/
884a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    BELOW = 1 << 4,
894a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control vertical alignment **/
904a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    VERTICAL_ALIGNMENT_MASK = 0xff,
914a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
924a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
934a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is centered horizontally with respect to either the top or bottom
944a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * extent of the card, depending on whether the detail is above or below the card.
954a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    CENTER_HORIZONTAL = 1 << 8,
974a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
984a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the left edge of either the top or the bottom of
994a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1004a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1014a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    LEFT = 1 << 9,
1024a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /**
1034a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * Detail is aligned with the right edge of either the top or the bottom of
1044a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * the card, depending on whether the detail is above or below the card.
1054a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     * (not yet implemented)
1064a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney     */
1074a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    RIGHT = 1 << 10,
1084a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    /** Mask that selects those bits that control horizontal alignment **/
1094a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney    HORIZONTAL_ALIGNMENT_MASK = 0xff00,
1104a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney};
1114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney
1125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
1135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
114594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumastatic const int CMD_CARD_LONGPRESS = 110;
1155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
1165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
1175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
1185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
1195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
1205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
1217cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REQUEST_DETAIL_TEXTURE = 600;
1227cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
1237cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_PING = 1000;
1245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
1265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
1275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this %
12843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float OVERSCROLL_SLOTS = 1.0f; // amount of allowed overscroll (in slots)
1295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
1317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugCamera = false; // dumps ray/camera coordinate stuff
13243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugSelection = false; // logs selection events
1337cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugTextureLoading = false; // for debugging texture load/unload
1347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugGeometryLoading = false; // for debugging geometry load/unload
1357c09ccce478100d75e4427d87866ff19d758ae7aJim Shumaconst bool debugDetails = false; // for debugging detail texture geometry
136b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerconst bool debugRendering = false; // flashes display when the frame changes
13743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerconst bool debugRays = false; // shows visual depiction of hit tests, See renderWithRays().
1385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
1405ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
1418b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich// TODO: remove tmpCards code when allocations support resizing
1428b55d7500c1e5a88c415dae8dcead16b152d7929Jack PalevichCard_t *tmpCards; // temporary array used to prevent flashing when we add more cards
1435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
1445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
1455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
1465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
1477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerint visibleDetailCount; // number of visible detail textures to show
1484fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumaint prefetchCardCount; // how many cards to keep in memory
1494a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinneyint detailTextureAlignment; // How to align detail texture with respect to card
150bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shumabool drawCardsWithBlending; // Enable blending while drawing cards (for translucent card textures)
1517c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawRuler; // whether to draw a ruler from the card to the detail texture
1525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
1535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
15483d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinneybool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle
155c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat swaySensitivity; // how much to rotate cards in relation to the rotation velocity
156c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat frictionCoeff; // how much to slow down the carousel over time
157c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat dragFactor; // a scale factor for how sensitive the carousel is to user dragging
158420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerint fadeInDuration; // amount of time (in ms) for smoothly switching out textures
159420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
160a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerfloat detailFadeRate; // rate at which details fade as they move into the distance
16143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerfloat4 backgroundColor;
1625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_store programStore;
163bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shumars_program_store programStoreOpaque;
164bf39450b962d91ec78af53db39826d55ddb39902Jim Shumars_program_store programStoreDetail;
165a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment singleTextureFragmentProgram;
166a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment multiTextureFragmentProgram;
1675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
1685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
1695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
1705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
1719afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerrs_allocation backgroundTexture; // drawn behind everything, if set
1727cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerrs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
173420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_allocation detailLoadingTexture; // used when detail texture is loading
1745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
1755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
1765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
1775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
178420b44b8b11ec1c309ea130e69a6876325dbfef9Jim MillerFragmentShaderConstants* shaderConstants;
179420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_sampler linearClamp;
1805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
181594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma#pragma rs export_func(createCards, copyCards, lookAt)
182594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma#pragma rs export_func(doStart, doStop, doMotion, doLongPress, doSelection)
18343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller#pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera)
184a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich#pragma rs export_func(setCarouselRotationAngle)
1855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
1875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
1885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
1895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool initialized;
1905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
19143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int animatedSelection = -1;
19243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic int currentFirstCard = -1;
1935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t touchTime = -1;  // time of first touch (see doStart())
1945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
19543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float2 touchPosition; // position of first touch, as defined by last call to doStart(x,y)
196c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float velocity = 0.0f;  // angular velocity in radians/s
19743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool overscroll = false; // whether we're in the overscroll animation
19843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool isDragging = false; // true while the user is dragging the carousel
19943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float selectionRadius = 50.0f; // movement greater than this will result in no selection
20043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool enableSelection = false; // enabled until the user drags outside of selectionRadius
20143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
20243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Default plane of the carousel. Used for angular motion estimation in view.
20343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic Plane carouselPlane = {
20443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 0.0f, 0.0f }, // point
20543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       { 0.0f, 1.0f, 0.0f }, // normal
20643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller       0.0f // plane constant (= -dot(P, N))
20743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller};
2085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2093df59346f395434454d310b070fff195089fbaf1Jim Miller// Because allocations can't have 0 dimensions, we have to track whether or not
2103df59346f395434454d310b070fff195089fbaf1Jim Miller// cards are valid separately.
2113df59346f395434454d310b070fff195089fbaf1Jim Miller// TODO: Remove this dependency once allocations can have a zero dimension.
2123df59346f395434454d310b070fff195089fbaf1Jim Millerstatic bool cardAllocationValid = false;
2133df59346f395434454d310b070fff195089fbaf1Jim Miller
2145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
2155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
2165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
2175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
2185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
2195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
2205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
2235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
2245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
2255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
2265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
2275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
2285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
2295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
2305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
2315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
2325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
2345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
235b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
236b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
237b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
238b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y);
2395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
2405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
2425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
2435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
2442ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) rsDebug("Renderscript: init()", 0);
2455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
2465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
2475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
2487cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    visibleDetailCount = 3;
2495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
2505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    radius = 1.0f;
2515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
25283d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    cardsFaceTangent = false;
2535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
2545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
255b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
2563df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = false;
2573df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = 0;
258420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    fadeInDuration = 250;
259420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
260a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    detailFadeRate = 0.5f; // fade details over this many slot positions.
2615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2638b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevichstatic void updateAllocationVars(Card_t* newcards)
2645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
2668b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rs_allocation cardAlloc = rsGetAllocation(newcards);
2675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: use new rsIsObject()
2683df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = (cardAllocationValid && cardAlloc.p != 0) ? rsAllocationGetDimX(cardAlloc) : 0;
2695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid createCards(int n)
2725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2732ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) {
2742ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsDebug("*** CreateCards with count", n);
2752ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
2762ba04e061b52c488a154739379501dc833e39f79Jim Miller
2772ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Since allocations can't have 0-size, we track validity ourselves based on the call to
2782ba04e061b52c488a154739379501dc833e39f79Jim Miller    // this method.
2793df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = n > 0;
2802ba04e061b52c488a154739379501dc833e39f79Jim Miller
2815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
2828b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
2838b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich}
2848b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich
2858b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevichvoid copyCard(Card_t* dest, Card_t * src)
2868b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
2878b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->texture, src->texture);
2888b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->detailTexture, src->detailTexture);
2898b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureOffset = src->detailTextureOffset;
290b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    dest->detailLineOffset = src->detailLineOffset;
2918b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->geometry, src->geometry);
2928b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->matrix = src->matrix;
2938b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->textureState = src->textureState;
2948b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureState = src->detailTextureState;
2958b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->geometryState = src->geometryState;
2968b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->visible = src->visible;
2978b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->textureTimeStamp = src->textureTimeStamp;
2988b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureTimeStamp = src->detailTextureTimeStamp;
2992ba04e061b52c488a154739379501dc833e39f79Jim Miller}
3008b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich
3012ba04e061b52c488a154739379501dc833e39f79Jim Millervoid initCard(Card_t* card)
3028b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
3032ba04e061b52c488a154739379501dc833e39f79Jim Miller    static const float2 zero = {0.0f, 0.0f};
3042ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->texture);
3052ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->detailTexture);
3062ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureOffset = zero;
3072ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailLineOffset = zero;
3082ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->geometry);
3092ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsMatrixLoadIdentity(&card->matrix);
3102ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureState = STATE_INVALID;
3112ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureState = STATE_INVALID;
3122ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->geometryState = STATE_INVALID;
3132ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->visible = false;
3142ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureTimeStamp = 0;
3152ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureTimeStamp = 0;
3162ba04e061b52c488a154739379501dc833e39f79Jim Miller}
3172ba04e061b52c488a154739379501dc833e39f79Jim Miller
3182ba04e061b52c488a154739379501dc833e39f79Jim Millervoid copyCards(int n)
3192ba04e061b52c488a154739379501dc833e39f79Jim Miller{
3202ba04e061b52c488a154739379501dc833e39f79Jim Miller    unsigned int oldsize = cardAllocationValid ? rsAllocationGetDimX(rsGetAllocation(cards)) : 0;
3218b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    unsigned int newsize = rsAllocationGetDimX(rsGetAllocation(tmpCards));
3222ba04e061b52c488a154739379501dc833e39f79Jim Miller    unsigned int copysize = min(oldsize, newsize);
3232ba04e061b52c488a154739379501dc833e39f79Jim Miller
3242ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Copy existing cards
3252ba04e061b52c488a154739379501dc833e39f79Jim Miller    for (int i = 0; i < copysize; i++) {
3262ba04e061b52c488a154739379501dc833e39f79Jim Miller        if (debugTextureLoading) {
3278b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich            rsDebug("copying card ", i);
3288b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich        }
3292ba04e061b52c488a154739379501dc833e39f79Jim Miller        copyCard(tmpCards + i, cards + i);
3302ba04e061b52c488a154739379501dc833e39f79Jim Miller        // Release these now so we don't have to wait for GC for cards allocation.
3312ba04e061b52c488a154739379501dc833e39f79Jim Miller        // Assumes we're done with the cards allocation structure.
3322ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].texture);
3332ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].detailTexture);
3342ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].geometry);
3352ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].textureState = STATE_INVALID;
3362ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].detailTextureState = STATE_INVALID;
3372ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].geometryState = STATE_INVALID;
3388b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    }
3392ba04e061b52c488a154739379501dc833e39f79Jim Miller
3402ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Initialize remaining cards.
3412ba04e061b52c488a154739379501dc833e39f79Jim Miller    int first = cardAllocationValid ? min(oldsize, newsize) : 0;
3422ba04e061b52c488a154739379501dc833e39f79Jim Miller    for (int k = first; k < newsize; k++) {
3432ba04e061b52c488a154739379501dc833e39f79Jim Miller        initCard(tmpCards + k);
3442ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
3452ba04e061b52c488a154739379501dc833e39f79Jim Miller
3462ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Since allocations can't have 0-size, we use the same trick as createCards() where
3472ba04e061b52c488a154739379501dc833e39f79Jim Miller    // we track validity ourselves. Grrr.
3482ba04e061b52c488a154739379501dc833e39f79Jim Miller    cardAllocationValid = n > 0;
3492ba04e061b52c488a154739379501dc833e39f79Jim Miller
3508b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(tmpCards);
3515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
353420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller// Computes an alpha value for a card using elapsed time and constant fadeInDuration
354420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat getAnimatedAlpha(int64_t startTime, int64_t currentTime)
355420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller{
356420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double timeElapsed = (double) (currentTime - startTime); // in ms
357420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double alpha = (double) timeElapsed / fadeInDuration;
358420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return min(1.0f, (float) alpha);
359420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller}
360420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
3615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return angle for position p. Typically p will be an integer position, but can be fractional.
3625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float cardPosition(float p)
3635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + bias + 2.0f * M_PI * p / slotCount;
3655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
3685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float slotPosition(float p)
3695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + 2.0f * M_PI * p / slotCount;
3715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
373f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller// Returns total angle for given number of cards
374f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Millerstatic float wedgeAngle(float cards)
375f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller{
376f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    return cards * 2.0f * M_PI / slotCount;
377f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller}
378f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller
379a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from carousel rotation angle (in card slot units) to radians.
380a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float carouselRotationAngleToRadians(float carouselRotationAngle)
381a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
382a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -wedgeAngle(carouselRotationAngle);
383a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
384a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
385a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich// convert from radians to carousel rotation angle (in card slot units).
386a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichstatic float radiansToCarouselRotationAngle(float angle)
387a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich{
388a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    return -angle * slotCount / ( 2.0f * M_PI );
389a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
390a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
391a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
3925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return the lowest slot number for a given angular position.
3935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cardIndex(float angle)
3945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
3965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
3995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
4005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
4015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
4025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
4035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
4045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
4055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
4065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
4075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
4095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
4105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
4115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
4125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
4135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
4145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
4155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
4165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
4175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
4185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
4215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
4225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
4245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
4255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
4265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
4275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
4285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
4295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
4325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
4335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
4345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
4365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
4375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
4385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
4395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
4405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
4415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
4425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
4435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
4445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
4455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
4465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
4475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
4485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
4495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
4505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
4515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
4525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
4535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
4545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
4555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
4585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4593df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
460c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].texture, texture);
4617cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
462420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].textureTimeStamp = rsUptimeMillis();
4637cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
4647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
465b378af500b36226635b6343b1d5009ee9af44fc1Jim Millervoid setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
4667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
4673df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
468c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].detailTexture, texture);
4697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.x = offx;
4707cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.y = offy;
471b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.x = loffx;
472b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.y = loffy;
4737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
474420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].detailTextureTimeStamp = rsUptimeMillis();
4755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
4785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4793df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
480c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].geometry, geometry);
4815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
4825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
4835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
4845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
4855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
487a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichvoid setCarouselRotationAngle(float carouselRotationAngle) {
488a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    bias = carouselRotationAngleToRadians(carouselRotationAngle);
489198a060d650bc849ef0f25b597888fac9546803bJack Palevich}
490198a060d650bc849ef0f25b597888fac9546803bJack Palevich
4915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float3 getAnimatedScaleForSelected()
4925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t dt = (rsUptimeMillis() - touchTime);
4945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f;
4955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float3 one = { 1.0f, 1.0f, 1.0f };
4965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return one + fraction * SELECTED_SCALE_FACTOR;
4975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
499c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
500c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma//    P(t) = 1 / (1 + e^(-t))
501c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Parameter t: Any real number
502c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Returns: A float in the range (0,1), with P(0.5)=0
503c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float logistic(float t) {
504af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao    return 1.f / (1.f + exp(-t));
505c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
506c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
5077c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic float getSwayAngleForVelocity(float v, bool enableSway)
508c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma{
5097c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float sway = 0.0f;
5107c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma
5117c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    if (enableSway) {
512d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak
513d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        sway = range * (logistic(-v * swaySensitivity) - 0.5f);
5147c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    }
515c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
516c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    return sway;
517c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
518c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
5197c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// matrix: The output matrix.
5207c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// i: The card we're getting the matrix for.
5217c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
5227c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic void getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway)
5235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
5257c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
5265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
5275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, radius, 0, 0);
52883d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    float rotation = cardRotation + swayAngle;
52983d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    if (!cardsFaceTangent) {
53083d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney      rotation -= theta;
53183d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    }
53283d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0);
53343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (i == animatedSelection && enableSelection) {
5345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 scale = getAnimatedScaleForSelected();
5355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
5365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
5375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: apply custom matrix for cards[i].geometry
5385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
540420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller/*
541420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Draws cards around the Carousel.
542420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
543420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller */
544420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawCards(int64_t currentTime)
5455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
546420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
547420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
548420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
549420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    for (int i = cardCount-1; i >= 0; i--) {
5505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
551420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // If this card was recently loaded, this will be < 1.0f until the animation completes
552420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime);
553420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (animatedAlpha < 1.0f) {
554420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                stillAnimating = true;
555420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
556420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
557420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Compute fade out for cards in the distance
558420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float positionAlpha;
559420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (rezInCardCount > 0.0f) {
560420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
561420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
562420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            } else {
563420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = 1.0f;
564420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
565420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
566420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Set alpha for blending between the textures
567420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
568420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
569420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
570b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // Bind the appropriate shader network.  If there's no alpha blend, then
571b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // switch to single shader for better performance.
572b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            const bool loaded = cards[i].textureState == STATE_LOADED;
573b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
574b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindProgramFragment(singleTextureFragmentProgram);
575b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(singleTextureFragmentProgram, 0,
576b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        (loaded && shaderConstants->fadeAmount == 1.0f) ?
577b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        cards[i].texture : loadingTexture);
5785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
579b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindProgramFragment(multiTextureFragmentProgram);
580b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
581b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
582b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        cards[i].texture : loadingTexture);
5835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
5865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
5877c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, i, true);
5885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
5895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
5905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(cards[i].geometry);
5915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
5925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(loadingGeometry);
5935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
5945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(defaultGeometry);
5955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
5965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
5975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
5985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
5995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
6005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
6015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
6025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
605420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
6065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6084fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma/**
6094fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * Convert projection from normalized coordinates to pixel coordinates.
6104fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma *
6114fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * @return True on success, false on failure.
6124fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma */
6134fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumastatic bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) {
6144fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // This is probably cheaper than pre-multiplying with another matrix.
6154fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    if (screenCoord->w == 0.0f) {
6164fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        rsDebug("Bad transform while converting from normalized to pixel coordinates: ",
6174fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma            screenCoord);
6184fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        return false;
6194fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    }
6204fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    *screenCoord *= 1.0f / screenCoord->w;
6214fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x += 1.0f;
6224fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y += 1.0f;
6234fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z += 1.0f;
6244fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x = round(screenCoord->x * 0.5f * width);
6254fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y = round(screenCoord->y * 0.5f * height);
6264fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z = - 0.5f * screenCoord->z;
6274fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    return true;
6284fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma}
6294fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma
6307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller/*
6317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * Draws a screen-aligned card with the exact dimensions from the detail texture.
6324a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney * This is used to display information about the object being displayed.
633420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
6347cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller */
635420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawDetails(int64_t currentTime)
6367cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
6377cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float width = rsgGetWidth();
6387cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float height = rsgGetHeight();
6397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
640420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
641420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
6427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // We'll be drawing in screen space, sampled on pixel centers
6437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 projection, model;
6447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
6457cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadProjectionMatrix(&projection);
6467cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadIdentity(&model);
6477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadModelMatrix(&model);
6487cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
6497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
6507cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
6517cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
652420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // This can be done once...
653a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture);
654a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
655a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
656a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where details start fading from 1.0f
657a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle;
658a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where detail alpha is 0.0f
659a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
660420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
661a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
6627cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        if (cards[i].visible) {
6637cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) {
6647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
6657cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
6664fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                // Compute position in screen space of top corner or bottom corner of card
667af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao                rsMatrixLoad(&model, &modelviewMatrix);
6687c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                getMatrixForCard(&model, i, false);
6697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rs_matrix4x4 matrix;
6707cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
671d443c88da4c7cf1947c12b26f111cb899cc8afe4Jim Miller
6724fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                int indexLeft, indexRight;
6734fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoord;
6744a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
6754fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 0;
6764fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 1;
6774fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                } else {
6784fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 3;
6794fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 2;
6804fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
6814fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
6824fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
6834fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) {
6847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    // this shouldn't happen
6857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    rsDebug("Bad transform: ", screenCoord);
6867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    continue;
6877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
6884a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_VERTICAL) {
6894a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    // If we're centering vertically, we'll need the other vertices too
6904a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
6914a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 3;
6924a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 2;
6934a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
6944a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexLeft = 0;
6954a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        indexRight = 1;
6964a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
6974a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
6984a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
6994a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y
7004a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        + otherScreenLeft.y + otherScreenRight.y) / 4.;
7014a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                }
7024fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
7034fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
7044fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (debugDetails) {
7054fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordLeft);
7064fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordRight);
7074fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
7084fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                screenCoord = screenCoordLeft;
7094a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & BELOW) {
7104fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
7114a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                } else if (detailTextureAlignment & CENTER_VERTICAL) {
7124a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    screenCoord.y -= rsAllocationGetDimY(cards[i].detailTexture) / 2.;
7134fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
7144a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                if (detailTextureAlignment & CENTER_HORIZONTAL) {
7154fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.x += (screenCoordRight.x - screenCoordLeft.x) / 2. -
7164fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                        rsAllocationGetDimX(cards[i].detailTexture) / 2.;
7174fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
7187cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
719420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Compute alpha for gradually fading in details. Applied to both line and
720420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // detail texture. TODO: use a separate background texture for line.
721420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, currentTime);
722420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                if (animatedAlpha < 1.0f) {
723420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    stillAnimating = true;
724420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                }
725420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
726a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                // Compute alpha based on position. We fade cards quickly so they cannot overlap
727a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i))
728a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                        / (endDetailFadeAngle - startDetailFadeAngle);
729a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = max(0.0f, positionAlpha);
730a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = min(1.0f, positionAlpha);
731a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
732a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
733a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
734b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 0.0f) continue; // nothing to draw
735b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
736b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(singleTextureFragmentProgram);
737b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
738b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(multiTextureFragmentProgram);
739b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
740a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
741420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Set alpha for blending between the textures
742a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                shaderConstants->fadeAmount = blendedAlpha;
743420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
744420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
7454a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // Draw line from the card to the detail texture.
7464a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // The line is drawn from the top or bottom left of the card
7474a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // to either the top of the screen or the top of the detail
7484a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                // texture, depending on detailTextureAlignment.
7497c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (drawRuler) {
7504a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerTop;
7514a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    float rulerBottom;
7524a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    if (detailTextureAlignment & BELOW) {
7534a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = screenCoord.y;
7544a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = 0;
7554a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    } else {
7564a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerTop = height;
7574a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        rulerBottom = screenCoord.y;
7584a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                    }
7597c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float halfWidth = lineWidth * 0.5f;
760b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float x0 = cards[i].detailLineOffset.x + screenCoord.x - halfWidth;
761b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float x1 = cards[i].detailLineOffset.x + screenCoord.x + halfWidth;
762b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y0 = rulerBottom + yPadding;
763b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y;
764b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
765b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    if (blendedAlpha == 1.0f) {
766b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture);
767b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    } else {
768b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture);
769b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    }
770b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
771b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                            x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
7727c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
7737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
7747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw the detail texture next to it using the offsets provided.
7757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
7767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
7777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offx = cards[i].detailTextureOffset.x;
7787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offy = -cards[i].detailTextureOffset.y;
7794a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                const float textureTop = (detailTextureAlignment & VIEW_TOP)
7804a8736e22d7b40ab9dfa3fbd8a10de92144912b3Bryan Mawhinney                        ? height : screenCoord.y;
781b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
782b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
783b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
784b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
785b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
786b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
787b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
788b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
789b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture);
790b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
791b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
792b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
7937cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
7947cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
7957cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    }
796420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
7977cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
7987cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
7999afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerstatic void drawBackground()
8009afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller{
801b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    static bool toggle;
8029afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    if (backgroundTexture.p != 0) {
8039afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
8049afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rs_matrix4x4 projection, model;
8059afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
8069afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadProjectionMatrix(&projection);
8079afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadIdentity(&model);
8089afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadModelMatrix(&model);
809a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller        rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture);
8109afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        float z = -0.9999f;
8119afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgDrawQuad(
8129afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[0].x, cardVertices[0].y, z,
8139afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[1].x, cardVertices[1].y, z,
8149afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[2].x, cardVertices[2].y, z,
8159afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[3].x, cardVertices[3].y, z);
8169afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        updateCamera = true; // we mucked with the matrix.
8179afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    } else {
8189afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
819b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        if (debugRendering) { // for debugging - flash the screen so we know we're still rendering
820b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            rsgClearColor(toggle ? backgroundColor.x : 1.0f,
821b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.y : 0.0f,
822b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.z : 0.0f,
823b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        backgroundColor.w);
8249afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            toggle = !toggle;
825b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        } else {
8267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
8277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                   backgroundColor.w);
8289afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       }
8299afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    }
8309afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller}
8319afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller
8325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
8335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
8355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
8365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
8375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
8385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
8395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
8415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
8425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
8435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
8475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
8485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
8495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
8505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
8515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
8525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityThreshold = 0.1f * M_PI / 180.0f;
8535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityTracker;
8545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int velocityTrackerCount;
8555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
8565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
8585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
8595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
8615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
8635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
8665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
8685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint doSelection(float x, float y)
8715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
873b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (makeRayForPixelAt(&ray, &camera, x, y)) {
8745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
8755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
8765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
8785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
88043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millervoid sendAnimationStarted() {
88143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    rsSendToClient(CMD_ANIMATION_STARTED);
88243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
88343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
884a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevichvoid sendAnimationFinished() {
885a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    float data[1];
886a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    data[0] = radiansToCarouselRotationAngle(bias);
887a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich    rsSendToClient(CMD_ANIMATION_FINISHED, (int*) data, sizeof(data));
888a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich}
889a84feeb7e4dc1a75ec6d0b1f2494893987fc3ca3Jack Palevich
8905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStart(float x, float y)
8915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
89243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    touchPosition = lastPosition = (float2) { x, y };
8935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
8945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTracker = 0.0f;
8955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTrackerCount = 0;
8965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchTime = rsUptimeMillis();
8975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
89843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = true;
89943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection = true;
90043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    animatedSelection = doSelection(x, y); // used to provide visual feedback on touch
9015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStop(float x, float y)
9045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
9068b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
90743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
90843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (enableSelection) {
9095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
91043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        int selection = doSelection(x, y);
91143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (selection != -1) {
91243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (debugSelection) rsDebug("Selected item on doStop():", selection);
91343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            data[0] = selection;
91443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
91543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
91643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = false;
9175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
91843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // TODO: move velocity tracking to Java
9195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        velocity = velocityTrackerCount > 0 ?
9205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
9215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(velocity) > velocityThreshold) {
9225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
9235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
9245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
92543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection = false;
9265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = rsUptimeMillis();
92743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    isDragging = false;
9285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
930594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumavoid doLongPress()
931594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma{
932594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    int64_t currentTime = rsUptimeMillis();
933594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    updateAllocationVars(cards);
93443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Selection happens for most recent position detected in doMotion()
93543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    int selection = doSelection(lastPosition.x, lastPosition.y);
93643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (selection != -1) {
93743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (debugSelection) rsDebug("doLongPress(), selection = ", selection);
938594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        int data[1];
93943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        data[0] = selection;
940594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data));
941594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    }
942594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    lastTime = rsUptimeMillis();
943594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma}
944594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma
9455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doMotion(float x, float y)
9465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
94743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float firstBias = wedgeAngle(0.0f);
94843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float lastBias = -max(0.0f, wedgeAngle(cardCount - visibleDetailCount));
9495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
9505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
95143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (!enableSelection) {
95243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias += deltaOmega;
95343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias = clamp(bias, lastBias - wedgeAngle(OVERSCROLL_SLOTS),
95443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                firstBias + wedgeAngle(OVERSCROLL_SLOTS));
95543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
95643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float2 delta = (float2) { x, y } - touchPosition;
95743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float distance = sqrt(dot(delta, delta));
95843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool inside = (distance < selectionRadius);
95943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    enableSelection &= inside;
96043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastPosition = (float2) { x, y };
9615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float dt = deltaTimeInSeconds(currentTime);
9625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
9635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
96443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocityTracker += v;
96543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocityTrackerCount++;
9665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
967c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    velocity = velocityTrackerCount > 0 ?
968c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma                (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
9695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
9705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
9735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
9745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
97543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float EPSILON = 1.0e-6f;
97643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic const float tmin = 0.0f;
9775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
97943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float* tout)
9805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
9825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
9835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
9845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
9865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
9875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
9895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
9905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
9925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
9935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
9955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
9965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
9975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
9995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
10005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
10015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
10025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
10035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
100543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
100643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/plane intersection. Returns false if no intersection found.
100743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1008b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
100943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayPlaneIntersect(Ray* ray, Plane* plane, float* tout)
1010b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
101143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float denom = dot(ray->direction, plane->normal);
101243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (fabs(denom) > EPSILON) {
101343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float t = - (plane->constant + dot(ray->position, plane->normal)) / denom;
101443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (t > tmin && t < *tout) {
101543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            *tout = t;
101643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            return true;
101743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
101843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
101943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1020b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1021b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
102243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
102343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes ray/cylindr intersection. There are 0, 1 or 2 hits.
102443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Returns true and sets *tout to the closest point or
102543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// returns false if no intersection found.
102643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
1027b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
102843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim MillerrayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout)
1029b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
103043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float A = ray->direction.x * ray->direction.x + ray->direction.z * ray->direction.z;
103143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (A < EPSILON) return false; // ray misses
103243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
103343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Compute quadratic equation coefficients
103443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float B = 2.0f * (ray->direction.x * ray->position.x
103543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->direction.z * ray->position.z);
103643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float C = ray->position.x * ray->position.x
103743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            + ray->position.z * ray->position.z
103843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            - cylinder->radius * cylinder->radius;
103943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float disc = B*B - 4*A*C;
104043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
104143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (disc < 0.0f) return false; // ray misses
104243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    disc = sqrt(disc);
104343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float denom = 2.0f * A;
104443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
104543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Nearest point
104643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t1 = (-B - disc) / denom;
104743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (t1 > tmin && t1 < *tout) {
104843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t1;
104943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
105043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
105143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
105243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    // Far point
105343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float t2 = (-B + disc) / denom;
105443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (t2 > tmin && t2 < *tout) {
105543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        *tout = t2;
105643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
105743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
105843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return false;
1059b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1060b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1061b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates.
10625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1063b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1064b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y)
10655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
10675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
1068b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.from:", cam->from);
1069b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.at:", cam->at);
1070b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.dir:", normalize(cam->at - cam->from));
10715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
10745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
1075b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float u = x / rsgGetWidth();
1076b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float v = 1.0f - (y / rsgGetHeight());
1077b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float aspect = (float) rsgGetWidth() / rsgGetHeight();
1078b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f));
1079b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dir = normalize(cam->at - cam->from);
1080b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 du = tanfov2 * normalize(cross(dir, cam->up));
1081b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dv = tanfov2 * normalize(cross(du, dir));
1082b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    du *= aspect;
1083b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
1084b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = cam->from;
1085b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
1086b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1087b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (vector math) = ", rayDir);
10885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1090b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1091b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
1092b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return true;
1093b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
1094b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1095b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a model view and projection matrix.
1096b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
1097b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
1098b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y)
1099b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
1100b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rs_matrix4x4 pm = *model;
1101b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsMatrixLoadMultiply(&pm, proj, model);
1102b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (!rsMatrixInverse(&pm)) {
1103b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("ERROR: SINGULAR PM MATRIX", 0);
1104b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        return false;
11055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
1106b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float width = rsgGetWidth();
1107b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float height = rsgGetHeight();
1108b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winx = 2.0f * x / width - 1.0f;
1109b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winy = 2.0f * y / height - 1.0f;
1110b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1111b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
1112b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 at = { winx, winy, 1.0f, 1.0f };
1113b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1114b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye = rsMatrixMultiply(&pm, eye);
1115b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye *= 1.0f / eye.w;
11165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1117b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at = rsMatrixMultiply(&pm, at);
1118b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at *= 1.0f / at.w;
1119b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
1120b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = { eye.x, eye.y, eye.z };
1121b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 atPoint = { at.x, at.y, at.z };
1122b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(atPoint - rayPoint);
1123b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
1124b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winx: ", winx);
1125b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winy: ", winy);
1126b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray position (transformed) = ", eye);
1127b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (transformed) = ", rayDir);
1128b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    }
1129b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
1130b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
11315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
11325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
11355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
11365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
11375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
11385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[id].visible) {
11395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
11405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
11415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
11435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
11447c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, id, true);
11455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
11465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
11475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
11485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
11495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
11505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
11515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
11525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
11535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
11545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
11555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
11585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
11595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
11605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
11615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
11635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
11645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
11655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
116843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// simple physics model.  If the cards are still in motion, returns true.
116943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic bool doPhysics(float dt)
117043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
117143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
117243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
117343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    dt /= N;
117443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    for (int i = 0; i < N; i++) {
117543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Force friction - always opposes motion
117643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Ff = -frictionCoeff * velocity;
117743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
117843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // Restoring force to match cards with slots
117943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float theta = startAngle + bias;
118043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float dtheta = 2.0f * M_PI / slotCount;
118143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float position = theta / dtheta;
118243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float fraction = position - floor(position); // fractional position between slots
118343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        float x;
118443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (fraction > 0.5f) {
118543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = - (1.0f - fraction);
118643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
118743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            x = fraction;
118843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
118943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float Fr = - springConstant * x;
119043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
119143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        // compute velocity
119243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        const float momentum = mass * velocity + (Ff + Fr)*dt;
119343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocity = momentum / mass;
119443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        bias += velocity * dt;
119543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
119643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return fabs(velocity) > velocityThreshold;
119743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
119843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
119943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Millerstatic float easeOut(float x)
120043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller{
120143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return x;
120243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller}
120343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
120443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller// Computes the next value for bias using the current animation (physics or overscroll)
12055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
12065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
120743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    static const float biasMin = 1e-4f; // close enough if we're within this margin of result
12085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
120943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float dt = deltaTimeInSeconds(currentTime);
12105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
121143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (dt <= 0.0f) {
121243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (debugRendering) rsDebug("Time delta was <= 0", dt);
121343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        return true;
12145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1216f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    const float firstBias = wedgeAngle(0.0f);
1217f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    const float lastBias = -max(0.0f, wedgeAngle(cardCount - visibleDetailCount));
121843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool stillAnimating = false;
121943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (overscroll) {
122043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (bias > firstBias) {
122143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            bias -= 4.0f * dt * easeOut((bias - firstBias) * 2.0f);
122243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (fabs(bias - firstBias) < biasMin) {
122343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                bias = firstBias;
122443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            } else {
122543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                stillAnimating = true;
122643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            }
122743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else if (bias < lastBias) {
122843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            bias += 4.0f * dt * easeOut((lastBias - bias) * 2.0f);
122943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (fabs(bias - lastBias) < biasMin) {
123043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                bias = lastBias;
123143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            } else {
123243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller                stillAnimating = true;
123343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            }
123443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
123543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            overscroll = false;
123643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
123743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    } else {
123843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = doPhysics(dt);
123943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        overscroll = bias > firstBias || bias < lastBias;
1240cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller        if (overscroll) {
1241cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller            velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation.
1242cfe41767a3596a65eef91b6f68286fd0f916a4c7Jim Miller        }
1243c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    }
124443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    float newbias = clamp(bias, lastBias - wedgeAngle(OVERSCROLL_SLOTS),
124543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            firstBias + wedgeAngle(OVERSCROLL_SLOTS));
124643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (newbias != bias) { // we clamped
124743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        velocity = 0.0f;
124843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        overscroll = true;
124943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
125043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bias = newbias;
125143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return stillAnimating;
12525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
12535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
12555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
12565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
12575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cullCards()
12585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
12594fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // TODO(jshuma): Instead of fully fetching prefetchCardCount cards, make a distinction between
12604fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // STATE_LOADED and a new STATE_PRELOADING, which will keep the textures loaded but will not
12614fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // attempt to actually draw them.
12624fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const int prefetchCardCountPerSide = prefetchCardCount / 2;
12634fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaFirst = slotPosition(-prefetchCardCountPerSide);
1264198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelected = slotPosition(0);
1265198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f;
1266198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedLow = thetaSelected - thetaHalfAngle;
1267198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedHigh = thetaSelected + thetaHalfAngle;
12684fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaLast = slotPosition(visibleSlotCount - 1 + prefetchCardCountPerSide);
12695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int count = 0;
12715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
12725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
12735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
12745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
12755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (p >= thetaFirst && p < thetaLast) {
12765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = true;
12775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                count++;
12785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
12795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = false;
12805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
12815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
12825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
12835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
12845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].visible = true;
12855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            count++;
12865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
12875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return count;
12895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
12905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
12925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
1293420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic void updateCardResources(int64_t currentTime)
12945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1295a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
12965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
12975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
129843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp);
12992ba04e061b52c488a154739379501dc833e39f79Jim Miller
13005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request texture from client if not loaded
13015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_INVALID) {
13025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
13035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
13045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
13055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_LOADING;
13065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
13077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
13087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
13097cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
13107cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // request detail texture from client if not loaded
13117cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_INVALID) {
13127cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
13137cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
13147cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
13157cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_LOADING;
13167cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
13177cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
13185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request geometry from client if not loaded
13215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_INVALID) {
13225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
13235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
13245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
13255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_LOADING;
13265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
13277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
13285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
13315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
1332dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].textureState != STATE_INVALID) {
13335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
13345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
13355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
13365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
1337420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].textureTimeStamp = currentTime;
13385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
13397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
13407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
13417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
13427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // ask the host to remove the detail texture
13437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState != STATE_INVALID) {
13447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
13457cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
13467cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
13477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_INVALID;
1348420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].detailTextureTimeStamp = currentTime;
13497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
13507cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
13515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
1354dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].geometryState != STATE_INVALID) {
13555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
13565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
13575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
13585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
13595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
13607cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
13615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
13645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
13655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
13665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
13685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
13695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
13705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
13715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
13725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
13735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
13745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
13755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    color(1.0f, 0.0f, 0.0f, 1.0f);
13765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
13775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
13785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
13795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
13805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
1381b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (makeRayForPixelAt(&ray, &camera, posX, posY)) {
13825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
13835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
13845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
13855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
13865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
13875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
13885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
13895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
13905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
13925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
13935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
13945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
13955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
1396b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp);
1397b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp);
1398b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp);
13995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14008b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
14015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (!initialized) {
140343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (debugTextureLoading) {
14042ba04e061b52c488a154739379501dc833e39f79Jim Miller            rsDebug("*** initialized was false, updating all cards (cards = ", cards);
14052ba04e061b52c488a154739379501dc833e39f79Jim Miller        }
14067cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        for (int i = 0; i < cardCount; i++) {
14072ba04e061b52c488a154739379501dc833e39f79Jim Miller            initCard(cards + i);
14087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
14095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        initialized = true;
14105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1412a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindProgramFragment(singleTextureFragmentProgram);
1413bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    rsgBindProgramStore(programStoreOpaque);
14149afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    drawBackground();
14155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
14175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
141843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    bool stillAnimating = (currentTime - touchTime) <= ANIMATION_SCALE_TIME;
141943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
142043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (!isDragging && animating) {
142143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        stillAnimating = updateNextPosition(currentTime);
142243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
142343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
142443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    lastTime = currentTime;
14255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
14275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1428420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    updateCardResources(currentTime);
14295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1430bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    // Draw cards opaque only if requested, and always draw detail textures with blending.
1431bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    if (drawCardsWithBlending) {
1432bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma        rsgBindProgramStore(programStore);
1433bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    } else {
1434bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma        // programStoreOpaque is already bound
1435bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    }
1436bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawCards(currentTime);
1437bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    rsgBindProgramStore(programStoreDetail);
1438bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawDetails(currentTime);
14395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
144043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (stillAnimating != animating) {
144143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        if (stillAnimating) {
144243471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we just started animating
144343471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationStarted();
144443471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        } else {
144543471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            // we were animating but stopped animating just now
144643471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller            sendAnimationFinished();
144743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        }
144843471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller        animating = stillAnimating;
144943471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    }
145043471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller
145143471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    if (debugRays) {
14525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
14535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
14545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
14555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
14565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
145743471a7e84593d0dd855ec5c66d70891a6fd4c81Jim Miller    return animating ? 1 : 0;
14585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1459