1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-23 20:47:45 +00:00

Add bone editing to mdlviewer

This is big, as it shows we are now correctly parsing the havok XML
sidecard data and you can edit the scale of the bones in the viewport.

This also pulls in a new libxiv version, which is required to fill out
the used bones list on a Model. Right now the bone editing is incredibly
basic, and the viewport suffers from a lack of depth testing still.
This commit is contained in:
Joshua Goins 2022-04-28 17:50:05 -04:00
parent 606c2f97cd
commit 9688c091af
9 changed files with 314 additions and 11 deletions

View file

@ -2,7 +2,9 @@ find_package(assimp REQUIRED)
add_executable(mdlviewer
src/main.cpp
src/mainwindow.cpp)
src/mainwindow.cpp
src/vec3edit.cpp
include/vec3edit.h)
target_include_directories(mdlviewer
PUBLIC
include)

View file

@ -7,6 +7,7 @@
#include "renderer.hpp"
#include "types/slot.h"
#include "types/race.h"
#include "havokxmlparser.h"
struct ModelInfo {
int primaryID;
@ -47,10 +48,14 @@ private:
Race currentRace = Race::HyurMidlanderMale;
int currentLod = 0;
glm::vec3 currentScale = glm::vec3(1);
Bone* currentEditedBone = nullptr;
GameData& data;
Renderer* renderer;
VulkanWindow* vkWindow;
StandaloneWindow* standaloneWindow;
Skeleton skeleton;
};

View file

@ -0,0 +1,23 @@
#pragma once
#include <QSpinBox>
#include <QWidget>
#include <glm/glm.hpp>
class Vector3Edit : public QWidget {
Q_OBJECT
public:
explicit Vector3Edit(glm::vec3& vec, QWidget* parent = nullptr);
~Vector3Edit();
signals:
void onValueChanged();
private:
struct {
QDoubleSpinBox *x, *y, *z;
} spinBoxes;
glm::vec3& vec;
QTimer* updateTimer;
};

View file

