198 assert(ctx.
animEntries &&
"DrawContext::animEntries must not be null");
199 assert(ctx.
selectedAnimCombo &&
"DrawContext::selectedAnimCombo must not be null");
200 assert(ctx.
fbo &&
"DrawContext::fbo must not be null");
201 assert(ctx.
camera &&
"DrawContext::camera must not be null");
205 ImGui::TextDisabled(
"Game not loaded.");
217 const bool isChar = model && ctx.
isChar;
219 const ImVec2 avail = ImGui::GetContentRegionAvail();
220 const float spacing = ImGui::GetStyle().ItemSpacing.x;
221 const float centerW = avail.x - s_leftWidth - s_rightWidth - spacing * 2.0f;
226 ImGui::BeginChild(
"##cvLeft", ImVec2(s_leftWidth, -1), ImGuiChildFlags_Borders);
229 ImGui::SeparatorText(
"Race");
231 const char* racePreview =
232 (s_selectedRaceIdx >= 0 &&
233 s_selectedRaceIdx < static_cast<int>(s_races.size()))
234 ? s_races[s_selectedRaceIdx].name.c_str()
237 ImGui::SetNextItemWidth(-1);
238 if (ImGui::BeginCombo(
"##cvRace", racePreview))
240 for (
int i = 0; i < static_cast<int>(s_races.size()); ++i)
242 const auto& race = s_races[i];
243 bool selected = (i == s_selectedRaceIdx);
244 std::string label = race.name +
"##race" + std::to_string(race.raceID);
245 if (ImGui::Selectable(label.c_str(), selected) && !selected)
247 s_selectedRaceIdx = i;
248 buildBodyTypes(race.raceID);
249 if (!s_bodyTypes.empty())
251 s_selectedBodyType = 0;
252 loadBodyType(s_bodyTypes[0].fileDataID, ctx);
255 if (selected) ImGui::SetItemDefaultFocus();
261 if (!s_bodyTypes.empty())
263 const char* bodyPreview =
264 (s_selectedBodyType >= 0 &&
265 s_selectedBodyType < static_cast<int>(s_bodyTypes.size()))
266 ? s_bodyTypes[s_selectedBodyType].label.c_str()
269 ImGui::SetNextItemWidth(-1);
270 if (ImGui::BeginCombo(
"##cvBodyType", bodyPreview))
272 for (
int i = 0; i < static_cast<int>(s_bodyTypes.size()); ++i)
274 bool selected = (i == s_selectedBodyType);
275 std::string bodyLabel = s_bodyTypes[i].label +
"##body" + std::to_string(s_bodyTypes[i].chrModelID);
276 if (ImGui::Selectable(bodyLabel.c_str(), selected) && !selected)
278 s_selectedBodyType = i;
279 loadBodyType(s_bodyTypes[i].fileDataID, ctx);
281 if (selected) ImGui::SetItemDefaultFocus();
291 const auto* catTable =
WOWDB.getTable(
"ChrCustomizationCategory");
293 unsigned int lastCatID = UINT_MAX;
296 if (opt.choiceNames.empty())
continue;
299 if (opt.categoryID != lastCatID)
301 lastCatID = opt.categoryID;
305 auto catRow = catTable->getRow(opt.categoryID);
307 catName = catRow.getString(
"CategoryName_Lang");
310 catName =
"Customization";
311 ImGui::SeparatorText(catName.c_str());
314 const char* preview =
315 (opt.selectedIndex >= 0 &&
316 opt.selectedIndex <
static_cast<int>(opt.choiceNames.size()))
317 ? opt.choiceNames[opt.selectedIndex].c_str()
319 ImGui::Text(
"%s:", opt.name.c_str());
320 ImGui::SetNextItemWidth(-1);
321 std::string comboID =
"##cvOpt" + std::to_string(opt.optionID);
322 if (ImGui::BeginCombo(comboID.c_str(), preview))
324 for (
int c = 0; c < static_cast<int>(opt.choiceNames.size()); ++c)
326 bool sel = (c == opt.selectedIndex);
327 std::string choiceLabel = opt.choiceNames[c] +
"##ch" + std::to_string(opt.choiceIDs[c]);
328 if (ImGui::Selectable(choiceLabel.c_str(), sel))
330 if (c != opt.selectedIndex)
332 opt.selectedIndex = c;
333 model->
cd.
set(opt.optionID, opt.choiceIDs[c]);
337 if (sel) ImGui::SetItemDefaultFocus();
351 ImGui::BeginChild(
"##cvCenter",
352 ImVec2(centerW > 100.0f ? centerW : 100.0f, -1),
353 ImGuiChildFlags_None);
362 const char* animPreview =
363 (selectedAnimCombo >= 0 &&
364 selectedAnimCombo < static_cast<int>(animEntries.size()))
365 ? animEntries[selectedAnimCombo].label.c_str()
368 ImGui::SetNextItemWidth(180);
369 if (ImGui::BeginCombo(
"##cvAnim", animPreview))
371 for (
int i = 0; i < static_cast<int>(animEntries.size()); ++i)
373 bool selected = (i == selectedAnimCombo);
374 if (ImGui::Selectable(animEntries[i].label.c_str(), selected))
376 selectedAnimCombo = i;
377 int idx = animEntries[i].animIndex;
382 if (selected) ImGui::SetItemDefaultFocus();
388 if (ImGui::Button(
"\xE2\x97\x80##cv"))
393 if (ImGui::Button(
"\xE2\x96\xB6##cv"))
398 if (ImGui::Button(
"\xE2\x8F\xB8##cv"))
402 if (ImGui::Button(
"\xE2\x96\xB8##cv"))
406 ImGui::SetNextItemWidth(120);
411 if (ImGui::SliderInt(
"##cvFrame", &curFrame, 0, frameCount))
416 ImGui::Text(
"%d", curFrame);
420 ImVec2 vpSize = ImGui::GetContentRegionAvail();
421 int vpW =
static_cast<int>(vpSize.x);
422 int vpH =
static_cast<int>(vpSize.y);
428 [root = ctx.
root]() {
429 glEnable(GL_LIGHTING);
430 glEnable(GL_TEXTURE_2D);
431 glEnable(GL_DEPTH_TEST);
432 glDepthFunc(GL_LEQUAL);
434 glEnable(GL_TEXTURE_2D);
435 glDisable(GL_LIGHTING);
436 glDepthMask(GL_FALSE);
438 root->drawParticles();
440 glDepthMask(GL_TRUE);
443 static_cast<ImTextureID
>(
static_cast<uintptr_t
>(ctx.
fbo->
colorTex)),
444 vpSize, ImVec2(0, 1), ImVec2(1, 0));
457 ImGui::BeginChild(
"##cvRight", ImVec2(s_rightWidth, -1), ImGuiChildFlags_Borders);
459 ImGui::SeparatorText(
"Equipment");
461 static const char* slotLabels[] = {
462 "Head",
"Neck",
"Shoulder",
"Back",
"Chest",
"Shirt",
463 "Tabard",
"Wrist",
"Hands",
"Waist",
"Legs",
"Feet",
464 "Main-hand",
"Off-hand"
467 for (
int i = 0; i < 14; ++i)
469 float w = ImGui::GetContentRegionAvail().x;
472 ImGui::Text(
"%s:", slotLabels[i]);
473 ImGui::SameLine(w - ImGui::CalcTextSize(
"Empty").x);
474 ImGui::TextDisabled(
"Empty");
476 if (i < 13) ImGui::Separator();
482 if (ImGui::Button(
"Clear All Equipment", ImVec2(-1, 0)))
490 ImGui::SeparatorText(
"Export");
494 if (ImGui::Button(
"Export glTF", ImVec2(-1, 0)))
497 ImGui::SetWindowFocus(
"Export");
502 ImGui::TextDisabled(
"Load a character first.");