carousel.rs revision 5ce730797a8a7278dfe19dac8a9460b25675fed0
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 {
245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_allocation texture;
255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_mesh geometry;
265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rs_matrix4x4 matrix; // custom transform for this card/geometry
275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int textureState;  // whether or not the texture is loaded.
285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int geometryState; // whether or not geometry is loaded
295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int visible; // not bool because of packing bug?
305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Card_t;
315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct Ray_s {
335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 position;
345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 direction;
355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} Ray;
365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millertypedef struct PerspectiveCamera_s {
385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 from;
395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 at;
405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 up;
415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  fov;
425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  aspect;
435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  near;
445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float  far;
455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller} PerspectiveCamera;
465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request states. Used for loading 3D object properties from the Java client.
485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Typical properties: texture, geometry and matrices.
495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerenum {
505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_INVALID = 0, // item hasn't been loaded
515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADING, // we've requested an item but are waiting for it to load
525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    STATE_LOADED // item was delivered
535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_CARD_SELECTED = 100;
575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_TEXTURE = 200;
585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_TEXTURE = 210;
595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_REQUEST_GEOMETRY = 300;
605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_INVALIDATE_GEOMETRY = 310;
615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_STARTED = 400;
625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_ANIMATION_FINISHED = 500;
635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int CMD_PING = 600;
645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Constants
665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this %
685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Debug flags
705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerbool debugCamera = false; // dumps ray/camera coordinate stuff
715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerbool debugPicking = false; // renders picking area on top of geometry
725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Exported variables. These will be reflected to Java set_* variables.
745ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerCard_t *cards; // array of cards to draw
755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat startAngle; // position of initial card, in radians
765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint slotCount; // number of positions where a card can be
775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint cardCount; // number of cards in stack
785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint visibleSlotCount; // number of visible slots (for culling)
795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat radius; // carousel radius. Cards will be centered on a circle with this radius
805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerfloat cardRotation; // rotation of card in XY plane relative to Z=1
815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_store programStore;
825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_fragment fragmentProgram;
835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_vertex vertexProgram;
845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_program_raster rasterProgram;
855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation defaultTexture; // shown when no other texture is assigned
865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh defaultGeometry; // shown when no geometry is loaded
885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_mesh loadingGeometry; // shown when geometry is loading
895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 projectionMatrix;
905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerrs_matrix4x4 modelviewMatrix;
915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_var(radius, cards, slotCount, visibleSlotCount, cardRotation)
935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_var(programStore, fragmentProgram, vertexProgram, rasterProgram)
945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_var(startAngle, defaultTexture, loadingTexture, defaultGeometry, loadingGeometry)
955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_func(createCards, lookAt, doStart, doStop, doMotion, doSelection, setTexture)
965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller#pragma rs export_func(setGeometry, debugCamera, debugPicking)
975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Local variables
995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float bias; // rotation bias, in radians. Used for animation and dragging.
1005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateCamera;    // force a recompute of projection and lookat matrices
1015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool initialized;
1025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float3 backgroundColor = { 0.0f, 0.0f, 0.0f };
1035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float FLT_MAX = 1.0e37;
1045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int currentSelection = -1;
1055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t touchTime = -1;  // time of first touch (see doStart())
1065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float touchBias = 0.0f; // bias on first touch
1075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default geometry when card.geometry is not set.
1095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float3 cardVertices[4] = {
1105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { -1.0, -1.0, 0.0 },
1115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, -1.0, 0.0 },
1125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        { 1.0, 1.0, 0.0 },
1135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {-1.0, 1.0, 0.0 }
1145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Default camera
1175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic PerspectiveCamera camera = {
1185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {2,2,2}, // from
1195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,0,0}, // at
1205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        {0,1,0}, // up
1215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        25.0f,   // field of view
1225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        1.0f,    // aspect
1235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        0.1f,    // near
1245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        100.0f   // far
1255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller};
1265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Forward references
1285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime);
1295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool makeRayForPixelAt(Ray* ray, float x, float y);
1305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current);
1315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid init() {
1335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // initializers currently have a problem when the variables are exported, so initialize
1345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // globals here.
1355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsDebug("Renderscript: init()", 0);
1365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    startAngle = 0.0f;
1375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    slotCount = 10;
1385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    visibleSlotCount = 1;
1395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias = 0.0f;
1405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    radius = 1.0f;
1415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardRotation = 0.0f;
1425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
1435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
1445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateAllocationVars()
1475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Cards
1495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rs_allocation cardAlloc = rsGetAllocation(cards);
1505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: use new rsIsObject()
1515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cardCount = cardAlloc.p != 0 ? rsAllocationGetDimX(cardAlloc) : 0;
1525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid createCards(int n)
1555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsDebug("CreateCards: ", n);
1575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    initialized = false;
1585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
1595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return angle for position p. Typically p will be an integer position, but can be fractional.
1625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float cardPosition(float p)
1635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + bias + 2.0f * M_PI * p / slotCount;
1655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
1685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float slotPosition(float p)
1695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return startAngle + 2.0f * M_PI * p / slotCount;
1715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Return the lowest slot number for a given angular position.
1745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cardIndex(float angle)
1755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
1775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
1785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
1795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Set basic camera properties:
1805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    from - position of the camera in x,y,z
1815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    at - target we're looking at - used to compute view direction
1825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//    up - a normalized vector indicating up (typically { 0, 1, 0})
1835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller//
1845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
1855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid lookAt(float fromX, float fromY, float fromZ,
1865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float atX, float atY, float atZ,
1875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float upX, float upY, float upZ)
1885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
1895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.x = fromX;
1905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.y = fromY;
1915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.from.z = fromZ;
1925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.x = atX;
1935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.y = atY;
1945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.at.z = atZ;
1955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.x = upX;
1965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.y = upY;
1975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    camera.up.z = upZ;
1985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCamera = true;
1995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Load a projection matrix for the given parameters.  This is equivalent to gluPerspective()
2025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
2035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadIdentity(matrix);
2055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float top = near * tan((float) (fovy * M_PI / 360.0f));
2065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float bottom = -top;
2075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float left = bottom * aspect;
2085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float right = top * aspect;
2095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
2105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Construct a matrix based on eye point, center and up direction. Based on the
2135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// man page for gluLookat(). Up must be normalized.
2145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
2155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 f = normalize(center - eye);
2175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s = normalize(cross(f, up));
2185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 u = cross(s, f);
2195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float m[16];
2205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[0] = s.x;
2215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[4] = s.y;
2225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[8] = s.z;
2235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[12] = 0.0f;
2245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[1] = u.x;
2255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[5] = u.y;
2265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[9] = u.z;
2275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[13] = 0.0f;
2285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[2] = -f.x;
2295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[6] = -f.y;
2305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[10] = -f.z;
2315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[14] = 0.0f;
2325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[3] = m[7] = m[11] = 0.0f;
2335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    m[15] = 1.0f;
2345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixLoad(matrix, m);
2355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
2365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setTexture(int n, rs_allocation texture)
2395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cards[n].texture = texture;
2415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].texture.p != 0)
2425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].textureState = STATE_LOADED;
2435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
2445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].textureState = STATE_INVALID;
2455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid setGeometry(int n, rs_mesh geometry)
2485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cards[n].geometry = geometry;
2505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (cards[n].geometry.p != 0)
2515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_LOADED;
2525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else
2535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        cards[n].geometryState = STATE_INVALID;
2545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float3 getAnimatedScaleForSelected()
2575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t dt = (rsUptimeMillis() - touchTime);
2595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f;
2605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float3 one = { 1.0f, 1.0f, 1.0f };
2615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return one + fraction * SELECTED_SCALE_FACTOR;
2625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void getMatrixForCard(rs_matrix4x4* matrix, int i)
2655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float theta = cardPosition(i);
2675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
2685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixTranslate(matrix, radius, 0, 0);
2695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsMatrixRotate(matrix, degrees(-theta + cardRotation), 0, 1, 0);
2705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (i == currentSelection) {
2715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 scale = getAnimatedScaleForSelected();
2725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixScale(matrix, scale.x, scale.y, scale.z);
2735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
2745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: apply custom matrix for cards[i].geometry
2755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
2765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void drawCards()
2785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
2795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float depth = 1.0f;
2805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
2815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
2825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Bind texture
2835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_LOADED) {
2845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgBindTexture(fragmentProgram, 0, cards[i].texture);
2855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].textureState == STATE_LOADING) {
2865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgBindTexture(fragmentProgram, 0, loadingTexture);
2875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
2885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgBindTexture(fragmentProgram, 0, defaultTexture);
2895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
2905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
2915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Draw geometry
2925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix = modelviewMatrix;
2935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            getMatrixForCard(&matrix, i);
2945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgProgramVertexLoadModelMatrix(&matrix);
2955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
2965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(cards[i].geometry);
2975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
2985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(loadingGeometry);
2995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (defaultGeometry.p != 0) {
3005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawMesh(defaultGeometry);
3015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
3025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                // Draw place-holder geometry
3035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                rsgDrawQuad(
3045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
3055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
3065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
3075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
3085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
3095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
3105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCameraMatrix(float width, float height)
3145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float aspect = width / height;
3165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (aspect != camera.aspect || updateCamera) {
3175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        camera.aspect = aspect;
3185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
3195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
3205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
3225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
3235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        updateCamera = false;
3245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
3285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Behavior/Physics
3295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
3305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocity = 0.0f;  // angular velocity in radians/s
3315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool isDragging;
3325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int64_t lastTime = 0L; // keep track of how much time has passed between frames
3335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float2 lastPosition;
3345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool animating = false;
3355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityThreshold = 0.1f * M_PI / 180.0f;
3365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float velocityTracker;
3375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int velocityTrackerCount;
3385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float mass = 5.0f; // kg
3395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float G = 9.80f; // gravity constant, in m/s
3415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float springConstant = 0.0f;
3425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float frictionCoeff = 10.0f;
3435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic const float dragFactor = 0.25f;
3445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float dragFunction(float x, float y)
3465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
3485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic float deltaTimeInSeconds(int64_t current)
3515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
3535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint doSelection(float x, float y)
3565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    Ray ray;
3585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (makeRayForPixelAt(&ray, x, y)) {
3595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float bestTime = FLT_MAX;
3605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return intersectGeometry(&ray, &bestTime);
3615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return -1;
3635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStart(float x, float y)
3665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
3685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
3695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocity = 0.0f;
3705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
3715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClient(CMD_ANIMATION_FINISHED);
3725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = false;
3735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTracker = 0.0f;
3755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    velocityTrackerCount = 0;
3765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchTime = rsUptimeMillis();
3775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    touchBias = bias;
3785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    currentSelection = doSelection(x, y);
3795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
3805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
3825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doStop(float x, float y)
3835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
3845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
3855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
3865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1 && (currentTime - touchTime) < ANIMATION_SCALE_TIME) {
3875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("HIT!", currentSelection);
3885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
3895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        data[0] = currentSelection;
3905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
3915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
3925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        velocity = velocityTrackerCount > 0 ?
3935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
3945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(velocity) > velocityThreshold) {
3955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            animating = true;
3965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_STARTED);
3975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
3985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
3995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    currentSelection = -1;
4005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = rsUptimeMillis();
4015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millervoid doMotion(float x, float y)
4045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
4065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float deltaOmega = dragFunction(x, y);
4075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bias += deltaOmega;
4085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.x = x;
4095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastPosition.y = y;
4105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float dt = deltaTimeInSeconds(currentTime);
4115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (dt > 0.0f) {
4125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float v = deltaOmega / dt;
4135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //if ((velocityTracker > 0.0f) == (v > 0.0f)) {
4145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTracker += v;
4155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocityTrackerCount++;
4165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //} else {
4175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTracker = v;
4185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //    velocityTrackerCount = 1;
4195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //}
4205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
4215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Drop current selection if user drags position +- a partial slot
4235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (currentSelection != -1) {
4245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float slotMargin = 0.5f * (2.0f * M_PI / slotCount);
4255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (fabs(touchBias - bias) > slotMargin) {
4265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            currentSelection = -1;
4275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
4285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
4295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
4305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
4335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Hit detection using ray casting.
4345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller////////////////////////////////////////////////////////////////////////////////////////////////////
4355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool
4375ce730797a8a7278dfe19dac8a9460b25675fed0Jim MillerrayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout)
4385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    static const float tmin = 0.0f;
4405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e1 = p1 - p0;
4425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 e2 = p2 - p0;
4435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s1 = cross(ray->direction, e2);
4445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float div = dot(s1, e1);
4465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (div == 0.0f) return false;  // ray is parallel to plane.
4475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 d = ray->position - p0;
4495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float invDiv = 1.0f / div;
4505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float u = dot(d, s1) * invDiv;
4525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (u < 0.0f || u > 1.0f) return false;
4535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float3 s2 = cross(d, e1);
4555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float v = dot(ray->direction, s2) * invDiv;
4565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if ( v < 0.0f || (u+v) > 1.0f) return false;
4575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    float t = dot(e2, s2) * invDiv;
4595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (t < tmin || t > *tout)
4605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        return false;
4615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    *tout = t;
4625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
4635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
4645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Creates a ray for an Android pixel coordinate.
4665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Note that the Y coordinate is opposite of GL rendering coordinates.
4675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool makeRayForPixelAt(Ray* ray, float x, float y)
4685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
4695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugCamera) {
4705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("------ makeRay() -------", 0);
4715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.from:", camera.from);
4725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.at:", camera.at);
4735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsDebug("Camera.dir:", normalize(camera.at - camera.from));
4745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
4755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Vector math.  This has the potential to be much faster.
4775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
4785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (true) {
4795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float u = x / rsgGetWidth();
4805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float v = 1.0f - (y / rsgGetHeight());
4815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float aspect = (float) rsgGetWidth() / rsgGetHeight();
4825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float tanfov2 = 2.0f * tan(radians(camera.fov / 2.0f));
4835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 dir = normalize(camera.at - camera.from);
4845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 du = tanfov2 * normalize(cross(dir, camera.up));
4855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 dv = tanfov2 * normalize(cross(du, dir));
4865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        du *= aspect;
4875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
4885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayPoint = camera.from;
4895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
4905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (debugCamera) {
4915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray direction (vector math) = ", rayDir);
4925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
4935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->position =  rayPoint;
4955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->direction = rayDir;
4965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
4975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
4985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // Matrix math.  This is more generic if we allow setting model view and projection matrices
4995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    // directly
5005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    else {
5015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rs_matrix4x4 pm = modelviewMatrix;
5025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsMatrixLoadMultiply(&pm, &projectionMatrix, &modelviewMatrix);
5035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (!rsMatrixInverse(&pm)) {
5045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("ERROR: SINGULAR PM MATRIX", 0);
5055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            return false;
5065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
5075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float width = rsgGetWidth();
5085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float height = rsgGetHeight();
5095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float winx = 2.0f * x / width - 1.0f;
5105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float winy = 2.0f * y / height - 1.0f;
5115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
5135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float4 at = { winx, winy, 1.0f, 1.0f };
5145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        eye = rsMatrixMultiply(&pm, eye);
5165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        eye *= 1.0f / eye.w;
5175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        at = rsMatrixMultiply(&pm, at);
5195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        at *= 1.0f / at.w;
5205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayPoint = { eye.x, eye.y, eye.z };
5225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 atPoint = { at.x, at.y, at.z };
5235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float3 rayDir = normalize(atPoint - rayPoint);
5245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (debugCamera) {
5255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("winx: ", winx);
5265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("winy: ", winy);
5275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray position (transformed) = ", eye);
5285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsDebug("Ray direction (transformed) = ", rayDir);
5295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
5305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->position =  rayPoint;
5315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        ray->direction = rayDir;
5325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
5335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return true;
5355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int intersectGeometry(Ray* ray, float *bestTime)
5385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int hit = -1;
5405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int id = 0; id < cardCount; id++) {
5415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[id].visible) {
5425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rs_matrix4x4 matrix;
5435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float3 p[4];
5445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Transform card vertices to world space
5465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsMatrixLoadIdentity(&matrix);
5475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            getMatrixForCard(&matrix, id);
5485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            for (int vertex = 0; vertex < 4; vertex++) {
5495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
5505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (tmp.w != 0.0f) {
5515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].x = tmp.x;
5525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].y = tmp.y;
5535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex].z = tmp.z;
5545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    p[vertex] *= 1.0f / tmp.w;
5555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
5565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Bad w coord: ", tmp);
5575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
5585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Intersect card geometry
5615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
5625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
5635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                hit = id;
5645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
5665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
5675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return hit;
5685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
5695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// This method computes the position of all the cards by updating bias based on a
5715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// simple physics model.
5725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If the cards are still in motion, returns true.
5735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic bool updateNextPosition(int64_t currentTime)
5745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
5755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (animating) {
5765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float dt = deltaTimeInSeconds(currentTime);
5775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (dt <= 0.0f)
5785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            return animating;
5795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
5805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
5815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        dt /= N;
5825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < N; i++) {
5835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Force friction - always opposes motion
5845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Ff = -frictionCoeff * velocity;
5855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Restoring force to match cards with slots
5875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float theta = startAngle + bias;
5885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float dtheta = 2.0f * M_PI / slotCount;
5895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float position = theta / dtheta;
5905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float fraction = position - floor(position); // fractional position between slots
5915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float x;
5925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (fraction > 0.5f) {
5935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = - (1.0f - fraction);
5945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
5955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                x = fraction;
5965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
5975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float Fr = - springConstant * x;
5985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
5995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // compute velocity
6005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float momentum = mass * velocity + (Ff + Fr)*dt;
6015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            velocity = momentum / mass;
6025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            bias += velocity * dt;
6035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        // TODO: Add animation to smoothly move back to slots. Currently snaps to location.
6065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cardCount <= visibleSlotCount) {
6075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO: this aligns the cards to the first slot (theta = startAngle) when there aren't
6085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // enough visible cards. It should be generalized to allow alignment to front,
6095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // middle or back of the stack.
6105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cardPosition(0) != slotPosition(0)) {
6115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bias = 0.0f;
6125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
6145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cardPosition(cardCount) < 0.0f) {
6155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bias = -slotPosition(cardCount);
6165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else if (cardPosition(0) > slotPosition(0)) {
6175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bias = 0.0f;
6185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        animating = fabs(velocity) > velocityThreshold;
6225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (!animating) {
6235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            const float dtheta = 2.0f * M_PI / slotCount;
6245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            bias = round((startAngle + bias) / dtheta) * dtheta - startAngle;
6255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsSendToClient(CMD_ANIMATION_FINISHED);
6265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
6285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    lastTime = currentTime;
6295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return animating;
6315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Cull cards based on visibility and visibleSlotCount.
6345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// If visibleSlotCount is > 0, then only show those slots and cull the rest.
6355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Otherwise, it should cull based on bounds of geometry.
6365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic int cullCards()
6375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float thetaFirst = slotPosition(-1); // -1 keeps the card in front around a bit longer
6395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float thetaLast = slotPosition(visibleSlotCount);
6405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int count = 0;
6425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
6435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (visibleSlotCount > 0) {
6445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
6455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float p = cardPosition(i);
6465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (p >= thetaFirst && p < thetaLast) {
6475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = true;
6485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                count++;
6495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            } else {
6505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                cards[i].visible = false;
6515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
6535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // Cull the rest of the cards using bounding box of geometry.
6545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // TODO
6555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].visible = true;
6565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            count++;
6575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
6585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
6595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return count;
6605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
6615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
6625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Request texture/geometry for items that have come into view
6635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// or doesn't have a texture yet.
6645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void updateCardResources()
6655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
6665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int i = 0; i < cardCount; i++) {
6675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        int data[1];
6685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (cards[i].visible) {
6695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request texture from client if not loaded
6705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_INVALID) {
6715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
6725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
6735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
6745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_LOADING;
6755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
6765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
6775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
6785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // request geometry from client if not loaded
6805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_INVALID) {
6815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
6825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
6835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
6845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_LOADING;
6855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
6865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
6875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
6885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
6895ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        } else {
6905ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the texture
6915ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].textureState == STATE_LOADED) {
6925ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
6935ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
6945ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
6955ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].textureState = STATE_INVALID;
6965ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
6975ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
6985ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
6995ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7005ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            // ask the host to remove the geometry
7015ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (cards[i].geometryState == STATE_LOADED) {
7025ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                data[0] = i;
7035ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
7045ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (enqueued) {
7055ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    cards[i].geometryState = STATE_INVALID;
7065ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                } else {
7075ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
7085ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
7095ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7105ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7115ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7125ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7135ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7145ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7155ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// Places dots on geometry to visually inspect that objects can be seen by rays.
7165ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
7175ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller// card.
7185ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerstatic void renderWithRays()
7195ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller{
7205ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float w = rsgGetWidth();
7215ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const float h = rsgGetHeight();
7225ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const int skip = 8;
7235ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    color(1.0f, 0.0f, 0.0f, 1.0f);
7245ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    for (int j = 0; j < (int) h; j+=skip) {
7255ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        float posY = (float) j;
7265ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < (int) w; i+=skip) {
7275ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            float posX = (float) i;
7285ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            Ray ray;
7295ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            if (makeRayForPixelAt(&ray, posX, posY)) {
7305ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                float bestTime = FLT_MAX;
7315ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                if (intersectGeometry(&ray, &bestTime) != -1) {
7325ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
7335ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller                }
7345ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            }
7355ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        }
7365ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7375ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
7385ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7395ce730797a8a7278dfe19dac8a9460b25675fed0Jim Millerint root() {
7405ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    int64_t currentTime = rsUptimeMillis();
7415ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7425ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgClearDepth(1.0f);
7435ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramVertex(vertexProgram);
7445ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramFragment(fragmentProgram);
7455ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramStore(programStore);
7465ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    rsgBindProgramRaster(rasterProgram);
7475ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7485ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateAllocationVars();
7495ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7505ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (!initialized) {
7515ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        for (int i = 0; i < cardCount; i++)
7525ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            cards[i].textureState = STATE_INVALID;
7535ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        initialized = true;
7545ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7555ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7565ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (false) { // for debugging - flash the screen so we know we're still rendering
7575ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        static bool toggle;
7585ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        if (toggle)
7595ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
7605ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        else
7615ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller            rsgClearColor(1.0f, 0.0f, 0.0f, 1.f);
7625ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        toggle = !toggle;
7635ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    } else {
7645ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
7655ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7665ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7675ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
7685ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7695ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME;
7705ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (timeExpired) {
7715ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        //currentSelection = -1;
7725ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7735ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    bool stillAnimating = updateNextPosition(currentTime) || !timeExpired;
7745ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7755ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    cullCards();
7765ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7775ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    updateCardResources();
7785ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7795ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    drawCards();
7805ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7815ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    if (debugPicking) {
7825ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller        renderWithRays();
7835ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    }
7845ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7855ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    //rsSendToClient(CMD_PING);
7865ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller
7875ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller    return stillAnimating ? 1 : 0;
7885ce730797a8a7278dfe19dac8a9460b25675fed0Jim Miller}
789