WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
Application.cpp
Go to the documentation of this file.
1#include "Application.h"
2
3#ifdef _WIN32
4#include <windows.h>
5#endif
6
7#include <glad/gl.h>
8#include <GLFW/glfw3.h>
9#ifdef _WIN32
10#define GLFW_EXPOSE_NATIVE_WIN32
11#include <GLFW/glfw3native.h>
12#endif
13#include "imgui.h"
14#include "imgui_internal.h"
15
16#include <algorithm>
17#include <chrono>
18#include <filesystem>
19#include <format>
20#include <memory>
21#include <string>
22
23#include "GlobalSettings.h"
24#include "Logger.h"
25#include "LogOutputFile.h"
26#include "LogOutputConsole.h"
27#include "video.h"
28#include "Attachment.h"
29#include "WoWModel.h"
30#include "Renderer.h"
31#include "CustomTitleBar.h"
32#include "ViewportController.h"
33
34// Panel headers
35#include "FileBrowserPanel.h"
37#include "AnimationPanel.h"
39#include "ExportPanel.h"
40#include "ScreenshotPanel.h"
41#include "LogPanel.h"
42#include "PresetsPanel.h"
43#include "MountsPanel.h"
44#include "ItemSetsPanel.h"
45#include "NpcBrowserPanel.h"
46#include "ItemBrowserPanel.h"
47#include "SettingsPanel.h"
48#include "AppDialogs.h"
49
50// Exporters / Importers
51#include "OBJExporter.h"
52#include "FBXExporter.h"
53#include "ArmoryImporter.h"
54#include "WowheadImporter.h"
55
56// Helper modules (namespace-based, take AppState&)
57#include "GameLoader.h"
58#include "ModelLoader.h"
59#include "PresetManager.h"
60#include "database.h"
61
62// ============================================================================
63// DrawContext factory helpers — wire AppState fields into per-panel contexts.
64// Each function assembles a single panel's DrawContext from AppState so that
65// drawPanels() stays concise and each panel remains decoupled from AppState.
66// ============================================================================
67
68namespace {
69
70CharacterViewerPanel::DrawContext buildCharViewerCtx(
71 AppState& st, std::function<void()> handleInput)
72{
75 ctx.isDBReady = st.loading.initDB;
76 ctx.isChar = st.scene.isChar;
80 ctx.renderer = &st.scene.renderer;
81 ctx.fbo = &st.scene.fbo;
82 ctx.camera = &st.scene.camera;
83 ctx.root = st.scene.root.get();
84 ctx.fov = video.fov;
85 ctx.bgColor = st.settings.bgColor;
86 ctx.drawGrid = st.settings.drawGrid;
87 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
88 ctx.loadModel = [&st](GameFile* f) { ModelLoader::loadModel(f, st, video.fov); };
89 ctx.handleViewportInput = std::move(handleInput);
90 return ctx;
91}
92
94{
96 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
99 ctx.animSpeed = &st.anim.animSpeed;
100 ctx.loopCount = &st.anim.loopCount;
101 ctx.lockAnims = &st.anim.lockAnims;
104 ctx.mouthSpeed = &st.anim.mouthSpeed;
105 ctx.skinEntries = &st.anim.skinEntries;
107 ctx.blpSkin[0] = st.anim.blpSkin[0];
108 ctx.blpSkin[1] = st.anim.blpSkin[1];
109 ctx.blpSkin[2] = st.anim.blpSkin[2];
110 ctx.applySkin = [&st](WoWModel* m, int idx) { ModelLoader::applySkin(m, idx, st); };
111 return ctx;
112}
113
114ViewportOptionsPanel::DrawContext buildViewportOptsCtx(AppState& st)
115{
117 ctx.renderer = &st.scene.renderer;
118 ctx.drawGrid = &st.settings.drawGrid;
119 ctx.bgColor = &st.settings.bgColor;
120 ctx.camera = &st.scene.camera;
121 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
123 ctx.pcrState = &st.browsers.pcrState;
125 ctx.applySkin = [&st](WoWModel* m, int idx) { ModelLoader::applySkin(m, idx, st); };
126 ctx.resetCamera = [&st]() {
128 };
129 return ctx;
130}
131
132MountsPanel::DrawContext buildMountsCtx(AppState& st)
133{
135 ctx.isChar = st.scene.isChar;
136 ctx.isMounted = st.scene.isMounted;
137 ctx.mountList = &st.browsers.mountList;
142 ctx.mountTab = &st.browsers.mountTab;
144 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
145 ctx.buildMountList = [&st]() { ModelLoader::buildMountList(st); };
147 ctx.mountCharacter = [&st](int d, GameFile* f) { ModelLoader::mountCharacter(d, f, st, video.fov); };
149 return ctx;
150}
151
152ItemSetsPanel::DrawContext buildItemSetsCtx(AppState& st)
153{
155 ctx.isChar = st.scene.isChar;
156 ctx.itemSets = &st.browsers.itemSets;
166 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
167 ctx.buildItemSets = [&st]() { ModelLoader::buildItemSets(st); };
169 ctx.applyItemSet = [&st](WoWModel* m, int id) { ModelLoader::applyItemSet(m, id, st, items); };
172 ctx.applyStartOutfit = [&st](WoWModel* m, int id) { ModelLoader::applyStartOutfit(m, id, st, items); };
173 return ctx;
174}
175
176NpcBrowserPanel::DrawContext buildNpcBrowserCtx(AppState& st)
177{
180 ctx.isDBReady = st.loading.initDB;
181 ctx.npcs = &npcs;
186 ctx.loadNPC = [&st](unsigned int id) { ModelLoader::loadNPC(id, st, video.fov); };
187 return ctx;
188}
189
190ItemBrowserPanel::DrawContext buildItemBrowserCtx(AppState& st)
191{
194 ctx.isDBReady = st.loading.initDB;
195 ctx.items = &items;
200 ctx.loadItemModel = [&st](unsigned int id) { ModelLoader::loadItemModel(id, st, video.fov); };
201 return ctx;
202}
203
204ExportPanel::DrawContext buildExportCtx(AppState& st)
205{
207 ctx.getLoadedModel = [&st]() { return ModelLoader::getLoadedModel(st); };
208 ctx.exporters = &st.exporting.exporters;
210 ctx.animEntries = &st.anim.animEntries;
215 return ctx;
216}
217
218ScreenshotPanel::DrawContext buildScreenshotCtx(AppState& st)
219{
221 ctx.renderer = &st.scene.renderer;
227 ctx.fbo = &st.scene.fbo;
228 ctx.camera = &st.scene.camera;
229 ctx.root = st.scene.root.get();
230 ctx.fov = video.fov;
231 ctx.bgColor = st.settings.bgColor;
232 ctx.drawGrid = st.settings.drawGrid;
233 return ctx;
234}
235
236PresetsPanel::DrawContext buildPresetsCtx(AppState& st)
237{
241 ctx.isChar = st.scene.isChar;
242 ctx.hasModel = ModelLoader::getLoadedModel(st) != nullptr;
243 ctx.savePreset = [&st](const char* p) { PresetManager::save(p, st); };
244 ctx.loadPreset = [&st](const char* p) { PresetManager::load(p, st); };
245 return ctx;
246}
247
248LogPanel::DrawContext buildLogCtx(AppState& st)
249{
251 ctx.logLines = &st.ui.logLines;
254 return ctx;
255}
256
257SettingsPanel::DrawContext buildSettingsCtx(AppState& st, GLFWwindow* window, bool* showDemoWindow)
258{
260 ctx.pathBuf = &st.loading.pathBuf;
268 ctx.settings = &st.settings;
270 ctx.fontsDirty = &st.ui.fontsDirty;
271 ctx.window = window;
272 ctx.showDemoWindow = showDemoWindow;
273 ctx.camera = &st.scene.camera;
274 ctx.getLoadStatus = [&st]() { return GameLoader::getLoadStatus(st); };
275 return ctx;
276}
277
278} // anonymous namespace
279
280// ============================================================================
281// run() — single entry point called from main()
282// ============================================================================
283
284Application::~Application() = default;
285
287{
288 if (!init())
289 return 1;
290
291 mainLoop();
292 shutdown();
293 return 0;
294}
295
296// ============================================================================
297// Lifecycle phases
298// ============================================================================
299
301{
302 // ---- Platform window (GLFW + glad) ----
303 if (!m_window.init(1600, 900, "WoW Model Viewer"))
304 return false;
305
306 m_window.setIcon(WMV_ICON_PATH);
307
308 // ---- Custom title bar (embed menus in the window frame) ----
310
311 // ---- Engine init ----
312 initEngine();
313 initGL();
314
315 // Create root attachment (scene graph root — no model yet)
316 m_state.scene.root = std::make_unique<Attachment>(nullptr, nullptr, -1, -1);
317
318 // ---- Dear ImGui ----
324
325 m_state.scene.lastTick = std::chrono::steady_clock::now();
327
328 return true;
329}
330
332{
333 while (!m_window.shouldClose())
334 {
336
337 // ---- Rebuild font atlas if font/size changed ----
341
342 // Check if background loading thread has finished
344
345 // Animation tick
346 tickScene();
347
348 // ---- ImGui frame ----
350
351 // ---- Resolve input bindings for this frame ----
353
354 // ---- Draw UI ----
357 drawPanels();
358 drawDialogs();
359
360 // ---- Finalise frame ----
363 }
364}
365
367{
368 if (m_state.loading.loadThread.joinable())
370
372
373 m_state.scene.root.reset();
375
377
380
382
383 // AppWindow destructor handles glfwDestroyWindow + glfwTerminate
384
385 LOG_INFO << "WoW Model Viewer shutdown complete.";
386}
387
388// ============================================================================
389// Engine / GL initialisation
390// ============================================================================
391
393{
394 GLOBALSETTINGS.bShowParticle = true;
395 GLOBALSETTINGS.bZeroParticle = true;
396
397#ifdef _WIN32
398 CreateDirectoryA("userSettings", nullptr);
399#endif
400
401 LOGGER.addChild(new WMVLog::LogOutputFile("userSettings/log_imgui.txt"));
402 LOGGER.addChild(new WMVLog::LogOutputConsole());
403
404 LOG_INFO << "==============================================";
405 LOG_INFO << "Starting:" << GLOBALSETTINGS.appName()
406 << GLOBALSETTINGS.appVersion()
407 << GLOBALSETTINGS.buildName();
408 LOG_INFO << "==============================================";
409
411
412#ifdef _WIN32
413 if (HWND hConsole = GetConsoleWindow())
414 ShowWindow(hConsole, m_state.settings.showConsole ? SW_SHOW : SW_HIDE);
415#endif
416
418
419 m_state.exporting.exporters.push_back(std::make_unique<OBJExporter>());
420 m_state.exporting.exporters.push_back(std::make_unique<FBXExporter>());
421
422 m_state.exporting.importers.push_back(std::make_unique<ArmoryImporter>());
423 m_state.exporting.importers.push_back(std::make_unique<WowheadImporter>());
424}
425
427{
428 video.render = true;
429 video.InitGL();
431 LOG_INFO << "OpenGL initialisation complete.";
432}
433
434// ============================================================================
435// Per-frame helpers
436// ============================================================================
437
439{
440 auto now = std::chrono::steady_clock::now();
441 float dt = std::chrono::duration<float>(now - m_state.scene.lastTick).count();
442 m_state.scene.lastTick = now;
443
444 if (dt > 0.1f) dt = 0.1f;
445 if (dt < 0.0f) dt = 0.0f;
446
447 m_state.scene.fpsAccum += dt;
449 if (m_state.scene.fpsAccum >= 0.5f)
450 {
453 m_state.scene.fpsAccum = 0.0f;
454 }
455
456 m_state.scene.animTime += dt;
457
458 if (m_state.scene.root)
459 m_state.scene.root->tick(dt * 1000.0f);
460}
461
470
471// ============================================================================
472// UI drawing
473// ============================================================================
474
476{
477 GLFWwindow* window = m_window.handle();
478
479 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0f, 8.0f));
480 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(14.0f, 8.0f));
481 if (CustomTitleBar::begin(window))
482 {
483 if (ImGui::BeginMenu("File"))
484 {
485 if (ImGui::MenuItem("Load WoW", nullptr, false,
488 ImGui::Separator();
489 if (ImGui::MenuItem("Import from URL...", nullptr, false,
491 {
495 }
496 ImGui::Separator();
497 if (ImGui::MenuItem("Close Model", nullptr, false,
500 ImGui::Separator();
501 if (ImGui::MenuItem("Export...", nullptr, false,
503 {
504 m_state.ui.panel("Export") = true;
505 ImGui::SetWindowFocus("Export");
506 }
507 if (ImGui::MenuItem("Screenshot...", "Ctrl+S"))
508 {
509 m_state.ui.panel("Screenshot") = true;
510 ImGui::SetWindowFocus("Screenshot");
511 }
512 ImGui::Separator();
513 if (ImGui::MenuItem("Exit", "Alt+F4"))
515 ImGui::EndMenu();
516 }
517
518 if (ImGui::BeginMenu("Edit"))
519 {
520 if (ImGui::MenuItem("Reset Camera", "Numpad 5", false,
524 video.fov);
525 ImGui::EndMenu();
526 }
527
528 if (ImGui::BeginMenu("Window"))
529 {
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"));
535 ImGui::MenuItem("Mounts", nullptr, &m_state.ui.panel("Mounts"));
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"));
539 ImGui::MenuItem("Export", nullptr, &m_state.ui.panel("Export"));
540 ImGui::MenuItem("Screenshot", nullptr, &m_state.ui.panel("Screenshot"));
541 ImGui::MenuItem("Presets", nullptr, &m_state.ui.panel("Presets"));
542 ImGui::MenuItem("Log", nullptr, &m_state.ui.panel("Log"));
543 ImGui::MenuItem("Settings", nullptr, &m_showSettings);
544 ImGui::Separator();
545 ImGui::MenuItem("Dear ImGui Demo", nullptr, &m_showDemoWindow);
546 ImGui::EndMenu();
547 }
548
549 if (ImGui::BeginMenu("Options"))
550 {
551 if (ImGui::MenuItem("Language / Locale..."))
553 ImGui::Separator();
554 if (ImGui::MenuItem("Settings..."))
555 m_showSettings = true;
556 ImGui::EndMenu();
557 }
558
559 if (ImGui::BeginMenu("Help"))
560 {
561 if (ImGui::MenuItem("About..."))
563 ImGui::EndMenu();
564 }
565
566 // ---- Status bar + window controls ----
567 std::string statusText;
568 {
570 if (sm)
571 {
572 int curFrame = 0, totalFrames = 0;
573 if (sm->animManager)
574 {
575 curFrame = static_cast<int>(sm->animManager->GetFrame());
576 totalFrames = static_cast<int>(sm->animManager->GetFrameCount());
577 }
578 statusText = std::format("FPS: {:.0f} | {} | V:{} B:{} T:{} | Frame: {}/{}",
579 m_state.scene.fps, sm->name(),
581 curFrame, totalFrames);
582 }
583 else
584 {
585 statusText = std::format("FPS: {:.0f}", m_state.scene.fps);
586 }
587 }
588 CustomTitleBar::end(window, statusText.c_str());
589 }
590 ImGui::PopStyleVar(2);
591}
592
594{
595 const float titleBarH = CustomTitleBar::height();
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"));
609 ImGui::End();
610
611 if (m_firstFrame)
612 {
613 m_firstFrame = false;
614 if (!std::filesystem::exists(AppSettings::imguiIniPath))
615 {
616 ImGui::DockBuilderRemoveNode(dockspace_id);
617 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
618
619 int fw, fh;
621 ImGui::DockBuilderSetNodeSize(dockspace_id,
622 ImVec2(static_cast<float>(fw), static_cast<float>(fh)));
623
624 ImGuiID dock_left, dock_center;
625 ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.15f, &dock_left, &dock_center);
626
627 ImGuiID dock_right;
628 ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Right, 0.20f, &dock_right, &dock_center);
629
630 ImGuiID dock_bottom;
631 ImGui::DockBuilderSplitNode(dock_center, ImGuiDir_Down, 0.25f, &dock_bottom, &dock_center);
632
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);
647 }
648 }
649}
650
652{
653 auto& st = m_state;
654
655 // ===== Character Viewer =====
656 if (st.ui.panel("Character Viewer"))
657 {
658 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 4));
659 if (ImGui::Begin("Character Viewer", &st.ui.panel("Character Viewer")))
660 {
661 auto ctx = buildCharViewerCtx(st, [this]() { handleViewportInput(); });
663 }
664 ImGui::End();
665 ImGui::PopStyleVar();
666 }
667
668 // ===== 3D Viewport =====
669 if (st.ui.panel("3D Viewport"))
670 {
671 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
672 if (ImGui::Begin("3D Viewport", &st.ui.panel("3D Viewport")))
673 {
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)
678 {
679 st.scene.renderer.renderScene(st.scene.fbo, vpW, vpH, st.scene.camera,
681 [&]() {
682 if (auto* root = st.scene.root.get())
683 {
684 glEnable(GL_LIGHTING);
685 glEnable(GL_TEXTURE_2D);
686 glEnable(GL_DEPTH_TEST);
687 glDepthFunc(GL_LEQUAL);
688 root->draw();
689 glEnable(GL_TEXTURE_2D);
690 glDisable(GL_LIGHTING);
691 glDepthMask(GL_FALSE);
692 glEnable(GL_BLEND);
693 root->drawParticles();
694 glDisable(GL_BLEND);
695 glDepthMask(GL_TRUE);
696 }
697 });
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())
702 }
703 }
704 ImGui::End();
705 ImGui::PopStyleVar();
706 }
707
708 // ===== File Browser =====
709 if (st.ui.panel("File Browser"))
710 {
711 auto statusStr = GameLoader::getLoadStatus(st);
716 ls.statusText = statusStr.c_str();
717 if (GameFile* picked = FileBrowserPanel::draw(st.ui.panel("File Browser"), ls))
718 ModelLoader::loadModel(picked, st, video.fov);
719 }
720
721 // ===== Animation Control =====
722 if (st.ui.panel("Animation"))
723 {
724 if (ImGui::Begin("Animation", &st.ui.panel("Animation")))
725 {
726 auto ctx = buildAnimCtx(st);
728 st.anim.blpSkin[0] = ctx.blpSkin[0];
729 st.anim.blpSkin[1] = ctx.blpSkin[1];
730 st.anim.blpSkin[2] = ctx.blpSkin[2];
731 }
732 ImGui::End();
733 }
734
735 // ===== Viewport Options =====
736 if (st.ui.panel("Viewport Options"))
737 {
738 if (ImGui::Begin("Viewport Options", &st.ui.panel("Viewport Options")))
739 {
740 auto ctx = buildViewportOptsCtx(st);
742 }
743 ImGui::End();
744 }
745
746 // ===== Mounts =====
747 if (st.ui.panel("Mounts"))
748 {
749 if (ImGui::Begin("Mounts", &st.ui.panel("Mounts")))
750 {
751 auto ctx = buildMountsCtx(st);
753 }
754 ImGui::End();
755 }
756
757 // ===== Item Sets =====
758 if (st.ui.panel("Item Sets"))
759 {
760 if (ImGui::Begin("Item Sets", &st.ui.panel("Item Sets")))
761 {
762 auto ctx = buildItemSetsCtx(st);
764 }
765 ImGui::End();
766 }
767
768 // ===== NPC Browser =====
769 if (st.ui.panel("NPC Browser"))
770 {
771 if (ImGui::Begin("NPC Browser", &st.ui.panel("NPC Browser")))
772 {
773 auto ctx = buildNpcBrowserCtx(st);
775 }
776 ImGui::End();
777 }
778
779 // ===== Item Browser =====
780 if (st.ui.panel("Item Browser"))
781 {
782 if (ImGui::Begin("Item Browser", &st.ui.panel("Item Browser")))
783 {
784 auto ctx = buildItemBrowserCtx(st);
786 }
787 ImGui::End();
788 }
789
790 // ===== Export =====
791 if (st.ui.panel("Export"))
792 {
793 if (ImGui::Begin("Export", &st.ui.panel("Export")))
794 {
795 auto ctx = buildExportCtx(st);
797 }
798 ImGui::End();
799 }
800
801 // ===== Screenshot =====
802 if (st.ui.panel("Screenshot"))
803 {
804 if (ImGui::Begin("Screenshot", &st.ui.panel("Screenshot")))
805 {
806 auto ctx = buildScreenshotCtx(st);
808 }
809 ImGui::End();
810 }
811
812 // ===== Presets =====
813 if (st.ui.panel("Presets"))
814 {
815 if (ImGui::Begin("Presets", &st.ui.panel("Presets")))
816 {
817 auto ctx = buildPresetsCtx(st);
819 }
820 ImGui::End();
821 }
822
823 // ===== Log =====
824 if (st.ui.panel("Log"))
825 {
826 if (ImGui::Begin("Log", &st.ui.panel("Log")))
827 {
828 auto ctx = buildLogCtx(st);
829 LogPanel::draw(ctx);
830 }
831 ImGui::End();
832 }
833
834 // ===== Settings (non-docking dialog) =====
835 if (m_showSettings)
836 {
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))
841 {
842 auto ctx = buildSettingsCtx(st, m_window.handle(), &m_showDemoWindow);
844 }
845 ImGui::End();
846 }
847}
848
#define GLOBALSETTINGS
#define LOGGER
Definition Logger.h:9
#define LOG_INFO
Definition Logger.h:10
size_t GetFrameCount()
size_t GetFrame()
Definition AnimManager.h:95
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.
Definition AppWindow.cpp:70
float queryDpiScale() const
Query the monitor content-scale (DPI).
Definition AppWindow.cpp:97
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)
Definition AppWindow.cpp:34
GLFWwindow * handle() const noexcept
Raw GLFW handle — needed by ImGui backends and CustomTitleBar.
Definition AppWindow.h:47
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()
AppWindow m_window
Definition Application.h:34
bool m_firstFrame
Definition Application.h:42
void drawDialogs()
InputManager m_inputManager
Definition Application.h:36
void drawDockspace()
bool m_showDemoWindow
Definition Application.h:40
void initEngine()
AppState m_state
Definition Application.h:37
bool m_showSettings
Definition Application.h:41
void drawTitleBarAndMenus()
void drawPanels()
ImGuiLayer m_imguiLayer
Definition Application.h:35
std::string name() const
Definition Component.cpp:52
Abstract base class representing a file within the game data archive.
Definition GameFile.h:12
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 loadDefaults()
Populate the binding table with default viewer controls.
const InputState & state() const noexcept
Resolved state from the most recent update().
void renderScene(ViewportFBO &fbo, int w, int h, const OrbitCamera &camera, float fov, const glm::vec3 &clearColor, bool drawGrid, const std::function< void()> &drawObjects)
Definition Renderer.cpp:280
void init()
Create GPU resources. Call once after the OpenGL context is current.
Definition Renderer.cpp:16
void shutdown()
Release GPU resources.
Definition Renderer.cpp:33
void InitGL()
Definition video.cpp:187
float fov
Definition video.h:69
bool render
Definition video.h:62
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.
Definition WoWModel.h:50
ModelHeader header
Definition WoWModel.h:217
AnimManager * animManager
Definition WoWModel.h:180
std::vector< NPCRecord > npcs
Definition database.cpp:8
ItemDatabase items
Definition database.cpp:7
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)
Definition LogPanel.cpp:27
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
std::function< WoWModel *()> getLoadedModel
std::vector< SkinEntry > skinEntries
Definition AppState.h:164
int selectedSecondaryAnim
Definition AppState.h:159
int selectedMouthAnim
Definition AppState.h:160
int blpSkin[3]
Definition AppState.h:166
float mouthSpeed
Definition AppState.h:161
float animSpeed
Definition AppState.h:157
int selectedAnimCombo
Definition AppState.h:156
std::vector< AnimEntry > animEntries
Definition AppState.h:155
std::string gamePath
Definition AppSettings.h:10
glm::vec3 bgColor
Definition AppSettings.h:18
bool showConsole
Definition AppSettings.h:14
float fontSize
Definition AppSettings.h:22
static constexpr const char * imguiIniPath
Definition AppSettings.h:26
void load()
Load from Config.ini (also restores the current ThemeManager theme).
Top-level aggregate of all mutable application state.
Definition AppState.h:261
LoadingState loading
Definition AppState.h:263
SceneState scene
Definition AppState.h:262
AppSettings settings
Definition AppState.h:269
AnimationState anim
Definition AppState.h:265
ExportState exporting
Definition AppState.h:268
CharacterState character
Definition AppState.h:266
BrowserState browsers
Definition AppState.h:267
UIState ui
Definition AppState.h:264
std::string npcSearchBuf
Definition AppState.h:202
std::vector< size_t > itemBrowseFiltered
Definition AppState.h:208
bool itemBrowseFilterDirty
Definition AppState.h:209
std::vector< MountEntry > mountList
Definition AppState.h:212
std::string itemBrowseSearchBuf
Definition AppState.h:207
std::vector< std::string > creatureModelNames
Definition AppState.h:214
std::vector< StartOutfitEntry > startOutfits
Definition AppState.h:195
bool mountFilterDirty
Definition AppState.h:219
std::string itemSetSearchBuf
Definition AppState.h:190
std::vector< size_t > mountFiltered
Definition AppState.h:218
std::vector< size_t > npcFiltered
Definition AppState.h:203
ParticleColorState pcrState
Definition AppState.h:223
bool npcFilterDirty
Definition AppState.h:204
std::string mountSearchBuf
Definition AppState.h:216
std::vector< size_t > startOutfitFiltered
Definition AppState.h:198
std::vector< GeosetGroupEntry > geosetGroups
Definition AppState.h:222
std::string startOutfitSearchBuf
Definition AppState.h:197
std::vector< GameFile * > creatureModels
Definition AppState.h:213
std::vector< ItemSetEntry > itemSets
Definition AppState.h:188
bool itemSetFilterDirty
Definition AppState.h:192
bool startOutfitsBuilt
Definition AppState.h:196
bool startOutfitFilterDirty
Definition AppState.h:199
std::vector< size_t > itemSetFiltered
Definition AppState.h:191
bool itemSetsBuilt
Definition AppState.h:189
std::vector< CustomizationOption > customizationOptions
Definition AppState.h:174
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.
Definition ExportPanel.h:20
std::string * exportPath
Definition ExportPanel.h:27
std::vector< std::unique_ptr< ExporterPlugin > > * exporters
Definition ExportPanel.h:22
std::vector< AnimEntry > * animEntries
Definition ExportPanel.h:24
std::vector< char > * exportAnimChecked
Definition ExportPanel.h:25
std::function< WoWModel *()> getLoadedModel
Definition ExportPanel.h:21
std::string * exportStatus
Definition ExportPanel.h:28
std::vector< std::unique_ptr< ExporterPlugin > > exporters
Definition AppState.h:231
std::string screenshotPath
Definition AppState.h:238
int canvasWidth
Definition AppState.h:241
std::string presetPath
Definition AppState.h:245
std::string screenshotStatus
Definition AppState.h:239
int canvasHeight
Definition AppState.h:242
int selectedExporter
Definition AppState.h:232
std::vector< std::unique_ptr< ImporterPlugin > > importers
Definition AppState.h:249
std::string presetStatus
Definition AppState.h:246
std::string exportStatus
Definition AppState.h:234
std::string exportPath
Definition AppState.h:233
bool useCanvasOverride
Definition AppState.h:240
std::string importStatus
Definition AppState.h:251
std::vector< char > exportAnimChecked
Definition AppState.h:235
bool resetCamera
Definition InputAction.h:78
Per-frame context for the item browser panel.
std::function< void()> rebuildItemBrowseFilter
std::function< void(unsigned int)> loadItemModel
std::vector< size_t > * itemBrowseFiltered
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
std::function< void(WoWModel *)> buildStartOutfits
std::vector< size_t > * itemSetFiltered
std::function< WoWModel *()> getLoadedModel
std::vector< StartOutfitEntry > * startOutfits
std::string * startOutfitSearchBuf
std::function< void()> buildItemSets
std::function< void()> rebuildStartOutfitFilter
std::atomic< float > loadProgress
Definition AppState.h:94
std::string pathBuf
Definition AppState.h:100
bool isWoWLoaded
Definition AppState.h:91
std::thread loadThread
Definition AppState.h:96
std::atomic< bool > initDB
Definition AppState.h:92
bool loadInProgress
Definition AppState.h:95
Per-frame context for the log panel.
Definition LogPanel.h:12
std::vector< std::string > * logLines
Definition LogPanel.h:13
uint32 nTextures
uint32 nBones
uint32 nVertices
Per-frame context for the mounts panel.
Definition MountsPanel.h:23
std::vector< GameFile * > * creatureModels
Definition MountsPanel.h:29
std::function< WoWModel *()> getLoadedModel
Definition MountsPanel.h:35
std::function< void()> dismountCharacter
Definition MountsPanel.h:39
std::string * mountSearchBuf
Definition MountsPanel.h:33
std::function< void()> buildMountList
Definition MountsPanel.h:36
std::function< void()> rebuildMountFilter
Definition MountsPanel.h:37
std::vector< size_t > * mountFiltered
Definition MountsPanel.h:30
std::function< void(int, GameFile *)> mountCharacter
Definition MountsPanel.h:38
std::vector< std::string > * creatureModelNames
Definition MountsPanel.h:28
std::vector< MountEntry > * mountList
Definition MountsPanel.h:27
Per-frame context for the NPC browser panel.
std::vector< size_t > * npcFiltered
const std::vector< NPCRecord > * npcs
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
bool isMounted
Definition AppState.h:83
float animTime
Definition AppState.h:75
Renderer renderer
Definition AppState.h:69
std::chrono::steady_clock::time_point lastTick
Definition AppState.h:76
bool isChar
Definition AppState.h:82
OrbitCamera camera
Definition AppState.h:70
float fpsAccum
Definition AppState.h:79
float fps
Definition AppState.h:77
ViewportFBO fbo
Definition AppState.h:73
std::unique_ptr< Attachment > root
Definition AppState.h:71
int fpsFrameCount
Definition AppState.h:78
Per-frame context for the screenshot panel.
Per-frame context for the settings panel.
std::filesystem::path * folderPickerCurrent
std::vector< std::filesystem::path > * folderPickerEntries
std::function< std::string()> getLoadStatus
std::vector< FontEntry > * availableFonts
std::atomic< float > * loadProgress
bool showAboutDialog
Definition AppState.h:112
bool folderPickerNeedsRefresh
Definition AppState.h:142
float dpiScale
Definition AppState.h:137
std::vector< std::filesystem::path > folderPickerEntries
Definition AppState.h:141
bool logAutoScroll
Definition AppState.h:146
bool logNeedsReload
Definition AppState.h:147
bool showLanguageDialog
Definition AppState.h:113
bool importPopupJustOpened
Definition AppState.h:132
bool showImportDialog
Definition AppState.h:128
bool fontsDirty
Definition AppState.h:136
bool & panel(const char *name)
Definition AppState.h:121
std::vector< FontEntry > availableFonts
Definition AppState.h:135
bool showFolderPicker
Definition AppState.h:129
std::vector< std::string > logLines
Definition AppState.h:145
std::filesystem::path folderPickerCurrent
Definition AppState.h:140
void destroy()
Release all GPU resources.
Definition ViewportFBO.h:59
GLuint colorTex
Colour attachment (GL_RGBA8 texture).
Definition ViewportFBO.h:12
Per-frame context for the viewport options panel.
std::function< void(WoWModel *, int)> applySkin
std::function< WoWModel *()> getLoadedModel
std::vector< GeosetGroupEntry > * geosetGroups
VideoSettings video
Definition video.cpp:38