1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-24 21:07:46 +00:00

Apply racial scaling deforms

This commit is contained in:
Joshua Goins 2023-07-07 16:01:39 -04:00
parent fecb015ff2
commit 8daa9f502d
4 changed files with 229 additions and 106 deletions

View file

@ -33,7 +33,7 @@ public:
explicit GearView(GameData* data); explicit GearView(GameData* data);
/// Returns an inclusive list of races supported by the current gearset. /// Returns an inclusive list of races supported by the current gearset.
std::vector<Race> supportedRaces() const; std::vector<std::pair<Race, Subrace>> supportedRaces() const;
/// Returns an inclusive list of genders supported by the current gearset. /// Returns an inclusive list of genders supported by the current gearset.
std::vector<Gender> supportedGenders() const; std::vector<Gender> supportedGenders() const;
@ -41,31 +41,36 @@ public:
/// Returns an inclusive list of LoDs supported by the current gearset. /// Returns an inclusive list of LoDs supported by the current gearset.
int lodCount() const; 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 gearChanged();
void modelReloaded(); void modelReloaded();
void raceChanged(); void raceChanged();
void subraceChanged();
void genderChanged(); void genderChanged();
void levelOfDetailChanged(); void levelOfDetailChanged();
public Q_SLOTS: public Q_SLOTS:
void clear(); void clear();
void addGear(GearInfo& gear); void addGear(GearInfo& gear);
void setRace(Race race); void setRace(Race race);
void setSubrace(Subrace subrace);
void setGender(Gender gender); void setGender(Gender gender);
void setLevelOfDetail(int lod); void setLevelOfDetail(int lod);
void reloadModel(); void reloadModel();
void reloadRaceDeforms();
private: private:
Race currentRace = Race::Hyur;
Gender currentGender = Gender::Female;
int currentLod = 0; int currentLod = 0;
uint32_t maxLod = 0; uint32_t maxLod = 0;

View file

