2 // SPDX-License-Identifier: LGPL-2.1-or-later
4 #include "CGLTFMeshFileLoader.h"
6 #include "SMaterialLayer.h"
8 #include "CSkinnedMesh.h"
9 #include "IAnimatedMesh.h"
10 #include "IReadFile.h"
15 #include "quaternion.h"
34 /* Notes on the coordinate system.
36 * glTF uses a right-handed coordinate system where +Z is the
37 * front-facing axis, and Irrlicht uses a left-handed coordinate
38 * system where -Z is the front-facing axis.
39 * We convert between them by mirroring the mesh across the X axis.
40 * Doing this correctly requires negating the Z coordinate on
41 * vertex positions and normals, and reversing the winding order
42 * of the vertex indices.
45 // Right-to-left handedness conversions
48 static inline T
convertHandedness(const T
&t
);
51 core::vector3df
convertHandedness(const core::vector3df
&p
)
53 return core::vector3df(p
.X
, p
.Y
, -p
.Z
);
57 core::quaternion
convertHandedness(const core::quaternion
&q
)
59 return core::quaternion(q
.X
, q
.Y
, -q
.Z
, q
.W
);
63 core::matrix4
convertHandedness(const core::matrix4
&mat
)
65 // Base transformation between left & right handed coordinate systems.
66 static const core::matrix4 invertZ
= core::matrix4(
71 // Convert from left-handed to right-handed,
73 // then convert from right-handed to left-handed.
74 // Both conversions just invert Z.
75 return invertZ
* mat
* invertZ
;
80 using SelfType
= CGLTFMeshFileLoader
;
84 SelfType::Accessor
<T
>::sparseIndices(const tiniergltf::GlTF
&model
,
85 const tiniergltf::AccessorSparseIndices
&indices
,
86 const std::size_t count
)
88 const auto &view
= model
.bufferViews
->at(indices
.bufferView
);
89 const auto byteStride
= view
.byteStride
.value_or(indices
.elementSize());
91 const auto &buffer
= model
.buffers
->at(view
.buffer
);
92 const auto source
= buffer
.data
.data() + view
.byteOffset
+ indices
.byteOffset
;
94 return SelfType::Accessor
<T
>(source
, byteStride
, count
);
99 SelfType::Accessor
<T
>::sparseValues(const tiniergltf::GlTF
&model
,
100 const tiniergltf::AccessorSparseValues
&values
,
101 const std::size_t count
,
102 const std::size_t defaultByteStride
)
104 const auto &view
= model
.bufferViews
->at(values
.bufferView
);
105 const auto byteStride
= view
.byteStride
.value_or(defaultByteStride
);
107 const auto &buffer
= model
.buffers
->at(view
.buffer
);
108 const auto source
= buffer
.data
.data() + view
.byteOffset
+ values
.byteOffset
;
110 return SelfType::Accessor
<T
>(source
, byteStride
, count
);
114 SelfType::Accessor
<T
>
115 SelfType::Accessor
<T
>::base(const tiniergltf::GlTF
&model
, std::size_t accessorIdx
)
117 const auto &accessor
= model
.accessors
->at(accessorIdx
);
119 if (!accessor
.bufferView
.has_value()) {
120 return Accessor
<T
>(accessor
.count
);
123 const auto &view
= model
.bufferViews
->at(accessor
.bufferView
.value());
124 const auto byteStride
= view
.byteStride
.value_or(accessor
.elementSize());
126 const auto &buffer
= model
.buffers
->at(view
.buffer
);
127 const auto source
= buffer
.data
.data() + view
.byteOffset
+ accessor
.byteOffset
;
129 return Accessor
<T
>(source
, byteStride
, accessor
.count
);
133 SelfType::Accessor
<T
>
134 SelfType::Accessor
<T
>::make(const tiniergltf::GlTF
&model
, std::size_t accessorIdx
)
136 const auto &accessor
= model
.accessors
->at(accessorIdx
);
137 if (accessor
.componentType
!= getComponentType() || accessor
.type
!= getType())
138 throw std::runtime_error("invalid accessor");
140 const auto base
= Accessor
<T
>::base(model
, accessorIdx
);
142 if (accessor
.sparse
.has_value()) {
143 std::vector
<T
> vec(accessor
.count
);
144 for (std::size_t i
= 0; i
< accessor
.count
; ++i
) {
145 vec
[i
] = base
.get(i
);
147 const auto overriddenCount
= accessor
.sparse
->count
;
148 const auto indicesAccessor
= ([&]() -> AccessorVariant
<u8
, u16
, u32
> {
149 switch (accessor
.sparse
->indices
.componentType
) {
150 case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_BYTE
:
151 return Accessor
<u8
>::sparseIndices(model
, accessor
.sparse
->indices
, overriddenCount
);
152 case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_SHORT
:
153 return Accessor
<u16
>::sparseIndices(model
, accessor
.sparse
->indices
, overriddenCount
);
154 case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_INT
:
155 return Accessor
<u32
>::sparseIndices(model
, accessor
.sparse
->indices
, overriddenCount
);
157 throw std::logic_error("invalid enum value");
160 const auto valuesAccessor
= Accessor
<T
>::sparseValues(model
,
161 accessor
.sparse
->values
, overriddenCount
,
162 accessor
.bufferView
.has_value()
163 ? model
.bufferViews
->at(*accessor
.bufferView
).byteStride
.value_or(accessor
.elementSize())
164 : accessor
.elementSize());
166 for (std::size_t i
= 0; i
< overriddenCount
; ++i
) {
168 std::visit([&](auto &&acc
) { index
= acc
.get(i
); }, indicesAccessor
);
169 if (index
>= accessor
.count
)
170 throw std::runtime_error("index out of bounds");
171 vec
[index
] = valuesAccessor
.get(i
);
173 return Accessor
<T
>(vec
, accessor
.count
);
179 #define ACCESSOR_TYPES(T, U, V) \
181 constexpr tiniergltf::Accessor::Type SelfType::Accessor<T>::getType() \
183 return tiniergltf::Accessor::Type::U; \
186 constexpr tiniergltf::Accessor::ComponentType SelfType::Accessor<T>::getComponentType() \
188 return tiniergltf::Accessor::ComponentType::V; \
191 #define VEC_ACCESSOR_TYPES(T, U, N) \
193 constexpr tiniergltf::Accessor::Type SelfType::Accessor<std::array<T, N>>::getType() \
195 return tiniergltf::Accessor::Type::VEC##N; \
198 constexpr tiniergltf::Accessor::ComponentType SelfType::Accessor<std::array<T, N>>::getComponentType() \
200 return tiniergltf::Accessor::ComponentType::U; \
203 std::array<T, N> SelfType::rawget(const char *ptr) \
205 std::array<T, N> res; \
206 for (int i = 0; i < N; ++i) \
207 res[i] = rawget<T>(ptr + sizeof(T) * i); \
211 #define ACCESSOR_PRIMITIVE(T, U) \
212 ACCESSOR_TYPES(T, SCALAR, U) \
213 VEC_ACCESSOR_TYPES(T, U, 2) \
214 VEC_ACCESSOR_TYPES(T, U, 3) \
215 VEC_ACCESSOR_TYPES(T, U, 4)
217 ACCESSOR_PRIMITIVE(f32
, FLOAT
)
218 ACCESSOR_PRIMITIVE(u8
, UNSIGNED_BYTE
)
219 ACCESSOR_PRIMITIVE(u16
, UNSIGNED_SHORT
)
220 ACCESSOR_PRIMITIVE(u32
, UNSIGNED_INT
)
222 ACCESSOR_TYPES(core::vector3df
, VEC3
, FLOAT
)
223 ACCESSOR_TYPES(core::quaternion
, VEC4
, FLOAT
)
224 ACCESSOR_TYPES(core::matrix4
, MAT4
, FLOAT
)
227 T
SelfType::Accessor
<T
>::get(std::size_t i
) const
229 // Buffer-based accessor: Read directly from the buffer.
230 if (std::holds_alternative
<BufferSource
>(source
)) {
231 const auto bufsrc
= std::get
<BufferSource
>(source
);
232 return rawget
<T
>(bufsrc
.ptr
+ i
* bufsrc
.byteStride
);
234 // Array-based accessor (used for sparse accessors): Read from array.
235 if (std::holds_alternative
<std::vector
<T
>>(source
)) {
236 return std::get
<std::vector
<T
>>(source
)[i
];
238 // Default-initialized accessor.
239 // We differ slightly from glTF here in that
240 // we default-initialize quaternions and matrices properly,
241 // but this does not cause any discrepancies for valid glTF models.
242 std::get
<std::tuple
<>>(source
);
246 template <typename T
>
247 T
SelfType::rawget(const char *ptr
)
250 std::memcpy(&dest
, ptr
, sizeof(dest
));
251 #ifdef __BIG_ENDIAN__
252 return os::Byteswap::byteswap(dest
);
258 // Note that these "more specialized templates" should win.
261 core::matrix4
SelfType::rawget(const char *ptr
)
264 for (u8 i
= 0; i
< 16; ++i
) {
265 mat
[i
] = rawget
<f32
>(ptr
+ i
* sizeof(f32
));
271 core::vector3df
SelfType::rawget(const char *ptr
)
273 return core::vector3df(
275 rawget
<f32
>(ptr
+ sizeof(f32
)),
276 rawget
<f32
>(ptr
+ 2 * sizeof(f32
)));
280 core::quaternion
SelfType::rawget(const char *ptr
)
282 return core::quaternion(
284 rawget
<f32
>(ptr
+ sizeof(f32
)),
285 rawget
<f32
>(ptr
+ 2 * sizeof(f32
)),
286 rawget
<f32
>(ptr
+ 3 * sizeof(f32
)));
289 template <std::size_t N
>
290 SelfType::NormalizedValuesAccessor
<N
>
291 SelfType::createNormalizedValuesAccessor(
292 const tiniergltf::GlTF
&model
,
293 const std::size_t accessorIdx
)
295 const auto &acc
= model
.accessors
->at(accessorIdx
);
296 switch (acc
.componentType
) {
297 case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE
:
298 return Accessor
<std::array
<u8
, N
>>::make(model
, accessorIdx
);
299 case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT
:
300 return Accessor
<std::array
<u16
, N
>>::make(model
, accessorIdx
);
301 case tiniergltf::Accessor::ComponentType::FLOAT
:
302 return Accessor
<std::array
<f32
, N
>>::make(model
, accessorIdx
);
304 throw std::runtime_error("invalid component type");
308 template <std::size_t N
>
309 std::array
<f32
, N
> SelfType::getNormalizedValues(
310 const NormalizedValuesAccessor
<N
> &accessor
,
313 std::array
<f32
, N
> values
;
314 if (std::holds_alternative
<Accessor
<std::array
<u8
, N
>>>(accessor
)) {
315 const auto u8s
= std::get
<Accessor
<std::array
<u8
, N
>>>(accessor
).get(i
);
316 for (u8 i
= 0; i
< N
; ++i
)
317 values
[i
] = static_cast<f32
>(u8s
[i
]) / std::numeric_limits
<u8
>::max();
318 } else if (std::holds_alternative
<Accessor
<std::array
<u16
, N
>>>(accessor
)) {
319 const auto u16s
= std::get
<Accessor
<std::array
<u16
, N
>>>(accessor
).get(i
);
320 for (u8 i
= 0; i
< N
; ++i
)
321 values
[i
] = static_cast<f32
>(u16s
[i
]) / std::numeric_limits
<u16
>::max();
323 values
= std::get
<Accessor
<std::array
<f32
, N
>>>(accessor
).get(i
);
324 for (u8 i
= 0; i
< N
; ++i
) {
325 if (values
[i
] < 0 || values
[i
] > 1)
326 throw std::runtime_error("invalid normalized value");
332 bool SelfType::isALoadableFileExtension(
333 const io::path
& filename
) const
335 return core::hasFileExtension(filename
, "gltf") ||
336 core::hasFileExtension(filename
, "glb");
340 * Entry point into loading a GLTF model.
342 IAnimatedMesh
* SelfType::createMesh(io::IReadFile
* file
)
344 const char *filename
= file
->getFileName().c_str();
346 tiniergltf::GlTF model
= parseGLTF(file
);
347 irr_ptr
<CSkinnedMesh
> mesh(new CSkinnedMesh());
348 MeshExtractor
extractor(std::move(model
), mesh
.get());
351 for (const auto &warning
: extractor
.getWarnings()) {
352 os::Printer::log(filename
, warning
.c_str(), ELL_WARNING
);
354 return mesh
.release();
355 } catch (const std::runtime_error
&e
) {
356 os::Printer::log("error converting gltf to irrlicht mesh", e
.what(), ELL_ERROR
);
358 } catch (const std::runtime_error
&e
) {
359 os::Printer::log("error parsing gltf", e
.what(), ELL_ERROR
);
364 static void transformVertices(std::vector
<video::S3DVertex
> &vertices
, const core::matrix4
&transform
)
366 for (auto &vertex
: vertices
) {
367 // Apply scaling, rotation and rotation (in that order) to the position.
368 transform
.transformVect(vertex
.Pos
);
369 // For the normal, we do not want to apply the translation.
370 vertex
.Normal
= transform
.rotateAndScaleVect(vertex
.Normal
);
371 // Renormalize (length might have been affected by scaling).
372 vertex
.Normal
.normalize();
376 static void checkIndices(const std::vector
<u16
> &indices
, const std::size_t nVerts
)
378 for (u16 index
: indices
) {
380 throw std::runtime_error("index out of bounds");
384 static std::vector
<u16
> generateIndices(const std::size_t nVerts
)
386 std::vector
<u16
> indices(nVerts
);
387 for (std::size_t i
= 0; i
< nVerts
; i
+= 3) {
388 // Reverse winding order per triangle
390 indices
[i
+ 1] = i
+ 1;
396 using Wrap
= tiniergltf::Sampler::Wrap
;
397 static video::E_TEXTURE_CLAMP
convertTextureWrap(const Wrap wrap
) {
400 return video::ETC_REPEAT
;
401 case Wrap::CLAMP_TO_EDGE
:
402 return video::ETC_CLAMP_TO_EDGE
;
403 case Wrap::MIRRORED_REPEAT
:
404 return video::ETC_MIRROR
;
406 throw std::runtime_error("invalid sampler wrapping mode");
410 void SelfType::MeshExtractor::addPrimitive(
411 const tiniergltf::MeshPrimitive
&primitive
,
412 const std::optional
<std::size_t> skinIdx
,
413 CSkinnedMesh::SJoint
*parent
)
415 auto vertices
= getVertices(primitive
);
416 if (!vertices
.has_value())
417 return; // "When positions are not specified, client implementations SHOULD skip primitive’s rendering"
419 const auto n_vertices
= vertices
->size();
421 // Excludes the max value for consistency.
422 if (n_vertices
>= std::numeric_limits
<u16
>::max())
423 throw std::runtime_error("too many vertices");
425 // Apply the global transform along the parent chain.
426 transformVertices(*vertices
, parent
->GlobalMatrix
);
428 auto maybeIndices
= getIndices(primitive
);
429 std::vector
<u16
> indices
;
430 if (maybeIndices
.has_value()) {
431 indices
= std::move(*maybeIndices
);
432 checkIndices(indices
, vertices
->size());
434 // Non-indexed geometry
435 indices
= generateIndices(vertices
->size());
438 m_irr_model
->addMeshBuffer(
439 new SSkinMeshBuffer(std::move(*vertices
), std::move(indices
)));
440 const auto meshbufNr
= m_irr_model
->getMeshBufferCount() - 1;
441 auto *meshbuf
= m_irr_model
->getMeshBuffer(meshbufNr
);
443 if (primitive
.material
.has_value()) {
444 const auto &material
= m_gltf_model
.materials
->at(*primitive
.material
);
445 if (material
.pbrMetallicRoughness
.has_value()) {
446 const auto &texture
= material
.pbrMetallicRoughness
->baseColorTexture
;
447 if (texture
.has_value()) {
448 m_irr_model
->setTextureSlot(meshbufNr
, static_cast<u32
>(texture
->index
));
449 const auto samplerIdx
= m_gltf_model
.textures
->at(texture
->index
).sampler
;
450 if (samplerIdx
.has_value()) {
451 auto &sampler
= m_gltf_model
.samplers
->at(*samplerIdx
);
452 auto &layer
= meshbuf
->getMaterial().TextureLayers
[0];
453 layer
.TextureWrapU
= convertTextureWrap(sampler
.wrapS
);
454 layer
.TextureWrapV
= convertTextureWrap(sampler
.wrapT
);
460 if (!skinIdx
.has_value()) {
461 // No skin => all vertices belong entirely to their parent
462 for (std::size_t v
= 0; v
< n_vertices
; ++v
) {
463 auto *weight
= m_irr_model
->addWeight(parent
);
464 weight
->buffer_id
= meshbufNr
;
465 weight
->vertex_id
= v
;
466 weight
->strength
= 1.0f
;
471 const auto &skin
= m_gltf_model
.skins
->at(*skinIdx
);
473 const auto &attrs
= primitive
.attributes
;
474 const auto &joints
= attrs
.joints
;
475 if (!joints
.has_value())
478 const auto &weights
= attrs
.weights
;
479 for (std::size_t set
= 0; set
< joints
->size(); ++set
) {
480 const auto jointAccessor
= ([&]() -> ArrayAccessorVariant
<4, u8
, u16
> {
481 const auto idx
= joints
->at(set
);
482 const auto &acc
= m_gltf_model
.accessors
->at(idx
);
484 switch (acc
.componentType
) {
485 case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE
:
486 return Accessor
<std::array
<u8
, 4>>::make(m_gltf_model
, idx
);
487 case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT
:
488 return Accessor
<std::array
<u16
, 4>>::make(m_gltf_model
, idx
);
490 throw std::runtime_error("invalid component type");
494 const auto weightAccessor
= createNormalizedValuesAccessor
<4>(m_gltf_model
, weights
->at(set
));
496 for (std::size_t v
= 0; v
< n_vertices
; ++v
) {
497 std::array
<u16
, 4> jointIdxs
;
498 if (std::holds_alternative
<Accessor
<std::array
<u8
, 4>>>(jointAccessor
)) {
499 const auto jointIdxsU8
= std::get
<Accessor
<std::array
<u8
, 4>>>(jointAccessor
).get(v
);
500 jointIdxs
= {jointIdxsU8
[0], jointIdxsU8
[1], jointIdxsU8
[2], jointIdxsU8
[3]};
501 } else if (std::holds_alternative
<Accessor
<std::array
<u16
, 4>>>(jointAccessor
)) {
502 jointIdxs
= std::get
<Accessor
<std::array
<u16
, 4>>>(jointAccessor
).get(v
);
504 std::array
<f32
, 4> strengths
= getNormalizedValues(weightAccessor
, v
);
507 for (std::size_t in_set
= 0; in_set
< 4; ++in_set
) {
508 u16 jointIdx
= jointIdxs
[in_set
];
509 f32 strength
= strengths
[in_set
];
513 CSkinnedMesh::SWeight
*weight
= m_irr_model
->addWeight(m_loaded_nodes
.at(skin
.joints
.at(jointIdx
)));
514 weight
->buffer_id
= meshbufNr
;
515 weight
->vertex_id
= v
;
516 weight
->strength
= strength
;
523 * Load up the rawest form of the model. The vertex positions and indices.
524 * Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes
525 * If material is undefined, then a default material MUST be used.
527 void SelfType::MeshExtractor::deferAddMesh(
528 const std::size_t meshIdx
,
529 const std::optional
<std::size_t> skinIdx
,
530 CSkinnedMesh::SJoint
*parent
)
532 m_mesh_loaders
.emplace_back([=] {
533 for (std::size_t pi
= 0; pi
< getPrimitiveCount(meshIdx
); ++pi
) {
534 const auto &primitive
= m_gltf_model
.meshes
->at(meshIdx
).primitives
.at(pi
);
535 addPrimitive(primitive
, skinIdx
, parent
);
540 // Base transformation between left & right handed coordinate systems.
541 // This just inverts the Z axis.
542 static const core::matrix4 leftToRight
= core::matrix4(
548 static const core::matrix4 rightToLeft
= leftToRight
;
550 static core::matrix4
loadTransform(const tiniergltf::Node::Matrix
&m
, CSkinnedMesh::SJoint
*joint
)
552 // Note: Under the hood, this casts these doubles to floats.
553 core::matrix4 mat
= convertHandedness(core::matrix4(
554 m
[0], m
[1], m
[2], m
[3],
555 m
[4], m
[5], m
[6], m
[7],
556 m
[8], m
[9], m
[10], m
[11],
557 m
[12], m
[13], m
[14], m
[15]));
559 // Decompose the matrix into translation, scale, and rotation.
560 joint
->Animatedposition
= mat
.getTranslation();
562 auto scale
= mat
.getScale();
563 joint
->Animatedscale
= scale
;
564 core::matrix4 inverseScale
;
565 inverseScale
.setScale(core::vector3df(
566 scale
.X
== 0 ? 0 : 1 / scale
.X
,
567 scale
.Y
== 0 ? 0 : 1 / scale
.Y
,
568 scale
.Z
== 0 ? 0 : 1 / scale
.Z
));
570 core::matrix4 axisNormalizedMat
= inverseScale
* mat
;
571 joint
->Animatedrotation
= axisNormalizedMat
.getRotationDegrees();
572 // Invert the rotation because it is applied using `getMatrix_transposed`,
573 // which again inverts.
574 joint
->Animatedrotation
.makeInverse();
579 static core::matrix4
loadTransform(const tiniergltf::Node::TRS
&trs
, CSkinnedMesh::SJoint
*joint
)
581 const auto &trans
= trs
.translation
;
582 const auto &rot
= trs
.rotation
;
583 const auto &scale
= trs
.scale
;
584 core::matrix4 transMat
;
585 joint
->Animatedposition
= convertHandedness(core::vector3df(trans
[0], trans
[1], trans
[2]));
586 transMat
.setTranslation(joint
->Animatedposition
);
587 core::matrix4 rotMat
;
588 joint
->Animatedrotation
= convertHandedness(core::quaternion(rot
[0], rot
[1], rot
[2], rot
[3]));
589 core::quaternion(joint
->Animatedrotation
).getMatrix_transposed(rotMat
);
590 joint
->Animatedscale
= core::vector3df(scale
[0], scale
[1], scale
[2]);
591 core::matrix4 scaleMat
;
592 scaleMat
.setScale(joint
->Animatedscale
);
593 return transMat
* rotMat
* scaleMat
;
596 static core::matrix4
loadTransform(std::optional
<std::variant
<tiniergltf::Node::Matrix
, tiniergltf::Node::TRS
>> transform
,
597 CSkinnedMesh::SJoint
*joint
) {
598 if (!transform
.has_value()) {
599 return core::matrix4();
601 return std::visit([joint
](const auto &t
) { return loadTransform(t
, joint
); }, *transform
);
604 void SelfType::MeshExtractor::loadNode(
605 const std::size_t nodeIdx
,
606 CSkinnedMesh::SJoint
*parent
)
608 const auto &node
= m_gltf_model
.nodes
->at(nodeIdx
);
609 auto *joint
= m_irr_model
->addJoint(parent
);
610 const core::matrix4 transform
= loadTransform(node
.transform
, joint
);
611 joint
->LocalMatrix
= transform
;
612 joint
->GlobalMatrix
= parent
? parent
->GlobalMatrix
* joint
->LocalMatrix
: joint
->LocalMatrix
;
613 if (node
.name
.has_value()) {
614 joint
->Name
= node
.name
->c_str();
616 m_loaded_nodes
[nodeIdx
] = joint
;
617 if (node
.mesh
.has_value()) {
618 deferAddMesh(*node
.mesh
, node
.skin
, joint
);
620 if (node
.children
.has_value()) {
621 for (const auto &child
: *node
.children
) {
622 loadNode(child
, joint
);
627 void SelfType::MeshExtractor::loadNodes()
629 m_loaded_nodes
= std::vector
<CSkinnedMesh::SJoint
*>(m_gltf_model
.nodes
->size());
631 std::vector
<bool> isChild(m_gltf_model
.nodes
->size());
632 for (const auto &node
: *m_gltf_model
.nodes
) {
633 if (!node
.children
.has_value())
635 for (const auto &child
: *node
.children
) {
636 isChild
[child
] = true;
639 // Load all nodes that aren't children.
640 // Children will be loaded by their parent nodes.
641 for (std::size_t i
= 0; i
< m_gltf_model
.nodes
->size(); ++i
) {
643 loadNode(i
, nullptr);
648 void SelfType::MeshExtractor::loadSkins()
650 if (!m_gltf_model
.skins
.has_value())
653 for (const auto &skin
: *m_gltf_model
.skins
) {
654 if (!skin
.inverseBindMatrices
.has_value())
656 const auto accessor
= Accessor
<core::matrix4
>::make(m_gltf_model
, *skin
.inverseBindMatrices
);
657 if (accessor
.getCount() < skin
.joints
.size())
658 throw std::runtime_error("accessor contains too few matrices");
659 for (std::size_t i
= 0; i
< skin
.joints
.size(); ++i
) {
660 m_loaded_nodes
.at(skin
.joints
[i
])->GlobalInversedMatrix
= convertHandedness(accessor
.get(i
));
665 void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx
)
667 const auto &anim
= m_gltf_model
.animations
->at(animIdx
);
668 for (const auto &channel
: anim
.channels
) {
670 const auto &sampler
= anim
.samplers
.at(channel
.sampler
);
671 if (sampler
.interpolation
!= tiniergltf::AnimationSampler::Interpolation::LINEAR
)
672 throw std::runtime_error("unsupported interpolation, only linear interpolation is supported");
674 const auto inputAccessor
= Accessor
<f32
>::make(m_gltf_model
, sampler
.input
);
675 const auto n_frames
= inputAccessor
.getCount();
677 if (!channel
.target
.node
.has_value())
678 throw std::runtime_error("no animated node");
680 const auto &joint
= m_loaded_nodes
.at(*channel
.target
.node
);
681 switch (channel
.target
.path
) {
682 case tiniergltf::AnimationChannelTarget::Path::TRANSLATION
: {
683 const auto outputAccessor
= Accessor
<core::vector3df
>::make(m_gltf_model
, sampler
.output
);
684 for (std::size_t i
= 0; i
< n_frames
; ++i
) {
685 auto *key
= m_irr_model
->addPositionKey(joint
);
686 key
->frame
= inputAccessor
.get(i
);
687 key
->position
= convertHandedness(outputAccessor
.get(i
));
691 case tiniergltf::AnimationChannelTarget::Path::ROTATION
: {
692 const auto outputAccessor
= Accessor
<core::quaternion
>::make(m_gltf_model
, sampler
.output
);
693 for (std::size_t i
= 0; i
< n_frames
; ++i
) {
694 auto *key
= m_irr_model
->addRotationKey(joint
);
695 key
->frame
= inputAccessor
.get(i
);
696 key
->rotation
= convertHandedness(outputAccessor
.get(i
));
700 case tiniergltf::AnimationChannelTarget::Path::SCALE
: {
701 const auto outputAccessor
= Accessor
<core::vector3df
>::make(m_gltf_model
, sampler
.output
);
702 for (std::size_t i
= 0; i
< n_frames
; ++i
) {
703 auto *key
= m_irr_model
->addScaleKey(joint
);
704 key
->frame
= inputAccessor
.get(i
);
705 key
->scale
= outputAccessor
.get(i
);
709 case tiniergltf::AnimationChannelTarget::Path::WEIGHTS
:
710 throw std::runtime_error("no support for morph animations");
715 void SelfType::MeshExtractor::load()
717 if (m_gltf_model
.extensionsRequired
)
718 throw std::runtime_error("model requires extensions, but we support none");
720 if (!(m_gltf_model
.buffers
.has_value()
721 && m_gltf_model
.bufferViews
.has_value()
722 && m_gltf_model
.accessors
.has_value()
723 && m_gltf_model
.meshes
.has_value()
724 && m_gltf_model
.nodes
.has_value())) {
725 throw std::runtime_error("missing required fields");
728 if (m_gltf_model
.images
.has_value())
729 warn("embedded images are not supported");
733 for (const auto &load_mesh
: m_mesh_loaders
) {
737 // Load the first animation, if there is one.
738 if (m_gltf_model
.animations
.has_value()) {
739 if (m_gltf_model
.animations
->size() > 1)
740 warn("multiple animations are not supported");
743 m_irr_model
->setAnimationSpeed(1);
745 } catch (const std::out_of_range
&e
) {
746 throw std::runtime_error(e
.what());
747 } catch (const std::bad_optional_access
&e
) {
748 throw std::runtime_error(e
.what());
751 m_irr_model
->finalize();
755 * Extracts GLTF mesh indices.
757 std::optional
<std::vector
<u16
>> SelfType::MeshExtractor::getIndices(
758 const tiniergltf::MeshPrimitive
&primitive
) const
760 const auto accessorIdx
= primitive
.indices
;
761 if (!accessorIdx
.has_value())
762 return std::nullopt
; // non-indexed geometry
764 const auto accessor
= ([&]() -> AccessorVariant
<u8
, u16
, u32
> {
765 const auto &acc
= m_gltf_model
.accessors
->at(*accessorIdx
);
766 switch (acc
.componentType
) {
767 case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE
:
768 return Accessor
<u8
>::make(m_gltf_model
, *accessorIdx
);
769 case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT
:
770 return Accessor
<u16
>::make(m_gltf_model
, *accessorIdx
);
771 case tiniergltf::Accessor::ComponentType::UNSIGNED_INT
:
772 return Accessor
<u32
>::make(m_gltf_model
, *accessorIdx
);
774 throw std::runtime_error("invalid component type");
777 const auto count
= std::visit([](auto &&a
) { return a
.getCount(); }, accessor
);
779 std::vector
<u16
> indices
;
780 for (std::size_t i
= 0; i
< count
; ++i
) {
781 // TODO (low-priority, maybe never) also reverse winding order based on determinant of global transform
782 // FIXME this hack also reverses triangle draw order
783 std::size_t elemIdx
= count
- i
- 1; // reverse index order
785 // Note: glTF forbids the max value for each component type.
786 if (std::holds_alternative
<Accessor
<u8
>>(accessor
)) {
787 index
= std::get
<Accessor
<u8
>>(accessor
).get(elemIdx
);
788 if (index
== std::numeric_limits
<u8
>::max())
789 throw std::runtime_error("invalid index");
790 } else if (std::holds_alternative
<Accessor
<u16
>>(accessor
)) {
791 index
= std::get
<Accessor
<u16
>>(accessor
).get(elemIdx
);
792 if (index
== std::numeric_limits
<u16
>::max())
793 throw std::runtime_error("invalid index");
795 _IRR_DEBUG_BREAK_IF(!std::holds_alternative
<Accessor
<u32
>>(accessor
));
796 u32 indexWide
= std::get
<Accessor
<u32
>>(accessor
).get(elemIdx
);
797 // Use >= here for consistency.
798 if (indexWide
>= std::numeric_limits
<u16
>::max())
799 throw std::runtime_error("index too large (>= 65536)");
800 index
= static_cast<u16
>(indexWide
);
802 indices
.push_back(index
);
809 * Create a vector of video::S3DVertex (model data) from a mesh & primitive index.
811 std::optional
<std::vector
<video::S3DVertex
>> SelfType::MeshExtractor::getVertices(
812 const tiniergltf::MeshPrimitive
&primitive
) const
814 const auto &attributes
= primitive
.attributes
;
815 const auto positionAccessorIdx
= attributes
.position
;
816 if (!positionAccessorIdx
.has_value()) {
817 // "When positions are not specified, client implementations SHOULD skip primitive's rendering"
821 std::vector
<video::S3DVertex
> vertices
;
822 const auto vertexCount
= m_gltf_model
.accessors
->at(*positionAccessorIdx
).count
;
823 vertices
.resize(vertexCount
);
824 copyPositions(*positionAccessorIdx
, vertices
);
826 const auto normalAccessorIdx
= attributes
.normal
;
827 if (normalAccessorIdx
.has_value()) {
828 copyNormals(normalAccessorIdx
.value(), vertices
);
830 // TODO verify that the automatic normal recalculation done in Minetest indeed works correctly
832 const auto &texcoords
= attributes
.texcoord
;
833 if (texcoords
.has_value()) {
834 const auto tCoordAccessorIdx
= texcoords
->at(0);
835 copyTCoords(tCoordAccessorIdx
, vertices
);
842 * Get the amount of meshes that a model contains.
844 std::size_t SelfType::MeshExtractor::getMeshCount() const
846 return m_gltf_model
.meshes
->size();
850 * Get the amount of primitives that a mesh in a model contains.
852 std::size_t SelfType::MeshExtractor::getPrimitiveCount(
853 const std::size_t meshIdx
) const
855 return m_gltf_model
.meshes
->at(meshIdx
).primitives
.size();
859 * Streams vertex positions raw data into usable buffer via reference.
860 * Buffer: ref Vector<video::S3DVertex>
862 void SelfType::MeshExtractor::copyPositions(
863 const std::size_t accessorIdx
,
864 std::vector
<video::S3DVertex
>& vertices
) const
866 const auto accessor
= Accessor
<core::vector3df
>::make(m_gltf_model
, accessorIdx
);
867 for (std::size_t i
= 0; i
< accessor
.getCount(); i
++) {
868 vertices
[i
].Pos
= convertHandedness(accessor
.get(i
));
873 * Streams normals raw data into usable buffer via reference.
874 * Buffer: ref Vector<video::S3DVertex>
876 void SelfType::MeshExtractor::copyNormals(
877 const std::size_t accessorIdx
,
878 std::vector
<video::S3DVertex
>& vertices
) const
880 const auto accessor
= Accessor
<core::vector3df
>::make(m_gltf_model
, accessorIdx
);
881 for (std::size_t i
= 0; i
< accessor
.getCount(); ++i
) {
882 vertices
[i
].Normal
= convertHandedness(accessor
.get(i
));
887 * Streams texture coordinate raw data into usable buffer via reference.
888 * Buffer: ref Vector<video::S3DVertex>
890 void SelfType::MeshExtractor::copyTCoords(
891 const std::size_t accessorIdx
,
892 std::vector
<video::S3DVertex
>& vertices
) const
894 const auto componentType
= m_gltf_model
.accessors
->at(accessorIdx
).componentType
;
895 if (componentType
== tiniergltf::Accessor::ComponentType::FLOAT
) {
896 // If floats are used, they need not be normalized: Wrapping may take effect.
897 const auto accessor
= Accessor
<std::array
<f32
, 2>>::make(m_gltf_model
, accessorIdx
);
898 for (std::size_t i
= 0; i
< accessor
.getCount(); ++i
) {
899 vertices
[i
].TCoords
= core::vector2d
<f32
>(accessor
.get(i
));
902 const auto accessor
= createNormalizedValuesAccessor
<2>(m_gltf_model
, accessorIdx
);
903 const auto count
= std::visit([](auto &&a
) { return a
.getCount(); }, accessor
);
904 for (std::size_t i
= 0; i
< count
; ++i
) {
905 vertices
[i
].TCoords
= core::vector2d
<f32
>(getNormalizedValues(accessor
, i
));
911 * This is where the actual model's GLTF file is loaded and parsed by tiniergltf.
913 tiniergltf::GlTF
SelfType::parseGLTF(io::IReadFile
* file
)
915 const bool isGlb
= core::hasFileExtension(file
->getFileName(), "glb");
916 auto size
= file
->getSize();
917 if (size
< 0) // this can happen if `ftell` fails
918 throw std::runtime_error("error reading file");
920 throw std::runtime_error("file is empty");
922 std::unique_ptr
<char[]> buf(new char[size
+ 1]);
923 if (file
->read(buf
.get(), size
) != static_cast<std::size_t>(size
))
924 throw std::runtime_error("file ended prematurely");
925 // We probably don't need this, but add it just to be sure.
929 return tiniergltf::readGlb(buf
.get(), size
);
931 return tiniergltf::readGlTF(buf
.get(), size
);
932 } catch (const std::out_of_range
&e
) {
933 throw std::runtime_error(e
.what());
934 } catch (const std::bad_optional_access
&e
) {
935 throw std::runtime_error(e
.what());