WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
ViewportOptionsPanel.cpp
Go to the documentation of this file.
2
3#include <cassert>
4
5#include "imgui.h"
6#include "Renderer.h"
7#include "OrbitCamera.h"
8#include "WoWModel.h"
9
10#include <format>
11
12#include <glm/glm.hpp>
13
14namespace
15{
16
17// ---- Background colour palette (constant data) ---------------------------
18constexpr glm::vec3 g_bgPalette[] = {
19 {0.22f, 0.22f, 0.22f}, // Dark gray (default)
20 {0.0f, 0.0f, 0.0f}, // Black
21 {1.0f, 1.0f, 1.0f}, // White
22 {0.278f,0.373f,0.475f}, // Steel blue
23 {0.15f, 0.30f, 0.15f}, // Forest green
24 {0.35f, 0.15f, 0.15f}, // Dark red
25 {0.0f, 0.47f, 0.84f}, // WoW blue
26 {0.10f, 0.10f, 0.18f}, // Midnight
27};
28constexpr int g_bgPaletteCount = sizeof(g_bgPalette) / sizeof(g_bgPalette[0]);
29
30// ---- Particle color helper -----------------------------------------------
31void applyParticleColors(WoWModel* model,
33{
34 if (!model) return;
35
36 model->particleColorReplacements.clear();
37 for (int s = 0; s < 3; ++s)
38 {
39 std::vector<glm::vec4> pcs;
40 for (int p = 0; p < 3; ++p)
41 {
42 pcs.push_back(glm::vec4(
43 pcrState.colors[s][p][0],
44 pcrState.colors[s][p][1],
45 pcrState.colors[s][p][2],
46 1.0f));
47 }
48 model->particleColorReplacements.push_back(pcs);
49 }
50 model->replaceParticleColors = true;
51}
52
53} // anonymous namespace
54
56{
57
58void draw(DrawContext& ctx)
59{
60 assert(ctx.drawGrid && "DrawContext::drawGrid must not be null");
61 assert(ctx.bgColor && "DrawContext::bgColor must not be null");
62 assert(ctx.camera && "DrawContext::camera must not be null");
63 assert(ctx.renderer && "DrawContext::renderer must not be null");
64
65 auto& rs = ctx.renderer->state();
66
67 // ---- Background ----
68 if (ImGui::CollapsingHeader("Background", ImGuiTreeNodeFlags_DefaultOpen))
69 {
70 ImGui::Checkbox("Draw Grid", ctx.drawGrid);
71 ImGui::Checkbox("Checkerboard Background", &rs.drawCheckerBg);
72 if (ImGui::Checkbox("Gradient Background", &rs.drawGradientBg))
73 {
74 if (rs.drawGradientBg)
75 rs.drawCheckerBg = false;
76 }
77 if (rs.drawGradientBg)
78 {
79 ImGui::ColorEdit3("Gradient Top", &rs.gradientTop.x);
80 ImGui::ColorEdit3("Gradient Bottom", &rs.gradientBottom.x);
81 }
82 ImGui::ColorEdit3("Background Color", &ctx.bgColor->x);
83
84 ImGui::Spacing();
85 ImGui::Text("Palette:");
86 for (int i = 0; i < g_bgPaletteCount; ++i)
87 {
88 ImGui::PushID(i);
89 if (ImGui::ColorButton("##pal",
90 ImVec4(g_bgPalette[i].x, g_bgPalette[i].y, g_bgPalette[i].z, 1.0f),
91 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)))
92 *ctx.bgColor = g_bgPalette[i];
93 ImGui::PopID();
94 if (i < g_bgPaletteCount - 1) ImGui::SameLine();
95 }
96
97 ImGui::Spacing();
98 ImGui::Text("Camera Presets:");
99 if (ImGui::Button("Front"))
100 ctx.camera->setYawAndPitch(0.f, 90.f);
101 ImGui::SameLine();
102 if (ImGui::Button("Side"))
103 ctx.camera->setYawAndPitch(270.f, 90.f);
104 ImGui::SameLine();
105 if (ImGui::Button("Back"))
106 ctx.camera->setYawAndPitch(180.f, 90.f);
107 ImGui::SameLine();
108 if (ImGui::Button("Iso"))
109 ctx.camera->setYawAndPitch(315.f, 90.f);
110 if (ImGui::Button("Top"))
111 ctx.camera->setYawAndPitch(ctx.camera->yaw(), 179.f);
112 ImGui::SameLine();
113 if (ImGui::Button("Bottom"))
114 ctx.camera->setYawAndPitch(ctx.camera->yaw(), 1.f);
115 ImGui::SameLine();
116 if (ImGui::Button("Reset Camera"))
117 ctx.resetCamera();
118 }
119
120 // ---- Lighting ----
121 if (ImGui::CollapsingHeader("Lighting", ImGuiTreeNodeFlags_DefaultOpen))
122 {
123 ImGui::Checkbox("Enable Lighting", &rs.light.enabled);
124
125 if (!rs.light.enabled)
126 ImGui::BeginDisabled();
127
128 static const char* lightTypeNames[] = { "Directional", "Point", "Spot", "Ambient Only" };
129 ImGui::Combo("##LightType",
130 reinterpret_cast<int*>(&rs.light.type),
131 lightTypeNames, IM_ARRAYSIZE(lightTypeNames));
132
133 if (rs.light.type == Renderer::LightType::Directional ||
134 rs.light.type == Renderer::LightType::Spot)
135 ImGui::DragFloat3("Dir XYZ", rs.light.direction, 0.01f, -5.0f, 5.0f, "%.2f");
136
137 if (rs.light.type == Renderer::LightType::Point ||
138 rs.light.type == Renderer::LightType::Spot)
139 ImGui::DragFloat3("Pos XYZ", rs.light.position, 0.1f, -50.0f, 50.0f, "%.1f");
140
141 if (rs.light.type == Renderer::LightType::Spot)
142 {
143 ImGui::SliderFloat("Cutoff Angle", &rs.light.spotCutoff,
144 1.0f, 90.0f, "%.1f deg");
145 ImGui::SliderFloat("Exponent", &rs.light.spotExponent,
146 0.0f, 128.0f, "%.1f");
147 }
148
149 ImGui::ColorEdit3("Diffuse", rs.light.diffuse, ImGuiColorEditFlags_Float);
150 ImGui::ColorEdit3("Ambient", rs.light.ambient, ImGuiColorEditFlags_Float);
151 ImGui::ColorEdit3("Specular", rs.light.specular, ImGuiColorEditFlags_Float);
152 ImGui::SliderFloat("Intensity", &rs.light.intensity, 0.0f, 3.0f, "%.2f");
153
154 if (!rs.light.enabled)
155 ImGui::EndDisabled();
156
157 ImGui::Spacing();
158 if (ImGui::Button("Default##light", ImVec2(-1, 0)))
159 rs.light = Renderer::LightSettings{};
160 if (ImGui::Button("Bright Daylight", ImVec2(-1, 0)))
161 {
162 auto& l = rs.light;
163 l.direction[0] = -0.5f; l.direction[1] = 1.0f;
164 l.direction[2] = -0.3f; l.direction[3] = 0.0f;
165 l.diffuse[0] = 1.0f; l.diffuse[1] = 0.98f; l.diffuse[2] = 0.92f;
166 l.ambient[0] = 0.45f; l.ambient[1] = 0.45f; l.ambient[2] = 0.50f;
167 l.specular[0] = 0.3f; l.specular[1] = 0.3f; l.specular[2] = 0.3f;
168 l.intensity = 1.2f;
169 l.enabled = true;
170 }
171 if (ImGui::Button("Warm Sunset", ImVec2(-1, 0)))
172 {
173 auto& l = rs.light;
174 l.direction[0] = -1.0f; l.direction[1] = 0.3f;
175 l.direction[2] = -0.5f; l.direction[3] = 0.0f;
176 l.diffuse[0] = 1.0f; l.diffuse[1] = 0.65f; l.diffuse[2] = 0.3f;
177 l.ambient[0] = 0.25f; l.ambient[1] = 0.2f; l.ambient[2] = 0.25f;
178 l.specular[0] = 0.1f; l.specular[1] = 0.05f; l.specular[2] = 0.0f;
179 l.intensity = 1.0f;
180 l.enabled = true;
181 }
182 if (ImGui::Button("Cool Moonlight", ImVec2(-1, 0)))
183 {
184 auto& l = rs.light;
185 l.direction[0] = 0.3f; l.direction[1] = 1.0f;
186 l.direction[2] = -0.7f; l.direction[3] = 0.0f;
187 l.diffuse[0] = 0.6f; l.diffuse[1] = 0.65f; l.diffuse[2] = 0.8f;
188 l.ambient[0] = 0.15f; l.ambient[1] = 0.15f; l.ambient[2] = 0.2f;
189 l.specular[0] = 0.0f; l.specular[1] = 0.0f; l.specular[2] = 0.0f;
190 l.intensity = 0.7f;
191 l.enabled = true;
192 }
193 if (ImGui::Button("Flat (No Shading)", ImVec2(-1, 0)))
194 {
195 auto& l = rs.light;
196 l.direction[0] = 0.0f; l.direction[1] = 0.0f;
197 l.direction[2] = -1.0f; l.direction[3] = 0.0f;
198 l.diffuse[0] = 1.0f; l.diffuse[1] = 1.0f; l.diffuse[2] = 1.0f;
199 l.ambient[0] = 1.0f; l.ambient[1] = 1.0f; l.ambient[2] = 1.0f;
200 l.specular[0] = 0.0f; l.specular[1] = 0.0f; l.specular[2] = 0.0f;
201 l.intensity = 0.5f;
202 l.enabled = true;
203 }
204 }
205
206 // ---- Model Control ----
207 if (ImGui::CollapsingHeader("Model", ImGuiTreeNodeFlags_DefaultOpen))
208 {
209 WoWModel* mModel = ctx.getLoadedModel();
210 if (mModel)
211 {
212 ImGui::Text("Name: %s", mModel->name().c_str());
213 if (mModel->gamefile)
214 {
215 ImGui::Text("File: %s", mModel->gamefile->fullname().c_str());
216 ImGui::Text("FileDataID: %d", mModel->gamefile->fileDataId());
217 }
218 ImGui::Text("V:%u B:%u T:%u A:%zu G:%zu",
219 mModel->header.nVertices, mModel->header.nBones,
220 mModel->header.nTextures, mModel->anims.size(), mModel->geosets.size());
221
222 ImGui::Separator();
223 ImGui::Checkbox("Render", &mModel->showModel);
224 ImGui::SameLine();
225 ImGui::Checkbox("Wireframe", &mModel->showWireframe);
226 ImGui::Checkbox("Texture", &mModel->showTexture);
227 ImGui::SameLine();
228 ImGui::Checkbox("Bones", &mModel->showBones);
229 ImGui::Checkbox("Bounds", &mModel->showBounds);
230 ImGui::SameLine();
231 ImGui::Checkbox("Particles", &mModel->showParticles);
232
233 int alphaPercent = static_cast<int>(mModel->alpha_ * 100.0f);
234 if (ImGui::SliderInt("Alpha", &alphaPercent, 0, 100))
235 mModel->alpha_ = alphaPercent / 100.0f;
236 ImGui::SliderFloat("Scale", &mModel->scale_, 0.1f, 3.0f, "%.2f");
237
238 ImGui::DragFloat3("Position", &mModel->pos_.x, 0.1f);
239 ImGui::DragFloat3("Rotation", &mModel->rot_.x, 1.0f);
240
241 // Geosets
242 if (ctx.geosetGroups && !ctx.geosetGroups->empty())
243 {
244 ImGui::Separator();
245 ImGui::TextDisabled("Geosets (click to toggle)");
246 for (auto& group : *ctx.geosetGroups)
247 {
248 if (ImGui::TreeNode(group.name.c_str()))
249 {
250 for (auto& ge : group.geosets)
251 {
252 bool displayed = mModel->isGeosetDisplayed(static_cast<uint>(ge.index));
253 ImVec4 color = displayed
254 ? ImVec4(0.3f, 1.0f, 0.3f, 1.0f)
255 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
256 ImGui::PushStyleColor(ImGuiCol_Text, color);
257 ImGuiTreeNodeFlags flags =
258 ImGuiTreeNodeFlags_Leaf |
259 ImGuiTreeNodeFlags_NoTreePushOnOpen |
260 ImGuiTreeNodeFlags_SpanAvailWidth;
261 ImGui::TreeNodeEx(ge.label.c_str(), flags);
262 if (ImGui::IsItemClicked())
263 mModel->showGeoset(static_cast<uint>(ge.index), !displayed);
264 ImGui::PopStyleColor();
265 }
266 ImGui::TreePop();
267 }
268 }
269 }
270
271 // Particle Color Replacement
272 if (ctx.pcrState)
273 {
274 auto& pcr = *ctx.pcrState;
275 bool anyPCR = pcr.hasSet[0] || pcr.hasSet[1] || pcr.hasSet[2];
276 if (anyPCR)
277 {
278 ImGui::Separator();
279 if (ImGui::Checkbox("Replace Particle Colors", &pcr.enabled))
280 {
281 if (pcr.enabled)
282 applyParticleColors(mModel, pcr);
283 else
284 {
285 mModel->replaceParticleColors = false;
286 if (ctx.selectedSkin && *ctx.selectedSkin >= 0 && ctx.applySkin)
287 ctx.applySkin(mModel, *ctx.selectedSkin);
288 }
289 }
290
291 static const char* setNames[] = {"ID 11", "ID 12", "ID 13"};
292 static const char* phaseNames[] = {"Start", "Mid", "End"};
293 for (int s = 0; s < 3; ++s)
294 {
295 if (!pcr.hasSet[s]) continue;
296 ImGui::Text("%s", setNames[s]);
297 for (int p = 0; p < 3; ++p)
298 {
299 std::string label = std::format("{} {}##pcr{}{}",
300 setNames[s], phaseNames[p], s, p);
301 if (ImGui::ColorEdit3(label.c_str(), pcr.colors[s][p]))
302 {
303 if (pcr.enabled)
304 applyParticleColors(mModel, pcr);
305 }
306 }
307 }
308 }
309 }
310 }
311 else
312 {
313 ImGui::TextDisabled("No model loaded.");
314 }
315 }
316}
317
318} // namespace ViewportOptionsPanel
std::string name() const
Definition Component.cpp:52
const std::string & fullname() const
Definition GameFile.h:56
int fileDataId()
Definition GameFile.h:57
float yaw() const
Current yaw angle in radians.
Definition OrbitCamera.h:45
void setYawAndPitch(float yaw, float pitch)
Set both yaw and pitch simultaneously.
RenderState & state() noexcept
Definition Renderer.h:71
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
float alpha_
Definition WoWModel.h:159
std::vector< ModelAnimation > anims
Definition WoWModel.h:178
bool showBones
Definition WoWModel.h:153
bool showTexture
Definition WoWModel.h:158
glm::vec3 pos_
Definition WoWModel.h:163
ModelHeader header
Definition WoWModel.h:217
bool replaceParticleColors
Definition WoWModel.h:115
bool showParticles
Definition WoWModel.h:156
bool showWireframe
Definition WoWModel.h:155
glm::vec3 rot_
Definition WoWModel.h:164
bool isGeosetDisplayed(uint geosetindex)
float scale_
Definition WoWModel.h:160
bool showBounds
Definition WoWModel.h:154
bool showModel
Definition WoWModel.h:157
std::vector< particleColorSet > particleColorReplacements
Definition WoWModel.h:130
std::vector< ModelGeosetHD * > geosets
Definition WoWModel.h:149
void showGeoset(uint geosetindex, bool value)
GameFile * gamefile
Definition WoWModel.h:112
void draw(DrawContext &ctx)
Draw the Viewport Options panel contents (call between Begin / End).
uint32 nTextures
uint32 nBones
uint32 nVertices
Plain-data description of a single light source.
Definition Renderer.h:32
Per-frame context for the viewport options panel.
std::function< void(WoWModel *, int)> applySkin
std::function< WoWModel *()> getLoadedModel
std::vector< GeosetGroupEntry > * geosetGroups
Editable state for particle colour replacement (colour IDs 11, 12, 13).
float colors[3][3][3]
Colour values [set 0..2 for IDs 11,12,13][phase 0..2][r/g/b].
bool hasSet[3]
Which colour IDs (11, 12, 13) are present in the model.
unsigned int uint
Definition types.h:36