1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +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/CResDataBar.cpp
adventureMap/CTerrainRect.cpp
adventureMap/MapRenderer.cpp
adventureMap/MapView.cpp
adventureMap/mapHandler.cpp
battle/BattleActionsController.cpp
@ -121,6 +123,9 @@ set(client_HEADERS
adventureMap/CMinimap.h
adventureMap/CResDataBar.h
adventureMap/CTerrainRect.h
adventureMap/MapRenderer.h
adventureMap/MapRendererContext.h
adventureMap/MapView.h
adventureMap/mapHandler.h
battle/BattleActionsController.h

View File

@ -475,19 +475,6 @@ void CPlayerInterface::openTownWindow(const CGTownInstance * town)
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()
{
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->moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32;
//adventureInt->terrain->moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32;
//adventureInt->terrain->moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32;
}
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 updateInfo(const CGObjectInstance * specific);
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
// show dialogs

View File

@ -90,11 +90,11 @@ CAdvMapInt::CAdvMapInt():
resdatabar(new CResDataBar),
terrain(new CTerrainRect),
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),
activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false),
swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
swipeTargetPosition(int3(-1, -1, -1))
swipeTargetPosition(Point(0, 0))
{
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
@ -285,16 +285,16 @@ void CAdvMapInt::fswitchLevel()
if (maxLevels < 2)
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();
worldViewUnderground->setIndex(position.z, true);
worldViewUnderground->setIndex(terrain->getLevel(), true);
worldViewUnderground->redraw();
redrawOnNextFrame = true;
minimap->setLevel(position.z);
minimap->setLevel(terrain->getLevel());
if (mode == EAdvMapMode::WORLD_VIEW)
terrain->redraw();
@ -520,8 +520,6 @@ void CAdvMapInt::showAll(SDL_Surface * to)
infoBar->showAll(to);
break;
case EAdvMapMode::WORLD_VIEW:
terrain->showAll(to);
break;
}
activeMapPanel->showAll(to);
@ -594,25 +592,15 @@ void CAdvMapInt::show(SDL_Surface * to)
}
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++)
gems[i]->showAll(to);
redrawOnNextFrame=false;
LOCPLINT->cingconsole->show(to);
}
else
{
terrain->showAnim(to);
for(int i = 0; i < 4; i++)
gems[i]->showAll(to);
}
terrain->show(to);
for(int i = 0; i < 4; i++)
gems[i]->showAll(to);
infoBar->show(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((animValHitCount % (4 / scrollSpeed)) == 0)
{
if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW))
position.x--;
if(scrollingDir & LEFT)
terrain->moveViewBy(Point(-4, 0));
if((scrollingDir & RIGHT) && (position.x < CGI->mh->map->width - CGI->mh->tilesW + CGI->mh->frameW))
position.x++;
if(scrollingDir & RIGHT)
terrain->moveViewBy(Point(+4, 0));
if((scrollingDir & UP) && (position.y > -CGI->mh->frameH))
position.y--;
if(scrollingDir & UP)
terrain->moveViewBy(Point(0, -4));
if((scrollingDir & DOWN) && (position.y < CGI->mh->map->height - CGI->mh->tilesH + CGI->mh->frameH))
position.y++;
if(scrollingDir & DOWN)
terrain->moveViewBy(Point(0, +4));
if(scrollingDir)
{
setScrollingCursor(scrollingDir);
scrollingState = true;
redrawOnNextFrame = true;
minimap->redraw();
if(mode == EAdvMapMode::WORLD_VIEW)
terrain->redraw();
}
else if(scrollingState)
{
@ -657,9 +641,7 @@ void CAdvMapInt::handleSwipeUpdate()
{
if(swipeMovementRequested)
{
auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
position.x = fixedPos.x;
position.y = fixedPos.y;
terrain->setViewCenter(swipeTargetPosition, terrain->getLevel());
CCS->curh->set(Cursor::Map::POINTER);
redrawOnNextFrame = true;
minimap->redraw();
@ -676,37 +658,22 @@ void CAdvMapInt::selectionChanged()
void CAdvMapInt::centerOn(int3 on, bool fade)
{
bool switchedLevels = on.z != position.z;
bool switchedLevels = on.z != terrain->getLevel();
if (fade)
{
terrain->fadeFromCurrentView();
}
switch (mode)
{
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;
}
terrain->setViewCenter(on);
on = LOCPLINT->repairScreenPos(on);
position = on;
redrawOnNextFrame=true;
underground->setIndex(on.z,true); //change underground switch button image
underground->redraw();
worldViewUnderground->setIndex(on.z, true);
worldViewUnderground->redraw();
if (switchedLevels)
minimap->setLevel(position.z);
minimap->setLevel(terrain->getLevel());
minimap->redraw();
if (mode == EAdvMapMode::WORLD_VIEW)

View File

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

View File

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

View File

@ -11,6 +11,7 @@
#include "CTerrainRect.h"
#include "mapHandler.h"
#include "MapView.h"
#include "CAdvMapInt.h"
#include "../CGameInfo.h"
@ -40,14 +41,15 @@ CTerrainRect::CTerrainRect()
curHoveredTile(-1,-1,-1),
currentPath(nullptr)
{
tilesw=(ADVOPT.advmapW+31)/32;
tilesh=(ADVOPT.advmapH+31)/32;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.x=ADVOPT.advmapX;
pos.y=ADVOPT.advmapY;
pos.w=ADVOPT.advmapW;
pos.h=ADVOPT.advmapH;
moveX = moveY = 0;
addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
renderer = std::make_shared<MapView>( Point(0,0), pos.dimensions() );
}
CTerrainRect::~CTerrainRect()
@ -56,6 +58,16 @@ CTerrainRect::~CTerrainRect()
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()
{
CIntObject::deactivate();
@ -143,10 +155,8 @@ void CTerrainRect::handleSwipeMove(const Point & cursorPosition)
if(isSwiping)
{
adventureInt->swipeTargetPosition.x =
swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - cursorPosition.x) / 32;
adventureInt->swipeTargetPosition.y =
swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - cursorPosition.y) / 32;
adventureInt->swipeTargetPosition.x = swipeInitialViewPos.x + swipeInitialRealPos.x - cursorPosition.x;
adventureInt->swipeTargetPosition.y = swipeInitialViewPos.y + swipeInitialRealPos.y - cursorPosition.y;
adventureInt->swipeMovementRequested = true;
}
}
@ -156,7 +166,7 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
if(btnPressed)
{
swipeInitialRealPos = Point(GH.getCursorPosition().x, GH.getCursorPosition().y);
swipeInitialMapPos = int3(adventureInt->position);
swipeInitialViewPos = getViewCenter();
return true;
}
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)
{
int3 tHovered = whichTileIsIt(cursorPosition.x, cursorPosition.y);
int3 tHovered = whichTileIsIt(cursorPosition);
int3 pom = adventureInt->verifyPos(tHovered);
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)
{
/*
const static int pns[9][9] = {
{16, 17, 18, 7, -1, 19, 6, 5, -1},
{ 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;
std::vector<CGPathNode> & cv = currentPath->nodes;
/* Vector directions
* 0 1 2
* \ | /
* 3 - 4 - 5
* / | \
* 6 7 8
*For example:
* |
* |__\
* /
* is id1=7, id2=5 (pns[7][5])
*/
// Vector directions
// 0 1 2
// \ | /
// 3 - 4 - 5
// / | \
// 6 7 8
//For example:
// |
// |__\
// /
// is id1=7, id2=5 (pns[7][5])
//
bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
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++)
}
*/}
/*
void CTerrainRect::show(SDL_Surface * to)
{
if (adventureInt->mode == EAdvMapMode::NORMAL)
@ -338,7 +349,7 @@ void CTerrainRect::show(SDL_Surface * to)
fadeAnim->draw(to, r.topLeft());
}
if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path
if (currentPath) //drawing path
{
showPath(pos, to);
}
@ -366,33 +377,20 @@ void CTerrainRect::showAnim(SDL_Surface * to)
if (fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED)
show(to);
}
int3 CTerrainRect::whichTileIsIt(const int x, const int y)
*/
int3 CTerrainRect::whichTileIsIt(const Point &position)
{
int3 ret;
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;
return renderer->getTileAtPoint(position - pos);
}
int3 CTerrainRect::whichTileIsIt()
{
return whichTileIsIt(GH.getCursorPosition().x, GH.getCursorPosition().y);
return whichTileIsIt(GH.getCursorPosition());
}
Rect CTerrainRect::visibleTilesArea()
{
switch (adventureInt->mode)
{
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);
}
return renderer->getVisibleAreaTiles();
}
void CTerrainRect::fadeFromCurrentView()
@ -413,3 +411,27 @@ bool CTerrainRect::needsAnimUpdate()
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;
class CFadeAnimation;
class MapView;
/// Holds information about which tiles of the terrain are shown/not shown at the screen
class CTerrainRect : public CIntObject
{
std::shared_ptr<MapView> renderer;
SDL_Surface * fadeSurface;
EMapAnimRedrawStatus lastRedrawStatus;
std::shared_ptr<CFadeAnimation> fadeAnim;
int3 swipeInitialMapPos;
Point swipeInitialViewPos;
Point swipeInitialRealPos;
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 handleSwipeMove(const Point & cursorPosition);
@ -37,19 +45,26 @@ class CTerrainRect : public CIntObject
bool handleSwipeStateChange(bool btnPressed);
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
void showPath(const Rect &extRect, SDL_Surface * to);
bool needsAnimUpdate();
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;
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
void deactivate() override;
void clickLeft(tribool down, bool previousState) override;
@ -57,10 +72,10 @@ public:
void clickMiddle(tribool down, bool previousState) override;
void hover(bool on) override;
void mouseMoved (const Point & cursorPosition) override;
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
//void show(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
Rect visibleTilesArea();
@ -68,4 +83,3 @@ public:
/// animates view by caching current surface and crossfading it with normal screen
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/CFadeAnimation.h"
#include "../render/Colors.h"
#include "../gui/CGuiHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../CGameInfo.h"
#include "../render/Graphics.h"
#include "../render/IImage.h"
#include "../render/Canvas.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CObjectClassesHandler.h"
@ -1483,4 +1488,5 @@ TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, Rect rect_,
TerrainTileObject::~TerrainTileObject()
{
}

View File

@ -14,6 +14,8 @@
#include "../../lib/spells/ViewSpellInt.h"
#include "../../lib/Rect.h"
#include "../gui/CIntObject.h"
#ifdef IN
#undef IN
#endif
@ -346,7 +348,7 @@ public: //TODO: make private
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)
const CMap * map;
private:
// Max number of tiles that will fit in the map screen. Tiles
// can be partial on each edges.
int tilesW;

