diff --git a/mdlviewer/CMakeLists.txt b/mdlviewer/CMakeLists.txt index 9eb3820..520b665 100644 --- a/mdlviewer/CMakeLists.txt +++ b/mdlviewer/CMakeLists.txt @@ -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) diff --git a/mdlviewer/include/mainwindow.h b/mdlviewer/include/mainwindow.h index cb8e8ae..73748ab 100644 --- a/mdlviewer/include/mainwindow.h +++ b/mdlviewer/include/mainwindow.h @@ -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; }; \ No newline at end of file diff --git a/mdlviewer/include/vec3edit.h b/mdlviewer/include/vec3edit.h new file mode 100644 index 0000000..4bf3cc1 --- /dev/null +++ b/mdlviewer/include/vec3edit.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +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; +}; diff --git a/mdlviewer/src/mainwindow.cpp b/mdlviewer/src/mainwindow.cpp index 89517d3..3d14fdb 100644 --- a/mdlviewer/src/mainwindow.cpp +++ b/mdlviewer/src/mainwindow.cpp @@ -17,11 +17,16 @@ #include #include #include +#include +#include #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 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 diff --git a/mdlviewer/src/vec3edit.cpp b/mdlviewer/src/vec3edit.cpp new file mode 100644 index 0000000..05fa378 --- /dev/null +++ b/mdlviewer/src/vec3edit.cpp @@ -0,0 +1,58 @@ +#include "vec3edit.h" + +#include +#include + +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(&QDoubleSpinBox::valueChanged), [this, &vec](double d) { + vec.x = d; + emit onValueChanged(); + }); + connect(spinBoxes.y, static_cast(&QDoubleSpinBox::valueChanged), [this, &vec](double d) { + vec.y = d; + emit onValueChanged(); + }); + connect(spinBoxes.z, static_cast(&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(); +} diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp index 62e15e5..92d45af 100644 --- a/renderer/include/renderer.hpp +++ b/renderer/include/renderer.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "mdlparser.h" @@ -11,11 +12,14 @@ struct RenderPart { VkBuffer vertexBuffer, indexBuffer; VkDeviceMemory vertexMemory, indexMemory; + + std::vector submeshes; }; struct RenderModel { Model model; std::vector parts; + std::array 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 inFlightFences; std::array 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; diff --git a/renderer/shaders/mesh.vert b/renderer/shaders/mesh.vert index dbad9f4..bbf192a 100644 --- a/renderer/shaders/mesh.vert +++ b/renderer/shaders/mesh.vert @@ -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)); } diff --git a/renderer/shaders/mesh.vert.spv b/renderer/shaders/mesh.vert.spv index b2a2cd0..77595fd 100644 Binary files a/renderer/shaders/mesh.vert.spv and b/renderer/shaders/mesh.vert.spv differ diff --git a/renderer/src/renderer.cpp b/renderer/src/renderer.cpp index 9a423cf..021aa63 100644 --- a/renderer/src/renderer.cpp +++ b/renderer/src/renderer.cpp @@ -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 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 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 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 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(buffer.data()), fileSize); -} \ No newline at end of file +} + +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); +}