minimap_manager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #include "minimap_manager.h"
  2. #include "game/core/component.h"
  3. #include "game/core/world.h"
  4. #include "game/map/map_loader.h"
  5. #include "game/map/minimap/camera_viewport_layer.h"
  6. #include "game/map/minimap/minimap_generator.h"
  7. #include "game/map/minimap/minimap_utils.h"
  8. #include "game/map/minimap/unit_layer.h"
  9. #include "game/map/visibility_service.h"
  10. #include "game/systems/selection_system.h"
  11. #include "game/units/troop_type.h"
  12. #include "render/gl/camera.h"
  13. #include <QDebug>
  14. #include <QPainter>
  15. #include <algorithm>
  16. #include <cmath>
  17. #include <unordered_set>
  18. MinimapManager::MinimapManager() = default;
  19. MinimapManager::~MinimapManager() = default;
  20. bool MinimapManager::consume_dirty_flag() {
  21. bool was_dirty = m_dirty;
  22. m_dirty = false;
  23. return was_dirty;
  24. }
  25. void MinimapManager::generate_for_map(const Game::Map::MapDefinition &map_def) {
  26. Game::Map::Minimap::MinimapOrientation::instance().set_yaw_degrees(
  27. map_def.camera.yaw_deg);
  28. Game::Map::Minimap::MinimapGenerator generator;
  29. m_minimap_base_image = generator.generate(map_def);
  30. if (!m_minimap_base_image.isNull()) {
  31. qDebug() << "MinimapManager: Generated minimap of size"
  32. << m_minimap_base_image.width() << "x"
  33. << m_minimap_base_image.height();
  34. m_world_width = static_cast<float>(map_def.grid.width);
  35. m_world_height = static_cast<float>(map_def.grid.height);
  36. m_tile_size = map_def.grid.tile_size;
  37. m_minimap_fog_image = m_minimap_base_image.copy();
  38. m_minimap_image = m_minimap_fog_image.copy();
  39. m_fog_lookup_entries.clear();
  40. m_fog_lookup_vis_width = 0;
  41. m_fog_lookup_vis_height = 0;
  42. m_fog_lookup_img_width = 0;
  43. m_fog_lookup_img_height = 0;
  44. m_unit_layer = std::make_unique<Game::Map::Minimap::UnitLayer>();
  45. m_unit_layer->init(m_minimap_base_image.width(),
  46. m_minimap_base_image.height(), m_world_width,
  47. m_world_height);
  48. qDebug() << "MinimapManager: Initialized unit layer for world"
  49. << m_world_width << "x" << m_world_height;
  50. m_camera_viewport_layer =
  51. std::make_unique<Game::Map::Minimap::CameraViewportLayer>();
  52. m_camera_viewport_layer->init(m_minimap_base_image.width(),
  53. m_minimap_base_image.height(), m_world_width,
  54. m_world_height);
  55. m_minimap_fog_version = 0;
  56. m_minimap_update_timer = MINIMAP_UPDATE_INTERVAL;
  57. update_fog(0.0F, 1);
  58. mark_dirty();
  59. } else {
  60. qWarning() << "MinimapManager: Failed to generate minimap";
  61. }
  62. }
  63. void MinimapManager::rebuild_fog_lookup(int vis_width, int vis_height) {
  64. if (m_minimap_base_image.isNull() || vis_width <= 0 || vis_height <= 0) {
  65. m_fog_lookup_entries.clear();
  66. m_fog_lookup_vis_width = 0;
  67. m_fog_lookup_vis_height = 0;
  68. m_fog_lookup_img_width = 0;
  69. m_fog_lookup_img_height = 0;
  70. return;
  71. }
  72. m_fog_lookup_vis_width = vis_width;
  73. m_fog_lookup_vis_height = vis_height;
  74. m_fog_lookup_img_width = m_minimap_base_image.width();
  75. m_fog_lookup_img_height = m_minimap_base_image.height();
  76. const int img_width = m_fog_lookup_img_width;
  77. const int img_height = m_fog_lookup_img_height;
  78. m_fog_lookup_entries.resize(static_cast<std::size_t>(img_width * img_height));
  79. const auto &orient = Game::Map::Minimap::MinimapOrientation::instance();
  80. const float inv_cos = orient.cos_yaw();
  81. const float inv_sin = -orient.sin_yaw();
  82. const float scale_x =
  83. static_cast<float>(vis_width) / static_cast<float>(img_width);
  84. const float scale_y =
  85. static_cast<float>(vis_height) / static_cast<float>(img_height);
  86. const float half_img_w = static_cast<float>(img_width) * 0.5F;
  87. const float half_img_h = static_cast<float>(img_height) * 0.5F;
  88. const float half_vis_w = static_cast<float>(vis_width) * 0.5F;
  89. const float half_vis_h = static_cast<float>(vis_height) * 0.5F;
  90. std::size_t lookup_idx = 0;
  91. for (int y = 0; y < img_height; ++y) {
  92. const float centered_y = static_cast<float>(y) - half_img_h;
  93. for (int x = 0; x < img_width; ++x) {
  94. const float centered_x = static_cast<float>(x) - half_img_w;
  95. const float world_x = centered_x * inv_cos - centered_y * inv_sin;
  96. const float world_y = centered_x * inv_sin + centered_y * inv_cos;
  97. const float vis_x = (world_x * scale_x) + half_vis_w;
  98. const float vis_y = (world_y * scale_y) + half_vis_h;
  99. const int vx0 = std::clamp(static_cast<int>(vis_x), 0, vis_width - 1);
  100. const int vx1 = std::clamp(vx0 + 1, 0, vis_width - 1);
  101. const int vy0 = std::clamp(static_cast<int>(vis_y), 0, vis_height - 1);
  102. const int vy1 = std::clamp(vy0 + 1, 0, vis_height - 1);
  103. FogLookupEntry &entry = m_fog_lookup_entries[lookup_idx++];
  104. entry.idx00 = vy0 * vis_width + vx0;
  105. entry.idx10 = vy0 * vis_width + vx1;
  106. entry.idx01 = vy1 * vis_width + vx0;
  107. entry.idx11 = vy1 * vis_width + vx1;
  108. entry.fx = vis_x - static_cast<float>(vx0);
  109. entry.fy = vis_y - static_cast<float>(vy0);
  110. }
  111. }
  112. }
  113. void MinimapManager::update_fog(float dt, int local_owner_id) {
  114. Q_UNUSED(local_owner_id);
  115. if (m_minimap_base_image.isNull()) {
  116. return;
  117. }
  118. m_minimap_update_timer += dt;
  119. if (m_minimap_update_timer < MINIMAP_UPDATE_INTERVAL) {
  120. return;
  121. }
  122. m_minimap_update_timer = 0.0F;
  123. auto &visibility_service = Game::Map::VisibilityService::instance();
  124. if (!visibility_service.is_initialized()) {
  125. if (m_minimap_fog_image.isNull() ||
  126. m_minimap_fog_image.size() != m_minimap_base_image.size()) {
  127. m_minimap_fog_image = m_minimap_base_image.copy();
  128. }
  129. return;
  130. }
  131. const auto current_version = visibility_service.version();
  132. if (current_version == m_minimap_fog_version &&
  133. !m_minimap_fog_image.isNull()) {
  134. return;
  135. }
  136. m_minimap_fog_version = current_version;
  137. mark_dirty();
  138. const auto visibility_snapshot = visibility_service.snapshot();
  139. const int vis_width = visibility_snapshot.width;
  140. const int vis_height = visibility_snapshot.height;
  141. const auto &cells = visibility_snapshot.cells;
  142. if (cells.empty() || vis_width <= 0 || vis_height <= 0) {
  143. m_minimap_fog_image = m_minimap_base_image.copy();
  144. return;
  145. }
  146. const int img_width = m_minimap_base_image.width();
  147. const int img_height = m_minimap_base_image.height();
  148. if (m_fog_lookup_vis_width != vis_width ||
  149. m_fog_lookup_vis_height != vis_height ||
  150. m_fog_lookup_img_width != img_width ||
  151. m_fog_lookup_img_height != img_height ||
  152. m_fog_lookup_entries.size() !=
  153. static_cast<std::size_t>(img_width * img_height)) {
  154. rebuild_fog_lookup(vis_width, vis_height);
  155. }
  156. if (m_minimap_fog_image.isNull() ||
  157. m_minimap_fog_image.size() != m_minimap_base_image.size() ||
  158. m_minimap_fog_image.format() != m_minimap_base_image.format()) {
  159. m_minimap_fog_image = m_minimap_base_image.copy();
  160. }
  161. constexpr int FOG_R = 45;
  162. constexpr int FOG_G = 38;
  163. constexpr int FOG_B = 30;
  164. constexpr int ALPHA_UNSEEN = 180;
  165. constexpr int ALPHA_EXPLORED = 60;
  166. constexpr int ALPHA_VISIBLE = 0;
  167. constexpr float ALPHA_THRESHOLD = 0.5F;
  168. constexpr float ALPHA_SCALE = 1.0F / 255.0F;
  169. constexpr std::uint8_t k_visible =
  170. static_cast<std::uint8_t>(Game::Map::VisibilityState::Visible);
  171. constexpr std::uint8_t k_explored =
  172. static_cast<std::uint8_t>(Game::Map::VisibilityState::Explored);
  173. auto alpha_from_cell = [&](std::uint8_t state) -> float {
  174. if (state == k_visible) {
  175. return static_cast<float>(ALPHA_VISIBLE);
  176. }
  177. if (state == k_explored) {
  178. return static_cast<float>(ALPHA_EXPLORED);
  179. }
  180. return static_cast<float>(ALPHA_UNSEEN);
  181. };
  182. std::size_t lookup_idx = 0;
  183. for (int y = 0; y < img_height; ++y) {
  184. const auto *base_scanline =
  185. reinterpret_cast<const QRgb *>(m_minimap_base_image.constScanLine(y));
  186. auto *scanline = reinterpret_cast<QRgb *>(m_minimap_fog_image.scanLine(y));
  187. for (int x = 0; x < img_width; ++x) {
  188. const FogLookupEntry &sample = m_fog_lookup_entries[lookup_idx++];
  189. const float a00 =
  190. alpha_from_cell(cells[static_cast<std::size_t>(sample.idx00)]);
  191. const float a10 =
  192. alpha_from_cell(cells[static_cast<std::size_t>(sample.idx10)]);
  193. const float a01 =
  194. alpha_from_cell(cells[static_cast<std::size_t>(sample.idx01)]);
  195. const float a11 =
  196. alpha_from_cell(cells[static_cast<std::size_t>(sample.idx11)]);
  197. const float alpha_top = a00 + (a10 - a00) * sample.fx;
  198. const float alpha_bot = a01 + (a11 - a01) * sample.fx;
  199. const float fog_alpha = alpha_top + (alpha_bot - alpha_top) * sample.fy;
  200. if (fog_alpha > ALPHA_THRESHOLD) {
  201. const QRgb original = base_scanline[x];
  202. const int orig_r = qRed(original);
  203. const int orig_g = qGreen(original);
  204. const int orig_b = qBlue(original);
  205. const float blend = fog_alpha * ALPHA_SCALE;
  206. const float inv_blend = 1.0F - blend;
  207. const int new_r = static_cast<int>(orig_r * inv_blend + FOG_R * blend);
  208. const int new_g = static_cast<int>(orig_g * inv_blend + FOG_G * blend);
  209. const int new_b = static_cast<int>(orig_b * inv_blend + FOG_B * blend);
  210. scanline[x] = qRgba(new_r, new_g, new_b, 255);
  211. } else {
  212. scanline[x] = base_scanline[x];
  213. }
  214. }
  215. }
  216. }
  217. void MinimapManager::update_units(
  218. Engine::Core::World *world,
  219. Game::Systems::SelectionSystem *selection_system, int local_owner_id) {
  220. if (m_minimap_fog_image.isNull() || !m_unit_layer || !world) {
  221. return;
  222. }
  223. m_minimap_image = m_minimap_fog_image.copy();
  224. std::vector<Game::Map::Minimap::UnitMarker> markers;
  225. constexpr size_t EXPECTED_MAX_UNITS = 128;
  226. markers.reserve(EXPECTED_MAX_UNITS);
  227. std::unordered_set<Engine::Core::EntityID> selected_ids;
  228. if (selection_system) {
  229. const auto &sel = selection_system->get_selected_units();
  230. selected_ids.insert(sel.begin(), sel.end());
  231. }
  232. std::uint64_t unit_hash = 0;
  233. {
  234. const std::lock_guard<std::recursive_mutex> lock(world->get_entity_mutex());
  235. const auto &entities = world->get_entities();
  236. for (const auto &[entity_id, entity] : entities) {
  237. const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  238. if (!unit) {
  239. continue;
  240. }
  241. if (unit->health <= 0) {
  242. continue;
  243. }
  244. const auto *transform =
  245. entity->get_component<Engine::Core::TransformComponent>();
  246. if (!transform) {
  247. continue;
  248. }
  249. Game::Map::Minimap::UnitMarker marker;
  250. marker.world_x = transform->position.x;
  251. marker.world_z = transform->position.z;
  252. marker.owner_id = unit->owner_id;
  253. marker.is_selected = selected_ids.count(entity_id) > 0;
  254. marker.is_building = Game::Units::is_building_spawn(unit->spawn_type);
  255. markers.push_back(marker);
  256. unit_hash ^= static_cast<std::uint64_t>(entity_id);
  257. unit_hash ^=
  258. static_cast<std::uint64_t>(
  259. *reinterpret_cast<const std::uint32_t *>(&marker.world_x))
  260. << 1;
  261. unit_hash ^=
  262. static_cast<std::uint64_t>(
  263. *reinterpret_cast<const std::uint32_t *>(&marker.world_z))
  264. << 2;
  265. unit_hash ^= static_cast<std::uint64_t>(marker.is_selected) << 3;
  266. }
  267. }
  268. if (unit_hash != m_last_unit_hash) {
  269. m_last_unit_hash = unit_hash;
  270. mark_dirty();
  271. }
  272. auto &visibility_service = Game::Map::VisibilityService::instance();
  273. Game::Map::Minimap::VisibilityCheckFn visibility_check = nullptr;
  274. if (visibility_service.is_initialized()) {
  275. auto visibility_snapshot =
  276. std::make_shared<Game::Map::VisibilityService::Snapshot>(
  277. visibility_service.snapshot());
  278. visibility_check = [visibility_snapshot](float world_x,
  279. float world_z) -> bool {
  280. return visibility_snapshot->isVisibleWorld(world_x, world_z) ||
  281. visibility_snapshot->isExploredWorld(world_x, world_z);
  282. };
  283. }
  284. m_unit_layer->update(markers, local_owner_id, visibility_check, nullptr);
  285. const QImage &unit_overlay = m_unit_layer->get_image();
  286. if (!unit_overlay.isNull()) {
  287. QPainter painter(&m_minimap_image);
  288. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  289. painter.drawImage(0, 0, unit_overlay);
  290. }
  291. }
  292. void MinimapManager::update_camera_viewport(const Render::GL::Camera *camera,
  293. float screen_width,
  294. float screen_height) {
  295. if (m_minimap_image.isNull() || !m_camera_viewport_layer || !camera) {
  296. return;
  297. }
  298. const QVector3D &target = camera->get_target();
  299. const float distance = camera->get_distance();
  300. const float fov_rad =
  301. camera->get_fov() * Game::Map::Minimap::Constants::k_degrees_to_radians;
  302. const float aspect = screen_width / std::max(screen_height, 1.0F);
  303. const float viewport_half_height = distance * std::tan(fov_rad * 0.5F);
  304. const float viewport_half_width = viewport_half_height * aspect;
  305. const float viewport_width = viewport_half_width * 2.0F / m_tile_size;
  306. const float viewport_height = viewport_half_height * 2.0F / m_tile_size;
  307. const float camera_x = target.x() / m_tile_size;
  308. const float camera_z = target.z() / m_tile_size;
  309. constexpr float EPSILON = 0.01F;
  310. if (std::abs(camera_x - m_last_camera_x) > EPSILON ||
  311. std::abs(camera_z - m_last_camera_z) > EPSILON ||
  312. std::abs(viewport_width - m_last_viewport_w) > EPSILON ||
  313. std::abs(viewport_height - m_last_viewport_h) > EPSILON) {
  314. m_last_camera_x = camera_x;
  315. m_last_camera_z = camera_z;
  316. m_last_viewport_w = viewport_width;
  317. m_last_viewport_h = viewport_height;
  318. mark_dirty();
  319. }
  320. m_camera_viewport_layer->update(camera_x, camera_z, viewport_width,
  321. viewport_height);
  322. const QImage &viewport_overlay = m_camera_viewport_layer->get_image();
  323. if (!viewport_overlay.isNull()) {
  324. QPainter painter(&m_minimap_image);
  325. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  326. painter.drawImage(0, 0, viewport_overlay);
  327. }
  328. }