1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-06-07 13:47:45 +00:00

Add glTF import support, multiple fixes for upstream physis changes

Adds basic glTF import (although right now it only imports back
positions) and fixes support for more of the vertex data that's
available to us. The MDL file isn't written back out yet either, it only
displays in the viewport.
This commit is contained in:
Joshua Goins 2023-12-09 14:49:31 -05:00
parent be5625c1b8
commit c7b6dd076c
9 changed files with 217 additions and 33 deletions

View file

@ -45,6 +45,8 @@ private Q_SLOTS:
void reloadGear();
private:
void importModel(const QString &filename);
std::optional<GearInfo> currentGear;
Race currentRace = Race::Hyur;

View file

@ -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<Vertex> 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"

2
extern/libphysis vendored

@ -1 +1 @@
Subproject commit 14fa0367302aa2b3ef9a8adba5cbe61f81a1f672
Subproject commit 66f64ee7ee510c2e2b30800e1f94b77a21bc0cf0

View file

@ -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();

View file

@ -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;

View file

@ -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<RenderModel> models);

View file

@ -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;
}

Binary file not shown.

View file

@ -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;