1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
luanti/irr/src/CGLTFMeshFileLoader.h
Lars Müller 05d31222f7
Allow non-normalized weights in glTF models (#15310)
We are being lax here, but the glTF specification just requires that "when the weights are stored using float component type, their linear sum SHOULD be as close as reasonably possible to 1.0 for a given vertex"

In particular weights > 1 and weight sums well below or above 1 can be observed in models exported by Blender if they aren't manually normalized.
These fail the glTF validator but Irrlicht normalizes weights itself so we can support them just fine.

The docs have been updated to recommend normalizing weights (as well as documenting the status of interpolation support).

Weights < 0, most of them close to 0, also occur. Consistent with Irrlicht, we ignore them, but we also raise a warning.
2024-12-06 18:05:03 +01:00

169 lines
4.4 KiB
C++

// Minetest
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "SkinnedMesh.h"
#include "IMeshLoader.h"
#include "IReadFile.h"
#include "irrTypes.h"
#include "path.h"
#include "S3DVertex.h"
#include "tiniergltf.hpp"
#include <functional>
#include <cstddef>
#include <tuple>
#include <vector>
namespace irr
{
namespace scene
{
class CGLTFMeshFileLoader : public IMeshLoader
{
public:
CGLTFMeshFileLoader() noexcept {};
bool isALoadableFileExtension(const io::path &filename) const override;
IAnimatedMesh *createMesh(io::IReadFile *file) override;
private:
template <typename T>
static T rawget(const char *ptr);
template <class T>
class Accessor
{
struct BufferSource
{
const char *ptr;
std::size_t byteStride;
};
using Source = std::variant<BufferSource, std::vector<T>, std::tuple<>>;
public:
static Accessor sparseIndices(
const tiniergltf::GlTF &model,
const tiniergltf::AccessorSparseIndices &indices,
const std::size_t count);
static Accessor sparseValues(
const tiniergltf::GlTF &model,
const tiniergltf::AccessorSparseValues &values,
const std::size_t count,
const std::size_t defaultByteStride);
static Accessor base(
const tiniergltf::GlTF &model,
std::size_t accessorIdx);
static Accessor make(const tiniergltf::GlTF &model, std::size_t accessorIdx);
static constexpr tiniergltf::Accessor::Type getType();
static constexpr tiniergltf::Accessor::ComponentType getComponentType();
std::size_t getCount() const { return count; }
T get(std::size_t i) const;
private:
Accessor(const char *ptr, std::size_t byteStride, std::size_t count) :
source(BufferSource{ptr, byteStride}), count(count) {}
Accessor(std::vector<T> vec, std::size_t count) :
source(vec), count(count) {}
Accessor(std::size_t count) :
source(std::make_tuple()), count(count) {}
// Directly from buffer, sparse, or default-initialized
const Source source;
const std::size_t count;
};
template <typename... Ts>
using AccessorVariant = std::variant<Accessor<Ts>...>;
template <std::size_t N, typename... Ts>
using ArrayAccessorVariant = std::variant<Accessor<std::array<Ts, N>>...>;
template <std::size_t N>
using NormalizedValuesAccessor = ArrayAccessorVariant<N, u8, u16, f32>;
template <std::size_t N>
static NormalizedValuesAccessor<N> createNormalizedValuesAccessor(
const tiniergltf::GlTF &model,
const std::size_t accessorIdx);
template <std::size_t N, bool validate = true>
static std::array<f32, N> getNormalizedValues(
const NormalizedValuesAccessor<N> &accessor,
const std::size_t i);
class MeshExtractor
{
public:
MeshExtractor(tiniergltf::GlTF &&model,
SkinnedMesh *mesh) noexcept
: m_gltf_model(std::move(model)), m_irr_model(mesh) {};
/* Gets indices for the given mesh/primitive.
*
* Values are return in Irrlicht winding order.
*/
std::optional<std::vector<u16>> getIndices(
const tiniergltf::MeshPrimitive &primitive) const;
std::optional<std::vector<video::S3DVertex>> getVertices(
const tiniergltf::MeshPrimitive &primitive) const;
std::size_t getMeshCount() const;
std::size_t getPrimitiveCount(const std::size_t meshIdx) const;
void load();
const std::unordered_set<std::string> &getWarnings() {
return warnings;
}
private:
const tiniergltf::GlTF m_gltf_model;
SkinnedMesh *m_irr_model;
std::vector<std::function<void()>> m_mesh_loaders;
std::vector<SkinnedMesh::SJoint *> m_loaded_nodes;
std::unordered_set<std::string> warnings;
void warn(const std::string &warning) {
warnings.insert(warning);
}
void copyPositions(const std::size_t accessorIdx,
std::vector<video::S3DVertex>& vertices) const;
void copyNormals(const std::size_t accessorIdx,
std::vector<video::S3DVertex>& vertices) const;
void copyTCoords(const std::size_t accessorIdx,
std::vector<video::S3DVertex>& vertices) const;
void addPrimitive(const tiniergltf::MeshPrimitive &primitive,
const std::optional<std::size_t> skinIdx,
SkinnedMesh::SJoint *parent);
void deferAddMesh(const std::size_t meshIdx,
const std::optional<std::size_t> skinIdx,
SkinnedMesh::SJoint *parentJoint);
void loadNode(const std::size_t nodeIdx, SkinnedMesh::SJoint *parentJoint);
void loadNodes();
void loadSkins();
void loadAnimation(const std::size_t animIdx);
};
tiniergltf::GlTF parseGLTF(io::IReadFile *file);
};
} // namespace scene
} // namespace irr