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