10#define GLFW_EXPOSE_NATIVE_WIN32
11#include <GLFW/glfw3native.h>
14#include "imgui_internal.h"
71 AppState& st, std::function<
void()> handleInput)
316 m_state.
scene.
root = std::make_unique<Attachment>(
nullptr,
nullptr, -1, -1);
385 LOG_INFO <<
"WoW Model Viewer shutdown complete.";
398 CreateDirectoryA(
"userSettings",
nullptr);
404 LOG_INFO <<
"==============================================";
408 LOG_INFO <<
"==============================================";
413 if (HWND hConsole = GetConsoleWindow())
431 LOG_INFO <<
"OpenGL initialisation complete.";
440 auto now = std::chrono::steady_clock::now();
444 if (dt > 0.1f) dt = 0.1f;
445 if (dt < 0.0f) dt = 0.0f;
479 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0f, 8.0f));
480 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(14.0f, 8.0f));
483 if (ImGui::BeginMenu(
"File"))
485 if (ImGui::MenuItem(
"Load WoW",
nullptr,
false,
489 if (ImGui::MenuItem(
"Import from URL...",
nullptr,
false,
497 if (ImGui::MenuItem(
"Close Model",
nullptr,
false,
501 if (ImGui::MenuItem(
"Export...",
nullptr,
false,
505 ImGui::SetWindowFocus(
"Export");
507 if (ImGui::MenuItem(
"Screenshot...",
"Ctrl+S"))
510 ImGui::SetWindowFocus(
"Screenshot");
513 if (ImGui::MenuItem(
"Exit",
"Alt+F4"))
518 if (ImGui::BeginMenu(
"Edit"))
520 if (ImGui::MenuItem(
"Reset Camera",
"Numpad 5",
false,
528 if (ImGui::BeginMenu(
"Window"))
530 ImGui::MenuItem(
"3D Viewport",
nullptr, &
m_state.
ui.
panel(
"3D Viewport"));
531 ImGui::MenuItem(
"Character Viewer",
nullptr, &
m_state.
ui.
panel(
"Character Viewer"));
532 ImGui::MenuItem(
"File Browser",
nullptr, &
m_state.
ui.
panel(
"File Browser"));
533 ImGui::MenuItem(
"Animation",
nullptr, &
m_state.
ui.
panel(
"Animation"));
534 ImGui::MenuItem(
"Viewport Options",
nullptr, &
m_state.
ui.
panel(
"Viewport Options"));
536 ImGui::MenuItem(
"Item Sets",
nullptr, &
m_state.
ui.
panel(
"Item Sets"));
537 ImGui::MenuItem(
"NPC Browser",
nullptr, &
m_state.
ui.
panel(
"NPC Browser"));
538 ImGui::MenuItem(
"Item Browser",
nullptr, &
m_state.
ui.
panel(
"Item Browser"));
540 ImGui::MenuItem(
"Screenshot",
nullptr, &
m_state.
ui.
panel(
"Screenshot"));
541 ImGui::MenuItem(
"Presets",
nullptr, &
m_state.
ui.
panel(
"Presets"));
549 if (ImGui::BeginMenu(
"Options"))
551 if (ImGui::MenuItem(
"Language / Locale..."))
554 if (ImGui::MenuItem(
"Settings..."))
559 if (ImGui::BeginMenu(
"Help"))
561 if (ImGui::MenuItem(
"About..."))
567 std::string statusText;
572 int curFrame = 0, totalFrames = 0;
578 statusText = std::format(
"FPS: {:.0f} | {} | V:{} B:{} T:{} | Frame: {}/{}",
581 curFrame, totalFrames);
590 ImGui::PopStyleVar(2);
596 const ImGuiViewport* mainVp = ImGui::GetMainViewport();
597 ImGui::SetNextWindowPos(ImVec2(mainVp->WorkPos.x, mainVp->WorkPos.y + titleBarH));
598 ImGui::SetNextWindowSize(ImVec2(mainVp->WorkSize.x, mainVp->WorkSize.y - titleBarH));
599 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
600 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
601 ImGui::Begin(
"##DockHost",
nullptr,
602 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
603 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
604 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
605 ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus |
606 ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoBackground);
607 ImGui::PopStyleVar(2);
608 ImGuiID dockspace_id = ImGui::DockSpace(ImGui::GetID(
"MainDockspace"));
616 ImGui::DockBuilderRemoveNode(dockspace_id);
617 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
621 ImGui::DockBuilderSetNodeSize(dockspace_id,
622 ImVec2(
static_cast<float>(fw),
static_cast<float>(fh)));
624 ImGuiID dock_left, dock_center;
625 ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.15f, &dock_left, &dock_center);
628 ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Right, 0.20f, &dock_right, &dock_center);
631 ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Down, 0.25f, &dock_bottom, &dock_center);
633 ImGui::DockBuilderDockWindow(
"File Browser", dock_left);
634 ImGui::DockBuilderDockWindow(
"NPC Browser", dock_left);
635 ImGui::DockBuilderDockWindow(
"Item Browser", dock_left);
636 ImGui::DockBuilderDockWindow(
"3D Viewport", dock_center);
637 ImGui::DockBuilderDockWindow(
"Character Viewer", dock_center);
638 ImGui::DockBuilderDockWindow(
"Animation", dock_bottom);
639 ImGui::DockBuilderDockWindow(
"Screenshot", dock_bottom);
640 ImGui::DockBuilderDockWindow(
"Export", dock_bottom);
641 ImGui::DockBuilderDockWindow(
"Presets", dock_bottom);
642 ImGui::DockBuilderDockWindow(
"Viewport Options", dock_right);
643 ImGui::DockBuilderDockWindow(
"Mounts", dock_right);
644 ImGui::DockBuilderDockWindow(
"Item Sets", dock_right);
645 ImGui::DockBuilderDockWindow(
"Log", dock_bottom);
646 ImGui::DockBuilderFinish(dockspace_id);
656 if (st.
ui.
panel(
"Character Viewer"))
658 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 4));
659 if (ImGui::Begin(
"Character Viewer", &st.
ui.
panel(
"Character Viewer")))
665 ImGui::PopStyleVar();
669 if (st.
ui.
panel(
"3D Viewport"))
671 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
672 if (ImGui::Begin(
"3D Viewport", &st.
ui.
panel(
"3D Viewport")))
674 ImVec2 panelSize = ImGui::GetContentRegionAvail();
675 int vpW =
static_cast<int>(panelSize.x);
676 int vpH =
static_cast<int>(panelSize.y);
677 if (vpW > 0 && vpH > 0)
684 glEnable(GL_LIGHTING);
685 glEnable(GL_TEXTURE_2D);
686 glEnable(GL_DEPTH_TEST);
687 glDepthFunc(GL_LEQUAL);
689 glEnable(GL_TEXTURE_2D);
690 glDisable(GL_LIGHTING);
691 glDepthMask(GL_FALSE);
693 root->drawParticles();
695 glDepthMask(GL_TRUE);
698 ImGui::Image(
static_cast<ImTextureID
>(
static_cast<uintptr_t
>(st.
scene.
fbo.
colorTex)),
699 panelSize, ImVec2(0, 1), ImVec2(1, 0));
700 if (ImGui::IsItemHovered())
705 ImGui::PopStyleVar();
709 if (st.
ui.
panel(
"File Browser"))
724 if (ImGui::Begin(
"Animation", &st.
ui.
panel(
"Animation")))
726 auto ctx = buildAnimCtx(st);
736 if (st.
ui.
panel(
"Viewport Options"))
738 if (ImGui::Begin(
"Viewport Options", &st.
ui.
panel(
"Viewport Options")))
740 auto ctx = buildViewportOptsCtx(st);
749 if (ImGui::Begin(
"Mounts", &st.
ui.
panel(
"Mounts")))
751 auto ctx = buildMountsCtx(st);
760 if (ImGui::Begin(
"Item Sets", &st.
ui.
panel(
"Item Sets")))
762 auto ctx = buildItemSetsCtx(st);
769 if (st.
ui.
panel(
"NPC Browser"))
771 if (ImGui::Begin(
"NPC Browser", &st.
ui.
panel(
"NPC Browser")))
773 auto ctx = buildNpcBrowserCtx(st);
780 if (st.
ui.
panel(
"Item Browser"))
782 if (ImGui::Begin(
"Item Browser", &st.
ui.
panel(
"Item Browser")))
784 auto ctx = buildItemBrowserCtx(st);
793 if (ImGui::Begin(
"Export", &st.
ui.
panel(
"Export")))
795 auto ctx = buildExportCtx(st);
804 if (ImGui::Begin(
"Screenshot", &st.
ui.
panel(
"Screenshot")))
806 auto ctx = buildScreenshotCtx(st);
815 if (ImGui::Begin(
"Presets", &st.
ui.
panel(
"Presets")))
817 auto ctx = buildPresetsCtx(st);
826 if (ImGui::Begin(
"Log", &st.
ui.
panel(
"Log")))
828 auto ctx = buildLogCtx(st);
837 ImGui::SetNextWindowSize(ImVec2(480, 340), ImGuiCond_FirstUseEver);
838 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
839 ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
840 if (ImGui::Begin(
"Settings", &
m_showSettings, ImGuiWindowFlags_NoDocking))
void pollEvents()
Poll platform events (must be called once per frame).
void setIcon(const char *fallbackPath)
Load and set the window icon from a PNG file.
float queryDpiScale() const
Query the monitor content-scale (DPI).
void requestClose()
Signal the window to close.
void framebufferSize(int &w, int &h) const
Retrieve the framebuffer size in pixels.
bool init(int width, int height, const char *title)
GLFWwindow * handle() const noexcept
Raw GLFW handle — needed by ImGui backends and CustomTitleBar.
void swapBuffers()
Swap the front/back framebuffers.
bool shouldClose() const
Returns true when the user (or code) has requested the window to close.
void handleViewportInput()
InputManager m_inputManager
void drawTitleBarAndMenus()
Abstract base class representing a file within the game data archive.
void rebuildFontAtlasIfDirty(bool &fontsDirty, const std::vector< FontEntry > &fonts, int selectedFont, float fontSize, float dpiScale)
If the fontsDirty flag is set, rebuild the atlas and clear the flag.
void shutdown()
Shut down ImGui backends and destroy the context.
void discoverFonts(std::vector< FontEntry > &fonts, int &selectedFont)
void endFrame(const AppWindow &window)
bool init(GLFWwindow *window, float dpiScale, UIState *uiState)
void beginFrame()
Start a new ImGui frame (backend NewFrame + ImGui::NewFrame).
void buildFontAtlas(const std::vector< FontEntry > &fonts, int selectedFont, float fontSize, float dpiScale)
Build (or rebuild) the font atlas from the currently selected font.
void renderScene(ViewportFBO &fbo, int w, int h, const OrbitCamera &camera, float fov, const glm::vec3 &clearColor, bool drawGrid, const std::function< void()> &drawObjects)
void init()
Create GPU resources. Call once after the OpenGL context is current.
void shutdown()
Release GPU resources.
Log output sink that writes messages to the standard console (stdout).
Thread-safe log output sink that writes messages to a file on disk.
Core WoW .m2 model: geometry, animation, textures, and character data.
AnimManager * animManager
std::vector< NPCRecord > npcs
void draw(DrawContext &ctx)
Draw the Animation panel contents (call between ImGui::Begin / End).
void drawConfigPopup(AppState &app)
Draw the game config selection popup (locale / product picker).
void drawAboutDialog(AppState &app)
Draw the About dialog with version and credits.
void drawLanguageDialog(AppState &app)
Draw the Language / Locale selection dialog.
void drawImportDialog(AppState &app)
Draw the URL import dialog (Armory / Wowhead).
void draw(const DrawContext &ctx)
Draw the Character Viewer panel contents (call between ImGui::Begin / End).
void end(GLFWwindow *window, const char *statusText)
bool begin(GLFWwindow *window)
float height() noexcept
Title bar height in logical pixels (call after begin()).
void init(GLFWwindow *window)
void draw(DrawContext &ctx)
GameFile * draw(bool &visible, const LoadState &load)
void shutdown()
Free all internal allocations (call at application shutdown).
void pollAsyncLoad(AppState &app)
std::string getLoadStatus(AppState &app)
void beginLoadWoW(AppState &app)
void draw(DrawContext &ctx)
void draw(DrawContext &ctx)
void draw(DrawContext &ctx)
void rebuildItemBrowseFilter(AppState &app, const ItemDatabase &db)
void rebuildMountFilter(AppState &app)
void applySkin(WoWModel *model, int skinIndex, AppState &app)
Apply a creature / item skin variant to the model.
void mountCharacter(int displayID, GameFile *creatureFile, AppState &app, float fov)
void rebuildNpcFilter(AppState &app, const std::vector< NPCRecord > &npcList)
void loadModel(GameFile *file, AppState &app, float fov)
Load a .m2 GameFile, create a WoWModel, attach to root, init controls.
void buildStartOutfits(WoWModel *model, AppState &app)
void clearModel(AppState &app)
Tear down the current model and reset related state.
void applyStartOutfit(WoWModel *model, int outfitId, AppState &app, const ItemDatabase &db)
void rebuildStartOutfitFilter(AppState &app)
void dismountCharacter(AppState &app, float fov)
void loadNPC(unsigned int creatureID, AppState &app, float fov)
void loadItemModel(unsigned int itemId, AppState &app, float fov)
void applyItemSet(WoWModel *model, int setId, AppState &app, const ItemDatabase &db)
void rebuildItemSetFilter(AppState &app)
void resetCameraToModel(OrbitCamera &camera, const WoWModel *model, float fov)
Reset the orbit camera to frame the given model.
void buildMountList(AppState &app)
WoWModel * getLoadedModel(AppState &app)
Return the currently loaded WoWModel (first child of root), or nullptr.
void buildItemSets(AppState &app)
void draw(DrawContext &ctx)
void draw(DrawContext &ctx)
void load(const char *path, AppState &app)
Load a previously saved character preset from an INI file.
void save(const char *path, AppState &app)
void draw(DrawContext &ctx)
void draw(DrawContext &ctx)
void draw(DrawContext &ctx)
void apply(const InputState &input, OrbitCamera &camera)
Apply the resolved input state to the orbit camera.
void draw(DrawContext &ctx)
Draw the Viewport Options panel contents (call between Begin / End).
Per-frame context passed by the caller so the panel never touches globals.
std::function< void(WoWModel *, int)> applySkin
std::vector< SkinEntry > * skinEntries
std::vector< AnimEntry > * animEntries
int * selectedSecondaryAnim
std::function< WoWModel *()> getLoadedModel
std::vector< SkinEntry > skinEntries
int selectedSecondaryAnim
std::vector< AnimEntry > animEntries
static constexpr const char * imguiIniPath
void load()
Load from Config.ini (also restores the current ThemeManager theme).
Top-level aggregate of all mutable application state.
std::vector< size_t > itemBrowseFiltered
bool itemBrowseFilterDirty
std::vector< MountEntry > mountList
std::string itemBrowseSearchBuf
std::vector< std::string > creatureModelNames
std::vector< StartOutfitEntry > startOutfits
std::string itemSetSearchBuf
std::vector< size_t > mountFiltered
std::vector< size_t > npcFiltered
ParticleColorState pcrState
std::string mountSearchBuf
std::vector< size_t > startOutfitFiltered
std::vector< GeosetGroupEntry > geosetGroups
std::string startOutfitSearchBuf
std::vector< GameFile * > creatureModels
std::vector< ItemSetEntry > itemSets
bool startOutfitFilterDirty
std::vector< size_t > itemSetFiltered
std::vector< CustomizationOption > customizationOptions
Per-frame context passed by the caller so the panel never touches globals.
std::function< void()> handleViewportInput
std::vector< AnimEntry > * animEntries
std::function< WoWModel *()> getLoadedModel
std::function< void(GameFile *)> loadModel
std::vector< CustomizationOption > * customizationOptions
Per-frame context for the export panel.
std::vector< std::unique_ptr< ExporterPlugin > > * exporters
std::vector< AnimEntry > * animEntries
std::vector< char > * exportAnimChecked
std::function< WoWModel *()> getLoadedModel
std::string * exportStatus
std::vector< std::unique_ptr< ExporterPlugin > > exporters
std::string screenshotPath
std::string screenshotStatus
std::vector< std::unique_ptr< ImporterPlugin > > importers
std::vector< char > exportAnimChecked
Per-frame context for the item browser panel.
std::function< void()> rebuildItemBrowseFilter
const ItemDatabase * items
std::string * itemBrowseSearchBuf
std::function< void(unsigned int)> loadItemModel
std::vector< size_t > * itemBrowseFiltered
bool * itemBrowseFilterDirty
Per-frame context for the item sets panel.
std::function< void(WoWModel *, int)> applyStartOutfit
std::function< void()> rebuildItemSetFilter
std::vector< size_t > * startOutfitFiltered
std::function< void(WoWModel *, int)> applyItemSet
std::vector< ItemSetEntry > * itemSets
bool * itemSetFilterDirty
std::function< void(WoWModel *)> buildStartOutfits
std::vector< size_t > * itemSetFiltered
std::function< WoWModel *()> getLoadedModel
std::vector< StartOutfitEntry > * startOutfits
std::string * startOutfitSearchBuf
std::string * itemSetSearchBuf
std::function< void()> buildItemSets
std::function< void()> rebuildStartOutfitFilter
bool * startOutfitFilterDirty
std::atomic< float > loadProgress
std::atomic< bool > initDB
Per-frame context for the log panel.
std::vector< std::string > * logLines
Per-frame context for the mounts panel.
std::vector< GameFile * > * creatureModels
std::function< WoWModel *()> getLoadedModel
std::function< void()> dismountCharacter
std::string * mountSearchBuf
std::function< void()> buildMountList
std::function< void()> rebuildMountFilter
std::vector< size_t > * mountFiltered
std::function< void(int, GameFile *)> mountCharacter
std::vector< std::string > * creatureModelNames
std::vector< MountEntry > * mountList
Per-frame context for the NPC browser panel.
std::vector< size_t > * npcFiltered
const std::vector< NPCRecord > * npcs
std::string * npcSearchBuf
std::function< void()> rebuildNpcFilter
std::function< void(unsigned int)> loadNPC
Per-frame context for the presets panel.
std::function< void(const char *)> loadPreset
std::function< void(const char *)> savePreset
std::string * presetStatus
std::chrono::steady_clock::time_point lastTick
std::unique_ptr< Attachment > root
Per-frame context for the screenshot panel.
std::string * screenshotPath
std::string * screenshotStatus
Per-frame context for the settings panel.
std::filesystem::path * folderPickerCurrent
std::vector< std::filesystem::path > * folderPickerEntries
std::function< std::string()> getLoadStatus
bool * folderPickerNeedsRefresh
std::vector< FontEntry > * availableFonts
std::atomic< float > * loadProgress
bool folderPickerNeedsRefresh
std::vector< std::filesystem::path > folderPickerEntries
bool importPopupJustOpened
bool & panel(const char *name)
std::vector< FontEntry > availableFonts
std::vector< std::string > logLines
std::filesystem::path folderPickerCurrent
void destroy()
Release all GPU resources.
GLuint colorTex
Colour attachment (GL_RGBA8 texture).
Per-frame context for the viewport options panel.
std::function< void(WoWModel *, int)> applySkin
std::function< WoWModel *()> getLoadedModel
std::vector< GeosetGroupEntry > * geosetGroups
std::function< void()> resetCamera
ParticleColorState * pcrState