1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-15 01:24:45 +02:00

Initial WIP of adventure map rendering rewrite

This commit is contained in:
Ivan Savenko
2023-02-14 23:49:12 +02:00
parent 84fa19dadf
commit bb6e1f7ee1
19 changed files with 1322 additions and 151 deletions

View File

@ -11,6 +11,8 @@ set(client_SRCS
adventureMap/CMinimap.cpp adventureMap/CMinimap.cpp
adventureMap/CResDataBar.cpp adventureMap/CResDataBar.cpp
adventureMap/CTerrainRect.cpp adventureMap/CTerrainRect.cpp
adventureMap/MapRenderer.cpp
adventureMap/MapView.cpp
adventureMap/mapHandler.cpp adventureMap/mapHandler.cpp
battle/BattleActionsController.cpp battle/BattleActionsController.cpp
@ -121,6 +123,9 @@ set(client_HEADERS
adventureMap/CMinimap.h adventureMap/CMinimap.h
adventureMap/CResDataBar.h adventureMap/CResDataBar.h
adventureMap/CTerrainRect.h adventureMap/CTerrainRect.h
adventureMap/MapRenderer.h
adventureMap/MapRendererContext.h
adventureMap/MapView.h
adventureMap/mapHandler.h adventureMap/mapHandler.h
battle/BattleActionsController.h battle/BattleActionsController.h

View File

@ -475,19 +475,6 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
GH.pushInt(newCastleInt); GH.pushInt(newCastleInt);
} }
int3 CPlayerInterface::repairScreenPos(int3 pos)
{
if (pos.x<-CGI->mh->frameW)
pos.x = -CGI->mh->frameW;
if (pos.y<-CGI->mh->frameH)
pos.y = -CGI->mh->frameH;
if (pos.x>CGI->mh->sizes.x - adventureInt->terrain->tilesw + CGI->mh->frameW)
pos.x = CGI->mh->sizes.x - adventureInt->terrain->tilesw + CGI->mh->frameW;
if (pos.y>CGI->mh->sizes.y - adventureInt->terrain->tilesh + CGI->mh->frameH)
pos.y = CGI->mh->sizes.y - adventureInt->terrain->tilesh + CGI->mh->frameH;
return pos;
}
void CPlayerInterface::activateForSpectator() void CPlayerInterface::activateForSpectator()
{ {
adventureInt->state = CAdvMapInt::INGAME; adventureInt->state = CAdvMapInt::INGAME;
@ -1719,8 +1706,8 @@ void CPlayerInterface::movementPxStep( const TryMoveHero &details, int i, const
} }
} }
adventureInt->terrain->moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32; //adventureInt->terrain->moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32;
adventureInt->terrain->moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32; //adventureInt->terrain->moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32;
} }
void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ) void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho )

View File

@ -220,7 +220,6 @@ public:
void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
void updateInfo(const CGObjectInstance * specific); void updateInfo(const CGObjectInstance * specific);
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override; void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
void activateForSpectator(); // TODO: spectator probably need own player interface class void activateForSpectator(); // TODO: spectator probably need own player interface class
// show dialogs // show dialogs

View File

@ -90,11 +90,11 @@ CAdvMapInt::CAdvMapInt():
resdatabar(new CResDataBar), resdatabar(new CResDataBar),
terrain(new CTerrainRect), terrain(new CTerrainRect),
state(NA), state(NA),
spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr), spellBeingCasted(nullptr), selection(nullptr),
redrawOnNextFrame(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0), redrawOnNextFrame(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false), activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false),
swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false), swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
swipeTargetPosition(int3(-1, -1, -1)) swipeTargetPosition(Point(0, 0))
{ {
pos.x = pos.y = 0; pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x; pos.w = GH.screenDimensions().x;
@ -285,16 +285,16 @@ void CAdvMapInt::fswitchLevel()
if (maxLevels < 2) if (maxLevels < 2)
return; return;
position.z = (position.z + 1) % maxLevels; terrain->setLevel((terrain->getLevel() + 1) % maxLevels);
underground->setIndex(position.z, true); underground->setIndex(terrain->getLevel(), true);
underground->redraw(); underground->redraw();
worldViewUnderground->setIndex(position.z, true); worldViewUnderground->setIndex(terrain->getLevel(), true);
worldViewUnderground->redraw(); worldViewUnderground->redraw();
redrawOnNextFrame = true; redrawOnNextFrame = true;
minimap->setLevel(position.z); minimap->setLevel(terrain->getLevel());
if (mode == EAdvMapMode::WORLD_VIEW) if (mode == EAdvMapMode::WORLD_VIEW)
terrain->redraw(); terrain->redraw();
@ -520,8 +520,6 @@ void CAdvMapInt::showAll(SDL_Surface * to)
infoBar->showAll(to); infoBar->showAll(to);
break; break;
case EAdvMapMode::WORLD_VIEW: case EAdvMapMode::WORLD_VIEW:
terrain->showAll(to);
break; break;
} }
activeMapPanel->showAll(to); activeMapPanel->showAll(to);
@ -594,25 +592,15 @@ void CAdvMapInt::show(SDL_Surface * to)
} }
if(redrawOnNextFrame) if(redrawOnNextFrame)
{ {
int3 betterPos = LOCPLINT->repairScreenPos(position);
if (betterPos != position)
{
logGlobal->warn("Incorrect position for adventure map!");
position = betterPos;
}
terrain->show(to);
for(int i = 0; i < 4; i++) for(int i = 0; i < 4; i++)
gems[i]->showAll(to); gems[i]->showAll(to);
redrawOnNextFrame=false; redrawOnNextFrame=false;
LOCPLINT->cingconsole->show(to); LOCPLINT->cingconsole->show(to);
} }
else
{ terrain->show(to);
terrain->showAnim(to);
for(int i = 0; i < 4; i++) for(int i = 0; i < 4; i++)
gems[i]->showAll(to); gems[i]->showAll(to);
}
infoBar->show(to); infoBar->show(to);
statusbar->showAll(to); statusbar->showAll(to);
@ -624,26 +612,22 @@ void CAdvMapInt::handleMapScrollingUpdate()
//if advmap needs updating AND (no dialog is shown OR ctrl is pressed) //if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
if((animValHitCount % (4 / scrollSpeed)) == 0) if((animValHitCount % (4 / scrollSpeed)) == 0)
{ {
if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW)) if(scrollingDir & LEFT)
position.x--; terrain->moveViewBy(Point(-4, 0));
if((scrollingDir & RIGHT) && (position.x < CGI->mh->map->width - CGI->mh->tilesW + CGI->mh->frameW)) if(scrollingDir & RIGHT)
position.x++; terrain->moveViewBy(Point(+4, 0));
if((scrollingDir & UP) && (position.y > -CGI->mh->frameH)) if(scrollingDir & UP)
position.y--; terrain->moveViewBy(Point(0, -4));
if((scrollingDir & DOWN) && (position.y < CGI->mh->map->height - CGI->mh->tilesH + CGI->mh->frameH)) if(scrollingDir & DOWN)
position.y++; terrain->moveViewBy(Point(0, +4));
if(scrollingDir) if(scrollingDir)
{ {
setScrollingCursor(scrollingDir); setScrollingCursor(scrollingDir);
scrollingState = true; scrollingState = true;
redrawOnNextFrame = true;
minimap->redraw();
if(mode == EAdvMapMode::WORLD_VIEW)
terrain->redraw();
} }
else if(scrollingState) else if(scrollingState)
{ {
@ -657,9 +641,7 @@ void CAdvMapInt::handleSwipeUpdate()
{ {
if(swipeMovementRequested) if(swipeMovementRequested)
{ {
auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition); terrain->setViewCenter(swipeTargetPosition, terrain->getLevel());
position.x = fixedPos.x;
position.y = fixedPos.y;
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);
redrawOnNextFrame = true; redrawOnNextFrame = true;
minimap->redraw(); minimap->redraw();
@ -676,37 +658,22 @@ void CAdvMapInt::selectionChanged()
void CAdvMapInt::centerOn(int3 on, bool fade) void CAdvMapInt::centerOn(int3 on, bool fade)
{ {
bool switchedLevels = on.z != position.z; bool switchedLevels = on.z != terrain->getLevel();
if (fade) if (fade)
{ {
terrain->fadeFromCurrentView(); terrain->fadeFromCurrentView();
} }
switch (mode) terrain->setViewCenter(on);
{
default:
case EAdvMapMode::NORMAL:
on.x -= CGI->mh->frameW; // is this intentional? frame size doesn't really have to correspond to camera size...
on.y -= CGI->mh->frameH;
break;
case EAdvMapMode::WORLD_VIEW:
on.x -= static_cast<si32>(CGI->mh->tilesW / 2 / worldViewScale);
on.y -= static_cast<si32>(CGI->mh->tilesH / 2 / worldViewScale);
break;
}
on = LOCPLINT->repairScreenPos(on);
position = on;
redrawOnNextFrame=true; redrawOnNextFrame=true;
underground->setIndex(on.z,true); //change underground switch button image underground->setIndex(on.z,true); //change underground switch button image
underground->redraw(); underground->redraw();
worldViewUnderground->setIndex(on.z, true); worldViewUnderground->setIndex(on.z, true);
worldViewUnderground->redraw(); worldViewUnderground->redraw();
if (switchedLevels) if (switchedLevels)
minimap->setLevel(position.z); minimap->setLevel(terrain->getLevel());
minimap->redraw(); minimap->redraw();
if (mode == EAdvMapMode::WORLD_VIEW) if (mode == EAdvMapMode::WORLD_VIEW)

View File

@ -72,7 +72,7 @@ private:
bool swipeEnabled; bool swipeEnabled;
bool swipeMovementRequested; bool swipeMovementRequested;
int3 swipeTargetPosition; Point swipeTargetPosition;
EGameStates state; EGameStates state;
@ -80,7 +80,7 @@ private:
ui8 heroAnim, heroAnimValHitCount; //animation frame ui8 heroAnim, heroAnimValHitCount; //animation frame
/// top left corner of visible map part /// top left corner of visible map part
int3 position; //int3 position;
EAdvMapMode mode; EAdvMapMode mode;
float worldViewScale; float worldViewScale;

View File

@ -180,6 +180,7 @@ void CMinimap::showAll(SDL_Surface * to)
}; };
Canvas clippedTarget(target, pos); Canvas clippedTarget(target, pos);
CSDL_Ext::CClipRectGuard guard(to, pos);
clippedTarget.drawBorderDashed(radar, CSDL_Ext::fromSDL(Colors::PURPLE)); clippedTarget.drawBorderDashed(radar, CSDL_Ext::fromSDL(Colors::PURPLE));
} }
} }

