1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-24 13:07:44 +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);
/// 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.
std::vector<Gender> 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;

View file

@ -7,27 +7,36 @@
GearView::GearView(GameData *data) : data(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);
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::raceChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::genderChanged, this, [=] {
reloadRaceDeforms();
reloadModel();
});
connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel);
}
std::vector<Race> GearView::supportedRaces() const {
std::vector<Race> races;
for (const auto& gear : gears) {
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);
std::vector<std::pair<Race, Subrace>> GearView::supportedRaces() const {
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);
if(physis_gamedata_exists(data, equip_path))
races.push_back(race);
if (physis_gamedata_exists(data, equip_path))
races.emplace_back(race, subrace);
}
}
}
@ -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,24 +129,49 @@ 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<physis_Material> 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)),
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())) {
@ -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"

View file

@ -1,24 +1,27 @@
#include "mdlpart.h"
#include "glm/gtx/transform.hpp"
#include <QWindow>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QVulkanInstance>
#include <QVulkanWindow>
#include <QResizeEvent>
#include <QWindow>
#include <assimp/Exporter.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <fmt/core.h>
#include <glm/glm.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>
#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) {
VulkanWindow(MDLPart *part, Renderer *renderer, QVulkanInstance *instance)
: part(part), m_renderer(renderer), m_instance(instance) {
setSurfaceType(VulkanSurface);
setVulkanInstance(instance);
}
@ -29,7 +32,7 @@ public:
m_initialized = true;
auto surface = m_instance->surfaceForWindow(this);
if(!m_renderer->initSwapchain(surface, width(), height()))
if (!m_renderer->initSwapchain(surface, width(), height()))
m_initialized = false;
else
render();
@ -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) {
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);
}
}

View file

@ -15,27 +15,30 @@ class MDLPart : public QWidget {
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;
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};
glm::vec3 position{0, 0, 0};
std::unique_ptr<physis_Skeleton> skeleton;
struct BoneData {
glm::mat4 localTransform, finalTransform, inversePose;
glm::mat4 deformRaceMatrix{1.0f};
};
std::vector<BoneData> boneData;
Q_SIGNALS:
void modelChanged();
void skeletonChanged();
@ -45,11 +48,15 @@ public Q_SLOTS:
void clear();
/// 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.
void setSkeleton(physis_Skeleton skeleton);
/// Sets the race deform matrices
void loadRaceDeformMatrices(physis_Buffer buffer);
/// Clears the current skeleton.
void clearSkeleton();
@ -57,7 +64,7 @@ public Q_SLOTS:
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 calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone);
@ -66,12 +73,6 @@ private:
std::vector<RenderModel> models;
struct BoneData {
glm::mat4 localTransform, finalTransform, inversePose;
};
std::vector<BoneData> boneData;
Renderer* renderer;
VulkanWindow* vkWindow;
StandaloneWindow* standaloneWindow;