carousel.rs revision 8a357ebe4ae3063dbb3d8b3bdf6f665b05dd8e6f
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 initCard 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 float2 detailTexturePosition[2]; // screen coordinates of detail texture, computed at draw time 30 rs_mesh geometry; 31 rs_matrix4x4 matrix; // custom transform for this card/geometry 32 int textureState; // whether or not the primary card texture is loaded. 33 int detailTextureState; // whether or not the detail for the card is loaded. 34 int geometryState; // whether or not geometry is loaded 35 int cardVisible; // not bool because of packing bug? 36 int detailVisible; // not bool because of packing bug? 37 int64_t textureTimeStamp; // time when this texture was last updated, in ms 38 int64_t detailTextureTimeStamp; // time when this texture was last updated, in ms 39 int64_t geometryTimeStamp; // time when the card itself was last updated, in ms 40} Card_t; 41 42typedef struct Ray_s { 43 float3 position; 44 float3 direction; 45} Ray; 46 47typedef struct Plane_s { 48 float3 point; 49 float3 normal; 50 float constant; 51} Plane; 52 53typedef struct Cylinder_s { 54 float3 center; // center of a y-axis-aligned infinite cylinder 55 float radius; 56} Cylinder; 57 58typedef struct PerspectiveCamera_s { 59 float3 from; 60 float3 at; 61 float3 up; 62 float fov; 63 float aspect; 64 float near; 65 float far; 66} PerspectiveCamera; 67 68typedef struct ProgramStore_s { 69 rs_program_store programStore; 70} ProgramStore_t; 71 72typedef struct FragmentShaderConstants_s { 73 float fadeAmount; 74 float overallAlpha; 75} FragmentShaderConstants; 76 77// Request states. Used for loading 3D object properties from the Java client. 78// Typical properties: texture, geometry and matrices. 79enum { 80 STATE_INVALID = 0, // item hasn't been loaded 81 STATE_LOADING, // we've requested an item but are waiting for it to load 82 STATE_STALE, // we have an old item, but should request an update 83 STATE_UPDATING, // we've requested an update, and will display the old one in the meantime 84 STATE_LOADED // item was delivered 85}; 86 87// Detail texture alignments ** THIS LIST MUST MATCH THOSE IN CarouselView.java *** 88enum { 89 /** Detail is centered vertically with respect to the card **/ 90 CENTER_VERTICAL = 1, 91 /** Detail is aligned with the top edge of the carousel view **/ 92 VIEW_TOP = 1 << 1, 93 /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/ 94 VIEW_BOTTOM = 1 << 2, 95 /** Detail is positioned above the card (not yet implemented) **/ 96 ABOVE = 1 << 3, 97 /** Detail is positioned below the card **/ 98 BELOW = 1 << 4, 99 /** Mask that selects those bits that control vertical alignment **/ 100 VERTICAL_ALIGNMENT_MASK = 0xff, 101 102 /** 103 * Detail is centered horizontally with respect to either the top or bottom 104 * extent of the card, depending on whether the detail is above or below the card. 105 */ 106 CENTER_HORIZONTAL = 1 << 8, 107 /** 108 * Detail is aligned with the left edge of either the top or the bottom of 109 * the card, depending on whether the detail is above or below the card. 110 */ 111 LEFT = 1 << 9, 112 /** 113 * Detail is aligned with the right edge of either the top or the bottom of 114 * the card, depending on whether the detail is above or below the card. 115 * (not yet implemented) 116 */ 117 RIGHT = 1 << 10, 118 /** Mask that selects those bits that control horizontal alignment **/ 119 HORIZONTAL_ALIGNMENT_MASK = 0xff00, 120}; 121 122// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. *** 123static const int CMD_CARD_SELECTED = 100; 124static const int CMD_DETAIL_SELECTED = 105; 125static const int CMD_CARD_LONGPRESS = 110; 126static const int CMD_REQUEST_TEXTURE = 200; 127static const int CMD_INVALIDATE_TEXTURE = 210; 128static const int CMD_REQUEST_GEOMETRY = 300; 129static const int CMD_INVALIDATE_GEOMETRY = 310; 130static const int CMD_ANIMATION_STARTED = 400; 131static const int CMD_ANIMATION_FINISHED = 500; 132static const int CMD_REQUEST_DETAIL_TEXTURE = 600; 133static const int CMD_INVALIDATE_DETAIL_TEXTURE = 610; 134static const int CMD_PING = 1000; 135 136// Drag model *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. *** 137static const int DRAG_MODEL_SCREEN_DELTA = 0; // Drag relative to x coordinate of motion vector 138static const int DRAG_MODEL_PLANE = 1; // Drag relative to projected point on plane of carousel 139static const int DRAG_MODEL_CYLINDER_INSIDE = 2; // Drag relative to point on inside of cylinder 140static const int DRAG_MODEL_CYLINDER_OUTSIDE = 3; // Drag relative to point on outside of cylinder 141 142// Constants 143static const int ANIMATION_DELAY_TIME = 100; // hold off scale animation until this time 144static const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms 145static const float3 SELECTED_SCALE_FACTOR = { 0.0f, 0.0f, 0.0f }; // increase by this % 146static const float OVERSCROLL_SLOTS = 1.0f; // amount of allowed overscroll (in slots) 147 148// Debug flags 149const bool debugCamera = false; // dumps ray/camera coordinate stuff 150const bool debugSelection = false; // logs selection events 151const bool debugTextureLoading = false; // for debugging texture load/unload 152const bool debugGeometryLoading = false; // for debugging geometry load/unload 153const bool debugDetails = false; // for debugging detail texture geometry 154const bool debugRendering = false; // flashes display when the frame changes 155const bool debugRays = false; // shows visual depiction of hit tests, See renderWithRays(). 156 157// Exported variables. These will be reflected to Java set_* variables. 158Card_t *cards; // array of cards to draw 159float startAngle; // position of initial card, in radians 160int slotCount; // number of positions where a card can be 161int cardCount; // number of cards in stack 162int programStoresCardCount; // number of program fragment stores 163int visibleSlotCount; // number of visible slots (for culling) 164int visibleDetailCount; // number of visible detail textures to show 165int prefetchCardCount; // how many cards to keep in memory 166int detailTextureAlignment; // How to align detail texture with respect to card 167bool drawRuler; // whether to draw a ruler from the card to the detail texture 168float radius; // carousel radius. Cards will be centered on a circle with this radius 169float cardRotation; // rotation of card in XY plane relative to Z=1 170bool cardsFaceTangent; // whether cards are rotated to face along a tangent to the circle 171float swaySensitivity; // how much to rotate cards in relation to the rotation velocity 172float frictionCoeff; // how much to slow down the carousel over time 173float dragFactor; // a scale factor for how sensitive the carousel is to user dragging 174int fadeInDuration; // amount of time (in ms) for smoothly switching out textures 175int cardCreationFadeDuration; // amount of time (in ms) to fade while initially showing a card 176float rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in 177float detailFadeRate; // rate at which details fade as they move into the distance 178float4 backgroundColor; 179int rowCount; // number of rows of cards in a given slot, default 1 180float rowSpacing; // spacing between rows of cards 181bool firstCardTop; // set true for first card on top row when multiple rows used 182 183int dragModel = DRAG_MODEL_SCREEN_DELTA; 184int fillDirection; // the order in which to lay out cards: +1 for CCW (default), -1 for CW 185ProgramStore_t *programStoresCard; 186rs_program_store programStoreBackground; 187rs_program_store programStoreDetail; 188rs_program_fragment singleTextureFragmentProgram; 189rs_program_fragment singleTextureBlendingFragmentProgram; 190rs_program_fragment multiTextureFragmentProgram; 191rs_program_fragment multiTextureBlendingFragmentProgram; 192rs_program_vertex vertexProgram; 193rs_program_raster rasterProgram; 194rs_allocation defaultTexture; // shown when no other texture is assigned 195rs_allocation loadingTexture; // progress texture (shown when app is fetching the texture) 196rs_allocation backgroundTexture; // drawn behind everything, if set 197rs_allocation detailLineTexture; // used to draw detail line (as a quad, of course) 198rs_allocation detailLoadingTexture; // used when detail texture is loading 199rs_mesh defaultGeometry; // shown when no geometry is loaded 200rs_mesh loadingGeometry; // shown when geometry is loading 201rs_matrix4x4 defaultCardMatrix; 202rs_matrix4x4 projectionMatrix; 203rs_matrix4x4 modelviewMatrix; 204FragmentShaderConstants* shaderConstants; 205rs_sampler linearClamp; 206 207// Local variables 208static float bias; // rotation bias, in radians. Used for animation and dragging. 209static bool updateCamera; // force a recompute of projection and lookat matrices 210static const float FLT_MAX = 1.0e37; 211static int animatedSelection = -1; 212static int currentFirstCard = -1; 213static int64_t touchTime = -1; // time of first touch (see doStart()) 214static float touchBias = 0.0f; // bias on first touch 215static float2 touchPosition; // position of first touch, as defined by last call to doStart(x,y) 216static float velocity = 0.0f; // angular velocity in radians/s 217static bool overscroll = false; // whether we're in the overscroll animation 218static bool isDragging = false; // true while the user is dragging the carousel 219static float selectionRadius = 50.0f; // movement greater than this will result in no selection 220static bool enableSelection = false; // enabled until the user drags outside of selectionRadius 221 222// Default plane of the carousel. Used for angular motion estimation in view. 223static Plane carouselPlane = { 224 { 0.0f, 0.0f, 0.0f }, // point 225 { 0.0f, 1.0f, 0.0f }, // normal 226 0.0f // plane constant (= -dot(P, N)) 227}; 228 229static Cylinder carouselCylinder = { 230 {0.0f, 0.0f, 0.0f }, // center 231 1.0f // radius - update with carousel radius. 232}; 233 234// Because allocations can't have 0 dimensions, we have to track whether or not 235// cards and program stores are valid separately. 236// TODO: Remove this dependency once allocations can have a zero dimension. 237static bool cardAllocationValid = false; 238static bool programStoresAllocationValid = false; 239 240// Default geometry when card.geometry is not set. 241static const float3 cardVertices[4] = { 242 { -1.0, -1.0, 0.0 }, 243 { 1.0, -1.0, 0.0 }, 244 { 1.0, 1.0, 0.0 }, 245 {-1.0, 1.0, 0.0 } 246}; 247 248// Default camera 249static PerspectiveCamera camera = { 250 {2,2,2}, // from 251 {0,0,0}, // at 252 {0,1,0}, // up 253 25.0f, // field of view 254 1.0f, // aspect 255 0.1f, // near 256 100.0f // far 257}; 258 259// Forward references 260static int intersectGeometry(Ray* ray, float *bestTime); 261static int intersectDetailTexture(float x, float y, float2 *tapCoordinates); 262static bool __attribute__((overloadable)) 263 makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y); 264static bool __attribute__((overloadable)) 265 makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y); 266static float deltaTimeInSeconds(int64_t current); 267static bool rayPlaneIntersect(Ray* ray, Plane* plane, float* tout); 268static bool rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout); 269 270void init() { 271 // initializers currently have a problem when the variables are exported, so initialize 272 // globals here. 273 if (debugTextureLoading) rsDebug("Renderscript: init()", 0); 274 startAngle = 0.0f; 275 slotCount = 10; 276 visibleSlotCount = 1; 277 visibleDetailCount = 3; 278 bias = 0.0f; 279 radius = carouselCylinder.radius = 1.0f; 280 cardRotation = 0.0f; 281 cardsFaceTangent = false; 282 updateCamera = true; 283 backgroundColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f }; 284 cardAllocationValid = false; 285 programStoresAllocationValid = false; 286 cardCount = 0; 287 rowCount = 1; 288 rowSpacing = 0.0f; 289 firstCardTop = false; 290 fadeInDuration = 250; 291 rezInCardCount = 0.0f; // alpha will ramp to 1.0f over this many cards (0.0f means disabled) 292 detailFadeRate = 0.5f; // fade details over this many slot positions. 293 rsMatrixLoadIdentity(&defaultCardMatrix); 294} 295 296static void updateAllocationVars() 297{ 298 // Cards 299 rs_allocation cardAlloc; 300 rsSetObject(&cardAlloc, rsGetAllocation(cards)); 301 cardCount = (cardAllocationValid && rsIsObject(cardAlloc)) ? rsAllocationGetDimX(cardAlloc) : 0; 302 303 // Program stores 304 rs_allocation psAlloc; 305 rsSetObject(&psAlloc, rsGetAllocation(programStoresCard)); 306 programStoresCardCount = (programStoresAllocationValid && rsIsObject(psAlloc) ? 307 rsAllocationGetDimX(psAlloc) : 0); 308} 309 310void setRadius(float rad) 311{ 312 radius = carouselCylinder.radius = rad; 313} 314 315static void initCard(Card_t* card) 316{ 317 // Object refs are always initilized cleared. 318 static const float2 zero = {0.0f, 0.0f}; 319 card->detailTextureOffset = zero; 320 card->detailLineOffset = zero; 321 rsMatrixLoad(&card->matrix, &defaultCardMatrix); 322 card->textureState = STATE_INVALID; 323 card->detailTextureState = STATE_INVALID; 324 card->geometryState = STATE_INVALID; 325 card->cardVisible = false; 326 card->detailVisible = false; 327 card->textureTimeStamp = 0; 328 card->detailTextureTimeStamp = 0; 329 card->geometryTimeStamp = rsUptimeMillis(); 330} 331 332void createCards(int start, int total) 333{ 334 if (!cardAllocationValid) { 335 // If the allocation is invalid, it contains a single place-holder 336 // card that has not yet been initialized (see CarouselRS.createCards). 337 // Here we ensure that it is initialized when growing the total. 338 start = 0; 339 } 340 for (int k = start; k < total; k++) { 341 initCard(cards + k); 342 } 343 344 // Since allocations can't have 0-size, we track validity ourselves based on the call to 345 // this method. 346 cardAllocationValid = total > 0; 347 348 updateAllocationVars(cards); 349} 350 351// Computes an alpha value for a card using elapsed time and constant fadeInDuration 352static float getAnimatedAlpha(int64_t startTime, int64_t currentTime, int64_t duration) 353{ 354 double timeElapsed = (double) (currentTime - startTime); // in ms 355 double alpha = duration > 0 ? (double) timeElapsed / duration : 1.0; 356 return min(1.0f, (float) alpha); 357} 358 359// Returns total angle for given number of slots 360static float wedgeAngle(float slots) 361{ 362 return slots * 2.0f * M_PI / slotCount; 363} 364 365// Return angle of slot in position p. 366static float slotPosition(int p) 367{ 368 return startAngle + wedgeAngle(p) * fillDirection; 369} 370 371// Return angle for card in position p. 372static float cardPosition(int p) 373{ 374 return bias + slotPosition(p / rowCount); 375} 376 377// Return the lowest possible bias value, based on the fill direction 378static float minimumBias() 379{ 380 const int totalSlots = (cardCount + rowCount - 1) / rowCount; 381 return (fillDirection > 0) ? 382 -max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)) : 383 wedgeAngle(0.0f); 384} 385 386// Return the highest possible bias value, based on the fill direction 387static float maximumBias() 388{ 389 const int totalSlots = (cardCount + rowCount - 1) / rowCount; 390 return (fillDirection > 0) ? 391 wedgeAngle(0.0f) : 392 max(0.0f, wedgeAngle(totalSlots - visibleDetailCount)); 393} 394 395 396// convert from carousel rotation angle (in card slot units) to radians. 397static float carouselRotationAngleToRadians(float carouselRotationAngle) 398{ 399 return -wedgeAngle(carouselRotationAngle); 400} 401 402// convert from radians to carousel rotation angle (in card slot units). 403static float radiansToCarouselRotationAngle(float angle) 404{ 405 return -angle * slotCount / ( 2.0f * M_PI ); 406} 407 408// Set basic camera properties: 409// from - position of the camera in x,y,z 410// at - target we're looking at - used to compute view direction 411// up - a normalized vector indicating up (typically { 0, 1, 0}) 412// 413// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other 414void lookAt(float fromX, float fromY, float fromZ, 415 float atX, float atY, float atZ, 416 float upX, float upY, float upZ) 417{ 418 camera.from.x = fromX; 419 camera.from.y = fromY; 420 camera.from.z = fromZ; 421 camera.at.x = atX; 422 camera.at.y = atY; 423 camera.at.z = atZ; 424 camera.up.x = upX; 425 camera.up.y = upY; 426 camera.up.z = upZ; 427 updateCamera = true; 428} 429 430// Load a projection matrix for the given parameters. This is equivalent to gluPerspective() 431static void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far) 432{ 433 rsMatrixLoadIdentity(matrix); 434 float top = near * tan((float) (fovy * M_PI / 360.0f)); 435 float bottom = -top; 436 float left = bottom * aspect; 437 float right = top * aspect; 438 rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far); 439} 440 441// Construct a matrix based on eye point, center and up direction. Based on the 442// man page for gluLookat(). Up must be normalized. 443static void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up) 444{ 445 float3 f = normalize(center - eye); 446 float3 s = normalize(cross(f, up)); 447 float3 u = cross(s, f); 448 float m[16]; 449 m[0] = s.x; 450 m[4] = s.y; 451 m[8] = s.z; 452 m[12] = 0.0f; 453 m[1] = u.x; 454 m[5] = u.y; 455 m[9] = u.z; 456 m[13] = 0.0f; 457 m[2] = -f.x; 458 m[6] = -f.y; 459 m[10] = -f.z; 460 m[14] = 0.0f; 461 m[3] = m[7] = m[11] = 0.0f; 462 m[15] = 1.0f; 463 rsMatrixLoad(matrix, m); 464 rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z); 465} 466 467/* 468 * Returns true if a state represents a texture that is loaded enough to draw 469 */ 470static bool textureEverLoaded(int state) { 471 return (state == STATE_LOADED) || (state == STATE_STALE) || (state == STATE_UPDATING); 472} 473 474void setTexture(int n, rs_allocation texture) 475{ 476 if (n < 0 || n >= cardCount) return; 477 rsSetObject(&cards[n].texture, texture); 478 if (cards[n].textureState != STATE_STALE && 479 cards[n].textureState != STATE_UPDATING) { 480 cards[n].textureTimeStamp = rsUptimeMillis(); 481 } 482 cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID; 483} 484 485void setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture) 486{ 487 if (n < 0 || n >= cardCount) return; 488 rsSetObject(&cards[n].detailTexture, texture); 489 if (cards[n].detailTextureState != STATE_STALE && 490 cards[n].detailTextureState != STATE_UPDATING) { 491 cards[n].detailTextureTimeStamp = rsUptimeMillis(); 492 } 493 cards[n].detailTextureOffset.x = offx; 494 cards[n].detailTextureOffset.y = offy; 495 cards[n].detailLineOffset.x = loffx; 496 cards[n].detailLineOffset.y = loffy; 497 cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID; 498} 499 500void invalidateTexture(int n, bool eraseCurrent) 501{ 502 if (n < 0 || n >= cardCount) return; 503 if (eraseCurrent) { 504 cards[n].textureState = STATE_INVALID; 505 rsClearObject(&cards[n].texture); 506 } else { 507 cards[n].textureState = 508 textureEverLoaded(cards[n].textureState) ? STATE_STALE : STATE_INVALID; 509 } 510} 511 512void invalidateDetailTexture(int n, bool eraseCurrent) 513{ 514 if (n < 0 || n >= cardCount) return; 515 if (eraseCurrent) { 516 cards[n].detailTextureState = STATE_INVALID; 517 rsClearObject(&cards[n].detailTexture); 518 } else { 519 cards[n].detailTextureState = 520 textureEverLoaded(cards[n].detailTextureState) ? STATE_STALE : STATE_INVALID; 521 } 522} 523 524void setGeometry(int n, rs_mesh geometry) 525{ 526 if (n < 0 || n >= cardCount) return; 527 rsSetObject(&cards[n].geometry, geometry); 528 if (cards[n].geometry.p != 0) 529 cards[n].geometryState = STATE_LOADED; 530 else 531 cards[n].geometryState = STATE_INVALID; 532 cards[n].geometryTimeStamp = rsUptimeMillis(); 533} 534 535void setMatrix(int n, rs_matrix4x4 matrix) { 536 if (n < 0 || n >= cardCount) return; 537 cards[n].matrix = matrix; 538} 539 540void setProgramStoresCard(int n, rs_program_store programStore) 541{ 542 rsSetObject(&programStoresCard[n].programStore, programStore); 543 programStoresAllocationValid = true; 544} 545 546void setCarouselRotationAngle(float carouselRotationAngle) { 547 bias = carouselRotationAngleToRadians(carouselRotationAngle); 548} 549 550// Gets animated scale value for current selected card. 551// If card is currently being animated, returns true, otherwise returns false. 552static bool getAnimatedScaleForSelected(float3* scale) 553{ 554 const float3 one = { 1.0f, 1.0f, 1.0f }; 555 int64_t dt = rsUptimeMillis() - touchTime; 556 if (dt >= ANIMATION_DELAY_TIME) { 557 float fraction = (float) (dt - ANIMATION_DELAY_TIME) / ANIMATION_SCALE_TIME; 558 fraction = min(fraction, 1.0f); 559 *scale = one + fraction * SELECTED_SCALE_FACTOR; 560 } else { 561 *scale = one; 562 } 563 return dt < (ANIMATION_DELAY_TIME + ANIMATION_SCALE_TIME); // still animating; 564} 565 566// The Verhulst logistic function: http://en.wikipedia.org/wiki/Logistic_function 567// P(t) = 1 / (1 + e^(-t)) 568// Parameter t: Any real number 569// Returns: A float in the range (0,1), with P(0.5)=0 570static float logistic(float t) { 571 return 1.f / (1.f + exp(-t)); 572} 573 574static float getSwayAngleForVelocity(float v, bool enableSway) 575{ 576 float sway = 0.0f; 577 578 if (enableSway) { 579 const float range = M_PI * 2./3.; // How far we can deviate from center, peak-to-peak 580 sway = range * (logistic(-v * swaySensitivity) - 0.5f); 581 } 582 583 return sway; 584} 585 586// Returns the vertical offset for a card in its slot, 587// depending on the number of rows configured. 588static float getVerticalOffsetForCard(int i) { 589 if (rowCount == 1) { 590 // fast path 591 return 0; 592 } 593 const float cardHeight = (cardVertices[3].y - cardVertices[0].y) * 594 rsMatrixGet(&defaultCardMatrix, 1, 1); 595 const float totalHeight = rowCount * (cardHeight + rowSpacing) - rowSpacing; 596 if (firstCardTop) 597 i = rowCount - (i % rowCount) - 1; 598 else 599 i = i % rowCount; 600 const float rowOffset = i * (cardHeight + rowSpacing); 601 return (cardHeight - totalHeight) / 2 + rowOffset; 602} 603 604/* 605 * Composes a matrix for the given card. 606 * matrix: The output matrix. 607 * i: The card we're getting the matrix for. 608 * enableSway: Whether to enable swaying. (We want it on for cards, and off for detail textures.) 609 * enableCardMatrix: Whether to also consider the user-specified card matrix 610 * 611 * returns true if an animation is being applied to the given card 612 */ 613static bool getMatrixForCard(rs_matrix4x4* matrix, int i, bool enableSway, bool enableCardMatrix) 614{ 615 float theta = cardPosition(i); 616 float swayAngle = getSwayAngleForVelocity(velocity, enableSway); 617 rsMatrixRotate(matrix, degrees(theta), 0, 1, 0); 618 rsMatrixTranslate(matrix, radius, getVerticalOffsetForCard(i), 0); 619 float rotation = cardRotation + swayAngle; 620 if (!cardsFaceTangent) { 621 rotation -= theta; 622 } 623 rsMatrixRotate(matrix, degrees(rotation), 0, 1, 0); 624 bool stillAnimating = false; 625 if (i == animatedSelection && enableSelection) { 626 float3 scale; 627 stillAnimating = getAnimatedScaleForSelected(&scale); 628 rsMatrixScale(matrix, scale.x, scale.y, scale.z); 629 } 630 // TODO(jshuma): Instead of ignoring this matrix for the detail texture, use card bounding box 631 if (enableCardMatrix) { 632 rsMatrixLoadMultiply(matrix, matrix, &cards[i].matrix); 633 } 634 return stillAnimating; 635} 636 637/* 638 * Draws the requested mesh, with the appropriate program store in effect. 639 */ 640static void drawMesh(rs_mesh mesh) 641{ 642 if (programStoresCardCount == 1) { 643 // Draw the entire mesh, with the only available program store 644 rsgBindProgramStore(programStoresCard[0].programStore); 645 rsgDrawMesh(mesh); 646 } else { 647 // Draw each primitive in the mesh with the corresponding program store 648 for (int i=0; i<programStoresCardCount; ++i) { 649 if (programStoresCard[i].programStore.p != 0) { 650 rsgBindProgramStore(programStoresCard[i].programStore); 651 rsgDrawMesh(mesh, i); 652 } 653 } 654 } 655} 656 657/* 658 * Draws cards around the Carousel. 659 * Returns true if we're still animating any property of the cards (e.g. fades). 660 */ 661static bool drawCards(int64_t currentTime) 662{ 663 const float wedgeAngle = 2.0f * M_PI / slotCount; 664 const float endAngle = startAngle + visibleSlotCount * wedgeAngle; 665 bool stillAnimating = false; 666 for (int i = cardCount-1; i >= 0; i--) { 667 if (cards[i].cardVisible) { 668 // If this card was recently loaded, this will be < 1.0f until the animation completes 669 float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime, 670 fadeInDuration); 671 float overallAlpha = getAnimatedAlpha(cards[i].geometryTimeStamp, currentTime, 672 cardCreationFadeDuration); 673 if (animatedAlpha < 1.0f || overallAlpha < 1.0f) { 674 stillAnimating = true; 675 } 676 677 // Compute fade out for cards in the distance 678 float positionAlpha; 679 if (rezInCardCount > 0.0f) { 680 positionAlpha = (endAngle - cardPosition(i)) / wedgeAngle; 681 positionAlpha = min(1.0f, positionAlpha / rezInCardCount); 682 } else { 683 positionAlpha = 1.0f; 684 } 685 686 // Set alpha for blending between the textures 687 shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha); 688 shaderConstants->overallAlpha = overallAlpha; 689 rsgAllocationSyncAll(rsGetAllocation(shaderConstants)); 690 691 // Bind the appropriate shader network. If there's no alpha blend, then 692 // switch to single shader for better performance. 693 const int state = cards[i].textureState; 694 bool loaded = textureEverLoaded(state) && rsIsObject(cards[i].texture); 695 if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) { 696 if (overallAlpha < 1.0) { 697 rsgBindProgramFragment(singleTextureBlendingFragmentProgram); 698 rsgBindTexture(singleTextureBlendingFragmentProgram, 0, 699 (loaded && shaderConstants->fadeAmount == 1.0f) ? 700 cards[i].texture : loadingTexture); 701 } else { 702 rsgBindProgramFragment(singleTextureFragmentProgram); 703 rsgBindTexture(singleTextureFragmentProgram, 0, 704 (loaded && shaderConstants->fadeAmount == 1.0f) ? 705 cards[i].texture : loadingTexture); 706 } 707 } else { 708 if (overallAlpha < 1.0) { 709 rsgBindProgramFragment(multiTextureBlendingFragmentProgram); 710 rsgBindTexture(multiTextureBlendingFragmentProgram, 0, loadingTexture); 711 rsgBindTexture(multiTextureBlendingFragmentProgram, 1, loaded ? 712 cards[i].texture : loadingTexture); 713 } else { 714 rsgBindProgramFragment(multiTextureFragmentProgram); 715 rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture); 716 rsgBindTexture(multiTextureFragmentProgram, 1, loaded ? 717 cards[i].texture : loadingTexture); 718 } 719 } 720 721 // Draw geometry 722 rs_matrix4x4 matrix = modelviewMatrix; 723 stillAnimating |= getMatrixForCard(&matrix, i, true, true); 724 rsgProgramVertexLoadModelMatrix(&matrix); 725 if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) { 726 drawMesh(cards[i].geometry); 727 } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) { 728 drawMesh(loadingGeometry); 729 } else if (defaultGeometry.p != 0) { 730 drawMesh(defaultGeometry); 731 } else { 732 // Draw place-holder geometry 733 rsgBindProgramStore(programStoresCard[0].programStore); 734 rsgDrawQuad( 735 cardVertices[0].x, cardVertices[0].y, cardVertices[0].z, 736 cardVertices[1].x, cardVertices[1].y, cardVertices[1].z, 737 cardVertices[2].x, cardVertices[2].y, cardVertices[2].z, 738 cardVertices[3].x, cardVertices[3].y, cardVertices[3].z); 739 } 740 } 741 } 742 return stillAnimating; 743} 744 745/** 746 * Convert projection from normalized coordinates to pixel coordinates. 747 * 748 * @return True on success, false on failure. 749 */ 750static bool convertNormalizedToPixelCoordinates(float4 *screenCoord, float width, float height) { 751 // This is probably cheaper than pre-multiplying with another matrix. 752 if (screenCoord->w == 0.0f) { 753 rsDebug("Bad transform while converting from normalized to pixel coordinates: ", 754 screenCoord); 755 return false; 756 } 757 *screenCoord *= 1.0f / screenCoord->w; 758 screenCoord->x += 1.0f; 759 screenCoord->y += 1.0f; 760 screenCoord->z += 1.0f; 761 screenCoord->x = round(screenCoord->x * 0.5f * width); 762 screenCoord->y = round(screenCoord->y * 0.5f * height); 763 screenCoord->z = - 0.5f * screenCoord->z; 764 return true; 765} 766 767/* 768 * Draws a screen-aligned card with the exact dimensions from the detail texture. 769 * This is used to display information about the object being displayed. 770 * Returns true if we're still animating any property of the cards (e.g. fades). 771 */ 772static bool drawDetails(int64_t currentTime) 773{ 774 const float width = rsgGetWidth(); 775 const float height = rsgGetHeight(); 776 777 bool stillAnimating = false; 778 779 // We'll be drawing in screen space, sampled on pixel centers 780 rs_matrix4x4 projection, model; 781 rsMatrixLoadOrtho(&projection, 0.0f, width, 0.0f, height, 0.0f, 1.0f); 782 rsgProgramVertexLoadProjectionMatrix(&projection); 783 rsMatrixLoadIdentity(&model); 784 rsgProgramVertexLoadModelMatrix(&model); 785 updateCamera = true; // we messed with the projection matrix. Reload on next pass... 786 787 const float yPadding = 5.0f; // draw line this far (in pixels) away from top and geometry 788 789 // This can be done once... 790 rsgBindTexture(multiTextureFragmentProgram, 0, detailLoadingTexture); 791 792 const float wedgeAngle = 2.0f * M_PI / slotCount; 793 // Angle where details start fading from 1.0f 794 const float startDetailFadeAngle = startAngle + (visibleDetailCount - 1) * wedgeAngle; 795 // Angle where detail alpha is 0.0f 796 const float endDetailFadeAngle = startDetailFadeAngle + detailFadeRate * wedgeAngle; 797 798 for (int i = cardCount-1; i >= 0; --i) { 799 if (cards[i].cardVisible) { 800 const int state = cards[i].detailTextureState; 801 const bool isLoaded = textureEverLoaded(state); 802 if (isLoaded && cards[i].detailTexture.p != 0) { 803 const float lineWidth = rsAllocationGetDimX(detailLineTexture); 804 805 // Compute position in screen space of top corner or bottom corner of card 806 rsMatrixLoad(&model, &modelviewMatrix); 807 stillAnimating |= getMatrixForCard(&model, i, false, false); 808 rs_matrix4x4 matrix; 809 rsMatrixLoadMultiply(&matrix, &projectionMatrix, &model); 810 811 int indexLeft, indexRight; 812 float4 screenCoord; 813 if (detailTextureAlignment & BELOW) { 814 indexLeft = 0; 815 indexRight = 1; 816 } else { 817 indexLeft = 3; 818 indexRight = 2; 819 } 820 float4 screenCoordLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]); 821 float4 screenCoordRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]); 822 if (screenCoordLeft.w == 0.0f || screenCoordRight.w == 0.0f) { 823 // this shouldn't happen 824 rsDebug("Bad transform: ", screenCoord); 825 continue; 826 } 827 if (detailTextureAlignment & CENTER_VERTICAL) { 828 // If we're centering vertically, we'll need the other vertices too 829 if (detailTextureAlignment & BELOW) { 830 indexLeft = 3; 831 indexRight = 2; 832 } else { 833 indexLeft = 0; 834 indexRight = 1; 835 } 836 float4 otherScreenLeft = rsMatrixMultiply(&matrix, cardVertices[indexLeft]); 837 float4 otherScreenRight = rsMatrixMultiply(&matrix, cardVertices[indexRight]); 838 screenCoordRight.y = screenCoordLeft.y = (screenCoordLeft.y + screenCoordRight.y 839 + otherScreenLeft.y + otherScreenRight.y) / 4.; 840 } 841 (void) convertNormalizedToPixelCoordinates(&screenCoordLeft, width, height); 842 (void) convertNormalizedToPixelCoordinates(&screenCoordRight, width, height); 843 if (debugDetails) { 844 RS_DEBUG(screenCoordLeft); 845 RS_DEBUG(screenCoordRight); 846 } 847 screenCoord = screenCoordLeft; 848 if (detailTextureAlignment & BELOW) { 849 screenCoord.y = min(screenCoordLeft.y, screenCoordRight.y); 850 } else if (detailTextureAlignment & CENTER_VERTICAL) { 851 screenCoord.y -= round(rsAllocationGetDimY(cards[i].detailTexture) / 2.0f); 852 } 853 if (detailTextureAlignment & CENTER_HORIZONTAL) { 854 screenCoord.x += round((screenCoordRight.x - screenCoordLeft.x) / 2.0f - 855 rsAllocationGetDimX(cards[i].detailTexture) / 2.0f); 856 } 857 858 // Compute alpha for gradually fading in details. Applied to both line and 859 // detail texture. TODO: use a separate background texture for line. 860 float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, 861 currentTime, fadeInDuration); 862 if (animatedAlpha < 1.0f) { 863 stillAnimating = true; 864 } 865 866 // Compute alpha based on position. We fade cards quickly so they cannot overlap 867 float positionAlpha = ((float)endDetailFadeAngle - cardPosition(i)) 868 / (endDetailFadeAngle - startDetailFadeAngle); 869 positionAlpha = max(0.0f, positionAlpha); 870 positionAlpha = min(1.0f, positionAlpha); 871 872 const float blendedAlpha = min(1.0f, animatedAlpha * positionAlpha); 873 874 if (blendedAlpha == 0.0f) { 875 cards[i].detailVisible = false; 876 continue; // nothing to draw 877 } else { 878 cards[i].detailVisible = true; 879 } 880 if (blendedAlpha == 1.0f) { 881 rsgBindProgramFragment(singleTextureFragmentProgram); 882 } else { 883 rsgBindProgramFragment(multiTextureFragmentProgram); 884 } 885 886 // Set alpha for blending between the textures 887 shaderConstants->fadeAmount = blendedAlpha; 888 rsgAllocationSyncAll(rsGetAllocation(shaderConstants)); 889 890 // Draw line from the card to the detail texture. 891 // The line is drawn from the top or bottom left of the card 892 // to either the top of the screen or the top of the detail 893 // texture, depending on detailTextureAlignment. 894 if (drawRuler) { 895 float rulerTop; 896 float rulerBottom; 897 if (detailTextureAlignment & BELOW) { 898 rulerTop = screenCoord.y; 899 rulerBottom = 0; 900 } else { 901 rulerTop = height; 902 rulerBottom = screenCoord.y; 903 } 904 const float halfWidth = lineWidth * 0.5f; 905 const float x0 = trunc(cards[i].detailLineOffset.x + screenCoord.x - halfWidth); 906 const float x1 = x0 + lineWidth; 907 const float y0 = rulerBottom + yPadding; 908 const float y1 = rulerTop - yPadding - cards[i].detailLineOffset.y; 909 910 if (blendedAlpha == 1.0f) { 911 rsgBindTexture(singleTextureFragmentProgram, 0, detailLineTexture); 912 } else { 913 rsgBindTexture(multiTextureFragmentProgram, 1, detailLineTexture); 914 } 915 rsgDrawQuad(x0, y0, screenCoord.z, x1, y0, screenCoord.z, 916 x1, y1, screenCoord.z, x0, y1, screenCoord.z); 917 } 918 919 // Draw the detail texture next to it using the offsets provided. 920 const float textureWidth = rsAllocationGetDimX(cards[i].detailTexture); 921 const float textureHeight = rsAllocationGetDimY(cards[i].detailTexture); 922 const float offx = cards[i].detailTextureOffset.x; 923 const float offy = -cards[i].detailTextureOffset.y; 924 const float textureTop = (detailTextureAlignment & VIEW_TOP) 925 ? height : screenCoord.y; 926 const float x0 = cards[i].detailLineOffset.x + screenCoord.x + offx; 927 const float x1 = cards[i].detailLineOffset.x + screenCoord.x + offx + textureWidth; 928 const float y0 = textureTop + offy - textureHeight - cards[i].detailLineOffset.y; 929 const float y1 = textureTop + offy - cards[i].detailLineOffset.y; 930 cards[i].detailTexturePosition[0].x = x0; 931 cards[i].detailTexturePosition[0].y = height - y1; 932 cards[i].detailTexturePosition[1].x = x1; 933 cards[i].detailTexturePosition[1].y = height - y0; 934 935 if (blendedAlpha == 1.0f) { 936 rsgBindTexture(singleTextureFragmentProgram, 0, cards[i].detailTexture); 937 } else { 938 rsgBindTexture(multiTextureFragmentProgram, 1, cards[i].detailTexture); 939 } 940 rsgDrawQuad(x0, y0, screenCoord.z, x1, y0, screenCoord.z, 941 x1, y1, screenCoord.z, x0, y1, screenCoord.z); 942 } 943 } 944 } 945 return stillAnimating; 946} 947 948static void drawBackground() 949{ 950 static bool toggle; 951 if (backgroundTexture.p != 0) { 952 rsgClearDepth(1.0f); 953 rs_matrix4x4 projection, model; 954 rsMatrixLoadOrtho(&projection, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f); 955 rsgProgramVertexLoadProjectionMatrix(&projection); 956 rsMatrixLoadIdentity(&model); 957 rsgProgramVertexLoadModelMatrix(&model); 958 rsgBindTexture(singleTextureFragmentProgram, 0, backgroundTexture); 959 float z = -0.9999f; 960 rsgDrawQuad( 961 cardVertices[0].x, cardVertices[0].y, z, 962 cardVertices[1].x, cardVertices[1].y, z, 963 cardVertices[2].x, cardVertices[2].y, z, 964 cardVertices[3].x, cardVertices[3].y, z); 965 updateCamera = true; // we mucked with the matrix. 966 } else { 967 rsgClearDepth(1.0f); 968 if (debugRendering) { // for debugging - flash the screen so we know we're still rendering 969 rsgClearColor(toggle ? backgroundColor.x : 1.0f, 970 toggle ? backgroundColor.y : 0.0f, 971 toggle ? backgroundColor.z : 0.0f, 972 backgroundColor.w); 973 toggle = !toggle; 974 } else { 975 rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 976 backgroundColor.w); 977 } 978 } 979} 980 981static void updateCameraMatrix(float width, float height) 982{ 983 float aspect = width / height; 984 if (aspect != camera.aspect || updateCamera) { 985 camera.aspect = aspect; 986 loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far); 987 rsgProgramVertexLoadProjectionMatrix(&projectionMatrix); 988 989 loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up); 990 rsgProgramVertexLoadModelMatrix(&modelviewMatrix); 991 updateCamera = false; 992 } 993} 994 995//////////////////////////////////////////////////////////////////////////////////////////////////// 996// Behavior/Physics 997//////////////////////////////////////////////////////////////////////////////////////////////////// 998static int64_t lastTime = 0L; // keep track of how much time has passed between frames 999static float lastAngle = 0.0f; 1000static float2 lastPosition; 1001static bool animating = false; 1002static float stopVelocity = 0.1f * M_PI / 180.0f; // slower than this: carousel stops 1003static float selectionVelocity = 15.0f * M_PI / 180.0f; // faster than this: tap won't select 1004static float velocityTracker; 1005static int velocityTrackerCount; 1006static float mass = 5.0f; // kg 1007 1008static const float G = 9.80f; // gravity constant, in m/s 1009static const float springConstant = 0.0f; 1010 1011// Computes a hit angle from the center of the carousel to a point on either a plane 1012// or on a cylinder. If neither is hit, returns false. 1013static bool hitAngle(float x, float y, float *angle) 1014{ 1015 Ray ray; 1016 makeRayForPixelAt(&ray, &camera, x, y); 1017 float t = FLT_MAX; 1018 if (dragModel == DRAG_MODEL_PLANE && rayPlaneIntersect(&ray, &carouselPlane, &t)) { 1019 const float3 point = (ray.position + t*ray.direction); 1020 const float3 direction = point - carouselPlane.point; 1021 *angle = atan2(direction.x, direction.z); 1022 if (debugSelection) rsDebug("Plane Angle = ", degrees(*angle)); 1023 return true; 1024 } else if ((dragModel == DRAG_MODEL_CYLINDER_INSIDE || dragModel == DRAG_MODEL_CYLINDER_OUTSIDE) 1025 && rayCylinderIntersect(&ray, &carouselCylinder, &t)) { 1026 const float3 point = (ray.position + t*ray.direction); 1027 const float3 direction = point - carouselCylinder.center; 1028 *angle = atan2(direction.x, direction.z); 1029 if (debugSelection) rsDebug("Cylinder Angle = ", degrees(*angle)); 1030 return true; 1031 } 1032 return false; 1033} 1034 1035static float dragFunction(float x, float y) 1036{ 1037 float result; 1038 float angle; 1039 if (hitAngle(x, y, &angle)) { 1040 result = angle - lastAngle; 1041 // Handle singularity where atan2 switches between +- PI 1042 if (result < -M_PI) { 1043 result += 2.0f * M_PI; 1044 } else if (result > M_PI) { 1045 result -= 2.0f * M_PI; 1046 } 1047 lastAngle = angle; 1048 } else { 1049 // If we didn't hit anything or drag model wasn't plane or cylinder, we use screen delta 1050 result = dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI; 1051 } 1052 return result; 1053} 1054 1055static float deltaTimeInSeconds(int64_t current) 1056{ 1057 return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f; 1058} 1059 1060static int doSelection(float x, float y) 1061{ 1062 Ray ray; 1063 if (makeRayForPixelAt(&ray, &camera, x, y)) { 1064 float bestTime = FLT_MAX; 1065 return intersectGeometry(&ray, &bestTime); 1066 } 1067 return -1; 1068} 1069 1070static void sendAnimationStarted() { 1071 rsSendToClient(CMD_ANIMATION_STARTED); 1072} 1073 1074static void sendAnimationFinished() { 1075 float data[1]; 1076 data[0] = radiansToCarouselRotationAngle(bias); 1077 rsSendToClient(CMD_ANIMATION_FINISHED, (int*) data, sizeof(data)); 1078} 1079 1080void doStart(float x, float y, long eventTime) 1081{ 1082 touchPosition = lastPosition = (float2) { x, y }; 1083 lastAngle = hitAngle(x,y, &lastAngle) ? lastAngle : 0.0f; 1084 enableSelection = fabs(velocity) < selectionVelocity; 1085 velocity = 0.0f; 1086 velocityTracker = 0.0f; 1087 velocityTrackerCount = 0; 1088 touchTime = lastTime = eventTime; 1089 touchBias = bias; 1090 isDragging = true; 1091 overscroll = false; 1092 animatedSelection = doSelection(x, y); // used to provide visual feedback on touch 1093} 1094 1095void doStop(float x, float y, long eventTime) 1096{ 1097 updateAllocationVars(cards); 1098 1099 if (enableSelection) { 1100 int data[3]; 1101 int selection; 1102 float2 point; 1103 1104 if ((selection = intersectDetailTexture(x, y, &point)) != -1) { 1105 if (debugSelection) rsDebug("Selected detail texture on doStop():", selection); 1106 data[0] = selection; 1107 data[1] = point.x; 1108 data[2] = point.y; 1109 rsSendToClientBlocking(CMD_DETAIL_SELECTED, data, sizeof(data)); 1110 } 1111 else if ((selection = doSelection(x, y))!= -1) { 1112 if (debugSelection) rsDebug("Selected item on doStop():", selection); 1113 data[0] = selection; 1114 rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data)); 1115 } 1116 animating = false; 1117 } else { 1118 // TODO: move velocity tracking to Java 1119 velocity = velocityTrackerCount > 0 ? 1120 (velocityTracker / velocityTrackerCount) : 0.0f; // avg velocity 1121 if (fabs(velocity) > stopVelocity) { 1122 animating = true; 1123 } 1124 } 1125 enableSelection = false; 1126 lastTime = eventTime; 1127 isDragging = false; 1128} 1129 1130void doLongPress() 1131{ 1132 int64_t currentTime = rsUptimeMillis(); 1133 updateAllocationVars(cards); 1134 // Selection happens for most recent position detected in doMotion() 1135 if (enableSelection && animatedSelection != -1) { 1136 if (debugSelection) rsDebug("doLongPress(), selection = ", animatedSelection); 1137 int data[7]; 1138 data[0] = animatedSelection; 1139 data[1] = lastPosition.x; 1140 data[2] = lastPosition.y; 1141 data[3] = cards[animatedSelection].detailTexturePosition[0].x; 1142 data[4] = cards[animatedSelection].detailTexturePosition[0].y; 1143 data[5] = cards[animatedSelection].detailTexturePosition[1].x; 1144 data[6] = cards[animatedSelection].detailTexturePosition[1].y; 1145 rsSendToClientBlocking(CMD_CARD_LONGPRESS, data, sizeof(data)); 1146 enableSelection = false; 1147 } 1148 lastTime = rsUptimeMillis(); 1149} 1150 1151void doMotion(float x, float y, long eventTime) 1152{ 1153 const float highBias = maximumBias(); 1154 const float lowBias = minimumBias(); 1155 float deltaOmega = dragFunction(x, y); 1156 if (!enableSelection) { 1157 bias += deltaOmega; 1158 bias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS), 1159 highBias + wedgeAngle(OVERSCROLL_SLOTS)); 1160 } 1161 const float2 delta = (float2) { x, y } - touchPosition; 1162 float distance = sqrt(dot(delta, delta)); 1163 bool inside = (distance < selectionRadius); 1164 enableSelection &= inside; 1165 lastPosition = (float2) { x, y }; 1166 float dt = deltaTimeInSeconds(eventTime); 1167 if (dt > 0.0f) { 1168 float v = deltaOmega / dt; 1169 velocityTracker += v; 1170 velocityTrackerCount++; 1171 } 1172 velocity = velocityTrackerCount > 0 ? 1173 (velocityTracker / velocityTrackerCount) : 0.0f; // avg velocity 1174 lastTime = eventTime; 1175} 1176 1177//////////////////////////////////////////////////////////////////////////////////////////////////// 1178// Hit detection using ray casting. 1179//////////////////////////////////////////////////////////////////////////////////////////////////// 1180static const float EPSILON = 1.0e-6f; 1181static const float tmin = 0.0f; 1182 1183static bool 1184rayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float* tout) 1185{ 1186 float3 e1 = p1 - p0; 1187 float3 e2 = p2 - p0; 1188 float3 s1 = cross(ray->direction, e2); 1189 1190 float div = dot(s1, e1); 1191 if (div == 0.0f) return false; // ray is parallel to plane. 1192 1193 float3 d = ray->position - p0; 1194 float invDiv = 1.0f / div; 1195 1196 float u = dot(d, s1) * invDiv; 1197 if (u < 0.0f || u > 1.0f) return false; 1198 1199 float3 s2 = cross(d, e1); 1200 float v = dot(ray->direction, s2) * invDiv; 1201 if ( v < 0.0f || (u+v) > 1.0f) return false; 1202 1203 float t = dot(e2, s2) * invDiv; 1204 if (t < tmin || t > *tout) 1205 return false; 1206 *tout = t; 1207 return true; 1208} 1209 1210//////////////////////////////////////////////////////////////////////////////////////////////////// 1211// Computes ray/plane intersection. Returns false if no intersection found. 1212//////////////////////////////////////////////////////////////////////////////////////////////////// 1213static bool 1214rayPlaneIntersect(Ray* ray, Plane* plane, float* tout) 1215{ 1216 float denom = dot(ray->direction, plane->normal); 1217 if (fabs(denom) > EPSILON) { 1218 float t = - (plane->constant + dot(ray->position, plane->normal)) / denom; 1219 if (t > tmin && t < *tout) { 1220 *tout = t; 1221 return true; 1222 } 1223 } 1224 return false; 1225} 1226 1227//////////////////////////////////////////////////////////////////////////////////////////////////// 1228// Computes ray/cylindr intersection. There are 0, 1 or 2 hits. 1229// Returns true and sets *tout to the closest point or 1230// returns false if no intersection found. 1231//////////////////////////////////////////////////////////////////////////////////////////////////// 1232static bool 1233rayCylinderIntersect(Ray* ray, Cylinder* cylinder, float* tout) 1234{ 1235 const float A = ray->direction.x * ray->direction.x + ray->direction.z * ray->direction.z; 1236 if (A < EPSILON) return false; // ray misses 1237 1238 // Compute quadratic equation coefficients 1239 const float B = 2.0f * (ray->direction.x * ray->position.x 1240 + ray->direction.z * ray->position.z); 1241 const float C = ray->position.x * ray->position.x 1242 + ray->position.z * ray->position.z 1243 - cylinder->radius * cylinder->radius; 1244 float disc = B*B - 4*A*C; 1245 1246 if (disc < 0.0f) return false; // ray misses 1247 disc = sqrt(disc); 1248 const float denom = 2.0f * A; 1249 1250 // Nearest point 1251 const float t1 = (-B - disc) / denom; 1252 if (dragModel == DRAG_MODEL_CYLINDER_OUTSIDE && t1 > tmin && t1 < *tout) { 1253 *tout = t1; 1254 return true; 1255 } 1256 1257 // Far point 1258 const float t2 = (-B + disc) / denom; 1259 if (dragModel == DRAG_MODEL_CYLINDER_INSIDE && t2 > tmin && t2 < *tout) { 1260 *tout = t2; 1261 return true; 1262 } 1263 return false; 1264} 1265 1266// Creates a ray for an Android pixel coordinate given a camera, ray and coordinates. 1267// Note that the Y coordinate is opposite of GL rendering coordinates. 1268static bool __attribute__((overloadable)) 1269makeRayForPixelAt(Ray* ray, PerspectiveCamera* cam, float x, float y) 1270{ 1271 if (debugCamera) { 1272 rsDebug("------ makeRay() -------", 0); 1273 rsDebug("Camera.from:", cam->from); 1274 rsDebug("Camera.at:", cam->at); 1275 rsDebug("Camera.dir:", normalize(cam->at - cam->from)); 1276 } 1277 1278 // Vector math. This has the potential to be much faster. 1279 // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math. 1280 const float u = x / rsgGetWidth(); 1281 const float v = 1.0f - (y / rsgGetHeight()); 1282 const float aspect = (float) rsgGetWidth() / rsgGetHeight(); 1283 const float tanfov2 = 2.0f * tan(radians(cam->fov / 2.0f)); 1284 float3 dir = normalize(cam->at - cam->from); 1285 float3 du = tanfov2 * normalize(cross(dir, cam->up)); 1286 float3 dv = tanfov2 * normalize(cross(du, dir)); 1287 du *= aspect; 1288 float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv); 1289 const float3 rayPoint = cam->from; 1290 const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv); 1291 if (debugCamera) { 1292 rsDebug("Ray direction (vector math) = ", rayDir); 1293 } 1294 1295 ray->position = rayPoint; 1296 ray->direction = rayDir; 1297 return true; 1298} 1299 1300// Creates a ray for an Android pixel coordinate given a model view and projection matrix. 1301// Note that the Y coordinate is opposite of GL rendering coordinates. 1302static bool __attribute__((overloadable)) 1303makeRayForPixelAt(Ray* ray, rs_matrix4x4* model, rs_matrix4x4* proj, float x, float y) 1304{ 1305 rs_matrix4x4 pm = *model; 1306 rsMatrixLoadMultiply(&pm, proj, model); 1307 if (!rsMatrixInverse(&pm)) { 1308 rsDebug("ERROR: SINGULAR PM MATRIX", 0); 1309 return false; 1310 } 1311 const float width = rsgGetWidth(); 1312 const float height = rsgGetHeight(); 1313 const float winx = 2.0f * x / width - 1.0f; 1314 const float winy = 2.0f * y / height - 1.0f; 1315 1316 float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f }; 1317 float4 at = { winx, winy, 1.0f, 1.0f }; 1318 1319 eye = rsMatrixMultiply(&pm, eye); 1320 eye *= 1.0f / eye.w; 1321 1322 at = rsMatrixMultiply(&pm, at); 1323 at *= 1.0f / at.w; 1324 1325 const float3 rayPoint = { eye.x, eye.y, eye.z }; 1326 const float3 atPoint = { at.x, at.y, at.z }; 1327 const float3 rayDir = normalize(atPoint - rayPoint); 1328 if (debugCamera) { 1329 rsDebug("winx: ", winx); 1330 rsDebug("winy: ", winy); 1331 rsDebug("Ray position (transformed) = ", eye); 1332 rsDebug("Ray direction (transformed) = ", rayDir); 1333 } 1334 ray->position = rayPoint; 1335 ray->direction = rayDir; 1336 return true; 1337} 1338 1339static int intersectDetailTexture(float x, float y, float2 *tapCoordinates) 1340{ 1341 for (int id = 0; id < cardCount; id++) { 1342 if (cards[id].detailVisible) { 1343 const int x0 = cards[id].detailTexturePosition[0].x; 1344 const int y0 = cards[id].detailTexturePosition[0].y; 1345 const int x1 = cards[id].detailTexturePosition[1].x; 1346 const int y1 = cards[id].detailTexturePosition[1].y; 1347 if (x >= x0 && x <= x1 && y >= y0 && y <= y1) { 1348 float2 point = { x - x0, y - y0 }; 1349 *tapCoordinates = point; 1350 return id; 1351 } 1352 } 1353 } 1354 return -1; 1355} 1356 1357static int intersectGeometry(Ray* ray, float *bestTime) 1358{ 1359 int hit = -1; 1360 for (int id = 0; id < cardCount; id++) { 1361 if (cards[id].cardVisible) { 1362 rs_matrix4x4 matrix; 1363 float3 p[4]; 1364 1365 // Transform card vertices to world space 1366 rsMatrixLoadIdentity(&matrix); 1367 getMatrixForCard(&matrix, id, true, true); 1368 for (int vertex = 0; vertex < 4; vertex++) { 1369 float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]); 1370 if (tmp.w != 0.0f) { 1371 p[vertex].x = tmp.x; 1372 p[vertex].y = tmp.y; 1373 p[vertex].z = tmp.z; 1374 p[vertex] *= 1.0f / tmp.w; 1375 } else { 1376 rsDebug("Bad w coord: ", tmp); 1377 } 1378 } 1379 1380 // Intersect card geometry 1381 if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime) 1382 || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) { 1383 hit = id; 1384 } 1385 } 1386 } 1387 return hit; 1388} 1389 1390// This method computes the position of all the cards by updating bias based on a 1391// simple physics model. If the cards are still in motion, returns true. 1392static bool doPhysics(float dt) 1393{ 1394 const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame 1395 const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1; 1396 dt /= N; 1397 for (int i = 0; i < N; i++) { 1398 // Force friction - always opposes motion 1399 const float Ff = -frictionCoeff * velocity; 1400 1401 // Restoring force to match cards with slots 1402 const float theta = startAngle + bias; 1403 const float dtheta = 2.0f * M_PI / slotCount; 1404 const float position = theta / dtheta; 1405 const float fraction = position - floor(position); // fractional position between slots 1406 float x; 1407 if (fraction > 0.5f) { 1408 x = - (1.0f - fraction); 1409 } else { 1410 x = fraction; 1411 } 1412 const float Fr = - springConstant * x; 1413 1414 // compute velocity 1415 const float momentum = mass * velocity + (Ff + Fr)*dt; 1416 velocity = momentum / mass; 1417 bias += velocity * dt; 1418 } 1419 return fabs(velocity) > stopVelocity; 1420} 1421 1422static float easeOut(float x) 1423{ 1424 return x; 1425} 1426 1427// Computes the next value for bias using the current animation (physics or overscroll) 1428static bool updateNextPosition(int64_t currentTime) 1429{ 1430 static const float biasMin = 1e-4f; // close enough if we're within this margin of result 1431 1432 float dt = deltaTimeInSeconds(currentTime); 1433 1434 if (dt <= 0.0f) { 1435 if (debugRendering) rsDebug("Time delta was <= 0", dt); 1436 return true; 1437 } 1438 1439 const float highBias = maximumBias(); 1440 const float lowBias = minimumBias(); 1441 bool stillAnimating = false; 1442 if (overscroll) { 1443 if (bias > highBias) { 1444 bias -= 4.0f * dt * easeOut((bias - highBias) * 2.0f); 1445 if (fabs(bias - highBias) < biasMin) { 1446 bias = highBias; 1447 } else { 1448 stillAnimating = true; 1449 } 1450 } else if (bias < lowBias) { 1451 bias += 4.0f * dt * easeOut((lowBias - bias) * 2.0f); 1452 if (fabs(bias - lowBias) < biasMin) { 1453 bias = lowBias; 1454 } else { 1455 stillAnimating = true; 1456 } 1457 } else { 1458 overscroll = false; 1459 } 1460 } else { 1461 stillAnimating = doPhysics(dt); 1462 overscroll = bias > highBias || bias < lowBias; 1463 if (overscroll) { 1464 velocity = 0.0f; // prevent bouncing due to v > 0 after overscroll animation. 1465 } 1466 } 1467 float newbias = clamp(bias, lowBias - wedgeAngle(OVERSCROLL_SLOTS), 1468 highBias + wedgeAngle(OVERSCROLL_SLOTS)); 1469 if (newbias != bias) { // we clamped 1470 velocity = 0.0f; 1471 overscroll = true; 1472 } 1473 bias = newbias; 1474 return stillAnimating; 1475} 1476 1477// Cull cards based on visibility and visibleSlotCount. 1478// If visibleSlotCount is > 0, then only show those slots and cull the rest. 1479// Otherwise, it should cull based on bounds of geometry. 1480static int cullCards() 1481{ 1482 // TODO(jshuma): Instead of fully fetching prefetchCardCount cards, make a distinction between 1483 // STATE_LOADED and a new STATE_PRELOADING, which will keep the textures loaded but will not 1484 // attempt to actually draw them. 1485 const int prefetchCardCountPerSide = prefetchCardCount / 2; 1486 const float thetaFirst = slotPosition(-prefetchCardCountPerSide); 1487 const float thetaSelected = slotPosition(0); 1488 const float thetaHalfAngle = (thetaSelected - thetaFirst) * 0.5f; 1489 const float thetaSelectedLow = thetaSelected - thetaHalfAngle; 1490 const float thetaSelectedHigh = thetaSelected + thetaHalfAngle; 1491 const float thetaLast = slotPosition(visibleSlotCount - 1 + prefetchCardCountPerSide); 1492 1493 int count = 0; 1494 for (int i = 0; i < cardCount; i++) { 1495 if (visibleSlotCount > 0) { 1496 // If visibleSlotCount is specified, then only show up to visibleSlotCount cards. 1497 float p = cardPosition(i); 1498 if (p >= thetaFirst && p < thetaLast || p <= thetaFirst && p > thetaLast) { 1499 cards[i].cardVisible = true; 1500 // cards[i].detailVisible will be set at draw time 1501 count++; 1502 } else { 1503 cards[i].cardVisible = false; 1504 cards[i].detailVisible = false; 1505 } 1506 } else { 1507 // Cull the rest of the cards using bounding box of geometry. 1508 // TODO 1509 cards[i].cardVisible = true; 1510 // cards[i].detailVisible will be set at draw time 1511 count++; 1512 } 1513 } 1514 return count; 1515} 1516 1517// Request texture/geometry for items that have come into view 1518// or doesn't have a texture yet. 1519static void updateCardResources(int64_t currentTime) 1520{ 1521 for (int i = cardCount-1; i >= 0; --i) { 1522 int data[1]; 1523 if (cards[i].cardVisible) { 1524 if (debugTextureLoading) rsDebug("*** Texture stamp: ", (int)cards[i].textureTimeStamp); 1525 1526 // request texture from client if not loaded 1527 if (cards[i].textureState == STATE_INVALID) { 1528 data[0] = i; 1529 bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data)); 1530 if (enqueued) { 1531 cards[i].textureState = STATE_LOADING; 1532 } else { 1533 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0); 1534 } 1535 } else if (cards[i].textureState == STATE_STALE) { 1536 data[0] = i; 1537 bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data)); 1538 if (enqueued) { 1539 cards[i].textureState = STATE_UPDATING; 1540 } else { 1541 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0); 1542 } 1543 } 1544 // request detail texture from client if not loaded 1545 if (cards[i].detailTextureState == STATE_INVALID) { 1546 data[0] = i; 1547 bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data)); 1548 if (enqueued) { 1549 cards[i].detailTextureState = STATE_LOADING; 1550 } else { 1551 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0); 1552 } 1553 } else if (cards[i].detailTextureState == STATE_STALE) { 1554 data[0] = i; 1555 bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data)); 1556 if (enqueued) { 1557 cards[i].detailTextureState = STATE_UPDATING; 1558 } else { 1559 if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0); 1560 } 1561 } 1562 // request geometry from client if not loaded 1563 if (cards[i].geometryState == STATE_INVALID) { 1564 data[0] = i; 1565 bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data)); 1566 if (enqueued) { 1567 cards[i].geometryState = STATE_LOADING; 1568 } else { 1569 if (debugGeometryLoading) rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0); 1570 } 1571 } 1572 } else { 1573 // ask the host to remove the texture 1574 if (cards[i].textureState != STATE_INVALID) { 1575 data[0] = i; 1576 bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data)); 1577 if (enqueued) { 1578 cards[i].textureState = STATE_INVALID; 1579 cards[i].textureTimeStamp = currentTime; 1580 } else { 1581 if (debugTextureLoading) rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0); 1582 } 1583 } 1584 // ask the host to remove the detail texture 1585 if (cards[i].detailTextureState != STATE_INVALID) { 1586 data[0] = i; 1587 bool enqueued = rsSendToClient(CMD_INVALIDATE_DETAIL_TEXTURE, data, sizeof(data)); 1588 if (enqueued) { 1589 cards[i].detailTextureState = STATE_INVALID; 1590 cards[i].detailTextureTimeStamp = currentTime; 1591 } else { 1592 if (debugTextureLoading) rsDebug("Can't send CMD_INVALIDATE_DETAIL_TEXTURE", 0); 1593 } 1594 } 1595 // ask the host to remove the geometry 1596 if (cards[i].geometryState != STATE_INVALID) { 1597 data[0] = i; 1598 bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data)); 1599 if (enqueued) { 1600 cards[i].geometryState = STATE_INVALID; 1601 } else { 1602 if (debugGeometryLoading) rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0); 1603 } 1604 } 1605 } 1606 } 1607} 1608 1609// Places dots on geometry to visually inspect that objects can be seen by rays. 1610// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered 1611// card. 1612static void renderWithRays() 1613{ 1614 const float w = rsgGetWidth(); 1615 const float h = rsgGetHeight(); 1616 const int skip = 8; 1617 1618 rsgProgramFragmentConstantColor(singleTextureFragmentProgram, 1.0f, 0.0f, 0.0f, 1.0f); 1619 for (int j = 0; j < (int) h; j+=skip) { 1620 float posY = (float) j; 1621 for (int i = 0; i < (int) w; i+=skip) { 1622 float posX = (float) i; 1623 Ray ray; 1624 if (makeRayForPixelAt(&ray, &camera, posX, posY)) { 1625 float bestTime = FLT_MAX; 1626 if (intersectGeometry(&ray, &bestTime) != -1) { 1627 rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f); 1628 } 1629 } 1630 } 1631 } 1632} 1633 1634int root() { 1635 int64_t currentTime = rsUptimeMillis(); 1636 1637 rsgBindProgramVertex(vertexProgram); 1638 rsgBindProgramRaster(rasterProgram); 1639 rsgBindSampler(singleTextureFragmentProgram, 0, linearClamp); 1640 rsgBindSampler(multiTextureFragmentProgram, 0, linearClamp); 1641 rsgBindSampler(multiTextureFragmentProgram, 1, linearClamp); 1642 1643 updateAllocationVars(cards); 1644 1645 rsgBindProgramFragment(singleTextureFragmentProgram); 1646 // rsgClearDepth() currently follows the value of glDepthMask(), so it's disabled when 1647 // the mask is disabled. We may want to change the following to always draw w/o Z for 1648 // the background if we can guarantee the depth buffer will get cleared and 1649 // there's a performance advantage. 1650 rsgBindProgramStore(programStoreBackground); 1651 drawBackground(); 1652 1653 updateCameraMatrix(rsgGetWidth(), rsgGetHeight()); 1654 1655 bool stillAnimating = (currentTime - touchTime) <= ANIMATION_SCALE_TIME; 1656 1657 if (!isDragging && animating) { 1658 stillAnimating = updateNextPosition(currentTime); 1659 } 1660 1661 lastTime = currentTime; 1662 1663 cullCards(); 1664 1665 updateCardResources(currentTime); 1666 1667 // Draw cards opaque only if requested, and always draw detail textures with blending. 1668 stillAnimating |= drawCards(currentTime); 1669 rsgBindProgramStore(programStoreDetail); 1670 stillAnimating |= drawDetails(currentTime); 1671 1672 if (stillAnimating != animating) { 1673 if (stillAnimating) { 1674 // we just started animating 1675 sendAnimationStarted(); 1676 } else { 1677 // we were animating but stopped animating just now 1678 sendAnimationFinished(); 1679 } 1680 animating = stillAnimating; 1681 } 1682 1683 if (debugRays) { 1684 renderWithRays(); 1685 } 1686 1687 //rsSendToClient(CMD_PING); 1688 1689 return animating ? 1 : 0; 1690} 1691