View File

@ -11,6 +11,7 @@
#include "CTerrainRect.h" #include "CTerrainRect.h"
#include "mapHandler.h" #include "mapHandler.h"
#include "MapView.h"
#include "CAdvMapInt.h" #include "CAdvMapInt.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -40,14 +41,15 @@ CTerrainRect::CTerrainRect()
curHoveredTile(-1,-1,-1), curHoveredTile(-1,-1,-1),
currentPath(nullptr) currentPath(nullptr)
{ {
tilesw=(ADVOPT.advmapW+31)/32; OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
tilesh=(ADVOPT.advmapH+31)/32;
pos.x=ADVOPT.advmapX; pos.x=ADVOPT.advmapX;
pos.y=ADVOPT.advmapY; pos.y=ADVOPT.advmapY;
pos.w=ADVOPT.advmapW; pos.w=ADVOPT.advmapW;
pos.h=ADVOPT.advmapH; pos.h=ADVOPT.advmapH;
moveX = moveY = 0;
addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE); addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
renderer = std::make_shared<MapView>( Point(0,0), pos.dimensions() );
} }
CTerrainRect::~CTerrainRect() CTerrainRect::~CTerrainRect()
@ -56,6 +58,16 @@ CTerrainRect::~CTerrainRect()
SDL_FreeSurface(fadeSurface); SDL_FreeSurface(fadeSurface);
} }
void CTerrainRect::setViewCenter(const int3 &coordinates)
{
renderer->setViewCenter(coordinates);
}
void CTerrainRect::setViewCenter(const Point & position, int level)
{
renderer->setViewCenter(position, level);
}
void CTerrainRect::deactivate() void CTerrainRect::deactivate()
{ {
CIntObject::deactivate(); CIntObject::deactivate();
@ -143,10 +155,8 @@ void CTerrainRect::handleSwipeMove(const Point & cursorPosition)
if(isSwiping) if(isSwiping)
{ {
adventureInt->swipeTargetPosition.x = adventureInt->swipeTargetPosition.x = swipeInitialViewPos.x + swipeInitialRealPos.x - cursorPosition.x;
swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - cursorPosition.x) / 32; adventureInt->swipeTargetPosition.y = swipeInitialViewPos.y + swipeInitialRealPos.y - cursorPosition.y;
adventureInt->swipeTargetPosition.y =
swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - cursorPosition.y) / 32;
adventureInt->swipeMovementRequested = true; adventureInt->swipeMovementRequested = true;
} }
} }
@ -156,7 +166,7 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
if(btnPressed) if(btnPressed)
{ {
swipeInitialRealPos = Point(GH.getCursorPosition().x, GH.getCursorPosition().y); swipeInitialRealPos = Point(GH.getCursorPosition().x, GH.getCursorPosition().y);
swipeInitialMapPos = int3(adventureInt->position); swipeInitialViewPos = getViewCenter();
return true; return true;
} }
else if(isSwiping) // only accept this touch if it wasn't a swipe else if(isSwiping) // only accept this touch if it wasn't a swipe
@ -169,7 +179,7 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
void CTerrainRect::handleHover(const Point & cursorPosition) void CTerrainRect::handleHover(const Point & cursorPosition)
{ {
int3 tHovered = whichTileIsIt(cursorPosition.x, cursorPosition.y); int3 tHovered = whichTileIsIt(cursorPosition);
int3 pom = adventureInt->verifyPos(tHovered); int3 pom = adventureInt->verifyPos(tHovered);
if(tHovered != pom) //tile outside the map if(tHovered != pom) //tile outside the map
@ -196,6 +206,7 @@ void CTerrainRect::hover(bool on)
} }
void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to) void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
{ {
/*
const static int pns[9][9] = { const static int pns[9][9] = {
{16, 17, 18, 7, -1, 19, 6, 5, -1}, {16, 17, 18, 7, -1, 19, 6, 5, -1},
{ 8, 9, 18, 7, -1, 19, 6, -1, 20}, { 8, 9, 18, 7, -1, 19, 6, -1, 20},
@ -228,18 +239,18 @@ void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
const int3 &prevPos = currentPath->nodes[i-1].coord; const int3 &prevPos = currentPath->nodes[i-1].coord;
std::vector<CGPathNode> & cv = currentPath->nodes; std::vector<CGPathNode> & cv = currentPath->nodes;
/* Vector directions // Vector directions
* 0 1 2 // 0 1 2
* \ | / // \ | /
* 3 - 4 - 5 // 3 - 4 - 5
* / | \ // / | \
* 6 7 8 // 6 7 8
*For example: //For example:
* | // |
* |__\ // |__\
* / // /
* is id1=7, id2=5 (pns[7][5]) // is id1=7, id2=5 (pns[7][5])
*/ //
bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos); bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK) if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK)
{ {
@ -317,8 +328,8 @@ void CTerrainRect::showPath(const Rect & extRect, SDL_Surface * to)
} }
} //for (int i=0;i<currentPath->nodes.size()-1;i++) } //for (int i=0;i<currentPath->nodes.size()-1;i++)
} */}
/*
void CTerrainRect::show(SDL_Surface * to) void CTerrainRect::show(SDL_Surface * to)
{ {
if (adventureInt->mode == EAdvMapMode::NORMAL) if (adventureInt->mode == EAdvMapMode::NORMAL)
@ -338,7 +349,7 @@ void CTerrainRect::show(SDL_Surface * to)
fadeAnim->draw(to, r.topLeft()); fadeAnim->draw(to, r.topLeft());
} }
if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path if (currentPath) //drawing path
{ {
showPath(pos, to); showPath(pos, to);
} }
@ -366,33 +377,20 @@ void CTerrainRect::showAnim(SDL_Surface * to)
if (fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED) if (fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED)
show(to); show(to);
} }
*/
int3 CTerrainRect::whichTileIsIt(const int x, const int y) int3 CTerrainRect::whichTileIsIt(const Point &position)
{ {
int3 ret; return renderer->getTileAtPoint(position - pos);
ret.x = adventureInt->position.x + ((x-CGI->mh->offsetX-pos.x)/32);
ret.y = adventureInt->position.y + ((y-CGI->mh->offsetY-pos.y)/32);
ret.z = adventureInt->position.z;
return ret;
} }
int3 CTerrainRect::whichTileIsIt() int3 CTerrainRect::whichTileIsIt()
{ {
return whichTileIsIt(GH.getCursorPosition().x, GH.getCursorPosition().y); return whichTileIsIt(GH.getCursorPosition());
} }
Rect CTerrainRect::visibleTilesArea() Rect CTerrainRect::visibleTilesArea()
{ {
switch (adventureInt->mode) return renderer->getVisibleAreaTiles();
{
default:
logGlobal->error("Unknown map mode %d", (int)adventureInt->mode);
return Rect();
case EAdvMapMode::NORMAL:
return Rect(adventureInt->position.x, adventureInt->position.y, tilesw, tilesh);
case EAdvMapMode::WORLD_VIEW:
return Rect(adventureInt->position.x, adventureInt->position.y, tilesw / adventureInt->worldViewScale, tilesh / adventureInt->worldViewScale);
}
} }
void CTerrainRect::fadeFromCurrentView() void CTerrainRect::fadeFromCurrentView()
@ -413,3 +411,27 @@ bool CTerrainRect::needsAnimUpdate()
return fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED; return fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED;
} }
void CTerrainRect::setLevel(int level)
{
renderer->setViewCenter(renderer->getViewCenter(), level);
}
void CTerrainRect::moveViewBy(const Point & delta)
{
renderer->setViewCenter(renderer->getViewCenter() + delta, getLevel());
}
int3 CTerrainRect::getTileCenter()
{
return renderer->getTileCenter();
}
Point CTerrainRect::getViewCenter()
{
return renderer->getViewCenter();
}
int CTerrainRect::getLevel()
{
return renderer->getTileCenter().z;
}

