1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-26 13:47:46 +00:00
novus/armoury/src/gearview.cpp

433 lines
14 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "gearview.h"
#include <QDebug>
#include <QThreadPool>
2023-07-07 16:16:21 -04:00
#include <QVBoxLayout>
#include <QtConcurrent>
2023-09-26 17:09:12 -04:00
#include <imgui.h>
#include "filecache.h"
#include "magic_enum.hpp"
GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) {
mdlPart = new MDLPart(data, cache);
2023-07-07 16:01:39 -04:00
reloadRaceDeforms();
2023-07-07 16:16:21 -04:00
auto layout = new QVBoxLayout();
layout->addWidget(mdlPart);
setLayout(layout);
mdlPart->requestUpdate = [this] {
auto &io = ImGui::GetIO();
if (updating) {
if (ImGui::Begin("Loading", nullptr, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs)) {
ImGui::SetWindowPos(ImVec2(0, 0));
ImGui::SetWindowSize(io.DisplaySize);
const char *loadingLabel{"Loading gear..."};
ImGui::SetCursorPosX((io.DisplaySize.x - ImGui::CalcTextSize(loadingLabel).x) * 0.5f);
ImGui::SetCursorPosY((io.DisplaySize.y - ImGui::CalcTextSize(loadingLabel).y) * 0.5f);
ImGui::Text("%s", loadingLabel);
}
ImGui::End();
}
2023-09-26 17:09:12 -04:00
if (updating) {
return;
}
if (needsUpdate()) {
updating = true;
Q_EMIT loadingChanged(true);
QtConcurrent::run(QThreadPool::globalInstance(), [this] {
updatePart();
Q_EMIT loadingChanged(false);
});
}
};
}
2023-07-07 16:01:39 -04:00
std::vector<std::pair<Race, Subrace>> GearView::supportedRaces() const {
2023-07-07 16:16:21 -04:00
std::vector<std::pair<Race, Subrace>> races;
for (const auto &gear : loadedGears) {
for (const auto [race, race_name] : magic_enum::enum_entries<Race>()) {
for (const auto subrace : physis_get_supported_subraces(race).subraces) {
auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, race, subrace, currentGender, gear.info.slot);
2023-07-07 16:16:21 -04:00
if (cache.fileExists(QLatin1String(equip_path)))
2023-07-07 16:16:21 -04:00
races.emplace_back(race, subrace);
}
}
}
return races;
}
std::vector<Gender> GearView::supportedGenders() const {
std::vector<Gender> genders;
for (const auto &gear : loadedGears) {
2023-07-07 16:16:21 -04:00
for (auto [gender, gender_name] : magic_enum::enum_entries<Gender>()) {
auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.info.slot);
if (cache.fileExists(QLatin1String(equip_path)))
genders.push_back(gender);
}
}
return genders;
}
int GearView::lodCount() const {
return maxLod;
}
2023-07-07 16:16:21 -04:00
void GearView::exportModel(const QString& fileName) {
mdlPart->exportModel(fileName);
}
void GearView::addGear(GearInfo &gear)
{
qDebug() << "Adding gear" << gear.name.c_str();
queuedGearAdditions.emplace_back(gear);
gearDirty = true;
Q_EMIT gearChanged();
}
void GearView::removeGear(GearInfo &gear)
{
qDebug() << "Removing gear" << gear.name.c_str();
queuedGearRemovals.emplace_back(gear);
gearDirty = true;
Q_EMIT gearChanged();
}
void GearView::setRace(Race race) {
if (currentRace == race) {
return;
}
currentRace = race;
2023-07-07 16:01:39 -04:00
const auto supportedSubraces = physis_get_supported_subraces(race);
if (supportedSubraces.subraces[0] != currentSubrace && supportedSubraces.subraces[1] != currentSubrace) {
2023-07-07 16:01:39 -04:00
setSubrace(supportedSubraces.subraces[0]);
}
if (race == Race::AuRa || race == Race::Miqote) {
setTail(1);
} else {
setTail(-1);
}
raceDirty = true;
Q_EMIT raceChanged();
}
2023-07-07 16:01:39 -04:00
void GearView::setSubrace(Subrace subrace) {
if (currentSubrace == subrace) {
return;
}
currentSubrace = subrace;
// Hyur is the only race that has two different subraces
if (currentRace == Race::Hyur) {
raceDirty = true;
}
2023-07-07 16:01:39 -04:00
Q_EMIT subraceChanged();
}
void GearView::setGender(Gender gender) {
if (currentGender == gender) {
return;
}
currentGender = gender;
raceDirty = true;
Q_EMIT genderChanged();
}
void GearView::setLevelOfDetail(int lod) {
if (currentLod == lod) {
return;
}
currentLod = lod;
Q_EMIT levelOfDetailChanged();
}
void GearView::setFace(const int faceCode)
{
if (face == faceCode) {
return;
}
if (faceCode == -1) {
face = std::nullopt;
} else {
face = faceCode;
}
faceDirty = true;
Q_EMIT faceChanged();
}
void GearView::setHair(int hairCode)
{
if (hair == hairCode) {
return;
}
if (hairCode == -1) {
hair = std::nullopt;
} else {
hair = hairCode;
}
hairDirty = true;
Q_EMIT hairChanged();
}
void GearView::setEar(const int earCode)
{
if (ear == earCode) {
return;
}
if (earCode == -1) {
ear = std::nullopt;
} else {
ear = earCode;
}
earDirty = true;
Q_EMIT earChanged();
}
void GearView::setTail(const int tailCode)
{
if (tail == tailCode) {
return;
}
if (tailCode == -1) {
tail = std::nullopt;
} else {
tail = tailCode;
}
tailDirty = true;
Q_EMIT tailChanged();
}
void GearView::reloadRaceDeforms()
{
qDebug() << "Loading race deform matrices for " << magic_enum::enum_name(currentRace).data() << magic_enum::enum_name(currentSubrace).data()
<< magic_enum::enum_name(currentGender).data();
const int raceCode = physis_get_race_code(currentRace, currentSubrace, currentGender);
qDebug() << "Race code: " << raceCode;
2023-09-26 00:37:55 -04:00
QString skelName = QStringLiteral("c%1b0001.skel").arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str())));
// racial deforms don't work on Hyur Midlander, not needed? TODO not sure
if (currentSubrace != Subrace::Midlander) {
2023-09-26 00:37:55 -04:00
QString deformName = QStringLiteral("c%1_deform.json").arg(raceCode, 4, 10, QLatin1Char{'0'});
mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str()));
} else {
for (auto &data : mdlPart->boneData) {
data.deformRaceMatrix = glm::mat4(1.0f);
}
}
}
MDLPart &GearView::part() const
{
return *mdlPart;
}
2023-07-07 16:01:39 -04:00
void GearView::updatePart()
{
qInfo() << raceDirty << gearDirty << updating;
if (raceDirty) {
// if race changes, all of the models need to be reloaded.
// TODO: in the future, we can be a bit smarter about this, lots of races use the same model (hyur)
for (auto &part : loadedGears) {
mdlPart->removeModel(part.mdl);
2023-07-07 16:01:39 -04:00
}
queuedGearAdditions = loadedGears;
loadedGears.clear();
gearDirty = true;
}
2023-07-07 16:01:39 -04:00
if (gearDirty) {
for (auto &gearAddition : queuedGearAdditions) {
qInfo() << "Looking up" << magic_enum::enum_name(currentRace) << magic_enum::enum_name(currentSubrace) << magic_enum::enum_name(currentGender);
2023-09-26 00:37:55 -04:00
auto mdl_data = cache.lookupFile(QLatin1String(
physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gearAddition.info.slot)));
// attempt to load the next best race
// currently hardcoded to hyur midlander
Race fallbackRace = currentRace;
Subrace fallbackSubrace = currentSubrace;
if (mdl_data.size == 0) {
2023-09-26 00:37:55 -04:00
mdl_data = cache.lookupFile(QLatin1String(
physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gearAddition.info.slot)));
fallbackRace = Race::Hyur;
fallbackSubrace = Subrace::Midlander;
}
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
2023-07-07 16:01:39 -04:00
std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) {
const char *material_name = mdl.material_names[i];
const std::string mtrl_path = gearAddition.info.getMtrlPath(material_name);
const std::string skinmtrl_path =
physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name);
if (cache.fileExists(QLatin1String(mtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(mtrl_path.c_str())));
materials.push_back(mat);
}
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(skinmtrl_path.c_str())));
materials.push_back(mat);
}
}
maxLod = std::max(mdl.num_lod, maxLod);
mdlPart->addModel(mdl, materials, currentLod);
gearAddition.mdl = mdl;
loadedGears.push_back(gearAddition);
}
}
for (auto &queuedRemoval : queuedGearRemovals) {
mdlPart->removeModel(queuedRemoval.mdl);
loadedGears.erase(std::remove_if(loadedGears.begin(),
loadedGears.end(),
[queuedRemoval](const LoadedGear other) {
return queuedRemoval.info == other.info;
}),
loadedGears.end());
}
}
2023-07-06 17:38:19 -04:00
if (face) {
2023-09-26 00:37:55 -04:00
auto mdl_data =
cache.lookupFile(QLatin1String(physis_build_character_path(CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender)));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) {
const char* material_name = mdl.material_names[i];
const std::string skinmtrl_path = physis_build_face_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *face, material_name);
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(skinmtrl_path.c_str())));
materials.push_back(mat);
}
}
mdlPart->addModel(mdl, materials, currentLod);
}
}
if (hair) {
2023-09-26 00:37:55 -04:00
auto mdl_data =
cache.lookupFile(QLatin1String(physis_build_character_path(CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender)));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) {
const char* material_name = mdl.material_names[i];
const std::string skinmtrl_path = physis_build_hair_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *hair, material_name);
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(skinmtrl_path.c_str())));
materials.push_back(mat);
}
}
mdlPart->addModel(mdl, materials, currentLod);
}
}
if (ear) {
2023-09-26 00:37:55 -04:00
auto mdl_data = cache.lookupFile(QLatin1String(physis_build_character_path(CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender)));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) {
const char* material_name = mdl.material_names[i];
const std::string skinmtrl_path = physis_build_ear_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *ear, material_name);
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(skinmtrl_path.c_str())));
materials.push_back(mat);
}
}
mdlPart->addModel(mdl, materials, currentLod);
}
}
if (tail) {
2023-09-26 00:37:55 -04:00
auto mdl_data =
cache.lookupFile(QLatin1String(physis_build_character_path(CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender)));
if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
const char* material_name = mdl.material_names[0];
const std::string skinmtrl_path = physis_build_tail_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *tail, material_name);
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
2023-09-26 00:37:55 -04:00
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(skinmtrl_path.c_str())));
mdlPart->addModel(mdl, {mat}, currentLod);
}
}
}
raceDirty = false;
gearDirty = false;
updating = false;
faceDirty = false;
hairDirty = false;
earDirty = false;
tailDirty = false;
2023-07-06 17:38:19 -04:00
}
bool GearView::needsUpdate() const
{
return gearDirty || raceDirty || faceDirty || hairDirty || earDirty || tailDirty;
2023-07-07 16:16:21 -04:00
}
2023-07-07 16:01:39 -04:00
#include "moc_gearview.cpp"