carousel.rs revision bf39450b962d91ec78af53db39826d55ddb39902
15ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller/*
25ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Copyright (C) 2010 The Android Open Source Project
35ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
45ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Licensed under the Apache License, Version 2.0 (the "License");
55ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * you may not use this file except in compliance with the License.
65ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * You may obtain a copy of the License at
75ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
85ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *      http://www.apache.org/licenses/LICENSE-2.0
95ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller *
105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * Unless required by applicable law or agreed to in writing, software
115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * distributed under the License is distributed on an "AS IS" BASIS,
125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * See the License for the specific language governing permissions and
145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller * limitations under the License.
155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller */
165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma version(1)
185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs java_package_name(com.android.ex.carousel);
195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs set_reflect_license()
205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#include "rs_graphics.rsh"
225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct __attribute__((aligned(4))) Card {
248b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    // *** Update copyCard if you add/remove fields here.
257cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation texture; // basic card texture
267cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_allocation detailTexture; // screen-aligned detail texture
277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    float2 detailTextureOffset; // offset to add, in screen coordinates
28b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float2 detailLineOffset; // offset to add to detail line, in screen coordinates
295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_mesh geometry;
307cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 matrix; // custom transform for this card/geometry
317cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int textureState;  // whether or not the primary card texture is loaded.
327cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    int detailTextureState; // whether or not the detail for the card is loaded.
335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int geometryState; // whether or not geometry is loaded
345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int visible; // not bool because of packing bug?
35420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // TODO: Change when int64_t is supported.  This will break after ~40 days of uptime.
36420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    unsigned int textureTimeStamp; // time when this texture was last updated, in seconds
37420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    unsigned int detailTextureTimeStamp; // time when this texture was last updated, in seconds
385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
55420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millertypedef struct FragmentShaderConstants_s {
56420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    float fadeAmount;
57420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller} FragmentShaderConstants;
58420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
69594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumastatic const int CMD_CARD_LONGPRESS = 110;
705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REQUEST_DETAIL_TEXTURE = 600;
777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_INVALIDATE_DETAIL_TEXTURE = 610;
787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_REPORT_FIRST_CARD_POSITION = 700;
797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerstatic const int CMD_PING = 1000;
805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this %
845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugCamera = false; // dumps ray/camera coordinate stuff
877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugPicking = false; // renders picking area on top of geometry
887cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugTextureLoading = false; // for debugging texture load/unload
897cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerconst bool debugGeometryLoading = false; // for debugging geometry load/unload
907c09ccce478100d75e4427d87866ff19d758ae7aJim Shumaconst bool debugDetails = false; // for debugging detail texture geometry
91b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerconst bool debugRendering = false; // flashes display when the frame changes
925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
945ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
958b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich// TODO: remove tmpCards code when allocations support resizing
968b55d7500c1e5a88c415dae8dcead16b152d7929Jack PalevichCard_t *tmpCards; // temporary array used to prevent flashing when we add more cards
975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
1005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
1017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerint visibleDetailCount; // number of visible detail textures to show
1024fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumaint prefetchCardCount; // how many cards to keep in memory
1037c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawDetailBelowCard; // whether detail goes above (false) or below (true) the card
1044fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma// TODO(jshuma): Replace detailTexturesCentered with a detailTextureAlignment mode enum
1054fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumabool detailTexturesCentered; // line up detail center and card center (instead of left edges)
106bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shumabool drawCardsWithBlending; // Enable blending while drawing cards (for translucent card textures)
1077c09ccce478100d75e4427d87866ff19d758ae7aJim Shumabool drawRuler; // whether to draw a ruler from the card to the detail texture
1085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
1095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
11083d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinneybool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle
111c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat swaySensitivity; // how much to rotate cards in relation to the rotation velocity
112c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat frictionCoeff; // how much to slow down the carousel over time
113c0bb8af58ae15674178f2db240283719918c6f28Jim Shumafloat dragFactor; // a scale factor for how sensitive the carousel is to user dragging
114420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerint fadeInDuration; // amount of time (in ms) for smoothly switching out textures
115420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
116a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerfloat detailFadeRate; // rate at which details fade as they move into the distance
1175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_store programStore;
118bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shumars_program_store programStoreOpaque;
119bf39450b962d91ec78af53db39826d55ddb39902Jim Shumars_program_store programStoreDetail;
120a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment singleTextureFragmentProgram;
121a9e9c4bef076e718094786edfe0290f798e1db4bJim Millerrs_program_fragment multiTextureFragmentProgram;
1225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
1235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
1245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
1255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
1269afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerrs_allocation backgroundTexture; // drawn behind everything, if set
1277cb0068e59dde61ef0e649735199e5ba31c9c6afJim Millerrs_allocation detailLineTexture; // used to draw detail line (as a quad, of course)
128420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_allocation detailLoadingTexture; // used when detail texture is loading
1295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
1305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
1315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
1325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
133420b44b8b11ec1c309ea130e69a6876325dbfef9Jim MillerFragmentShaderConstants* shaderConstants;
134420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerrs_sampler linearClamp;
1355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
136594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma#pragma rs export_func(createCards, copyCards, lookAt)
137594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma#pragma rs export_func(doStart, doStop, doMotion, doLongPress, doSelection)
1387cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller#pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera, debugPicking)
139198a060d650bc849ef0f25b597888fac9546803bJack Palevich#pragma rs export_func(requestFirstCardPosition)
1405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
1425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
1435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
1445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool initialized;
145b06b5d3798e2668b8b5660da625c13c743daf469Stephen Hinesfloat4 backgroundColor;
1465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
1475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int currentSelection = -1;
148198a060d650bc849ef0f25b597888fac9546803bJack Palevichstatic int currentFirstCard = -1;
1495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t touchTime = -1;  // time of first touch (see doStart())
1505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
151c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float velocity = 0.0f;  // angular velocity in radians/s
1525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1533df59346f395434454d310b070fff195089fbaf1Jim Miller// Because allocations can't have 0 dimensions, we have to track whether or not
1543df59346f395434454d310b070fff195089fbaf1Jim Miller// cards are valid separately.
1553df59346f395434454d310b070fff195089fbaf1Jim Miller// TODO: Remove this dependency once allocations can have a zero dimension.
1563df59346f395434454d310b070fff195089fbaf1Jim Millerstatic bool cardAllocationValid = false;
1573df59346f395434454d310b070fff195089fbaf1Jim Miller
1585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
1595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
1605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
1615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
1625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
1635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
1645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
1675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
1685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
1695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
1705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
1715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
1725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
1735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
1745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
1755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
1785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
179b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
180b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y);
181b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
182b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y);
1835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
1845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
1865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
1875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
1882ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) rsDebug("Renderscript: init()", 0);
1895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
1905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
1915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
1927cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    visibleDetailCount = 3;
1935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
1945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    radius = 1.0f;
1955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
19683d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    cardsFaceTangent = false;
1975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
1985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
199b0f070636c29ad178f4e21306f301fe3d20c183bJim Miller    backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
2003df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = false;
2013df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = 0;
202420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    fadeInDuration = 250;
203420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled)
204a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    detailFadeRate = 0.5f; // fade details over this many slot positions.
2055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2078b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevichstatic void updateAllocationVars(Card_t* newcards)
2085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
2108b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rs_allocation cardAlloc = rsGetAllocation(newcards);
2115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: use new rsIsObject()
2123df59346f395434454d310b070fff195089fbaf1Jim Miller    cardCount = (cardAllocationValid && cardAlloc.p != 0) ? rsAllocationGetDimX(cardAlloc) : 0;
2135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid createCards(int n)
2165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2172ba04e061b52c488a154739379501dc833e39f79Jim Miller    if (debugTextureLoading) {
2182ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsDebug("*** CreateCards with count", n);
2192ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
2202ba04e061b52c488a154739379501dc833e39f79Jim Miller
2212ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Since allocations can't have 0-size, we track validity ourselves based on the call to
2222ba04e061b52c488a154739379501dc833e39f79Jim Miller    // this method.
2233df59346f395434454d310b070fff195089fbaf1Jim Miller    cardAllocationValid = n > 0;
2242ba04e061b52c488a154739379501dc833e39f79Jim Miller
2255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
2268b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
2278b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich}
2288b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich
2298b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevichvoid copyCard(Card_t* dest, Card_t * src)
2308b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
2318b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->texture, src->texture);
2328b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->detailTexture, src->detailTexture);
2338b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureOffset = src->detailTextureOffset;
234b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    dest->detailLineOffset = src->detailLineOffset;
2358b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    rsSetObject(&dest->geometry, src->geometry);
2368b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->matrix = src->matrix;
2378b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->textureState = src->textureState;
2388b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureState = src->detailTextureState;
2398b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->geometryState = src->geometryState;
2408b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->visible = src->visible;
2418b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->textureTimeStamp = src->textureTimeStamp;
2428b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    dest->detailTextureTimeStamp = src->detailTextureTimeStamp;
2432ba04e061b52c488a154739379501dc833e39f79Jim Miller}
2448b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich
2452ba04e061b52c488a154739379501dc833e39f79Jim Millervoid initCard(Card_t* card)
2468b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich{
2472ba04e061b52c488a154739379501dc833e39f79Jim Miller    static const float2 zero = {0.0f, 0.0f};
2482ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->texture);
2492ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->detailTexture);
2502ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureOffset = zero;
2512ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailLineOffset = zero;
2522ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsClearObject(&card->geometry);
2532ba04e061b52c488a154739379501dc833e39f79Jim Miller    rsMatrixLoadIdentity(&card->matrix);
2542ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureState = STATE_INVALID;
2552ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureState = STATE_INVALID;
2562ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->geometryState = STATE_INVALID;
2572ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->visible = false;
2582ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->textureTimeStamp = 0;
2592ba04e061b52c488a154739379501dc833e39f79Jim Miller    card->detailTextureTimeStamp = 0;
2602ba04e061b52c488a154739379501dc833e39f79Jim Miller}
2612ba04e061b52c488a154739379501dc833e39f79Jim Miller
2622ba04e061b52c488a154739379501dc833e39f79Jim Millervoid copyCards(int n)
2632ba04e061b52c488a154739379501dc833e39f79Jim Miller{
2642ba04e061b52c488a154739379501dc833e39f79Jim Miller    unsigned int oldsize = cardAllocationValid ? rsAllocationGetDimX(rsGetAllocation(cards)) : 0;
2658b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    unsigned int newsize = rsAllocationGetDimX(rsGetAllocation(tmpCards));
2662ba04e061b52c488a154739379501dc833e39f79Jim Miller    unsigned int copysize = min(oldsize, newsize);
2672ba04e061b52c488a154739379501dc833e39f79Jim Miller
2682ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Copy existing cards
2692ba04e061b52c488a154739379501dc833e39f79Jim Miller    for (int i = 0; i < copysize; i++) {
2702ba04e061b52c488a154739379501dc833e39f79Jim Miller        if (debugTextureLoading) {
2718b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich            rsDebug("copying card ", i);
2728b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich        }
2732ba04e061b52c488a154739379501dc833e39f79Jim Miller        copyCard(tmpCards + i, cards + i);
2742ba04e061b52c488a154739379501dc833e39f79Jim Miller        // Release these now so we don't have to wait for GC for cards allocation.
2752ba04e061b52c488a154739379501dc833e39f79Jim Miller        // Assumes we're done with the cards allocation structure.
2762ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].texture);
2772ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].detailTexture);
2782ba04e061b52c488a154739379501dc833e39f79Jim Miller        rsClearObject(&cards[i].geometry);
2792ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].textureState = STATE_INVALID;
2802ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].detailTextureState = STATE_INVALID;
2812ba04e061b52c488a154739379501dc833e39f79Jim Miller        cards[i].geometryState = STATE_INVALID;
2828b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    }
2832ba04e061b52c488a154739379501dc833e39f79Jim Miller
2842ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Initialize remaining cards.
2852ba04e061b52c488a154739379501dc833e39f79Jim Miller    int first = cardAllocationValid ? min(oldsize, newsize) : 0;
2862ba04e061b52c488a154739379501dc833e39f79Jim Miller    for (int k = first; k < newsize; k++) {
2872ba04e061b52c488a154739379501dc833e39f79Jim Miller        initCard(tmpCards + k);
2882ba04e061b52c488a154739379501dc833e39f79Jim Miller    }
2892ba04e061b52c488a154739379501dc833e39f79Jim Miller
2902ba04e061b52c488a154739379501dc833e39f79Jim Miller    // Since allocations can't have 0-size, we use the same trick as createCards() where
2912ba04e061b52c488a154739379501dc833e39f79Jim Miller    // we track validity ourselves. Grrr.
2922ba04e061b52c488a154739379501dc833e39f79Jim Miller    cardAllocationValid = n > 0;
2932ba04e061b52c488a154739379501dc833e39f79Jim Miller
2948b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(tmpCards);
2955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
297420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller// Computes an alpha value for a card using elapsed time and constant fadeInDuration
298420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerfloat getAnimatedAlpha(int64_t startTime, int64_t currentTime)
299420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller{
300420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double timeElapsed = (double) (currentTime - startTime); // in ms
301420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    double alpha = (double) timeElapsed / fadeInDuration;
302420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return min(1.0f, (float) alpha);
303420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller}
304420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
3055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return angle for position p. Typically p will be an integer position, but can be fractional.
3065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float cardPosition(float p)
3075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + bias + 2.0f * M_PI * p / slotCount;
3095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
3125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float slotPosition(float p)
3135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + 2.0f * M_PI * p / slotCount;
3155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
317f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller// Returns total angle for given number of cards
318f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Millerstatic float wedgeAngle(float cards)
319f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller{
320f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    return cards * 2.0f * M_PI / slotCount;
321f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller}
322f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller
3235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return the lowest slot number for a given angular position.
3245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cardIndex(float angle)
3255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
3275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
3305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
3315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
3325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
3335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
3345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
3355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
3365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
3375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
3385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
3405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
3415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
3425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
3435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
3445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
3455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
3465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
3475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
3485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
3495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
3525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
3535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
3555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
3565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
3575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
3585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
3595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
3605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
3635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
3645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
3655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
3675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
3685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
3695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
3705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
3715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
3725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
3735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
3745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
3755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
3765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
3775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
3785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
3795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
3805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
3815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
3825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
3835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
3845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
3855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
3865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
3895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3903df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
391c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].texture, texture);
3927cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
393420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].textureTimeStamp = rsUptimeMillis();
3947cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
3957cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
396b378af500b36226635b6343b1d5009ee9af44fc1Jim Millervoid setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
3977cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
3983df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
399c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].detailTexture, texture);
4007cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.x = offx;
4017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureOffset.y = offy;
402b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.x = loffx;
403b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    cards[n].detailLineOffset.y = loffy;
4047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
405420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    cards[n].detailTextureTimeStamp = rsUptimeMillis();
4065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
4095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4103df59346f395434454d310b070fff195089fbaf1Jim Miller    if (n < 0 || n >= cardCount) return;
411c4c6f38bf410af40e10c63b152befd5a39df87c8Jim Miller    rsSetObject(&cards[n].geometry, geometry);
4125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
4135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
4145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
4155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
4165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
418198a060d650bc849ef0f25b597888fac9546803bJack Palevichvoid requestFirstCardPosition()
419198a060d650bc849ef0f25b597888fac9546803bJack Palevich{
420198a060d650bc849ef0f25b597888fac9546803bJack Palevich    int data[1];
421198a060d650bc849ef0f25b597888fac9546803bJack Palevich    data[0] = currentFirstCard;
422198a060d650bc849ef0f25b597888fac9546803bJack Palevich    bool enqueued = rsSendToClient(CMD_REPORT_FIRST_CARD_POSITION, data, sizeof(data));
423198a060d650bc849ef0f25b597888fac9546803bJack Palevich    if (!enqueued) {
424198a060d650bc849ef0f25b597888fac9546803bJack Palevich        rsDebug("Couldn't send CMD_REPORT_FIRST_CARD_POSITION", 0);
425198a060d650bc849ef0f25b597888fac9546803bJack Palevich    }
426198a060d650bc849ef0f25b597888fac9546803bJack Palevich}
427198a060d650bc849ef0f25b597888fac9546803bJack Palevich
4285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float3 getAnimatedScaleForSelected()
4295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t dt = (rsUptimeMillis() - touchTime);
4315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f;
4325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float3 one = { 1.0f, 1.0f, 1.0f };
4335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return one + fraction * SELECTED_SCALE_FACTOR;
4345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
436c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function
437c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma//    P(t) = 1 / (1 + e^(-t))
438c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Parameter t: Any real number
439c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma// Returns: A float in the range (0,1), with P(0.5)=0
440c0bb8af58ae15674178f2db240283719918c6f28Jim Shumastatic float logistic(float t) {
441af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao    return 1.f / (1.f + exp(-t));
442c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
443c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
4447c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic float getSwayAngleForVelocity(float v, bool enableSway)
445c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma{
4467c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float sway = 0.0f;
4477c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma
4487c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    if (enableSway) {
449d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak
450d7fa647e6fa4e832381be5bdd03065f9ea35c3f1Jim Shuma        sway = range * (logistic(-v * swaySensitivity) - 0.5f);
4517c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    }
452c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
453c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    return sway;
454c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma}
455c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
4567c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// matrix: The output matrix.
4577c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// i: The card we're getting the matrix for.
4587c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma// enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.)
4597c09ccce478100d75e4427d87866ff19d758ae7aJim Shumastatic void getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway)
4605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
4627c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma    float swayAngle = getSwayAngleForVelocity(velocity, enableSway);
4635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
4645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, radius, 0, 0);
46583d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    float rotation = cardRotation + swayAngle;
46683d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    if (!cardsFaceTangent) {
46783d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney      rotation -= theta;
46883d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    }
46983d7a5f03e6511372f73e3e4e03a6d403b20125dBryan Mawhinney    rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0);
4705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (i == currentSelection) {
4715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 scale = getAnimatedScaleForSelected();
4725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
4735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
4745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: apply custom matrix for cards[i].geometry
4755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
477420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller/*
478420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Draws cards around the Carousel.
479420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
480420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller */
481420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawCards(int64_t currentTime)
4825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
483420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
484420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    const float endAngle = startAngle + visibleSlotCount * wedgeAngle;
485420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
486420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    for (int i = cardCount-1; i >= 0; i--) {
4875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
488420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // If this card was recently loaded, this will be < 1.0f until the animation completes
489420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime);
490420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (animatedAlpha < 1.0f) {
491420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                stillAnimating = true;
492420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
493420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
494420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Compute fade out for cards in the distance
495420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            float positionAlpha;
496420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            if (rezInCardCount > 0.0f) {
497420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle;
498420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = min(1.0f, positionAlpha / rezInCardCount);
499420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            } else {
500420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                positionAlpha = 1.0f;
501420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            }
502420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
503420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            // Set alpha for blending between the textures
504420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
505420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller            rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
506420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
507b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // Bind the appropriate shader network.  If there's no alpha blend, then
508b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            // switch to single shader for better performance.
509b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            const bool loaded = cards[i].textureState == STATE_LOADED;
510b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
511b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindProgramFragment(singleTextureFragmentProgram);
512b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(singleTextureFragmentProgram, 0,
513b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        (loaded && shaderConstants->fadeAmount == 1.0f) ?
514b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        cards[i].texture : loadingTexture);
5155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
516b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindProgramFragment(multiTextureFragmentProgram);
517b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
518b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
519b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        cards[i].texture : loadingTexture);
5205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
5235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
5247c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, i, true);
5255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
5265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
5275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(cards[i].geometry);
5285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
5295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(loadingGeometry);
5305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
5315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(defaultGeometry);
5325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
5335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
5345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
5355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
5365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
5375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
5385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
5395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
5415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
542420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
5435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5454fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma/**
5464fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * Convert projection from normalized coordinates to pixel coordinates.
5474fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma *
5484fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma * @return True on success, false on failure.
5494fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma */
5504fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shumastatic bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) {
5514fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // This is probably cheaper than pre-multiplying with another matrix.
5524fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    if (screenCoord->w == 0.0f) {
5534fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        rsDebug("Bad transform while converting from normalized to pixel coordinates: ",
5544fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma            screenCoord);
5554fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma        return false;
5564fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    }
5574fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    *screenCoord *= 1.0f / screenCoord->w;
5584fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x += 1.0f;
5594fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y += 1.0f;
5604fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z += 1.0f;
5614fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->x = round(screenCoord->x * 0.5f * width);
5624fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->y = round(screenCoord->y * 0.5f * height);
5634fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    screenCoord->z = - 0.5f * screenCoord->z;
5644fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    return true;
5654fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma}
5664fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma
5677cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller/*
5687cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * Draws a screen-aligned card with the exact dimensions from the detail texture.
5697cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller * This is used to display information about the object being displayed above the geomertry.
570420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller * Returns true if we're still animating any property of the cards (e.g. fades).
5717cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller */
572420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic bool drawDetails(int64_t currentTime)
5737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller{
5747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float width = rsgGetWidth();
5757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float height = rsgGetHeight();
5767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
577420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    bool stillAnimating = false;
578420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
5797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    // We'll be drawing in screen space, sampled on pixel centers
5807cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rs_matrix4x4 projection, model;
5817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f);
5827cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadProjectionMatrix(&projection);
5837cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsMatrixLoadIdentity(&model);
5847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    rsgProgramVertexLoadModelMatrix(&model);
5857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    updateCamera = true; // we messed with the projection matrix. Reload on next pass...
5867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
5877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry
5887cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
589420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    // This can be done once...
590a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture);
591a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
592a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float wedgeAngle = 2.0f * M_PI / slotCount;
593a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where details start fading from 1.0f
594a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle;
595a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    // Angle where detail alpha is 0.0f
596a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle;
597420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
598a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
5997cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        if (cards[i].visible) {
6007cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) {
6017cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float lineWidth = rsAllocationGetDimX(detailLineTexture);
6027cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
6034fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                // Compute position in screen space of top corner or bottom corner of card
604af8cf9a3bbe517b604b48e217b00085351ab2496Shih-wei Liao                rsMatrixLoad(&model, &modelviewMatrix);
6057c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                getMatrixForCard(&model, i, false);
6067cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rs_matrix4x4 matrix;
6077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model);
608d443c88da4c7cf1947c12b26f111cb899cc8afe4Jim Miller
6094fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                int indexLeft, indexRight;
6104fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoord;
6114fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (drawDetailBelowCard) {
6124fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 0;
6134fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 1;
6144fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                } else {
6154fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexLeft = 3;
6164fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    indexRight = 2;
6174fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
6184fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]);
6194fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]);
6204fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) {
6217cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    // this shouldn't happen
6227cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    rsDebug("Bad transform: ", screenCoord);
6237cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    continue;
6247cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
6254fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height);
6264fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height);
6274fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (debugDetails) {
6284fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordLeft);
6294fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    RS_DEBUG(screenCoordRight);
6304fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
6314fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                screenCoord = screenCoordLeft;
6324fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (drawDetailBelowCard) {
6334fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y);
6344fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
6354fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                if (detailTexturesCentered) {
6364fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                    screenCoord.x += (screenCoordRight.x - screenCoordLeft.x) / 2. -
6374fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                        rsAllocationGetDimX(cards[i].detailTexture) / 2.;
6384fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma                }
6397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
640420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Compute alpha for gradually fading in details. Applied to both line and
641420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // detail texture. TODO: use a separate background texture for line.
642420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, currentTime);
643420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                if (animatedAlpha < 1.0f) {
644420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    stillAnimating = true;
645420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                }
646420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
647a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                // Compute alpha based on position. We fade cards quickly so they cannot overlap
648a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i))
649a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                        / (endDetailFadeAngle - startDetailFadeAngle);
650a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = max(0.0f, positionAlpha);
651a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                positionAlpha = min(1.0f, positionAlpha);
652a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
653a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha);
654a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
655b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 0.0f) continue; // nothing to draw
656b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
657b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(singleTextureFragmentProgram);
658b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
659b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindProgramFragment(multiTextureFragmentProgram);
660b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
661a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller
662420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                // Set alpha for blending between the textures
663a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller                shaderConstants->fadeAmount = blendedAlpha;
664420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
665420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller
6667cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw line from upper left card corner to the top of the screen
6677c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                if (drawRuler) {
6687c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float halfWidth = lineWidth * 0.5f;
6697c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float rulerTop = drawDetailBelowCard ? screenCoord.y : height;
6707c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                    const float rulerBottom = drawDetailBelowCard ? 0 : screenCoord.y;
671b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float x0 = cards[i].detailLineOffset.x + screenCoord.x - halfWidth;
672b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float x1 = cards[i].detailLineOffset.x + screenCoord.x + halfWidth;
673b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y0 = rulerBottom + yPadding;
674b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y;
675b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
676b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    if (blendedAlpha == 1.0f) {
677b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture);
678b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    } else {
679b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture);
680b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    }
681b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
682b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                            x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
6837c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                }
6847cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
6857cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                // Draw the detail texture next to it using the offsets provided.
6867cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture);
6877cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture);
6887cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offx = cards[i].detailTextureOffset.x;
6897cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                const float offy = -cards[i].detailTextureOffset.y;
6907c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma                const float textureTop = drawDetailBelowCard ? screenCoord.y : height;
691b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx;
692b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth;
693b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y;
694b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                const float y1 = textureTop + offy - cards[i].detailLineOffset.y;
695b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
696b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                if (blendedAlpha == 1.0f) {
697b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture);
698b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                } else {
699b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                    rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture);
700b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                }
701b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                rsgDrawQuad(x0, y0, screenCoord.z,  x1, y0, screenCoord.z,
702b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        x1, y1, screenCoord.z,  x0, y1, screenCoord.z);
7037cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
7047cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
7057cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller    }
706420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    return stillAnimating;
7077cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller}
7087cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller
7099afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Millerstatic void drawBackground()
7109afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller{
711b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    static bool toggle;
7129afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    if (backgroundTexture.p != 0) {
7139afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
7149afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rs_matrix4x4 projection, model;
7159afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f);
7169afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadProjectionMatrix(&projection);
7179afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsMatrixLoadIdentity(&model);
7189afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgProgramVertexLoadModelMatrix(&model);
719a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller        rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture);
7209afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        float z = -0.9999f;
7219afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgDrawQuad(
7229afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[0].x, cardVertices[0].y, z,
7239afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[1].x, cardVertices[1].y, z,
7249afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[2].x, cardVertices[2].y, z,
7259afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            cardVertices[3].x, cardVertices[3].y, z);
7269afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        updateCamera = true; // we mucked with the matrix.
7279afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    } else {
7289afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller        rsgClearDepth(1.0f);
729b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        if (debugRendering) { // for debugging - flash the screen so we know we're still rendering
730b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            rsgClearColor(toggle ? backgroundColor.x : 1.0f,
731b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.y : 0.0f,
732b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        toggle ? backgroundColor.z : 0.0f,
733b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller                        backgroundColor.w);
7349afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller            toggle = !toggle;
735b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        } else {
7367cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller           rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z,
7377cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                   backgroundColor.w);
7389afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller       }
7399afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    }
7409afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller}
7419afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller
7425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
7435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
7455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
7465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
7475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
7485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
7495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
7515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
7525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
7535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
7575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
7585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
7595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool isDragging;
7605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
7615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
7625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
7635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityThreshold = 0.1f * M_PI / 180.0f;
7645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityTracker;
7655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int velocityTrackerCount;
7665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
7675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
7695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
7705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
7725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
7745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
7775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
7795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint doSelection(float x, float y)
7825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
784b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (makeRayForPixelAt(&ray, &camera, x, y)) {
7855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
7865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
7875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
7895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStart(float x, float y)
7925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
7945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
7955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
7965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
7975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClient(CMD_ANIMATION_FINISHED);
7985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = false;
7997867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma        currentSelection = -1;
8007867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma    } else {
8017867abe6e7af226fc29285890d6decb0ce3daa0fJim Shuma        currentSelection = doSelection(x, y);
8025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTracker = 0.0f;
8045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTrackerCount = 0;
8055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchTime = rsUptimeMillis();
8065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
8075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStop(float x, float y)
8115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
8138b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
8145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1 && (currentTime - touchTime) < ANIMATION_SCALE_TIME) {
8157cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        // rsDebug("HIT!", currentSelection);
8165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
8175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        data[0] = currentSelection;
8185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
8195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
8205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        velocity = velocityTrackerCount > 0 ?
8215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
8225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(velocity) > velocityThreshold) {
8235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
8245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_STARTED);
8255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    currentSelection = -1;
8285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = rsUptimeMillis();
8295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
831594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shumavoid doLongPress()
832594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma{
833594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    int64_t currentTime = rsUptimeMillis();
834594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    updateAllocationVars(cards);
835594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    if (currentSelection != -1) {
836594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        // rsDebug("HIT!", currentSelection);
837594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        int data[1];
838594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        data[0] = currentSelection;
839594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma        rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data));
840594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    }
841594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    currentSelection = -1;
842594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma    lastTime = rsUptimeMillis();
843594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma}
844594ff62c170509c0d69b30f4c2a5e71d4799a9c8Jim Shuma
8455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doMotion(float x, float y)
8465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
8485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
8495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias += deltaOmega;
8505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
8515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
8525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float dt = deltaTimeInSeconds(currentTime);
8535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
8545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
8555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //if ((velocityTracker > 0.0f) == (v > 0.0f)) {
8565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTracker += v;
8575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTrackerCount++;
8585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //} else {
8595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTracker = v;
8605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTrackerCount = 1;
8615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //}
8625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
863c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    velocity = velocityTrackerCount > 0 ?
864c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma                (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
8655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Drop current selection if user drags position +- a partial slot
8675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1) {
8685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float slotMargin = 0.5f * (2.0f * M_PI / slotCount);
8695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(touchBias - bias) > slotMargin) {
8705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            currentSelection = -1;
8715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
8725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
8735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
8745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
8755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
8775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
8785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
8795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
8815ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout)
8825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
8835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    static const float tmin = 0.0f;
8845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
8865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
8875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
8885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
8905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
8915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
8935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
8945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
8965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
8975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
8985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
8995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
9005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
9015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
9035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
9045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
9055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
9065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
9075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
909b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
910b378af500b36226635b6343b1d5009ee9af44fc1Jim MillerrayPlaneIntersect(Ray* ray, float3 point, float3 normal)
911b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
912b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return false; // TODO
913b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
914b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
915b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool
916b378af500b36226635b6343b1d5009ee9af44fc1Jim MillerrayCylinderIntersect(Ray* ray, float3 center, float radius)
917b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
918b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return false; // TODO
919b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
920b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
921b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates.
9225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
923b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
924b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y)
9255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
9275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
928b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.from:", cam->from);
929b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.at:", cam->at);
930b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Camera.dir:", normalize(cam->at - cam->from));
9315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
9325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
9345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
935b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float u = x / rsgGetWidth();
936b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float v = 1.0f - (y / rsgGetHeight());
937b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float aspect = (float) rsgGetWidth() / rsgGetHeight();
938b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f));
939b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dir = normalize(cam->at - cam->from);
940b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 du = tanfov2 * normalize(cross(dir, cam->up));
941b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 dv = tanfov2 * normalize(cross(du, dir));
942b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    du *= aspect;
943b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
944b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = cam->from;
945b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
946b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
947b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (vector math) = ", rayDir);
9485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
9495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
950b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
951b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
952b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    return true;
953b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller}
954b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
955b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Creates a ray for an Android pixel coordinate given a model view and projection matrix.
956b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
957b378af500b36226635b6343b1d5009ee9af44fc1Jim Millerstatic bool __attribute__((overloadable))
958b378af500b36226635b6343b1d5009ee9af44fc1Jim MillermakeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y)
959b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller{
960b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rs_matrix4x4 pm = *model;
961b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsMatrixLoadMultiply(&pm, proj, model);
962b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (!rsMatrixInverse(&pm)) {
963b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("ERROR: SINGULAR PM MATRIX", 0);
964b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        return false;
9655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
966b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float width = rsgGetWidth();
967b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float height = rsgGetHeight();
968b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winx = 2.0f * x / width - 1.0f;
969b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float winy = 2.0f * y / height - 1.0f;
970b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
971b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
972b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    float4 at = { winx, winy, 1.0f, 1.0f };
973b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
974b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye = rsMatrixMultiply(&pm, eye);
975b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    eye *= 1.0f / eye.w;
9765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
977b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at = rsMatrixMultiply(&pm, at);
978b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    at *= 1.0f / at.w;
979b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller
980b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayPoint = { eye.x, eye.y, eye.z };
981b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 atPoint = { at.x, at.y, at.z };
982b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    const float3 rayDir = normalize(atPoint - rayPoint);
983b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    if (debugCamera) {
984b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winx: ", winx);
985b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("winy: ", winy);
986b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray position (transformed) = ", eye);
987b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller        rsDebug("Ray direction (transformed) = ", rayDir);
988b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    }
989b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->position =  rayPoint;
990b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    ray->direction = rayDir;
9915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
9925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
9935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
9945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
9955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
9965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
9975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
9985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[id].visible) {
9995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
10005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
10015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
10035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
10047c09ccce478100d75e4427d87866ff19d758ae7aJim Shuma            getMatrixForCard(&matrix, id, true);
10055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
10065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
10075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
10085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
10095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
10105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
10115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
10125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
10135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
10145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
10155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
10185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
10195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
10205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
10215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
10235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
10255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
10285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// simple physics model.
10295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If the cards are still in motion, returns true.
10305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
10315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
10335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float dt = deltaTimeInSeconds(currentTime);
10345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (dt <= 0.0f)
10355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            return animating;
10365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
10375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
10385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        dt /= N;
10395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < N; i++) {
10405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Force friction - always opposes motion
10415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Ff = -frictionCoeff * velocity;
10425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Restoring force to match cards with slots
10445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float theta = startAngle + bias;
10455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float dtheta = 2.0f * M_PI / slotCount;
10465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float position = theta / dtheta;
10475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float fraction = position - floor(position); // fractional position between slots
10485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float x;
10495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (fraction > 0.5f) {
10505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = - (1.0f - fraction);
10515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
10525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = fraction;
10535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
10545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Fr = - springConstant * x;
10555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // compute velocity
10575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float momentum = mass * velocity + (Ff + Fr)*dt;
10585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocity = momentum / mass;
10595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            bias += velocity * dt;
10605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
10615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = fabs(velocity) > velocityThreshold;
10635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (!animating) {
10645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_FINISHED);
10655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
10665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
10675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
10685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1069f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    const float firstBias = wedgeAngle(0.0f);
1070f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    const float lastBias = -max(0.0f, wedgeAngle(cardCount - visibleDetailCount));
1071f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller
1072f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    if (bias > firstBias) {
1073f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller        bias = firstBias;
1074f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller    } else if (bias < lastBias) {
1075f7c724da4bb4fcd3cd02add04a7bb8052e07e4c3Jim Miller        bias = lastBias;
1076c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma    }
1077c0bb8af58ae15674178f2db240283719918c6f28Jim Shuma
10785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return animating;
10795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
10805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
10825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
10835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
10845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cullCards()
10855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
10864fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // TODO(jshuma): Instead of fully fetching prefetchCardCount cards, make a distinction between
10874fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // STATE_LOADED and a new STATE_PRELOADING, which will keep the textures loaded but will not
10884fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    // attempt to actually draw them.
10894fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const int prefetchCardCountPerSide = prefetchCardCount / 2;
10904fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaFirst = slotPosition(-prefetchCardCountPerSide);
1091198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelected = slotPosition(0);
1092198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f;
1093198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedLow = thetaSelected - thetaHalfAngle;
1094198a060d650bc849ef0f25b597888fac9546803bJack Palevich    const float thetaSelectedHigh = thetaSelected + thetaHalfAngle;
10954fe6ea729d1fc44c8126de7a92a710c3885fb2ecJim Shuma    const float thetaLast = slotPosition(visibleSlotCount - 1 + prefetchCardCountPerSide);
10965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
10975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int count = 0;
1098198a060d650bc849ef0f25b597888fac9546803bJack Palevich    int firstVisible = -1;
10995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
11005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
11015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
11025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
11035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (p >= thetaFirst && p < thetaLast) {
1104198a060d650bc849ef0f25b597888fac9546803bJack Palevich                if (firstVisible == -1 && p >= thetaSelectedLow && p < thetaSelectedHigh) {
1105198a060d650bc849ef0f25b597888fac9546803bJack Palevich                    firstVisible = i;
1106198a060d650bc849ef0f25b597888fac9546803bJack Palevich                }
11075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = true;
11085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                count++;
11095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
11105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = false;
11115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
11135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
11145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
11155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].visible = true;
11165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            count++;
11175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
11185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
1119198a060d650bc849ef0f25b597888fac9546803bJack Palevich    currentFirstCard = firstVisible;
11205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return count;
11215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
11245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
1125420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Millerstatic void updateCardResources(int64_t currentTime)
11265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1127a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    for (int i = cardCount-1; i >= 0; --i) {
11285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
11295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
11302ba04e061b52c488a154739379501dc833e39f79Jim Miller            if (debugTextureLoading) rsDebug("*** Texture stamp: ", cards[i].textureTimeStamp);
11312ba04e061b52c488a154739379501dc833e39f79Jim Miller
11325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request texture from client if not loaded
11335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_INVALID) {
11345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
11355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
11365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
11375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_LOADING;
11385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
11397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
11407cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
11417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
11427cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // request detail texture from client if not loaded
11437cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState == STATE_INVALID) {
11447cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
11457cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
11467cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
11477cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_LOADING;
11487cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
11497cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
11505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
11515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request geometry from client if not loaded
11535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_INVALID) {
11545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
11555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
11565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
11575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_LOADING;
11585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
11597cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
11605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
11615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
11635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
1164dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].textureState != STATE_INVALID) {
11655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
11665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
11675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
11685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
1169420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].textureTimeStamp = currentTime;
11705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
11717cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
11727cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                }
11737cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            }
11747cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            // ask the host to remove the detail texture
11757cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller            if (cards[i].detailTextureState != STATE_INVALID) {
11767cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                data[0] = i;
11777cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data));
11787cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                if (enqueued) {
11797cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    cards[i].detailTextureState = STATE_INVALID;
1180420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller                    cards[i].detailTextureTimeStamp = currentTime;
11817cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                } else {
11827cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0);
11835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
11845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
1186dce9af330efceae2b8d1d7c25e7e236b4e21719bJack Palevich            if (cards[i].geometryState != STATE_INVALID) {
11875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
11885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
11895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
11905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
11915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
11927cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller                    if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
11935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
11945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
11955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
11965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
11975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
11985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
11995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
12005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
12015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
12025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
12035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
12045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
12055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
12065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
12075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    color(1.0f, 0.0f, 0.0f, 1.0f);
12085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
12095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
12105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
12115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
12125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
1213b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller            if (makeRayForPixelAt(&ray, &camera, posX, posY)) {
12145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
12155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
12165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
12175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
12185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
12195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
12205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
12225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
12245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
12255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
12275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
1228b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp);
1229b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp);
1230b378af500b36226635b6343b1d5009ee9af44fc1Jim Miller    rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp);
12315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12328b55d7500c1e5a88c415dae8dcead16b152d7929Jack Palevich    updateAllocationVars(cards);
12335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (!initialized) {
12352ba04e061b52c488a154739379501dc833e39f79Jim Miller        if (debugTextureLoading){
12362ba04e061b52c488a154739379501dc833e39f79Jim Miller            rsDebug("*** initialized was false, updating all cards (cards = ", cards);
12372ba04e061b52c488a154739379501dc833e39f79Jim Miller        }
12387cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        const float2 zero = {0.0f, 0.0f};
12397cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        for (int i = 0; i < cardCount; i++) {
12402ba04e061b52c488a154739379501dc833e39f79Jim Miller            initCard(cards + i);
12417cb0068e59dde61ef0e649735199e5ba31c9c6afJim Miller        }
12425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        initialized = true;
12435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1245a9e9c4bef076e718094786edfe0290f798e1db4bJim Miller    rsgBindProgramFragment(singleTextureFragmentProgram);
1246bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    rsgBindProgramStore(programStoreOpaque);
12479afba8c61f6aff94c68acbfaae1cc58bd28c13eaJim Miller    drawBackground();
12485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
12505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME;
12525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bool stillAnimating = updateNextPosition(currentTime) || !timeExpired;
12535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
12555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1256420b44b8b11ec1c309ea130e69a6876325dbfef9Jim Miller    updateCardResources(currentTime);
12575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1258bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    // Draw cards opaque only if requested, and always draw detail textures with blending.
1259bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    if (drawCardsWithBlending) {
1260bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma        rsgBindProgramStore(programStore);
1261bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    } else {
1262bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma        // programStoreOpaque is already bound
1263bfc5ce2da9e0d8d0ec2535c465624574d98418d7Jim Shuma    }
1264bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawCards(currentTime);
1265bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    rsgBindProgramStore(programStoreDetail);
1266bf39450b962d91ec78af53db39826d55ddb39902Jim Shuma    stillAnimating |= drawDetails(currentTime);
12675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugPicking) {
12695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
12705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
12715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
12735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
12745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return stillAnimating ? 1 : 0;
12755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1276