minimap_manager.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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/minimap_generator.h"
  6. #include "game/map/minimap/unit_layer.h"
  7. #include "game/map/visibility_service.h"
  8. #include "game/systems/selection_system.h"
  9. #include "game/units/troop_type.h"
  10. #include <QPainter>
  11. #include <QDebug>
  12. #include <algorithm>
  13. #include <unordered_set>
  14. MinimapManager::MinimapManager() = default;
  15. MinimapManager::~MinimapManager() = default;
  16. void MinimapManager::generate_for_map(
  17. const Game::Map::MapDefinition &map_def) {
  18. Game::Map::Minimap::MinimapGenerator generator;
  19. m_minimap_base_image = generator.generate(map_def);
  20. if (!m_minimap_base_image.isNull()) {
  21. qDebug() << "MinimapManager: Generated minimap of size"
  22. << m_minimap_base_image.width() << "x"
  23. << m_minimap_base_image.height();
  24. m_world_width = static_cast<float>(map_def.grid.width);
  25. m_world_height = static_cast<float>(map_def.grid.height);
  26. m_unit_layer = std::make_unique<Game::Map::Minimap::UnitLayer>();
  27. m_unit_layer->init(m_minimap_base_image.width(),
  28. m_minimap_base_image.height(), m_world_width,
  29. m_world_height);
  30. qDebug() << "MinimapManager: Initialized unit layer for world"
  31. << m_world_width << "x" << m_world_height;
  32. m_minimap_fog_version = 0;
  33. m_minimap_update_timer = MINIMAP_UPDATE_INTERVAL;
  34. update_fog(0.0F, 1);
  35. } else {
  36. qWarning() << "MinimapManager: Failed to generate minimap";
  37. }
  38. }
  39. void MinimapManager::update_fog(float dt, int local_owner_id) {
  40. if (m_minimap_base_image.isNull()) {
  41. return;
  42. }
  43. m_minimap_update_timer += dt;
  44. if (m_minimap_update_timer < MINIMAP_UPDATE_INTERVAL) {
  45. return;
  46. }
  47. m_minimap_update_timer = 0.0F;
  48. auto &visibility_service = Game::Map::VisibilityService::instance();
  49. if (!visibility_service.is_initialized()) {
  50. if (m_minimap_image != m_minimap_base_image) {
  51. m_minimap_image = m_minimap_base_image;
  52. }
  53. return;
  54. }
  55. const auto current_version = visibility_service.version();
  56. if (current_version == m_minimap_fog_version && !m_minimap_image.isNull()) {
  57. return;
  58. }
  59. m_minimap_fog_version = current_version;
  60. const int vis_width = visibility_service.getWidth();
  61. const int vis_height = visibility_service.getHeight();
  62. const auto cells = visibility_service.snapshotCells();
  63. if (cells.empty() || vis_width <= 0 || vis_height <= 0) {
  64. m_minimap_image = m_minimap_base_image;
  65. return;
  66. }
  67. m_minimap_image = m_minimap_base_image.copy();
  68. const int img_width = m_minimap_image.width();
  69. const int img_height = m_minimap_image.height();
  70. // Rotation constants for -45 degree isometric projection
  71. // k_inv_cos = -cos(45°), k_inv_sin = sin(45°)
  72. constexpr float k_inv_cos = -0.70710678118F;
  73. constexpr float k_inv_sin = 0.70710678118F;
  74. const float scale_x =
  75. static_cast<float>(vis_width) / static_cast<float>(img_width);
  76. const float scale_y =
  77. static_cast<float>(vis_height) / static_cast<float>(img_height);
  78. constexpr int FOG_R = 45;
  79. constexpr int FOG_G = 38;
  80. constexpr int FOG_B = 30;
  81. constexpr int ALPHA_UNSEEN = 180;
  82. constexpr int ALPHA_EXPLORED = 60;
  83. constexpr int ALPHA_VISIBLE = 0;
  84. constexpr float ALPHA_THRESHOLD = 0.5F;
  85. constexpr float ALPHA_SCALE = 1.0F / 255.0F;
  86. auto get_alpha = [&cells, vis_width, ALPHA_VISIBLE, ALPHA_EXPLORED,
  87. ALPHA_UNSEEN](int vx, int vy) -> float {
  88. const size_t idx = static_cast<size_t>(vy * vis_width + vx);
  89. if (idx >= cells.size()) {
  90. return static_cast<float>(ALPHA_UNSEEN);
  91. }
  92. const auto state = static_cast<Game::Map::VisibilityState>(cells[idx]);
  93. switch (state) {
  94. case Game::Map::VisibilityState::Visible:
  95. return static_cast<float>(ALPHA_VISIBLE);
  96. case Game::Map::VisibilityState::Explored:
  97. return static_cast<float>(ALPHA_EXPLORED);
  98. default:
  99. return static_cast<float>(ALPHA_UNSEEN);
  100. }
  101. };
  102. const float half_img_w = static_cast<float>(img_width) * 0.5F;
  103. const float half_img_h = static_cast<float>(img_height) * 0.5F;
  104. const float half_vis_w = static_cast<float>(vis_width) * 0.5F;
  105. const float half_vis_h = static_cast<float>(vis_height) * 0.5F;
  106. for (int y = 0; y < img_height; ++y) {
  107. auto *scanline = reinterpret_cast<QRgb *>(m_minimap_image.scanLine(y));
  108. for (int x = 0; x < img_width; ++x) {
  109. const float centered_x = static_cast<float>(x) - half_img_w;
  110. const float centered_y = static_cast<float>(y) - half_img_h;
  111. const float world_x = centered_x * k_inv_cos - centered_y * k_inv_sin;
  112. const float world_y = centered_x * k_inv_sin + centered_y * k_inv_cos;
  113. const float vis_x = (world_x * scale_x) + half_vis_w;
  114. const float vis_y = (world_y * scale_y) + half_vis_h;
  115. const int vx0 = std::clamp(static_cast<int>(vis_x), 0, vis_width - 1);
  116. const int vx1 = std::clamp(vx0 + 1, 0, vis_width - 1);
  117. const float fx = vis_x - static_cast<float>(vx0);
  118. const int vy0 = std::clamp(static_cast<int>(vis_y), 0, vis_height - 1);
  119. const int vy1 = std::clamp(vy0 + 1, 0, vis_height - 1);
  120. const float fy = vis_y - static_cast<float>(vy0);
  121. const float a00 = get_alpha(vx0, vy0);
  122. const float a10 = get_alpha(vx1, vy0);
  123. const float a01 = get_alpha(vx0, vy1);
  124. const float a11 = get_alpha(vx1, vy1);
  125. const float alpha_top = a00 + (a10 - a00) * fx;
  126. const float alpha_bot = a01 + (a11 - a01) * fx;
  127. const float fog_alpha = alpha_top + (alpha_bot - alpha_top) * fy;
  128. if (fog_alpha > ALPHA_THRESHOLD) {
  129. const QRgb original = scanline[x];
  130. const int orig_r = qRed(original);
  131. const int orig_g = qGreen(original);
  132. const int orig_b = qBlue(original);
  133. const float blend = fog_alpha * ALPHA_SCALE;
  134. const float inv_blend = 1.0F - blend;
  135. const int new_r = static_cast<int>(orig_r * inv_blend + FOG_R * blend);
  136. const int new_g = static_cast<int>(orig_g * inv_blend + FOG_G * blend);
  137. const int new_b = static_cast<int>(orig_b * inv_blend + FOG_B * blend);
  138. scanline[x] = qRgba(new_r, new_g, new_b, 255);
  139. }
  140. }
  141. }
  142. }
  143. void MinimapManager::update_units(
  144. Engine::Core::World *world,
  145. Game::Systems::SelectionSystem *selection_system) {
  146. if (m_minimap_image.isNull() || !m_unit_layer || !world) {
  147. return;
  148. }
  149. std::vector<Game::Map::Minimap::UnitMarker> markers;
  150. // Reserve space for typical unit count to avoid reallocations
  151. // Based on common game scenarios with up to ~100 units per side
  152. constexpr size_t EXPECTED_MAX_UNITS = 128;
  153. markers.reserve(EXPECTED_MAX_UNITS);
  154. std::unordered_set<Engine::Core::EntityID> selected_ids;
  155. if (selection_system) {
  156. const auto &sel = selection_system->get_selected_units();
  157. selected_ids.insert(sel.begin(), sel.end());
  158. }
  159. {
  160. const std::lock_guard<std::recursive_mutex> lock(
  161. world->get_entity_mutex());
  162. const auto &entities = world->get_entities();
  163. for (const auto &[entity_id, entity] : entities) {
  164. const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
  165. if (!unit) {
  166. continue;
  167. }
  168. const auto *transform =
  169. entity->get_component<Engine::Core::TransformComponent>();
  170. if (!transform) {
  171. continue;
  172. }
  173. Game::Map::Minimap::UnitMarker marker;
  174. marker.world_x = transform->position.x;
  175. marker.world_z = transform->position.z;
  176. marker.owner_id = unit->owner_id;
  177. marker.is_selected = selected_ids.count(entity_id) > 0;
  178. marker.is_building = Game::Units::is_building_spawn(unit->spawn_type);
  179. markers.push_back(marker);
  180. }
  181. }
  182. m_unit_layer->update(markers);
  183. const QImage &unit_overlay = m_unit_layer->get_image();
  184. if (!unit_overlay.isNull()) {
  185. QPainter painter(&m_minimap_image);
  186. painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
  187. painter.drawImage(0, 0, unit_overlay);
  188. }
  189. }