4#include <fastgltf/core.hpp>
5#include <fastgltf/math.hpp>
6#include <fastgltf/tools.hpp>
7#include <fastgltf/types.hpp>
15#include <unordered_map>
26 MeshRuntimeError result;
28 result.
detail = std::move(detail);
34 const fastgltf::Asset& asset,
40 *
primitive.materialIndex >= asset.materials.size())
43 const auto& material =
44 asset.materials[*
primitive.materialIndex];
45 const auto&
factor = material.pbrData.baseColorFactor;
48 result.diffuse[
index] =
50 result.metallic =
static_cast<float>(
51 material.pbrData.metallicFactor);
52 result.roughness =
static_cast<float>(
53 material.pbrData.roughnessFactor);
55 result.emissive[
index] =
56 static_cast<float>(material.emissiveFactor[
index]);
57 result.emissiveStrength =
58 static_cast<float>(material.emissiveStrength);
59 result.doubleSided = material.doubleSided;
63 return static_cast<std::uint32_t
>(
65 std::clamp(
value, 0.0F, 1.0F) * 255.0F));
69 (
channel(result.diffuse[1]) << 8U) |
70 (
channel(result.diffuse[2]) << 16U);
84 3.14159265358979323846F / 180.0F;
90 fastgltf::math::fvec3
value,
91 const YawRotation&
rotation)
noexcept
93 return fastgltf::math::fvec3(
101 const fastgltf::math::fmat4x4& world,
102 fastgltf::math::fvec3 position,
104 const YawRotation&
rotation)
noexcept
106 const auto transformed = world * fastgltf::math::fvec4(
107 position[0], position[1], position[2], 1.0F);
109 fastgltf::math::fvec3(
114 return rotated * unitsPerMeter;
120 fastgltf::math::fvec3 normal,
121 const YawRotation&
rotation)
noexcept
123 return fastgltf::math::normalize(
130 const MeshVertex& vertex,
131 bool& initialized)
noexcept
135 bounds.minimum = vertex.position;
136 bounds.maximum = vertex.position;
142 bounds.minimum[
axis] =
143 std::min(bounds.minimum[
axis], vertex.position[
axis]);
144 bounds.maximum[
axis] =
145 std::max(bounds.maximum[
axis], vertex.position[
axis]);
153 if (
index >= asset.bufferViews.size())
return false;
154 const auto& view = asset.bufferViews[
index];
155 if (view.bufferIndex >= asset.buffers.size())
return false;
156 const auto bytes = std::visit(fastgltf::visitor{
157 [](
const auto&) -> std::size_t {
return 0; },
158 [](
const fastgltf::sources::Array&
value) {
return value.bytes.size_bytes(); },
159 [](
const fastgltf::sources::Vector&
value) {
return value.bytes.size(); },
160 [](
const fastgltf::sources::ByteView&
value) {
return value.bytes.size(); }
161 }, asset.buffers[view.bufferIndex].data);
162 return view.byteOffset <=
bytes && view.byteLength <=
bytes - view.byteOffset;
166 std::size_t
index, fastgltf::AccessorType type)
168 if (
index >= asset.accessors.size())
return false;
171 accessor.componentType == fastgltf::ComponentType::Invalid)
180 const auto& view = asset.bufferViews[
viewIndex];
184 const auto available = view.byteLength -
offset;
185 return count == 0 || (
size <= available &&
196 (
sparse.indexComponentType != fastgltf::ComponentType::UnsignedByte &&
197 sparse.indexComponentType != fastgltf::ComponentType::UnsignedShort &&
198 sparse.indexComponentType != fastgltf::ComponentType::UnsignedInt) ||
200 sparse.count, fastgltf::getComponentByteSize(
sparse.indexComponentType),
false) ||
211 struct Entry { std::size_t node; std::size_t
nextChild; };
212 std::vector<std::uint8_t>
colors(asset.nodes.size());
213 std::vector<std::size_t>
depths(asset.nodes.size(), 1);
214 std::vector<Entry>
stack;
215 for (std::size_t
root = 0;
root < asset.nodes.size(); ++
root)
220 while (!
stack.empty())
223 const auto& children = asset.nodes[
entry.node].children;
224 if (
entry.nextChild == children.size())
226 for (
const auto child : children)
233 const auto child = children[
entry.nextChild++];
237 if (
stack.size() >= 1024)
return false;
248 using ImageCache = std::unordered_map<std::size_t,
249 std::shared_ptr<const ModernTextureImage>>;
253 const fastgltf::Asset& asset,
const fastgltf::TextureInfo& info,
257 if (info.texCoordIndex != 0 || info.transform)
259 "modern GLB maps require TEXCOORD_0 without texture transforms"));
260 if (!std::isfinite(scale) || info.textureIndex >= asset.textures.size())
262 "modern GLB texture reference or map scale is invalid"));
263 const auto&
texture = asset.textures[info.textureIndex];
264 if (!
texture.imageIndex || *
texture.imageIndex >= asset.images.size())
266 "modern GLB texture requires a valid image reference"));
267 ModernTextureBinding result;
268 result.colorSpace = colorSpace;
269 result.scale = scale;
272 if (*
texture.samplerIndex >= asset.samplers.size())
274 "modern GLB sampler reference is out of range"));
275 const auto& sampler = asset.samplers[*
texture.samplerIndex];
276 const auto wrap = [](fastgltf::Wrap
source) -> std::optional<ModernTextureWrap>
283 default:
return std::nullopt;
286 const auto filter = [](fastgltf::Filter
source) -> std::optional<ModernTextureFilter>
296 default:
return std::nullopt;
299 const auto wrapS = wrap(sampler.wrapS), wrapT = wrap(sampler.wrapT);
300 const auto min =
filter(sampler.minFilter.value_or(fastgltf::Filter::Linear));
301 const auto mag =
filter(sampler.magFilter.value_or(fastgltf::Filter::Linear));
302 if (!wrapS || !wrapT || !
min || !
mag ||
305 "modern GLB sampler wrap or filter is invalid"));
306 result.sampler = {*wrapS, *wrapT, *
min, *
mag};
311 result.image = cached->second;
314 const auto*
source = std::get_if<fastgltf::sources::BufferView>(
318 "modern GLB images must be embedded buffer views; external and data URIs are unsupported"));
321 "modern GLB image buffer view is outside its embedded payload"));
326 "modern GLB embedded image MIME must be PNG or JPEG"));
327 const auto bytes = fastgltf::DefaultBufferDataAdapter{}(asset,
source->bufferViewIndex);
328 ModernImageDecodeLimits limits;
329 limits.maximumDecodedBytes = std::min(limits.maximumDecodedBytes,
341 const fastgltf::Asset& asset,
const fastgltf::Primitive&
primitive,
345 if (!
primitive.materialIndex)
return result;
346 const auto& material = asset.materials[*
primitive.materialIndex];
347 if (material.alphaMode == fastgltf::AlphaMode::Blend)
349 "modern GLB alpha blending requires transparent sorting and pipelines"));
350 result.alphaMode = material.alphaMode == fastgltf::AlphaMode::Mask ?
352 result.alphaCutoff =
static_cast<float>(material.alphaCutoff);
353 if (!std::isfinite(result.alphaCutoff) || result.alphaCutoff < 0.0F)
355 "modern GLB alpha cutoff must be finite and nonnegative"));
356 const auto bind = [&](
const auto&
source, std::optional<ModernTextureBinding>&
target,
358 -> std::expected<void, MeshRuntimeError>
366 if (
auto bound =
bind(material.pbrData.baseColorTexture, result.baseColorTexture,
368 if (
auto bound =
bind(material.pbrData.metallicRoughnessTexture, result.metallicRoughnessTexture,
371 material.normalTexture ?
static_cast<float>(material.normalTexture->scale) : 1.0F);
372 !
bound)
return std::unexpected(
bound.error());
373 if (
auto bound =
bind(material.emissiveTexture, result.emissiveTexture,
376 material.occlusionTexture ?
static_cast<float>(material.occlusionTexture->strength) : 1.0F);
377 !
bound)
return std::unexpected(
bound.error());
378 if (result.occlusionTexture &&
379 (result.occlusionTexture->scale < 0.0F || result.occlusionTexture->scale > 1.0F))
381 "modern GLB occlusion strength must be between zero and one"));
382 if ((result.baseColorTexture || result.metallicRoughnessTexture || result.normalTexture ||
383 result.emissiveTexture || result.occlusionTexture) &&
386 "modern GLB textured primitive requires TEXCOORD_0"));
393 std::shared_ptr<const MeshRenderData>,
396 const std::filesystem::path&
path,
403 [](
float value) { return std::isfinite(value); }))
404 return std::unexpected(
error(
406 "invalid modern GLB path or world scale"));
416 "modern GLB file is unavailable: " +
path.string()));
420 "modern GLB input exceeds the 256 MiB parser budget"));
422 fastgltf::GltfFileStream stream(
path);
423 if (!stream.isOpen())
424 return std::unexpected(
error(
426 "modern GLB file cannot be opened: " +
429 fastgltf::Parser
parser(fastgltf::Extensions::KHR_texture_transform |
430 fastgltf::Extensions::KHR_materials_emissive_strength);
434 fastgltf::Options::None);
435 if (
loaded.error() != fastgltf::Error::None ||
436 loaded.get_if() ==
nullptr)
437 return std::unexpected(
error(
439 std::string(
"fastgltf: ") +
440 std::string(fastgltf::getErrorMessage(
443 auto asset = std::move(
loaded.get());
444 if (!asset.animations.empty())
446 "modern GLB animations require the animation adapter"));
450 if (std::ranges::any_of(asset.accessors,
451 [](
const auto&
accessor) { return accessor.sparse.has_value(); }))
453 "modern GLB sparse accessors require bounded semantic validation"));
455 return std::unexpected(
error(
457 "modern GLB node hierarchy is invalid or too deep"));
458 if (asset.scenes.empty() || asset.nodes.empty())
459 return std::unexpected(
error(
461 "modern GLB has no scene nodes"));
463 auto result = std::make_shared<MeshRenderData>();
471 asset.defaultScene.value_or(std::size_t{0});
473 return std::unexpected(
error(
475 "modern GLB default scene is out of range"));
478 fastgltf::iterateSceneNodes(
481 fastgltf::math::fmat4x4(1.0F),
482 [&](fastgltf::Node& node,
483 const fastgltf::math::fmat4x4& world)
485 if (
failed || !node.meshIndex)
491 "modern GLB skins require the animation pipeline");
494 if (*node.meshIndex >= asset.meshes.size())
499 "modern GLB node references an invalid mesh");
503 const auto& mesh = asset.meshes[*node.meshIndex];
506 const auto linear = fastgltf::math::fmat3x3(world);
508 fastgltf::math::inverse(linear));
509 const auto determinant = fastgltf::math::determinant(linear);
510 for (
const auto&
primitive : mesh.primitives)
514 *
primitive.materialIndex >= asset.materials.size())
518 "modern GLB material reference is out of range");
521 if (
primitive.type != fastgltf::PrimitiveType::Triangles)
526 "modern GLB currently supports triangle primitives only");
534 "modern GLB morph targets are reserved for the animation pass");
541 failure = std::move(material.error());
545 const auto position =
primitive.findAttribute(
"POSITION");
546 const auto normal =
primitive.findAttribute(
"NORMAL");
547 if (position ==
primitive.attributes.end() ||
550 fastgltf::AccessorType::Vec3) ||
552 fastgltf::AccessorType::Vec3))
557 "modern GLB primitive requires valid POSITION and NORMAL accessors");
562 asset.accessors[position->accessorIndex];
564 asset.accessors[normal->accessorIndex];
572 "modern GLB position/normal counts differ");
577 result->vertices.size() >
581 std::numeric_limits<std::uint32_t>::max() -
582 result->vertices.size())
587 "modern GLB exceeds mesh vertex budget");
591 const auto baseVertex = result->vertices.size();
592 result->vertices.resize(
595 const auto uv =
primitive.findAttribute(
"TEXCOORD_0");
596 const auto tangent =
primitive.findAttribute(
"TANGENT");
597 if (tangent !=
primitive.attributes.end() &&
598 (!
validAccessor(asset, tangent->accessorIndex, fastgltf::AccessorType::Vec4) ||
603 "modern GLB tangent accessor must match the vertex count");
608 fastgltf::AccessorType::Vec2) ||
609 asset.accessors[uv->accessorIndex].count !=
614 "modern GLB UV accessor must match the vertex count");
617 for (std::size_t
index = 0;
622 fastgltf::getAccessorElement<
623 fastgltf::math::fvec3>(
626 fastgltf::getAccessorElement<
627 fastgltf::math::fvec3>(
646 "modern GLB transform or normal is nonfinite or singular");
661 if (tangent !=
primitive.attributes.end())
663 const auto sourceTangent = fastgltf::getAccessorElement<fastgltf::math::fvec4>(
664 asset, asset.accessors[tangent->accessorIndex],
index);
665 auto direction =
rotateAroundY(linear * fastgltf::math::fvec3(
668 const auto length = fastgltf::math::length(direction);
675 "modern GLB authored tangent is invalid or singular");
679 vertex.tangent = {direction[0], direction[1], direction[2],
686 fastgltf::getAccessorElement<
687 fastgltf::math::fvec2>(
689 asset.accessors[uv->accessorIndex],
698 "modern GLB UV coordinates are nonfinite");
712 fastgltf::AccessorType::Scalar))
716 "modern GLB index accessor is invalid");
721 (
indexAccessor->componentType != fastgltf::ComponentType::UnsignedByte &&
722 indexAccessor->componentType != fastgltf::ComponentType::UnsignedShort &&
723 indexAccessor->componentType != fastgltf::ComponentType::UnsignedInt))
727 "modern GLB indices must be unsigned integers");
732 if (indexCount % 3 != 0)
736 "modern GLB triangle indices must be scalar triplets");
740 result->indices.size() >
747 "modern GLB exceeds mesh index budget");
750 if (result->batches.size() >=
756 "modern GLB exceeds mesh batch budget");
760 const auto firstIndex = result->indices.size();
761 result->indices.reserve(
762 firstIndex + indexCount);
763 for (std::size_t
index = 0;
775 "modern GLB index exceeds primitive vertex count");
778 result->indices.push_back(
779 static_cast<std::uint32_t
>(
786 std::move(*material),
793 return std::unexpected(std::move(
failure));
795 result->vertices.empty() ||
796 result->indices.empty() ||
797 result->batches.empty())
798 return std::unexpected(
error(
800 "modern GLB contains no renderable triangle geometry"));
804 offset[1] -= result->bounds.minimum[1];
810 "modern GLB offset exceeds finite world coordinates"));
812 if (
offset != std::array<float, 3>{})
814 for (
auto& vertex : result->vertices)
824 return std::const_pointer_cast<const MeshRenderData>(result);
826 catch (
const std::filesystem::filesystem_error&
failure)
829 "modern GLB file access failed: " + std::string(
failure.what())));
831 catch (
const std::bad_alloc&)
834 "modern GLB allocation failed"));
836 catch (
const std::length_error&)
839 "modern GLB allocation exceeds container limits"));
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
std::expected< std::shared_ptr< const MeshRenderData >, MeshRuntimeError > loadModernGltfMesh(const std::filesystem::path &path, ModernGltfLoadOptions options)
std::expected< std::shared_ptr< const ModernTextureImage >, MeshRuntimeError > decodeModernImage(std::span< const std::byte > bytes, ModernImageEncoding encoding, ModernImageDecodeLimits limits)
std::size_t maximumVertices
std::size_t maximumGroups
std::size_t maximumIndices
std::array< float, 3 > localOffset