@ -17,11 +17,16 @@
#include <QPushButton>
#include <QFileDialog>
#include <magic_enum.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "gamedata.h"
#include "exhparser.h"
#include "exdparser.h"
#include "mdlparser.h"
#include "equipment.h"
#include "glm/glm.hpp"
#include "vec3edit.h"
#ifndef USE_STANDALONE_WINDOW
class VulkanWindow : public QWindow
@ -78,6 +83,22 @@ private:
#endif
void calculate_bone_inverse_pose(Skeleton& skeleton, Bone& bone, Bone* parent_bone) {
const glm::mat4 parentMatrix = parent_bone == nullptr ? glm::mat4(1.0f) : parent_bone->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]));
bone.inversePose = parentMatrix * local;
for(auto& b : skeleton.bones) {
if(b.parent != nullptr && b.parent->name == bone.name)
calculate_bone_inverse_pose(skeleton, b, &bone);
}
}
MainWindow::MainWindow(GameData& data) : data(data) {
setWindowTitle("mdlviewer");
setMinimumSize(QSize(640, 480));
@ -208,6 +229,36 @@ MainWindow::MainWindow(GameData& data) : data(data) {
}
}
});
skeleton = parseHavokXML("/home/josh/test.xml");
calculate_bone_inverse_pose(skeleton, *skeleton.root_bone, nullptr);
auto boneListWidget = new QListWidget();
for(auto& bone : skeleton.bones) {
bone.inversePose = glm::inverse(bone.inversePose);
boneListWidget->addItem(bone.name.c_str());
}
boneListWidget->setMaximumWidth(200);
connect(boneListWidget, &QListWidget::itemClicked, [this](QListWidgetItem* item) {
for(auto& bone : skeleton.bones) {
if(bone.name == item->text().toStdString()) {
currentScale = glm::make_vec3(bone.scale.data());
currentEditedBone = &bone;
}
}
});
layout->addWidget(boneListWidget);
Vector3Edit* scaleEdit = new Vector3Edit(currentScale);
connect(scaleEdit, &Vector3Edit::onValueChanged, [this] {
memcpy(currentEditedBone->scale.data(), glm::value_ptr(currentScale), sizeof(float) * 3);
reloadGearAppearance();
});
layout->addWidget(scaleEdit);
}
void MainWindow::exportModel(Model& model, QString fileName) {
@ -292,9 +343,47 @@ void MainWindow::reloadGearModel() {
reloadGearAppearance();
}
void calculate_bone(Skeleton& skeleton, Bone& bone, const Bone* parent_bone) {
glm::mat4 parent_matrix = glm::mat4(1.0f);
if(parent_bone != nullptr)
parent_matrix = parent_bone->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]));
bone.localTransform = parent_matrix * local;
bone.finalTransform = bone.localTransform * bone.inversePose;
for(auto& b : skeleton.bones) {
if(b.parent != nullptr && b.parent->name == bone.name)
calculate_bone(skeleton, b, &bone);
}
}
void MainWindow::reloadGearAppearance() {
loadedGear.renderModel = renderer->addModel(loadedGear.model, currentLod);
calculate_bone(skeleton, *skeleton.root_bone, nullptr);
for(int i = 0; i < 128; i++) {
loadedGear.renderModel.boneData[i] = glm::mat4(1.0f);
}
// we want to map the actual affected bones to bone ids
std::map<int, int> boneMapping;
for(int i = 0; i < loadedGear.model.affectedBoneNames.size(); i++) {
for(int k = 0; k < skeleton.bones.size(); k++) {
if(skeleton.bones[k].name == loadedGear.model.affectedBoneNames[i])
boneMapping[i] = k;
}
}
for(int i = 0; i < loadedGear.model.affectedBoneNames.size(); i++) {
loadedGear.renderModel.boneData[i] = skeleton.bones[boneMapping[i]].finalTransform;
}
#ifndef USE_STANDALONE_WINDOW
vkWindow->models = {loadedGear.renderModel};
#else

View file

@ -0,0 +1,58 @@
#include "vec3edit.h"
#include <QHBoxLayout>
#include <QTimer>
Vector3Edit::Vector3Edit(glm::vec3& vec, QWidget* parent) : QWidget(parent), vec(vec) {
QHBoxLayout* itemsLayout = new QHBoxLayout(this);
spinBoxes.x = new QDoubleSpinBox();
spinBoxes.y = new QDoubleSpinBox();
spinBoxes.z = new QDoubleSpinBox();
spinBoxes.x->setMinimum(-10000.0);
spinBoxes.x->setMaximum(10000.0);
spinBoxes.y->setMinimum(-10000.0);
spinBoxes.y->setMaximum(10000.0);
spinBoxes.z->setMinimum(-10000.0);
spinBoxes.z->setMaximum(10000.0);
itemsLayout->addWidget(spinBoxes.x);
itemsLayout->addWidget(spinBoxes.y);
itemsLayout->addWidget(spinBoxes.z);
spinBoxes.x->setValue(vec.x);
spinBoxes.y->setValue(vec.y);
spinBoxes.z->setValue(vec.z);
connect(spinBoxes.x, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this, &vec](double d) {
vec.x = d;
emit onValueChanged();
});
connect(spinBoxes.y, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this, &vec](double d) {
vec.y = d;
emit onValueChanged();
});
connect(spinBoxes.z, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this, &vec](double d) {
vec.z = d;
emit onValueChanged();
});
// TODO: find a better way to do this
updateTimer = new QTimer();
connect(updateTimer, &QTimer::timeout, [this, &vec]() {
if (vec.x != spinBoxes.x->value() || vec.y != spinBoxes.y->value() || vec.z != spinBoxes.z->value()) {
spinBoxes.x->setValue(vec.x);
spinBoxes.y->setValue(vec.y);
spinBoxes.z->setValue(vec.z);
}
});
updateTimer->start(1);
}
Vector3Edit::~Vector3Edit() {
updateTimer->stop();
}

View file

