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

@ -0,0 +1,157 @@
// composite_renderer.hpp — Vulkan off-screen composite rendering for the world map.
// Extracted from WorldMap (Phase 7 of refactoring plan).
// SRP — all GPU resource management separated from domain logic.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <unordered_set>
namespace wowee {
namespace rendering {
class VkContext;
class VkTexture;
class VkRenderTarget;
}
namespace pipeline { class AssetManager; }
namespace rendering {
namespace world_map {
/// Push constant for world map tile composite vertex shader.
struct WorldMapTilePush {
glm::vec2 gridOffset; // 8 bytes
float gridCols; // 4 bytes
float gridRows; // 4 bytes
}; // 16 bytes
/// Push constant for the overlay/fog pipeline (vertex + fragment stages).
struct OverlayPush {
glm::vec2 gridOffset; // 8 bytes (vertex)
float gridCols; // 4 bytes (vertex)
float gridRows; // 4 bytes (vertex)
glm::vec4 tintColor; // 16 bytes (fragment)
}; // 32 bytes
class CompositeRenderer {
public:
CompositeRenderer();
~CompositeRenderer();
bool initialize(VkContext* ctx, pipeline::AssetManager* am);
void shutdown();
/// Load base tile textures for a zone.
void loadZoneTextures(int zoneIdx, std::vector<Zone>& zones, const std::string& mapName);
/// Load exploration overlay textures for a zone.
void loadOverlayTextures(int zoneIdx, std::vector<Zone>& zones);
/// Request a composite for the given zone (deferred to compositePass).
void requestComposite(int zoneIdx);
/// Execute the off-screen composite pass.
void compositePass(VkCommandBuffer cmd,
const std::vector<Zone>& zones,
const std::unordered_set<int>& exploredOverlays,
bool hasServerMask);
/// Descriptor set for ImGui display of the composite.
VkDescriptorSet displayDescriptorSet() const { return imguiDisplaySet; }
/// Destroy all loaded zone textures (on map change).
void destroyZoneTextures(std::vector<Zone>& zones);
/// Detach zone textures for deferred GPU destruction.
/// Clears CPU tracking immediately but moves GPU texture objects to a stale
/// list so they can be freed later when no in-flight frames reference them.
void detachZoneTextures();
/// Free any GPU textures previously moved to the stale list by detachZoneTextures.
/// Calls vkDeviceWaitIdle internally to ensure no in-flight work references them.
void flushStaleTextures();
/// Index of the zone currently composited (-1 if none).
int compositedIdx() const { return compositedIdx_; }
/// Reset composited index to force re-composite.
void invalidateComposite() { compositedIdx_ = -1; }
/// Check whether a zone has any loaded tile textures.
bool hasAnyTile(int zoneIdx) const;
// FBO dimensions (public for overlay coordinate math)
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;
// WoW's WorldMapDetailFrame is 1002x668 — the visible map content area.
// The FBO is 1024x768 so we crop UVs to show only the actual map region.
static constexpr int MAP_W = 1002;
static constexpr int MAP_H = 668;
static constexpr float MAP_U_MAX = static_cast<float>(MAP_W) / static_cast<float>(FBO_W);
static constexpr float MAP_V_MAX = static_cast<float>(MAP_H) / static_cast<float>(FBO_H);
private:
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
bool initialized = false;
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 = 192;
static constexpr uint32_t MAX_OVERLAY_TILES = 48;
// Tile composite pipeline
VkPipeline tilePipeline = VK_NULL_HANDLE;
VkPipelineLayout tilePipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet tileDescSets[2][12] = {}; // [frameInFlight][tileSlot]
// Alpha-blended overlay pipeline (fog + explored area overlays)
VkPipeline overlayPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout overlayPipelineLayout_ = VK_NULL_HANDLE;
std::unique_ptr<VkTexture> fogTexture_; // 1×1 white pixel for fog quad
VkDescriptorSet fogDescSet_ = VK_NULL_HANDLE;
VkDescriptorSet overlayDescSets_[2][MAX_OVERLAY_TILES] = {};
// 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;
int compositedIdx_ = -1;
int pendingCompositeIdx_ = -1;
// Per-zone tile texture pointers (indexed by zone, then by tile slot)
// Stored separately since Zone struct is now Vulkan-free
struct ZoneTextureSlots {
VkTexture* tileTextures[12] = {};
bool tilesLoaded = false;
// Per-overlay tile textures
struct OverlaySlots {
std::vector<VkTexture*> tiles;
bool tilesLoaded = false;
};
std::vector<OverlaySlots> overlays;
};
std::vector<ZoneTextureSlots> zoneTextureSlots_;
void ensureTextureSlots(size_t zoneCount, const std::vector<Zone>& zones);
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,49 @@
// coordinate_projection.hpp — Pure coordinate math for world map UV projection.
// Extracted from WorldMap methods (Phase 2 of refactoring plan).
// All functions are stateless free functions — trivially testable.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <glm/glm.hpp>
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
/// Project render-space position to [0,1] UV on a zone or continent map.
glm::vec2 renderPosToMapUV(const glm::vec3& renderPos,
const ZoneBounds& bounds,
bool isContinent);
/// Derive effective projection bounds for a continent from its child zones.
/// Uses zoneBelongsToContinent() internally. Returns false if insufficient data.
bool getContinentProjectionBounds(const std::vector<Zone>& zones,
int contIdx,
float& left, float& right,
float& top, float& bottom);
/// Find the best-fit continent index for a player position.
/// Prefers the smallest containing continent; falls back to nearest center.
int findBestContinentForPlayer(const std::vector<Zone>& zones,
const glm::vec3& playerRenderPos);
/// Find the smallest zone (areaID != 0) containing the player position.
/// Returns -1 if no zone contains the position.
int findZoneForPlayer(const std::vector<Zone>& zones,
const glm::vec3& playerRenderPos);
/// Test if a zone spatially belongs to a given continent.
/// Uses parentWorldMapID when available, falls back to overlap heuristic.
bool zoneBelongsToContinent(const std::vector<Zone>& zones,
int zoneIdx, int contIdx);
/// Check whether the zone at idx is a root continent (has leaf continents as children).
bool isRootContinent(const std::vector<Zone>& zones, int idx);
/// Check whether the zone at idx is a leaf continent (parentWorldMapID != 0, areaID == 0).
bool isLeafContinent(const std::vector<Zone>& zones, int idx);
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,95 @@
// data_repository.hpp — DBC data loading, ZMP pixel map, and zone/POI/overlay storage.
// Extracted from WorldMap::loadZonesFromDBC, loadPOIData, buildCosmicView
// (Phase 5 of refactoring plan). SRP — all DBC parsing lives here.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <unordered_map>
#include <string>
#include <vector>
#include <array>
#include <cstdint>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
namespace world_map {
class DataRepository {
public:
/// Load all zone data from DBC files for the given map name.
void loadZones(const std::string& mapName, pipeline::AssetManager& assetManager);
/// Load area POI markers from AreaPOI.dbc.
void loadPOIs(pipeline::AssetManager& assetManager);
/// Build cosmic view entries for the active expansion (uses isActiveExpansion).
void buildCosmicView(int expansionLevel = 0);
/// Build Azeroth world-view continent regions for the active expansion.
void buildAzerothView(int expansionLevel = 0);
/// Load ZMP pixel map for the given continent name (e.g. "Azeroth").
/// The ZMP is a 128x128 grid of uint32 AreaTable IDs.
void loadZmpPixelMap(const std::string& continentName,
pipeline::AssetManager& assetManager);
/// Determine expansion level from the active expansion profile.
static int getExpansionLevel();
// --- Accessors ---
std::vector<Zone>& zones() { return zones_; }
const std::vector<Zone>& zones() const { return zones_; }
int cosmicIdx() const { return cosmicIdx_; }
int worldIdx() const { return worldIdx_; }
int currentMapId() const { return currentMapId_; }
const std::vector<CosmicMapEntry>& cosmicMaps() const { return cosmicMaps_; }
const std::vector<CosmicMapEntry>& azerothRegions() const { return azerothRegions_; }
bool cosmicEnabled() const { return cosmicEnabled_; }
const std::vector<POI>& poiMarkers() const { return poiMarkers_; }
const std::unordered_map<uint32_t, uint32_t>& exploreFlagByAreaId() const { return exploreFlagByAreaId_; }
const std::unordered_map<uint32_t, std::string>& areaNameByAreaId() const { return areaNameByAreaId_; }
/// ZMP pixel map accessors.
static constexpr int ZMP_SIZE = 128;
const std::array<uint32_t, 128 * 128>& zmpGrid() const { return zmpGrid_; }
bool hasZmpData() const { return zmpLoaded_; }
/// Look up zone index from an AreaTable ID (from ZMP). Returns -1 if not found.
int zoneIndexForAreaId(uint32_t areaId) const;
/// ZMP-derived bounding rectangles per zone index (UV [0,1] on display).
const std::unordered_map<int, ZmpRect>& zmpZoneBounds() const { return zmpZoneBounds_; }
/// Reset all data (called on map change).
void clear();
private:
std::vector<Zone> zones_;
std::vector<POI> poiMarkers_;
std::vector<CosmicMapEntry> cosmicMaps_;
std::vector<CosmicMapEntry> azerothRegions_;
std::unordered_map<uint32_t, uint32_t> exploreFlagByAreaId_;
std::unordered_map<uint32_t, std::string> areaNameByAreaId_;
int cosmicIdx_ = -1;
int worldIdx_ = -1;
int currentMapId_ = -1;
bool cosmicEnabled_ = true;
bool poisLoaded_ = false;
// ZMP pixel map: 128x128 grid of AreaTable IDs for continent-level hover
std::array<uint32_t, 128 * 128> zmpGrid_{};
bool zmpLoaded_ = false;
// AreaID → zone index (zones_ vector) for quick resolution
std::unordered_map<uint32_t, int> areaIdToZoneIdx_;
// ZMP-derived bounding boxes per zone index (UV coords on display)
std::unordered_map<int, ZmpRect> zmpZoneBounds_;
/// Scan ZMP grid and build bounding boxes for each zone.
void buildZmpZoneBounds();
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,53 @@
// exploration_state.hpp — Fog of war / exploration tracking (pure domain logic).
// Extracted from WorldMap::updateExploration (Phase 3 of refactoring plan).
// No rendering or GPU dependencies — fully testable standalone.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <glm/glm.hpp>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
class ExplorationState {
public:
void setServerMask(const std::vector<uint32_t>& masks, bool hasData);
bool hasServerMask() const { return hasServerMask_; }
/// Recompute explored zones and overlays for given player position.
/// @param zones All loaded zones
/// @param playerRenderPos Player position in render-space
/// @param currentZoneIdx Currently viewed zone index
/// @param exploreFlagByAreaId AreaID → ExploreFlag mapping from AreaTable.dbc
void update(const std::vector<Zone>& zones,
const glm::vec3& playerRenderPos,
int currentZoneIdx,
const std::unordered_map<uint32_t, uint32_t>& exploreFlagByAreaId);
const std::unordered_set<int>& exploredZones() const { return exploredZones_; }
const std::unordered_set<int>& exploredOverlays() const { return exploredOverlays_; }
/// Returns true if the explored overlay set changed since last update.
bool overlaysChanged() const { return overlaysChanged_; }
/// Clear accumulated local exploration data.
void clearLocal() { locallyExploredZones_.clear(); }
private:
bool isBitSet(uint32_t bitIndex) const;
std::vector<uint32_t> serverMask_;
bool hasServerMask_ = false;
std::unordered_set<int> exploredZones_;
std::unordered_set<int> exploredOverlays_;
std::unordered_set<int> locallyExploredZones_;
bool overlaysChanged_ = false;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,37 @@
// input_handler.hpp — Input processing for the world map.
// Extracted from WorldMap::render (Phase 9 of refactoring plan).
// SRP — input interpretation separated from state changes and rendering.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
namespace wowee {
namespace rendering {
namespace world_map {
enum class InputAction {
NONE,
CLOSE,
ZOOM_IN,
ZOOM_OUT,
CLICK_ZONE, // left-click on continent view zone
CLICK_COSMIC_REGION, // left-click on cosmic landmass
RIGHT_CLICK_BACK, // right-click to go back
};
struct InputResult {
InputAction action = InputAction::NONE;
int targetIdx = -1; // zone or cosmic region index
};
class InputHandler {
public:
/// Process input for current frame. Returns the highest-priority action.
InputResult process(ViewLevel currentLevel,
int hoveredZoneIdx,
bool cosmicEnabled);
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,16 @@
// coordinate_display.hpp — WoW coordinates under cursor on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
namespace wowee {
namespace rendering {
namespace world_map {
class CoordinateDisplay : public IOverlayLayer {
public:
void render(const LayerContext& ctx) override;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,24 @@
// corpse_marker_layer.hpp — Death corpse X marker on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
namespace world_map {
class CorpseMarkerLayer : public IOverlayLayer {
public:
void setCorpse(bool hasCorpse, glm::vec3 renderPos) {
hasCorpse_ = hasCorpse;
corpseRenderPos_ = renderPos;
}
void render(const LayerContext& ctx) override;
private:
bool hasCorpse_ = false;
glm::vec3 corpseRenderPos_ = {};
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,21 @@
// party_dot_layer.hpp — Party member position dots on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include "rendering/world_map/world_map_types.hpp"
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
class PartyDotLayer : public IOverlayLayer {
public:
void setDots(const std::vector<PartyDot>& dots) { dots_ = &dots; }
void render(const LayerContext& ctx) override;
private:
const std::vector<PartyDot>* dots_ = nullptr;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,16 @@
// player_marker_layer.hpp — Directional player arrow on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
namespace wowee {
namespace rendering {
namespace world_map {
class PlayerMarkerLayer : public IOverlayLayer {
public:
void render(const LayerContext& ctx) override;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,21 @@
// poi_marker_layer.hpp — Town/dungeon/capital POI icons on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include "rendering/world_map/world_map_types.hpp"
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
class POIMarkerLayer : public IOverlayLayer {
public:
void setMarkers(const std::vector<POI>& markers) { markers_ = &markers; }
void render(const LayerContext& ctx) override;
private:
const std::vector<POI>* markers_ = nullptr;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,21 @@
// quest_poi_layer.hpp — Quest objective markers on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include "rendering/world_map/world_map_types.hpp"
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
class QuestPOILayer : public IOverlayLayer {
public:
void setPois(const std::vector<QuestPOI>& pois) { pois_ = &pois; }
void render(const LayerContext& ctx) override;
private:
const std::vector<QuestPOI>* pois_ = nullptr;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,16 @@
// subzone_tooltip_layer.hpp — Overlay area hover labels in zone view.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
namespace wowee {
namespace rendering {
namespace world_map {
class SubzoneTooltipLayer : public IOverlayLayer {
public:
void render(const LayerContext& ctx) override;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,21 @@
// taxi_node_layer.hpp — Flight master diamond icons on the world map.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include "rendering/world_map/world_map_types.hpp"
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
class TaxiNodeLayer : public IOverlayLayer {
public:
void setNodes(const std::vector<TaxiNode>& nodes) { nodes_ = &nodes; }
void render(const LayerContext& ctx) override;
private:
const std::vector<TaxiNode>* nodes_ = nullptr;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,55 @@
// zone_highlight_layer.hpp — Continent view zone rectangles + hover effects.
#pragma once
#include "rendering/world_map/overlay_renderer.hpp"
#include "rendering/world_map/zone_metadata.hpp"
#include "rendering/vk_texture.hpp"
#include <vulkan/vulkan.h>
#include <unordered_map>
#include <memory>
namespace wowee {
namespace rendering {
class VkContext;
}
namespace pipeline { class AssetManager; }
namespace rendering {
namespace world_map {
class ZoneHighlightLayer : public IOverlayLayer {
public:
~ZoneHighlightLayer() override;
void setMetadata(const ZoneMetadata* metadata) { metadata_ = metadata; }
void initialize(VkContext* ctx, pipeline::AssetManager* am);
void clearTextures();
void render(const LayerContext& ctx) override;
int hoveredZone() const { return hoveredZone_; }
/// Get the ImGui texture ID for a highlight BLP, loading lazily.
/// key is used as cache key; customPath overrides the default path if non-empty.
ImTextureID getHighlightTexture(const std::string& key,
const std::string& customPath = "");
private:
/// Load the highlight BLP and register it with ImGui.
void ensureHighlight(const std::string& key, const std::string& customPath);
const ZoneMetadata* metadata_ = nullptr;
VkContext* vkCtx_ = nullptr;
pipeline::AssetManager* assetManager_ = nullptr;
struct HighlightEntry {
std::unique_ptr<VkTexture> texture;
VkDescriptorSet imguiDS = VK_NULL_HANDLE; // ImGui texture ID
};
std::unordered_map<std::string, HighlightEntry> highlights_;
std::unordered_set<std::string> missingHighlights_; // areas with no highlight file
int hoveredZone_ = -1;
int prevHoveredZone_ = -1;
float hoverHighlightAlpha_ = 0.0f;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,75 @@
// map_resolver.hpp — Centralized map navigation resolution for the world map.
// Determines the correct action when clicking a region or zone at any view level.
// All functions are stateless free functions — trivially testable.
// Map folder names are resolved from a built-in table matching
// Data/interface/worldmap/ rather than WorldLoader::mapIdToName.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <string>
#include <vector>
#include <cstdint>
namespace wowee {
namespace rendering {
namespace world_map {
// ── Map folder lookup (replaces WorldLoader::mapIdToName for world map) ──
/// Map ID → worldmap folder name (e.g. 0 → "Azeroth", 571 → "Northrend").
/// Returns empty string if unknown.
const char* mapIdToFolder(uint32_t mapId);
/// Worldmap folder name → map ID (e.g. "Azeroth" → 0, "Northrend" → 571).
/// Case-insensitive comparison. Returns -1 if unknown.
int folderToMapId(const std::string& folder);
/// Map ID → display name for UI (e.g. 0 → "Eastern Kingdoms", 571 → "Northrend").
/// Returns nullptr if unknown.
const char* mapDisplayName(uint32_t mapId);
// ── Result types ─────────────────────────────────────────────
enum class MapResolveAction {
NONE, ///< No valid navigation target
NAVIGATE_CONTINENT, ///< Switch to continent view within current map data
LOAD_MAP, ///< Load a different map entirely (switchToMap)
ENTER_ZONE, ///< Enter zone view within current continent
};
struct MapResolveResult {
MapResolveAction action = MapResolveAction::NONE;
int targetZoneIdx = -1; ///< Zone index for NAVIGATE_CONTINENT or ENTER_ZONE
std::string targetMapName; ///< Map folder name for LOAD_MAP
};
// ── Resolve functions ────────────────────────────────────────
/// Resolve WORLD view region click. Determines whether to navigate within
/// the current map data (e.g. clicking EK when already on Azeroth) or load
/// a new map (e.g. clicking Kalimdor or Northrend from Azeroth world view).
MapResolveResult resolveWorldRegionClick(uint32_t regionMapId,
const std::vector<Zone>& zones,
int currentMapId,
int cosmicIdx);
/// Resolve CONTINENT view zone click. Determines whether the clicked zone
/// can be entered directly (same map) or requires loading a different map
/// (zone's displayMapID differs from current).
MapResolveResult resolveZoneClick(int zoneIdx,
const std::vector<Zone>& zones,
int currentMapId);
/// Resolve COSMIC view map click. Always returns LOAD_MAP for the target.
MapResolveResult resolveCosmicClick(uint32_t targetMapId);
/// Find the best continent zone index to display for a given mapId within
/// the currently loaded zones. Prefers leaf continents over root continents.
/// Returns -1 if no suitable continent is found.
int findContinentForMapId(const std::vector<Zone>& zones,
uint32_t mapId,
int cosmicIdx);
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,71 @@
// overlay_renderer.hpp — ImGui overlay layer system for the world map.
// Extracted from WorldMap::renderImGuiOverlay (Phase 8 of refactoring plan).
// OCP — new marker types are added by implementing IOverlayLayer.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <glm/glm.hpp>
#include <vector>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <array>
#include <cstdint>
#include <functional>
#include <imgui.h>
struct ImDrawList;
namespace wowee {
namespace rendering {
namespace world_map {
/// Context passed to each overlay layer during rendering.
struct LayerContext {
ImDrawList* drawList = nullptr;
ImVec2 imgMin; // top-left of map image in screen space
float displayW = 0, displayH = 0;
glm::vec3 playerRenderPos;
float playerYawDeg = 0;
int currentZoneIdx = -1;
int continentIdx = -1;
int currentMapId = -1;
ViewLevel viewLevel = ViewLevel::ZONE;
const std::vector<Zone>* zones = nullptr;
const std::unordered_set<int>* exploredZones = nullptr;
const std::unordered_set<int>* exploredOverlays = nullptr;
const std::unordered_map<uint32_t, std::string>* areaNameByAreaId = nullptr;
// FBO dimensions for overlay coordinate math
int fboW = 1024;
int fboH = 768;
// ZMP pixel map for continent-view hover (128x128 grid of AreaTable IDs)
const std::array<uint32_t, 128 * 128>* zmpGrid = nullptr;
bool hasZmpData = false;
// Function to resolve AreaTable ID → zone index (from DataRepository)
int (*zmpResolveZoneIdx)(const void* repo, uint32_t areaId) = nullptr;
const void* zmpRepoPtr = nullptr; // opaque DataRepository pointer
// ZMP-derived zone bounding boxes (zone index → UV rect on display)
const std::unordered_map<int, ZmpRect>* zmpZoneBounds = nullptr;
};
/// Interface for an overlay layer rendered on top of the composite map.
class IOverlayLayer {
public:
virtual ~IOverlayLayer() = default;
virtual void render(const LayerContext& ctx) = 0;
};
/// Orchestrates rendering of all registered overlay layers.
class OverlayRenderer {
public:
void addLayer(std::unique_ptr<IOverlayLayer> layer);
void render(const LayerContext& ctx);
private:
std::vector<std::unique_ptr<IOverlayLayer>> layers_;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,65 @@
// view_state_machine.hpp — Navigation state and transitions for the world map.
// Extracted from WorldMap zoom/enter methods (Phase 6 of refactoring plan).
// SRP — pure state machine, no rendering or input code.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
namespace wowee {
namespace rendering {
namespace world_map {
/// Manages the current view level and transitions between views.
class ViewStateMachine {
public:
ViewLevel currentLevel() const { return level_; }
const TransitionState& transition() const { return transition_; }
int continentIdx() const { return continentIdx_; }
int currentZoneIdx() const { return currentIdx_; }
bool cosmicEnabled() const { return cosmicEnabled_; }
void setContinentIdx(int idx) { continentIdx_ = idx; }
void setCurrentZoneIdx(int idx) { currentIdx_ = idx; }
void setCosmicEnabled(bool enabled) { cosmicEnabled_ = enabled; }
void setLevel(ViewLevel level) { level_ = level; }
/// Result of a zoom/navigate operation.
struct ZoomResult {
bool changed = false;
ViewLevel newLevel = ViewLevel::ZONE;
int targetIdx = -1; // zone index to load/composite
};
/// Attempt to zoom in. hoveredZoneIdx is the zone under the cursor (-1 if none).
/// playerZoneIdx is the zone the player is standing in (-1 if none).
ZoomResult zoomIn(int hoveredZoneIdx, int playerZoneIdx);
/// Attempt to zoom out one level.
ZoomResult zoomOut();
/// Navigate to world view. Returns the root/fallback continent index to composite.
ZoomResult enterWorldView();
/// Navigate to cosmic view.
ZoomResult enterCosmicView();
/// Navigate directly into a zone from continent view.
ZoomResult enterZone(int zoneIdx);
/// Advance transition animation. Returns true while animating.
bool updateTransition(float deltaTime);
private:
void startTransition(ViewLevel from, ViewLevel to, float duration = 0.3f);
ViewLevel level_ = ViewLevel::CONTINENT;
TransitionState transition_;
int continentIdx_ = -1;
int currentIdx_ = -1;
bool cosmicEnabled_ = true;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,64 @@
// world_map_facade.hpp — Public API for the world map system.
// Drop-in replacement for the monolithic WorldMap class (Phase 10 of refactoring plan).
// Facade pattern — hides internal complexity behind the same public interface.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <glm/glm.hpp>
#include <string>
#include <vector>
#include <memory>
#include <vulkan/vulkan.h>
namespace wowee {
namespace rendering {
class VkContext;
}
namespace pipeline { class AssetManager; }
namespace rendering {
namespace world_map {
class WorldMapFacade {
public:
/// Backward-compatible alias for old WorldMap::QuestPoi usage.
using QuestPoi = QuestPOI;
WorldMapFacade();
~WorldMapFacade();
bool initialize(VkContext* ctx, pipeline::AssetManager* am);
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<PartyDot> dots);
void setTaxiNodes(std::vector<TaxiNode> nodes);
void setQuestPois(std::vector<QuestPOI> pois);
void setCorpsePos(bool hasCorpse, glm::vec3 renderPos);
bool isOpen() const;
void close();
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee
// Backward-compatible alias for gradual migration
namespace wowee {
namespace rendering {
using WorldMap = world_map::WorldMapFacade;
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,132 @@
// world_map_types.hpp — Vulkan-free domain types for the world map system.
// Extracted from rendering/world_map.hpp (Phase 1 of refactoring plan).
// Consumers of these types do NOT need Vulkan/VMA headers.
#pragma once
#include <glm/glm.hpp>
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace rendering {
namespace world_map {
// ── View hierarchy ───────────────────────────────────────────
enum class ViewLevel { COSMIC, WORLD, CONTINENT, ZONE };
// ── Transition animation ─────────────────────────────────────
struct TransitionState {
bool active = false;
float progress = 0.0f; // 0.0 → 1.0
float duration = 0.2f; // seconds
ViewLevel fromLevel = ViewLevel::ZONE;
ViewLevel toLevel = ViewLevel::ZONE;
};
// ── Zone faction & metadata ──────────────────────────────────
enum class ZoneFaction : uint8_t { Neutral, Alliance, Horde, Contested };
struct ZoneMeta {
uint8_t minLevel = 0, maxLevel = 0;
ZoneFaction faction = ZoneFaction::Neutral;
};
// ── Cosmic view (cross-realm) ────────────────────────────────
struct CosmicMapEntry {
int mapId = 0;
std::string label;
// Clickable region in UV space (0-1 range on the cosmic composite)
float uvLeft = 0, uvTop = 0, uvRight = 0, uvBottom = 0;
};
// ── Zone bounds (shared between Zone and coordinate projection) ──
struct ZoneBounds {
float locLeft = 0, locRight = 0;
float locTop = 0, locBottom = 0;
};
/// ZMP-derived bounding rectangle in display UV [0,1] coordinates.
/// Computed by scanning the ZMP grid for each zone's area ID.
/// Maps pixel-for-pixel to the continent map tiles shown on screen.
struct ZmpRect {
float uMin = 0, uMax = 0;
float vMin = 0, vMax = 0;
bool valid = false;
};
// ── Overlay entry (exploration overlay from WorldMapOverlay.dbc) ──
struct OverlayEntry {
uint32_t areaIDs[4] = {}; // Up to 4 AreaTable IDs contributing to this overlay
std::string textureName; // Texture prefix (e.g., "Goldshire")
uint16_t texWidth = 0, texHeight = 0; // Overlay size in pixels
uint16_t offsetX = 0, offsetY = 0; // Pixel offset within zone map
int tileCols = 0, tileRows = 0;
// HitRect from WorldMapOverlay.dbc fields 13-16 — fast AABB pre-filter for
// subzone hover detection in zone view (avoids sampling every overlay).
uint16_t hitRectLeft = 0, hitRectRight = 0;
uint16_t hitRectTop = 0, hitRectBottom = 0;
// NOTE: texture pointers are managed by CompositeRenderer, not stored here.
bool tilesLoaded = false;
};
// ── Zone (from WorldMapArea.dbc) ─────────────────────────────
struct Zone {
uint32_t wmaID = 0;
uint32_t areaID = 0; // 0 = continent level
std::string areaName; // texture folder name (from DBC)
ZoneBounds bounds;
uint32_t displayMapID = 0;
uint32_t parentWorldMapID = 0;
std::vector<uint32_t> exploreBits; // all AreaBit indices (zone + subzones)
std::vector<OverlayEntry> overlays;
};
// ── Party member dot (UI layer → world map overlay) ──────────
struct PartyDot {
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 (UI layer → world map overlay) ─
struct TaxiNode {
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
};
// ── Area Point of Interest from AreaPOI.dbc ──────────────────
struct POI {
uint32_t id = 0;
uint32_t importance = 0; ///< 0=small, 1=medium, 2=large (capital)
uint32_t iconType = 0; ///< Icon category from AreaPOI.dbc
uint32_t factionId = 0; ///< 0=neutral, 67=Horde, 469=Alliance
float wowX = 0, wowY = 0, wowZ = 0; ///< Canonical WoW coordinates
uint32_t mapId = 0; ///< WoW internal map ID
std::string name;
std::string description;
};
// ── Quest POI marker (from SMSG_QUEST_POI_QUERY_RESPONSE) ────
struct QuestPOI {
float wowX = 0, wowY = 0; ///< Canonical WoW coordinates (centroid)
std::string name; ///< Quest title
};
} // namespace world_map
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,37 @@
// zone_metadata.hpp — Zone level ranges, faction data, and label formatting.
// Extracted from WorldMap::initZoneMeta and inline label formatting
// (Phase 4 of refactoring plan). DRY — formatLabel used by multiple layers.
#pragma once
#include "rendering/world_map/world_map_types.hpp"
#include <string>
#include <unordered_map>
namespace wowee {
namespace rendering {
namespace world_map {
class ZoneMetadata {
public:
/// Initialize the zone metadata table (level ranges, factions).
void initialize();
/// Look up metadata for a zone by area name. Returns nullptr if not found.
const ZoneMeta* find(const std::string& areaName) const;
/// Format a zone label with level range and faction tag.
/// e.g. "Elwynn (1-10) [Alliance]"
static std::string formatLabel(const std::string& areaName,
const ZoneMeta* meta);
/// Format hover label with level range and bracket-tag for faction.
static std::string formatHoverLabel(const std::string& areaName,
const ZoneMeta* meta);
private:
std::unordered_map<std::string, ZoneMeta> table_;
};
} // namespace world_map
} // namespace rendering
} // namespace wowee