mirror of
https://github.com/redstrate/Novus.git
synced 2025-04-26 21:57:45 +00:00
These parts (inspired by the KDE parts system) will allow the tooling to reuse GUI mechanisms. Right now the two supported parts are for Excel and Models, and exdviewer and mdlviewer will be retrofitted to them in future commits.
408 lines
No EOL
14 KiB
C++
408 lines
No EOL
14 KiB
C++
#include "mdlpart.h"
|
|
#include "glm/gtx/transform.hpp"
|
|
|
|
#include <QWindow>
|
|
#include <QVulkanInstance>
|
|
#include <QVulkanWindow>
|
|
#include <QResizeEvent>
|
|
#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
|
|
{
|
|
public:
|
|
VulkanWindow(Renderer* renderer, QVulkanInstance* instance) : 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool event(QEvent *e) {
|
|
if (e->type() == QEvent::UpdateRequest)
|
|
render();
|
|
|
|
if (e->type() == QEvent::Resize) {
|
|
QResizeEvent* resizeEvent = (QResizeEvent*)e;
|
|
auto surface = m_instance->surfaceForWindow(this);
|
|
m_renderer->resize(surface, resizeEvent->size().width(), resizeEvent->size().height());
|
|
}
|
|
|
|
return QWindow::event(e);
|
|
}
|
|
|
|
void render() {
|
|
m_renderer->render(models);
|
|
m_instance->presentQueued(this);
|
|
requestUpdate();
|
|
}
|
|
|
|
std::vector<RenderModel> models;
|
|
|
|
private:
|
|
bool m_initialized = false;
|
|
Renderer* m_renderer;
|
|
QVulkanInstance* m_instance;
|
|
};
|
|
#else
|
|
#include "standalonewindow.h"
|
|
#include "equipment.h"
|
|
|
|
#endif
|
|
|
|
MDLPart::MDLPart(GameData *data) : data(data) {
|
|
auto viewportLayout = new QVBoxLayout();
|
|
setLayout(viewportLayout);
|
|
|
|
renderer = new Renderer();
|
|
|
|
#ifndef USE_STANDALONE_WINDOW
|
|
auto inst = new QVulkanInstance();
|
|
inst->setVkInstance(renderer->instance);
|
|
inst->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
|
|
inst->create();
|
|
|
|
vkWindow = new VulkanWindow(renderer, inst);
|
|
vkWindow->setVulkanInstance(inst);
|
|
|
|
auto widget = QWidget::createWindowContainer(vkWindow);
|
|
|
|
viewportLayout->addWidget(widget);
|
|
#else
|
|
standaloneWindow = new StandaloneWindow(renderer);
|
|
renderer->initSwapchain(standaloneWindow->getSurface(renderer->instance), 640, 480);
|
|
|
|
QTimer* timer = new QTimer();
|
|
connect(timer, &QTimer::timeout, this, [this] {
|
|
standaloneWindow->render();
|
|
});
|
|
timer->start(1000);
|
|
#endif
|
|
|
|
connect(this, &MDLPart::modelChanged, this, &MDLPart::reloadRenderer);
|
|
connect(this, &MDLPart::skeletonChanged, this, &MDLPart::reloadBoneData);
|
|
}
|
|
|
|
void MDLPart::exportModel(const QString &fileName) {
|
|
Assimp::Exporter exporter;
|
|
|
|
aiScene scene;
|
|
scene.mRootNode = new aiNode();
|
|
|
|
// TODO: hardcoded to the first model for now
|
|
scene.mRootNode->mNumChildren = models[0].model.lods[0].num_parts + 1; // plus one for the skeleton
|
|
scene.mRootNode->mChildren = new aiNode*[scene.mRootNode->mNumChildren];
|
|
|
|
scene.mNumMeshes = models[0].model.lods[0].num_parts;
|
|
scene.mMeshes = new aiMesh*[scene.mNumMeshes];
|
|
|
|
auto skeleton_node = new aiNode();
|
|
skeleton_node->mName = "Skeleton";
|
|
skeleton_node->mNumChildren = 1;
|
|
skeleton_node->mChildren = new aiNode*[skeleton_node->mNumChildren];
|
|
|
|
scene.mRootNode->mChildren[scene.mRootNode->mNumChildren - 1] = skeleton_node;
|
|
|
|
std::vector<aiNode*> skeletonNodes;
|
|
|
|
for(int i = 0; i < models[0].model.num_affected_bones; i++) {
|
|
auto& node = skeletonNodes.emplace_back();
|
|
node = new aiNode();
|
|
node->mName = models[0].model.affected_bone_names[i];
|
|
|
|
int real_bone_id = 0;
|
|
for(int k = 0; k < skeleton->num_bones; k++) {
|
|
if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) {
|
|
real_bone_id = k;
|
|
}
|
|
}
|
|
|
|
node->mChildren = new aiNode*[models[0].model.num_affected_bones];
|
|
|
|
auto& real_bone = skeleton->bones[real_bone_id];
|
|
memcpy(&node->mTransformation, glm::value_ptr(boneData[real_bone.index].finalTransform), sizeof(aiMatrix4x4));
|
|
}
|
|
|
|
// setup parenting
|
|
for(int i = 0; i < models[0].model.num_affected_bones; i++) {
|
|
int real_bone_id = 0;
|
|
for(int k = 0; k < skeleton->num_bones; k++) {
|
|
if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) {
|
|
real_bone_id = k;
|
|
}
|
|
}
|
|
|
|
auto& real_bone = skeleton->bones[real_bone_id];
|
|
if(real_bone.parent_bone != nullptr) {
|
|
for(int k = 0; k < models[0].model.num_affected_bones; k++) {
|
|
if(strcmp(models[0].model.affected_bone_names[k], real_bone.parent_bone->name) == 0) {
|
|
skeletonNodes[i]->mParent = skeletonNodes[k];
|
|
skeletonNodes[k]->mChildren[skeletonNodes[k]->mNumChildren++] = skeletonNodes[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
skeleton_node->mChildren[0] = new aiNode();
|
|
skeleton_node->mChildren[0]->mName = "root";
|
|
skeleton_node->mChildren[0]->mChildren = new aiNode*[models[0].model.num_affected_bones];
|
|
|
|
for(int i = 0; i < skeletonNodes.size(); i++) {
|
|
if(skeletonNodes[i]->mParent == nullptr) {
|
|
skeleton_node->mChildren[0]->mChildren[skeleton_node->mChildren[0]->mNumChildren++] = skeletonNodes[i];
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < models[0].model.lods[0].num_parts; i++) {
|
|
scene.mMeshes[i] = new aiMesh();
|
|
scene.mMeshes[i]->mMaterialIndex = 0;
|
|
|
|
auto& node = scene.mRootNode->mChildren[i];
|
|
node = new aiNode();
|
|
node->mNumMeshes = 1;
|
|
node->mMeshes = new unsigned int [scene.mRootNode->mNumMeshes];
|
|
node->mMeshes[0] = i;
|
|
|
|
auto mesh = scene.mMeshes[i];
|
|
mesh->mNumVertices = models[0].model.lods[0].parts[i].num_vertices;
|
|
mesh->mVertices = new aiVector3D [mesh->mNumVertices];
|
|
mesh->mNormals = new aiVector3D [mesh->mNumVertices];
|
|
mesh->mTextureCoords[0] = new aiVector3D [mesh->mNumVertices];
|
|
mesh->mNumUVComponents[0] = 2;
|
|
|
|
for(int j = 0; j < mesh->mNumVertices; j++) {
|
|
auto vertex = models[0].model.lods[0].parts[i].vertices[j];
|
|
mesh->mVertices[j] = aiVector3D(vertex.position[0], vertex.position[1], vertex.position[2]);
|
|
mesh->mNormals[j] = aiVector3D (vertex.normal[0], vertex.normal[1], vertex.normal[2]);
|
|
mesh->mTextureCoords[0][j] = aiVector3D(vertex.uv[0], vertex.uv[1], 0.0f);
|
|
}
|
|
|
|
mesh->mNumBones = models[0].model.num_affected_bones;
|
|
mesh->mBones = new aiBone*[mesh->mNumBones];
|
|
for(int j = 0; j < mesh->mNumBones; j++) {
|
|
int real_bone_id = j;
|
|
// TODO: is this still relevant?5
|
|
/*for(int k = 0; k < skeleton.bones.size(); k++) {
|
|
if(skeleton.bones[k].name == model.affectedBoneNames[j]) {
|
|
real_bone_id = k;
|
|
}
|
|
}*/
|
|
|
|
mesh->mBones[j] = new aiBone();
|
|
mesh->mBones[j]->mName = models[0].model.affected_bone_names[j];
|
|
mesh->mBones[j]->mNumWeights = mesh->mNumVertices * 4;
|
|
mesh->mBones[j]->mWeights = new aiVertexWeight[mesh->mBones[j]->mNumWeights];
|
|
mesh->mBones[j]->mNode = skeleton_node->mChildren[j];
|
|
|
|
for(int k = 0; k < mesh->mNumVertices; k++) {
|
|
for(int z = 0; z < 4; z++) {
|
|
if (models[0].model.lods[0].parts[i].vertices[k].bone_id[z] == real_bone_id) {
|
|
auto &weight = mesh->mBones[j]->mWeights[k * 4 + z];
|
|
weight.mVertexId = k;
|
|
weight.mWeight = models[0].model.lods[0].parts[i].vertices[k].bone_weight[z];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mesh->mNumFaces = models[0].model.lods[0].parts[i].num_indices / 3;
|
|
mesh->mFaces = new aiFace[mesh->mNumFaces];
|
|
|
|
int lastFace = 0;
|
|
for(int j = 0; j < models[0].model.lods[0].parts[i].num_indices; j += 3) {
|
|
aiFace& face = mesh->mFaces[lastFace++];
|
|
|
|
face.mNumIndices = 3;
|
|
face.mIndices = new unsigned int[face.mNumIndices];
|
|
|
|
face.mIndices[0] = models[0].model.lods[0].parts[i].indices[j];
|
|
face.mIndices[1] = models[0].model.lods[0].parts[i].indices[j + 1];
|
|
face.mIndices[2] = models[0].model.lods[0].parts[i].indices[j + 2];
|
|
}
|
|
}
|
|
|
|
scene.mNumMaterials = 1;
|
|
scene.mMaterials = new aiMaterial*[1];
|
|
scene.mMaterials[0] = new aiMaterial();
|
|
|
|
exporter.Export(&scene, "fbx", fileName.toStdString());
|
|
}
|
|
|
|
void MDLPart::clear() {
|
|
models.clear();
|
|
|
|
Q_EMIT modelChanged();
|
|
}
|
|
|
|
void MDLPart::addModel(physis_MDL mdl, std::vector<physis_Material> materials, int lod) {
|
|
qDebug() << "Adding model to MDLPart";
|
|
|
|
auto model = renderer->addModel(mdl, lod);
|
|
|
|
std::transform(materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material& mat) {
|
|
return createMaterial(mat);
|
|
});
|
|
|
|
models.push_back(model);
|
|
|
|
Q_EMIT modelChanged();
|
|
}
|
|
|
|
void MDLPart::setSkeleton(physis_Skeleton newSkeleton) {
|
|
skeleton = newSkeleton;
|
|
|
|
Q_EMIT skeletonChanged();
|
|
}
|
|
|
|
void MDLPart::clearSkeleton() {
|
|
skeleton.reset();
|
|
|
|
Q_EMIT skeletonChanged();
|
|
}
|
|
|
|
void MDLPart::reloadRenderer() {
|
|
qDebug() << "Reloading render models...";
|
|
|
|
reloadBoneData();
|
|
|
|
#ifndef USE_STANDALONE_WINDOW
|
|
vkWindow->models = models;
|
|
#else
|
|
standaloneWindow->models = models;
|
|
#endif
|
|
}
|
|
|
|
void MDLPart::reloadBoneData() {
|
|
if(skeleton.has_value()) {
|
|
// first-time data, TODO split out
|
|
boneData.resize(skeleton->num_bones);
|
|
calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr);
|
|
|
|
for(auto& bone : boneData) {
|
|
bone.inversePose = glm::inverse(bone.inversePose);
|
|
}
|
|
|
|
// update data
|
|
calculateBone(*skeleton, *skeleton->root_bone, nullptr);
|
|
|
|
for(auto& model : models) {
|
|
// we want to map the actual affected bones to bone ids
|
|
std::map<int, int> boneMapping;
|
|
for (int i = 0; i < model.model.num_affected_bones; i++) {
|
|
for (int k = 0; k < skeleton->num_bones; k++) {
|
|
if (strcmp(skeleton->bones[k].name, model.model.affected_bone_names[i]) == 0)
|
|
boneMapping[i] = k;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < model.model.num_affected_bones; i++) {
|
|
model.boneData[i] = boneData[boneMapping[i]].finalTransform;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RenderMaterial MDLPart::createMaterial(const physis_Material &material) {
|
|
RenderMaterial newMaterial;
|
|
|
|
for (int i = 0; i < material.num_textures; i++) {
|
|
std::string t = material.textures[i];
|
|
|
|
if (t.find("skin") != std::string::npos) {
|
|
newMaterial.type = MaterialType::Skin;
|
|
}
|
|
|
|
char type = t[t.length() - 5];
|
|
|
|
switch(type) {
|
|
case 'm': {
|
|
auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i]));
|
|
auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
|
|
|
|
newMaterial.multiTexture = new RenderTexture(tex);
|
|
}
|
|
case 'd': {
|
|
auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i]));
|
|
auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
|
|
|
|
newMaterial.diffuseTexture = new RenderTexture(tex);
|
|
}
|
|
break;
|
|
case 'n': {
|
|
auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i]));
|
|
auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
|
|
|
|
newMaterial.normalTexture = new RenderTexture(tex);
|
|
}
|
|
break;
|
|
case 's': {
|
|
auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i]));
|
|
auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
|
|
|
|
newMaterial.specularTexture = new RenderTexture(tex);
|
|
}
|
|
break;
|
|
default:
|
|
qDebug() << "unhandled type" << type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return newMaterial;
|
|
}
|
|
|
|
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);
|
|
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].inversePose = parentMatrix * local;
|
|
|
|
for(int i = 0; i < skeleton.num_bones; i++) {
|
|
if(skeleton.bones[i].parent_bone != nullptr && strcmp(skeleton.bones[i].parent_bone->name, bone.name) == 0) {
|
|
calculateBoneInversePose(skeleton, skeleton.bones[i], &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;
|
|
|
|
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]));
|
|
|
|
boneData[bone.index].localTransform = parent_matrix * local;
|
|
boneData[bone.index].finalTransform = boneData[bone.index].localTransform * boneData[bone.index].inversePose;
|
|
|
|
for(int i = 0; i < skeleton.num_bones; i++) {
|
|
if(skeleton.bones[i].parent_bone != nullptr && strcmp(skeleton.bones[i].parent_bone->name, bone.name) == 0) {
|
|
calculateBone(skeleton, skeleton.bones[i], &bone);
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "moc_mdlpart.cpp" |