View File

@ -18,18 +18,26 @@ VCMI_LIB_NAMESPACE_END
enum class EMapAnimRedrawStatus; enum class EMapAnimRedrawStatus;
class CFadeAnimation; class CFadeAnimation;
class MapView;
/// Holds information about which tiles of the terrain are shown/not shown at the screen /// Holds information about which tiles of the terrain are shown/not shown at the screen
class CTerrainRect : public CIntObject class CTerrainRect : public CIntObject
{ {
std::shared_ptr<MapView> renderer;
SDL_Surface * fadeSurface; SDL_Surface * fadeSurface;
EMapAnimRedrawStatus lastRedrawStatus; EMapAnimRedrawStatus lastRedrawStatus;
std::shared_ptr<CFadeAnimation> fadeAnim; std::shared_ptr<CFadeAnimation> fadeAnim;
int3 swipeInitialMapPos; Point swipeInitialViewPos;
Point swipeInitialRealPos; Point swipeInitialRealPos;
bool isSwiping; bool isSwiping;
static constexpr float SwipeTouchSlop = 16.0f;
#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
static constexpr float SwipeTouchSlop = 16.0f; // touch UI
#else
static constexpr float SwipeTouchSlop = 1.0f; // mouse UI
#endif
void handleHover(const Point & cursorPosition); void handleHover(const Point & cursorPosition);
void handleSwipeMove(const Point & cursorPosition); void handleSwipeMove(const Point & cursorPosition);
@ -37,19 +45,26 @@ class CTerrainRect : public CIntObject
bool handleSwipeStateChange(bool btnPressed); bool handleSwipeStateChange(bool btnPressed);
int3 curHoveredTile; int3 curHoveredTile;
int3 whichTileIsIt(const int x, const int y); //x,y are cursor position int3 whichTileIsIt(const Point & position); //x,y are cursor position
int3 whichTileIsIt(); //uses current cursor pos int3 whichTileIsIt(); //uses current cursor pos
void showPath(const Rect &extRect, SDL_Surface * to); void showPath(const Rect &extRect, SDL_Surface * to);
bool needsAnimUpdate(); bool needsAnimUpdate();
public: public:
int tilesw, tilesh; //width and height of terrain to blit in tiles
int moveX, moveY; //shift between actual position of screen and the one we wil blit; ranges from -31 to 31 (in pixels)
CGPath * currentPath; CGPath * currentPath;
CTerrainRect(); CTerrainRect();
~CTerrainRect(); ~CTerrainRect();
void moveViewBy(const Point & delta);
void setViewCenter(const int3 & coordinates);
void setViewCenter(const Point & position, int level);
void setLevel(int level);
int3 getTileCenter();
Point getViewCenter();
int getLevel();
// CIntObject interface implementation // CIntObject interface implementation
void deactivate() override; void deactivate() override;
void clickLeft(tribool down, bool previousState) override; void clickLeft(tribool down, bool previousState) override;
@ -57,10 +72,10 @@ public:
void clickMiddle(tribool down, bool previousState) override; void clickMiddle(tribool down, bool previousState) override;
void hover(bool on) override; void hover(bool on) override;
void mouseMoved (const Point & cursorPosition) override; void mouseMoved (const Point & cursorPosition) override;
void show(SDL_Surface * to) override; //void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override; //void showAll(SDL_Surface * to) override;
void showAnim(SDL_Surface * to); //void showAnim(SDL_Surface * to);
/// @returns number of visible tiles on screen respecting current map scaling /// @returns number of visible tiles on screen respecting current map scaling
Rect visibleTilesArea(); Rect visibleTilesArea();
@ -68,4 +83,3 @@ public:
/// animates view by caching current surface and crossfading it with normal screen /// animates view by caching current surface and crossfading it with normal screen
void fadeFromCurrentView(); void fadeFromCurrentView();
}; };

View File

