RenderHandler.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * RenderHandler.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 "RenderHandler.h"
  12. #include "SDLImage.h"
  13. #include "ScalableImage.h"
  14. #include "FontChain.h"
  15. #include "../GameEngine.h"
  16. #include "../render/AssetGenerator.h"
  17. #include "../render/CAnimation.h"
  18. #include "../render/CanvasImage.h"
  19. #include "../render/CDefFile.h"
  20. #include "../render/Colors.h"
  21. #include "../render/ColorFilter.h"
  22. #include "../render/IScreenHandler.h"
  23. #include "../render/hdEdition/HdImageLoader.h"
  24. #include "../../lib/AsyncRunner.h"
  25. #include "../../lib/CConfigHandler.h"
  26. #include "../../lib/CThreadHelper.h"
  27. #include "../../lib/ExceptionsCommon.h"
  28. #include "../../lib/VCMIDirs.h"
  29. #include "../../lib/constants/StringConstants.h"
  30. #include "../../lib/entities/building/CBuilding.h"
  31. #include "../../lib/entities/faction/CTown.h"
  32. #include "../../lib/entities/faction/CTownHandler.h"
  33. #include "../../lib/filesystem/Filesystem.h"
  34. #include "../../lib/json/JsonUtils.h"
  35. #include <vcmi/ArtifactService.h>
  36. #include <vcmi/CreatureService.h>
  37. #include <vcmi/Entity.h>
  38. #include <vcmi/FactionService.h>
  39. #include <vcmi/HeroTypeService.h>
  40. #include <vcmi/Services.h>
  41. #include <vcmi/SkillService.h>
  42. #include <vcmi/spells/Service.h>
  43. #include <vcmi/ResourceTypeService.h>
  44. RenderHandler::RenderHandler()
  45. :assetGenerator(std::make_unique<AssetGenerator>())
  46. {
  47. }
  48. RenderHandler::~RenderHandler() = default;
  49. std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath & path)
  50. {
  51. AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
  52. {
  53. std::lock_guard<std::mutex> lock(animationCacheMutex);
  54. auto it = animationFiles.find(actualPath);
  55. if (it != animationFiles.end())
  56. {
  57. auto locked = it->second.lock();
  58. if (locked)
  59. return locked;
  60. }
  61. }
  62. if (!CResourceHandler::get()->existsResource(actualPath))
  63. return nullptr;
  64. auto result = std::make_shared<CDefFile>(actualPath);
  65. auto entries = result->getEntries();
  66. {
  67. std::lock_guard<std::mutex> lock(animationCacheMutex);
  68. for(const auto& entry : entries)
  69. for(size_t i = 0; i < entry.second; ++i)
  70. animationSpriteDefs[actualPath][entry.first][i] = {result->getName(i, entry.first), result->getFrameInfo(i, entry.first)};
  71. animationFiles[actualPath] = result;
  72. }
  73. return result;
  74. }
  75. std::pair<std::string, CDefFile::SSpriteDef> RenderHandler::getAnimationSpriteDef(const AnimationPath & path, int frame, int group)
  76. {
  77. AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
  78. return animationSpriteDefs[actualPath][group][frame];
  79. }
  80. ImagePath RenderHandler::getAnimationFrameName(const AnimationPath & path, int frame, int group)
  81. {
  82. auto info = getAnimationSpriteDef(path, frame, group);
  83. auto frameName = info.first;
  84. boost::iterator_range<std::string::iterator> sub = boost::find_first(frameName, ".");
  85. if(!sub.empty())
  86. frameName = std::string(frameName.begin(), sub.begin());
  87. boost::to_upper(frameName);
  88. return ImagePath::builtin(frameName);
  89. }
  90. void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config, EImageBlitMode mode) const
  91. {
  92. std::string basepath;
  93. basepath = config["basepath"].String();
  94. JsonNode base;
  95. base["margins"] = config["margins"];
  96. base["width"] = config["width"];
  97. base["height"] = config["height"];
  98. for(const JsonNode & group : config["sequences"].Vector())
  99. {
  100. size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
  101. source[groupID].clear();
  102. for(const JsonNode & frame : group["frames"].Vector())
  103. {
  104. JsonNode toAdd = frame;
  105. JsonUtils::inherit(toAdd, base);
  106. toAdd["file"].String() = basepath + frame.String();
  107. if(group["generateShadow"].isNumber())
  108. toAdd["generateShadow"].Integer() = group["generateShadow"].Integer();
  109. if(group["generateOverlay"].isNumber())
  110. toAdd["generateOverlay"].Integer() = group["generateOverlay"].Integer();
  111. source[groupID].emplace_back(toAdd, mode);
  112. }
  113. }
  114. for(const JsonNode & node : config["images"].Vector())
  115. {
  116. size_t group = node["group"].Integer();
  117. size_t frame = node["frame"].Integer();
  118. if (source[group].size() <= frame)
  119. source[group].resize(frame+1);
  120. JsonNode toAdd = node;
  121. JsonUtils::inherit(toAdd, base);
  122. if (toAdd.Struct().count("file"))
  123. toAdd["file"].String() = basepath + node["file"].String();
  124. if (toAdd.Struct().count("defFile"))
  125. toAdd["defFile"].String() = basepath + node["defFile"].String();
  126. source[group][frame] = ImageLocator(toAdd, mode);
  127. }
  128. }
  129. RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode)
  130. {
  131. static constexpr std::array scaledSpritesPath = {
  132. "", // 0x
  133. "SPRITES/",
  134. "SPRITES2X/",
  135. "SPRITES3X/",
  136. "SPRITES4X/",
  137. };
  138. std::string pathString = path.getName();
  139. if (boost::starts_with(pathString, "SPRITES/"))
  140. pathString = pathString.substr(std::string("SPRITES/").length());
  141. AnimationPath actualPath = AnimationPath::builtin(scaledSpritesPath.at(scalingFactor) + pathString);
  142. auto it = animationLayouts.find(actualPath);
  143. if (it != animationLayouts.end() && (settings["video"]["useHdTextures"].Bool() || scalingFactor == 1))
  144. return it->second;
  145. AnimationLayoutMap result;
  146. auto defFile = getAnimationFile(actualPath);
  147. if(defFile)
  148. {
  149. const std::map<size_t, size_t> defEntries = defFile->getEntries();
  150. for (const auto & defEntry : defEntries)
  151. result[defEntry.first].resize(defEntry.second);
  152. }
  153. auto jsonResource = actualPath.toType<EResType::JSON>();
  154. auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource);
  155. for(auto & loader : configList)
  156. {
  157. auto stream = loader->load(jsonResource);
  158. std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
  159. stream->read(textData.get(), stream->getSize());
  160. const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), path.getOriginalName());
  161. initFromJson(result, config, mode);
  162. }
  163. animationLayouts[actualPath] = result;
  164. return animationLayouts[actualPath];
  165. }
  166. int RenderHandler::getScalingFactor() const
  167. {
  168. return ENGINE->screenHandler().getScalingFactor();
  169. }
  170. ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode)
  171. {
  172. const auto & layout = getAnimationLayout(path, scaling, mode);
  173. if (!layout.count(group))
  174. return ImageLocator();
  175. if (frame >= layout.at(group).size())
  176. return ImageLocator();
  177. const auto & locator = layout.at(group).at(frame);
  178. if (locator.image || locator.defFile)
  179. return locator;
  180. return ImageLocator(path, frame, group, mode);
  181. }
  182. std::shared_ptr<ScalableImageShared> RenderHandler::loadImageImpl(const ImageLocator & locator)
  183. {
  184. auto it = imageFiles.find(locator);
  185. if (it != imageFiles.end())
  186. {
  187. auto locked = it->second.lock();
  188. if (locked)
  189. return locked;
  190. }
  191. auto sdlImage = loadImageFromFileUncached(locator);
  192. auto scaledImage = std::make_shared<ScalableImageShared>(locator, sdlImage);
  193. storeCachedImage(locator, scaledImage);
  194. return scaledImage;
  195. }
  196. std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const ImageLocator & locator)
  197. {
  198. if(locator.image)
  199. {
  200. auto imagePath = *locator.image;
  201. auto imagePathSprites = imagePath.addPrefix("SPRITES/");
  202. auto imagePathData = imagePath.addPrefix("DATA/");
  203. if(CResourceHandler::get()->existsResource(imagePathSprites))
  204. return std::make_shared<SDLImageShared>(imagePathSprites);
  205. if(CResourceHandler::get()->existsResource(imagePathData))
  206. return std::make_shared<SDLImageShared>(imagePathData);
  207. if(CResourceHandler::get()->existsResource(imagePath))
  208. return std::make_shared<SDLImageShared>(imagePath);
  209. auto generated = assetGenerator->generateImage(imagePath);
  210. if (generated)
  211. {
  212. generated->setAsyncUpscale(false); // do not async upscale base image for generated images -> fixes #6201
  213. return generated;
  214. }
  215. logGlobal->error("Failed to load image %s", locator.image->getOriginalName());
  216. return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"));
  217. }
  218. if(locator.defFile)
  219. {
  220. auto defFile = getAnimationFile(*locator.defFile);
  221. if(defFile && defFile->hasFrame(locator.defFrame, locator.defGroup))
  222. {
  223. auto img = std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup);
  224. auto pathForDefFrame = getAnimationFrameName(*locator.defFile, locator.defFrame, locator.defGroup);
  225. if(hdImageLoader->exists(pathForDefFrame))
  226. img->setAsyncUpscale(false); // avoids flickering graphics when hd textures are enabled
  227. return img;
  228. }
  229. else
  230. {
  231. logGlobal->error("Frame %d in group %d not found in file: %s",
  232. locator.defFrame, locator.defGroup, locator.defFile->getName().c_str());
  233. return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"));
  234. }
  235. }
  236. throw std::runtime_error("Invalid image locator received!");
  237. }
  238. void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr<ScalableImageShared> image)
  239. {
  240. imageFiles[locator] = image;
  241. }
  242. std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocator & locator)
  243. {
  244. static constexpr std::array scaledDataPath = {
  245. "", // 0x
  246. "DATA/",
  247. "DATA2X/",
  248. "DATA3X/",
  249. "DATA4X/",
  250. };
  251. static constexpr std::array scaledSpritesPath = {
  252. "", // 0x
  253. "SPRITES/",
  254. "SPRITES2X/",
  255. "SPRITES3X/",
  256. "SPRITES4X/",
  257. };
  258. ImagePath pathToLoad;
  259. Point defMargins(0, 0);
  260. Point defFullSize(0, 0);
  261. if(locator.defFile)
  262. {
  263. auto remappedLocator = getLocatorForAnimationFrame(*locator.defFile, locator.defFrame, locator.defGroup, locator.scalingFactor, locator.layer);
  264. // we expect that .def's are only used for 1x data, upscaled assets should use standalone images
  265. if (!remappedLocator.image)
  266. {
  267. if(!settings["video"]["useHdTextures"].Bool() || locator.scalingFactor == 1)
  268. return nullptr;
  269. if(!CResourceHandler::get("core")->existsResource(*locator.defFile)) // HD mod supports only core
  270. return nullptr;
  271. auto info = getAnimationSpriteDef(*locator.defFile, locator.defFrame, locator.defGroup);
  272. defMargins = Point(info.second.leftMargin, info.second.topMargin);
  273. defFullSize = Point(info.second.fullWidth, info.second.fullHeight);
  274. auto pathForDefFrame = getAnimationFrameName(*locator.defFile, locator.defFrame, locator.defGroup);
  275. if(hdImageLoader->exists(pathForDefFrame))
  276. pathToLoad = pathForDefFrame;
  277. }
  278. else
  279. pathToLoad = *remappedLocator.image;
  280. }
  281. if(locator.image)
  282. pathToLoad = *locator.image;
  283. if (pathToLoad.empty())
  284. return nullptr;
  285. std::string imagePathString = pathToLoad.getName();
  286. auto imagePathOriginal = ImagePath::builtin(imagePathString);
  287. bool generateShadow = locator.generateShadow && (*locator.generateShadow) != SharedImageLocator::ShadowMode::SHADOW_NONE;
  288. bool generateOverlay = locator.generateOverlay && (*locator.generateOverlay) != SharedImageLocator::OverlayMode::OVERLAY_NONE;
  289. bool isShadow = locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION || locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR;
  290. bool isOverlay = locator.layer == EImageBlitMode::ONLY_FLAG_COLOR || locator.layer == EImageBlitMode::ONLY_SELECTION;
  291. bool optimizeImage = !(isShadow && generateShadow) && !(isOverlay && generateOverlay); // images needs to expanded
  292. bool overlay = isOverlay && !generateOverlay;
  293. bool shadow = isShadow && !generateShadow;
  294. if(overlay)
  295. imagePathString += "-OVERLAY";
  296. if(shadow)
  297. imagePathString += "-SHADOW";
  298. if(locator.playerColored.isValidPlayer())
  299. imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]);
  300. if(locator.playerColored == PlayerColor::NEUTRAL)
  301. imagePathString += "-NEUTRAL";
  302. auto imagePath = ImagePath::builtin(imagePathString);
  303. auto imagePathSprites = ImagePath::builtin(imagePathString).addPrefix(scaledSpritesPath.at(locator.scalingFactor));
  304. auto imagePathData = ImagePath::builtin(imagePathString).addPrefix(scaledDataPath.at(locator.scalingFactor));
  305. std::shared_ptr<SDLImageShared> img = nullptr;
  306. if(!img && CResourceHandler::get()->existsResource(imagePathSprites) && (settings["video"]["useHdTextures"].Bool() || locator.scalingFactor == 1))
  307. img = std::make_shared<SDLImageShared>(imagePathSprites, optimizeImage);
  308. if(!img && CResourceHandler::get()->existsResource(imagePathData) && (settings["video"]["useHdTextures"].Bool() || locator.scalingFactor == 1))
  309. img = std::make_shared<SDLImageShared>(imagePathData, optimizeImage);
  310. if(!img && hdImageLoader->exists(imagePathOriginal) && settings["video"]["useHdTextures"].Bool() && locator.scalingFactor > 1)
  311. {
  312. if((!isOverlay || !isShadow) || overlay || shadow)
  313. img = hdImageLoader->getImage(imagePathOriginal, defFullSize, defMargins, shadow, overlay);
  314. }
  315. if(!img && CResourceHandler::get()->existsResource(imagePath))
  316. img = std::make_shared<SDLImageShared>(imagePath, optimizeImage);
  317. if(!img && locator.scalingFactor == 1)
  318. img = std::dynamic_pointer_cast<SDLImageShared>(assetGenerator->generateImage(imagePath));
  319. if(img)
  320. {
  321. // TODO: Performance improvement - Run algorithm on optimized ("trimmed") images
  322. // Not implemented yet because different frame image sizes seems to cause wobbeling shadow -> needs a way around this
  323. if(isShadow && generateShadow)
  324. img = img->drawShadow((*locator.generateShadow) == SharedImageLocator::ShadowMode::SHADOW_SHEAR);
  325. if(isOverlay && generateOverlay && (*locator.generateOverlay) == SharedImageLocator::OverlayMode::OVERLAY_OUTLINE)
  326. img = img->drawOutline(Colors::WHITE, 1);
  327. if(locator.scalingFactor == 1)
  328. img->setAsyncUpscale(false); // no base image, needs to be done in sync
  329. }
  330. return img;
  331. }
  332. std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator)
  333. {
  334. ImageLocator adjustedLocator = locator;
  335. if(locator.image)
  336. {
  337. std::vector<std::string> splitted;
  338. boost::split(splitted, (*locator.image).getOriginalName(), boost::is_any_of(":"));
  339. if(splitted.size() == 3)
  340. {
  341. // allows image from def file with following filename (first group, then frame): "deffile.def:0:5"
  342. adjustedLocator.defFile = AnimationPath::builtin(splitted[0]);
  343. adjustedLocator.defGroup = std::stoi(splitted[1]);
  344. adjustedLocator.defFrame = std::stoi(splitted[2]);
  345. adjustedLocator.image = std::nullopt;
  346. }
  347. }
  348. std::shared_ptr<ScalableImageInstance> result;
  349. if (adjustedLocator.scalingFactor == 0)
  350. {
  351. auto scaledLocator = adjustedLocator;
  352. scaledLocator.scalingFactor = getScalingFactor();
  353. result = loadImageImpl(scaledLocator)->createImageReference();
  354. }
  355. else
  356. result = loadImageImpl(adjustedLocator)->createImageReference();
  357. if (locator.horizontalFlip)
  358. result->horizontalFlip();
  359. if (locator.verticalFlip)
  360. result->verticalFlip();
  361. return result;
  362. }
  363. std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
  364. {
  365. ImageLocator locator = getLocatorForAnimationFrame(path, frame, group, 1, mode);
  366. if (!locator.empty())
  367. return loadImage(locator);
  368. else
  369. {
  370. logGlobal->error("Failed to load non-existing image");
  371. return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode));
  372. }
  373. }
  374. std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
  375. {
  376. auto name = path.getOriginalName();
  377. std::vector<std::string> splitted;
  378. boost::split(splitted, name, boost::is_any_of(":"));
  379. if(splitted.size() == 3)
  380. {
  381. // allows image from def file with following filename (first group, then frame): "deffile.def:0:5"
  382. ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(splitted[0]), std::stoi(splitted[2]), std::stoi(splitted[1]), 1, mode);
  383. return loadImage(locator);
  384. }
  385. ImageLocator locator(path, mode);
  386. return loadImage(locator);
  387. }
  388. std::shared_ptr<CanvasImage> RenderHandler::createImage(const Point & size, CanvasScalingPolicy scalingPolicy)
  389. {
  390. return std::make_shared<CanvasImage>(size, scalingPolicy);
  391. }
  392. std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)
  393. {
  394. return std::make_shared<CAnimation>(path, getAnimationLayout(path, 1, mode), mode);
  395. }
  396. void RenderHandler::addImageListEntries(const EntityService * service)
  397. {
  398. service->forEachBase([this](const Entity * entity, bool & stop)
  399. {
  400. entity->registerIcons([this](size_t index, size_t group, const std::string & listName, const std::string & imageName)
  401. {
  402. if (imageName.empty())
  403. return;
  404. auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::COLORKEY);
  405. JsonNode entry;
  406. entry["file"].String() = imageName;
  407. if (index >= layout[group].size())
  408. layout[group].resize(index + 1);
  409. layout[group][index] = ImageLocator(entry, EImageBlitMode::SIMPLE);
  410. });
  411. });
  412. }
  413. static void detectOverlappingBuildings(RenderHandler * renderHandler, const Faction * factionBase)
  414. {
  415. if (!factionBase->hasTown())
  416. return;
  417. auto faction = dynamic_cast<const CFaction*>(factionBase);
  418. for (const auto & left : faction->town->clientInfo.structures)
  419. {
  420. for (const auto & right : faction->town->clientInfo.structures)
  421. {
  422. if (left->identifier <= right->identifier)
  423. continue; // only a<->b comparison is needed, not a<->a or b<->a
  424. if (left->building && right->building && left->building->getBase() == right->building->getBase())
  425. {
  426. if (left->pos.z != right->pos.z)
  427. logMod->warn("Town %s: Upgrades of same building have different z-index: '%s' and '%s'", faction->getJsonKey(), left->identifier, right->identifier);
  428. continue; // upgrades of the same buildings are expected to overlap
  429. }
  430. if (left->pos.z != right->pos.z)
  431. continue; // buildings already have different z-index and have well-defined overlap logic
  432. auto leftImage = renderHandler->loadImage(left->defName, 0, 0, EImageBlitMode::COLORKEY);
  433. auto rightImage = renderHandler->loadImage(right->defName, 0, 0, EImageBlitMode::COLORKEY);
  434. Rect leftRect( left->pos.x, left->pos.y, leftImage->width(), leftImage->height());
  435. Rect rightRect( right->pos.x, right->pos.y, rightImage->width(), rightImage->height());
  436. Rect intersection = leftRect.intersect(rightRect);
  437. Point intersectionPosition;
  438. bool intersectionFound = false;
  439. for (int y = 0; y < intersection.h && !intersectionFound; ++y)
  440. {
  441. for (int x = 0; x < intersection.w && !intersectionFound; ++x)
  442. {
  443. Point leftPoint = Point(x,y) - leftRect.topLeft() + intersection.topLeft();
  444. Point rightPoint = Point(x,y) - rightRect.topLeft() + intersection.topLeft();
  445. if (!leftImage->isTransparent(leftPoint) && !rightImage->isTransparent(rightPoint))
  446. {
  447. intersectionFound = true;
  448. intersectionPosition = intersection.topLeft() + Point(x,y);
  449. }
  450. }
  451. }
  452. if (intersectionFound)
  453. logMod->warn("Town %s: Detected overlapping buildings '%s' and '%s' at (%d, %d) with same z-index!", faction->getJsonKey(), left->identifier, right->identifier, intersectionPosition.x, intersectionPosition.y);
  454. }
  455. }
  456. };
  457. void RenderHandler::onLibraryLoadingFinished(const Services * services)
  458. {
  459. hdImageLoader = std::make_unique<HdImageLoader>(); // needs to initialize after class construction because we need loaded screenHandler for getScalingFactor()
  460. assert(animationLayouts.empty());
  461. assetGenerator->initialize();
  462. updateGeneratedAssets();
  463. addImageListEntries(services->creatures());
  464. addImageListEntries(services->heroTypes());
  465. addImageListEntries(services->artifacts());
  466. addImageListEntries(services->factions());
  467. addImageListEntries(services->spells());
  468. addImageListEntries(services->skills());
  469. addImageListEntries(services->resources());
  470. if (settings["mods"]["validation"].String() == "full")
  471. {
  472. services->factions()->forEach([this](const Faction * factionBase, bool & stop)
  473. {
  474. detectOverlappingBuildings(this, factionBase);
  475. });
  476. }
  477. }
  478. std::shared_ptr<const IFont> RenderHandler::loadFont(EFonts font)
  479. {
  480. if (fonts.count(font))
  481. return fonts.at(font);
  482. const int8_t index = static_cast<int8_t>(font);
  483. logGlobal->debug("Loading font %d", static_cast<int>(index));
  484. auto configList = CResourceHandler::get()->getResourcesWithName(JsonPath::builtin("config/fonts.json"));
  485. std::shared_ptr<FontChain> loadedFont = std::make_shared<FontChain>();
  486. std::string bitmapPath;
  487. for(auto & loader : configList)
  488. {
  489. auto stream = loader->load(JsonPath::builtin("config/fonts.json"));
  490. std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
  491. stream->read(textData.get(), stream->getSize());
  492. const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), "config/fonts.json");
  493. const JsonVector & bmpConf = config["bitmap"].Vector();
  494. const JsonNode & ttfConf = config["trueType"];
  495. bitmapPath = bmpConf[index].String();
  496. if (!ttfConf[bitmapPath].isNull())
  497. loadedFont->addTrueTypeFont(ttfConf[bitmapPath], !config["lowPriority"].Bool());
  498. }
  499. loadedFont->addBitmapFont(bitmapPath);
  500. fonts[font] = loadedFont;
  501. return loadedFont;
  502. }
  503. void RenderHandler::exportGeneratedAssets()
  504. {
  505. for (const auto & entry : assetGenerator->generateAllImages())
  506. entry.second->exportBitmap(VCMIDirs::get().userDataPath() / "Generated" / (entry.first.getOriginalName() + ".png"), nullptr);
  507. }
  508. std::shared_ptr<AssetGenerator> RenderHandler::getAssetGenerator()
  509. {
  510. return assetGenerator;
  511. }
  512. void RenderHandler::updateGeneratedAssets()
  513. {
  514. for(const auto & [key, value] : assetGenerator->generateAllAnimations())
  515. animationLayouts[key] = value;
  516. }