Files
WoWee/include/rendering/minimap.hpp
Kelsi 7e987328d2 feat: draw the real minimap inside FrameXML's minimap frame
The map is a Vulkan pass of its own drawn inside the main render pass,
not an image the widget renderer could place, so it is told where the
frame ended up rather than the frame being handed a picture. Minimap
gained a settable screen rect; unset it still goes in the top-right
corner, which is where this client's own interface puts it.

Handing over the minimap without this drew the ring and its buttons
around nothing.
2026-08-01 23:24:17 -07:00

152 lines
5.5 KiB
C++

#pragma once
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <chrono>
#include <memory>
#include <string>
#include <unordered_map>
#include <deque>
#include <algorithm>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class Camera;
class VkContext;
class VkTexture;
class VkRenderTarget;
class Minimap {
public:
Minimap();
~Minimap();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, int size = 200);
void shutdown();
void recreatePipelines();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
void setMapName(const std::string& name);
/// Off-screen composite pass — call BEFORE the main render pass begins.
void compositePass(VkCommandBuffer cmd, const glm::vec3& centerWorldPos);
/// Display quad — call INSIDE the main render pass.
void render(VkCommandBuffer cmd, const Camera& playerCamera,
const glm::vec3& centerWorldPos, int screenWidth, int screenHeight,
float playerOrientation = 0.0f, bool hasPlayerOrientation = false);
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
void toggle() { enabled = !enabled; }
void setViewRadius(float radius) { viewRadius = radius; }
void setRotateWithCamera(bool rotate) { rotateWithCamera = rotate; }
bool isRotateWithCamera() const { return rotateWithCamera; }
void setSquareShape(bool square) { squareShape = square; }
bool isSquareShape() const { return squareShape; }
float getViewRadius() const { return viewRadius; }
void zoomIn() { viewRadius = std::max(100.0f, viewRadius - 50.0f); }
void zoomOut() { viewRadius = std::min(800.0f, viewRadius + 50.0f); }
void setOpacity(float opacity) { opacity_ = opacity; }
/// Where the map is drawn, in pixels from the top-left of the window.
///
/// Unset, it goes in the top-right corner at its own size, which is where
/// this client's own interface puts it. FrameXML puts it inside a frame it
/// owns, so when the original interface is drawing the minimap the rect of
/// that frame is handed here instead — this is a Vulkan pass of its own
/// rather than an image the widget renderer could draw, so the map moves to
/// the frame rather than the frame receiving the map.
void setScreenRect(float x, float y, float w, float h) {
rectX_ = x; rectY_ = y; rectW_ = w; rectH_ = h; haveRect_ = true;
}
void clearScreenRect() { haveRect_ = false; }
float getArrowRotation() const { return arrowRotation_; }
VkDescriptorSet getArrowDS() const { return arrowDS_; }
// Public accessors for WorldMap
VkTexture* getOrLoadTileTexture(int tileX, int tileY);
void ensureTRSParsed() { if (!trsParsed) parseTRS(); }
const std::string& getMapName() const { return mapName; }
private:
void parseTRS();
void updateTileDescriptors(uint32_t frameIdx, int centerTileX, int centerTileY);
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
std::string mapName = "Azeroth";
// TRS lookup: "Azeroth\map32_49" → "e7f0dea73ee6baca78231aaf4b7e772a"
std::unordered_map<std::string, std::string> trsLookup;
bool trsParsed = false;
// Tile texture cache: hash → VkTexture
// Evicted (FIFO) when the count of successfully-loaded tiles exceeds MAX_TILE_CACHE.
static constexpr size_t MAX_TILE_CACHE = 128;
std::unordered_map<std::string, std::unique_ptr<VkTexture>> tileTextureCache;
std::deque<std::string> tileInsertionOrder; // hashes of successfully loaded tiles, oldest first
std::unique_ptr<VkTexture> noDataTexture;
// Composite render target (3x3 tiles = 768x768)
std::unique_ptr<VkRenderTarget> compositeTarget;
static constexpr int TILE_PX = 256;
static constexpr int COMPOSITE_PX = TILE_PX * 3; // 768
// Shared quad vertex buffer (6 verts, pos2 + uv2 = 16 bytes/vert)
::VkBuffer quadVB = VK_NULL_HANDLE;
VmaAllocation quadVBAlloc = VK_NULL_HANDLE;
// Descriptor resources (shared layout: 1 combined image sampler at binding 0)
VkDescriptorSetLayout samplerSetLayout = VK_NULL_HANDLE;
VkDescriptorPool descPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_DESC_SETS = 24;
// Tile composite pipeline (renders into VkRenderTarget)
VkPipeline tilePipeline = VK_NULL_HANDLE;
VkPipelineLayout tilePipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet tileDescSets[2][9] = {}; // [frameInFlight][tileSlot]
// Display pipeline (renders into main render pass)
VkPipeline displayPipeline = VK_NULL_HANDLE;
VkPipelineLayout displayPipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet displayDescSet = VK_NULL_HANDLE;
int mapSize = 200;
float viewRadius = 400.0f;
bool enabled = true;
bool rotateWithCamera = false;
bool squareShape = false;
float opacity_ = 1.0f;
bool haveRect_ = false;
float rectX_ = 0.0f, rectY_ = 0.0f, rectW_ = 0.0f, rectH_ = 0.0f;
// Throttling
float updateIntervalSec = 0.25f;
float updateDistance = 6.0f;
std::chrono::steady_clock::time_point lastUpdateTime{};
glm::vec3 lastUpdatePos{0.0f};
bool hasCachedFrame = false;
// Tile tracking
int lastCenterTileX = -1;
int lastCenterTileY = -1;
// Player arrow texture (MinimapArrow.blp)
std::unique_ptr<VkTexture> arrowTexture_;
VkDescriptorSet arrowDS_ = VK_NULL_HANDLE;
bool arrowLoadAttempted_ = false;
float arrowRotation_ = 0.0f;
};
} // namespace rendering
} // namespace wowee