carousel.rs revision 420b44b8b11ec1c309ea130e69a6876325dbfef9
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 {
247cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation texture; // basic card texture
257cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation detailTexture; // screen-aligned detail texture
267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    float2 detailTextureOffset; // offset to add, in screen coordinates
275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_mesh geometry;
287cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 matrix; // custom transform for this card/geometry
297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int textureState;  // whether or not the primary card texture is loaded.
307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int detailTextureState; // whether or not the detail for the card is loaded.
315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int geometryState; // whether or not geometry is loaded
325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int visible; // not bool because of packing bug?
33420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // TODO: Change when int64_t is supported.  This will break after ~40 days of uptime.
34420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    unsigned int textureTimeStamp; // time when this texture was last updated, in seconds
35420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    unsigned int detailTextureTimeStamp; // time when this texture was last updated, in seconds
365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
53420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millertypedef struct FragmentShaderConstants_s {
54420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    float fadeAmount;
55420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller} FragmentShaderConstants;
56420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REQUEST_DETAIL_TEXTURE = 600;
747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REPORT_FIRST_CARD_POSITION = 700;
767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_PING = 1000;
775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this %
815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
837cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugCamera = false; // dumps ray/camera coordinate stuff
847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugPicking = false; // renders picking area on top of geometry
857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugTextureLoading = false; // for debugging texture load/unload
867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugGeometryLoading = false; // for debugging geometry load/unload
877c09ccce478100d75e4427d87866ff19d758ae7aJim Shumaconst bool debugDetails = false; // for debugging detail texture geometry
885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
905ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
957cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerint visibleDetailCount; // number of visible detail textures to show
967c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawDetailBelowCard; // whether detail goes above (false) or below (true) the card
977c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawRuler; // whether to draw a ruler from the card to the detail texture
985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
100c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat swaySensitivity; // how much to rotate cards in relation to the rotation velocity
101c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat frictionCoeff; // how much to slow down the carousel over time
102c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat dragFactor; // a scale factor for how sensitive the carousel is to user dragging
103420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerint fadeInDuration; // amount of time (in ms) for smoothly switching out textures
104420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
1055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_store programStore;
1065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_fragment fragmentProgram;
1075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
1085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
1095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
1105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
1119afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerrs_allocation backgroundTexture; // drawn behind everything, if set
1127cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerrs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
113420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_allocation detailLoadingTexture; // used when detail texture is loading
1145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
1155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
1165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
1175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
118420b44b8b11ec1c309ea130e69a6876325dbfef9Jim MillerFragmentShaderConstants* shaderConstants;
119420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_sampler linearClamp;
1205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
121b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller#pragma rs export_var(radius, cards, slotCount, visibleSlotCount, cardRotation, backgroundColor)
122c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma#pragma rs export_var(swaySensitivity, frictionCoeff, dragFactor)
1237c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma#pragma rs export_var(visibleDetailCount, drawDetailBelowCard, drawRuler)
1247cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller#pragma rs export_var(programStore, fragmentProgram, vertexProgram, rasterProgram)
125420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller#pragma rs export_var(detailLineTexture, detailLoadingTexture, backgroundTexture)
126420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller#pragma rs export_var(linearClamp, shaderConstants)
1275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_var(startAngle, defaultTexture, loadingTexture, defaultGeometry, loadingGeometry)
128420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller#pragma rs export_var(fadeInDuration, rezInCardCount)
1297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller#pragma rs export_func(createCards, lookAt, doStart, doStop, doMotion, doSelection)
1307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller#pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera, debugPicking)
131198a060d650bc849ef0f25b597888fac9546803bJack Palevich#pragma rs export_func(requestFirstCardPosition)
1325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
1345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
1355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
1365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool initialized;
137b0f070636c29ad178f4e21306f301fe3d20c183bJim Millerstatic float4 backgroundColor;
1385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
1395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int currentSelection = -1;
140198a060d650bc849ef0f25b597888fac9546803bJack Palevichstatic int currentFirstCard = -1;
1415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t touchTime = -1;  // time of first touch (see doStart())
1425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
143c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float velocity = 0.0f;  // angular velocity in radians/s
1445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1453df59346f395434454d310b070fff195089fbaf1Jim Miller// Because allocations can't have 0 dimensions, we have to track whether or not
1463df59346f395434454d310b070fff195089fbaf1Jim Miller// cards are valid separately.
1473df59346f395434454d310b070fff195089fbaf1Jim Miller// TODO: Remove this dependency once allocations can have a zero dimension.
1483df59346f395434454d310b070fff195089fbaf1Jim Millerstatic bool cardAllocationValid = false;
1493df59346f395434454d310b070fff195089fbaf1Jim Miller
1505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
1515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
1525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
1535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
1545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
1555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
1565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
1595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
1605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
1615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
1625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
1635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
1645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
1655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
1665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
1675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
1705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
1715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool makeRayForPixelAt(Ray* ray, float x, float y);
1725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
1735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
1755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
1765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
1777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // rsDebug("Renderscript: init()", 0);
1785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
1795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
1805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
1817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    visibleDetailCount = 3;
1825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
1835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    radius = 1.0f;
1845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
1855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
1865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
187b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
1883df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = false;
1893df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = 0;
190420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    fadeInDuration = 250;
191420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
1925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateAllocationVars()
1955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
1975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_allocation cardAlloc = rsGetAllocation(cards);
1985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: use new rsIsObject()
1993df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = (cardAllocationValid && cardAlloc.p != 0) ? rsAllocationGetDimX(cardAlloc) : 0;
2005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid createCards(int n)
2035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    if (debugTextureLoading) rsDebug("CreateCards: ", n);
2053df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = n > 0;
2065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
2075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
2085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
210420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller// Computes an alpha value for a card using elapsed time and constant fadeInDuration
211420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat getAnimatedAlpha(int64_t startTime, int64_t currentTime)
212420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller{
213420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double timeElapsed = (double) (currentTime - startTime); // in ms
214420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double alpha = (double) timeElapsed / fadeInDuration;
215420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return min(1.0f, (float) alpha);
216420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller}
217420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
2185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return angle for position p. Typically p will be an integer position, but can be fractional.
2195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float cardPosition(float p)
2205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + bias + 2.0f * M_PI * p / slotCount;
2225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
2255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float slotPosition(float p)
2265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + 2.0f * M_PI * p / slotCount;
2285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return the lowest slot number for a given angular position.
2315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cardIndex(float angle)
2325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
2345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
2375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
2385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
2395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
2405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
2415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
2425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
2435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
2445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
2455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
2475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
2485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
2495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
2505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
2515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
2525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
2535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
2545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
2555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
2565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
2595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
2605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
2625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
2635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
2645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
2655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
2665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
2675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
2705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
2715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
2725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
2745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
2755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
2765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
2775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
2785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
2795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
2805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
2815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
2825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
2835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
2845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
2855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
2865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
2875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
2885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
2895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
2905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
2915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
2925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
2935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
2965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2973df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
298c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].texture, texture);
2997cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
300420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].textureTimeStamp = rsUptimeMillis();
3017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
3027cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
3037cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millervoid setDetailTexture(int n, float offx, float offy, rs_allocation texture)
3047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
3053df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
306c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].detailTexture, texture);
3077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.x = offx;
3087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.y = offy;
3097cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
310420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].detailTextureTimeStamp = rsUptimeMillis();
3115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
3145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3153df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
316c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].geometry, geometry);
3175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
3185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
3195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
3205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
3215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
323198a060d650bc849ef0f25b597888fac9546803bJack Palevichvoid requestFirstCardPosition()
324198a060d650bc849ef0f25b597888fac9546803bJack Palevich{
325198a060d650bc849ef0f25b597888fac9546803bJack Palevich    int data[1];
326198a060d650bc849ef0f25b597888fac9546803bJack Palevich    data[0] = currentFirstCard;
327198a060d650bc849ef0f25b597888fac9546803bJack Palevich    bool enqueued = rsSendToClient(CMD_REPORT_FIRST_CARD_POSITION, data, sizeof(data));
328198a060d650bc849ef0f25b597888fac9546803bJack Palevich    if (!enqueued) {
329198a060d650bc849ef0f25b597888fac9546803bJack Palevich        rsDebug("Couldn't send CMD_REPORT_FIRST_CARD_POSITION", 0);
330198a060d650bc849ef0f25b597888fac9546803bJack Palevich    }
331198a060d650bc849ef0f25b597888fac9546803bJack Palevich}
332198a060d650bc849ef0f25b597888fac9546803bJack Palevich
3335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float3 getAnimatedScaleForSelected()
3345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t dt = (rsUptimeMillis() - touchTime);
3365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f;
3375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float3 one = { 1.0f, 1.0f, 1.0f };
3385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return one + fraction * SELECTED_SCALE_FACTOR;
3395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
341c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
342c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma//    P(t) = 1 / (1 + e^(-t))
343c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Parameter t: Any real number
344c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Returns: A float in the range (0,1), with P(0.5)=0
345c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float logistic(float t) {
346af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao    return 1.f / (1.f + exp(-t));
347c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
348c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
3497c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic float getSwayAngleForVelocity(float v, bool enableSway)
350c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma{
3517c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float sway = 0.0f;
3527c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma
3537c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    if (enableSway) {
3547c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma        const float range = M_PI; // How far we can deviate from center, peak-to-peak
355af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao        sway = M_PI * (logistic(-v * swaySensitivity) - 0.5f);
3567c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    }
357c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
358c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    return sway;
359c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
360c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
3617c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// matrix: The output matrix.
3627c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// i: The card we're getting the matrix for.
3637c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
3647c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic void getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway)
3655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
3677c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
3685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
3695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, radius, 0, 0);
370c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    rsMatrixRotate(matrix, degrees(-theta + cardRotation + swayAngle), 0, 1, 0);
3715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (i == currentSelection) {
3725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 scale = getAnimatedScaleForSelected();
3735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
3745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: apply custom matrix for cards[i].geometry
3765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
378420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller/*
379420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Draws cards around the Carousel.
380420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
381420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller */
382420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawCards(int64_t currentTime)
3835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
384420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
385420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
386420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
387420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    for (int i = cardCount-1; i >= 0; i--) {
3885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
389420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // If this card was recently loaded, this will be < 1.0f until the animation completes
390420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime);
391420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (animatedAlpha < 1.0f) {
392420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                stillAnimating = true;
393420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
394420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
395420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Compute fade out for cards in the distance
396420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float positionAlpha;
397420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (rezInCardCount > 0.0f) {
398420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
399420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
400420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            } else {
401420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = 1.0f;
402420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
403420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
404420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Set alpha for blending between the textures
405420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
406420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
407420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
408420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Bind place-holder texture
409420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsgBindSampler(fragmentProgram, 0, linearClamp);
410420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsgBindTexture(fragmentProgram, 0, loadingTexture);
411420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
412420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Bind artwork texture, if loaded
413420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsgBindSampler(fragmentProgram, 1, linearClamp);
4145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_LOADED) {
415420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsgBindTexture(fragmentProgram, 1, cards[i].texture);
4165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
417420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsgBindTexture(fragmentProgram, 1, loadingTexture);
4185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
4195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
4215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
4227c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, i, true);
4235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
4245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
4255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(cards[i].geometry);
4265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
4275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(loadingGeometry);
4285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
4295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(defaultGeometry);
4305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
4315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
4325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
4335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
4345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
4365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
4375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
4385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
4395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
440420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
4415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller/*
4447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * Draws a screen-aligned card with the exact dimensions from the detail texture.
4457cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * This is used to display information about the object being displayed above the geomertry.
446420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
4477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller */
448420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawDetails(int64_t currentTime)
4497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
4507cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float width = rsgGetWidth();
4517cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float height = rsgGetHeight();
4527cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
453420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
454420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
4557cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // We'll be drawing in screen space, sampled on pixel centers
4567cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 projection, model;
4577cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
4587cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadProjectionMatrix(&projection);
4597cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadIdentity(&model);
4607cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadModelMatrix(&model);
4617cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
4627cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
4637cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
4647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
4657cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int drawn = 0; // number of details drawn
466420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
467420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // This can be done once...
468420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rsgBindSampler(fragmentProgram, 0, linearClamp);
469420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rsgBindTexture(fragmentProgram, 0, detailLoadingTexture);
470420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
4717cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    for (int i = 0; i < cardCount && drawn < visibleDetailCount; i++) {
4727cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        if (cards[i].visible) {
4737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) {
4747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
4757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
4767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Compute position in screen space of upper left corner of card
477af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao                rsMatrixLoad(&model, &modelviewMatrix);
4787c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                getMatrixForCard(&model, i, false);
4797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rs_matrix4x4 matrix;
4807cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
481d443c88da4c7cf1947c12b26f111cb899cc8afe4Jim Miller
4827c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                float4 screenCoord = rsMatrixMultiply(&matrix,
4837c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    cardVertices[drawDetailBelowCard ? 0 : 3]);
4847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (screenCoord.w == 0.0f) {
4857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    // this shouldn't happen
4867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    rsDebug("Bad transform: ", screenCoord);
4877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    continue;
4887cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
4897cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
490420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Compute alpha for gradually fading in details. Applied to both line and
491420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // detail texture. TODO: use a separate background texture for line.
492420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, currentTime);
493420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                if (animatedAlpha < 1.0f) {
494420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    stillAnimating = true;
495420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                }
496420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
497420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Set alpha for blending between the textures
498420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                shaderConstants->fadeAmount = min(1.0f, animatedAlpha);
499420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
500420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
5017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Convert projection from normalized coordinates to pixel coordinates.
5027cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // This is probably cheaper than pre-multiplying the above with another matrix.
5037cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord *= 1.0f / screenCoord.w;
5047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord.x += 1.0f;
5057cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord.y += 1.0f;
5067cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord.z = 0.0f; // make sure it's in front
5077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord.x = round(screenCoord.x * 0.5f * width);
5087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                screenCoord.y = round(screenCoord.y * 0.5f * height);
5097c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (debugDetails) {
5107c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    RS_DEBUG(screenCoord);
5117c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
5127cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
5137cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw line from upper left card corner to the top of the screen
5147c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (drawRuler) {
5157c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float halfWidth = lineWidth * 0.5f;
5167c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float rulerTop = drawDetailBelowCard ? screenCoord.y : height;
5177c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float rulerBottom = drawDetailBelowCard ? 0 : screenCoord.y;
518420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    rsgBindSampler(fragmentProgram, 1, linearClamp);
519420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    rsgBindTexture(fragmentProgram, 1, detailLineTexture);
5207c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    rsgDrawQuad(
5217c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                            screenCoord.x - halfWidth, rulerBottom + yPadding, 0,
5227c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                            screenCoord.x + halfWidth, rulerBottom + yPadding, 0,
5237c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                            screenCoord.x + halfWidth, rulerTop - yPadding, 0,
5247c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                            screenCoord.x - halfWidth, rulerTop - yPadding, 0);
5257c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
5267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
5277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw the detail texture next to it using the offsets provided.
5287cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
5297cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
5307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offx = cards[i].detailTextureOffset.x;
5317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offy = -cards[i].detailTextureOffset.y;
5327c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                const float textureTop = drawDetailBelowCard ? screenCoord.y : height;
533420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsgBindSampler(fragmentProgram, 1, linearClamp);
534420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsgBindTexture(fragmentProgram, 1, cards[i].detailTexture);
5357cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsgDrawQuad(
5367c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                        screenCoord.x + offx, textureTop + offy - textureHeight, 0,
5377c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                        screenCoord.x + offx + textureWidth, textureTop + offy - textureHeight, 0,
5387c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                        screenCoord.x + offx + textureWidth, textureTop + offy, 0,
5397c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                        screenCoord.x + offx, textureTop + offy, 0);
5407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
5417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                drawn++;
5427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
5437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
5447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    }
545420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
5467cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
5477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
5489afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerstatic void drawBackground()
5499afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller{
5509afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    if (backgroundTexture.p != 0) {
5519afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
5529afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rs_matrix4x4 projection, model;
5539afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
5549afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadProjectionMatrix(&projection);
5559afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadIdentity(&model);
5569afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadModelMatrix(&model);
5579afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgBindTexture(fragmentProgram, 0, backgroundTexture);
558420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller        rsgBindTexture(fragmentProgram, 1, backgroundTexture); // TODO: background blending
5599afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        float z = -0.9999f;
5609afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgDrawQuad(
5619afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[0].x, cardVertices[0].y, z,
5629afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[1].x, cardVertices[1].y, z,
5639afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[2].x, cardVertices[2].y, z,
5649afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[3].x, cardVertices[3].y, z);
5659afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        updateCamera = true; // we mucked with the matrix.
5669afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    } else {
5679afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
5689afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        if (false) { // for debugging - flash the screen so we know we're still rendering
5699afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            static bool toggle;
5709afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            if (toggle)
5717cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller               rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
5727cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                       backgroundColor.w);
5739afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            else
5749afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller               rsgClearColor(1.0f, 0.0f, 0.0f, 1.f);
5759afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            toggle = !toggle;
5769afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       } else {
5777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
5787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                   backgroundColor.w);
5799afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       }
5809afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    }
5819afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller}
5829afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller
5835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
5845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
5865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
5875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
5885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
5895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
5905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
5925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
5935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
5945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
5955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
5985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
5995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
6005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool isDragging;
6015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
6025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
6035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
6045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityThreshold = 0.1f * M_PI / 180.0f;
6055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityTracker;
6065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int velocityTrackerCount;
6075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
6085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
6105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
6115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
6135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
6155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
6185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
6205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint doSelection(float x, float y)
6235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
6255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (makeRayForPixelAt(&ray, x, y)) {
6265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
6275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
6285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
6295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
6305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStart(float x, float y)
6335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
6355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
6365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
6375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
6385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClient(CMD_ANIMATION_FINISHED);
6395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = false;
6407867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma        currentSelection = -1;
6417867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma    } else {
6427867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma        currentSelection = doSelection(x, y);
6435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
6445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTracker = 0.0f;
6455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTrackerCount = 0;
6465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchTime = rsUptimeMillis();
6475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
6485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStop(float x, float y)
6525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
6545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
6555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1 && (currentTime - touchTime) < ANIMATION_SCALE_TIME) {
6567cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        // rsDebug("HIT!", currentSelection);
6575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
6585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        data[0] = currentSelection;
6595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
6605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
6615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        velocity = velocityTrackerCount > 0 ?
6625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
6635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(velocity) > velocityThreshold) {
6645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
6655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_STARTED);
6665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
6685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    currentSelection = -1;
6695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = rsUptimeMillis();
6705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doMotion(float x, float y)
6735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
6755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
6765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias += deltaOmega;
6775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
6785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
6795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float dt = deltaTimeInSeconds(currentTime);
6805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
6815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
6825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //if ((velocityTracker > 0.0f) == (v > 0.0f)) {
6835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTracker += v;
6845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTrackerCount++;
6855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //} else {
6865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTracker = v;
6875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTrackerCount = 1;
6885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //}
6895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
690c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    velocity = velocityTrackerCount > 0 ?
691c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma                (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
6925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Drop current selection if user drags position +- a partial slot
6945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1) {
6955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float slotMargin = 0.5f * (2.0f * M_PI / slotCount);
6965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(touchBias - bias) > slotMargin) {
6975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            currentSelection = -1;
6985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
7015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
7045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
7055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
7065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
7085ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout)
7095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    static const float tmin = 0.0f;
7115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
7135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
7145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
7155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
7175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
7185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
7205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
7215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
7235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
7245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
7265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
7275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
7285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
7305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
7315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
7325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
7335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
7345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Creates a ray for an Android pixel coordinate.
7375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
7385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool makeRayForPixelAt(Ray* ray, float x, float y)
7395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
7415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
7425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.from:", camera.from);
7435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.at:", camera.at);
7445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.dir:", normalize(camera.at - camera.from));
7455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
7485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
7495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (true) {
7505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float u = x / rsgGetWidth();
7515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float v = 1.0f - (y / rsgGetHeight());
7525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float aspect = (float) rsgGetWidth() / rsgGetHeight();
7535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float tanfov2 = 2.0f * tan(radians(camera.fov / 2.0f));
7545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 dir = normalize(camera.at - camera.from);
7555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 du = tanfov2 * normalize(cross(dir, camera.up));
7565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 dv = tanfov2 * normalize(cross(du, dir));
7575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        du *= aspect;
7585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
7595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayPoint = camera.from;
7605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
7615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (debugCamera) {
7625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray direction (vector math) = ", rayDir);
7635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->position =  rayPoint;
7665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->direction = rayDir;
7675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Matrix math.  This is more generic if we allow setting model view and projection matrices
7705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // directly
7715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else {
7725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rs_matrix4x4 pm = modelviewMatrix;
7735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixLoadMultiply(&pm, &projectionMatrix, &modelviewMatrix);
7745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (!rsMatrixInverse(&pm)) {
7755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("ERROR: SINGULAR PM MATRIX", 0);
7765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            return false;
7775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float width = rsgGetWidth();
7795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float height = rsgGetHeight();
7805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float winx = 2.0f * x / width - 1.0f;
7815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float winy = 2.0f * y / height - 1.0f;
7825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
7845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float4 at = { winx, winy, 1.0f, 1.0f };
7855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        eye = rsMatrixMultiply(&pm, eye);
7875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        eye *= 1.0f / eye.w;
7885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        at = rsMatrixMultiply(&pm, at);
7905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        at *= 1.0f / at.w;
7915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayPoint = { eye.x, eye.y, eye.z };
7935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 atPoint = { at.x, at.y, at.z };
7945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayDir = normalize(atPoint - rayPoint);
7955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (debugCamera) {
7965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("winx: ", winx);
7975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("winy: ", winy);
7985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray position (transformed) = ", eye);
7995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray direction (transformed) = ", rayDir);
8005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->position =  rayPoint;
8025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->direction = rayDir;
8035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
8065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
8095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
8115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
8125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[id].visible) {
8135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
8145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
8155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
8175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
8187c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, id, true);
8195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
8205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
8215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
8225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
8235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
8245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
8255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
8265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
8275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
8285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
8295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
8305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
8325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
8335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
8345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
8355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
8365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
8395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
8425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// simple physics model.
8435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If the cards are still in motion, returns true.
8445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
8455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
8475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float dt = deltaTimeInSeconds(currentTime);
8485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (dt <= 0.0f)
8495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            return animating;
8505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
8515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
8525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        dt /= N;
8535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < N; i++) {
8545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Force friction - always opposes motion
8555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Ff = -frictionCoeff * velocity;
8565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Restoring force to match cards with slots
8585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float theta = startAngle + bias;
8595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float dtheta = 2.0f * M_PI / slotCount;
8605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float position = theta / dtheta;
8615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float fraction = position - floor(position); // fractional position between slots
8625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float x;
8635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (fraction > 0.5f) {
8645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = - (1.0f - fraction);
8655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
8665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = fraction;
8675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
8685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Fr = - springConstant * x;
8695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // compute velocity
8715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float momentum = mass * velocity + (Ff + Fr)*dt;
8725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocity = momentum / mass;
8735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            bias += velocity * dt;
8745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = fabs(velocity) > velocityThreshold;
8775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (!animating) {
8785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_FINISHED);
8795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
8825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
883c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    // TODO: Add animation to smoothly move back to slots. Currently snaps to location.
884c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    if (cardCount <= visibleSlotCount) {
885c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        // TODO: this aligns the cards to the first slot (theta = startAngle) when there aren't
886c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        // enough visible cards. It should be generalized to allow alignment to front,
887c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        // middle or back of the stack.
888c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        if (cardPosition(0) != slotPosition(0)) {
889c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma            bias = 0.0f;
890c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        }
891c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    } else {
892c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        if (cardPosition(cardCount) < 0.0f) {
893c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma            bias = -slotPosition(cardCount);
894c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        } else if (cardPosition(0) > slotPosition(0)) {
895c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma            bias = 0.0f;
896c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma        }
897c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    }
898c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
8995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return animating;
9005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
9035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
9045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
9055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cullCards()
9065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float thetaFirst = slotPosition(-1); // -1 keeps the card in front around a bit longer
908198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelected = slotPosition(0);
909198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f;
910198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedLow = thetaSelected - thetaHalfAngle;
911198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedHigh = thetaSelected + thetaHalfAngle;
9125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float thetaLast = slotPosition(visibleSlotCount);
9135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int count = 0;
915198a060d650bc849ef0f25b597888fac9546803bJack Palevich    int firstVisible = -1;
9165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
9175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
9185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
9195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
9205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (p >= thetaFirst && p < thetaLast) {
921198a060d650bc849ef0f25b597888fac9546803bJack Palevich                if (firstVisible == -1 && p >= thetaSelectedLow && p < thetaSelectedHigh) {
922198a060d650bc849ef0f25b597888fac9546803bJack Palevich                    firstVisible = i;
923198a060d650bc849ef0f25b597888fac9546803bJack Palevich                }
9245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = true;
9255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                count++;
9265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
9275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = false;
9285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
9295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
9305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
9315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
9325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].visible = true;
9335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            count++;
9345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
9355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
936198a060d650bc849ef0f25b597888fac9546803bJack Palevich    currentFirstCard = firstVisible;
9375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return count;
9385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
9415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
942420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic void updateCardResources(int64_t currentTime)
9435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
9455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
9465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
9475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request texture from client if not loaded
9485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_INVALID) {
9495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
9505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
9515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
9525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_LOADING;
9535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
9547cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
9557cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
9567cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
9577cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // request detail texture from client if not loaded
9587cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_INVALID) {
9597cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
9607cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
9617cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
9627cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_LOADING;
9637cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
9647cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
9655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
9665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
9675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request geometry from client if not loaded
9685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_INVALID) {
9695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
9705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
9715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
9725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_LOADING;
9735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
9747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
9755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
9765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
9775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
9785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
979dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].textureState != STATE_INVALID) {
9805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
9815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
9825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
9835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
984420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].textureTimeStamp = currentTime;
9855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
9867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
9877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
9887cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
9897cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // ask the host to remove the detail texture
9907cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState != STATE_INVALID) {
9917cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
9927cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
9937cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
9947cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_INVALID;
995420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].detailTextureTimeStamp = currentTime;
9967cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
9977cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
9985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
9995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
1001dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].geometryState != STATE_INVALID) {
10025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
10035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
10045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
10055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
10065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
10077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
10085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
10095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
10125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
10165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
10175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
10185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
10195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
10215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
10225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
10235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    color(1.0f, 0.0f, 0.0f, 1.0f);
10245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
10255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
10265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
10275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
10285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
10295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (makeRayForPixelAt(&ray, posX, posY)) {
10305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
10315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
10325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
10335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
10345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
10365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
10405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
10415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
10435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramFragment(fragmentProgram);
10445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramStore(programStore);
10455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
10465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
10485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (!initialized) {
10507cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        const float2 zero = {0.0f, 0.0f};
10517cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        for (int i = 0; i < cardCount; i++) {
10525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].textureState = STATE_INVALID;
10537cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            cards[i].detailTextureState = STATE_INVALID;
10547cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            cards[i].detailTextureOffset = zero;
10557cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
10565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        initialized = true;
10575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10599afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    drawBackground();
10605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
10625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME;
10645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bool stillAnimating = updateNextPosition(currentTime) || !timeExpired;
10655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
10675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1068420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    updateCardResources(currentTime);
10695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1070420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    stillAnimating |= drawCards(currentTime);
1071420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    drawDetails(currentTime);
10725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugPicking) {
10745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
10755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
10785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return stillAnimating ? 1 : 0;
10805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1081