diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 52aa46d53..4bb9529b1 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -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 diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index b952c9e46..e95b03fd5 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -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 ) diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 68bd37e3f..c3dc81287 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -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 ENV, std::shared_ptr 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 diff --git a/client/adventureMap/CAdvMapInt.cpp b/client/adventureMap/CAdvMapInt.cpp index f5b6da634..b1317c6f7 100644 --- a/client/adventureMap/CAdvMapInt.cpp +++ b/client/adventureMap/CAdvMapInt.cpp @@ -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(CGI->mh->tilesW / 2 / worldViewScale); - on.y -= static_cast(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) diff --git a/client/adventureMap/CAdvMapInt.h b/client/adventureMap/CAdvMapInt.h index 25b07283e..4b934b45f 100644 --- a/client/adventureMap/CAdvMapInt.h +++ b/client/adventureMap/CAdvMapInt.h @@ -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; diff --git a/client/adventureMap/CMinimap.cpp b/client/adventureMap/CMinimap.cpp index efed856ca..e5f497df5 100644 --- a/client/adventureMap/CMinimap.cpp +++ b/client/adventureMap/CMinimap.cpp @@ -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)); } } diff --git a/client/adventureMap/CTerrainRect.cpp b/client/adventureMap/CTerrainRect.cpp index 095a65674..a1178921f 100644 --- a/client/adventureMap/CTerrainRect.cpp +++ b/client/adventureMap/CTerrainRect.cpp @@ -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( 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(swipeInitialRealPos.x - cursorPosition.x) / 32; - adventureInt->swipeTargetPosition.y = - swipeInitialMapPos.y + static_cast(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 & 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;inodes.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; +} diff --git a/client/adventureMap/CTerrainRect.h b/client/adventureMap/CTerrainRect.h index cd255fe69..c45ecf6ce 100644 --- a/client/adventureMap/CTerrainRect.h +++ b/client/adventureMap/CTerrainRect.h @@ -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 renderer; + SDL_Surface * fadeSurface; EMapAnimRedrawStatus lastRedrawStatus; std::shared_ptr 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(); }; - diff --git a/client/adventureMap/MapRenderer.cpp b/client/adventureMap/MapRenderer.cpp new file mode 100644 index 000000000..1cabf0fb8 --- /dev/null +++ b/client/adventureMap/MapRenderer.cpp @@ -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(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 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("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("TSHRC"); + fogOfWarFullHide->preload(); + fogOfWarPartialHide = std::make_unique("TSHRE"); + fogOfWarPartialHide->preload(); + + static const std::vector 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 MapRendererObjects::getAnimation(const CGObjectInstance* obj) +{ + const auto & info = obj->appearance; + + //the only(?) invisible object + if(info->id == Obj::EVENT) + return std::shared_ptr(); + + if(info->animationFile.empty()) + { + logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid); + return std::shared_ptr(); + } + + return getAnimation(info->animationFile); +} + +std::shared_ptr MapRendererObjects::getAnimation(const std::string & filename) +{ + if (animations.count(filename)) + return animations[filename]; + + auto ret = std::make_shared(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(obj.get())->inTownGarrison) + continue; + + if(obj->ID == Obj::BOAT && dynamic_cast(obj.get())->hero) + continue; + + std::shared_ptr 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 MapRendererObjects::getFlagAnimation(const CGObjectInstance* obj) +{ + //TODO: relocate to config file? + static const std::vector heroFlags = { + "AF00", "AF01", "AF02", "AF03", "AF04", "AF05", "AF06", "AF07" + }; + + //TODO: relocate to config file? + static const std::vector> 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(obj) != nullptr); + assert(obj->tempOwner.isValidPlayer()); + return getAnimation(heroFlags[obj->tempOwner.getNum()]); + } + if(obj->ID == Obj::BOAT) + { + const auto * boat = dynamic_cast(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 MapRendererObjects::getImage(const IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr& 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 moveGroups = {99, 10, 5, 6, 7, 8, 9, 12, 11}; + static const std::vector idleGroups = {99, 13, 0, 1, 2, 3, 4, 15, 14}; + + if(obj->ID == Obj::HERO) + { + const auto * hero = dynamic_cast(obj); + return idleGroups[hero->moveDir]; + } + + if(obj->ID == Obj::BOAT) + { + const auto * boat = dynamic_cast(obj); + return idleGroups[boat->direction]; + } + + return 0; +} + +void MapRendererObjects::renderImage(Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr& 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); +} diff --git a/client/adventureMap/MapRenderer.h b/client/adventureMap/MapRenderer.h new file mode 100644 index 000000000..ea5fc07d2 --- /dev/null +++ b/client/adventureMap/MapRenderer.h @@ -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, 4>; + std::vector animations; +public: + explicit MapTileStorage( size_t capacity); + void load(size_t index, const std::string& filename); + std::shared_ptr 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; + + boost::multi_array objects; + std::map> animations; + + std::shared_ptr getFlagAnimation(const CGObjectInstance* obj); + std::shared_ptr getAnimation(const CGObjectInstance* obj); + std::shared_ptr getAnimation(const std::string & filename); + + std::shared_ptr getImage(const IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr& 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& 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 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 fogOfWarFullHide; + std::unique_ptr 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); + +}; diff --git a/client/adventureMap/MapRendererContext.h b/client/adventureMap/MapRendererContext.h new file mode 100644 index 000000000..29e89f8d2 --- /dev/null +++ b/client/adventureMap/MapRendererContext.h @@ -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>; + using ObjectsVector = std::vector< ConstTransitivePtr >; + + 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; +}; diff --git a/client/adventureMap/MapView.cpp b/client/adventureMap/MapView.cpp new file mode 100644 index 000000000..9d24c6271 --- /dev/null +++ b/client/adventureMap/MapView.cpp @@ -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(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(std::floor(static_cast(visibleAreaPixels.left()) / tileSize.x)), + static_cast(std::floor(static_cast(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(std::floor(static_cast(absolutePosition.x) / tileSize.x)), + static_cast(std::floor(static_cast(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(); +} diff --git a/client/adventureMap/MapView.h b/client/adventureMap/MapView.h new file mode 100644 index 000000000..42c9cdf21 --- /dev/null +++ b/client/adventureMap/MapView.h @@ -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 terrain; + + Point tileSize; + Point viewCenter; + Point viewDimensionsTiles; + Point viewDimensionsPixels; + Point targetDimensionsPixels; + + int mapLevel; + + std::unique_ptr context; + std::unique_ptr 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 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; +}; diff --git a/client/adventureMap/mapHandler.cpp b/client/adventureMap/mapHandler.cpp index 4ca232fa3..55185f6d8 100644 --- a/client/adventureMap/mapHandler.cpp +++ b/client/adventureMap/mapHandler.cpp @@ -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() { + } diff --git a/client/adventureMap/mapHandler.h b/client/adventureMap/mapHandler.h index 61ec91fa5..465f5b53b 100644 --- a/client/adventureMap/mapHandler.h +++ b/client/adventureMap/mapHandler.h @@ -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 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; diff --git a/client/render/Canvas.cpp b/client/render/Canvas.cpp index 9e2dd7d94..003c36178 100644 --- a/client/render/Canvas.cpp +++ b/client/render/Canvas.cpp @@ -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 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 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); } } diff --git a/client/render/Canvas.h b/client/render/Canvas.h index 73c12986d..d6ca437c4 100644 --- a/client/render/Canvas.h +++ b/client/render/Canvas.h @@ -27,8 +27,8 @@ class Canvas /// Clip rect that was in use on surface originally and needs to be restored on destruction boost::optional 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: diff --git a/lib/Point.h b/lib/Point.h index f58ffac73..602c776af 100644 --- a/lib/Point.h +++ b/lib/Point.h @@ -28,7 +28,7 @@ public: :x(X),y(Y) {}; - Point(const int3 &a); + explicit DLL_LINKAGE Point(const int3 &a); template 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 Point& operator+=(const T &b) { diff --git a/lib/Rect.cpp b/lib/Rect.cpp index 28d164edc..fc87ef5de 100644 --- a/lib/Rect.cpp +++ b/lib/Rect.cpp @@ -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 {