carousel.rs revision 594ff62c170509c0d69b30f4c2a5e71d4799a9c8
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#pragma version(1) 18#pragma rs java_package_name(com.android.ex.carousel); 19#pragma rs set_reflect_license() 20 21#include "rs_graphics.rsh" 22 23typedef struct __attribute__((aligned(4))) Card { 24 // *** Update copyCard if you add/remove fields here. 25 rs_allocation texture; // basic card texture 26 rs_allocation detailTexture; // screen-aligned detail texture 27 float2 detailTextureOffset; // offset to add, in screen coordinates 28 float2 detailLineOffset; // offset to add to detail line, in screen coordinates 29 rs_mesh geometry; 30 rs_matrix4x4 matrix; // custom transform for this card/geometry 31 int textureState; // whether or not the primary card texture is loaded. 32 int detailTextureState; // whether or not the detail for the card is loaded. 33 int geometryState; // whether or not geometry is loaded 34 int visible; // not bool because of packing bug? 35 // TODO: Change when int64_t is supported. This will break after ~40 days of uptime. 36 unsigned int textureTimeStamp; // time when this texture was last updated, in seconds 37 unsigned int detailTextureTimeStamp; // time when this texture was last updated, in seconds 38} Card_t; 39 40typedef struct Ray_s { 41 float3 position; 42 float3 direction; 43} Ray; 44 45typedef struct PerspectiveCamera_s { 46 float3 from; 47 float3 at; 48 float3 up; 49 float fov; 50 float aspect; 51 float near; 52 float far; 53} PerspectiveCamera; 54 55typedef struct FragmentShaderConstants_s { 56 float fadeAmount; 57} FragmentShaderConstants; 58 59// Request states. Used for loading 3D object properties from the Java client. 60// Typical properties: texture, geometry and matrices. 61enum { 62 STATE_INVALID = 0, // item hasn't been loaded 63 STATE_LOADING, // we've requested an item but are waiting for it to load 64 STATE_LOADED // item was delivered 65}; 66 67// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. *** 68static const int CMD_CARD_SELECTED = 100; 69static const int CMD_CARD_LONGPRESS = 110; 70static const int CMD_REQUEST_TEXTURE = 200; 71static const int CMD_INVALIDATE_TEXTURE = 210; 72static const int CMD_REQUEST_GEOMETRY = 300; 73static const int CMD_INVALIDATE_GEOMETRY = 310; 74static const int CMD_ANIMATION_STARTED = 400; 75static const int CMD_ANIMATION_FINISHED = 500; 76static const int CMD_REQUEST_DETAIL_TEXTURE = 600; 77static const int CMD_INVALIDATE_DETAIL_TEXTURE = 610; 78static const int CMD_REPORT_FIRST_CARD_POSITION = 700; 79static const int CMD_PING = 1000; 80 81// Constants 82static const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms 83static const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this % 84 85// Debug flags 86const bool debugCamera = false; // dumps ray/camera coordinate stuff 87const bool debugPicking = false; // renders picking area on top of geometry 88const bool debugTextureLoading = false; // for debugging texture load/unload 89const bool debugGeometryLoading = false; // for debugging geometry load/unload 90const bool debugDetails = false; // for debugging detail texture geometry 91const bool debugRendering = false; // flashes display when the frame changes 92 93// Exported variables. These will be reflected to Java set_* variables. 94Card_t *cards; // array of cards to draw 95// TODO: remove tmpCards code when allocations support resizing 96Card_t *tmpCards; // temporary array used to prevent flashing when we add more cards 97float startAngle; // position of initial card, in radians 98int slotCount; // number of positions where a card can be 99int cardCount; // number of cards in stack 100int visibleSlotCount; // number of visible slots (for culling) 101int visibleDetailCount; // number of visible detail textures to show 102bool drawDetailBelowCard; // whether detail goes above (false) or below (true) the card 103bool drawRuler; // whether to draw a ruler from the card to the detail texture 104float radius; // carousel radius. Cards will be centered on a circle with this radius 105float cardRotation; // rotation of card in XY plane relative to Z=1 106bool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle 107float swaySensitivity; // how much to rotate cards in relation to the rotation velocity 108float frictionCoeff; // how much to slow down the carousel over time 109float dragFactor; // a scale factor for how sensitive the carousel is to user dragging 110int fadeInDuration; // amount of time (in ms) for smoothly switching out textures 111float rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in 112float detailFadeRate; // rate at which details fade as they move into the distance 113rs_program_store programStore; 114rs_program_fragment singleTextureFragmentProgram; 115rs_program_fragment multiTextureFragmentProgram; 116rs_program_vertex vertexProgram; 117rs_program_raster rasterProgram; 118rs_allocation defaultTexture; // shown when no other texture is assigned 119rs_allocation loadingTexture; // progress texture (shown when app is fetching the texture) 120rs_allocation backgroundTexture; // drawn behind everything, if set 121rs_allocation detailLineTexture; // used to draw detail line (as a quad, of course) 122rs_allocation detailLoadingTexture; // used when detail texture is loading 123rs_mesh defaultGeometry; // shown when no geometry is loaded 124rs_mesh loadingGeometry; // shown when geometry is loading 125rs_matrix4x4 projectionMatrix; 126rs_matrix4x4 modelviewMatrix; 127FragmentShaderConstants* shaderConstants; 128rs_sampler linearClamp; 129 130#pragma rs export_var(radius, cards, tmpCards, slotCount, visibleSlotCount, cardRotation, backgroundColor) 131#pragma rs export_var(cardsFaceTangent, swaySensitivity, frictionCoeff, dragFactor) 132#pragma rs export_var(visibleDetailCount, drawDetailBelowCard, drawRuler) 133#pragma rs export_var(programStore, vertexProgram, rasterProgram) 134#pragma rs export_var(singleTextureFragmentProgram, multiTextureFragmentProgram) 135#pragma rs export_var(detailLineTexture, detailLoadingTexture, backgroundTexture) 136#pragma rs export_var(linearClamp, shaderConstants) 137#pragma rs export_var(startAngle, defaultTexture, loadingTexture, defaultGeometry, loadingGeometry) 138#pragma rs export_var(fadeInDuration, rezInCardCount) 139#pragma rs export_func(createCards, copyCards, lookAt) 140#pragma rs export_func(doStart, doStop, doMotion, doLongPress, doSelection) 141#pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera, debugPicking) 142#pragma rs export_func(requestFirstCardPosition) 143 144// Local variables 145static float bias; // rotation bias, in radians. Used for animation and dragging. 146static bool updateCamera; // force a recompute of projection and lookat matrices 147static bool initialized; 148static float4 backgroundColor; 149static const float FLT_MAX = 1.0e37; 150static int currentSelection = -1; 151static int currentFirstCard = -1; 152static int64_t touchTime = -1; // time of first touch (see doStart()) 153static float touchBias = 0.0f; // bias on first touch 154static float velocity = 0.0f; // angular velocity in radians/s 155 156// Because allocations can't have 0 dimensions, we have to track whether or not 157// cards are valid separately. 158// TODO: Remove this dependency once allocations can have a zero dimension. 159static bool cardAllocationValid = false; 160 161// Default geometry when card.geometry is not set. 162static const float3 cardVertices[4] = { 163 { -1.0, -1.0, 0.0 }, 164 { 1.0, -1.0, 0.0 }, 165 { 1.0, 1.0, 0.0 }, 166 {-1.0, 1.0, 0.0 } 167}; 168 169// Default camera 170static PerspectiveCamera camera = { 171 {2,2,2}, // from 172 {0,0,0}, // at 173 {0,1,0}, // up 174 25.0f, // field of view 175 1.0f, // aspect 176 0.1f, // near 177 100.0f // far 178}; 179 180// Forward references 181static int intersectGeometry(Ray* ray, float *bestTime); 182static bool __attribute__((overloadable)) 183 makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y); 184static bool __attribute__((overloadable)) 185 makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y); 186static float deltaTimeInSeconds(int64_t current); 187 188void init() { 189 // initializers currently have a problem when the variables are exported, so initialize 190 // globals here. 191 if (debugTextureLoading) rsDebug("Renderscript: init()", 0); 192 startAngle = 0.0f; 193 slotCount = 10; 194 visibleSlotCount = 1; 195 visibleDetailCount = 3; 196 bias = 0.0f; 197 radius = 1.0f; 198 cardRotation = 0.0f; 199 cardsFaceTangent = false; 200 updateCamera = true; 201 initialized = false; 202 backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f }; 203 cardAllocationValid = false; 204 cardCount = 0; 205 fadeInDuration = 250; 206 rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled) 207 detailFadeRate = 0.5f; // fade details over this many slot positions. 208} 209 210static void updateAllocationVars(Card_t* newcards) 211{ 212 // Cards 213 rs_allocation cardAlloc = rsGetAllocation(newcards); 214 // TODO: use new rsIsObject() 215 cardCount = (cardAllocationValid && cardAlloc.p != 0) ? rsAllocationGetDimX(cardAlloc) : 0; 216} 217 218void createCards(int n) 219{ 220 if (debugTextureLoading) { 221 rsDebug("*** CreateCards with count", n); 222 } 223 224 // Since allocations can't have 0-size, we track validity ourselves based on the call to 225 // this method. 226 cardAllocationValid = n > 0; 227 228 initialized = false; 229 updateAllocationVars(cards); 230} 231 232void copyCard(Card_t* dest, Card_t * src) 233{ 234 rsSetObject(&dest->texture, src->texture); 235 rsSetObject(&dest->detailTexture, src->detailTexture); 236 dest->detailTextureOffset = src->detailTextureOffset; 237 dest->detailLineOffset = src->detailLineOffset; 238 rsSetObject(&dest->geometry, src->geometry); 239 dest->matrix = src->matrix; 240 dest->textureState = src->textureState; 241 dest->detailTextureState = src->detailTextureState; 242 dest->geometryState = src->geometryState; 243 dest->visible = src->visible; 244 dest->textureTimeStamp = src->textureTimeStamp; 245 dest->detailTextureTimeStamp = src->detailTextureTimeStamp; 246} 247 248void initCard(Card_t* card) 249{ 250 static const float2 zero = {0.0f, 0.0f}; 251 rsClearObject(&card->texture); 252 rsClearObject(&card->detailTexture); 253 card->detailTextureOffset = zero; 254 card->detailLineOffset = zero; 255 rsClearObject(&card->geometry); 256 rsMatrixLoadIdentity(&card->matrix); 257 card->textureState = STATE_INVALID; 258 card->detailTextureState = STATE_INVALID; 259 card->geometryState = STATE_INVALID; 260 card->visible = false; 261 card->textureTimeStamp = 0; 262 card->detailTextureTimeStamp = 0; 263} 264 265void copyCards(int n) 266{ 267 unsigned int oldsize = cardAllocationValid ? rsAllocationGetDimX(rsGetAllocation(cards)) : 0; 268 unsigned int newsize = rsAllocationGetDimX(rsGetAllocation(tmpCards)); 269 unsigned int copysize = min(oldsize, newsize); 270 271 // Copy existing cards 272 for (int i = 0; i < copysize; i++) { 273 if (debugTextureLoading) { 274 rsDebug("copying card ", i); 275 } 276 copyCard(tmpCards + i, cards + i); 277 // Release these now so we don't have to wait for GC for cards allocation. 278 // Assumes we're done with the cards allocation structure. 279 rsClearObject(&cards[i].texture); 280 rsClearObject(&cards[i].detailTexture); 281 rsClearObject(&cards[i].geometry); 282 cards[i].textureState = STATE_INVALID; 283 cards[i].detailTextureState = STATE_INVALID; 284 cards[i].geometryState = STATE_INVALID; 285 } 286 287 // Initialize remaining cards. 288 int first = cardAllocationValid ? min(oldsize, newsize) : 0; 289 for (int k = first; k < newsize; k++) { 290 initCard(tmpCards + k); 291 } 292 293 // Since allocations can't have 0-size, we use the same trick as createCards() where 294 // we track validity ourselves. Grrr. 295 cardAllocationValid = n > 0; 296 297 updateAllocationVars(tmpCards); 298} 299 300// Computes an alpha value for a card using elapsed time and constant fadeInDuration 301float getAnimatedAlpha(int64_t startTime, int64_t currentTime) 302{ 303 double timeElapsed = (double) (currentTime - startTime); // in ms 304 double alpha = (double) timeElapsed / fadeInDuration; 305 return min(1.0f, (float) alpha); 306} 307 308// Return angle for position p. Typically p will be an integer position, but can be fractional. 309static float cardPosition(float p) 310{ 311 return startAngle + bias + 2.0f * M_PI * p / slotCount; 312} 313 314// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional. 315static float slotPosition(float p) 316{ 317 return startAngle + 2.0f * M_PI * p / slotCount; 318} 319 320// Returns total angle for given number of cards 321static float wedgeAngle(float cards) 322{ 323 return cards * 2.0f * M_PI / slotCount; 324} 325 326// Return the lowest slot number for a given angular position. 327static int cardIndex(float angle) 328{ 329 return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI); 330} 331 332// Set basic camera properties: 333// from - position of the camera in x,y,z 334// at - target we're looking at - used to compute view direction 335// up - a normalized vector indicating up (typically { 0, 1, 0}) 336// 337// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other 338void lookAt(float fromX, float fromY, float fromZ, 339 float atX, float atY, float atZ, 340 float upX, float upY, float upZ) 341{ 342 camera.from.x = fromX; 343 camera.from.y = fromY; 344 camera.from.z = fromZ; 345 camera.at.x = atX; 346 camera.at.y = atY; 347 camera.at.z = atZ; 348 camera.up.x = upX; 349 camera.up.y = upY; 350 camera.up.z = upZ; 351 updateCamera = true; 352} 353 354// Load a projection matrix for the given parameters. This is equivalent to gluPerspective() 355static void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far) 356{ 357 rsMatrixLoadIdentity(matrix); 358 float top = near * tan((float) (fovy * M_PI / 360.0f)); 359 float bottom = -top; 360 float left = bottom * aspect; 361 float right = top * aspect; 362 rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far); 363} 364 365// Construct a matrix based on eye point, center and up direction. Based on the 366// man page for gluLookat(). Up must be normalized. 367static void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up) 368{ 369 float3 f = normalize(center - eye); 370 float3 s = normalize(cross(f, up)); 371 float3 u = cross(s, f); 372 float m[16]; 373 m[0] = s.x; 374 m[4] = s.y; 375 m[8] = s.z; 376 m[12] = 0.0f; 377 m[1] = u.x; 378 m[5] = u.y; 379 m[9] = u.z; 380 m[13] = 0.0f; 381 m[2] = -f.x; 382 m[6] = -f.y; 383 m[10] = -f.z; 384 m[14] = 0.0f; 385 m[3] = m[7] = m[11] = 0.0f; 386 m[15] = 1.0f; 387 rsMatrixLoad(matrix, m); 388 rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z); 389} 390 391void setTexture(int n, rs_allocation texture) 392{ 393 if (n < 0 || n >= cardCount) return; 394 rsSetObject(&cards[n].texture, texture); 395 cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID; 396 cards[n].textureTimeStamp = rsUptimeMillis(); 397} 398 399void setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture) 400{ 401 if (n < 0 || n >= cardCount) return; 402 rsSetObject(&cards[n].detailTexture, texture); 403 cards[n].detailTextureOffset.x = offx; 404 cards[n].detailTextureOffset.y = offy; 405 cards[n].detailLineOffset.x = loffx; 406 cards[n].detailLineOffset.y = loffy; 407 cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID; 408 cards[n].detailTextureTimeStamp = rsUptimeMillis(); 409} 410 411void setGeometry(int n, rs_mesh geometry) 412{ 413 if (n < 0 || n >= cardCount) return; 414 rsSetObject(&cards[n].geometry, geometry); 415 if (cards[n].geometry.p != 0) 416 cards[n].geometryState = STATE_LOADED; 417 else 418 cards[n].geometryState = STATE_INVALID; 419} 420 421void requestFirstCardPosition() 422{ 423 int data[1]; 424 data[0] = currentFirstCard; 425 bool enqueued = rsSendToClient(CMD_REPORT_FIRST_CARD_POSITION, data, sizeof(data)); 426 if (!enqueued) { 427 rsDebug("Couldn't send CMD_REPORT_FIRST_CARD_POSITION", 0); 428 } 429} 430 431static float3 getAnimatedScaleForSelected() 432{ 433 int64_t dt = (rsUptimeMillis() - touchTime); 434 float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f; 435 const float3 one = { 1.0f, 1.0f, 1.0f }; 436 return one + fraction * SELECTED_SCALE_FACTOR; 437} 438 439// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function 440// P(t) = 1 / (1 + e^(-t)) 441// Parameter t: Any real number 442// Returns: A float in the range (0,1), with P(0.5)=0 443static float logistic(float t) { 444 return 1.f / (1.f + exp(-t)); 445} 446 447static float getSwayAngleForVelocity(float v, bool enableSway) 448{ 449 float sway = 0.0f; 450 451 if (enableSway) { 452 const float range = M_PI; // How far we can deviate from center, peak-to-peak 453 sway = M_PI * (logistic(-v * swaySensitivity) - 0.5f); 454 } 455 456 return sway; 457} 458 459// matrix: The output matrix. 460// i: The card we're getting the matrix for. 461// enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.) 462static void getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway) 463{ 464 float theta = cardPosition(i); 465 float swayAngle = getSwayAngleForVelocity(velocity, enableSway); 466 rsMatrixRotate(matrix, degrees(theta), 0, 1, 0); 467 rsMatrixTranslate(matrix, radius, 0, 0); 468 float rotation = cardRotation + swayAngle; 469 if (!cardsFaceTangent) { 470 rotation -= theta; 471 } 472 rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0); 473 if (i == currentSelection) { 474 float3 scale = getAnimatedScaleForSelected(); 475 rsMatrixScale(matrix, scale.x, scale.y, scale.z); 476 } 477 // TODO: apply custom matrix for cards[i].geometry 478} 479 480/* 481 * Draws cards around the Carousel. 482 * Returns true if we're still animating any property of the cards (e.g. fades). 483 */ 484static bool drawCards(int64_t currentTime) 485{ 486 const float wedgeAngle = 2.0f * M_PI / slotCount; 487 const float endAngle = startAngle + visibleSlotCount * wedgeAngle; 488 bool stillAnimating = false; 489 for (int i = cardCount-1; i >= 0; i--) { 490 if (cards[i].visible) { 491 // If this card was recently loaded, this will be < 1.0f until the animation completes 492 float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime); 493 if (animatedAlpha < 1.0f) { 494 stillAnimating = true; 495 } 496 497 // Compute fade out for cards in the distance 498 float positionAlpha; 499 if (rezInCardCount > 0.0f) { 500 positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle; 501 positionAlpha = min(1.0f, positionAlpha / rezInCardCount); 502 } else { 503 positionAlpha = 1.0f; 504 } 505 506 // Set alpha for blending between the textures 507 shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha); 508 rsAllocationMarkDirty(rsGetAllocation(shaderConstants)); 509 510 // Bind the appropriate shader network. If there's no alpha blend, then 511 // switch to single shader for better performance. 512 const bool loaded = cards[i].textureState == STATE_LOADED; 513 if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) { 514 rsgBindProgramFragment(singleTextureFragmentProgram); 515 rsgBindTexture(singleTextureFragmentProgram, 0, 516 (loaded && shaderConstants->fadeAmount == 1.0f) ? 517 cards[i].texture : loadingTexture); 518 } else { 519 rsgBindProgramFragment(multiTextureFragmentProgram); 520 rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture); 521 rsgBindTexture(multiTextureFragmentProgram, 1, loaded ? 522 cards[i].texture : loadingTexture); 523 } 524 525 // Draw geometry 526 rs_matrix4x4 matrix = modelviewMatrix; 527 getMatrixForCard(&matrix, i, true); 528 rsgProgramVertexLoadModelMatrix(&matrix); 529 if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) { 530 rsgDrawMesh(cards[i].geometry); 531 } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) { 532 rsgDrawMesh(loadingGeometry); 533 } else if (defaultGeometry.p != 0) { 534 rsgDrawMesh(defaultGeometry); 535 } else { 536 // Draw place-holder geometry 537 rsgDrawQuad( 538 cardVertices[0].x, cardVertices[0].y, cardVertices[0].z, 539 cardVertices[1].x, cardVertices[1].y, cardVertices[1].z, 540 cardVertices[2].x, cardVertices[2].y, cardVertices[2].z, 541 cardVertices[3].x, cardVertices[3].y, cardVertices[3].z); 542 } 543 } 544 } 545 return stillAnimating; 546} 547 548/* 549 * Draws a screen-aligned card with the exact dimensions from the detail texture. 550 * This is used to display information about the object being displayed above the geomertry. 551 * Returns true if we're still animating any property of the cards (e.g. fades). 552 */ 553static bool drawDetails(int64_t currentTime) 554{ 555 const float width = rsgGetWidth(); 556 const float height = rsgGetHeight(); 557 558 bool stillAnimating = false; 559 560 // We'll be drawing in screen space, sampled on pixel centers 561 rs_matrix4x4 projection, model; 562 rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f); 563 rsgProgramVertexLoadProjectionMatrix(&projection); 564 rsMatrixLoadIdentity(&model); 565 rsgProgramVertexLoadModelMatrix(&model); 566 updateCamera = true; // we messed with the projection matrix. Reload on next pass... 567 568 const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry 569 570 // This can be done once... 571 rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture); 572 573 const float wedgeAngle = 2.0f * M_PI / slotCount; 574 // Angle where details start fading from 1.0f 575 const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle; 576 // Angle where detail alpha is 0.0f 577 const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle; 578 579 for (int i = cardCount-1; i >= 0; --i) { 580 if (cards[i].visible) { 581 if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) { 582 const float lineWidth = rsAllocationGetDimX(detailLineTexture); 583 584 // Compute position in screen space of upper left corner of card 585 rsMatrixLoad(&model, &modelviewMatrix); 586 getMatrixForCard(&model, i, false); 587 rs_matrix4x4 matrix; 588 rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model); 589 590 float4 screenCoord = rsMatrixMultiply(&matrix, 591 cardVertices[drawDetailBelowCard ? 0 : 3]); 592 if (screenCoord.w == 0.0f) { 593 // this shouldn't happen 594 rsDebug("Bad transform: ", screenCoord); 595 continue; 596 } 597 598 // Compute alpha for gradually fading in details. Applied to both line and 599 // detail texture. TODO: use a separate background texture for line. 600 float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, currentTime); 601 if (animatedAlpha < 1.0f) { 602 stillAnimating = true; 603 } 604 605 // Compute alpha based on position. We fade cards quickly so they cannot overlap 606 float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i)) 607 / (endDetailFadeAngle - startDetailFadeAngle); 608 positionAlpha = max(0.0f, positionAlpha); 609 positionAlpha = min(1.0f, positionAlpha); 610 611 const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha); 612 613 if (blendedAlpha == 0.0f) continue; // nothing to draw 614 if (blendedAlpha == 1.0f) { 615 rsgBindProgramFragment(singleTextureFragmentProgram); 616 } else { 617 rsgBindProgramFragment(multiTextureFragmentProgram); 618 } 619 620 // Set alpha for blending between the textures 621 shaderConstants->fadeAmount = blendedAlpha; 622 rsAllocationMarkDirty(rsGetAllocation(shaderConstants)); 623 624 // Convert projection from normalized coordinates to pixel coordinates. 625 // This is probably cheaper than pre-multiplying the above with another matrix. 626 screenCoord *= 1.0f / screenCoord.w; 627 screenCoord.x += 1.0f; 628 screenCoord.y += 1.0f; 629 screenCoord.z += 1.0f; 630 screenCoord.x = round(screenCoord.x * 0.5f * width); 631 screenCoord.y = round(screenCoord.y * 0.5f * height); 632 screenCoord.z = - 0.5f * screenCoord.z; 633 if (debugDetails) { 634 RS_DEBUG(screenCoord); 635 } 636 637 // Draw line from upper left card corner to the top of the screen 638 if (drawRuler) { 639 const float halfWidth = lineWidth * 0.5f; 640 const float rulerTop = drawDetailBelowCard ? screenCoord.y : height; 641 const float rulerBottom = drawDetailBelowCard ? 0 : screenCoord.y; 642 const float x0 = cards[i].detailLineOffset.x + screenCoord.x - halfWidth; 643 const float x1 = cards[i].detailLineOffset.x + screenCoord.x + halfWidth; 644 const float y0 = rulerBottom + yPadding; 645 const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y; 646 647 if (blendedAlpha == 1.0f) { 648 rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture); 649 } else { 650 rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture); 651 } 652 rsgDrawQuad(x0, y0, screenCoord.z, x1, y0, screenCoord.z, 653 x1, y1, screenCoord.z, x0, y1, screenCoord.z); 654 } 655 656 // Draw the detail texture next to it using the offsets provided. 657 const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture); 658 const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture); 659 const float offx = cards[i].detailTextureOffset.x; 660 const float offy = -cards[i].detailTextureOffset.y; 661 const float textureTop = drawDetailBelowCard ? screenCoord.y : height; 662 const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx; 663 const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth; 664 const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y; 665 const float y1 = textureTop + offy - cards[i].detailLineOffset.y; 666 667 if (blendedAlpha == 1.0f) { 668 rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture); 669 } else { 670 rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture); 671 } 672 rsgDrawQuad(x0, y0, screenCoord.z, x1, y0, screenCoord.z, 673 x1, y1, screenCoord.z, x0, y1, screenCoord.z); 674 } 675 } 676 } 677 return stillAnimating; 678} 679 680static void drawBackground() 681{ 682 static bool toggle; 683 if (backgroundTexture.p != 0) { 684 // Unfortunately, we also need to clear the background because some textures may be 685 // drawn with alpha. This takes about 1ms-2ms in my tests. May be worth optimizing at 686 // some point. 687 rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, backgroundColor.w); 688 689 rsgClearDepth(1.0f); 690 rs_matrix4x4 projection, model; 691 rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f); 692 rsgProgramVertexLoadProjectionMatrix(&projection); 693 rsMatrixLoadIdentity(&model); 694 rsgProgramVertexLoadModelMatrix(&model); 695 rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture); 696 float z = -0.9999f; 697 rsgDrawQuad( 698 cardVertices[0].x, cardVertices[0].y, z, 699 cardVertices[1].x, cardVertices[1].y, z, 700 cardVertices[2].x, cardVertices[2].y, z, 701 cardVertices[3].x, cardVertices[3].y, z); 702 updateCamera = true; // we mucked with the matrix. 703 } else { 704 rsgClearDepth(1.0f); 705 if (debugRendering) { // for debugging - flash the screen so we know we're still rendering 706 rsgClearColor(toggle ? backgroundColor.x : 1.0f, 707 toggle ? backgroundColor.y : 0.0f, 708 toggle ? backgroundColor.z : 0.0f, 709 backgroundColor.w); 710 toggle = !toggle; 711 } else { 712 rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 713 backgroundColor.w); 714 } 715 } 716} 717 718static void updateCameraMatrix(float width, float height) 719{ 720 float aspect = width / height; 721 if (aspect != camera.aspect || updateCamera) { 722 camera.aspect = aspect; 723 loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far); 724 rsgProgramVertexLoadProjectionMatrix(&projectionMatrix); 725 726 loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up); 727 rsgProgramVertexLoadModelMatrix(&modelviewMatrix); 728 updateCamera = false; 729 } 730} 731 732//////////////////////////////////////////////////////////////////////////////////////////////////// 733// Behavior/Physics 734//////////////////////////////////////////////////////////////////////////////////////////////////// 735static bool isDragging; 736static int64_t lastTime = 0L; // keep track of how much time has passed between frames 737static float2 lastPosition; 738static bool animating = false; 739static float velocityThreshold = 0.1f * M_PI / 180.0f; 740static float velocityTracker; 741static int velocityTrackerCount; 742static float mass = 5.0f; // kg 743 744static const float G = 9.80f; // gravity constant, in m/s 745static const float springConstant = 0.0f; 746 747static float dragFunction(float x, float y) 748{ 749 return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI; 750} 751 752static float deltaTimeInSeconds(int64_t current) 753{ 754 return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f; 755} 756 757int doSelection(float x, float y) 758{ 759 Ray ray; 760 if (makeRayForPixelAt(&ray, &camera, x, y)) { 761 float bestTime = FLT_MAX; 762 return intersectGeometry(&ray, &bestTime); 763 } 764 return -1; 765} 766 767void doStart(float x, float y) 768{ 769 lastPosition.x = x; 770 lastPosition.y = y; 771 velocity = 0.0f; 772 if (animating) { 773 rsSendToClient(CMD_ANIMATION_FINISHED); 774 animating = false; 775 currentSelection = -1; 776 } else { 777 currentSelection = doSelection(x, y); 778 } 779 velocityTracker = 0.0f; 780 velocityTrackerCount = 0; 781 touchTime = rsUptimeMillis(); 782 touchBias = bias; 783} 784 785 786void doStop(float x, float y) 787{ 788 int64_t currentTime = rsUptimeMillis(); 789 updateAllocationVars(cards); 790 if (currentSelection != -1 && (currentTime - touchTime) < ANIMATION_SCALE_TIME) { 791 // rsDebug("HIT!", currentSelection); 792 int data[1]; 793 data[0] = currentSelection; 794 rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data)); 795 } else { 796 velocity = velocityTrackerCount > 0 ? 797 (velocityTracker / velocityTrackerCount) : 0.0f; // avg velocity 798 if (fabs(velocity) > velocityThreshold) { 799 animating = true; 800 rsSendToClient(CMD_ANIMATION_STARTED); 801 } 802 } 803 currentSelection = -1; 804 lastTime = rsUptimeMillis(); 805} 806 807void doLongPress() 808{ 809 int64_t currentTime = rsUptimeMillis(); 810 updateAllocationVars(cards); 811 if (currentSelection != -1) { 812 // rsDebug("HIT!", currentSelection); 813 int data[1]; 814 data[0] = currentSelection; 815 rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data)); 816 } 817 currentSelection = -1; 818 lastTime = rsUptimeMillis(); 819} 820 821void doMotion(float x, float y) 822{ 823 int64_t currentTime = rsUptimeMillis(); 824 float deltaOmega = dragFunction(x, y); 825 bias += deltaOmega; 826 lastPosition.x = x; 827 lastPosition.y = y; 828 float dt = deltaTimeInSeconds(currentTime); 829 if (dt > 0.0f) { 830 float v = deltaOmega / dt; 831 //if ((velocityTracker > 0.0f) == (v > 0.0f)) { 832 velocityTracker += v; 833 velocityTrackerCount++; 834 //} else { 835 // velocityTracker = v; 836 // velocityTrackerCount = 1; 837 //} 838 } 839 velocity = velocityTrackerCount > 0 ? 840 (velocityTracker / velocityTrackerCount) : 0.0f; // avg velocity 841 842 // Drop current selection if user drags position +- a partial slot 843 if (currentSelection != -1) { 844 const float slotMargin = 0.5f * (2.0f * M_PI / slotCount); 845 if (fabs(touchBias - bias) > slotMargin) { 846 currentSelection = -1; 847 } 848 } 849 lastTime = currentTime; 850} 851 852//////////////////////////////////////////////////////////////////////////////////////////////////// 853// Hit detection using ray casting. 854//////////////////////////////////////////////////////////////////////////////////////////////////// 855 856static bool 857rayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout) 858{ 859 static const float tmin = 0.0f; 860 861 float3 e1 = p1 - p0; 862 float3 e2 = p2 - p0; 863 float3 s1 = cross(ray->direction, e2); 864 865 float div = dot(s1, e1); 866 if (div == 0.0f) return false; // ray is parallel to plane. 867 868 float3 d = ray->position - p0; 869 float invDiv = 1.0f / div; 870 871 float u = dot(d, s1) * invDiv; 872 if (u < 0.0f || u > 1.0f) return false; 873 874 float3 s2 = cross(d, e1); 875 float v = dot(ray->direction, s2) * invDiv; 876 if ( v < 0.0f || (u+v) > 1.0f) return false; 877 878 float t = dot(e2, s2) * invDiv; 879 if (t < tmin || t > *tout) 880 return false; 881 *tout = t; 882 return true; 883} 884 885static bool 886rayPlaneIntersect(Ray* ray, float3 point, float3 normal) 887{ 888 return false; // TODO 889} 890 891static bool 892rayCylinderIntersect(Ray* ray, float3 center, float radius) 893{ 894 return false; // TODO 895} 896 897// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates. 898// Note that the Y coordinate is opposite of GL rendering coordinates. 899static bool __attribute__((overloadable)) 900makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y) 901{ 902 if (debugCamera) { 903 rsDebug("------ makeRay() -------", 0); 904 rsDebug("Camera.from:", cam->from); 905 rsDebug("Camera.at:", cam->at); 906 rsDebug("Camera.dir:", normalize(cam->at - cam->from)); 907 } 908 909 // Vector math. This has the potential to be much faster. 910 // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math. 911 const float u = x / rsgGetWidth(); 912 const float v = 1.0f - (y / rsgGetHeight()); 913 const float aspect = (float) rsgGetWidth() / rsgGetHeight(); 914 const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f)); 915 float3 dir = normalize(cam->at - cam->from); 916 float3 du = tanfov2 * normalize(cross(dir, cam->up)); 917 float3 dv = tanfov2 * normalize(cross(du, dir)); 918 du *= aspect; 919 float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv); 920 const float3 rayPoint = cam->from; 921 const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv); 922 if (debugCamera) { 923 rsDebug("Ray direction (vector math) = ", rayDir); 924 } 925 926 ray->position = rayPoint; 927 ray->direction = rayDir; 928 return true; 929} 930 931// Creates a ray for an Android pixel coordinate given a model view and projection matrix. 932// Note that the Y coordinate is opposite of GL rendering coordinates. 933static bool __attribute__((overloadable)) 934makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y) 935{ 936 rs_matrix4x4 pm = *model; 937 rsMatrixLoadMultiply(&pm, proj, model); 938 if (!rsMatrixInverse(&pm)) { 939 rsDebug("ERROR: SINGULAR PM MATRIX", 0); 940 return false; 941 } 942 const float width = rsgGetWidth(); 943 const float height = rsgGetHeight(); 944 const float winx = 2.0f * x / width - 1.0f; 945 const float winy = 2.0f * y / height - 1.0f; 946 947 float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f }; 948 float4 at = { winx, winy, 1.0f, 1.0f }; 949 950 eye = rsMatrixMultiply(&pm, eye); 951 eye *= 1.0f / eye.w; 952 953 at = rsMatrixMultiply(&pm, at); 954 at *= 1.0f / at.w; 955 956 const float3 rayPoint = { eye.x, eye.y, eye.z }; 957 const float3 atPoint = { at.x, at.y, at.z }; 958 const float3 rayDir = normalize(atPoint - rayPoint); 959 if (debugCamera) { 960 rsDebug("winx: ", winx); 961 rsDebug("winy: ", winy); 962 rsDebug("Ray position (transformed) = ", eye); 963 rsDebug("Ray direction (transformed) = ", rayDir); 964 } 965 ray->position = rayPoint; 966 ray->direction = rayDir; 967 return true; 968} 969 970static int intersectGeometry(Ray* ray, float *bestTime) 971{ 972 int hit = -1; 973 for (int id = 0; id < cardCount; id++) { 974 if (cards[id].visible) { 975 rs_matrix4x4 matrix; 976 float3 p[4]; 977 978 // Transform card vertices to world space 979 rsMatrixLoadIdentity(&matrix); 980 getMatrixForCard(&matrix, id, true); 981 for (int vertex = 0; vertex < 4; vertex++) { 982 float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]); 983 if (tmp.w != 0.0f) { 984 p[vertex].x = tmp.x; 985 p[vertex].y = tmp.y; 986 p[vertex].z = tmp.z; 987 p[vertex] *= 1.0f / tmp.w; 988 } else { 989 rsDebug("Bad w coord: ", tmp); 990 } 991 } 992 993 // Intersect card geometry 994 if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime) 995 || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) { 996 hit = id; 997 } 998 } 999 } 1000 return hit; 1001} 1002 1003// This method computes the position of all the cards by updating bias based on a 1004// simple physics model. 1005// If the cards are still in motion, returns true. 1006static bool updateNextPosition(int64_t currentTime) 1007{ 1008 if (animating) { 1009 float dt = deltaTimeInSeconds(currentTime); 1010 if (dt <= 0.0f) 1011 return animating; 1012 const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame 1013 const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1; 1014 dt /= N; 1015 for (int i = 0; i < N; i++) { 1016 // Force friction - always opposes motion 1017 const float Ff = -frictionCoeff * velocity; 1018 1019 // Restoring force to match cards with slots 1020 const float theta = startAngle + bias; 1021 const float dtheta = 2.0f * M_PI / slotCount; 1022 const float position = theta / dtheta; 1023 const float fraction = position - floor(position); // fractional position between slots 1024 float x; 1025 if (fraction > 0.5f) { 1026 x = - (1.0f - fraction); 1027 } else { 1028 x = fraction; 1029 } 1030 const float Fr = - springConstant * x; 1031 1032 // compute velocity 1033 const float momentum = mass * velocity + (Ff + Fr)*dt; 1034 velocity = momentum / mass; 1035 bias += velocity * dt; 1036 } 1037 1038 animating = fabs(velocity) > velocityThreshold; 1039 if (!animating) { 1040 rsSendToClient(CMD_ANIMATION_FINISHED); 1041 } 1042 } 1043 lastTime = currentTime; 1044 1045 const float firstBias = wedgeAngle(0.0f); 1046 const float lastBias = -max(0.0f, wedgeAngle(cardCount - visibleDetailCount)); 1047 1048 if (bias > firstBias) { 1049 bias = firstBias; 1050 } else if (bias < lastBias) { 1051 bias = lastBias; 1052 } 1053 1054 return animating; 1055} 1056 1057// Cull cards based on visibility and visibleSlotCount. 1058// If visibleSlotCount is > 0, then only show those slots and cull the rest. 1059// Otherwise, it should cull based on bounds of geometry. 1060static int cullCards() 1061{ 1062 const float thetaFirst = slotPosition(-1); // -1 keeps the card in front around a bit longer 1063 const float thetaSelected = slotPosition(0); 1064 const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f; 1065 const float thetaSelectedLow = thetaSelected - thetaHalfAngle; 1066 const float thetaSelectedHigh = thetaSelected + thetaHalfAngle; 1067 const float thetaLast = slotPosition(visibleSlotCount); 1068 1069 int count = 0; 1070 int firstVisible = -1; 1071 for (int i = 0; i < cardCount; i++) { 1072 if (visibleSlotCount > 0) { 1073 // If visibleSlotCount is specified, then only show up to visibleSlotCount cards. 1074 float p = cardPosition(i); 1075 if (p >= thetaFirst && p < thetaLast) { 1076 if (firstVisible == -1 && p >= thetaSelectedLow && p < thetaSelectedHigh) { 1077 firstVisible = i; 1078 } 1079 cards[i].visible = true; 1080 count++; 1081 } else { 1082 cards[i].visible = false; 1083 } 1084 } else { 1085 // Cull the rest of the cards using bounding box of geometry. 1086 // TODO 1087 cards[i].visible = true; 1088 count++; 1089 } 1090 } 1091 currentFirstCard = firstVisible; 1092 return count; 1093} 1094 1095// Request texture/geometry for items that have come into view 1096// or doesn't have a texture yet. 1097static void updateCardResources(int64_t currentTime) 1098{ 1099 for (int i = cardCount-1; i >= 0; --i) { 1100 int data[1]; 1101 if (cards[i].visible) { 1102 if (debugTextureLoading) rsDebug("*** Texture stamp: ", cards[i].textureTimeStamp); 1103 1104 // request texture from client if not loaded 1105 if (cards[i].textureState == STATE_INVALID) { 1106 data[0] = i; 1107 bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data)); 1108 if (enqueued) { 1109 cards[i].textureState = STATE_LOADING; 1110 } else { 1111 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0); 1112 } 1113 } 1114 // request detail texture from client if not loaded 1115 if (cards[i].detailTextureState == STATE_INVALID) { 1116 data[0] = i; 1117 bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data)); 1118 if (enqueued) { 1119 cards[i].detailTextureState = STATE_LOADING; 1120 } else { 1121 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0); 1122 } 1123 } 1124 // request geometry from client if not loaded 1125 if (cards[i].geometryState == STATE_INVALID) { 1126 data[0] = i; 1127 bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data)); 1128 if (enqueued) { 1129 cards[i].geometryState = STATE_LOADING; 1130 } else { 1131 if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0); 1132 } 1133 } 1134 } else { 1135 // ask the host to remove the texture 1136 if (cards[i].textureState != STATE_INVALID) { 1137 data[0] = i; 1138 bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data)); 1139 if (enqueued) { 1140 cards[i].textureState = STATE_INVALID; 1141 cards[i].textureTimeStamp = currentTime; 1142 } else { 1143 if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0); 1144 } 1145 } 1146 // ask the host to remove the detail texture 1147 if (cards[i].detailTextureState != STATE_INVALID) { 1148 data[0] = i; 1149 bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data)); 1150 if (enqueued) { 1151 cards[i].detailTextureState = STATE_INVALID; 1152 cards[i].detailTextureTimeStamp = currentTime; 1153 } else { 1154 if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0); 1155 } 1156 } 1157 // ask the host to remove the geometry 1158 if (cards[i].geometryState != STATE_INVALID) { 1159 data[0] = i; 1160 bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data)); 1161 if (enqueued) { 1162 cards[i].geometryState = STATE_INVALID; 1163 } else { 1164 if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0); 1165 } 1166 } 1167 } 1168 } 1169} 1170 1171// Places dots on geometry to visually inspect that objects can be seen by rays. 1172// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered 1173// card. 1174static void renderWithRays() 1175{ 1176 const float w = rsgGetWidth(); 1177 const float h = rsgGetHeight(); 1178 const int skip = 8; 1179 color(1.0f, 0.0f, 0.0f, 1.0f); 1180 for (int j = 0; j < (int) h; j+=skip) { 1181 float posY = (float) j; 1182 for (int i = 0; i < (int) w; i+=skip) { 1183 float posX = (float) i; 1184 Ray ray; 1185 if (makeRayForPixelAt(&ray, &camera, posX, posY)) { 1186 float bestTime = FLT_MAX; 1187 if (intersectGeometry(&ray, &bestTime) != -1) { 1188 rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f); 1189 } 1190 } 1191 } 1192 } 1193} 1194 1195int root() { 1196 int64_t currentTime = rsUptimeMillis(); 1197 1198 rsgBindProgramVertex(vertexProgram); 1199 rsgBindProgramStore(programStore); 1200 rsgBindProgramRaster(rasterProgram); 1201 rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp); 1202 rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp); 1203 rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp); 1204 1205 updateAllocationVars(cards); 1206 1207 if (!initialized) { 1208 if (debugTextureLoading){ 1209 rsDebug("*** initialized was false, updating all cards (cards = ", cards); 1210 } 1211 const float2 zero = {0.0f, 0.0f}; 1212 for (int i = 0; i < cardCount; i++) { 1213 initCard(cards + i); 1214 } 1215 initialized = true; 1216 } 1217 1218 rsgBindProgramFragment(singleTextureFragmentProgram); 1219 drawBackground(); 1220 1221 updateCameraMatrix(rsgGetWidth(), rsgGetHeight()); 1222 1223 const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME; 1224 bool stillAnimating = updateNextPosition(currentTime) || !timeExpired; 1225 1226 cullCards(); 1227 1228 updateCardResources(currentTime); 1229 1230 stillAnimating |= drawCards(currentTime); 1231 stillAnimating |= drawDetails(currentTime); 1232 1233 if (debugPicking) { 1234 renderWithRays(); 1235 } 1236 1237 //rsSendToClient(CMD_PING); 1238 1239 return stillAnimating ? 1 : 0; 1240} 1241