minimap_generator.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #include "minimap_generator.h"
  2. #include <QColor>
  3. #include <QLinearGradient>
  4. #include <QPainter>
  5. #include <QPainterPath>
  6. #include <QPen>
  7. #include <QRadialGradient>
  8. #include <algorithm>
  9. #include <cmath>
  10. #include <random>
  11. namespace Game::Map::Minimap {
  12. namespace {
  13. constexpr float k_camera_yaw_cos = -0.70710678118F;
  14. constexpr float k_camera_yaw_sin = -0.70710678118F;
  15. namespace Palette {
  16. constexpr QColor PARCHMENT_BASE{235, 220, 190};
  17. constexpr QColor PARCHMENT_LIGHT{245, 235, 215};
  18. constexpr QColor PARCHMENT_DARK{200, 180, 150};
  19. constexpr QColor PARCHMENT_STAIN{180, 160, 130, 40};
  20. constexpr QColor INK_DARK{45, 35, 25};
  21. constexpr QColor INK_MEDIUM{80, 65, 50};
  22. constexpr QColor INK_LIGHT{120, 100, 80};
  23. constexpr QColor MOUNTAIN_SHADOW{95, 80, 65};
  24. constexpr QColor MOUNTAIN_FACE{140, 125, 105};
  25. constexpr QColor MOUNTAIN_HIGHLIGHT{180, 165, 145};
  26. constexpr QColor HILL_BASE{160, 145, 120};
  27. constexpr QColor WATER_DARK{55, 95, 130};
  28. constexpr QColor WATER_MAIN{75, 120, 160};
  29. constexpr QColor WATER_LIGHT{100, 145, 180};
  30. constexpr QColor ROAD_MAIN{130, 105, 75};
  31. constexpr QColor ROAD_HIGHLIGHT{165, 140, 110};
  32. constexpr QColor STRUCTURE_STONE{160, 150, 135};
  33. constexpr QColor STRUCTURE_SHADOW{100, 85, 70};
  34. constexpr QColor TEAM_BLUE{65, 105, 165};
  35. constexpr QColor TEAM_BLUE_DARK{40, 65, 100};
  36. constexpr QColor TEAM_RED{175, 65, 55};
  37. constexpr QColor TEAM_RED_DARK{110, 40, 35};
  38. } // namespace Palette
  39. auto hash_coords(int x, int y, int seed = 0) -> float {
  40. const int n = x + y * 57 + seed * 131;
  41. const int shifted = (n << 13) ^ n;
  42. return 1.0F -
  43. static_cast<float>(
  44. (shifted * (shifted * shifted * 15731 + 789221) + 1376312589) &
  45. 0x7fffffff) /
  46. 1073741824.0F;
  47. }
  48. } // namespace
  49. MinimapGenerator::MinimapGenerator() : m_config() {}
  50. MinimapGenerator::MinimapGenerator(const Config &config) : m_config(config) {}
  51. auto MinimapGenerator::generate(const MapDefinition &map_def) -> QImage {
  52. const int img_width =
  53. static_cast<int>(map_def.grid.width * m_config.pixels_per_tile);
  54. const int img_height =
  55. static_cast<int>(map_def.grid.height * m_config.pixels_per_tile);
  56. QImage image(img_width, img_height, QImage::Format_RGBA8888);
  57. image.fill(Palette::PARCHMENT_BASE);
  58. render_parchment_background(image);
  59. render_terrain_base(image, map_def);
  60. render_terrain_features(image, map_def);
  61. render_rivers(image, map_def);
  62. render_roads(image, map_def);
  63. render_bridges(image, map_def);
  64. render_structures(image, map_def);
  65. apply_historical_styling(image);
  66. return image;
  67. }
  68. auto MinimapGenerator::world_to_pixel(float world_x, float world_z,
  69. const GridDefinition &grid) const
  70. -> std::pair<float, float> {
  71. const float rotated_x =
  72. world_x * k_camera_yaw_cos - world_z * k_camera_yaw_sin;
  73. const float rotated_z =
  74. world_x * k_camera_yaw_sin + world_z * k_camera_yaw_cos;
  75. const float world_width = grid.width * grid.tile_size;
  76. const float world_height = grid.height * grid.tile_size;
  77. const float img_width = grid.width * m_config.pixels_per_tile;
  78. const float img_height = grid.height * m_config.pixels_per_tile;
  79. const float px = (rotated_x + world_width * 0.5F) * (img_width / world_width);
  80. const float py =
  81. (rotated_z + world_height * 0.5F) * (img_height / world_height);
  82. return {px, py};
  83. }
  84. auto MinimapGenerator::world_to_pixel_size(
  85. float world_size, const GridDefinition &grid) const -> float {
  86. return (world_size / grid.tile_size) * m_config.pixels_per_tile;
  87. }
  88. void MinimapGenerator::render_parchment_background(QImage &image) {
  89. QPainter painter(&image);
  90. for (int y = 0; y < image.height(); ++y) {
  91. for (int x = 0; x < image.width(); ++x) {
  92. const float noise = hash_coords(x / 3, y / 3, 42) * 0.08F;
  93. QColor pixel = Palette::PARCHMENT_BASE;
  94. int r = pixel.red() + static_cast<int>(noise * 20);
  95. int g = pixel.green() + static_cast<int>(noise * 18);
  96. int b = pixel.blue() + static_cast<int>(noise * 15);
  97. pixel.setRgb(std::clamp(r, 0, 255), std::clamp(g, 0, 255),
  98. std::clamp(b, 0, 255));
  99. image.setPixelColor(x, y, pixel);
  100. }
  101. }
  102. painter.setRenderHint(QPainter::Antialiasing, true);
  103. std::mt19937 rng(12345);
  104. std::uniform_real_distribution<float> dist_x(
  105. 0.0F, static_cast<float>(image.width()));
  106. std::uniform_real_distribution<float> dist_y(
  107. 0.0F, static_cast<float>(image.height()));
  108. std::uniform_real_distribution<float> dist_size(5.0F, 25.0F);
  109. std::uniform_real_distribution<float> dist_alpha(0.02F, 0.06F);
  110. const int num_stains = (image.width() * image.height()) / 8000;
  111. for (int i = 0; i < num_stains; ++i) {
  112. const float cx = dist_x(rng);
  113. const float cy = dist_y(rng);
  114. const float radius = dist_size(rng);
  115. const float alpha = dist_alpha(rng);
  116. QRadialGradient stain(cx, cy, radius);
  117. QColor stain_color = Palette::PARCHMENT_STAIN;
  118. stain_color.setAlphaF(static_cast<double>(alpha));
  119. stain.setColorAt(0, stain_color);
  120. stain.setColorAt(1, Qt::transparent);
  121. painter.setBrush(stain);
  122. painter.setPen(Qt::NoPen);
  123. painter.drawEllipse(QPointF(cx, cy), radius, radius);
  124. }
  125. }
  126. void MinimapGenerator::render_terrain_base(QImage &image,
  127. const MapDefinition &map_def) {
  128. QPainter painter(&image);
  129. painter.setRenderHint(QPainter::Antialiasing, true);
  130. const QColor biome_color = biome_to_base_color(map_def.biome);
  131. painter.setCompositionMode(QPainter::CompositionMode_Multiply);
  132. painter.setOpacity(0.15);
  133. painter.fillRect(image.rect(), biome_color);
  134. painter.setOpacity(1.0);
  135. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  136. }
  137. void MinimapGenerator::render_terrain_features(QImage &image,
  138. const MapDefinition &map_def) {
  139. QPainter painter(&image);
  140. painter.setRenderHint(QPainter::Antialiasing, true);
  141. for (const auto &feature : map_def.terrain) {
  142. const auto [px, py] =
  143. world_to_pixel(feature.center_x, feature.center_z, map_def.grid);
  144. float pixel_width = world_to_pixel_size(feature.width, map_def.grid);
  145. float pixel_depth = world_to_pixel_size(feature.depth, map_def.grid);
  146. constexpr float MIN_FEATURE_SIZE = 4.0F;
  147. pixel_width = std::max(pixel_width, MIN_FEATURE_SIZE);
  148. pixel_depth = std::max(pixel_depth, MIN_FEATURE_SIZE);
  149. if (feature.type == TerrainType::Mountain) {
  150. draw_mountain_symbol(painter, px, py, pixel_width, pixel_depth);
  151. } else if (feature.type == TerrainType::Hill) {
  152. draw_hill_symbol(painter, px, py, pixel_width, pixel_depth);
  153. }
  154. }
  155. }
  156. void MinimapGenerator::draw_mountain_symbol(QPainter &painter, float cx,
  157. float cy, float width,
  158. float height) {
  159. const float peak_height = height * 0.6F;
  160. const float base_width = width * 0.5F;
  161. QPainterPath shadow_path;
  162. shadow_path.moveTo(cx, cy - peak_height);
  163. shadow_path.lineTo(cx - base_width, cy + height * 0.3F);
  164. shadow_path.lineTo(cx, cy + height * 0.1F);
  165. shadow_path.closeSubpath();
  166. painter.setBrush(Palette::MOUNTAIN_SHADOW);
  167. painter.setPen(Qt::NoPen);
  168. painter.drawPath(shadow_path);
  169. QPainterPath lit_path;
  170. lit_path.moveTo(cx, cy - peak_height);
  171. lit_path.lineTo(cx + base_width, cy + height * 0.3F);
  172. lit_path.lineTo(cx, cy + height * 0.1F);
  173. lit_path.closeSubpath();
  174. painter.setBrush(Palette::MOUNTAIN_FACE);
  175. painter.drawPath(lit_path);
  176. QPainterPath snow_path;
  177. snow_path.moveTo(cx, cy - peak_height);
  178. snow_path.lineTo(cx - base_width * 0.3F, cy - peak_height * 0.5F);
  179. snow_path.lineTo(cx + base_width * 0.2F, cy - peak_height * 0.6F);
  180. snow_path.closeSubpath();
  181. painter.setBrush(Palette::MOUNTAIN_HIGHLIGHT);
  182. painter.drawPath(snow_path);
  183. painter.setBrush(Qt::NoBrush);
  184. painter.setPen(QPen(Palette::INK_MEDIUM, 0.8));
  185. QPainterPath outline;
  186. outline.moveTo(cx - base_width, cy + height * 0.3F);
  187. outline.lineTo(cx, cy - peak_height);
  188. outline.lineTo(cx + base_width, cy + height * 0.3F);
  189. painter.drawPath(outline);
  190. }
  191. void MinimapGenerator::draw_hill_symbol(QPainter &painter, float cx, float cy,
  192. float width, float height) {
  193. const float hill_height = height * 0.35F;
  194. const float base_width = width * 0.6F;
  195. QPainterPath hill_path;
  196. hill_path.moveTo(cx - base_width, cy + hill_height * 0.2F);
  197. hill_path.quadTo(cx - base_width * 0.3F, cy - hill_height, cx,
  198. cy - hill_height);
  199. hill_path.quadTo(cx + base_width * 0.3F, cy - hill_height, cx + base_width,
  200. cy + hill_height * 0.2F);
  201. hill_path.closeSubpath();
  202. QLinearGradient gradient(cx - base_width, cy, cx + base_width, cy);
  203. gradient.setColorAt(0.0, Palette::MOUNTAIN_SHADOW);
  204. gradient.setColorAt(0.4, Palette::HILL_BASE);
  205. gradient.setColorAt(1.0, Palette::MOUNTAIN_FACE);
  206. painter.setBrush(gradient);
  207. painter.setPen(QPen(Palette::INK_LIGHT, 0.6));
  208. painter.drawPath(hill_path);
  209. }
  210. void MinimapGenerator::render_rivers(QImage &image,
  211. const MapDefinition &map_def) {
  212. if (map_def.rivers.empty()) {
  213. return;
  214. }
  215. QPainter painter(&image);
  216. painter.setRenderHint(QPainter::Antialiasing, true);
  217. for (const auto &river : map_def.rivers) {
  218. const auto [x1, y1] =
  219. world_to_pixel(river.start.x(), river.start.z(), map_def.grid);
  220. const auto [x2, y2] =
  221. world_to_pixel(river.end.x(), river.end.z(), map_def.grid);
  222. float pixel_width = world_to_pixel_size(river.width, map_def.grid);
  223. pixel_width = std::max(pixel_width, 1.5F);
  224. draw_river_segment(painter, x1, y1, x2, y2, pixel_width);
  225. }
  226. }
  227. void MinimapGenerator::draw_river_segment(QPainter &painter, float x1, float y1,
  228. float x2, float y2, float width) {
  229. QPainterPath river_path;
  230. river_path.moveTo(x1, y1);
  231. const float dx = x2 - x1;
  232. const float dy = y2 - y1;
  233. const float length = std::sqrt(dx * dx + dy * dy);
  234. if (length > 10.0F) {
  235. const float mid_x = (x1 + x2) * 0.5F;
  236. const float mid_y = (y1 + y2) * 0.5F;
  237. const float perp_x = -dy / length;
  238. const float perp_y = dx / length;
  239. const float wave_amount =
  240. hash_coords(static_cast<int>(x1), static_cast<int>(y1)) * width * 0.5F;
  241. river_path.quadTo(mid_x + perp_x * wave_amount,
  242. mid_y + perp_y * wave_amount, x2, y2);
  243. } else {
  244. river_path.lineTo(x2, y2);
  245. }
  246. QPen outline_pen(Palette::WATER_DARK);
  247. outline_pen.setWidthF(width * 1.4F);
  248. outline_pen.setCapStyle(Qt::RoundCap);
  249. outline_pen.setJoinStyle(Qt::RoundJoin);
  250. painter.setPen(outline_pen);
  251. painter.setBrush(Qt::NoBrush);
  252. painter.drawPath(river_path);
  253. QPen main_pen(Palette::WATER_MAIN);
  254. main_pen.setWidthF(width);
  255. main_pen.setCapStyle(Qt::RoundCap);
  256. main_pen.setJoinStyle(Qt::RoundJoin);
  257. painter.setPen(main_pen);
  258. painter.drawPath(river_path);
  259. if (width > 2.0F) {
  260. QPen highlight_pen(Palette::WATER_LIGHT);
  261. highlight_pen.setWidthF(width * 0.4F);
  262. highlight_pen.setCapStyle(Qt::RoundCap);
  263. painter.setPen(highlight_pen);
  264. painter.drawPath(river_path);
  265. }
  266. }
  267. void MinimapGenerator::render_roads(QImage &image,
  268. const MapDefinition &map_def) {
  269. if (map_def.roads.empty()) {
  270. return;
  271. }
  272. QPainter painter(&image);
  273. painter.setRenderHint(QPainter::Antialiasing, true);
  274. for (const auto &road : map_def.roads) {
  275. const auto [x1, y1] =
  276. world_to_pixel(road.start.x(), road.start.z(), map_def.grid);
  277. const auto [x2, y2] =
  278. world_to_pixel(road.end.x(), road.end.z(), map_def.grid);
  279. float pixel_width = world_to_pixel_size(road.width, map_def.grid);
  280. pixel_width = std::max(pixel_width, 1.5F);
  281. draw_road_segment(painter, x1, y1, x2, y2, pixel_width);
  282. }
  283. }
  284. void MinimapGenerator::draw_road_segment(QPainter &painter, float x1, float y1,
  285. float x2, float y2, float width) {
  286. QPen road_pen(Palette::ROAD_MAIN);
  287. road_pen.setWidthF(width);
  288. road_pen.setCapStyle(Qt::RoundCap);
  289. QVector<qreal> dash_pattern;
  290. dash_pattern << 3.0 << 2.0;
  291. road_pen.setDashPattern(dash_pattern);
  292. painter.setPen(road_pen);
  293. painter.drawLine(QPointF(x1, y1), QPointF(x2, y2));
  294. const float dx = x2 - x1;
  295. const float dy = y2 - y1;
  296. const float length = std::sqrt(dx * dx + dy * dy);
  297. if (length > 8.0F) {
  298. painter.setPen(Qt::NoPen);
  299. painter.setBrush(Palette::ROAD_HIGHLIGHT);
  300. const int num_dots = static_cast<int>(length / 6.0F);
  301. for (int i = 1; i < num_dots; ++i) {
  302. const float t = static_cast<float>(i) / static_cast<float>(num_dots);
  303. const float dot_x = x1 + dx * t;
  304. const float dot_y = y1 + dy * t;
  305. painter.drawEllipse(QPointF(dot_x, dot_y), width * 0.25F, width * 0.25F);
  306. }
  307. }
  308. }
  309. void MinimapGenerator::render_bridges(QImage &image,
  310. const MapDefinition &map_def) {
  311. if (map_def.bridges.empty()) {
  312. return;
  313. }
  314. QPainter painter(&image);
  315. painter.setRenderHint(QPainter::Antialiasing, true);
  316. for (const auto &bridge : map_def.bridges) {
  317. const auto [x1, y1] =
  318. world_to_pixel(bridge.start.x(), bridge.start.z(), map_def.grid);
  319. const auto [x2, y2] =
  320. world_to_pixel(bridge.end.x(), bridge.end.z(), map_def.grid);
  321. float pixel_width = world_to_pixel_size(bridge.width, map_def.grid);
  322. pixel_width = std::max(pixel_width, 2.0F);
  323. painter.setPen(QPen(Palette::INK_DARK, 1.0));
  324. painter.setBrush(Palette::STRUCTURE_STONE);
  325. const float dx = x2 - x1;
  326. const float dy = y2 - y1;
  327. const float length = std::sqrt(dx * dx + dy * dy);
  328. if (length > 0.01F) {
  329. const float perp_x = -dy / length * pixel_width * 0.5F;
  330. const float perp_y = dx / length * pixel_width * 0.5F;
  331. QPolygonF bridge_poly;
  332. bridge_poly << QPointF(x1 - perp_x, y1 - perp_y)
  333. << QPointF(x1 + perp_x, y1 + perp_y)
  334. << QPointF(x2 + perp_x, y2 + perp_y)
  335. << QPointF(x2 - perp_x, y2 - perp_y);
  336. painter.drawPolygon(bridge_poly);
  337. painter.setPen(QPen(Palette::INK_LIGHT, 0.5));
  338. const int num_planks = static_cast<int>(length / 3.0F);
  339. for (int i = 1; i < num_planks; ++i) {
  340. const float t = static_cast<float>(i) / static_cast<float>(num_planks);
  341. const float plank_x = x1 + dx * t;
  342. const float plank_y = y1 + dy * t;
  343. painter.drawLine(QPointF(plank_x - perp_x, plank_y - perp_y),
  344. QPointF(plank_x + perp_x, plank_y + perp_y));
  345. }
  346. }
  347. }
  348. }
  349. void MinimapGenerator::render_structures(QImage &image,
  350. const MapDefinition &map_def) {
  351. if (map_def.spawns.empty()) {
  352. return;
  353. }
  354. QPainter painter(&image);
  355. painter.setRenderHint(QPainter::Antialiasing, true);
  356. for (const auto &spawn : map_def.spawns) {
  357. if (!Game::Units::isBuildingSpawn(spawn.type)) {
  358. continue;
  359. }
  360. const auto [px, py] = world_to_pixel(spawn.x, spawn.z, map_def.grid);
  361. QColor fill_color = Palette::STRUCTURE_STONE;
  362. QColor border_color = Palette::STRUCTURE_SHADOW;
  363. if (spawn.player_id == 1) {
  364. fill_color = Palette::TEAM_BLUE;
  365. border_color = Palette::TEAM_BLUE_DARK;
  366. } else if (spawn.player_id == 2) {
  367. fill_color = Palette::TEAM_RED;
  368. border_color = Palette::TEAM_RED_DARK;
  369. } else if (spawn.player_id > 0) {
  370. const int hue = (spawn.player_id * 47 + 30) % 360;
  371. fill_color.setHsv(hue, 140, 180);
  372. border_color.setHsv(hue, 180, 100);
  373. }
  374. draw_fortress_icon(painter, px, py, fill_color, border_color);
  375. }
  376. }
  377. void MinimapGenerator::draw_fortress_icon(QPainter &painter, float cx, float cy,
  378. const QColor &fill,
  379. const QColor &border) {
  380. constexpr float SIZE = 10.0F;
  381. constexpr float HALF = SIZE * 0.5F;
  382. painter.setBrush(fill);
  383. painter.setPen(QPen(border, 1.5));
  384. painter.drawRect(
  385. QRectF(cx - HALF * 0.7F, cy - HALF * 0.7F, SIZE * 0.7F, SIZE * 0.7F));
  386. constexpr float TOWER_SIZE = SIZE * 0.35F;
  387. constexpr float TOWER_OFFSET = HALF * 0.85F;
  388. painter.setBrush(fill);
  389. painter.setPen(QPen(border, 1.0));
  390. for (int i = 0; i < 4; ++i) {
  391. const float tx = cx + ((i & 1) != 0 ? TOWER_OFFSET : -TOWER_OFFSET);
  392. const float ty = cy + ((i & 2) != 0 ? TOWER_OFFSET : -TOWER_OFFSET);
  393. painter.drawRect(QRectF(tx - TOWER_SIZE * 0.5F, ty - TOWER_SIZE * 0.5F,
  394. TOWER_SIZE, TOWER_SIZE));
  395. }
  396. painter.setBrush(border);
  397. painter.setPen(Qt::NoPen);
  398. painter.drawRect(
  399. QRectF(cx - SIZE * 0.12F, cy + SIZE * 0.15F, SIZE * 0.24F, SIZE * 0.25F));
  400. constexpr float MERLON_W = SIZE * 0.15F;
  401. constexpr float MERLON_H = SIZE * 0.12F;
  402. painter.setBrush(fill);
  403. painter.setPen(QPen(border, 0.8));
  404. for (int i = 0; i < 3; ++i) {
  405. const float mx = cx - SIZE * 0.25F + static_cast<float>(i) * SIZE * 0.25F;
  406. const float my = cy - HALF * 0.7F - MERLON_H;
  407. painter.drawRect(QRectF(mx, my, MERLON_W, MERLON_H));
  408. }
  409. }
  410. void MinimapGenerator::apply_historical_styling(QImage &image) {
  411. QPainter painter(&image);
  412. painter.setRenderHint(QPainter::Antialiasing, true);
  413. draw_map_border(painter, image.width(), image.height());
  414. apply_vignette(painter, image.width(), image.height());
  415. draw_compass_rose(painter, image.width(), image.height());
  416. }
  417. void MinimapGenerator::draw_map_border(QPainter &painter, int width,
  418. int height) {
  419. constexpr float OUTER_MARGIN = 2.0F;
  420. constexpr float INNER_MARGIN = 5.0F;
  421. painter.setPen(QPen(Palette::INK_MEDIUM, 1.5));
  422. painter.setBrush(Qt::NoBrush);
  423. painter.drawRect(QRectF(OUTER_MARGIN, OUTER_MARGIN,
  424. static_cast<float>(width) - OUTER_MARGIN * 2,
  425. static_cast<float>(height) - OUTER_MARGIN * 2));
  426. painter.setPen(QPen(Palette::INK_LIGHT, 0.8));
  427. painter.drawRect(QRectF(INNER_MARGIN, INNER_MARGIN,
  428. static_cast<float>(width) - INNER_MARGIN * 2,
  429. static_cast<float>(height) - INNER_MARGIN * 2));
  430. }
  431. void MinimapGenerator::apply_vignette(QPainter &painter, int width,
  432. int height) {
  433. const float radius = static_cast<float>(std::max(width, height)) * 0.75F;
  434. QRadialGradient vignette(static_cast<float>(width) * 0.5F,
  435. static_cast<float>(height) * 0.5F, radius);
  436. vignette.setColorAt(0.0, Qt::transparent);
  437. vignette.setColorAt(0.7, Qt::transparent);
  438. vignette.setColorAt(1.0, QColor(60, 45, 30, 35));
  439. painter.setCompositionMode(QPainter::CompositionMode_Multiply);
  440. painter.fillRect(0, 0, width, height, vignette);
  441. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  442. }
  443. void MinimapGenerator::draw_compass_rose(QPainter &painter, int width,
  444. int height) {
  445. const float cx = static_cast<float>(width) - 18.0F;
  446. const float cy = static_cast<float>(height) - 18.0F;
  447. constexpr float SIZE = 10.0F;
  448. painter.setPen(QPen(Palette::INK_MEDIUM, 1.2));
  449. painter.setBrush(Qt::NoBrush);
  450. QPainterPath north_arrow;
  451. north_arrow.moveTo(cx, cy - SIZE);
  452. north_arrow.lineTo(cx - SIZE * 0.3F, cy);
  453. north_arrow.lineTo(cx + SIZE * 0.3F, cy);
  454. north_arrow.closeSubpath();
  455. painter.setBrush(Palette::INK_DARK);
  456. painter.drawPath(north_arrow);
  457. QPainterPath south_arrow;
  458. south_arrow.moveTo(cx, cy + SIZE);
  459. south_arrow.lineTo(cx - SIZE * 0.3F, cy);
  460. south_arrow.lineTo(cx + SIZE * 0.3F, cy);
  461. south_arrow.closeSubpath();
  462. painter.setBrush(Palette::PARCHMENT_LIGHT);
  463. painter.drawPath(south_arrow);
  464. painter.drawLine(QPointF(cx - SIZE * 0.7F, cy),
  465. QPointF(cx + SIZE * 0.7F, cy));
  466. painter.setBrush(Palette::INK_MEDIUM);
  467. painter.drawEllipse(QPointF(cx, cy), 2.0, 2.0);
  468. painter.setPen(QPen(Palette::INK_DARK, 1.2F));
  469. const float n_left = cx - 3.5F;
  470. const float n_right = cx + 3.5F;
  471. const float n_top = cy - SIZE - 7.0F;
  472. const float n_bottom = cy - SIZE - 1.5F;
  473. QPainterPath n_path;
  474. n_path.moveTo(n_left, n_bottom);
  475. n_path.lineTo(n_left, n_top);
  476. n_path.lineTo(n_right, n_bottom);
  477. n_path.lineTo(n_right, n_top);
  478. painter.drawPath(n_path);
  479. }
  480. auto MinimapGenerator::biome_to_base_color(const BiomeSettings &biome)
  481. -> QColor {
  482. const auto &grass = biome.grass_primary;
  483. QColor base = QColor::fromRgbF(static_cast<double>(grass.x()),
  484. static_cast<double>(grass.y()),
  485. static_cast<double>(grass.z()));
  486. int h, s, v;
  487. base.getHsv(&h, &s, &v);
  488. base.setHsv(h, static_cast<int>(s * 0.4), static_cast<int>(v * 0.85));
  489. return base;
  490. }
  491. auto MinimapGenerator::terrain_feature_color(TerrainType type) -> QColor {
  492. switch (type) {
  493. case TerrainType::Mountain:
  494. return Palette::MOUNTAIN_SHADOW;
  495. case TerrainType::Hill:
  496. return Palette::HILL_BASE;
  497. case TerrainType::River:
  498. return Palette::WATER_MAIN;
  499. case TerrainType::Flat:
  500. default:
  501. return Palette::PARCHMENT_DARK;
  502. }
  503. }
  504. } // namespace Game::Map::Minimap