@ -5,31 +5,40 @@
#include <QDebug> #include <QDebug>
GearView::GearView(GameData *data) : data(data) { 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(); auto layout = new QVBoxLayout();
layout->addWidget(mdlPart); layout->addWidget(mdlPart);
setLayout(layout); 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::raceChanged, this, [=] {
connect(this, &GearView::genderChanged, this, &GearView::reloadModel); reloadRaceDeforms();
connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel); reloadModel();
});
connect(this, &GearView::genderChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel);
} }
std::vector<Race> GearView::supportedRaces() const { std::vector<std::pair<Race, Subrace>> GearView::supportedRaces() const {
std::vector<Race> races; std::vector<std::pair<Race, Subrace>> races;
for (const auto& gear : gears) { for (const auto &gear : gears) {
for(auto [race, race_name] : magic_enum::enum_entries<Race>()) { for (auto [race, race_name] : magic_enum::enum_entries<Race>()) {
auto equip_path = physis_build_equipment_path(gear.modelInfo.primaryID, race, Subrace::Midlander, currentGender, gear.slot); 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)) if (physis_gamedata_exists(data, equip_path))
races.push_back(race); races.emplace_back(race, subrace);
} }
} }
}
return races; return races;
} }
@ -76,9 +85,26 @@ void GearView::setRace(Race race) {
} }
currentRace = 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(); Q_EMIT raceChanged();
} }
void GearView::setSubrace(Subrace subrace) {
if (currentSubrace == subrace) {
return;
}
currentSubrace = subrace;
Q_EMIT subraceChanged();
}
void GearView::setGender(Gender gender) { void GearView::setGender(Gender gender) {
if (currentGender == gender) { if (currentGender == gender) {
return; return;
@ -103,25 +129,50 @@ void GearView::reloadModel() {
maxLod = 0; maxLod = 0;
for (const auto& gear : gears) { 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) { if (mdl_data.size > 0) {
auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data);
std::vector<physis_Material> materials; std::vector<physis_Material> materials;
for (int i = 0; i < mdl.num_material_names; i++) { 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 =
std::string mtrl_path = fmt::format("chara/equipment/e{gearId:04d}/material/v{gearVersion:04d}{}", material_name, // loadedGear.gearInfo->getMtrlPath(201);
fmt::arg("gearId", gear.modelInfo.primaryID), 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)); fmt::arg("gearVersion", gear.modelInfo.gearVersion));
int bodyCode = 1; int bodyCode = 1;
// skin path // skin path
std::string skinmtrl_path = fmt::format("chara/human/c{raceCode:04d}/obj/body/b{bodyCode:04d}/material/v0001{}", material_name, std::string skinmtrl_path = fmt::format(
fmt::arg("raceCode", physis_get_race_code(currentRace, Subrace::Midlander, currentGender)), "chara/human/c{raceCode:04d}/obj/body/b{bodyCode:04d}/"
fmt::arg("bodyCode", bodyCode)); "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())) { if(physis_gamedata_exists(data, mtrl_path.c_str())) {
auto mat = physis_material_parse(physis_gamedata_extract_file(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(); Q_EMIT modelReloaded();
} }
MDLPart &GearView::part() const { void GearView::reloadRaceDeforms() {
return *mdlPart; 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" #include "moc_gearview.cpp"

View file

@ -1,40 +1,43 @@
#include "mdlpart.h" #include "mdlpart.h"
#include "glm/gtx/transform.hpp" #include "glm/gtx/transform.hpp"
#include <QWindow> #include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QVulkanInstance> #include <QVulkanInstance>
#include <QVulkanWindow> #include <QVulkanWindow>
#include <QResizeEvent> #include <QWindow>
#include <assimp/Exporter.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <fmt/core.h> #include <fmt/core.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <assimp/Exporter.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <QVBoxLayout>
#include <glm/gtc/type_ptr.inl> #include <glm/gtc/type_ptr.inl>
#ifndef USE_STANDALONE_WINDOW #ifndef USE_STANDALONE_WINDOW
class VulkanWindow : public QWindow class VulkanWindow : public QWindow {
{
public: public:
VulkanWindow(MDLPart* part, Renderer* renderer, QVulkanInstance* instance) : part(part), m_renderer(renderer), m_instance(instance) { VulkanWindow(MDLPart *part, Renderer *renderer, QVulkanInstance *instance)
setSurfaceType(VulkanSurface); : part(part), m_renderer(renderer), m_instance(instance) {
setVulkanInstance(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) { bool event(QEvent *e) {
@ -346,6 +349,32 @@ void MDLPart::setSkeleton(physis_Skeleton newSkeleton) {
Q_EMIT skeletonChanged(); 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() { void MDLPart::clearSkeleton() {
skeleton.reset(); skeleton.reset();
@ -366,12 +395,14 @@ void MDLPart::reloadRenderer() {
void MDLPart::reloadBoneData() { void MDLPart::reloadBoneData() {
if(skeleton) { if(skeleton) {
// first-time data, TODO split out
if (!firstTimeSkeletonDataCalculated) { if (!firstTimeSkeletonDataCalculated) {
boneData.resize(skeleton->num_bones); if (boneData.empty()) {
boneData.resize(skeleton->num_bones);
}
calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr); calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr);
for (auto &bone: boneData) { for (auto &bone : boneData) {
bone.inversePose = glm::inverse(bone.inversePose); bone.inversePose = glm::inverse(bone.inversePose);
} }
firstTimeSkeletonDataCalculated = true; 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) { 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; 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::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::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::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) { 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); glm::mat4 local = glm::mat4(1.0f);
local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); local = glm::translate(
local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); local, glm::vec3(bone.position[0], bone.position[1], bone.position[2]));
local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[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].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++) { 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}) { 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); calculateBone(skeleton, skeleton.bones[i], &bone);
} }
} }

View file

@ -12,66 +12,67 @@ class VulkanWindow;
class StandaloneWindow; class StandaloneWindow;
class MDLPart : public QWidget { class MDLPart : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit MDLPart(GameData* data); explicit MDLPart(GameData *data);
void exportModel(const QString& fileName); void exportModel(const QString &fileName);
int lastX = -1; int lastX = -1;
int lastY = -1; int lastY = -1;
enum class CameraMode { enum class CameraMode { None, Orbit, Move };
None,
Orbit,
Move
};
CameraMode cameraMode = CameraMode::None; CameraMode cameraMode = CameraMode::None;
float pitch = 0.0f; float pitch = 0.0f;
float yaw = 0.0f; float yaw = 0.0f;
float cameraDistance = 5.0f; float cameraDistance = 5.0f;
glm::vec3 position {0, 0, 0}; glm::vec3 position{0, 0, 0};
std::unique_ptr<physis_Skeleton> skeleton; std::unique_ptr<physis_Skeleton> skeleton;
struct BoneData {
glm::mat4 localTransform, finalTransform, inversePose;
glm::mat4 deformRaceMatrix{1.0f};
};
std::vector<BoneData> boneData;
Q_SIGNALS: Q_SIGNALS:
void modelChanged(); void modelChanged();
void skeletonChanged(); void skeletonChanged();
public Q_SLOTS: public Q_SLOTS:
/// Clears all stored MDLs. /// Clears all stored MDLs.
void clear(); void clear();
/// Adds a new MDL with a list of materials used. /// Adds a new MDL with a list of materials used.
void addModel(physis_MDL mdl, std::vector<physis_Material> materials, int lod); void addModel(physis_MDL mdl, std::vector<physis_Material> materials,
int lod);
/// Sets the skeleton any skinned MDLs should bind to. /// Sets the skeleton any skinned MDLs should bind to.
void setSkeleton(physis_Skeleton skeleton); void setSkeleton(physis_Skeleton skeleton);
/// Clears the current skeleton. /// Sets the race deform matrices
void clearSkeleton(); void loadRaceDeformMatrices(physis_Buffer buffer);
void reloadBoneData(); /// Clears the current skeleton.
void reloadRenderer(); void clearSkeleton();
void reloadBoneData();
void reloadRenderer();
private: 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); void calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone);
GameData* data = nullptr; GameData* data = nullptr;
std::vector<RenderModel> models; std::vector<RenderModel> models;
struct BoneData {
glm::mat4 localTransform, finalTransform, inversePose;
};
std::vector<BoneData> boneData;
Renderer* renderer; Renderer* renderer;
VulkanWindow* vkWindow; VulkanWindow* vkWindow;
StandaloneWindow* standaloneWindow; StandaloneWindow* standaloneWindow;