Skip to content

Commit

Permalink
[Data/GltfLoad] Node transforms are taken into account
Browse files Browse the repository at this point in the history
- A node's (submesh's) vertices are transformed in-place in respect to its parent transform and its own

- Updated the glTF test file to check this change

- There is still an issue if we load nodes with a hierarchy of more than 1 level and that a node is loaded before another one up the tree

- Fixed 2 warnings occurring with MSVC about int-to-bool implicit conversions in ImageFormat
  • Loading branch information
Razakhel committed Jan 13, 2024
1 parent b8354bc commit 39fc084
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 64 deletions.
94 changes: 87 additions & 7 deletions src/RaZ/Data/GltfLoad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "RaZ/Data/Image.hpp"
#include "RaZ/Data/ImageFormat.hpp"
#include "RaZ/Data/Mesh.hpp"
#include "RaZ/Math/Transform.hpp"
#include "RaZ/Render/MeshRenderer.hpp"
#include "RaZ/Utils/FilePath.hpp"
#include "RaZ/Utils/FileUtils.hpp"
Expand All @@ -13,6 +14,74 @@ namespace Raz::GltfFormat {

namespace {

Transform loadTransform(const fastgltf::Node& node) {
const auto* transform = std::get_if<fastgltf::Node::TRS>(&node.transform);

if (transform == nullptr) // Shouldn't happen with the option that splits a matrix in TRS components
throw std::invalid_argument("[GltfLoad] Unexpected node transform type.");

return Transform(Vec3f(transform->translation[0], transform->translation[1], transform->translation[2]),
Quaternionf(transform->rotation[3], transform->rotation[0], transform->rotation[1], transform->rotation[2]), // Input is XYZW
Vec3f(transform->scale[0], transform->scale[1], transform->scale[2]));

}

std::vector<std::optional<Transform>> loadTransforms(const std::vector<fastgltf::Node>& nodes, std::size_t meshNodeCount) {
std::vector<std::optional<Transform>> transforms;
transforms.resize(meshNodeCount);

for (std::size_t transformIndex = 0; transformIndex < meshNodeCount; ++transformIndex) {
const fastgltf::Node& node = nodes[transformIndex];

if (!node.meshIndex.has_value())
continue;

const std::size_t nodeMeshIndex = *node.meshIndex;

if (nodeMeshIndex >= meshNodeCount) {
Logger::error("[GltfLoad] Unexpected node mesh index.");
continue;
}

std::optional<Transform>& nodeTransform = transforms[nodeMeshIndex];

if (!nodeTransform.has_value())
nodeTransform = loadTransform(node);

for (const std::size_t childIndex : node.children) {
const fastgltf::Node& childNode = nodes[childIndex];

if (!childNode.meshIndex.has_value())
continue;

const std::size_t childNodeMeshIndex = *childNode.meshIndex;

if (childNodeMeshIndex >= meshNodeCount) {
Logger::error("[GltfLoad] Unexpected child node mesh index.");
continue;
}

std::optional<Transform>& childTransform = transforms[childNodeMeshIndex];

if (!childTransform.has_value())
childTransform = loadTransform(childNode);

// TODO: this will not be enough for configurations (perhaps a bit esoteric) that have children nodes referenced before their parents
// For example, take the following configuration, where the nodes are taken from the file in the order { A, B, C }:
// B -> A -> C
// We first load A's transform matrix which is relative to B's, its parent, then apply it to its children (here C), themselves in their
// own local transform (that we load at this moment, although it's not relevant here). Then we load B, the root, apply its transform to
// its *direct* children (here A), but *not recursively*. C ends up in a local space made up from its own transform and its *direct* parent's,
// not from those all the way up to the root
childTransform->setPosition(nodeTransform->getPosition() + nodeTransform->getRotation() * (childTransform->getPosition() * nodeTransform->getScale()));
childTransform->setRotation(nodeTransform->getRotation() * childTransform->getRotation());
childTransform->scale(nodeTransform->getScale());
}
}

return transforms;
}

template <typename T>
void loadVertexData(const fastgltf::Accessor& accessor,
const std::vector<fastgltf::Buffer>& buffers,
Expand Down Expand Up @@ -41,6 +110,7 @@ void loadVertices(const fastgltf::Primitive& primitive,
const std::vector<fastgltf::Buffer>& buffers,
const std::vector<fastgltf::BufferView>& bufferViews,
const std::vector<fastgltf::Accessor>& accessors,
const std::optional<Transform>& transform,
Submesh& submesh) {
Logger::debug("[GltfLoad] Loading vertices...");

Expand Down Expand Up @@ -101,6 +171,14 @@ void loadVertices(const fastgltf::Primitive& primitive,
if (!hasLoadedTangents)
submesh.computeTangents();

if (transform.has_value()) {
for (Vertex& vert : submesh.getVertices()) {
vert.position = transform->getPosition() + transform->getRotation() * (vert.position * transform->getScale());
vert.normal = (transform->getRotation() * vert.normal).normalize();
vert.tangent = (transform->getRotation() * vert.tangent).normalize();
}
}

Logger::debug("[GltfLoad] Loaded vertices");
}

Expand Down Expand Up @@ -155,14 +233,15 @@ void loadIndices(const fastgltf::Accessor& indicesAccessor,
std::pair<Mesh, MeshRenderer> loadMeshes(const std::vector<fastgltf::Mesh>& meshes,
const std::vector<fastgltf::Buffer>& buffers,
const std::vector<fastgltf::BufferView>& bufferViews,
const std::vector<fastgltf::Accessor>& accessors) {
const std::vector<fastgltf::Accessor>& accessors,
const std::vector<std::optional<Transform>>& transforms) {
Logger::debug("[GltfLoad] Loading " + std::to_string(meshes.size()) + " mesh(es)...");

Mesh loadedMesh;
MeshRenderer loadedMeshRenderer;

for (const fastgltf::Mesh& mesh : meshes) {
for (const fastgltf::Primitive& primitive : mesh.primitives) {
for (std::size_t meshIndex = 0; meshIndex < meshes.size(); ++meshIndex) {
for (const fastgltf::Primitive& primitive : meshes[meshIndex].primitives) {
if (!primitive.indicesAccessor.has_value())
throw std::invalid_argument("Error: The glTF file requires having indexed geometry.");

Expand All @@ -171,7 +250,7 @@ std::pair<Mesh, MeshRenderer> loadMeshes(const std::vector<fastgltf::Mesh>& mesh

// Indices must be loaded first as they are needed to compute the tangents if necessary
loadIndices(accessors[*primitive.indicesAccessor], buffers, bufferViews, submesh.getTriangleIndices());
loadVertices(primitive, buffers, bufferViews, accessors, submesh);
loadVertices(primitive, buffers, bufferViews, accessors, transforms[meshIndex], submesh);

submeshRenderer.load(submesh, (primitive.type == fastgltf::PrimitiveType::Triangles ? RenderMode::TRIANGLE : RenderMode::POINT));
submeshRenderer.setMaterialIndex(primitive.materialIndex.value_or(0));
Expand Down Expand Up @@ -327,11 +406,11 @@ std::pair<Mesh, MeshRenderer> load(const FilePath& filePath) {

switch (fastgltf::determineGltfFileType(&data)) {
case fastgltf::GltfType::glTF:
asset = parser.loadGLTF(&data, parentPath.getPath(), fastgltf::Options::LoadExternalBuffers);
asset = parser.loadGLTF(&data, parentPath.getPath(), fastgltf::Options::LoadExternalBuffers | fastgltf::Options::DecomposeNodeMatrices);
break;

case fastgltf::GltfType::GLB:
asset = parser.loadBinaryGLTF(&data, parentPath.getPath(), fastgltf::Options::LoadGLBBuffers);
asset = parser.loadBinaryGLTF(&data, parentPath.getPath(), fastgltf::Options::LoadGLBBuffers | fastgltf::Options::DecomposeNodeMatrices);
break;

default:
Expand All @@ -341,7 +420,8 @@ std::pair<Mesh, MeshRenderer> load(const FilePath& filePath) {
if (asset.error() != fastgltf::Error::None)
throw std::invalid_argument("Error: Failed to load glTF: " + fastgltf::getErrorMessage(asset.error()));

auto [mesh, meshRenderer] = loadMeshes(asset->meshes, asset->buffers, asset->bufferViews, asset->accessors);
const std::vector<std::optional<Transform>> transforms = loadTransforms(asset->nodes, asset->meshes.size());
auto [mesh, meshRenderer] = loadMeshes(asset->meshes, asset->buffers, asset->bufferViews, asset->accessors, transforms);

const std::vector<std::optional<Image>> images = loadImages(asset->images, asset->buffers, asset->bufferViews, parentPath);
loadMaterials(asset->materials, images, meshRenderer);
Expand Down
4 changes: 2 additions & 2 deletions src/RaZ/Data/ImageFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Image load(const FilePath& filePath, bool flipVertically) {
Logger::debug("[ImageFormat] Loading image '" + filePath + "'...");

const std::string fileStr = filePath.toUtf8();
const bool isHdr = stbi_is_hdr(fileStr.c_str());
const bool isHdr = (stbi_is_hdr(fileStr.c_str()) != 0);

stbi_set_flip_vertically_on_load(flipVertically);

Expand Down Expand Up @@ -81,7 +81,7 @@ Image loadFromData(const unsigned char* imgData, std::size_t dataSize, bool flip

stbi_set_flip_vertically_on_load(flipVertically);

const bool isHdr = stbi_is_hdr_from_memory(imgData, static_cast<int>(dataSize));
const bool isHdr = (stbi_is_hdr_from_memory(imgData, static_cast<int>(dataSize)) != 0);

int width {};
int height {};
Expand Down
125 changes: 85 additions & 40 deletions tests/assets/meshes/çûbè.gltf
Original file line number Diff line number Diff line change
Expand Up @@ -137,40 +137,7 @@
],
"materials": [
{
"name": "Cube1",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.99,
0.99,
0.99,
0.99
],
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.5,
"roughnessFactor": 0.25,
"metallicRoughnessTexture": {
"index": 0
}
},
"normalTexture": {
"index": 2
},
"occlusionTexture": {
"index": 0
},
"emissiveFactor": [
0.75,
0.75,
0.75
],
"emissiveTexture": {
"index": 1
}
},
{
"name": "Cube2",
"name": "Default",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.99,
Expand Down Expand Up @@ -205,7 +172,7 @@
],
"meshes": [
{
"name": "Cube1",
"name": "Child11",
"primitives": [
{
"attributes": {
Expand All @@ -221,7 +188,7 @@
]
},
{
"name": "Cube2",
"name": "Child1",
"primitives": [
{
"attributes": {
Expand All @@ -231,7 +198,23 @@
"TEXCOORD_0": 4
},
"indices": 0,
"material": 1,
"material": 0,
"mode": 4
}
]
},
{
"name": "Root",
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1,
"TANGENT": 3,
"TEXCOORD_0": 4
},
"indices": 0,
"material": 0,
"mode": 0
}
]
Expand All @@ -240,11 +223,69 @@
"nodes": [
{
"mesh": 0,
"name": "Cube1"
"name": "Child11",
"translation" : [
1.0,
1.0,
1.0
],
"rotation" : [
0.0,
0.0,
0.7071068286895752,
0.7071068286895752
],
"scale" : [
0.5,
0.5,
0.5
]
},
{
"children" : [
0
],
"mesh": 1,
"name": "Cube2"
"name": "Child1",
"translation" : [
10.0,
10.0,
10.0
],
"rotation" : [
0.0,
0.7071068286895752,
0.0,
0.7071068286895752
],
"scale" : [
0.1,
0.1,
0.1
]
},
{
"children" : [
1
],
"mesh": 2,
"name": "Root",
"translation" : [
100.0,
100.0,
100.0
],
"rotation" : [
0.7071068286895752,
0.0,
0.0,
0.7071068286895752
],
"scale" : [
2.0,
2.0,
2.0
]
}
],
"samplers": [
Expand All @@ -254,7 +295,7 @@
"scenes": [
{
"nodes": [
0
2
]
}
],
Expand All @@ -266,6 +307,10 @@
{
"sampler": 0,
"source": 1
},
{
"sampler": 0,
"source": 2
}
]
}
Loading

0 comments on commit 39fc084

Please sign in to comment.