@ -0,0 +1,578 @@
/*
* MapRenderer.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "MapRenderer.h"
#include "MapRendererContext.h"
#include "mapHandler.h"
#include "../CGameInfo.h"
#include "../render/CAnimation.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../../CCallback.h"
#include "../../lib/RiverHandler.h"
#include "../../lib/RoadHandler.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapping/CMap.h"
struct NeighborTilesInfo
{
bool d7, //789
d8, //456
d9, //123
d4,
d5,
d6,
d1,
d2,
d3;
NeighborTilesInfo( const IMapRendererContext & context, const int3 & pos)
{
auto getTile = [&](int dx, int dy)->bool
{
if ( dx + pos.x < 0 || dx + pos.x >= context.getMapSize().x
|| dy + pos.y < 0 || dy + pos.y >= context.getMapSize().y)
return false;
//FIXME: please do not read settings for every tile...
return context.isVisible( pos + int3(dx, dy, 0));
};
d7 = getTile(-1, -1); //789
d8 = getTile( 0, -1); //456
d9 = getTile(+1, -1); //123
d4 = getTile(-1, 0);
d5 = getTile( 0, 0);
d6 = getTile(+1, 0);
d1 = getTile(-1, +1);
d2 = getTile( 0, +1);
d3 = getTile(+1, +1);
}
bool areAllHidden() const
{
return !(d1 || d2 || d3 || d4 || d5 || d6 || d7 || d8 || d9);
}
int getBitmapID() const
{
//NOTE: some images have unused in VCMI pair (same blockmap but a bit different look)
// 0-1, 2-3, 4-5, 11-13, 12-14
static const int visBitmaps[256] = {
-1, 34, 4, 4, 22, 23, 4, 4, 36, 36, 38, 38, 47, 47, 38, 38, //16
3, 25, 12, 12, 3, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //32
35, 39, 48, 48, 41, 43, 48, 48, 36, 36, 38, 38, 47, 47, 38, 38, //48
26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //64
0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //80
13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //96
0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //112
13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //128
15, 17, 30, 30, 16, 19, 30, 30, 46, 46, 40, 40, 32, 32, 40, 40, //144
2, 25, 12, 12, 2, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //160
18, 42, 31, 31, 20, 21, 31, 31, 46, 46, 40, 40, 32, 32, 40, 40, //176
26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //192
0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //208
13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //224
0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //240
13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10 //256
};
return visBitmaps[d1 + d2 * 2 + d3 * 4 + d4 * 8 + d6 * 16 + d7 * 32 + d8 * 64 + d9 * 128]; // >=0 -> partial hide, <0 - full hide
}
};
MapObjectsSorter::MapObjectsSorter(const IMapRendererContext & context)
: context(context)
{
}
bool MapObjectsSorter::operator()(const ObjectInstanceID & left, const ObjectInstanceID & right) const
{
return (*this)(context.getObject(left), context.getObject(right));
}
bool MapObjectsSorter::operator()(const CGObjectInstance * left, const CGObjectInstance * right) const
{
//FIXME: remove mh access
return CGI->mh->compareObjectBlitOrder(left, right);
}
MapTileStorage::MapTileStorage(size_t capacity)
: animations(capacity)
{
}
void MapTileStorage::load(size_t index, const std::string & filename)
{
auto & terrainAnimations = animations[index];
for(auto & entry : terrainAnimations)
{
entry = std::make_unique<CAnimation>(filename);
entry->preload();
}
for(size_t i = 0; i < terrainAnimations[0]->size(); ++i)
{
terrainAnimations[1]->getImage(i)->verticalFlip();
terrainAnimations[3]->getImage(i)->verticalFlip();
terrainAnimations[2]->getImage(i)->horizontalFlip();
terrainAnimations[3]->getImage(i)->horizontalFlip();
}
}
std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
{
const auto & animation = animations[fileIndex][rotationIndex];
return animation->getImage(imageIndex);
}
MapRendererTerrain::MapRendererTerrain()
: storage(VLC->terrainTypeHandler->objects.size())
{
for(const auto & terrain : VLC->terrainTypeHandler->objects)
storage.load(terrain->getIndex(), terrain->tilesFilename);
}
void MapRendererTerrain::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
const TerrainTile & mapTile = context.getMapTile(coordinates);
int32_t terrainIndex = mapTile.terType->getIndex();
int32_t imageIndex = mapTile.terView;
int32_t rotationIndex = mapTile.extTileFlags % 4;
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
target.draw(image, Point(0, 0));
}
MapRendererRiver::MapRendererRiver()
: storage(VLC->riverTypeHandler->objects.size())
{
for(const auto & river : VLC->riverTypeHandler->objects)
storage.load(river->getIndex(), river->tilesFilename);
}
void MapRendererRiver::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
const TerrainTile & mapTile = context.getMapTile(coordinates);
if(mapTile.riverType->getId() != River::NO_RIVER)
{
int32_t terrainIndex = mapTile.riverType->getIndex();
int32_t imageIndex = mapTile.riverDir;
int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
target.draw(image, Point(0, 0));
}
}
MapRendererRoad::MapRendererRoad():
storage(VLC->roadTypeHandler->objects.size())
{
for(const auto & road : VLC->roadTypeHandler->objects)
storage.load(road->getIndex(), road->tilesFilename);
}
void MapRendererRoad::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
const int3 coordinatesAbove = coordinates - int3(0,1,0);
if (context.isInMap(coordinatesAbove))
{
const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove);
if (mapTileAbove.roadType->getId() != Road::NO_ROAD)
{
int32_t terrainIndex = mapTileAbove.roadType->getIndex();
int32_t imageIndex = mapTileAbove.roadDir;
int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4;
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
target.draw(image, Point(0,0), Rect(0, 16, 32, 16));
}
}
const TerrainTile & mapTile = context.getMapTile(coordinates);
if(mapTile.roadType->getId() != Road::NO_ROAD)
{
int32_t terrainIndex = mapTile.roadType->getIndex();
int32_t imageIndex = mapTile.roadDir;
int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4;
const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
target.draw(image, Point(0,16), Rect(0, 0, 32, 16));
}
}
MapRendererBorder::MapRendererBorder()
{
animation = std::make_unique<CAnimation>("EDG");
animation->preload();
}
size_t MapRendererBorder::getIndexForTile(const IMapRendererContext & context, const int3 & tile)
{
assert(!context.isInMap(tile));
int3 size = context.getMapSize();
if (tile.x < -1 || tile.x > size.x || tile.y < -1 || tile.y > size.y)
return std::abs(tile.x) % 4 + 4*(std::abs(tile.y) % 4);
if (tile.x == -1 && tile.y == -1)
return 16;
if (tile.x == size.x && tile.y == -1)
return 17;
if (tile.x == size.x && tile.y == size.y)
return 18;
if (tile.x == -1 && tile.y == size.y)
return 19;
if (tile.y == -1)
return 20 + (tile.x % 4);
if (tile.x == size.x)
return 24 + (tile.y % 4);
if (tile.y == size.y)
return 28 + (tile.x % 4);
if (tile.x == -1)
return 32 + (tile.y % 4);
//else - visible area, no renderable border
assert(0);
return 0;
}
void MapRendererBorder::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
const auto & image = animation->getImage(getIndexForTile(context, coordinates));
target.draw(image, Point(0,0));
}
MapRendererFow::MapRendererFow()
{
fogOfWarFullHide = std::make_unique<CAnimation>("TSHRC");
fogOfWarFullHide->preload();
fogOfWarPartialHide = std::make_unique<CAnimation>("TSHRE");
fogOfWarPartialHide->preload();
static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
size_t size = fogOfWarPartialHide->size(0);//group size after next rotation
for(const int rotation : rotations)
{
fogOfWarPartialHide->duplicateImage(0, rotation, 0);
auto image = fogOfWarPartialHide->getImage(size, 0);
image->verticalFlip();
size++;
}
}
void MapRendererFow::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
assert(!context.isVisible(coordinates));
const NeighborTilesInfo neighborInfo(context, coordinates);
int retBitmapID = neighborInfo.getBitmapID();// >=0 -> partial hide, <0 - full hide
if (retBitmapID < 0)
{
// generate a number that is predefined for each tile,
// but appears random to break visible pattern in large areas of fow
// current approach (use primes as magic numbers for formula) looks to be suitable
size_t pseudorandomNumber = ((coordinates.x * 997) ^ (coordinates.y * 1009)) / 101;
size_t imageIndex = pseudorandomNumber % fogOfWarFullHide->size();
target.draw(fogOfWarFullHide->getImage(imageIndex), Point(0,0));
}
else
{
target.draw(fogOfWarPartialHide->getImage(retBitmapID), Point(0,0));
}
}
std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const CGObjectInstance* obj)
{
const auto & info = obj->appearance;
//the only(?) invisible object
if(info->id == Obj::EVENT)
return std::shared_ptr<CAnimation>();
if(info->animationFile.empty())
{
logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
return std::shared_ptr<CAnimation>();
}
return getAnimation(info->animationFile);
}
std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const std::string & filename)
{
if (animations.count(filename))
return animations[filename];
auto ret = std::make_shared<CAnimation>(filename);
animations[filename] = ret;
ret->preload();
return ret;
}
void MapRendererObjects::initializeObjects(const IMapRendererContext & context)
{
auto mapSize = context.getMapSize();
objects.resize(boost::extents[mapSize.z][mapSize.x][mapSize.y]);
for(const auto & obj : context.getAllObjects())
{
if(!obj)
continue;
if(obj->ID == Obj::HERO && dynamic_cast<const CGHeroInstance *>(obj.get())->inTownGarrison)
continue;
if(obj->ID == Obj::BOAT && dynamic_cast<const CGBoat *>(obj.get())->hero)
continue;
std::shared_ptr<CAnimation> animation = getAnimation(obj);
//no animation at all, e.g. Event
if(!animation)
continue;
//empty animation. Illegal?
assert(animation->size(0) > 0);
if(animation->size(0) == 0)
continue;
auto image = animation->getImage(0, 0);
int imageWidthTiles = (image->width() + 31) / 32;
int imageHeightTiles = (image->height() + 31) / 32;
int objectWidth = std::min(obj->getWidth(), imageWidthTiles);
int objectHeight = std::min(obj->getHeight(), imageHeightTiles);
for(int fx = 0; fx < objectWidth; ++fx)
{
for(int fy = 0; fy < objectHeight; ++fy)
{
int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z);
if(context.isInMap(currTile) && obj->coveringAt(currTile.x, currTile.y))
objects[currTile.z][currTile.x][currTile.y].push_back(obj->id);
}
}
}
for(int z = 0; z < mapSize.z; z++)
{
for(int x = 0; x < mapSize.x; x++)
{
for(int y = 0; y < mapSize.y; y++)
{
auto & array = objects[z][x][y];
std::sort(array.begin(), array.end(), MapObjectsSorter(context));
}
}
}
}
MapRendererObjects::MapRendererObjects(const IMapRendererContext & context)
{
initializeObjects(context);
}
std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectInstance* obj)
{
//TODO: relocate to config file?
static const std::vector<std::string> heroFlags = {
"AF00", "AF01", "AF02", "AF03", "AF04", "AF05", "AF06", "AF07"
};
//TODO: relocate to config file?
static const std::vector<std::vector<std::string>> boatFlags = {
{"ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"},
{"ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"},
{"ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"}
};
if(obj->ID == Obj::HERO)
{
assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
assert(obj->tempOwner.isValidPlayer());
return getAnimation(heroFlags[obj->tempOwner.getNum()]);
}
if(obj->ID == Obj::BOAT)
{
const auto * boat = dynamic_cast<const CGBoat *>(obj);
assert(boat);
assert(boat->subID < boatFlags.size());
assert(!boat->hero || boat->hero->tempOwner.isValidPlayer());
if(boat->hero)
return getAnimation(boatFlags[obj->subID][boat->hero->tempOwner.getNum()]);
}
return nullptr;
}
std::shared_ptr<IImage> MapRendererObjects::getImage(const IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation>& animation) const
{
if(!animation)
return nullptr;
size_t groupIndex = getAnimationGroup(context, obj);
if(animation->size(groupIndex) == 0)
return nullptr;
size_t frameCounter = context.getAnimationTime() / context.getAnimationPeriod();
size_t frameIndex = frameCounter % animation->size(groupIndex);
return animation->getImage(frameIndex, groupIndex);
}
size_t MapRendererObjects::getAnimationGroup(const IMapRendererContext & context, const CGObjectInstance * obj) const
{
// TODO
//static const std::vector<size_t> moveGroups = {99, 10, 5, 6, 7, 8, 9, 12, 11};
static const std::vector<size_t> idleGroups = {99, 13, 0, 1, 2, 3, 4, 15, 14};
if(obj->ID == Obj::HERO)
{
const auto * hero = dynamic_cast<const CGHeroInstance *>(obj);
return idleGroups[hero->moveDir];
}
if(obj->ID == Obj::BOAT)
{
const auto * boat = dynamic_cast<const CGBoat *>(obj);
return idleGroups[boat->direction];
}
return 0;
}
void MapRendererObjects::renderImage(Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage>& image)
{
if(!image)
return;
image->setFlagColor(object->tempOwner);
int3 offsetTiles(object->getPosition() - coordinates);
Point offsetPixels(offsetTiles.x * 32, offsetTiles.y * 32);
Point imagePos = image->dimensions() - offsetPixels - Point(32, 32);
Point tileDimensions(32, 32);
target.draw(image, Point(0, 0), Rect(imagePos, tileDimensions));
}
void MapRendererObjects::renderObject(const IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance* instance)
{
renderImage(target, coordinates, instance, getImage(context, instance, getAnimation(instance)));
renderImage(target, coordinates, instance, getImage(context, instance, getFlagAnimation(instance)));
}
void MapRendererObjects::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
for(const auto & objectID : objects[coordinates.z][coordinates.x][coordinates.y])
{
const auto * objectInstance = context.getObject(objectID);
assert(objectInstance);
if(!objectInstance)
{
logGlobal->error("Stray map object that isn't fading");
continue;
}
renderObject(context, target, coordinates, objectInstance);
}
}
void MapRendererObjects::addObject(const IMapRendererContext & context, const CGObjectInstance * object)
{
}
void MapRendererObjects::removeObject(const IMapRendererContext & context, const CGObjectInstance * object)
{
}
void MapRendererDebugGrid::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
if(context.showGrid())
{
target.drawLine(Point(0, 0), Point(0, 31), {128, 128, 128, 128}, {128, 128, 128, 128});
target.drawLine(Point(0, 0), Point(31, 0), {128, 128, 128, 128}, {128, 128, 128, 128});
}
}
MapRenderer::MapRenderer(const IMapRendererContext & context)
: rendererObjects(context)
{
}
void MapRenderer::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
{
if (!context.isInMap(coordinates))
{
rendererBorder.renderTile(context, target, coordinates);
return;
}
const NeighborTilesInfo neighborInfo(context, coordinates);
if (neighborInfo.areAllHidden())
{
rendererFow.renderTile(context, target, coordinates);
}
else
{
rendererTerrain.renderTile(context, target, coordinates);
rendererRiver.renderTile(context, target, coordinates);
rendererRoad.renderTile(context, target, coordinates);
rendererObjects.renderTile(context, target, coordinates);
if (!context.isVisible(coordinates))
rendererFow.renderTile(context, target, coordinates);
}
rendererDebugGrid.renderTile(context, target,coordinates);
}
void MapRenderer::addObject(const IMapRendererContext & context, const CGObjectInstance * object)
{
rendererObjects.addObject(context, object);
}
void MapRenderer::removeObject(const IMapRendererContext & context, const CGObjectInstance * object)
{
rendererObjects.addObject(context, object);
}

View File

@ -0,0 +1,140 @@
/*
* MapRenderer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class int3;
class ObjectInstanceID;
class CGObjectInstance;
VCMI_LIB_NAMESPACE_END
class CAnimation;
class IImage;
class Canvas;
class IMapRendererContext;
class MapObjectsSorter
{
const IMapRendererContext & context;
public:
explicit MapObjectsSorter(const IMapRendererContext & context);
bool operator ()(const ObjectInstanceID & left, const ObjectInstanceID & right) const;
bool operator ()(const CGObjectInstance * left, const CGObjectInstance * right) const;
};
class MapTileStorage
{
using TerrainAnimation = std::array<std::unique_ptr<CAnimation>, 4>;
std::vector<TerrainAnimation> animations;
public:
explicit MapTileStorage( size_t capacity);
void load(size_t index, const std::string& filename);
std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex );
};
class MapRendererTerrain
{
MapTileStorage storage;
public:
MapRendererTerrain();
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererRiver
{
MapTileStorage storage;
public:
MapRendererRiver();
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererRoad
{
MapTileStorage storage;
public:
MapRendererRoad();
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererObjects
{
using MapObject = ObjectInstanceID;
using MapTile = std::vector<MapObject>;
boost::multi_array<MapTile, 3> objects;
std::map<std::string, std::shared_ptr<CAnimation>> animations;
std::shared_ptr<CAnimation> getFlagAnimation(const CGObjectInstance* obj);
std::shared_ptr<CAnimation> getAnimation(const CGObjectInstance* obj);
std::shared_ptr<CAnimation> getAnimation(const std::string & filename);
std::shared_ptr<IImage> getImage(const IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation>& animation) const;
size_t getAnimationGroup(const IMapRendererContext & context, const CGObjectInstance * obj) const;
void initializeObjects(const IMapRendererContext & context);
void renderImage(Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage>& image);
void renderObject(const IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance* obj);
public:
explicit MapRendererObjects(const IMapRendererContext & context);
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
void addObject(const IMapRendererContext & context, const CGObjectInstance * object);
void removeObject(const IMapRendererContext & context, const CGObjectInstance * object);
};
class MapRendererBorder
{
std::unique_ptr<CAnimation> animation;
size_t getIndexForTile(const IMapRendererContext & context, const int3 & coordinates);
public:
MapRendererBorder();
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererFow
{
std::unique_ptr<CAnimation> fogOfWarFullHide;
std::unique_ptr<CAnimation> fogOfWarPartialHide;
public:
MapRendererFow();
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererDebugGrid
{
public:
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRenderer
{
MapRendererTerrain rendererTerrain;
MapRendererRiver rendererRiver;
MapRendererRoad rendererRoad;
MapRendererBorder rendererBorder;
MapRendererFow rendererFow;
MapRendererObjects rendererObjects;
MapRendererDebugGrid rendererDebugGrid;
public:
explicit MapRenderer(const IMapRendererContext & context);
void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
void addObject(const IMapRendererContext & context, const CGObjectInstance * object);
void removeObject(const IMapRendererContext & context, const CGObjectInstance * object);
};

View File

@ -0,0 +1,47 @@
/*
* MapRenderer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/ConstTransitivePtr.h"
VCMI_LIB_NAMESPACE_BEGIN
class int3;
class Point;
class ObjectInstanceID;
class CGObjectInstance;
struct TerrainTile;
VCMI_LIB_NAMESPACE_END
class IMapRendererContext
{
public:
virtual ~IMapRendererContext() = default;
using VisibilityMap = std::shared_ptr<const boost::multi_array<ui8, 3>>;
using ObjectsVector = std::vector< ConstTransitivePtr<CGObjectInstance> >;
virtual int3 getMapSize() const = 0;
virtual bool isInMap(const int3 & coordinates) const = 0;
virtual const TerrainTile & getMapTile(const int3 & coordinates) const = 0;
virtual ObjectsVector getAllObjects() const = 0;
virtual const CGObjectInstance * getObject( ObjectInstanceID objectID ) const = 0;
virtual bool isVisible(const int3 & coordinates) const = 0;
virtual VisibilityMap getVisibilityMap() const = 0;
virtual uint32_t getAnimationPeriod() const = 0;
virtual uint32_t getAnimationTime() const = 0;
virtual Point tileSize() const = 0;
virtual bool showGrid() const = 0;
};

View File

@ -0,0 +1,297 @@
/*
* MapView.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "MapView.h"
#include "MapRenderer.h"
#include "mapHandler.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../render/CAnimation.h"
#include "../render/CFadeAnimation.h"
#include "../render/Canvas.h"
#include "../render/Colors.h"
#include "../render/Graphics.h"
#include "../render/IImage.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/CStopWatch.h"
#include "../../lib/Color.h"
#include "../../lib/RiverHandler.h"
#include "../../lib/RoadHandler.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CObjectClassesHandler.h"
#include "../../lib/mapping/CMap.h"
MapCache::~MapCache() = default;
MapCache::MapCache(const Point & tileSize, const Point & dimensions)
: tileSize(tileSize)
, context(new MapRendererContext())
, mapRenderer(new MapRenderer(*context))
, targetDimensionsPixels(dimensions)
, viewCenter(0, 0)
, mapLevel(0)
{
// total number of potentially visible tiles is:
// 1) number of completely visible tiles
// 2) additional tile that might be partially visible from left/top size
// 3) additional tile that might be partially visible from right/bottom size
Point visibleTiles{
dimensions.x / tileSize.x + 2,
dimensions.y / tileSize.y + 2,
};
viewDimensionsTiles = visibleTiles;
viewDimensionsPixels = visibleTiles * tileSize;
terrain = std::make_unique<Canvas>(viewDimensionsPixels);
}
void MapCache::setViewCenter(const Point & center, int newLevel)
{
viewCenter = center;
mapLevel = newLevel;
int3 mapSize = LOCPLINT->cb->getMapSize();
Point viewMax = Point(mapSize) * tileSize;
vstd::abetween(viewCenter.x, 0, viewMax.x);
vstd::abetween(viewCenter.y, 0, viewMax.y);
}
Canvas MapCache::getTile(const int3 & coordinates)
{
assert(mapLevel == coordinates.z);
assert(viewDimensionsTiles.x + coordinates.x >= 0);
assert(viewDimensionsTiles.y + coordinates.y >= 0);
Point tileIndex{
(viewDimensionsTiles.x + coordinates.x) % viewDimensionsTiles.x,
(viewDimensionsTiles.y + coordinates.y) % viewDimensionsTiles.y
};
Rect terrainSection(tileIndex * tileSize, tileSize);
return Canvas(*terrain, terrainSection);
}
void MapCache::updateTile(const int3 & coordinates)
{
Canvas target = getTile(coordinates);
mapRenderer->renderTile(*context, target, coordinates);
}
Rect MapCache::getVisibleAreaTiles() const
{
Rect visibleAreaPixels = {
viewCenter.x - targetDimensionsPixels.x / 2,
viewCenter.y - targetDimensionsPixels.y / 2,
targetDimensionsPixels.x,
targetDimensionsPixels.y
};
// NOTE: use division via double in order to use floor (which rounds to negative infinity and not towards zero)
Point topLeftTile{
static_cast<int>(std::floor(static_cast<double>(visibleAreaPixels.left()) / tileSize.x)),
static_cast<int>(std::floor(static_cast<double>(visibleAreaPixels.top()) / tileSize.y)),
};
Point bottomRightTile{
visibleAreaPixels.right() / tileSize.x,
visibleAreaPixels.bottom() / tileSize.y
};
return Rect(topLeftTile, bottomRightTile - topLeftTile + Point(1, 1));
}
int3 MapCache::getTileAtPoint(const Point & position) const
{
Point topLeftOffset = viewCenter - targetDimensionsPixels / 2;
Point absolutePosition = position + topLeftOffset;
return {
static_cast<int>(std::floor(static_cast<double>(absolutePosition.x) / tileSize.x)),
static_cast<int>(std::floor(static_cast<double>(absolutePosition.y) / tileSize.y)),
mapLevel
};
}
int3 MapCache::getTileCenter() const
{
return getTileAtPoint(getViewCenter());
}
Point MapCache::getViewCenter() const
{
return viewCenter;
}
void MapCache::update(uint32_t timeDelta)
{
context->advanceAnimations(timeDelta);
Rect dimensions = getVisibleAreaTiles();
for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
for(int x = dimensions.left(); x < dimensions.right(); ++x)
updateTile({x, y, mapLevel});
}
void MapCache::render(Canvas & target)
{
update(GH.mainFPSmng->getElapsedMilliseconds());
Rect dimensions = getVisibleAreaTiles();
for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
{
for(int x = dimensions.left(); x < dimensions.right(); ++x)
{
Point topLeftOffset = viewCenter - targetDimensionsPixels / 2;
Point tilePosAbsolute = Point(x, y) * tileSize;
Point tilePosRelative = tilePosAbsolute - topLeftOffset;
Canvas source = getTile(int3(x, y, mapLevel));
target.draw(source, tilePosRelative);
}
}
}
MapView::MapView(const Point & offset, const Point & dimensions)
: tilesCache(new MapCache(Point(32, 32), dimensions))
, tileSize(Point(32, 32))
{
pos += offset;
pos.w = dimensions.x;
pos.h = dimensions.y;
}
Rect MapView::getVisibleAreaTiles() const
{
return tilesCache->getVisibleAreaTiles();
}
int3 MapView::getTileCenter() const
{
return tilesCache->getTileCenter();
}
int3 MapView::getTileAtPoint(const Point & position) const
{
return tilesCache->getTileAtPoint(position);
}
Point MapView::getViewCenter() const
{
return tilesCache->getViewCenter();
}
void MapView::setViewCenter(const int3 & position)
{
setViewCenter(Point(position.x, position.y) * tileSize, position.z);
}
void MapView::setViewCenter(const Point & position, int level)
{
tilesCache->setViewCenter(position, level);
}
void MapView::show(SDL_Surface * to)
{
Canvas target(to);
Canvas targetClipped(target, pos);
CSDL_Ext::CClipRectGuard guard(to, pos);
tilesCache->render(targetClipped);
}
void MapView::showAll(SDL_Surface * to)
{
show(to);
}
void MapRendererContext::advanceAnimations(uint32_t ms)
{
animationTime += ms;
}
int3 MapRendererContext::getMapSize() const
{
return LOCPLINT->cb->getMapSize();
}
bool MapRendererContext::isInMap(const int3 & coordinates) const
{
return LOCPLINT->cb->isInTheMap(coordinates);
}
const TerrainTile & MapRendererContext::getMapTile(const int3 & coordinates) const
{
return CGI->mh->map->getTile(coordinates);
}
MapRendererContext::ObjectsVector MapRendererContext::getAllObjects() const
{
return CGI->mh->map->objects;
}
const CGObjectInstance * MapRendererContext::getObject(ObjectInstanceID objectID) const
{
return CGI->mh->map->objects.at(objectID.getNum());
}
bool MapRendererContext::isVisible(const int3 & coordinates) const
{
return LOCPLINT->cb->isVisible(coordinates) || settings["session"]["spectate"].Bool();
}
MapRendererContext::VisibilityMap MapRendererContext::getVisibilityMap() const
{
return LOCPLINT->cb->getVisibilityMap();
}
uint32_t MapRendererContext::getAnimationPeriod() const
{
// H3 timing for adventure map objects animation is 180 ms
// Terrain animations also use identical interval, however it is only present in HotA and/or HD Mod
// TODO: duration of fade-in/fade-out for teleport, entering/leaving boat, removal of objects
// TOOD: duration of hero movement animation, frame timing of hero movement animation, effect of hero speed option
// TOOD: duration of enemy hero movement animation, frame timing of enemy hero movement animation, effect of enemy hero speed option
return 180;
}
uint32_t MapRendererContext::getAnimationTime() const
{
return animationTime;
}
Point MapRendererContext::tileSize() const
{
return Point(32, 32);
}
bool MapRendererContext::showGrid() const
{
return true; // settings["session"]["showGrid"].Bool();
}

View File

@ -0,0 +1,96 @@
/*
* MapView.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../gui/CIntObject.h"
#include "MapRendererContext.h"
class Canvas;
class MapRenderer;
class MapRendererContext : public IMapRendererContext
{
uint32_t animationTime = 0;
public:
void advanceAnimations(uint32_t ms);
int3 getMapSize() const override;
bool isInMap(const int3 & coordinates) const override;
const TerrainTile & getMapTile(const int3 & coordinates) const override;
ObjectsVector getAllObjects() const override;
const CGObjectInstance * getObject(ObjectInstanceID objectID) const override;
bool isVisible(const int3 & coordinates) const override;
VisibilityMap getVisibilityMap() const override;
uint32_t getAnimationPeriod() const override;
uint32_t getAnimationTime() const override;
Point tileSize() const override;
bool showGrid() const override;
};
class MapCache
{
std::unique_ptr<Canvas> terrain;
Point tileSize;
Point viewCenter;
Point viewDimensionsTiles;
Point viewDimensionsPixels;
Point targetDimensionsPixels;
int mapLevel;
std::unique_ptr<MapRendererContext> context;
std::unique_ptr<MapRenderer> mapRenderer;
Canvas getTile(const int3 & coordinates);
void updateTile(const int3 & coordinates);
public:
explicit MapCache(const Point & tileSize, const Point & dimensions);
~MapCache();
void setViewCenter(const Point & center, int level);
Rect getVisibleAreaTiles() const;
int3 getTileCenter() const;
int3 getTileAtPoint(const Point & position) const;
Point getViewCenter() const;
void update(uint32_t timeDelta);
void render(Canvas & target);
};
class MapView : public CIntObject
{
std::unique_ptr<MapCache> tilesCache;
Point tileSize;
public:
MapView(const Point & offset, const Point & dimensions);
Rect getVisibleAreaTiles() const;
Point getViewCenter() const;
int3 getTileCenter() const;
int3 getTileAtPoint(const Point & position) const;
void setViewCenter(const int3 & position);
void setViewCenter(const Point & position, int level);
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
};

View File

@ -14,11 +14,16 @@
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
#include "../render/CFadeAnimation.h" #include "../render/CFadeAnimation.h"
#include "../render/Colors.h" #include "../render/Colors.h"
#include "../gui/CGuiHandler.h"
#include "../renderSDL/SDL_Extensions.h" #include "../renderSDL/SDL_Extensions.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../render/Graphics.h" #include "../render/Graphics.h"
#include "../render/IImage.h" #include "../render/IImage.h"
#include "../render/Canvas.h"
#include "../CMusicHandler.h" #include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CObjectClassesHandler.h" #include "../../lib/mapObjects/CObjectClassesHandler.h"
@ -1483,4 +1488,5 @@ TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, Rect rect_,
TerrainTileObject::~TerrainTileObject() TerrainTileObject::~TerrainTileObject()
{ {
} }

View File

@ -14,6 +14,8 @@
#include "../../lib/spells/ViewSpellInt.h" #include "../../lib/spells/ViewSpellInt.h"
#include "../../lib/Rect.h" #include "../../lib/Rect.h"
#include "../gui/CIntObject.h"
#ifdef IN #ifdef IN
#undef IN #undef IN
#endif #endif
@ -346,7 +348,7 @@ public: //TODO: make private
boost::multi_array<TerrainTile2, 3> ttiles; //informations about map tiles [z][x][y] boost::multi_array<TerrainTile2, 3> ttiles; //informations about map tiles [z][x][y]
int3 sizes; //map size (x = width, y = height, z = number of levels) int3 sizes; //map size (x = width, y = height, z = number of levels)
const CMap * map; const CMap * map;
private:
// Max number of tiles that will fit in the map screen. Tiles // Max number of tiles that will fit in the map screen. Tiles
// can be partial on each edges. // can be partial on each edges.
int tilesW; int tilesW;

View File

@ -18,14 +18,14 @@
Canvas::Canvas(SDL_Surface * surface): Canvas::Canvas(SDL_Surface * surface):
surface(surface), surface(surface),
renderOffset(0,0) renderArea(0,0, surface->w, surface->h)
{ {
surface->refcount++; surface->refcount++;
} }
Canvas::Canvas(const Canvas & other): Canvas::Canvas(const Canvas & other):
surface(other.surface), surface(other.surface),
renderOffset(other.renderOffset) renderArea(other.renderArea)
{ {
surface->refcount++; surface->refcount++;
} }
@ -33,25 +33,23 @@ Canvas::Canvas(const Canvas & other):
Canvas::Canvas(const Canvas & other, const Rect & newClipRect): Canvas::Canvas(const Canvas & other, const Rect & newClipRect):
Canvas(other) Canvas(other)
{ {
clipRect.emplace(); //clipRect.emplace();
CSDL_Ext::getClipRect(surface, clipRect.get()); //CSDL_Ext::getClipRect(surface, clipRect.get());
Rect currClipRect = newClipRect + renderOffset; renderArea = other.renderArea.intersect(newClipRect + other.renderArea.topLeft());
CSDL_Ext::setClipRect(surface, currClipRect); //CSDL_Ext::setClipRect(surface, currClipRect);
renderOffset += newClipRect.topLeft();
} }
Canvas::Canvas(const Point & size): Canvas::Canvas(const Point & size):
renderOffset(0,0), renderArea(Point(0,0), size),
surface(CSDL_Ext::newSurface(size.x, size.y)) surface(CSDL_Ext::newSurface(size.x, size.y))
{ {
} }
Canvas::~Canvas() Canvas::~Canvas()
{ {
if (clipRect) //if (clipRect)
CSDL_Ext::setClipRect(surface, clipRect.get()); // CSDL_Ext::setClipRect(surface, clipRect.get());
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
} }
@ -60,19 +58,19 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
{ {
assert(image); assert(image);
if (image) if (image)
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y); image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y);
} }
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect) void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect)
{ {
assert(image); assert(image);
if (image) if (image)
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect); image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y, &sourceRect);
} }
void Canvas::draw(Canvas & image, const Point & pos) void Canvas::draw(Canvas & image, const Point & pos)
{ {
CSDL_Ext::blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface); CSDL_Ext::blitSurface(image.surface, image.renderArea, surface, renderArea.topLeft() + pos);
} }
void Canvas::draw(Canvas & image, const Point & pos, const Point & targetSize) void Canvas::draw(Canvas & image, const Point & pos, const Point & targetSize)
@ -88,17 +86,17 @@ void Canvas::drawPoint(const Point & dest, const ColorRGBA & color)
void Canvas::drawLine(const Point & from, const Point & dest, const ColorRGBA & colorFrom, const ColorRGBA & colorDest) void Canvas::drawLine(const Point & from, const Point & dest, const ColorRGBA & colorFrom, const ColorRGBA & colorDest)
{ {
CSDL_Ext::drawLine(surface, renderOffset + from, renderOffset + dest, CSDL_Ext::toSDL(colorFrom), CSDL_Ext::toSDL(colorDest)); CSDL_Ext::drawLine(surface, renderArea.topLeft() + from, renderArea.topLeft() + dest, CSDL_Ext::toSDL(colorFrom), CSDL_Ext::toSDL(colorDest));
} }
void Canvas::drawLineDashed(const Point & from, const Point & dest, const ColorRGBA & color) void Canvas::drawLineDashed(const Point & from, const Point & dest, const ColorRGBA & color)
{ {
CSDL_Ext::drawLineDashed(surface, renderOffset + from, renderOffset + dest, CSDL_Ext::toSDL(color)); CSDL_Ext::drawLineDashed(surface, renderArea.topLeft() + from, renderArea.topLeft() + dest, CSDL_Ext::toSDL(color));
} }
void Canvas::drawBorderDashed(const Rect & target, const ColorRGBA & color) void Canvas::drawBorderDashed(const Rect & target, const ColorRGBA & color)
{ {
Rect realTarget = target + renderOffset; Rect realTarget = target + renderArea.topLeft();
CSDL_Ext::drawLineDashed(surface, realTarget.topLeft(), realTarget.topRight(), CSDL_Ext::toSDL(color)); CSDL_Ext::drawLineDashed(surface, realTarget.topLeft(), realTarget.topRight(), CSDL_Ext::toSDL(color));
CSDL_Ext::drawLineDashed(surface, realTarget.bottomLeft(), realTarget.bottomRight(), CSDL_Ext::toSDL(color)); CSDL_Ext::drawLineDashed(surface, realTarget.bottomLeft(), realTarget.bottomRight(), CSDL_Ext::toSDL(color));
@ -110,9 +108,9 @@ void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Col
{ {
switch (alignment) switch (alignment)
{ {
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderOffset + position); case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderOffset + position); case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderOffset + position); case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderArea.topLeft() + position);
} }
} }
@ -120,9 +118,9 @@ void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Col
{ {
switch (alignment) switch (alignment)
{ {
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderOffset + position); case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderOffset + position); case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderArea.topLeft() + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderOffset + position); case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderArea.topLeft() + position);
} }
} }

View File

@ -27,8 +27,8 @@ class Canvas
/// Clip rect that was in use on surface originally and needs to be restored on destruction /// Clip rect that was in use on surface originally and needs to be restored on destruction
boost::optional<Rect> clipRect; boost::optional<Rect> clipRect;
/// Current rendering area offset, all rendering operations will be moved into selected area /// Current rendering area, all rendering operations will be moved into selected area
Point renderOffset; Rect renderArea;
Canvas & operator = (Canvas & other) = delete; Canvas & operator = (Canvas & other) = delete;
public: public:

View File

@ -28,7 +28,7 @@ public:
:x(X),y(Y) :x(X),y(Y)
{}; {};
Point(const int3 &a); explicit DLL_LINKAGE Point(const int3 &a);
template<typename T> template<typename T>
Point operator+(const T &b) const Point operator+(const T &b) const
@ -48,6 +48,11 @@ public:
return Point(x*mul, y*mul); return Point(x*mul, y*mul);
} }
Point operator*(const Point &b) const
{
return Point(x*b.x,y*b.y);
}
template<typename T> template<typename T>
Point& operator+=(const T &b) Point& operator+=(const T &b)
{ {

View File

@ -10,9 +10,16 @@
#include "StdInc.h" #include "StdInc.h"
#include "Rect.h" #include "Rect.h"
#include "int3.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
Point::Point(const int3 & a)
: x(a.x)
, y(a.x)
{
}
/// Returns rect union - rect that covers both this rect and provided rect /// Returns rect union - rect that covers both this rect and provided rect
Rect Rect::include(const Rect & other) const Rect Rect::include(const Rect & other) const
{ {