refactor: decompose world map into modular component architecture

Break the monolithic 1360-line world_map.cpp into 16 focused modules
under src/rendering/world_map/:

Architecture:
- world_map_facade: public API composing all components (PIMPL)
- world_map_types: Vulkan-free domain types (Zone, ViewLevel, etc.)
- data_repository: DBC zone loading, ZMP pixel map, POI/overlay storage
- coordinate_projection: UV projection, zone/continent lookups
- composite_renderer: Vulkan tile pipeline + off-screen compositing
- exploration_state: server mask + local exploration tracking
- view_state_machine: COSMIC→WORLD→CONTINENT→ZONE navigation
- input_handler: keyboard/mouse input → InputAction mapping
- overlay_renderer: layer-based ImGui overlay system (OCP)
- map_resolver: cross-map navigation (Outland, Northrend, etc.)
- zone_metadata: level ranges and faction data

Overlay layers (each an IOverlayLayer):
- player_marker, party_dot, taxi_node, poi_marker, quest_poi,
  corpse_marker, zone_highlight, coordinate_display, subzone_tooltip

Fixes:
- Player marker no longer bleeds across continents (only shown when
  player is in a zone belonging to the displayed continent)
- Zone hover uses DBC-projected AABB rectangles (restored from
  original working behavior)
- Exploration overlay rendering for zone view subzones

Tests:
- 6 new test files covering coordinate projection, exploration state,
  map resolver, view state machine, zone metadata, and integration

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
Pavel Okhlopkov 2026-04-12 09:52:51 +03:00
parent db3f65a87e
commit fff06fc932
55 changed files with 6335 additions and 1542 deletions

View file