View File

@ -18,14 +18,14 @@
Canvas::Canvas(SDL_Surface * surface):
surface(surface),
renderOffset(0,0)
renderArea(0,0, surface->w, surface->h)
{
surface->refcount++;
}
Canvas::Canvas(const Canvas & other):
surface(other.surface),
renderOffset(other.renderOffset)
renderArea(other.renderArea)
{
surface->refcount++;
}
@ -33,25 +33,23 @@ Canvas::Canvas(const Canvas & other):
Canvas::Canvas(const Canvas & other, const Rect & newClipRect):
Canvas(other)
{
clipRect.emplace();
CSDL_Ext::getClipRect(surface, clipRect.get());
//clipRect.emplace();
//CSDL_Ext::getClipRect(surface, clipRect.get());
Rect currClipRect = newClipRect + renderOffset;
CSDL_Ext::setClipRect(surface, currClipRect);
renderOffset += newClipRect.topLeft();
renderArea = other.renderArea.intersect(newClipRect + other.renderArea.topLeft());
//CSDL_Ext::setClipRect(surface, currClipRect);
}
Canvas::Canvas(const Point & size):
renderOffset(0,0),
renderArea(Point(0,0), size),
surface(CSDL_Ext::newSurface(size.x, size.y))
{
}
Canvas::~Canvas()
{
if (clipRect)
CSDL_Ext::setClipRect(surface, clipRect.get());
//if (clipRect)
// CSDL_Ext::setClipRect(surface, clipRect.get());
SDL_FreeSurface(surface);
}
@ -60,19 +58,19 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
{
assert(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)
{
assert(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)
{
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)
@ -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)
{
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)
{
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)
{
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.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)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderOffset + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderOffset + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (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, renderArea.topLeft() + 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)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderOffset + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderOffset + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (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, renderArea.topLeft() + 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
boost::optional<Rect> clipRect;
/// Current rendering area offset, all rendering operations will be moved into selected area
Point renderOffset;
/// Current rendering area, all rendering operations will be moved into selected area
Rect renderArea;
Canvas & operator = (Canvas & other) = delete;
public:

View File

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

View File

@ -10,9 +10,16 @@
#include "StdInc.h"
#include "Rect.h"
#include "int3.h"
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
Rect Rect::include(const Rect & other) const
{