MapRenderer.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /*
  2. * MapRenderer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "MapRenderer.h"
  12. #include "MapRendererContext.h"
  13. #include "mapHandler.h"
  14. #include "../CGameInfo.h"
  15. #include "../render/CAnimation.h"
  16. #include "../render/Canvas.h"
  17. #include "../render/IImage.h"
  18. #include "../../CCallback.h"
  19. #include "../../lib/CPathfinder.h"
  20. #include "../../lib/RiverHandler.h"
  21. #include "../../lib/RoadHandler.h"
  22. #include "../../lib/TerrainHandler.h"
  23. #include "../../lib/mapObjects/CGHeroInstance.h"
  24. #include "../../lib/mapping/CMap.h"
  25. struct NeighborTilesInfo
  26. {
  27. //567
  28. //3 4
  29. //012
  30. std::bitset<8> d;
  31. NeighborTilesInfo(const IMapRendererContext & context, const int3 & pos)
  32. {
  33. auto checkTile = [&](int dx, int dy)
  34. {
  35. return context.isVisible(pos + int3(dx, dy, 0));
  36. };
  37. // sides
  38. d[1] = checkTile(0, +1);
  39. d[3] = checkTile(-1, 0);
  40. d[4] = checkTile(+1, 0);
  41. d[6] = checkTile(0, -1);
  42. // corners - select visible image if either corner or adjacent sides are visible
  43. d[0] = d[1] || d[3] || checkTile(-1, +1);
  44. d[2] = d[1] || d[4] || checkTile(+1, +1);
  45. d[5] = d[3] || d[6] || checkTile(-1, -1);
  46. d[7] = d[4] || d[6] || checkTile(+1, -1);
  47. }
  48. bool areAllHidden() const
  49. {
  50. return d.none();
  51. }
  52. int getBitmapID() const
  53. {
  54. //NOTE: some images have unused in VCMI pair (same blockmap but a bit different look)
  55. // 0-1, 2-3, 4-5, 11-13, 12-14
  56. static const int visBitmaps[256] = {
  57. -1, 34, 4, 4, 22, 23, 4, 4, 36, 36, 38, 38, 47, 47, 38, 38, //16
  58. 3, 25, 12, 12, 3, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //32
  59. 35, 39, 48, 48, 41, 43, 48, 48, 36, 36, 38, 38, 47, 47, 38, 38, //48
  60. 26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //64
  61. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //80
  62. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //96
  63. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //112
  64. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //128
  65. 15, 17, 30, 30, 16, 19, 30, 30, 46, 46, 40, 40, 32, 32, 40, 40, //144
  66. 2, 25, 12, 12, 2, 25, 12, 12, 9, 9, 6, 6, 9, 9, 6, 6, //160
  67. 18, 42, 31, 31, 20, 21, 31, 31, 46, 46, 40, 40, 32, 32, 40, 40, //176
  68. 26, 49, 28, 28, 26, 49, 28, 28, 9, 9, 6, 6, 9, 9, 6, 6, //192
  69. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //208
  70. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10, //224
  71. 0, 45, 29, 29, 24, 33, 29, 29, 37, 37, 7, 7, 50, 50, 7, 7, //240
  72. 13, 27, 44, 44, 13, 27, 44, 44, 8, 8, 10, 10, 8, 8, 10, 10 //256
  73. };
  74. return visBitmaps[d.to_ulong()]; // >=0 -> partial hide, <0 - full hide
  75. }
  76. };
  77. MapTileStorage::MapTileStorage(size_t capacity)
  78. : animations(capacity)
  79. {
  80. }
  81. void MapTileStorage::load(size_t index, const std::string & filename)
  82. {
  83. auto & terrainAnimations = animations[index];
  84. for(auto & entry : terrainAnimations)
  85. {
  86. entry = std::make_unique<CAnimation>(filename);
  87. entry->preload();
  88. }
  89. for(size_t i = 0; i < terrainAnimations[0]->size(); ++i)
  90. {
  91. terrainAnimations[1]->getImage(i)->verticalFlip();
  92. terrainAnimations[3]->getImage(i)->verticalFlip();
  93. terrainAnimations[2]->getImage(i)->horizontalFlip();
  94. terrainAnimations[3]->getImage(i)->horizontalFlip();
  95. }
  96. }
  97. std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
  98. {
  99. const auto & animation = animations[fileIndex][rotationIndex];
  100. return animation->getImage(imageIndex);
  101. }
  102. MapRendererTerrain::MapRendererTerrain()
  103. : storage(VLC->terrainTypeHandler->objects.size())
  104. {
  105. for(const auto & terrain : VLC->terrainTypeHandler->objects)
  106. storage.load(terrain->getIndex(), terrain->tilesFilename);
  107. }
  108. void MapRendererTerrain::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  109. {
  110. const TerrainTile & mapTile = context.getMapTile(coordinates);
  111. int32_t terrainIndex = mapTile.terType->getIndex();
  112. int32_t imageIndex = mapTile.terView;
  113. int32_t rotationIndex = mapTile.extTileFlags % 4;
  114. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  115. if(mapTile.terType->getId() == ETerrainId::LAVA)
  116. {
  117. image->shiftPalette(246, 9, context.terrainImageIndex(9));
  118. }
  119. if(mapTile.terType->getId() == ETerrainId::WATER)
  120. {
  121. image->shiftPalette(229, 12, context.terrainImageIndex(12));
  122. image->shiftPalette(242, 14, context.terrainImageIndex(14));
  123. }
  124. image->setBlitMode(EImageBlitMode::OPAQUE);
  125. target.draw(image, Point(0, 0));
  126. }
  127. MapRendererRiver::MapRendererRiver()
  128. : storage(VLC->riverTypeHandler->objects.size())
  129. {
  130. for(const auto & river : VLC->riverTypeHandler->objects)
  131. storage.load(river->getIndex(), river->tilesFilename);
  132. }
  133. void MapRendererRiver::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  134. {
  135. const TerrainTile & mapTile = context.getMapTile(coordinates);
  136. if(mapTile.riverType->getId() == River::NO_RIVER)
  137. return;
  138. int32_t terrainIndex = mapTile.riverType->getIndex();
  139. int32_t imageIndex = mapTile.riverDir;
  140. int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
  141. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  142. if(mapTile.riverType->getId() == River::WATER_RIVER)
  143. {
  144. image->shiftPalette(183, 12, context.terrainImageIndex(12));
  145. image->shiftPalette(195, 6, context.terrainImageIndex(6));
  146. }
  147. if(mapTile.riverType->getId() == River::MUD_RIVER)
  148. {
  149. image->shiftPalette(228, 12, context.terrainImageIndex(12));
  150. image->shiftPalette(183, 6, context.terrainImageIndex(6));
  151. image->shiftPalette(240, 6, context.terrainImageIndex(6));
  152. }
  153. if(mapTile.riverType->getId() == River::LAVA_RIVER)
  154. {
  155. image->shiftPalette(240, 9, context.terrainImageIndex(9));
  156. }
  157. target.draw(image, Point(0, 0));
  158. }
  159. MapRendererRoad::MapRendererRoad()
  160. : storage(VLC->roadTypeHandler->objects.size())
  161. {
  162. for(const auto & road : VLC->roadTypeHandler->objects)
  163. storage.load(road->getIndex(), road->tilesFilename);
  164. }
  165. void MapRendererRoad::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  166. {
  167. const int3 coordinatesAbove = coordinates - int3(0, 1, 0);
  168. if(context.isInMap(coordinatesAbove))
  169. {
  170. const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove);
  171. if(mapTileAbove.roadType->getId() != Road::NO_ROAD)
  172. {
  173. int32_t terrainIndex = mapTileAbove.roadType->getIndex();
  174. int32_t imageIndex = mapTileAbove.roadDir;
  175. int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4;
  176. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  177. target.draw(image, Point(0, 0), Rect(0, 16, 32, 16));
  178. }
  179. }
  180. const TerrainTile & mapTile = context.getMapTile(coordinates);
  181. if(mapTile.roadType->getId() != Road::NO_ROAD)
  182. {
  183. int32_t terrainIndex = mapTile.roadType->getIndex();
  184. int32_t imageIndex = mapTile.roadDir;
  185. int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4;
  186. const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
  187. target.draw(image, Point(0, 16), Rect(0, 0, 32, 16));
  188. }
  189. }
  190. MapRendererBorder::MapRendererBorder()
  191. {
  192. animation = std::make_unique<CAnimation>("EDG");
  193. animation->preload();
  194. }
  195. size_t MapRendererBorder::getIndexForTile(const IMapRendererContext & context, const int3 & tile)
  196. {
  197. assert(!context.isInMap(tile));
  198. int3 size = context.getMapSize();
  199. if(tile.x < -1 || tile.x > size.x || tile.y < -1 || tile.y > size.y)
  200. return std::abs(tile.x) % 4 + 4 * (std::abs(tile.y) % 4);
  201. if(tile.x == -1 && tile.y == -1)
  202. return 16;
  203. if(tile.x == size.x && tile.y == -1)
  204. return 17;
  205. if(tile.x == size.x && tile.y == size.y)
  206. return 18;
  207. if(tile.x == -1 && tile.y == size.y)
  208. return 19;
  209. if(tile.y == -1)
  210. return 20 + (tile.x % 4);
  211. if(tile.x == size.x)
  212. return 24 + (tile.y % 4);
  213. if(tile.y == size.y)
  214. return 28 + (tile.x % 4);
  215. if(tile.x == -1)
  216. return 32 + (tile.y % 4);
  217. //else - visible area, no renderable border
  218. assert(0);
  219. return 0;
  220. }
  221. void MapRendererBorder::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  222. {
  223. const auto & image = animation->getImage(getIndexForTile(context, coordinates));
  224. target.draw(image, Point(0, 0));
  225. }
  226. MapRendererFow::MapRendererFow()
  227. {
  228. fogOfWarFullHide = std::make_unique<CAnimation>("TSHRC");
  229. fogOfWarFullHide->preload();
  230. fogOfWarPartialHide = std::make_unique<CAnimation>("TSHRE");
  231. fogOfWarPartialHide->preload();
  232. static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
  233. size_t size = fogOfWarPartialHide->size(0); //group size after next rotation
  234. for(const int rotation : rotations)
  235. {
  236. fogOfWarPartialHide->duplicateImage(0, rotation, 0);
  237. auto image = fogOfWarPartialHide->getImage(size, 0);
  238. image->verticalFlip();
  239. size++;
  240. }
  241. }
  242. void MapRendererFow::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  243. {
  244. assert(!context.isVisible(coordinates));
  245. const NeighborTilesInfo neighborInfo(context, coordinates);
  246. int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide
  247. if(retBitmapID < 0)
  248. {
  249. // generate a number that is predefined for each tile,
  250. // but appears random to break visible pattern in large areas of fow
  251. // current approach (use primes as magic numbers for formula) looks to be suitable
  252. size_t pseudorandomNumber = ((coordinates.x * 997) ^ (coordinates.y * 1009)) / 101;
  253. size_t imageIndex = pseudorandomNumber % fogOfWarFullHide->size();
  254. target.draw(fogOfWarFullHide->getImage(imageIndex), Point(0, 0));
  255. }
  256. else
  257. {
  258. target.draw(fogOfWarPartialHide->getImage(retBitmapID), Point(0, 0));
  259. }
  260. }
  261. std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectInstance* obj)
  262. {
  263. const auto & info = obj->appearance;
  264. //the only(?) invisible object
  265. if(info->id == Obj::EVENT)
  266. return std::shared_ptr<CAnimation>();
  267. if(info->animationFile.empty())
  268. {
  269. logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
  270. return std::shared_ptr<CAnimation>();
  271. }
  272. bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO);
  273. //TODO: relocate to config file?
  274. // Boat appearance files only contain single, unanimated image
  275. // proper boat animations are actually in different file
  276. static const std::vector<std::string> boatAnimations = {
  277. "AB01_.def", "AB02_.def", "AB03_.def"
  278. };
  279. if (info->id == Obj::BOAT)
  280. return getAnimation(boatAnimations[info->subid], generateMovementGroups);
  281. return getAnimation(info->animationFile, generateMovementGroups);
  282. }
  283. std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const std::string & filename, bool generateMovementGroups)
  284. {
  285. if(animations.count(filename))
  286. return animations[filename];
  287. auto ret = std::make_shared<CAnimation>(filename);
  288. animations[filename] = ret;
  289. ret->preload();
  290. if(generateMovementGroups)
  291. {
  292. ret->createFlippedGroup(1, 13);
  293. ret->createFlippedGroup(2, 14);
  294. ret->createFlippedGroup(3, 15);
  295. ret->createFlippedGroup(6, 10);
  296. ret->createFlippedGroup(7, 11);
  297. ret->createFlippedGroup(8, 12);
  298. }
  299. return ret;
  300. }
  301. std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectInstance* obj)
  302. {
  303. //TODO: relocate to config file?
  304. static const std::vector<std::string> heroFlags = {
  305. "AF00", "AF01", "AF02", "AF03", "AF04", "AF05", "AF06", "AF07"
  306. };
  307. //TODO: relocate to config file?
  308. static const std::vector<std::vector<std::string>> boatFlags = {
  309. {"ABF01L", "ABF01G", "ABF01R", "ABF01D", "ABF01B", "ABF01P", "ABF01W", "ABF01K"},
  310. {"ABF02L", "ABF02G", "ABF02R", "ABF02D", "ABF02B", "ABF02P", "ABF02W", "ABF02K"},
  311. {"ABF03L", "ABF03G", "ABF03R", "ABF03D", "ABF03B", "ABF03P", "ABF03W", "ABF03K"}
  312. };
  313. if(obj->ID == Obj::HERO)
  314. {
  315. assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
  316. assert(obj->tempOwner.isValidPlayer());
  317. return getAnimation(heroFlags[obj->tempOwner.getNum()], true);
  318. }
  319. if(obj->ID == Obj::BOAT)
  320. {
  321. const auto * boat = dynamic_cast<const CGBoat *>(obj);
  322. assert(boat->subID < boatFlags.size());
  323. if (boat->hero)
  324. {
  325. assert(boat->hero->tempOwner.isValidPlayer());
  326. return getAnimation(boatFlags[boat->subID][boat->hero->tempOwner.getNum()], true);
  327. }
  328. }
  329. return nullptr;
  330. }
  331. std::shared_ptr<CAnimation> MapRendererObjects::getOverlayAnimation(const CGObjectInstance * obj)
  332. {
  333. if(obj->ID == Obj::BOAT)
  334. {
  335. //TODO: relocate to config file?
  336. // Boats have additional animation with waves around boat
  337. static const std::vector<std::string> boatAnimations = {
  338. "ABM01_.def", "ABM02_.def", "ABM03_.def"
  339. };
  340. const auto * boat = dynamic_cast<const CGBoat *>(obj);
  341. if (boat->hero)
  342. return getAnimation(boatAnimations[obj->subID], true);
  343. }
  344. return nullptr;
  345. }
  346. std::shared_ptr<IImage> MapRendererObjects::getImage(const IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation>& animation) const
  347. {
  348. if(!animation)
  349. return nullptr;
  350. size_t groupIndex = context.objectGroupIndex(obj->id);
  351. if(animation->size(groupIndex) == 0)
  352. return nullptr;
  353. size_t frameIndex = context.objectImageIndex(obj->id, animation->size(groupIndex));
  354. return animation->getImage(frameIndex, groupIndex);
  355. }
  356. void MapRendererObjects::renderImage(const IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * object, const std::shared_ptr<IImage>& image)
  357. {
  358. if(!image)
  359. return;
  360. auto transparency = static_cast<uint8_t>(std::round(255 * context.objectTransparency(object->id)));
  361. if (transparency == 0)
  362. return;
  363. image->setAlpha(transparency);
  364. image->setFlagColor(object->tempOwner);
  365. Point offsetPixels = context.objectImageOffset(object->id, coordinates);
  366. if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
  367. {
  368. Point imagePos = image->dimensions() - offsetPixels - Point(32, 32);
  369. target.draw(image, Point(0, 0), Rect(imagePos, Point(32,32)));
  370. }
  371. }
  372. void MapRendererObjects::renderObject(const IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * instance)
  373. {
  374. renderImage(context, target, coordinates, instance, getImage(context, instance, getBaseAnimation(instance)));
  375. renderImage(context, target, coordinates, instance, getImage(context, instance, getFlagAnimation(instance)));
  376. renderImage(context, target, coordinates, instance, getImage(context, instance, getOverlayAnimation(instance)));
  377. }
  378. void MapRendererObjects::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  379. {
  380. for(const auto & objectID : context.getObjects(coordinates))
  381. {
  382. const auto * objectInstance = context.getObject(objectID);
  383. assert(objectInstance);
  384. if(!objectInstance)
  385. {
  386. logGlobal->error("Stray map object that isn't fading");
  387. continue;
  388. }
  389. renderObject(context, target, coordinates, objectInstance);
  390. }
  391. }
  392. MapRendererDebug::MapRendererDebug()
  393. : imageGrid(IImage::createFromFile("debug/grid"))
  394. , imageBlockable(IImage::createFromFile("debug/blocked"))
  395. , imageVisitable(IImage::createFromFile("debug/visitable"))
  396. {
  397. }
  398. void MapRendererDebug::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  399. {
  400. if(context.showGrid())
  401. target.draw(imageGrid, Point(0,0));
  402. if(context.showVisitable() || context.showBlockable())
  403. {
  404. bool blockable = false;
  405. bool visitable = false;
  406. for(const auto & objectID : context.getObjects(coordinates))
  407. {
  408. const auto * object = context.getObject(objectID);
  409. if (context.objectTransparency(objectID) > 0)
  410. {
  411. visitable |= object->visitableAt(coordinates.x, coordinates.y);
  412. blockable |= object->blockingAt(coordinates.x, coordinates.y);
  413. }
  414. }
  415. if (context.showBlockable() && blockable)
  416. target.draw(imageBlockable, Point(0,0));
  417. if (context.showVisitable() && visitable)
  418. target.draw(imageVisitable, Point(0,0));
  419. }
  420. }
  421. MapRendererPath::MapRendererPath()
  422. : pathNodes(new CAnimation("ADAG"))
  423. {
  424. pathNodes->preload();
  425. }
  426. void MapRendererPath::renderImage(Canvas & target, bool reachableToday, size_t imageIndex)
  427. {
  428. const static size_t unreachableTodayOffset = 25;
  429. if(reachableToday)
  430. target.draw(pathNodes->getImage(imageIndex), Point(0, 0));
  431. else
  432. target.draw(pathNodes->getImage(imageIndex + unreachableTodayOffset), Point(0, 0));
  433. }
  434. void MapRendererPath::renderImageCross(Canvas & target, bool reachableToday, const int3 & curr)
  435. {
  436. renderImage(target, reachableToday, 0);
  437. }
  438. void MapRendererPath::renderImageArrow(Canvas & target, bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next)
  439. {
  440. // Vector directions
  441. // 0 1 2
  442. // |
  443. // 3 - 4 - 5
  444. // |
  445. // 6 7 8
  446. //For example:
  447. // |
  448. // +->
  449. // is (directionToArrowIndex[7][5])
  450. //
  451. const static size_t directionToArrowIndex[9][9] = {
  452. {16, 17, 18, 7, 0, 19, 6, 5, 0 },
  453. {8, 9, 18, 7, 0, 19, 6, 0, 20},
  454. {8, 1, 10, 7, 0, 19, 0, 21, 20},
  455. {24, 17, 18, 15, 0, 0, 6, 5, 4 },
  456. {0, 0, 0, 0, 0, 0, 0, 0, 0 },
  457. {8, 1, 2, 0, 0, 11, 22, 21, 20},
  458. {24, 17, 0, 23, 0, 3, 14, 5, 4 },
  459. {24, 0, 2, 23, 0, 3, 22, 13, 4 },
  460. {0, 1, 2, 23, 0, 3, 22, 21, 12}
  461. };
  462. size_t enterDirection = (curr.x - next.x + 1) + 3 * (curr.y - next.y + 1);
  463. size_t leaveDirection = (prev.x - curr.x + 1) + 3 * (prev.y - curr.y + 1);
  464. size_t imageIndex = directionToArrowIndex[enterDirection][leaveDirection];
  465. renderImage(target, reachableToday, imageIndex);
  466. }
  467. void MapRendererPath::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  468. {
  469. const auto & functor = [&](const CGPathNode & node)
  470. {
  471. return node.coord == coordinates;
  472. };
  473. const auto * path = context.currentPath();
  474. if(!path)
  475. return;
  476. const auto & iter = boost::range::find_if(path->nodes, functor);
  477. if(iter == path->nodes.end())
  478. return;
  479. bool reachableToday = iter->turns == 0;
  480. if(iter == path->nodes.begin())
  481. renderImageCross(target, reachableToday, iter->coord);
  482. auto next = iter + 1;
  483. auto prev = iter - 1;
  484. // start of path - current hero location
  485. if(next == path->nodes.end())
  486. return;
  487. bool pathContinuous = iter->coord.areNeighbours(next->coord) && iter->coord.areNeighbours(prev->coord);
  488. bool embarking = iter->action == CGPathNode::EMBARK || iter->action == CGPathNode::DISEMBARK;
  489. if(pathContinuous && !embarking)
  490. renderImageArrow(target, reachableToday, iter->coord, prev->coord, next->coord);
  491. else
  492. renderImageCross(target, reachableToday, iter->coord);
  493. }
  494. void MapRenderer::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
  495. {
  496. if(!context.isInMap(coordinates))
  497. {
  498. rendererBorder.renderTile(context, target, coordinates);
  499. return;
  500. }
  501. const NeighborTilesInfo neighborInfo(context, coordinates);
  502. if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
  503. {
  504. rendererFow.renderTile(context, target, coordinates);
  505. }
  506. else
  507. {
  508. rendererTerrain.renderTile(context, target, coordinates);
  509. rendererRiver.renderTile(context, target, coordinates);
  510. rendererRoad.renderTile(context, target, coordinates);
  511. rendererObjects.renderTile(context, target, coordinates);
  512. rendererPath.renderTile(context, target, coordinates);
  513. rendererDebug.renderTile(context, target, coordinates);
  514. if(!context.isVisible(coordinates))
  515. rendererFow.renderTile(context, target, coordinates);
  516. }
  517. }