@ -1,174 +1,23 @@
// world_map.hpp — Shim header for backward compatibility.
// Redirects to the modular world_map/world_map_facade.hpp.
// Consumers should migrate to #include "rendering/world_map/world_map_facade.hpp" directly.
#pragma once
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "rendering/world_map/world_map_facade.hpp"
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class VkContext;
class VkTexture;
class VkRenderTarget;
// Backward-compatible type aliases for old consumer code
// (game_screen_hud.cpp, renderer.cpp, etc.)
using WorldMapPartyDot = world_map::PartyDot;
using WorldMapTaxiNode = world_map::TaxiNode;
using MapPOI = world_map::POI;
/// Party member dot passed in from the UI layer for world map overlay.
struct WorldMapPartyDot {
glm::vec3 renderPos; ///< Position in render-space coordinates
uint32_t color; ///< RGBA packed color (IM_COL32 format)
std::string name; ///< Member name (shown as tooltip on hover)
};
/// Taxi (flight master) node passed from the UI layer for world map overlay.
struct WorldMapTaxiNode {
uint32_t id = 0; ///< TaxiNodes.dbc ID
uint32_t mapId = 0; ///< WoW internal map ID (0=EK,1=Kal,530=Outland,571=Northrend)
float wowX = 0, wowY = 0, wowZ = 0; ///< Canonical WoW coordinates
std::string name; ///< Node name (shown as tooltip)
bool known = false; ///< Player has discovered this node
};
struct WorldMapZone {
uint32_t wmaID = 0;
uint32_t areaID = 0; // 0 = continent level
std::string areaName; // texture folder name (from DBC)
float locLeft = 0, locRight = 0, locTop = 0, locBottom = 0;
uint32_t displayMapID = 0;
uint32_t parentWorldMapID = 0;
std::vector<uint32_t> exploreBits; // all AreaBit indices (zone + subzones)
// Per-zone cached textures (owned by WorldMap::zoneTextures)
VkTexture* tileTextures[12] = {};
bool tilesLoaded = false;
};
class WorldMap {
public:
WorldMap();
~WorldMap();
bool initialize(VkContext* ctx, pipeline::AssetManager* assetManager);
void shutdown();
/// Off-screen composite pass — call BEFORE the main render pass begins.
void compositePass(VkCommandBuffer cmd);
/// ImGui overlay — call INSIDE the main render pass (during ImGui frame).
void render(const glm::vec3& playerRenderPos, int screenWidth, int screenHeight,
float playerYawDeg = 0.0f);
void setMapName(const std::string& name);
void setServerExplorationMask(const std::vector<uint32_t>& masks, bool hasData);
void setPartyDots(std::vector<WorldMapPartyDot> dots) { partyDots_ = std::move(dots); }
void setTaxiNodes(std::vector<WorldMapTaxiNode> nodes) { taxiNodes_ = std::move(nodes); }
/// Quest POI marker for world map overlay (from SMSG_QUEST_POI_QUERY_RESPONSE).
struct QuestPoi {
float wowX = 0, wowY = 0; ///< Canonical WoW coordinates (centroid of POI area)
std::string name; ///< Quest title
};
void setQuestPois(std::vector<QuestPoi> pois) { questPois_ = std::move(pois); }
/// Set the player's corpse position for overlay rendering.
/// @param hasCorpse True when the player is a ghost with an unclaimed corpse on this map.
/// @param renderPos Corpse position in render-space coordinates.
void setCorpsePos(bool hasCorpse, glm::vec3 renderPos) {
hasCorpse_ = hasCorpse;
corpseRenderPos_ = renderPos;
}
bool isOpen() const { return open; }
void close() { open = false; }
private:
enum class ViewLevel { WORLD, CONTINENT, ZONE };
void enterWorldView();
void loadZonesFromDBC();
int findBestContinentForPlayer(const glm::vec3& playerRenderPos) const;
int findZoneForPlayer(const glm::vec3& playerRenderPos) const;
bool zoneBelongsToContinent(int zoneIdx, int contIdx) const;
bool getContinentProjectionBounds(int contIdx, float& left, float& right,
float& top, float& bottom) const;
void loadZoneTextures(int zoneIdx);
void requestComposite(int zoneIdx);
void renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWidth, int screenHeight,
float playerYawDeg);
void updateExploration(const glm::vec3& playerRenderPos);
void zoomIn(const glm::vec3& playerRenderPos);
void zoomOut();
glm::vec2 renderPosToMapUV(const glm::vec3& renderPos, int zoneIdx) const;
void destroyZoneTextures();
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
bool initialized = false;
bool open = false;
std::string mapName = "Azeroth";
// All zones for current map
std::vector<WorldMapZone> zones;
int continentIdx = -1;
int currentIdx = -1;
ViewLevel viewLevel = ViewLevel::CONTINENT;
int compositedIdx = -1;
int pendingCompositeIdx = -1;
// FBO replacement (4x3 tiles = 1024x768)
static constexpr int GRID_COLS = 4;
static constexpr int GRID_ROWS = 3;
static constexpr int TILE_PX = 256;
static constexpr int FBO_W = GRID_COLS * TILE_PX;
static constexpr int FBO_H = GRID_ROWS * TILE_PX;
std::unique_ptr<VkRenderTarget> compositeTarget;
// Quad vertex buffer (pos2 + uv2)
::VkBuffer quadVB = VK_NULL_HANDLE;
VmaAllocation quadVBAlloc = VK_NULL_HANDLE;
// Descriptor resources
VkDescriptorSetLayout samplerSetLayout = VK_NULL_HANDLE;
VkDescriptorPool descPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_DESC_SETS = 32;
// Tile composite pipeline
VkPipeline tilePipeline = VK_NULL_HANDLE;
VkPipelineLayout tilePipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet tileDescSets[2][12] = {}; // [frameInFlight][tileSlot]
// ImGui display descriptor set (points to composite render target)
VkDescriptorSet imguiDisplaySet = VK_NULL_HANDLE;
// Texture storage (owns all VkTexture objects for zone tiles)
std::vector<std::unique_ptr<VkTexture>> zoneTextures;
// Party member dots (set each frame from the UI layer)
std::vector<WorldMapPartyDot> partyDots_;
// Taxi node markers (set each frame from the UI layer)
std::vector<WorldMapTaxiNode> taxiNodes_;
int currentMapId_ = -1; ///< WoW map ID currently loaded (set in loadZonesFromDBC)
// Quest POI markers (set each frame from the UI layer)
std::vector<QuestPoi> questPois_;
// Corpse marker (ghost state — set each frame from the UI layer)
bool hasCorpse_ = false;
glm::vec3 corpseRenderPos_ = {};
// Exploration / fog of war
std::vector<uint32_t> serverExplorationMask;
bool hasServerExplorationMask = false;
std::unordered_set<int> exploredZones;
// Locally accumulated exploration (used as fallback when server mask is unavailable)
std::unordered_set<int> locallyExploredZones_;
};
// WorldMap alias is already provided by world_map_facade.hpp:
// using WorldMap = world_map::WorldMapFacade;
// WorldMap::QuestPoi alias is provided inside WorldMapFacade:
// using QuestPoi = QuestPOI;
} // namespace rendering
} // namespace wowee