@ -3,6 +3,7 @@
#include <vulkan/vulkan.h>
#include <vector>
#include <array>
#include <glm/ext/matrix_float4x4.hpp>
#include "mdlparser.h"
@ -11,11 +12,14 @@ struct RenderPart {
VkBuffer vertexBuffer, indexBuffer;
VkDeviceMemory vertexMemory, indexMemory;
std::vector<PartSubmesh> submeshes;
};
struct RenderModel {
Model model;
std::vector<RenderPart> parts;
std::array<glm::mat4, 128> boneData;
};
class Renderer {
@ -23,6 +27,7 @@ public:
Renderer();
void initPipeline();
void initDescriptors();
bool initSwapchain(VkSurfaceKHR surface, int width, int height);
void resize(VkSurfaceKHR surface, int width, int height);
@ -45,6 +50,14 @@ public:
std::array<VkFence, 3> inFlightFences;
std::array<VkSemaphore, 3> imageAvailableSemaphores, renderFinishedSemaphores;
uint32_t currentFrame = 0;
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
VkBuffer boneInfoBuffer = VK_NULL_HANDLE;
VkDeviceMemory boneInfoMemory = VK_NULL_HANDLE;
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE;
VkDescriptorSet set = VK_NULL_HANDLE;
VkPipeline pipeline;
VkPipelineLayout pipelineLayout;

View file

@ -2,16 +2,33 @@
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec4 inBoneWeights;
layout(location = 3) in uvec4 inBoneIds;
layout(location = 0) out vec3 outNormal;
layout(location = 1) out vec3 outFragPos;
layout(push_constant) uniform PushConstant {
mat4 mvp;
mat4 vp, model;
int boneOffset;
};
layout(std430, binding = 2) buffer readonly BoneInformation {
mat4 bones[128];
};
void main() {
gl_Position = mvp * vec4(inPosition, 1.0);
outNormal = inNormal;
outFragPos = inNormal;
mat4 BoneTransform = bones[boneOffset + inBoneIds[0]] * inBoneWeights[0];
BoneTransform += bones[boneOffset + inBoneIds[1]] * inBoneWeights[1];
BoneTransform += bones[boneOffset + inBoneIds[2]] * inBoneWeights[2];
BoneTransform += bones[boneOffset + inBoneIds[3]] * inBoneWeights[3];
BoneTransform = model * BoneTransform;
vec4 bPos = BoneTransform * vec4(inPosition, 1.0);
vec4 bNor = BoneTransform * vec4(inNormal, 0.0);
gl_Position = vp * bPos;
outNormal = bNor.xyz;
outFragPos = vec3(model * vec4(inPosition, 1.0));
}

Binary file not shown.

View file

@ -306,6 +306,7 @@ bool Renderer::initSwapchain(VkSurfaceKHR surface, int width, int height) {
vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass);
initDescriptors();
initPipeline();
swapchainFramebuffers.resize(swapchainViews.size());
@ -402,6 +403,25 @@ void Renderer::render(std::vector<RenderModel> models) {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
for(auto model : models) {
// copy bone data
{
const size_t bufferSize = sizeof(glm::mat4) * 128;
void *mapped_data = nullptr;
vkMapMemory(device, boneInfoMemory, 0, bufferSize, 0, &mapped_data);
memcpy(mapped_data, model.boneData.data(), bufferSize);
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = boneInfoMemory;
range.size = bufferSize;
vkFlushMappedMemoryRanges(device, 1, &range);
vkUnmapMemory(device, boneInfoMemory);
}
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, 0, nullptr);
for(auto part : model.parts) {
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &part.vertexBuffer, offsets);
@ -410,10 +430,17 @@ void Renderer::render(std::vector<RenderModel> models) {
glm::mat4 p = glm::perspective(glm::radians(45.0f), swapchainExtent.width / (float) swapchainExtent.height,
0.1f, 100.0f);
p[1][1] *= -1;
glm::mat4 v = glm::lookAt(glm::vec3(0, 1, -2.5), glm::vec3(0, 1, 0), glm::vec3(0, 1, 0));
glm::mat4 mvp = p * v;
glm::mat4 v = glm::lookAt(glm::vec3(3), glm::vec3(0, 1, 0), glm::vec3(0, 1, 0));
glm::mat4 vp = p * v;
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &mvp);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &vp);
glm::mat4 m = glm::mat4(1.0f);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), sizeof(glm::mat4), &m);
int test = 0;
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4) * 2, sizeof(int), &test);
vkCmdDrawIndexed(commandBuffer, part.numIndices, 1, 0, 0, 0);
}
@ -517,6 +544,7 @@ RenderModel Renderer::addModel(const Model& model, int lod) {
for(auto part : model.lods[lod].parts) {
RenderPart renderPart;
renderPart.submeshes = part.submeshes;
size_t vertexSize = part.vertices.size() * sizeof(Vertex);
auto[vertexBuffer, vertexMemory] = createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
@ -590,6 +618,7 @@ void Renderer::initPipeline() {
VkVertexInputAttributeDescription positionAttribute = {};
positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT;
positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT;
positionAttribute.offset = offsetof(Vertex, position);
VkVertexInputAttributeDescription normalAttribute = {};
@ -597,7 +626,17 @@ void Renderer::initPipeline() {
normalAttribute.location = 1;
normalAttribute.offset = offsetof(Vertex, normal);
std::array<VkVertexInputAttributeDescription, 2> attributes = {positionAttribute, normalAttribute};
VkVertexInputAttributeDescription boneWeightAttribute = {};
boneWeightAttribute.format = VK_FORMAT_R32G32B32_SFLOAT;
boneWeightAttribute.location = 2;
boneWeightAttribute.offset = offsetof(Vertex, boneWeights);
VkVertexInputAttributeDescription boneIdAttribute = {};
boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT;
boneIdAttribute.location = 3;
boneIdAttribute.offset = offsetof(Vertex, boneIds);
std::array<VkVertexInputAttributeDescription, 4> attributes = {positionAttribute, normalAttribute, boneWeightAttribute, boneIdAttribute};
VkPipelineVertexInputStateCreateInfo vertexInputState = {};
vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
@ -647,13 +686,15 @@ void Renderer::initPipeline() {
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
VkPushConstantRange pushConstantRange = {};
pushConstantRange.size = sizeof(glm::mat4);
pushConstantRange.size = (sizeof(glm::mat4) * 2) + sizeof(int);
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &setLayout;
vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout);
@ -700,4 +741,59 @@ VkShaderModule Renderer::loadShaderFromDisk(const std::string_view path) {
file.read(buffer.data(), fileSize);
return createShaderModule(reinterpret_cast<const uint32_t *>(buffer.data()), fileSize);
}
}
void Renderer::initDescriptors() {
VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
poolSize.descriptorCount = 1;
VkDescriptorPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolCreateInfo.poolSizeCount = 1;
poolCreateInfo.pPoolSizes = &poolSize;
poolCreateInfo.maxSets = 1;
vkCreateDescriptorPool(device, &poolCreateInfo, nullptr, &descriptorPool);
VkDescriptorSetLayoutBinding boneInfoBufferBinding = {};
boneInfoBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
boneInfoBufferBinding.descriptorCount = 1;
boneInfoBufferBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
boneInfoBufferBinding.binding = 2;
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &boneInfoBufferBinding;
vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &setLayout);
const size_t bufferSize = sizeof(glm::mat4) * 128;
auto [buffer, memory] = createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
boneInfoBuffer = buffer;
boneInfoMemory = memory;
VkDescriptorSetAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocateInfo.descriptorPool = descriptorPool;
allocateInfo.descriptorSetCount = 1;
allocateInfo.pSetLayouts = &setLayout;
vkAllocateDescriptorSets(device, &allocateInfo, &set);
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = boneInfoBuffer;
bufferInfo.range = bufferSize;
VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = set;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferInfo;
descriptorWrite.dstBinding = 2;
vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
}