mirror of
https://github.com/minetest/minetest.git
synced 2024-07-04 15:05:27 +02:00
Bring accessor implementation from other branch over
This commit is contained in:
parent
8a2cae1065
commit
411969c87c
@ -24,6 +24,8 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace irr {
|
||||
|
||||
/* Notes on the coordinate system.
|
||||
*
|
||||
* glTF uses a right-handed coordinate system where +Z is the
|
||||
@ -35,33 +37,284 @@
|
||||
* of the vertex indices.
|
||||
*/
|
||||
|
||||
namespace irr {
|
||||
// Right-to-left handedness conversions
|
||||
|
||||
template <typename T>
|
||||
static inline T convertHandedness(const T &t);
|
||||
|
||||
template <>
|
||||
core::vector3df convertHandedness(const core::vector3df &p)
|
||||
{
|
||||
return core::vector3df(p.X, p.Y, -p.Z);
|
||||
}
|
||||
|
||||
namespace scene {
|
||||
|
||||
CGLTFMeshFileLoader::BufferOffset::BufferOffset(
|
||||
const std::vector<unsigned char>& buf,
|
||||
const std::size_t offset)
|
||||
: m_buf(buf)
|
||||
, m_offset(offset)
|
||||
using CMFL = CGLTFMeshFileLoader;
|
||||
|
||||
template <class T>
|
||||
CMFL::Accessor<T>
|
||||
CMFL::Accessor<T>::sparseIndices(const tiniergltf::GlTF &model,
|
||||
const tiniergltf::AccessorSparseIndices &indices,
|
||||
const std::size_t count)
|
||||
{
|
||||
const auto &view = model.bufferViews->at(indices.bufferView);
|
||||
const auto byteStride = view.byteStride.value_or(indices.elementSize());
|
||||
|
||||
const auto &buffer = model.buffers->at(view.buffer);
|
||||
const auto source = buffer.data.data() + view.byteOffset + indices.byteOffset;
|
||||
|
||||
return CMFL::Accessor<T>(source, byteStride, count);
|
||||
}
|
||||
|
||||
CGLTFMeshFileLoader::BufferOffset::BufferOffset(
|
||||
const CGLTFMeshFileLoader::BufferOffset& other,
|
||||
const std::size_t fromOffset)
|
||||
: m_buf(other.m_buf)
|
||||
, m_offset(other.m_offset + fromOffset)
|
||||
template <class T>
|
||||
CMFL::Accessor<T>
|
||||
CMFL::Accessor<T>::sparseValues(const tiniergltf::GlTF &model,
|
||||
const tiniergltf::AccessorSparseValues &values,
|
||||
const std::size_t count,
|
||||
const std::size_t defaultByteStride)
|
||||
{
|
||||
const auto &view = model.bufferViews->at(values.bufferView);
|
||||
const auto byteStride = view.byteStride.value_or(defaultByteStride);
|
||||
|
||||
const auto &buffer = model.buffers->at(view.buffer);
|
||||
const auto source = buffer.data.data() + view.byteOffset + values.byteOffset;
|
||||
|
||||
return CMFL::Accessor<T>(source, byteStride, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a raw unsigned char (ubyte) from a buffer offset.
|
||||
*/
|
||||
unsigned char CGLTFMeshFileLoader::BufferOffset::at(
|
||||
const std::size_t fromOffset) const
|
||||
template <class T>
|
||||
CMFL::Accessor<T>
|
||||
CMFL::Accessor<T>::base(const tiniergltf::GlTF &model, std::size_t accessorIdx)
|
||||
{
|
||||
return m_buf.at(m_offset + fromOffset);
|
||||
const auto &accessor = model.accessors->at(accessorIdx);
|
||||
|
||||
if (!accessor.bufferView.has_value()) {
|
||||
return Accessor<T>(accessor.count);
|
||||
}
|
||||
|
||||
const auto &view = model.bufferViews->at(accessor.bufferView.value());
|
||||
const auto byteStride = view.byteStride.value_or(accessor.elementSize());
|
||||
|
||||
const auto &buffer = model.buffers->at(view.buffer);
|
||||
const auto source = buffer.data.data() + view.byteOffset + accessor.byteOffset;
|
||||
|
||||
return Accessor<T>(source, byteStride, accessor.count);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
CMFL::Accessor<T>
|
||||
CMFL::Accessor<T>::make(const tiniergltf::GlTF &model, std::size_t accessorIdx)
|
||||
{
|
||||
const auto &accessor = model.accessors->at(accessorIdx);
|
||||
if (accessor.componentType != getComponentType() || accessor.type != getType())
|
||||
throw std::runtime_error("invalid accessor");
|
||||
|
||||
const auto base = Accessor<T>::base(model, accessorIdx);
|
||||
|
||||
if (accessor.sparse.has_value()) {
|
||||
std::vector<T> vec(accessor.count);
|
||||
for (std::size_t i = 0; i < accessor.count; ++i) {
|
||||
vec[i] = base.get(i);
|
||||
}
|
||||
const auto overriddenCount = accessor.sparse->count;
|
||||
const auto indicesAccessor = ([&]() -> AccessorVariant<u8, u16, u32> {
|
||||
switch (accessor.sparse->indices.componentType) {
|
||||
case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_BYTE:
|
||||
return Accessor<u8>::sparseIndices(model, accessor.sparse->indices, overriddenCount);
|
||||
case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_SHORT:
|
||||
return Accessor<u16>::sparseIndices(model, accessor.sparse->indices, overriddenCount);
|
||||
case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_INT:
|
||||
return Accessor<u32>::sparseIndices(model, accessor.sparse->indices, overriddenCount);
|
||||
}
|
||||
throw std::logic_error("invalid enum value");
|
||||
})();
|
||||
|
||||
const auto valuesAccessor = Accessor<T>::sparseValues(model,
|
||||
accessor.sparse->values, overriddenCount,
|
||||
accessor.bufferView.has_value()
|
||||
? model.bufferViews->at(*accessor.bufferView).byteStride.value_or(accessor.elementSize())
|
||||
: accessor.elementSize());
|
||||
|
||||
for (std::size_t i = 0; i < overriddenCount; ++i) {
|
||||
u32 index;
|
||||
std::visit([&](auto &&acc) { index = acc.get(i); }, indicesAccessor);
|
||||
if (index >= accessor.count)
|
||||
throw std::runtime_error("index out of bounds");
|
||||
vec[index] = valuesAccessor.get(i);
|
||||
}
|
||||
return Accessor<T>(vec, accessor.count);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
#define ACCESSOR_TYPES(T, U, V) \
|
||||
template <> \
|
||||
constexpr tiniergltf::Accessor::Type CMFL::Accessor<T>::getType() \
|
||||
{ \
|
||||
return tiniergltf::Accessor::Type::U; \
|
||||
} \
|
||||
template <> \
|
||||
constexpr tiniergltf::Accessor::ComponentType CMFL::Accessor<T>::getComponentType() \
|
||||
{ \
|
||||
return tiniergltf::Accessor::ComponentType::V; \
|
||||
}
|
||||
|
||||
#define VEC_ACCESSOR_TYPES(T, U, n) \
|
||||
template <> \
|
||||
constexpr tiniergltf::Accessor::Type CMFL::Accessor<std::array<T, n>>::getType() \
|
||||
{ \
|
||||
return tiniergltf::Accessor::Type::VEC##n; \
|
||||
} \
|
||||
template <> \
|
||||
constexpr tiniergltf::Accessor::ComponentType CMFL::Accessor<std::array<T, n>>::getComponentType() \
|
||||
{ \
|
||||
return tiniergltf::Accessor::ComponentType::U; \
|
||||
} \
|
||||
template <> \
|
||||
std::array<T, n> CMFL::rawget(const void *ptr) \
|
||||
{ \
|
||||
const T *tptr = reinterpret_cast<const T *>(ptr); \
|
||||
std::array<T, n> res; \
|
||||
for (u8 i = 0; i < n; ++i) \
|
||||
res[i] = rawget<T>(tptr + i); \
|
||||
return res; \
|
||||
}
|
||||
|
||||
#define ACCESSOR_PRIMITIVE(T, U) \
|
||||
ACCESSOR_TYPES(T, SCALAR, U) \
|
||||
VEC_ACCESSOR_TYPES(T, U, 2) \
|
||||
VEC_ACCESSOR_TYPES(T, U, 3) \
|
||||
VEC_ACCESSOR_TYPES(T, U, 4)
|
||||
|
||||
ACCESSOR_PRIMITIVE(f32, FLOAT)
|
||||
ACCESSOR_PRIMITIVE(u8, UNSIGNED_BYTE)
|
||||
ACCESSOR_PRIMITIVE(u16, UNSIGNED_SHORT)
|
||||
ACCESSOR_PRIMITIVE(u32, UNSIGNED_INT)
|
||||
|
||||
ACCESSOR_TYPES(core::vector3df, VEC3, FLOAT)
|
||||
|
||||
template <class T>
|
||||
T CMFL::Accessor<T>::get(std::size_t i) const
|
||||
{
|
||||
// Buffer-based accessor: Read directly from the buffer.
|
||||
if (std::holds_alternative<BufferSource>(source)) {
|
||||
const auto bufsrc = std::get<BufferSource>(source);
|
||||
return rawget<T>(bufsrc.ptr + i * bufsrc.byteStride);
|
||||
}
|
||||
// Array-based accessor (used for sparse accessors): Read from array.
|
||||
if (std::holds_alternative<std::vector<T>>(source)) {
|
||||
return std::get<std::vector<T>>(source)[i];
|
||||
}
|
||||
// Default-initialized accessor.
|
||||
// We differ slightly from glTF here in that
|
||||
// we default-initialize quaternions and matrices properly,
|
||||
// but this does not cause any discrepancies for valid glTF models.
|
||||
std::get<std::tuple<>>(source);
|
||||
return T();
|
||||
}
|
||||
|
||||
// Note: clang and gcc should both optimize this out.
|
||||
static inline bool isBigEndian()
|
||||
{
|
||||
const u16 x = 0xFF00;
|
||||
return *(const u8 *)(&x);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T CMFL::rawget(const void *ptr)
|
||||
{
|
||||
if (!isBigEndian())
|
||||
return *reinterpret_cast<const T *>(ptr);
|
||||
// glTF uses little endian.
|
||||
// On big-endian systems, we have to swap the byte order.
|
||||
// TODO test this on a big endian system
|
||||
const u8 *bptr = reinterpret_cast<const u8 *>(ptr);
|
||||
u8 bytes[sizeof(T)];
|
||||
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||
bytes[sizeof(T) - i - 1] = bptr[i];
|
||||
}
|
||||
return *reinterpret_cast<const T *>(bytes);
|
||||
}
|
||||
|
||||
// Note that these "more specialized templates" should win.
|
||||
|
||||
template <>
|
||||
core::matrix4 CMFL::rawget(const void *ptr)
|
||||
{
|
||||
const f32 *fptr = reinterpret_cast<const f32 *>(ptr);
|
||||
f32 M[16];
|
||||
for (u8 i = 0; i < 16; ++i) {
|
||||
M[i] = rawget<f32>(fptr + i);
|
||||
}
|
||||
core::matrix4 mat;
|
||||
mat.setM(M);
|
||||
return mat;
|
||||
}
|
||||
|
||||
template <>
|
||||
core::vector3df CMFL::rawget(const void *ptr)
|
||||
{
|
||||
const f32 *fptr = reinterpret_cast<const f32 *>(ptr);
|
||||
return core::vector3df(
|
||||
rawget<f32>(fptr),
|
||||
rawget<f32>(fptr + 1),
|
||||
rawget<f32>(fptr + 2));
|
||||
}
|
||||
|
||||
template <>
|
||||
core::quaternion CMFL::rawget(const void *ptr)
|
||||
{
|
||||
const f32 *fptr = reinterpret_cast<const f32 *>(ptr);
|
||||
return core::quaternion(
|
||||
rawget<f32>(fptr),
|
||||
rawget<f32>(fptr + 1),
|
||||
rawget<f32>(fptr + 2),
|
||||
rawget<f32>(fptr + 3));
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
CMFL::NormalizedValuesAccessor<N>
|
||||
CMFL::createNormalizedValuesAccessor(
|
||||
const tiniergltf::GlTF &model,
|
||||
const std::size_t accessorIdx)
|
||||
{
|
||||
const auto &acc = model.accessors->at(accessorIdx);
|
||||
switch (acc.componentType) {
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE:
|
||||
return Accessor<std::array<u8, N>>::make(model, accessorIdx);
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT:
|
||||
return Accessor<std::array<u16, N>>::make(model, accessorIdx);
|
||||
case tiniergltf::Accessor::ComponentType::FLOAT:
|
||||
return Accessor<std::array<f32, N>>::make(model, accessorIdx);
|
||||
default:
|
||||
throw std::runtime_error("invalid component type");
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
std::array<f32, N> CMFL::getNormalizedValues(
|
||||
const NormalizedValuesAccessor<N> &accessor,
|
||||
const std::size_t i)
|
||||
{
|
||||
std::array<f32, N> values;
|
||||
if (std::holds_alternative<Accessor<std::array<u8, N>>>(accessor)) {
|
||||
const auto u8s = std::get<Accessor<std::array<u8, N>>>(accessor).get(i);
|
||||
for (u8 i = 0; i < N; ++i)
|
||||
values[i] = static_cast<f32>(u8s[i]) / std::numeric_limits<u8>::max();
|
||||
} else if (std::holds_alternative<Accessor<std::array<u16, N>>>(accessor)) {
|
||||
const auto u16s = std::get<Accessor<std::array<u16, N>>>(accessor).get(i);
|
||||
for (u8 i = 0; i < N; ++i)
|
||||
values[i] = static_cast<f32>(u16s[i]) / std::numeric_limits<u16>::max();
|
||||
} else {
|
||||
values = std::get<Accessor<std::array<f32, N>>>(accessor).get(i);
|
||||
for (u8 i = 0; i < N; ++i) {
|
||||
if (values[i] < 0 || values[i] > 1)
|
||||
throw std::runtime_error("invalid normalized value");
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
CGLTFMeshFileLoader::CGLTFMeshFileLoader() noexcept
|
||||
@ -262,49 +515,52 @@ void CGLTFMeshFileLoader::MeshExtractor::loadNodes() const
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts GLTF mesh indices into the irrlicht model.
|
||||
*/
|
||||
std::optional<std::vector<u16>> CGLTFMeshFileLoader::MeshExtractor::getIndices(
|
||||
* Extracts GLTF mesh indices.
|
||||
*/
|
||||
std::optional<std::vector<u16>> CMFL::MeshExtractor::getIndices(
|
||||
const std::size_t meshIdx,
|
||||
const std::size_t primitiveIdx) const
|
||||
{
|
||||
const auto accessorIdx = getIndicesAccessorIdx(meshIdx, primitiveIdx);
|
||||
const auto accessorIdx = m_gltf_model.meshes->at(meshIdx).primitives.at(primitiveIdx).indices;
|
||||
if (!accessorIdx.has_value())
|
||||
return std::nullopt; // non-indexed geometry
|
||||
const auto &accessor = m_gltf_model.accessors->at(accessorIdx.value());
|
||||
|
||||
const auto& buf = getBuffer(accessorIdx.value());
|
||||
|
||||
const auto accessor = ([&]() -> AccessorVariant<u8, u16, u32> {
|
||||
const auto &acc = m_gltf_model.accessors->at(*accessorIdx);
|
||||
switch (acc.componentType) {
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE:
|
||||
return Accessor<u8>::make(m_gltf_model, *accessorIdx);
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT:
|
||||
return Accessor<u16>::make(m_gltf_model, *accessorIdx);
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_INT:
|
||||
return Accessor<u32>::make(m_gltf_model, *accessorIdx);
|
||||
default:
|
||||
throw std::runtime_error("invalid component type");
|
||||
}
|
||||
})();
|
||||
const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor);
|
||||
|
||||
std::vector<u16> indices;
|
||||
const auto count = getElemCount(accessorIdx.value());
|
||||
indices.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
// TODO (low-priority, maybe never) also reverse winding order based on determinant of global transform
|
||||
// FIXME this hack also reverses triangle draw order
|
||||
std::size_t elemIdx = count - i - 1; // reverse index order
|
||||
u16 index;
|
||||
// Note: glTF forbids the max value for each component type.
|
||||
switch (accessor.componentType) {
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE: {
|
||||
index = readPrimitive<u8>(BufferOffset(buf, elemIdx * sizeof(u8)));
|
||||
if (index == std::numeric_limits<u8>::max())
|
||||
throw std::runtime_error("invalid index");
|
||||
break;
|
||||
}
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT: {
|
||||
index = readPrimitive<u16>(BufferOffset(buf, elemIdx * sizeof(u16)));
|
||||
if (index == std::numeric_limits<u16>::max())
|
||||
throw std::runtime_error("invalid index");
|
||||
break;
|
||||
}
|
||||
case tiniergltf::Accessor::ComponentType::UNSIGNED_INT: {
|
||||
u32 indexWide = readPrimitive<u32>(BufferOffset(buf, elemIdx * sizeof(u32)));
|
||||
// Use >= here for consistency.
|
||||
if (indexWide >= std::numeric_limits<u16>::max())
|
||||
throw std::runtime_error("index too large (>= 65536)");
|
||||
index = static_cast<u16>(indexWide);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("invalid index component type");
|
||||
if (std::holds_alternative<Accessor<u8>>(accessor)) {
|
||||
index = std::get<Accessor<u8>>(accessor).get(elemIdx);
|
||||
if (index == std::numeric_limits<u8>::max())
|
||||
throw std::runtime_error("invalid index");
|
||||
} else if (std::holds_alternative<Accessor<u16>>(accessor)) {
|
||||
index = std::get<Accessor<u16>>(accessor).get(elemIdx);
|
||||
if (index == std::numeric_limits<u16>::max())
|
||||
throw std::runtime_error("invalid index");
|
||||
} else if (std::holds_alternative<Accessor<u32>>(accessor)) {
|
||||
u32 indexWide = std::get<Accessor<u32>>(accessor).get(elemIdx);
|
||||
// Use >= here for consistency.
|
||||
if (indexWide >= std::numeric_limits<u16>::max())
|
||||
throw std::runtime_error("index too large (>= 65536)");
|
||||
index = static_cast<u16>(indexWide);
|
||||
}
|
||||
indices.push_back(index);
|
||||
}
|
||||
@ -314,32 +570,33 @@ std::optional<std::vector<u16>> CGLTFMeshFileLoader::MeshExtractor::getIndices(
|
||||
|
||||
/**
|
||||
* Create a vector of video::S3DVertex (model data) from a mesh & primitive index.
|
||||
*/
|
||||
std::optional<std::vector<video::S3DVertex>> CGLTFMeshFileLoader::MeshExtractor::getVertices(
|
||||
*/
|
||||
std::optional<std::vector<video::S3DVertex>> CMFL::MeshExtractor::getVertices(
|
||||
const std::size_t meshIdx,
|
||||
const std::size_t primitiveIdx) const
|
||||
{
|
||||
const auto positionAccessorIdx = getPositionAccessorIdx(
|
||||
meshIdx, primitiveIdx);
|
||||
const auto &attributes = m_gltf_model.meshes->at(meshIdx).primitives.at(primitiveIdx).attributes;
|
||||
const auto positionAccessorIdx = attributes.position;
|
||||
if (!positionAccessorIdx.has_value()) {
|
||||
// "When positions are not specified, client implementations SHOULD skip primitive's rendering"
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<vertex_t> vertices{};
|
||||
vertices.resize(getElemCount(*positionAccessorIdx));
|
||||
std::vector<video::S3DVertex> vertices;
|
||||
const auto vertexCount = m_gltf_model.accessors->at(*positionAccessorIdx).count;
|
||||
vertices.resize(vertexCount);
|
||||
copyPositions(*positionAccessorIdx, vertices);
|
||||
|
||||
const auto normalAccessorIdx = getNormalAccessorIdx(
|
||||
meshIdx, primitiveIdx);
|
||||
const auto normalAccessorIdx = attributes.normal;
|
||||
if (normalAccessorIdx.has_value()) {
|
||||
copyNormals(normalAccessorIdx.value(), vertices);
|
||||
}
|
||||
// TODO verify that the automatic normal recalculation done in Minetest indeed works correctly
|
||||
|
||||
const auto tCoordAccessorIdx = getTCoordAccessorIdx(
|
||||
meshIdx, primitiveIdx);
|
||||
if (tCoordAccessorIdx.has_value()) {
|
||||
copyTCoords(tCoordAccessorIdx.value(), vertices);
|
||||
const auto &texcoords = m_gltf_model.meshes->at(meshIdx).primitives[primitiveIdx].attributes.texcoord;
|
||||
if (texcoords.has_value()) {
|
||||
const auto tCoordAccessorIdx = texcoords->at(0);
|
||||
copyTCoords(tCoordAccessorIdx, vertices);
|
||||
}
|
||||
|
||||
return vertices;
|
||||
@ -362,70 +619,17 @@ std::size_t CGLTFMeshFileLoader::MeshExtractor::getPrimitiveCount(
|
||||
return m_gltf_model.meshes->at(meshIdx).primitives.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Templated buffer reader. Based on type width.
|
||||
* This is specifically used to build upon to read more complex data types.
|
||||
* It is also used raw to read arrays directly.
|
||||
* Basically we're using the width of the type to infer
|
||||
* how big of a gap we have from the beginning of the buffer.
|
||||
*/
|
||||
template <typename T>
|
||||
T CGLTFMeshFileLoader::MeshExtractor::readPrimitive(
|
||||
const BufferOffset& readFrom)
|
||||
{
|
||||
unsigned char d[sizeof(T)]{};
|
||||
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||
d[i] = readFrom.at(i);
|
||||
}
|
||||
T dest;
|
||||
std::memcpy(&dest, d, sizeof(dest));
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a vector2df from a buffer at an offset.
|
||||
* @return vec2 core::Vector2df
|
||||
*/
|
||||
core::vector2df CGLTFMeshFileLoader::MeshExtractor::readVec2DF(
|
||||
const CGLTFMeshFileLoader::BufferOffset& readFrom)
|
||||
{
|
||||
return core::vector2df(readPrimitive<float>(readFrom),
|
||||
readPrimitive<float>(BufferOffset(readFrom, sizeof(float))));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a vector3df from a buffer at an offset.
|
||||
* Also does right-to-left-handed coordinate system conversion (inverts Z axis).
|
||||
* @return vec3 core::Vector3df
|
||||
*/
|
||||
core::vector3df CGLTFMeshFileLoader::MeshExtractor::readVec3DF(
|
||||
const BufferOffset& readFrom,
|
||||
const core::vector3df scale = {1.0f,1.0f,1.0f})
|
||||
{
|
||||
return core::vector3df(
|
||||
readPrimitive<float>(readFrom),
|
||||
readPrimitive<float>(BufferOffset(readFrom, sizeof(float))),
|
||||
-readPrimitive<float>(BufferOffset(readFrom, 2 *
|
||||
sizeof(float))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams vertex positions raw data into usable buffer via reference.
|
||||
* Buffer: ref Vector<video::S3DVertex>
|
||||
*/
|
||||
void CGLTFMeshFileLoader::MeshExtractor::copyPositions(
|
||||
const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const
|
||||
std::vector<video::S3DVertex>& vertices) const
|
||||
{
|
||||
|
||||
const auto& buffer = getBuffer(accessorIdx);
|
||||
const auto count = getElemCount(accessorIdx);
|
||||
const auto byteStride = getByteStride(accessorIdx);
|
||||
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
const auto v = readVec3DF(BufferOffset(buffer, byteStride * i));
|
||||
vertices[i].Pos = v;
|
||||
const auto accessor = Accessor<core::vector3df>::make(m_gltf_model, accessorIdx);
|
||||
for (std::size_t i = 0; i < accessor.getCount(); i++) {
|
||||
vertices[i].Pos = convertHandedness(accessor.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,15 +639,11 @@ void CGLTFMeshFileLoader::MeshExtractor::copyPositions(
|
||||
*/
|
||||
void CGLTFMeshFileLoader::MeshExtractor::copyNormals(
|
||||
const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const
|
||||
std::vector<video::S3DVertex>& vertices) const
|
||||
{
|
||||
const auto& buffer = getBuffer(accessorIdx);
|
||||
const auto count = getElemCount(accessorIdx);
|
||||
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
const auto n = readVec3DF(BufferOffset(buffer,
|
||||
3 * sizeof(float) * i));
|
||||
vertices[i].Normal = n;
|
||||
const auto accessor = Accessor<core::vector3df>::make(m_gltf_model, accessorIdx);
|
||||
for (std::size_t i = 0; i < accessor.getCount(); ++i) {
|
||||
vertices[i].Normal = convertHandedness(accessor.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,62 +653,16 @@ void CGLTFMeshFileLoader::MeshExtractor::copyNormals(
|
||||
*/
|
||||
void CGLTFMeshFileLoader::MeshExtractor::copyTCoords(
|
||||
const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const
|
||||
std::vector<video::S3DVertex>& vertices) const
|
||||
{
|
||||
|
||||
const auto& buffer = getBuffer(accessorIdx);
|
||||
const auto count = getElemCount(accessorIdx);
|
||||
|
||||
const auto accessor = createNormalizedValuesAccessor<2>(m_gltf_model, accessorIdx);
|
||||
const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
const auto t = readVec2DF(BufferOffset(buffer,
|
||||
2 * sizeof(float) * i));
|
||||
vertices[i].TCoords = t;
|
||||
const auto vals = getNormalizedValues(accessor, i);
|
||||
vertices[i].TCoords = core::vector2df(vals[0], vals[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of elements referenced by this accessor, not to be confused with the number of bytes or number of components.
|
||||
* Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_accessor_count
|
||||
* Type: Integer
|
||||
* Required: YES
|
||||
*/
|
||||
std::size_t CGLTFMeshFileLoader::MeshExtractor::getElemCount(
|
||||
const std::size_t accessorIdx) const
|
||||
{
|
||||
return m_gltf_model.accessors->at(accessorIdx).count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The stride, in bytes, between vertex attributes.
|
||||
* When this is not defined, data is tightly packed.
|
||||
* When two or more accessors use the same buffer view, this field MUST be defined.
|
||||
* Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_bufferview_bytestride
|
||||
* Required: NO
|
||||
*/
|
||||
std::size_t CGLTFMeshFileLoader::MeshExtractor::getByteStride(
|
||||
const std::size_t accessorIdx) const
|
||||
{
|
||||
const auto& accessor = m_gltf_model.accessors->at(accessorIdx);
|
||||
// FIXME this does not work with sparse / zero-initialized accessors
|
||||
const auto& view = m_gltf_model.bufferViews->at(accessor.bufferView.value());
|
||||
return view.byteStride.value_or(accessor.elementSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through the complex chain of the model to extract the required buffer.
|
||||
* Accessor -> BufferView -> Buffer
|
||||
*/
|
||||
CGLTFMeshFileLoader::BufferOffset CGLTFMeshFileLoader::MeshExtractor::getBuffer(
|
||||
const std::size_t accessorIdx) const
|
||||
{
|
||||
const auto& accessor = m_gltf_model.accessors->at(accessorIdx);
|
||||
// FIXME this does not work with sparse / zero-initialized accessors
|
||||
const auto& view = m_gltf_model.bufferViews->at(accessor.bufferView.value());
|
||||
const auto& buffer = m_gltf_model.buffers->at(view.buffer);
|
||||
|
||||
return BufferOffset(buffer.data, view.byteOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* The index of the accessor that contains the vertex indices.
|
||||
* When this is undefined, the primitive defines non-indexed geometry.
|
||||
|
@ -30,24 +30,71 @@ class CGLTFMeshFileLoader : public IMeshLoader
|
||||
IAnimatedMesh* createMesh(io::IReadFile* file) override;
|
||||
|
||||
private:
|
||||
class BufferOffset
|
||||
template <typename T>
|
||||
static T rawget(const void *ptr);
|
||||
|
||||
template <class T>
|
||||
class Accessor
|
||||
{
|
||||
struct BufferSource
|
||||
{
|
||||
const u8 *ptr;
|
||||
std::size_t byteStride;
|
||||
};
|
||||
using Source = std::variant<BufferSource, std::vector<T>, std::tuple<>>;
|
||||
|
||||
public:
|
||||
BufferOffset(const std::vector<unsigned char>& buf,
|
||||
const std::size_t offset);
|
||||
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;
|
||||
|
||||
BufferOffset(const BufferOffset& other,
|
||||
const std::size_t fromOffset);
|
||||
|
||||
unsigned char at(const std::size_t fromOffset) const;
|
||||
private:
|
||||
const std::vector<unsigned char>& m_buf;
|
||||
std::size_t m_offset;
|
||||
Accessor(const u8 *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>
|
||||
static std::array<f32, N> getNormalizedValues(
|
||||
const NormalizedValuesAccessor<N> &accessor,
|
||||
const std::size_t i);
|
||||
|
||||
class MeshExtractor {
|
||||
public:
|
||||
using vertex_t = video::S3DVertex;
|
||||
|
||||
MeshExtractor(const tiniergltf::GlTF &model,
|
||||
ISkinnedMesh *mesh) noexcept
|
||||
@ -64,7 +111,7 @@ class CGLTFMeshFileLoader : public IMeshLoader
|
||||
std::optional<std::vector<u16>> getIndices(const std::size_t meshIdx,
|
||||
const std::size_t primitiveIdx) const;
|
||||
|
||||
std::optional<std::vector<vertex_t>> getVertices(std::size_t meshIdx,
|
||||
std::optional<std::vector<video::S3DVertex>> getVertices(std::size_t meshIdx,
|
||||
const std::size_t primitiveIdx) const;
|
||||
|
||||
std::size_t getMeshCount() const;
|
||||
@ -77,34 +124,14 @@ class CGLTFMeshFileLoader : public IMeshLoader
|
||||
const tiniergltf::GlTF m_gltf_model;
|
||||
ISkinnedMesh *m_irr_model;
|
||||
|
||||
template <typename T>
|
||||
static T readPrimitive(const BufferOffset& readFrom);
|
||||
|
||||
static core::vector2df readVec2DF(
|
||||
const BufferOffset& readFrom);
|
||||
|
||||
/* Read a vec3df from a buffer with transformations applied.
|
||||
*
|
||||
* Values are returned in Irrlicht coordinates.
|
||||
*/
|
||||
static core::vector3df readVec3DF(
|
||||
const BufferOffset& readFrom,
|
||||
const core::vector3df scale);
|
||||
|
||||
void copyPositions(const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const;
|
||||
std::vector<video::S3DVertex>& vertices) const;
|
||||
|
||||
void copyNormals(const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const;
|
||||
std::vector<video::S3DVertex>& vertices) const;
|
||||
|
||||
void copyTCoords(const std::size_t accessorIdx,
|
||||
std::vector<vertex_t>& vertices) const;
|
||||
|
||||
std::size_t getElemCount(const std::size_t accessorIdx) const;
|
||||
|
||||
std::size_t getByteStride(const std::size_t accessorIdx) const;
|
||||
|
||||
BufferOffset getBuffer(const std::size_t accessorIdx) const;
|
||||
std::vector<video::S3DVertex>& vertices) const;
|
||||
|
||||
std::optional<std::size_t> getIndicesAccessorIdx(const std::size_t meshIdx,
|
||||
const std::size_t primitiveIdx) const;
|
||||
|
Loading…
Reference in New Issue
Block a user