diff --git a/armoury/include/singlegearview.h b/armoury/include/singlegearview.h index bdcd5d4..dc3fec6 100644 --- a/armoury/include/singlegearview.h +++ b/armoury/include/singlegearview.h @@ -45,6 +45,8 @@ private Q_SLOTS: void reloadGear(); private: + void importModel(const QString &filename); + std::optional currentGear; Race currentRace = Race::Hyur; diff --git a/armoury/src/singlegearview.cpp b/armoury/src/singlegearview.cpp index 81a6d06..03a1c3e 100644 --- a/armoury/src/singlegearview.cpp +++ b/armoury/src/singlegearview.cpp @@ -11,6 +11,7 @@ #include "filecache.h" #include "magic_enum.hpp" +#include "tiny_gltf.h" SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent) : QWidget(parent) @@ -88,6 +89,21 @@ SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent importButton = new QPushButton(QStringLiteral("Import...")); importButton->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); + connect(importButton, &QPushButton::clicked, this, [this](bool) { + if (currentGear.has_value()) { + // TODO: deduplicate + const auto sanitizeMdlPath = [](const QString &mdlPath) -> QString { + return QString(mdlPath).section(QLatin1Char('/'), -1).remove(QStringLiteral(".mdl")); + }; + + const QString fileName = QFileDialog::getOpenFileName(this, + tr("Import Model"), + QStringLiteral("%1.glb").arg(sanitizeMdlPath(gearView->getLoadedGearPath())), + tr("glTF Binary File (*.glb)")); + + importModel(fileName); + } + }); topControlLayout->addWidget(importButton); exportButton = new QPushButton(QStringLiteral("Export...")); @@ -297,4 +313,75 @@ QString SingleGearView::getLoadedGearPath() const return gearView->getLoadedGearPath(); } +void SingleGearView::importModel(const QString &filename) +{ + tinygltf::Model model; + + std::string error, warning; + + tinygltf::TinyGLTF loader; + if (!loader.LoadBinaryFromFile(&model, &error, &warning, filename.toStdString())) { + qInfo() << "Error when loading glTF model:" << error; + return; + } + + if (!warning.empty()) { + qInfo() << "Warnings when loading glTF model:" << warning; + } + + auto &mdl = gearView->part().getModel(0); + + for (const auto &node : model.nodes) { + // Detect if it's a mesh node + if (node.mesh >= 0) { + qInfo() << "Importing" << node.name; + + const QStringList parts = QString::fromStdString(node.name).split(QLatin1Char(' ')); + const QString name = parts[0]; + const QStringList lodPartNumber = parts[2].split(QLatin1Char('.')); + + const int lodNumber = lodPartNumber[0].toInt(); + const int partNumber = lodPartNumber[1].toInt(); + + qInfo() << "- LOD:" << lodNumber; + qInfo() << "- Part:" << partNumber; + + auto &mesh = model.meshes[node.mesh]; + auto &primitive = mesh.primitives[0]; + + // All of the accessors are mapped to the same buffer vertex view + const auto &vertexAccessor = model.accessors[primitive.attributes["POSITION"]]; + const auto &vertexView = model.bufferViews[vertexAccessor.bufferView]; + const auto &vertexBuffer = model.buffers[vertexView.buffer]; + + const auto &indexAccessor = model.accessors[primitive.indices]; + const auto &indexView = model.bufferViews[indexAccessor.bufferView]; + const auto &indexBuffer = model.buffers[indexView.buffer]; + + qInfo() << "- Importing mesh of" << vertexAccessor.count << "vertices and" << indexAccessor.count << "indices."; + + auto vertexData = (glm::vec3 *)(&vertexBuffer.data.at(0) + vertexView.byteOffset); + + std::vector newVertices; + for (int i = 0; i < vertexAccessor.count; i++) { + // Replace position data + auto vertex = mdl.model.lods[lodNumber].parts[partNumber].vertices[i]; + vertex.position[0] = vertexData[i].x; + vertex.position[1] = vertexData[i].y; + vertex.position[2] = vertexData[i].z; + + newVertices.push_back(vertex); + } + + auto indexData = (const uint16_t *)(&indexBuffer.data.at(0) + indexView.byteOffset); + + physis_mdl_replace_vertices(&mdl.model, lodNumber, partNumber, vertexAccessor.count, newVertices.data(), indexAccessor.count, indexData); + } + } + + gearView->part().reloadModel(0); + + qInfo() << "Successfully imported model!"; +} + #include "moc_singlegearview.cpp" \ No newline at end of file diff --git a/extern/libphysis b/extern/libphysis index 14fa036..66f64ee 160000 --- a/extern/libphysis +++ b/extern/libphysis @@ -1 +1 @@ -Subproject commit 14fa0367302aa2b3ef9a8adba5cbe61f81a1f672 +Subproject commit 66f64ee7ee510c2e2b30800e1f94b77a21bc0cf0 diff --git a/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index 63f9388..3038f43 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -199,6 +199,9 @@ void MDLPart::exportModel(const QString &fileName) tinygltf::Model gltfModel; gltfModel.asset.generator = "Novus"; + // TODO: just write the code better! dummy!! + gltfModel.nodes.reserve(1 + model.num_affected_bones + lod.num_parts); + auto &gltfSkeletonNode = gltfModel.nodes.emplace_back(); gltfSkeletonNode.name = skeleton->root_bone->name; @@ -230,11 +233,19 @@ void MDLPart::exportModel(const QString &fileName) auto &real_bone = skeleton->bones[real_bone_id]; if (real_bone.parent_bone != nullptr) { + bool found = false; for (int k = 0; k < model.num_affected_bones; k++) { if (strcmp(model.affected_bone_names[k], real_bone.parent_bone->name) == 0) { gltfModel.nodes[k + 1].children.push_back(i + 1); // +1 for the skeleton node taking up the first index + found = true; } } + + // Find the next closest bone that isn't a direct descendant + // of n_root, but won't have a parent anyway + if (!found) { + gltfSkeletonNode.children.push_back(i + 1); + } } else { gltfSkeletonNode.children.push_back(i + 1); } @@ -280,13 +291,13 @@ void MDLPart::exportModel(const QString &fileName) } for (int i = 0; i < lod.num_parts; i++) { + gltfSkeletonNode.children.push_back(gltfModel.nodes.size()); + auto &gltfNode = gltfModel.nodes.emplace_back(); - ; + gltfNode.name = models[0].name.toStdString() + " Part " + std::to_string(i) + ".0"; gltfNode.skin = 0; - gltfSkeletonNode.children.push_back(gltfModel.nodes.size()); - gltfNode.mesh = gltfModel.meshes.size(); auto &gltfMesh = gltfModel.meshes.emplace_back(); @@ -295,9 +306,11 @@ void MDLPart::exportModel(const QString &fileName) auto &gltfPrimitive = gltfMesh.primitives.emplace_back(); gltfPrimitive.attributes["POSITION"] = gltfModel.accessors.size(); gltfPrimitive.attributes["TEXCOORD_0"] = gltfModel.accessors.size() + 1; - gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 2; - gltfPrimitive.attributes["WEIGHTS_0"] = gltfModel.accessors.size() + 3; - gltfPrimitive.attributes["JOINTS_0"] = gltfModel.accessors.size() + 4; + gltfPrimitive.attributes["TEXCOORD_1"] = gltfModel.accessors.size() + 2; + gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 3; + gltfPrimitive.attributes["COLOR_0"] = gltfModel.accessors.size() + 6; + gltfPrimitive.attributes["WEIGHTS_0"] = gltfModel.accessors.size() + 7; + gltfPrimitive.attributes["JOINTS_0"] = gltfModel.accessors.size() + 8; gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES; // Vertices @@ -308,12 +321,19 @@ void MDLPart::exportModel(const QString &fileName) positionAccessor.count = lod.parts[i].num_vertices; positionAccessor.type = TINYGLTF_TYPE_VEC3; - auto &uvAccessor = gltfModel.accessors.emplace_back(); - uvAccessor.bufferView = gltfModel.bufferViews.size(); - uvAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; - uvAccessor.count = lod.parts[i].num_vertices; - uvAccessor.type = TINYGLTF_TYPE_VEC2; - uvAccessor.byteOffset = offsetof(Vertex, uv); + auto &uv0Accessor = gltfModel.accessors.emplace_back(); + uv0Accessor.bufferView = gltfModel.bufferViews.size(); + uv0Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + uv0Accessor.count = lod.parts[i].num_vertices; + uv0Accessor.type = TINYGLTF_TYPE_VEC2; + uv0Accessor.byteOffset = offsetof(Vertex, uv0); + + auto &uv1Accessor = gltfModel.accessors.emplace_back(); + uv1Accessor.bufferView = gltfModel.bufferViews.size(); + uv1Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + uv1Accessor.count = lod.parts[i].num_vertices; + uv1Accessor.type = TINYGLTF_TYPE_VEC2; + uv1Accessor.byteOffset = offsetof(Vertex, uv1); auto &normalAccessor = gltfModel.accessors.emplace_back(); normalAccessor.bufferView = gltfModel.bufferViews.size(); @@ -322,6 +342,27 @@ void MDLPart::exportModel(const QString &fileName) normalAccessor.type = TINYGLTF_TYPE_VEC3; normalAccessor.byteOffset = offsetof(Vertex, normal); + auto &tangent1Accessor = gltfModel.accessors.emplace_back(); + tangent1Accessor.bufferView = gltfModel.bufferViews.size(); + tangent1Accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + tangent1Accessor.count = lod.parts[i].num_vertices; + tangent1Accessor.type = TINYGLTF_TYPE_VEC4; + tangent1Accessor.byteOffset = offsetof(Vertex, tangent1); + + auto &tangent2Accessor = gltfModel.accessors.emplace_back(); + tangent2Accessor.bufferView = gltfModel.bufferViews.size(); + tangent2Accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + tangent2Accessor.count = lod.parts[i].num_vertices; + tangent2Accessor.type = TINYGLTF_TYPE_VEC4; + tangent2Accessor.byteOffset = offsetof(Vertex, tangent2); + + auto &colorAccessor = gltfModel.accessors.emplace_back(); + colorAccessor.bufferView = gltfModel.bufferViews.size(); + colorAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + colorAccessor.count = lod.parts[i].num_vertices; + colorAccessor.type = TINYGLTF_TYPE_VEC4; + colorAccessor.byteOffset = offsetof(Vertex, color); + auto &boneWeightAccessor = gltfModel.accessors.emplace_back(); boneWeightAccessor.bufferView = gltfModel.bufferViews.size(); boneWeightAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; @@ -331,7 +372,7 @@ void MDLPart::exportModel(const QString &fileName) auto &boneIdAccessor = gltfModel.accessors.emplace_back(); boneIdAccessor.bufferView = gltfModel.bufferViews.size(); - boneIdAccessor.componentType = TINYGLTF_COMPONENT_TYPE_BYTE; + boneIdAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; boneIdAccessor.count = lod.parts[i].num_vertices; boneIdAccessor.type = TINYGLTF_TYPE_VEC4; boneIdAccessor.byteOffset = offsetof(Vertex, bone_id); @@ -378,6 +419,18 @@ void MDLPart::exportModel(const QString &fileName) loader.WriteGltfSceneToFile(&gltfModel, fileName.toStdString(), true, true, false, true); } +RenderModel &MDLPart::getModel(const int index) +{ + return models[index]; +} + +void MDLPart::reloadModel(const int index) +{ + renderer->reloadModel(models[index], 0); + + Q_EMIT modelChanged(); +} + void MDLPart::clear() { models.clear(); diff --git a/parts/mdl/mdlpart.h b/parts/mdl/mdlpart.h index 05308f1..15fb410 100644 --- a/parts/mdl/mdlpart.h +++ b/parts/mdl/mdlpart.h @@ -23,6 +23,8 @@ public: explicit MDLPart(GameData *data, FileCache &cache); void exportModel(const QString &fileName); + RenderModel &getModel(int index); + void reloadModel(int index); int lastX = -1; int lastY = -1; diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp index e9a4499..ee5dc17 100644 --- a/renderer/include/renderer.hpp +++ b/renderer/include/renderer.hpp @@ -69,6 +69,7 @@ public: void resize(VkSurfaceKHR surface, int width, int height); RenderModel addModel(const physis_MDL &model, int lod); + void reloadModel(RenderModel &model, int lod); RenderTexture addTexture(uint32_t width, uint32_t height, const uint8_t *data, uint32_t data_size); void render(std::vector models); diff --git a/renderer/shaders/mesh.vert b/renderer/shaders/mesh.vert index f132f4e..391db9e 100644 --- a/renderer/shaders/mesh.vert +++ b/renderer/shaders/mesh.vert @@ -4,10 +4,14 @@ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inUV; -layout(location = 3) in vec4 inBoneWeights; -layout(location = 4) in uvec4 inBoneIds; +layout(location = 1) in vec2 inUV0; +layout(location = 2) in vec2 inUV1; +layout(location = 3) in vec3 inNormal; +layout(location = 4) in uvec4 inTangent1; +layout(location = 5) in uvec4 inTangent2; +layout(location = 6) in vec4 inColor; +layout(location = 7) in vec4 inBoneWeights; +layout(location = 8) in uvec4 inBoneIds; layout(location = 0) out vec3 outNormal; layout(location = 1) out vec3 outFragPos; @@ -42,5 +46,5 @@ void main() { gl_Position = vp * bPos; outNormal = bNor.xyz; outFragPos = vec3(model * vec4(inPosition, 1.0)); - outUV = inUV; + outUV = inUV0; } diff --git a/renderer/shaders/mesh.vert.spv b/renderer/shaders/mesh.vert.spv index 4904d14..d1f17c5 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 c96075d..7b10944 100644 --- a/renderer/src/renderer.cpp +++ b/renderer/src/renderer.cpp @@ -638,13 +638,22 @@ RenderModel Renderer::addModel(const physis_MDL &model, int lod) RenderModel renderModel; renderModel.model = model; - if (lod < 0 || lod > model.num_lod) - return {}; + reloadModel(renderModel, lod); - for (int i = 0; i < model.lods[lod].num_parts; i++) { + return renderModel; +} + +void Renderer::reloadModel(RenderModel &renderModel, int lod) +{ + if (lod < 0 || lod > renderModel.model.num_lod) + return; + + renderModel.parts.clear(); + + for (int i = 0; i < renderModel.model.lods[lod].num_parts; i++) { RenderPart renderPart; - const physis_Part part = model.lods[lod].parts[i]; + const physis_Part part = renderModel.model.lods[lod].parts[i]; renderPart.materialIndex = part.material_index; @@ -702,8 +711,6 @@ RenderModel Renderer::addModel(const physis_MDL &model, int lod) renderModel.boneInfoBuffer = buffer; renderModel.boneInfoMemory = memory; - - return renderModel; } void Renderer::initPipeline() @@ -729,27 +736,55 @@ void Renderer::initPipeline() positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; positionAttribute.offset = offsetof(Vertex, position); - VkVertexInputAttributeDescription uvAttribute = {}; - uvAttribute.format = VK_FORMAT_R32G32_SFLOAT; - uvAttribute.location = 2; - uvAttribute.offset = offsetof(Vertex, uv); + VkVertexInputAttributeDescription uv0Attribute = {}; + uv0Attribute.format = VK_FORMAT_R32G32_SFLOAT; + uv0Attribute.location = 1; + uv0Attribute.offset = offsetof(Vertex, uv0); + + VkVertexInputAttributeDescription uv1Attribute = {}; + uv1Attribute.format = VK_FORMAT_R32G32_SFLOAT; + uv1Attribute.location = 2; + uv1Attribute.offset = offsetof(Vertex, uv1); VkVertexInputAttributeDescription normalAttribute = {}; normalAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; - normalAttribute.location = 1; + normalAttribute.location = 3; normalAttribute.offset = offsetof(Vertex, normal); + VkVertexInputAttributeDescription tangent1Attribute = {}; + tangent1Attribute.format = VK_FORMAT_R8G8B8A8_UINT; + tangent1Attribute.location = 4; + tangent1Attribute.offset = offsetof(Vertex, tangent1); + + VkVertexInputAttributeDescription tangent2Attribute = {}; + tangent2Attribute.format = VK_FORMAT_R8G8B8A8_UINT; + tangent2Attribute.location = 5; + tangent2Attribute.offset = offsetof(Vertex, tangent2); + + VkVertexInputAttributeDescription colorAttribute = {}; + colorAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT; + colorAttribute.location = 6; + colorAttribute.offset = offsetof(Vertex, color); + VkVertexInputAttributeDescription boneWeightAttribute = {}; boneWeightAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT; - boneWeightAttribute.location = 3; + boneWeightAttribute.location = 7; boneWeightAttribute.offset = offsetof(Vertex, bone_weight); VkVertexInputAttributeDescription boneIdAttribute = {}; boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT; - boneIdAttribute.location = 4; + boneIdAttribute.location = 8; boneIdAttribute.offset = offsetof(Vertex, bone_id); - const std::array attributes = {positionAttribute, normalAttribute, uvAttribute, boneWeightAttribute, boneIdAttribute}; + const std::array attributes = {positionAttribute, + uv0Attribute, + uv1Attribute, + normalAttribute, + tangent1Attribute, + tangent2Attribute, + colorAttribute, + boneWeightAttribute, + boneIdAttribute}; VkPipelineVertexInputStateCreateInfo vertexInputState = {}; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;