From 7888f05ca49da2057606aa2ac2e5f6a3826c4678 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Tue, 12 Apr 2016 01:02:09 +0100 Subject: [PATCH] Re-implement map rendering with nice circular minimap --- rwengine/include/render/MapRenderer.hpp | 26 ++-- rwengine/src/render/GameRenderer.cpp | 4 +- rwengine/src/render/MapRenderer.cpp | 159 +++++++++++++----------- rwgame/DrawUI.cpp | 37 +++--- rwgame/DrawUI.hpp | 4 +- rwgame/ingamestate.cpp | 2 +- rwgame/pausestate.cpp | 7 +- 7 files changed, 130 insertions(+), 109 deletions(-) diff --git a/rwengine/include/render/MapRenderer.hpp b/rwengine/include/render/MapRenderer.hpp index a6a46d46..b3fb3a66 100644 --- a/rwengine/include/render/MapRenderer.hpp +++ b/rwengine/include/render/MapRenderer.hpp @@ -15,23 +15,22 @@ public: struct MapInfo { - float scale = 1.f; /// World coordinate center - glm::vec2 center; + glm::vec2 worldCenter; + /// World units to fit on the map + float worldSize; + /// yaw of the map float rotation = 0.f; - - /// Top of the map on the screen - glm::vec2 mapScreenBottom; - /// Bottom of the map on the screen - glm::vec2 mapScreenTop; + + glm::vec2 screenPosition; + float screenSize; + /// Make the map circular, or don't. + bool clipToSize = true; }; MapRenderer(Renderer* renderer, GameData* data); - glm::vec2 worldToMap(const glm::vec2& coord); - glm::vec2 mapToScreen(const glm::vec2& map, const MapInfo& mi); - void draw(GameWorld* world, const MapInfo& mi); private: @@ -40,8 +39,11 @@ private: GeometryBuffer rectGeom; DrawBuffer rect; + + GeometryBuffer circleGeom; + DrawBuffer circle; Renderer::ShaderProgram* rectProg; - void drawBlip(const glm::vec2& map, const glm::mat4& model, const MapInfo& mi, const std::string& texture, float heading = 0.f); -}; \ No newline at end of file + void drawBlip(const glm::vec2& map, const glm::mat4& view, const MapInfo& mi, const std::string& texture, float heading = 0.f, float size = 18.f); +}; diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index 1a81d701..93325ed6 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -520,7 +520,9 @@ void GameRenderer::renderWorld(GameWorld* world, const ViewCamera &camera, float void GameRenderer::renderPostProcess() { glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glStencilMask(0xFF); + glClearStencil(0x00); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); renderer->useProgram(postProg); diff --git a/rwengine/src/render/MapRenderer.cpp b/rwengine/src/render/MapRenderer.cpp index 46593ed5..a9752612 100644 --- a/rwengine/src/render/MapRenderer.cpp +++ b/rwengine/src/render/MapRenderer.cpp @@ -34,8 +34,8 @@ out vec4 outColour; void main() { - vec4 c = texture(spriteTexture, TexCoord); - outColour = colour + c; + vec4 c = texture(spriteTexture, TexCoord*0.99); + outColour = vec4(colour.rgb + c.rgb, colour.a * c.a); })"; @@ -50,6 +50,15 @@ MapRenderer::MapRenderer(Renderer* renderer, GameData* _data) }); rect.addGeometry(&rectGeom); rect.setFaceType(GL_TRIANGLE_STRIP); + + std::vector circleVerts; + circleVerts.push_back({0.f, 0.f}); + for (int v = 0; v < 181; ++v) { + circleVerts.push_back({0.5f*cos(2*(v/180.f)*M_PI), 0.5f*sin(2*(v/180.f)*M_PI)}); + } + circleGeom.uploadVertices(circleVerts); + circle.addGeometry(&circleGeom); + circle.setFaceType(GL_TRIANGLE_FAN); rectProg = renderer->createShader( MapVertexShader, @@ -61,61 +70,53 @@ MapRenderer::MapRenderer(Renderer* renderer, GameData* _data) #define GAME_MAP_SIZE 4000 -glm::vec2 MapRenderer::worldToMap(const glm::vec2& coord) -{ - return glm::vec2(coord.x, -coord.y); -} - -glm::vec2 MapRenderer::mapToScreen(const glm::vec2& map, const MapInfo& mi) -{ - glm::vec2 screenSize = ((map-mi.center) * mi.scale); - glm::vec2 screenCenter(500.f, 500.f); - return screenSize + screenCenter; -} - void MapRenderer::draw(GameWorld* world, const MapInfo& mi) { renderer->pushDebugGroup("Map"); renderer->useProgram(rectProg); - - glm::vec2 bottom = glm::min(mi.mapScreenBottom, mi.mapScreenTop); - glm::vec2 top = glm::max(mi.mapScreenBottom, mi.mapScreenTop); - glm::vec2 screenPos = (bottom+top)/2.f; - glm::vec2 scissorSize = top - bottom; - const glm::ivec2& vp = renderer->getViewport(); - - glEnable(GL_SCISSOR_TEST); - glScissor(bottom.x, vp.y - bottom.y - scissorSize.y, scissorSize.x, scissorSize.y); - - auto proj = renderer->get2DProjection(); - glm::mat4 view, model; - view = glm::translate(view, glm::vec3(screenPos, 0.f)); - view = glm::scale(view, glm::vec3(mi.scale)); - model = glm::rotate(model, mi.rotation, glm::vec3(0.f, 0.f, 1.f)); - model = glm::translate(model, glm::vec3(-worldToMap(mi.center), 0.f)); - renderer->setUniform(rectProg, "view", view); - renderer->setUniform(rectProg, "proj", proj); - + + // World out the number of units per tile glm::vec2 worldSize(GAME_MAP_SIZE); const int mapBlockLine = 8; glm::vec2 tileSize = worldSize / (float)mapBlockLine; - + // Determine the scale to show the right number of world units on the screen + float worldScale = mi.screenSize / mi.worldSize; + + auto proj = renderer->get2DProjection(); + glm::mat4 view, model; + renderer->setUniform(rectProg, "proj", proj); + renderer->setUniform(rectProg, "model", glm::mat4()); + renderer->setUniform(rectProg, "colour", glm::vec4(0.f, 0.f, 0.f, 1.f)); + + view = glm::translate(view, glm::vec3(mi.screenPosition, 0.f)); + + if (mi.clipToSize) + { + glBindVertexArray( circle.getVAOName() ); + glBindTexture(GL_TEXTURE_2D, 0); + glm::mat4 circleView = glm::scale(view, glm::vec3(mi.screenSize)); + renderer->setUniform(rectProg, "view", circleView); + glEnable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + glStencilMask(0xFF); + glColorMask(0x00, 0x00, 0x00, 0x00); + glDrawArrays(GL_TRIANGLE_FAN, 0, 182); + glColorMask(0xFF, 0xFF, 0xFF, 0xFF); + glStencilFunc(GL_EQUAL, 1, 0xFF); + } + + view = glm::scale(view, glm::vec3(worldScale)); + view = glm::rotate(view, mi.rotation, glm::vec3(0.f, 0.f, 1.f)); + view = glm::translate(view, glm::vec3(glm::vec2(-1.f, 1.f) * mi.worldCenter, 0.f)); + renderer->setUniform(rectProg, "view", view); + glBindVertexArray( rect.getVAOName() ); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); - renderer->setUniformTexture(rectProg, "spriteTexture", 0); - glm::mat4 bg = glm::scale( model, glm::vec3( glm::vec2( worldSize ), 1.f ) ); - - renderer->setUniform(rectProg, "model", bg); - renderer->setUniform(rectProg, "colour", glm::vec4(0.0f, 0.0f, 0.0f, 1.f)); - - glBindTexture(GL_TEXTURE_2D, 0); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - // 2048, -2048 - - renderer->setUniform(rectProg, "colour", glm::vec4(0.f)); + // radar00 = -x, +y + // incrementing in X, then Y int initX = -(mapBlockLine/2); int initY = -(mapBlockLine/2); @@ -141,10 +142,24 @@ void MapRenderer::draw(GameWorld* world, const MapInfo& mi) glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 ); } - - glDisable(GL_SCISSOR_TEST); - - renderer->setUniform(rectProg, "view", view); + + // From here on out we will work in screenspace + renderer->setUniform(rectProg, "view", glm::mat4()); + + if (mi.clipToSize) { + glDisable(GL_STENCIL_TEST); + // We only need the outer ring if we're clipping. + glBlendFuncSeparate(GL_DST_COLOR, GL_ZERO, GL_ONE, GL_ZERO); + TextureData::Handle radarDisc = data->findTexture("radardisc"); + + glm::mat4 model; + model = glm::translate(model, glm::vec3(mi.screenPosition, 0.0f)); + model = glm::scale(model, glm::vec3(mi.screenSize*1.07)); + renderer->setUniform(rectProg, "model", model); + glBindTexture(GL_TEXTURE_2D, radarDisc->getName()); + glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 ); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + } for(auto& blip : world->state->radarBlips) { @@ -170,7 +185,7 @@ void MapRenderer::draw(GameWorld* world, const MapInfo& mi) } } - drawBlip(blippos, model, mi, ""); + drawBlip(blippos, view, mi, blip.second.texture); } // Draw the player blip @@ -179,9 +194,11 @@ void MapRenderer::draw(GameWorld* world, const MapInfo& mi) { glm::vec2 plyblip(player->getPosition()); float hdg = glm::roll(player->getRotation()); - drawBlip(plyblip, model, mi, "radar_centre", hdg); + drawBlip(plyblip, view, mi, "radar_centre", mi.rotation - hdg); } - + + drawBlip(mi.worldCenter + glm::vec2(0.f, mi.worldSize), view, mi, "radar_north", 0.f, 24.f); + glBindVertexArray( 0 ); glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); @@ -191,33 +208,31 @@ void MapRenderer::draw(GameWorld* world, const MapInfo& mi) renderer->popDebugGroup(); } -void MapRenderer::drawBlip(const glm::vec2& coord, const glm::mat4& model, const MapInfo& mi, const std::string& texture, float heading) +void MapRenderer::drawBlip(const glm::vec2& coord, const glm::mat4& view, const MapInfo& mi, const std::string& texture, float heading, float size) { - auto relPos = glm::vec2( model * glm::vec4( worldToMap(coord), 0.f, 1.f ) ); - - // Now that relPos is relative to the rotation of the map, we can clip it. + glm::vec2 adjustedCoord = coord; + if (mi.clipToSize) + { + float maxDist = mi.worldSize/2.f; + float centerDist = glm::distance(coord, mi.worldCenter); + if (centerDist > maxDist) { + adjustedCoord = mi.worldCenter + ((coord - mi.worldCenter)/centerDist)*maxDist; + } + } - float invScale = 1.f/mi.scale; - glm::vec2 mapCenter( (mi.mapScreenTop + mi.mapScreenBottom) / 2.f ); - glm::vec2 mapMin( (mi.mapScreenBottom - mapCenter) ); - glm::vec2 mapMax( (mi.mapScreenTop - mapCenter) ); - - relPos = glm::max( mapMin * invScale, glm::min( mapMax * invScale, relPos ) ); - glm::vec2 map = glm::vec2( glm::inverse(model) * glm::vec4( relPos, 0.f, 1.f ) ); + glm::vec3 viewPos(view * glm::vec4(glm::vec2(1.f,-1.f)*adjustedCoord, 0.f, 1.f)); + glm::mat4 model; + model = glm::translate(model, viewPos); + model = glm::scale(model, glm::vec3(size)); + model = glm::rotate(model, heading, glm::vec3(0.f, 0.f, 1.f)); + renderer->setUniform(rectProg, "model", model); - glm::mat4 m = model; - m = glm::translate(m, glm::vec3(map, 0.f)); - m = glm::scale(m, glm::vec3(16.f * 1.f/mi.scale)); - m = glm::rotate(m, heading, glm::vec3(0.f, 0.f, -1.f)); - - renderer->setUniform(rectProg, "model", m); - GLuint tex = 0; if ( !texture.empty() ) { auto sprite= data->findTexture(texture); tex = sprite->getName(); - renderer->setUniform(rectProg, "colour", glm::vec4(0.f)); + renderer->setUniform(rectProg, "colour", glm::vec4(0.f, 0.f, 0.f, 1.f)); } else { diff --git a/rwgame/DrawUI.cpp b/rwgame/DrawUI.cpp index 64758c73..73b89117 100644 --- a/rwgame/DrawUI.cpp +++ b/rwgame/DrawUI.cpp @@ -21,30 +21,31 @@ const glm::vec3 ui_timeColour(RGB_COLOR(196, 165, 119)); const glm::vec3 ui_moneyColour(RGB_COLOR(89, 113, 147)); const glm::vec3 ui_healthColour(RGB_COLOR(187, 102, 47)); const glm::vec3 ui_armourColour(RGB_COLOR(123, 136, 93)); +const float ui_mapSize = 150.f; +const float ui_worldSizeMin = 200.f; +const float ui_worldSizeMax = 300.f; -void drawMap(PlayerController* player, GameWorld* world, GameRenderer* render) +void drawMap(ViewCamera& currentView, PlayerController* player, GameWorld* world, GameRenderer* render) { MapRenderer::MapInfo map; - map.scale = 0.4f; - glm::quat plyRot; + glm::quat camRot = currentView.rotation; + map.rotation = glm::roll(camRot) - M_PI/2.f; + map.worldSize = ui_worldSizeMin; + map.worldSize = ui_worldSizeMax; if( player ) { - plyRot = player->getCharacter()->getRotation(); - } - - map.rotation = glm::roll(plyRot); - - const glm::ivec2& vp = render->getRenderer()->getViewport(); - - map.mapScreenTop = glm::vec2(260.f, vp.y - 10.f); - map.mapScreenBottom = glm::vec2(10.f, vp.y - 210.f); - - if( player ) - { - map.center = glm::vec2(player->getCharacter()->getPosition()); + map.worldCenter = glm::vec2(player->getCharacter()->getPosition()); } + + const glm::ivec2 &vp = render->getRenderer()->getViewport(); + + glm::vec2 mapTop = glm::vec2(ui_outerMargin, vp.y - (ui_outerMargin + ui_mapSize)); + glm::vec2 mapBottom = glm::vec2(ui_outerMargin + ui_mapSize, vp.y - ui_outerMargin); + + map.screenPosition = (mapTop + mapBottom)/2.f; + map.screenSize = ui_mapSize * 0.95; render->map.draw(world, map); } @@ -193,9 +194,9 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re } } -void drawHUD(PlayerController* player, GameWorld* world, GameRenderer* render) +void drawHUD(ViewCamera& currentView, PlayerController* player, GameWorld* world, GameRenderer* render) { - drawMap(player, world, render); + drawMap(currentView, player, world, render); drawPlayerInfo(player, world, render); } diff --git a/rwgame/DrawUI.hpp b/rwgame/DrawUI.hpp index 90156b2d..102509e3 100644 --- a/rwgame/DrawUI.hpp +++ b/rwgame/DrawUI.hpp @@ -4,6 +4,6 @@ class PlayerController; -void drawHUD(PlayerController* player, GameWorld* world, GameRenderer* render); +void drawHUD(ViewCamera& currentView, PlayerController* player, GameWorld* world, GameRenderer* render); -void drawOnScreenText(GameWorld* world, GameRenderer* renderer); \ No newline at end of file +void drawOnScreenText(GameWorld* world, GameRenderer* renderer); diff --git a/rwgame/ingamestate.cpp b/rwgame/ingamestate.cpp index dbdb03cc..87a5ba50 100644 --- a/rwgame/ingamestate.cpp +++ b/rwgame/ingamestate.cpp @@ -245,7 +245,7 @@ void IngameState::draw(GameRenderer* r) { if( !getWorld()->state->isCinematic && getWorld()->isCutsceneDone() ) { - drawHUD(game->getPlayer(), getWorld(), r); + drawHUD(_look, game->getPlayer(), getWorld(), r); } State::draw(r); diff --git a/rwgame/pausestate.cpp b/rwgame/pausestate.cpp index c1cc0186..494d82be 100644 --- a/rwgame/pausestate.cpp +++ b/rwgame/pausestate.cpp @@ -35,9 +35,10 @@ void PauseState::draw(GameRenderer* r) auto& vp = r->getRenderer()->getViewport(); - map.scale = 0.2f; - map.mapScreenTop = glm::vec2(vp.x, vp.y); - map.mapScreenBottom = glm::vec2(0.f, 0.f); + map.worldSize = 4000.f; + map.clipToSize = false; + map.screenPosition = glm::vec2(vp.x/2, vp.y/2); + map.screenSize = std::max(vp.x, vp.y); game->getRenderer()->map.draw(getWorld(), map);