// Copyright (c) 2014 Alex Diener. All rights reserved. #include "glgraphics/GLIncludes.h" #include "glgraphics/VertexTypes.h" #include "watertowerclassic/GameplayView.h" #include "watertowerclassic/PhysicsFunctions.h" #include "watertowerclassic/ShellStateGlobals.h" #include #include #include #include #define SUPERCLASS StemObject GameplayView * GameplayView_create(GameplayScreen * gameplayScreen, ResourceManager * resourceManager) { stemobject_create_implementation(GameplayView, init, gameplayScreen, resourceManager) } bool GameplayView_init(GameplayView * self, GameplayScreen * gameplayScreen, ResourceManager * resourceManager) { call_super(init, self); self->dispose = GameplayView_dispose; self->gameplayScreen = gameplayScreen; self->resourceManager = resourceManager; glGenBuffersARB(1, &self->vertexBufferID); glGenBuffersARB(1, &self->indexBufferID); self->spriteTexture = self->resourceManager->referenceResource(self->resourceManager, "texture", "sprites.json"); self->textureAtlas = self->resourceManager->referenceResource(self->resourceManager, "texture_atlas", "sprite_atlas.json"); self->bitmapFont = self->resourceManager->referenceResource(self->resourceManager, "bitmap_font", "pixelfont.json"); if (!strstr((char *) glGetString(GL_EXTENSIONS), "GL_ARB_shader_objects")) { self->noShaderCompatibilityMode = true; } else { self->noShaderCompatibilityMode = false; self->airBubbleShader = GLSLShader_createWithFiles("air_bubble.vert", "air_bubble.frag", NULL); } self->lastLevel = NULL; self->levelTransitionInterpolation.currentProgress = 1.0f; return true; } void GameplayView_dispose(GameplayView * self) { glDeleteBuffersARB(1, &self->vertexBufferID); glDeleteBuffersARB(1, &self->indexBufferID); self->resourceManager->releaseResource(self->resourceManager, "texture", "sprites.json"); self->resourceManager->releaseResource(self->resourceManager, "texture_atlas", "sprite_atlas.json"); self->resourceManager->releaseResource(self->resourceManager, "bitmap_font", "pixelfont.json"); if (!self->noShaderCompatibilityMode) { self->airBubbleShader->dispose(self->airBubbleShader); } if (self->lastLevel != NULL) { LevelModel_dispose(self->lastLevel); } call_super(dispose, self); } static const char * spriteNameForBlock(GameplayView * self, struct LevelBlock * block, float waterLevel, unsigned int frameIndex) { switch (block->type) { case BLOCK_TYPE_EMPTY: return NULL; case BLOCK_TYPE_STONE: return "stoneblock"; case BLOCK_TYPE_DRY_SOLID: if (!isSpongeBlockSolid(block, waterLevel)) { return "outline"; } return "blueblock"; case BLOCK_TYPE_WET_SOLID: if (!isSpongeBlockSolid(block, waterLevel)) { return "outline"; } return "yellowblock"; case BLOCK_TYPE_CRUMBLE: return "crumblingblock"; case BLOCK_TYPE_SWITCHABLE: if (!block->properties.switchable.on) { return NULL; } return "switchableblock"; case BLOCK_TYPE_DEATH: return "deadlyblock"; case BLOCK_TYPE_MOVING: return "movingblock"; case BLOCK_TYPE_FLOATING: return "woodblock"; case BLOCK_TYPE_PHASING: if (!isPhasingBlockSolid(block, frameIndex)) { return NULL; } return "phasingblock"; case BLOCK_TYPE_LOCK: return "lockedblock"; case BLOCK_TYPE_LADDER: return "ladder"; case BLOCK_TYPE_LEVER: return "switch"; case BLOCK_TYPE_PUSHABLE: return "boulder"; case BLOCK_TYPE_TRAMPOLINE: return "trampoline"; case BLOCK_TYPE_TELEPORT: return "teleport"; case BLOCK_TYPE_FIRE: return "fireblock"; case BLOCK_TYPE_ICE: return "iceblock"; case BLOCK_TYPE_SAND: return "sandblock"; case BLOCK_TYPE_STICKY: return "stickyblock"; case BLOCK_TYPE_FALLING: return "fallingblock"; default: return "stoneblock"; } } static void getBlockVertices(GameplayView * self, struct LevelBlock * block, float waterLevel, unsigned int frameIndex, float offsetY, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { const char * spriteName; rect4f rect; spriteName = spriteNameForBlock(self, block, waterLevel, frameIndex); if (spriteName == NULL) { return; } rect = getBlockPixelRect(block); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, spriteName, VECTOR2f(roundf(rect.left), roundf(rect.bottom + offsetY)), VECTOR2f(0.0f, 0.0f), VECTOR2f(rect.right - rect.left, rect.top - rect.bottom), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static const char * spriteNameForPowerupType(enum LevelPowerupType type) { switch (type) { case POWERUP_TYPE_HEART: return "heart"; case POWERUP_TYPE_KEY: return "key"; case POWERUP_TYPE_GEM: return "gem"; default: return NULL; } } static void getPowerupVertices(GameplayView * self, struct LevelPowerup * powerup, float offsetY, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { const char * spriteName; rect4f rect; spriteName = spriteNameForPowerupType(powerup->type); if (spriteName == NULL) { return; } rect = getPowerupPixelRect(powerup); return GLTextureAtlas_getVerticesWithColor(self->textureAtlas, spriteName, VECTOR2f(rect.left, rect.bottom + offsetY), VECTOR2f(0.0f, 0.0f), VECTOR2f(rect.right - rect.left, rect.top - rect.bottom), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static void getPlayerVertices(GameplayView * self, PlayerModel * player, float offsetY, struct vertex_p2f_t2f_c4f * outVertices, GLushort * outIndexes, unsigned int * ioVertexCount, unsigned int * ioIndexCount) { GLTextureAtlas_getVerticesWithColor(self->textureAtlas, player->getSpriteName(player), VECTOR2f(roundf(player->pixelLocation.x), roundf(player->pixelLocation.y + offsetY)), VECTOR2f(0.5f, 0.5f), VECTOR2f(LEVEL_BLOCK_SIZE, -PLAYER_HEIGHT), COLOR4f(1.0f, 1.0f, 1.0f, player->opacity / 255.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static void getSidebarVertices(GameplayView * self, unsigned int * ioVertexCount, unsigned int * ioIndexCount, GLushort * outIndexes, struct vertex_p2f_t2f_c4f * outVertices) { GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_topleft", VECTOR2f(512.0f, 0.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_top", VECTOR2f(517.0f, 0.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(118.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_topright", VECTOR2f(635.0f, 0.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_left", VECTOR2f(512.0f, 5.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -470.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_center", VECTOR2f(517.0f, 5.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(118.0f, -470.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_right", VECTOR2f(635.0f, 5.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -470.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_bottomleft", VECTOR2f(512.0f, 475.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_bottom", VECTOR2f(517.0f, 475.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(118.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "sidebar_bottomright", VECTOR2f(635.0f, 475.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(5.0f, -5.0f), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "heart", VECTOR2f(520.0f, 8.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(LEVEL_BLOCK_SIZE, -LEVEL_BLOCK_SIZE), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "key", VECTOR2f(576.0f, 8.0f), VECTOR2f(0.0f, 1.0f), VECTOR2f(LEVEL_BLOCK_SIZE, -LEVEL_BLOCK_SIZE), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } #define FONT_EM_HEIGHT 16 #define FONT_BASELINE_OFFSET_Y 5 static void getSidebarTextVertices(GameplayView * self, unsigned int * ioVertexCount, unsigned int * ioIndexCount, GLushort * outIndexes, struct vertex_p2f_t2f_c4f * outVertices) { char string[16]; snprintf(string, 16, " x %d", self->gameplayScreen->playerModel->numberOfLives + (self->gameplayScreen->gameState == GAME_STATE_DYING ? 1 : 0)); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, string, GLBITMAPFONT_USE_STRLEN, -FONT_EM_HEIGHT, VECTOR2f(536.0f, 20.0f + FONT_BASELINE_OFFSET_Y), VECTOR2f(0.0f, 0.0f), COLOR4f(0.0f, 0.0f, 0.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); snprintf(string, 16, " x %d", self->gameplayScreen->playerModel->numberOfKeys); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, string, GLBITMAPFONT_USE_STRLEN, -FONT_EM_HEIGHT, VECTOR2f(592.0f, 20.0f + FONT_BASELINE_OFFSET_Y), VECTOR2f(0.0f, 0.0f), COLOR4f(0.0f, 0.0f, 0.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); snprintf(string, 16, "Points: %ld", self->gameplayScreen->playerModel->numberOfPoints + self->gameplayScreen->playerModel->totalPoints); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, string, GLBITMAPFONT_USE_STRLEN, -FONT_EM_HEIGHT, VECTOR2f(520.0f, 36.0f + FONT_BASELINE_OFFSET_Y), VECTOR2f(0.0f, 0.0f), COLOR4f(0.0f, 0.0f, 0.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); snprintf(string, 16, "Level: %d", self->gameplayScreen->levelIndex + 1); GLBitmapFont_getStringVerticesWithColor(self->bitmapFont, string, GLBITMAPFONT_USE_STRLEN, -FONT_EM_HEIGHT, VECTOR2f(520.0f, 52.0f + FONT_BASELINE_OFFSET_Y), VECTOR2f(0.0f, 0.0f), COLOR4f(0.0f, 0.0f, 0.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } static bool isSpecialBlockType(enum LevelBlockType type) { switch (type) { case BLOCK_TYPE_MOVING: case BLOCK_TYPE_PUSHABLE: case BLOCK_TYPE_FLOATING: case BLOCK_TYPE_ICE: case BLOCK_TYPE_FALLING: return true; default: return false; } } static void getGameplayVertices(GameplayView * self, LevelModel * levelModel, float offsetY, unsigned int * ioVertexCount, unsigned int * ioIndexCount, GLushort * outIndexes, struct vertex_p2f_t2f_c4f * outVertices) { unsigned int blockIndex, powerupIndex; for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { if (isSpecialBlockType(levelModel->blocks[blockIndex].type)) { continue; } getBlockVertices(self, &levelModel->blocks[blockIndex], levelModel->waterLevel, levelModel == self->lastLevel ? self->lastLevelFrameIndex : self->gameplayScreen->frameIndex, offsetY, outVertices, outIndexes, ioVertexCount, ioIndexCount); } for (blockIndex = 0; blockIndex < LEVEL_BLOCK_COUNT_X * LEVEL_BLOCK_COUNT_Y; blockIndex++) { if (!isSpecialBlockType(levelModel->blocks[blockIndex].type)) { continue; } getBlockVertices(self, &levelModel->blocks[blockIndex], levelModel->waterLevel, levelModel == self->lastLevel ? self->lastLevelFrameIndex : self->gameplayScreen->frameIndex, offsetY, outVertices, outIndexes, ioVertexCount, ioIndexCount); } for (powerupIndex = 0; powerupIndex < levelModel->powerupCount; powerupIndex++) { getPowerupVertices(self, &levelModel->powerups[powerupIndex], offsetY, outVertices, outIndexes, ioVertexCount, ioIndexCount); } if (self->gameplayScreen->gameState != GAME_STATE_FINISHED_LEVEL && self->gameplayScreen->gameState != GAME_STATE_LEVEL_TRANSITION) { getPlayerVertices(self, self->gameplayScreen->playerModel, offsetY, outVertices, outIndexes, ioVertexCount, ioIndexCount); } if (levelModel->waterLevel > 0) { GLTextureAtlas_getVerticesWithColor(self->textureAtlas, "water", VECTOR2f(0.0f, 480.0f + offsetY), VECTOR2f(0.0f, 0.0f), VECTOR2f(512.0f, -levelModel->waterLevel), COLOR4f(1.0f, 1.0f, 1.0f, 1.0f), GL_UNSIGNED_SHORT, outVertices, outIndexes, ioVertexCount, ioIndexCount); } } #define BUBBLE_TESSELATIONS 128 #define BUBBLE_SHINE_TESSELATIONS 48 #define BUBBLE_SHINE_START_ANGLE 1.7825957145940461f #define BUBBLE_SHINE_END_ANGLE 3.141592653589793f void GameplayView_draw(GameplayView * self, double interval) { struct vertex_p2f_t2f_c4f * vertices; GLushort * indexes; unsigned int vertexCount, indexCount, textIndexCount; float waterBottom, gaugeBottom, gaugeWaterLevel; if (self->lastLevel != NULL) { interpolationAdvance(&self->levelTransitionInterpolation, interval); if (self->levelTransitionInterpolation.currentProgress >= 1.0f) { LevelModel_dispose(self->lastLevel); self->lastLevel = NULL; } } glLoadIdentity(); glOrtho(0.0f, 640.0f, 480.0f, 0.0f, -1.0f, 1.0f); glBegin(GL_QUADS); glColor4ub(0x9D, 0xAB, 0xCC, 0xFF); glVertex2f(0.0f, 480.0f); glVertex2f(0.0f, 0.0f); glColor4ub(0xDE, 0xDF, 0xE0, 0xFF); glVertex2f(256.0f, 0.0f); glVertex2f(256.0f, 480.0f); glVertex2f(256.0f, 480.0f); glVertex2f(256.0f, 0.0f); glColor4ub(0x9D, 0xAB, 0xCC, 0xFF); glVertex2f(512.0f, 0.0f); glVertex2f(512.0f, 480.0f); glEnd(); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glBindBufferARB(GL_ARRAY_BUFFER, self->vertexBufferID); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, self->indexBufferID); vertexCount = indexCount = 0; if (self->lastLevel != NULL) { getGameplayVertices(self, self->lastLevel, self->levelTransitionInterpolation.currentValue, &vertexCount, &indexCount, NULL, NULL); } getGameplayVertices(self, self->gameplayScreen->currentLevel, self->lastLevel == NULL ? 0.0f : self->levelTransitionInterpolation.currentValue - 480.0f, &vertexCount, &indexCount, NULL, NULL); getSidebarVertices(self, &vertexCount, &indexCount, NULL, NULL); textIndexCount = indexCount; getSidebarTextVertices(self, &vertexCount, &indexCount, NULL, NULL); glBufferDataARB(GL_ARRAY_BUFFER, sizeof(struct vertex_p2f_t2f_c4f) * vertexCount, NULL, GL_STREAM_DRAW); glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * indexCount, NULL, GL_STREAM_DRAW); vertices = glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY); indexes = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); vertexCount = indexCount = 0; if (self->lastLevel != NULL) { getGameplayVertices(self, self->lastLevel, self->levelTransitionInterpolation.currentValue, &vertexCount, &indexCount, indexes, vertices); } getGameplayVertices(self, self->gameplayScreen->currentLevel, self->lastLevel == NULL ? 0.0f : self->levelTransitionInterpolation.currentValue - 480.0f, &vertexCount, &indexCount, indexes, vertices); getSidebarVertices(self, &vertexCount, &indexCount, indexes, vertices); getSidebarTextVertices(self, &vertexCount, &indexCount, indexes, vertices); glUnmapBufferARB(GL_ARRAY_BUFFER); glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER); glVertexPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, position)); glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, texCoords)); glColorPointer(4, GL_FLOAT, sizeof(struct vertex_p2f_t2f_c4f), (void *) offsetof(struct vertex_p2f_t2f_c4f, color)); self->spriteTexture->activate(self->spriteTexture); glDrawElements(GL_TRIANGLES, textIndexCount, GL_UNSIGNED_SHORT, 0); self->spriteTexture->deactivate(self->spriteTexture); GLTexture_activate(self->bitmapFont->atlas->texture); glDrawElements(GL_TRIANGLES, indexCount - textIndexCount, GL_UNSIGNED_SHORT, (void *) (ptrdiff_t) (textIndexCount * sizeof(GLushort))); GLTexture_deactivate(self->bitmapFont->atlas->texture); glBindBufferARB(GL_ARRAY_BUFFER, 0); glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); waterBottom = self->gameplayScreen->levelSet->levels[self->gameplayScreen->levelIndex]->waterLevel; if (waterBottom > 0.0f) { gaugeBottom = 228.0f; gaugeWaterLevel = 128.0f + (480.0f - self->gameplayScreen->currentLevel->waterLevel) * 100.0f / 480.0f; } else { gaugeBottom = 128.0f - 480.0f / ((waterBottom - 480.0f) / 100); gaugeWaterLevel = 128 + (-self->gameplayScreen->currentLevel->waterLevel + 480.0f) / ((-waterBottom + 480.0f) / 100.0f); } if (gaugeWaterLevel > 228) { gaugeWaterLevel = 228; } glEnable(GL_BLEND); glBegin(GL_QUADS); // Frame glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glVertex2f(560, 127); glVertex2f(560, 229); glVertex2f(592, 229); glVertex2f(592, 127); // Tick marks glVertex2f(557, gaugeBottom); glVertex2f(559, gaugeBottom); glVertex2f(559, gaugeBottom + 1); glVertex2f(557, gaugeBottom + 1); glVertex2f(593, gaugeBottom); glVertex2f(595, gaugeBottom); glVertex2f(595, gaugeBottom + 1); glVertex2f(593, gaugeBottom + 1); // Background above level bottom glColor4f(0.625f, 0.625f, 0.625f, 1.0f); glVertex2f(561, gaugeBottom); glVertex2f(561, 228); glVertex2f(591, 228); glVertex2f(591, gaugeBottom); // Background below level bottom glColor4f(0.8125f, 0.8125f, 0.8125f, 1.0f); glVertex2f(561, 128); glVertex2f(561, gaugeBottom); glVertex2f(591, gaugeBottom); glVertex2f(591, 128); // Water glColor4ub(0x00, 0x0F, 0x7F, 0x7F); glVertex2f(561, 228); glVertex2f(591, 228); glVertex2f(591, gaugeWaterLevel); glVertex2f(561, gaugeWaterLevel); glEnd(); if (self->noShaderCompatibilityMode) { unsigned int vertexIndex; float radius, angle, outerX, outerY, innerX, innerY, sinAngle, cosAngle; radius = self->gameplayScreen->playerModel->airLeft / (float) PLAYER_BREATH_DURATION; radius = floor(radius * 50.0) / 50.0; glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); for (vertexIndex = 0; vertexIndex < BUBBLE_TESSELATIONS; vertexIndex++) { angle = M_PI * 2 * vertexIndex / (BUBBLE_TESSELATIONS - 1); sinAngle = sin(angle); cosAngle = cos(angle); outerX = cosAngle * radius * 50; outerY = sinAngle * radius * 50; innerX = outerX - 2 * cosAngle; innerY = outerY - 2 * sinAngle; if ((innerX < 0) != (outerX < 0)) { innerX = 0; } if ((innerY < 0) != (outerY < 0)) { innerY = 0; } glVertex2f(outerX + 576, outerY + 306); glVertex2f(innerX + 576, innerY + 306); } glEnd(); glBegin(GL_TRIANGLE_STRIP); for (vertexIndex = 0; vertexIndex < BUBBLE_SHINE_TESSELATIONS; vertexIndex++) { angle = BUBBLE_SHINE_START_ANGLE + (BUBBLE_SHINE_END_ANGLE - BUBBLE_SHINE_START_ANGLE) * vertexIndex / (BUBBLE_SHINE_TESSELATIONS - 1); sinAngle = sin(angle); cosAngle = cos(angle); outerX = cosAngle * radius * 35; outerY = sinAngle * radius * 35; innerX = outerX - 2 * cosAngle; innerY = outerY - 2 * sinAngle; if ((innerX < 0) != (outerX < 0)) { innerX = 0; } if ((innerY < 0) != (outerY < 0)) { innerY = 0; } glVertex2f(outerX + 576, -outerY + 306); glVertex2f(innerX + 576, -innerY + 306); } glEnd(); } else { self->airBubbleShader->activate(self->airBubbleShader); glUniform1f(GLSLShader_getUniformLocation(self->airBubbleShader, "radius"), self->gameplayScreen->playerModel->airLeft / (float) PLAYER_BREATH_DURATION); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_QUADS); glTexCoord2f(-1.0f, -1.0f); glVertex2f(526, 356); glTexCoord2f(1.0f, -1.0f); glVertex2f(626, 356); glTexCoord2f(1.0f, 1.0f); glVertex2f(626, 256); glTexCoord2f(-1.0f, 1.0f); glVertex2f(526, 256); glEnd(); self->airBubbleShader->deactivate(self->airBubbleShader); } if (self->gameplayScreen->paused) { glColor4f(1.0f, 1.0f, 1.0f, 0.75f); glBegin(GL_QUADS); glVertex2f(0.0f, 0.0f); glVertex2f(640.0f, 0.0f); glVertex2f(640.0f, 480.0f); glVertex2f(0.0f, 480.0f); glEnd(); } glDisable(GL_BLEND); } static float levelScrollCurveFunc(float value) { if (value < 0.5f) { return pow(value * 2.0f, 1.9375f) * 0.5f; } else { return 1.0f - pow((1.0f - value) * 2.0f, 1.9375f) * 0.5f; } } void GameplayView_beginLevelTransition(GameplayView * self) { if (self->lastLevel != NULL) { LevelModel_dispose(self->lastLevel); } self->lastLevel = LevelModel_copy(self->gameplayScreen->currentLevel); self->lastLevelFrameIndex = self->gameplayScreen->frameIndex; self->levelTransitionInterpolation = interpolationContextInit(0.0f, 480.0f, 43.0 / 60.0, false, levelScrollCurveFunc); }