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:
parent
be5625c1b8
commit
c7b6dd076c
9 changed files with 217 additions and 33 deletions
|
@ -45,6 +45,8 @@ private Q_SLOTS:
|
|||
void reloadGear();
|
||||
|
||||
private:
|
||||
void importModel(const QString &filename);
|
||||
|
||||
std::optional<GearInfo> currentGear;
|
||||
|
||||
Race currentRace = Race::Hyur;
|
||||
|
|
|
@ -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
2
extern/libphysis
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 14fa0367302aa2b3ef9a8adba5cbe61f81a1f672
|
||||
Subproject commit 66f64ee7ee510c2e2b30800e1f94b77a21bc0cf0
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue