WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
AnimationPanel.cpp
Go to the documentation of this file.
1// ============================================================================
2// AnimationPanel — animation control, playback, and skin/texture selection.
3// ============================================================================
4#include "AnimationPanel.h"
5
6#include <cassert>
7
8#include "imgui.h"
9#include "WoWModel.h"
10
12{
13
14void draw(DrawContext& ctx)
15{
16 assert(ctx.selectedAnimCombo && "DrawContext::selectedAnimCombo must not be null");
17 assert(ctx.animSpeed && "DrawContext::animSpeed must not be null");
18 assert(ctx.loopCount && "DrawContext::loopCount must not be null");
19 assert(ctx.lockAnims && "DrawContext::lockAnims must not be null");
20 assert(ctx.selectedSecondaryAnim && "DrawContext::selectedSecondaryAnim must not be null");
21 assert(ctx.selectedMouthAnim && "DrawContext::selectedMouthAnim must not be null");
22 assert(ctx.mouthSpeed && "DrawContext::mouthSpeed must not be null");
23
24 WoWModel* aModel = ctx.getLoadedModel ? ctx.getLoadedModel() : nullptr;
25 if (!aModel || !ctx.animEntries || ctx.animEntries->empty())
26 {
27 ImGui::TextDisabled("No model loaded.");
28 return;
29 }
30
31 auto& animEntries = *ctx.animEntries;
32 int& selectedAnimCombo = *ctx.selectedAnimCombo;
33 float& animSpeed = *ctx.animSpeed;
34 int& loopCount = *ctx.loopCount;
35 bool& lockAnims = *ctx.lockAnims;
36 int& selectedSecondaryAnim = *ctx.selectedSecondaryAnim;
37 int& selectedMouthAnim = *ctx.selectedMouthAnim;
38 float& mouthSpeed = *ctx.mouthSpeed;
39
40 // ---- Animation selector ----
41 ImGui::SeparatorText("Animation");
42 const char* previewAnim = (selectedAnimCombo >= 0 && selectedAnimCombo < static_cast<int>(animEntries.size()))
43 ? animEntries[selectedAnimCombo].label.c_str() : "<none>";
44 if (ImGui::BeginCombo("##AnimCombo", previewAnim))
45 {
46 for (int i = 0; i < static_cast<int>(animEntries.size()); ++i)
47 {
48 bool selected = (i == selectedAnimCombo);
49 if (ImGui::Selectable(animEntries[i].label.c_str(), selected))
50 {
51 selectedAnimCombo = i;
52 int idx = animEntries[i].animIndex;
53 aModel->currentAnim = idx;
54 aModel->animManager->SetAnim(0, idx, 0);
55 aModel->animManager->Play();
56 }
57 if (selected) ImGui::SetItemDefaultFocus();
58 }
59 ImGui::EndCombo();
60 }
61
62 // ---- Playback controls ----
63 if (ImGui::Button("Play"))
64 aModel->animManager->Play();
65 ImGui::SameLine();
66 if (ImGui::Button("Pause"))
67 aModel->animManager->Pause();
68 ImGui::SameLine();
69 if (ImGui::Button("Stop"))
70 aModel->animManager->Stop();
71 ImGui::SameLine();
72 if (ImGui::Button("<<"))
73 aModel->animManager->PrevFrame();
74 ImGui::SameLine();
75 if (ImGui::Button(">>"))
76 aModel->animManager->NextFrame();
77
78 if (aModel->animManager->IsPaused())
79 ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.3f, 1.0f), "Paused");
80
81 // ---- Speed ----
82 if (ImGui::SliderFloat("Speed", &animSpeed, 0.0f, 4.0f, "%.2f"))
83 aModel->animManager->SetSpeed(animSpeed);
84
85 // ---- Frame scrubber ----
86 int frameCount = static_cast<int>(aModel->animManager->GetFrameCount());
87 int curFrame = static_cast<int>(aModel->animManager->GetFrame());
88 if (frameCount > 0)
89 {
90 if (ImGui::SliderInt("Frame", &curFrame, 0, frameCount))
91 aModel->animManager->SetFrame(static_cast<size_t>(curFrame));
92 }
93 else
94 {
95 ImGui::Text("Frame: %d", curFrame);
96 }
97
98 // ---- Loop count ----
99 if (ImGui::SliderInt("Loops", &loopCount, 0, 9))
100 {
101 aModel->animManager->Stop();
102 int idx = animEntries[selectedAnimCombo].animIndex;
103 aModel->animManager->SetAnim(0, idx, static_cast<short>(loopCount));
104 // Chain next animations if available
105 int nextAnim = idx;
106 for (int i = 1; i < 4; ++i)
107 {
108 if (nextAnim >= 0 && nextAnim < static_cast<int>(aModel->anims.size()))
109 {
110 nextAnim = aModel->anims[nextAnim].NextAnimation;
111 if (nextAnim >= 0)
112 aModel->animManager->AddAnim(static_cast<unsigned int>(nextAnim), static_cast<short>(loopCount));
113 else
114 break;
115 }
116 else
117 break;
118 }
119 aModel->animManager->Play();
120 }
121 ImGui::SetItemTooltip("0 = infinite loop");
122
123 // ---- Secondary / Mouth animations ----
124 ImGui::SeparatorText("Body Animation");
125 if (ImGui::Checkbox("Lock animations", &lockAnims))
126 {
127 if (lockAnims)
128 {
129 aModel->animManager->ClearSecondary();
130 aModel->animManager->ClearMouth();
131 selectedSecondaryAnim = -1;
132 selectedMouthAnim = -1;
133 }
134 }
135 ImGui::SetItemTooltip("Uncheck to enable independent upper body and mouth animations");
136
137 if (!lockAnims)
138 {
139 // Secondary animation (upper body)
140 {
141 const char* previewSec = (selectedSecondaryAnim >= 0 && selectedSecondaryAnim < static_cast<int>(animEntries.size()))
142 ? animEntries[selectedSecondaryAnim].label.c_str() : "<none>";
143 if (ImGui::BeginCombo("Upper Body##SecAnim", previewSec))
144 {
145 if (ImGui::Selectable("<none>", selectedSecondaryAnim < 0))
146 {
147 selectedSecondaryAnim = -1;
148 aModel->animManager->ClearSecondary();
149 }
150 for (int i = 0; i < static_cast<int>(animEntries.size()); ++i)
151 {
152 bool selected = (i == selectedSecondaryAnim);
153 if (ImGui::Selectable(animEntries[i].label.c_str(), selected))
154 {
155 selectedSecondaryAnim = i;
156 aModel->animManager->SetSecondary(animEntries[i].animIndex);
157 }
158 if (selected) ImGui::SetItemDefaultFocus();
159 }
160 ImGui::EndCombo();
161 }
162 }
163
164 // Mouth animation
165 {
166 const char* previewMouth = (selectedMouthAnim >= 0 && selectedMouthAnim < static_cast<int>(animEntries.size()))
167 ? animEntries[selectedMouthAnim].label.c_str() : "<none>";
168 if (ImGui::BeginCombo("Mouth##MouthAnim", previewMouth))
169 {
170 if (ImGui::Selectable("<none>", selectedMouthAnim < 0))
171 {
172 selectedMouthAnim = -1;
173 aModel->animManager->ClearMouth();
174 }
175 for (int i = 0; i < static_cast<int>(animEntries.size()); ++i)
176 {
177 bool selected = (i == selectedMouthAnim);
178 if (ImGui::Selectable(animEntries[i].label.c_str(), selected))
179 {
180 selectedMouthAnim = i;
181 aModel->animManager->SetMouth(animEntries[i].animIndex);
182 }
183 if (selected) ImGui::SetItemDefaultFocus();
184 }
185 ImGui::EndCombo();
186 }
187 }
188
189 // Mouth speed
190 if (ImGui::SliderFloat("Mouth Speed", &mouthSpeed, 0.0f, 4.0f, "%.2f"))
191 aModel->animManager->SetMouthSpeed(mouthSpeed);
192 }
193
194 // ---- Skin selector ----
195 if (!ctx.skinEntries || ctx.skinEntries->empty())
196 return;
197
198 auto& skinEntries = *ctx.skinEntries;
199 int& selectedSkin = *ctx.selectedSkin;
200
201 ImGui::SeparatorText("Skin / Texture");
202 const char* previewSkin = (selectedSkin >= 0 && selectedSkin < static_cast<int>(skinEntries.size()))
203 ? skinEntries[selectedSkin].label.c_str() : "<none>";
204 if (ImGui::BeginCombo("##SkinCombo", previewSkin))
205 {
206 for (int i = 0; i < static_cast<int>(skinEntries.size()); ++i)
207 {
208 bool selected = (i == selectedSkin);
209 if (ImGui::Selectable(skinEntries[i].label.c_str(), selected))
210 {
211 if (ctx.applySkin)
212 ctx.applySkin(aModel, i);
213 ctx.blpSkin[0] = ctx.blpSkin[1] = ctx.blpSkin[2] = -1;
214 }
215 if (selected) ImGui::SetItemDefaultFocus();
216 }
217 ImGui::EndCombo();
218 }
219
220 // Per-slot BLP skin selector
221 size_t maxSlots = 0;
222 for (const auto& se : skinEntries)
223 if (se.count > maxSlots) maxSlots = se.count;
224 if (maxSlots > 3) maxSlots = 3;
225
226 if (maxSlots > 1)
227 {
228 const char* slotLabels[3] = { "Texture 1", "Texture 2", "Texture 3" };
229 for (size_t slot = 0; slot < maxSlots; ++slot)
230 {
231 const char* preview = (ctx.blpSkin[slot] >= 0 && ctx.blpSkin[slot] < static_cast<int>(skinEntries.size()))
232 ? skinEntries[ctx.blpSkin[slot]].label.c_str() : "(grouped)";
233 char comboId[32];
234 snprintf(comboId, sizeof(comboId), "##BLPSlot%zu", slot);
235 if (ImGui::BeginCombo(slotLabels[slot], preview))
236 {
237 for (int i = 0; i < static_cast<int>(skinEntries.size()); ++i)
238 {
239 if (!skinEntries[i].tex[0]) continue;
240 bool selected = (i == ctx.blpSkin[slot]);
241 if (ImGui::Selectable(skinEntries[i].label.c_str(), selected))
242 {
243 ctx.blpSkin[slot] = i;
244 aModel->updateTextureList(skinEntries[i].tex[0],
245 skinEntries[i].base + static_cast<int>(slot));
246 }
247 if (selected) ImGui::SetItemDefaultFocus();
248 }
249 ImGui::EndCombo();
250 }
251 }
252 }
253}
254
255} // namespace AnimationPanel
size_t GetFrameCount()
void SetSecondary(int id)
Definition AnimManager.h:56
void SetAnim(short index, unsigned int id, short loop)
void Pause(bool force=false)
bool IsPaused()
void SetFrame(size_t f)
void SetMouthSpeed(float speed)
Definition AnimManager.h:79
void ClearSecondary()
Definition AnimManager.h:62
void AddAnim(unsigned int id, short loop)
void SetMouth(int id)
Definition AnimManager.h:69
void SetSpeed(float speed)
Definition AnimManager.h:97
size_t GetFrame()
Definition AnimManager.h:95
void ClearMouth()
Definition AnimManager.h:75
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
std::vector< ModelAnimation > anims
Definition WoWModel.h:178
size_t currentAnim
Definition WoWModel.h:183
void updateTextureList(GameFile *tex, int special)
AnimManager * animManager
Definition WoWModel.h:180
void draw(DrawContext &ctx)
Draw the Animation panel contents (call between ImGui::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