From 8daa9f502d705896438df65d6400f3cfcbcfdce7 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 7 Jul 2023 16:01:39 -0400 Subject: [PATCH] Apply racial scaling deforms --- mdlviewer/include/gearview.h | 21 +++--- mdlviewer/src/gearview.cpp | 130 +++++++++++++++++++++++++++-------- parts/mdl/mdlpart.cpp | 111 ++++++++++++++++++++---------- parts/mdl/mdlpart.h | 73 ++++++++++---------- 4 files changed, 229 insertions(+), 106 deletions(-) diff --git a/mdlviewer/include/gearview.h b/mdlviewer/include/gearview.h index f5ddf80..084ec12 100644 --- a/mdlviewer/include/gearview.h +++ b/mdlviewer/include/gearview.h @@ -33,7 +33,7 @@ public: explicit GearView(GameData* data); /// Returns an inclusive list of races supported by the current gearset. - std::vector supportedRaces() const; + std::vector> supportedRaces() const; /// Returns an inclusive list of genders supported by the current gearset. std::vector supportedGenders() const; @@ -41,31 +41,36 @@ public: /// Returns an inclusive list of LoDs supported by the current gearset. int lodCount() const; - void exportModel(const QString& fileName); + void exportModel(const QString &fileName); - MDLPart& part() const; + MDLPart &part() const; -Q_SIGNALS: + Race currentRace = Race::Hyur; + Subrace currentSubrace = Subrace::Midlander; + Gender currentGender = Gender::Male; + + Q_SIGNALS: void gearChanged(); void modelReloaded(); void raceChanged(); + void subraceChanged(); void genderChanged(); void levelOfDetailChanged(); -public Q_SLOTS: + public Q_SLOTS: void clear(); void addGear(GearInfo& gear); void setRace(Race race); + void setSubrace(Subrace subrace); void setGender(Gender gender); void setLevelOfDetail(int lod); void reloadModel(); + void reloadRaceDeforms(); -private: - Race currentRace = Race::Hyur; - Gender currentGender = Gender::Female; + private: int currentLod = 0; uint32_t maxLod = 0; diff --git a/mdlviewer/src/gearview.cpp b/mdlviewer/src/gearview.cpp index cbe6500..727cbdb 100644 --- a/mdlviewer/src/gearview.cpp +++ b/mdlviewer/src/gearview.cpp @@ -5,31 +5,40 @@ #include GearView::GearView(GameData *data) : data(data) { - mdlPart = new MDLPart(data); + mdlPart = new MDLPart(data); - mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file("c0101b0001.skel"))); + reloadRaceDeforms(); - auto layout = new QVBoxLayout(); - layout->addWidget(mdlPart); - setLayout(layout); + auto layout = new QVBoxLayout(); + layout->addWidget(mdlPart); + setLayout(layout); - connect(this, &GearView::gearChanged, this, &GearView::reloadModel); + connect(this, &GearView::gearChanged, this, [=] { reloadModel(); }); - connect(this, &GearView::raceChanged, this, &GearView::reloadModel); - connect(this, &GearView::genderChanged, this, &GearView::reloadModel); - connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel); + connect(this, &GearView::raceChanged, this, [=] { + reloadRaceDeforms(); + reloadModel(); + }); + connect(this, &GearView::genderChanged, this, [=] { + reloadRaceDeforms(); + reloadModel(); + }); + connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel); } -std::vector GearView::supportedRaces() const { - std::vector races; - for (const auto& gear : gears) { - for(auto [race, race_name] : magic_enum::enum_entries()) { - auto equip_path = physis_build_equipment_path(gear.modelInfo.primaryID, race, Subrace::Midlander, currentGender, gear.slot); +std::vector> GearView::supportedRaces() const { + std::vector> races; + for (const auto &gear : gears) { + for (auto [race, race_name] : magic_enum::enum_entries()) { + for (auto subrace : physis_get_supported_subraces(race).subraces) { + auto equip_path = physis_build_equipment_path( + gear.modelInfo.primaryID, race, subrace, currentGender, gear.slot); - if(physis_gamedata_exists(data, equip_path)) - races.push_back(race); - } + if (physis_gamedata_exists(data, equip_path)) + races.emplace_back(race, subrace); + } } + } return races; } @@ -76,9 +85,26 @@ void GearView::setRace(Race race) { } currentRace = race; + + auto supportedSubraces = physis_get_supported_subraces(race); + if (supportedSubraces.subraces[0] == currentSubrace || + supportedSubraces.subraces[1] == currentSubrace) { + } else { + setSubrace(supportedSubraces.subraces[0]); + } + Q_EMIT raceChanged(); } +void GearView::setSubrace(Subrace subrace) { + if (currentSubrace == subrace) { + return; + } + + currentSubrace = subrace; + Q_EMIT subraceChanged(); +} + void GearView::setGender(Gender gender) { if (currentGender == gender) { return; @@ -103,25 +129,50 @@ void GearView::reloadModel() { maxLod = 0; for (const auto& gear : gears) { - auto mdl_data = physis_gamedata_extract_file(data, physis_build_equipment_path(gear.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.slot)); + auto mdl_data = physis_gamedata_extract_file( + data, physis_build_equipment_path(gear.modelInfo.primaryID, + currentRace, currentSubrace, + currentGender, gear.slot)); + + // attempt to load the next best race + // currently hardcoded to hyur midlander + Race fallbackRace = currentRace; + Subrace fallbackSubrace = currentSubrace; + if (mdl_data.size == 0) { + mdl_data = physis_gamedata_extract_file( + data, physis_build_equipment_path( + gear.modelInfo.primaryID, Race::Hyur, + Subrace::Midlander, currentGender, gear.slot)); + fallbackRace = Race::Hyur; + fallbackSubrace = Subrace::Midlander; + } + if (mdl_data.size > 0) { auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); std::vector materials; for (int i = 0; i < mdl.num_material_names; i++) { - const char* material_name = mdl.material_names[i]; + const char *material_name = mdl.material_names[i]; - //std::string mtrl_path = loadedGear.gearInfo->getMtrlPath(201); - std::string mtrl_path = fmt::format("chara/equipment/e{gearId:04d}/material/v{gearVersion:04d}{}", material_name, - fmt::arg("gearId", gear.modelInfo.primaryID), + // std::string mtrl_path = + // loadedGear.gearInfo->getMtrlPath(201); + std::string mtrl_path = fmt::format( + "chara/equipment/e{gearId:04d}/material/" + "v{gearVersion:04d}{}", + material_name, fmt::arg("gearId", gear.modelInfo.primaryID), fmt::arg("gearVersion", gear.modelInfo.gearVersion)); int bodyCode = 1; // skin path - std::string skinmtrl_path = fmt::format("chara/human/c{raceCode:04d}/obj/body/b{bodyCode:04d}/material/v0001{}", material_name, - fmt::arg("raceCode", physis_get_race_code(currentRace, Subrace::Midlander, currentGender)), - fmt::arg("bodyCode", bodyCode)); + std::string skinmtrl_path = fmt::format( + "chara/human/c{raceCode:04d}/obj/body/b{bodyCode:04d}/" + "material/v0001{}", + material_name, + fmt::arg("raceCode", + physis_get_race_code(fallbackRace, fallbackSubrace, + currentGender)), + fmt::arg("bodyCode", bodyCode)); if(physis_gamedata_exists(data, mtrl_path.c_str())) { auto mat = physis_material_parse(physis_gamedata_extract_file(data, mtrl_path.c_str())); @@ -143,8 +194,33 @@ void GearView::reloadModel() { Q_EMIT modelReloaded(); } -MDLPart &GearView::part() const { - return *mdlPart; +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; + + 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()))); + + // racial deforms don't work on Hyur, not needed? TODO not sure + if (currentRace != Race::Hyur) { + QString deformName = + QString{"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; } + #include "moc_gearview.cpp" diff --git a/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index e9115a0..b0b3e3c 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -1,40 +1,43 @@ #include "mdlpart.h" #include "glm/gtx/transform.hpp" -#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include #include #ifndef USE_STANDALONE_WINDOW -class VulkanWindow : public QWindow -{ +class VulkanWindow : public QWindow { public: - VulkanWindow(MDLPart* part, Renderer* renderer, QVulkanInstance* instance) : part(part), m_renderer(renderer), m_instance(instance) { - setSurfaceType(VulkanSurface); - setVulkanInstance(instance); + VulkanWindow(MDLPart *part, Renderer *renderer, QVulkanInstance *instance) + : part(part), m_renderer(renderer), m_instance(instance) { + setSurfaceType(VulkanSurface); + setVulkanInstance(instance); + } + + void exposeEvent(QExposeEvent *) { + if (isExposed()) { + if (!m_initialized) { + m_initialized = true; + + auto surface = m_instance->surfaceForWindow(this); + if (!m_renderer->initSwapchain(surface, width(), height())) + m_initialized = false; + else + render(); + } } - - void exposeEvent(QExposeEvent *) { - if (isExposed()) { - if (!m_initialized) { - m_initialized = true; - - auto surface = m_instance->surfaceForWindow(this); - if(!m_renderer->initSwapchain(surface, width(), height())) - m_initialized = false; - else - render(); - } - } } bool event(QEvent *e) { @@ -346,6 +349,32 @@ void MDLPart::setSkeleton(physis_Skeleton newSkeleton) { Q_EMIT skeletonChanged(); } +void MDLPart::loadRaceDeformMatrices(physis_Buffer buffer) { + QJsonDocument document = QJsonDocument::fromJson( + QByteArray((const char *)buffer.data, buffer.size)); + for (auto boneObj : document.object()["Data"].toArray()) { + QJsonArray matrix = boneObj.toObject()["Matrix"].toArray(); + QString boneName = boneObj.toObject()["Name"].toString(); + + glm::mat4 actualMatrix; + int i = 0; + for (auto val : matrix) { + glm::value_ptr(actualMatrix)[i++] = val.toDouble(); + } + + for (int i = 0; i < skeleton->num_bones; i++) { + if (std::string_view{skeleton->bones[i].name} == + boneName.toStdString()) { + auto &data = boneData[i]; + + data.deformRaceMatrix = actualMatrix; + } + } + + firstTimeSkeletonDataCalculated = false; + } +} + void MDLPart::clearSkeleton() { skeleton.reset(); @@ -366,12 +395,14 @@ void MDLPart::reloadRenderer() { void MDLPart::reloadBoneData() { if(skeleton) { - // first-time data, TODO split out if (!firstTimeSkeletonDataCalculated) { - boneData.resize(skeleton->num_bones); + if (boneData.empty()) { + boneData.resize(skeleton->num_bones); + } + calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr); - for (auto &bone: boneData) { + for (auto &bone : boneData) { bone.inversePose = glm::inverse(bone.inversePose); } firstTimeSkeletonDataCalculated = true; @@ -450,7 +481,7 @@ RenderMaterial MDLPart::createMaterial(const physis_Material &material) { void MDLPart::calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& bone, physis_Bone* parent_bone) { const glm::mat4 parentMatrix = parent_bone == nullptr ? glm::mat4(1.0f) : boneData[parent_bone->index].inversePose; - glm::mat4 local(1.0f); + glm::mat4 local = glm::mat4(1.0f); local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); @@ -465,18 +496,28 @@ void MDLPart::calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& b } void MDLPart::calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone) { - const glm::mat4 parent_matrix = parent_bone == nullptr ? glm::mat4(1.0f) : boneData[parent_bone->index].localTransform; + const glm::mat4 parent_matrix = + parent_bone == nullptr ? glm::mat4(1.0f) + : boneData[parent_bone->index].localTransform; glm::mat4 local = glm::mat4(1.0f); - local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); - local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); - local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); + local = glm::translate( + local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); + local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], + bone.rotation[1], bone.rotation[2])); + local = glm::scale(local, + glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); boneData[bone.index].localTransform = parent_matrix * local; - boneData[bone.index].finalTransform = boneData[bone.index].localTransform * boneData[bone.index].inversePose; + boneData[bone.index].finalTransform = + boneData[bone.index].localTransform * + boneData[bone.index].deformRaceMatrix * + boneData[bone.index].inversePose; - for(int i = 0; i < skeleton.num_bones; i++) { - if(skeleton.bones[i].parent_bone != nullptr && std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{bone.name}) { + for (int i = 0; i < skeleton.num_bones; i++) { + if (skeleton.bones[i].parent_bone != nullptr && + std::string_view{skeleton.bones[i].parent_bone->name} == + std::string_view{bone.name}) { calculateBone(skeleton, skeleton.bones[i], &bone); } } diff --git a/parts/mdl/mdlpart.h b/parts/mdl/mdlpart.h index 7a62a23..f13ba6b 100644 --- a/parts/mdl/mdlpart.h +++ b/parts/mdl/mdlpart.h @@ -12,66 +12,67 @@ class VulkanWindow; class StandaloneWindow; class MDLPart : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit MDLPart(GameData* data); + explicit MDLPart(GameData *data); - void exportModel(const QString& fileName); + void exportModel(const QString &fileName); - int lastX = -1; - int lastY = -1; + int lastX = -1; + int lastY = -1; - enum class CameraMode { - None, - Orbit, - Move - }; + enum class CameraMode { None, Orbit, Move }; - CameraMode cameraMode = CameraMode::None; - float pitch = 0.0f; - float yaw = 0.0f; - float cameraDistance = 5.0f; - glm::vec3 position {0, 0, 0}; + CameraMode cameraMode = CameraMode::None; + float pitch = 0.0f; + float yaw = 0.0f; + float cameraDistance = 5.0f; + glm::vec3 position{0, 0, 0}; - std::unique_ptr skeleton; + std::unique_ptr skeleton; + + struct BoneData { + glm::mat4 localTransform, finalTransform, inversePose; + glm::mat4 deformRaceMatrix{1.0f}; + }; + + std::vector boneData; Q_SIGNALS: - void modelChanged(); - void skeletonChanged(); + void modelChanged(); + void skeletonChanged(); public Q_SLOTS: - /// Clears all stored MDLs. - void clear(); + /// Clears all stored MDLs. + void clear(); - /// Adds a new MDL with a list of materials used. - void addModel(physis_MDL mdl, std::vector materials, int lod); + /// Adds a new MDL with a list of materials used. + void addModel(physis_MDL mdl, std::vector materials, + int lod); - /// Sets the skeleton any skinned MDLs should bind to. - void setSkeleton(physis_Skeleton skeleton); + /// Sets the skeleton any skinned MDLs should bind to. + void setSkeleton(physis_Skeleton skeleton); - /// Clears the current skeleton. - void clearSkeleton(); + /// Sets the race deform matrices + void loadRaceDeformMatrices(physis_Buffer buffer); - void reloadBoneData(); - void reloadRenderer(); + /// Clears the current skeleton. + void clearSkeleton(); + + void reloadBoneData(); + void reloadRenderer(); private: - RenderMaterial createMaterial(const physis_Material& mat); + RenderMaterial createMaterial(const physis_Material &mat); - void calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& bone, physis_Bone* parent_bone); + void calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& bone, physis_Bone* parent_bone); void calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone); GameData* data = nullptr; std::vector models; - struct BoneData { - glm::mat4 localTransform, finalTransform, inversePose; - }; - - std::vector boneData; - Renderer* renderer; VulkanWindow* vkWindow; StandaloneWindow* standaloneWindow;