2023-08-06 08:48:11 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
#include "gearview.h"
|
|
|
|
|
|
|
|
#include <QDebug>
|
2023-07-07 16:16:21 -04:00
|
|
|
#include <QVBoxLayout>
|
2023-04-09 15:31:19 -04:00
|
|
|
|
2023-07-09 10:54:27 -04:00
|
|
|
#include "filecache.h"
|
2023-08-06 08:48:11 -04:00
|
|
|
#include "magic_enum.hpp"
|
2023-07-09 10:54:27 -04:00
|
|
|
|
|
|
|
GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) {
|
|
|
|
mdlPart = new MDLPart(data, cache);
|
2023-04-09 15:31:19 -04:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
connect(this, &GearView::gearChanged, this, [=] {
|
|
|
|
reloadModel();
|
|
|
|
});
|
|
|
|
connect(this, &GearView::raceChanged, this, [=] {
|
|
|
|
reloadRaceDeforms();
|
|
|
|
reloadModel();
|
|
|
|
});
|
2023-07-07 16:29:43 -04:00
|
|
|
connect(this, &GearView::subraceChanged, this, [=] {
|
|
|
|
reloadRaceDeforms();
|
|
|
|
reloadModel();
|
|
|
|
});
|
2023-07-07 16:16:21 -04:00
|
|
|
connect(this, &GearView::genderChanged, this, [=] {
|
|
|
|
reloadRaceDeforms();
|
|
|
|
reloadModel();
|
|
|
|
});
|
|
|
|
connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel);
|
2023-07-08 11:58:38 -04:00
|
|
|
|
|
|
|
connect(this, &GearView::faceChanged, this, &GearView::reloadModel);
|
|
|
|
connect(this, &GearView::hairChanged, this, &GearView::reloadModel);
|
|
|
|
connect(this, &GearView::earChanged, this, &GearView::reloadModel);
|
|
|
|
connect(this, &GearView::tailChanged, this, &GearView::reloadModel);
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
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 : gears) {
|
|
|
|
for (auto [race, race_name] : magic_enum::enum_entries<Race>()) {
|
|
|
|
for (auto subrace : physis_get_supported_subraces(race).subraces) {
|
|
|
|
auto equip_path =
|
|
|
|
physis_build_equipment_path(gear.modelInfo.primaryID, race, subrace, currentGender, gear.slot);
|
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(equip_path)))
|
2023-07-07 16:16:21 -04:00
|
|
|
races.emplace_back(race, subrace);
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return races;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Gender> GearView::supportedGenders() const {
|
|
|
|
std::vector<Gender> genders;
|
|
|
|
for (const auto& gear : gears) {
|
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.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.slot);
|
2023-04-09 15:31:19 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(equip_path)))
|
2023-04-09 15:31:19 -04:00
|
|
|
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) {
|
2023-04-09 15:31:19 -04:00
|
|
|
mdlPart->exportModel(fileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::clear() {
|
|
|
|
gears.clear();
|
|
|
|
|
|
|
|
Q_EMIT gearChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::addGear(GearInfo& gear) {
|
|
|
|
qDebug() << "Adding gear" << gear.name.c_str();
|
|
|
|
|
|
|
|
gears.push_back(gear);
|
|
|
|
|
|
|
|
Q_EMIT gearChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::setRace(Race race) {
|
|
|
|
if (currentRace == race) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentRace = race;
|
2023-07-07 16:01:39 -04:00
|
|
|
|
|
|
|
auto supportedSubraces = physis_get_supported_subraces(race);
|
2023-07-07 16:16:21 -04:00
|
|
|
if (supportedSubraces.subraces[0] == currentSubrace || supportedSubraces.subraces[1] == currentSubrace) {
|
2023-07-07 16:01:39 -04:00
|
|
|
} else {
|
|
|
|
setSubrace(supportedSubraces.subraces[0]);
|
|
|
|
}
|
|
|
|
|
2023-07-08 11:58:38 -04:00
|
|
|
if (race == Race::AuRa || race == Race::Miqote) {
|
|
|
|
setTail(1);
|
|
|
|
} else {
|
|
|
|
setTail(-1);
|
|
|
|
}
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
Q_EMIT raceChanged();
|
|
|
|
}
|
|
|
|
|
2023-07-07 16:01:39 -04:00
|
|
|
void GearView::setSubrace(Subrace subrace) {
|
|
|
|
if (currentSubrace == subrace) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentSubrace = subrace;
|
|
|
|
Q_EMIT subraceChanged();
|
|
|
|
}
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
void GearView::setGender(Gender gender) {
|
|
|
|
if (currentGender == gender) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentGender = gender;
|
|
|
|
Q_EMIT genderChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::setLevelOfDetail(int lod) {
|
|
|
|
if (currentLod == lod) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
currentLod = lod;
|
|
|
|
Q_EMIT levelOfDetailChanged();
|
|
|
|
}
|
|
|
|
|
2023-07-08 11:58:38 -04:00
|
|
|
void GearView::setFace(int bodyVer) {
|
|
|
|
if (face == bodyVer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bodyVer == -1) {
|
|
|
|
face = std::nullopt;
|
|
|
|
} else {
|
|
|
|
face = bodyVer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT faceChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::setHair(int bodyVer) {
|
|
|
|
if (hair == bodyVer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bodyVer == -1) {
|
|
|
|
hair = std::nullopt;
|
|
|
|
} else {
|
|
|
|
hair = bodyVer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT hairChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::setEar(int bodyVer) {
|
|
|
|
if (ear == bodyVer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bodyVer == -1) {
|
|
|
|
ear = std::nullopt;
|
|
|
|
} else {
|
|
|
|
ear = bodyVer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT earChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GearView::setTail(int bodyVer) {
|
|
|
|
if (tail == bodyVer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bodyVer == -1) {
|
|
|
|
tail = std::nullopt;
|
|
|
|
} else {
|
|
|
|
tail = bodyVer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT tailChanged();
|
|
|
|
}
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
void GearView::reloadModel() {
|
|
|
|
mdlPart->clear();
|
|
|
|
|
|
|
|
maxLod = 0;
|
|
|
|
|
|
|
|
for (const auto& gear : gears) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mdl_data = cache.lookupFile(physis_build_equipment_path(
|
2023-07-07 16:16:21 -04:00
|
|
|
gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot));
|
2023-07-07 16:01:39 -04:00
|
|
|
|
|
|
|
// attempt to load the next best race
|
|
|
|
// currently hardcoded to hyur midlander
|
|
|
|
Race fallbackRace = currentRace;
|
|
|
|
Subrace fallbackSubrace = currentSubrace;
|
|
|
|
if (mdl_data.size == 0) {
|
2023-07-09 10:54:27 -04:00
|
|
|
mdl_data = cache.lookupFile(physis_build_equipment_path(
|
2023-07-07 16:16:21 -04:00
|
|
|
gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot));
|
2023-07-07 16:01:39 -04:00
|
|
|
fallbackRace = Race::Hyur;
|
|
|
|
fallbackSubrace = Subrace::Midlander;
|
|
|
|
}
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
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++) {
|
2023-07-07 16:16:21 -04:00
|
|
|
const char* material_name = mdl.material_names[i];
|
2023-07-07 16:01:39 -04:00
|
|
|
|
2023-09-23 14:51:47 -04:00
|
|
|
const std::string mtrl_path = gear.getMtrlPath(material_name);
|
|
|
|
const std::string skinmtrl_path = physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name);
|
2023-04-09 15:31:19 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(mtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(mtrl_path.c_str()));
|
2023-04-09 15:31:19 -04:00
|
|
|
materials.push_back(mat);
|
|
|
|
}
|
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str()));
|
2023-04-09 15:31:19 -04:00
|
|
|
materials.push_back(mat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
maxLod = std::max(mdl.num_lod, maxLod);
|
|
|
|
|
|
|
|
mdlPart->addModel(mdl, materials, currentLod);
|
|
|
|
}
|
|
|
|
}
|
2023-07-06 17:38:19 -04:00
|
|
|
|
2023-07-08 11:58:38 -04:00
|
|
|
if (face) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mdl_data = cache.lookupFile(physis_build_character_path(
|
2023-07-08 11:58:38 -04:00
|
|
|
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];
|
2023-09-23 14:51:47 -04:00
|
|
|
const std::string skinmtrl_path = physis_build_face_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *face, material_name);
|
2023-07-08 11:58:38 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str()));
|
2023-07-08 11:58:38 -04:00
|
|
|
materials.push_back(mat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mdlPart->addModel(mdl, materials, currentLod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hair) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mdl_data = cache.lookupFile(physis_build_character_path(
|
2023-07-08 11:58:38 -04:00
|
|
|
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];
|
2023-09-23 14:51:47 -04:00
|
|
|
const std::string skinmtrl_path = physis_build_hair_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *hair, material_name);
|
2023-07-08 11:58:38 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str()));
|
2023-07-08 11:58:38 -04:00
|
|
|
materials.push_back(mat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mdlPart->addModel(mdl, materials, currentLod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ear) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mdl_data = cache.lookupFile(physis_build_character_path(
|
2023-07-08 11:58:38 -04:00
|
|
|
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];
|
2023-09-23 14:51:47 -04:00
|
|
|
const std::string skinmtrl_path = physis_build_ear_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *ear, material_name);
|
2023-07-08 11:58:38 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str()));
|
2023-07-08 11:58:38 -04:00
|
|
|
materials.push_back(mat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mdlPart->addModel(mdl, materials, currentLod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tail) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mdl_data = cache.lookupFile(physis_build_character_path(
|
2023-07-08 11:58:38 -04:00
|
|
|
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];
|
2023-09-23 14:51:47 -04:00
|
|
|
const std::string skinmtrl_path = physis_build_tail_material_path(physis_get_race_code(currentRace, currentSubrace, currentGender), *tail, material_name);
|
2023-07-08 11:58:38 -04:00
|
|
|
|
2023-09-25 22:03:33 -04:00
|
|
|
if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) {
|
2023-07-09 10:54:27 -04:00
|
|
|
auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str()));
|
2023-07-08 11:58:38 -04:00
|
|
|
mdlPart->addModel(mdl, {mat}, currentLod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-06 17:38:19 -04:00
|
|
|
Q_EMIT modelReloaded();
|
|
|
|
}
|
|
|
|
|
2023-07-07 16:01:39 -04:00
|
|
|
void GearView::reloadRaceDeforms() {
|
2023-07-07 16:16:21 -04:00
|
|
|
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);
|
2023-07-07 16:01:39 -04:00
|
|
|
qDebug() << "Race code: " << raceCode;
|
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
QString skelName = QString{"c%1b0001.skel"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
|
|
|
|
mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str())));
|
2023-07-07 16:01:39 -04:00
|
|
|
|
2023-07-08 09:53:10 -04:00
|
|
|
// racial deforms don't work on Hyur Midlander, not needed? TODO not sure
|
|
|
|
if (currentSubrace != Subrace::Midlander) {
|
2023-07-07 16:16:21 -04:00
|
|
|
QString deformName = QString{"c%1_deform.json"}.arg(raceCode, 4, 10, QLatin1Char{'0'});
|
|
|
|
mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str()));
|
2023-07-07 16:01:39 -04:00
|
|
|
} else {
|
2023-07-07 16:16:21 -04:00
|
|
|
for (auto& data : mdlPart->boneData) {
|
2023-07-07 16:01:39 -04:00
|
|
|
data.deformRaceMatrix = glm::mat4(1.0f);
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
MDLPart& GearView::part() const {
|
|
|
|
return *mdlPart;
|
|
|
|
}
|
2023-07-07 16:01:39 -04:00
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
#include "moc_gearview.cpp"
|