• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 18062694977

27 Sep 2025 04:20PM UTC coverage: 74.093% (+0.04%) from 74.05%
18062694977

push

github

Razakhel
[Utils/Logger] Added formatted logging overloads

- Formatted calls to logging functions and made use of std::format() in several other places

100 of 170 new or added lines in 36 files covered. (58.82%)

4 existing lines in 2 files now uncovered.

8334 of 11248 relevant lines covered (74.09%)

1757.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.26
/src/RaZ/Data/GltfLoad.cpp
1
#include "RaZ/Data/GltfFormat.hpp"
2
#include "RaZ/Data/Image.hpp"
3
#include "RaZ/Data/ImageFormat.hpp"
4
#include "RaZ/Data/Mesh.hpp"
5
#include "RaZ/Math/Transform.hpp"
6
#include "RaZ/Render/MeshRenderer.hpp"
7
#include "RaZ/Utils/FilePath.hpp"
8
#include "RaZ/Utils/FileUtils.hpp"
9
#include "RaZ/Utils/Logger.hpp"
10

11
#include "fastgltf/core.hpp"
12
#include "fastgltf/math.hpp"
13
#include "fastgltf/tools.hpp"
14

15
#include "tracy/Tracy.hpp"
16

17
namespace Raz::GltfFormat {
18

19
namespace {
20

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

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

27
  return Transform(Vec3f(transform->translation.x(), transform->translation.y(), transform->translation.z()),
6✔
28
                   Quaternionf(transform->rotation.w(), transform->rotation.x(), transform->rotation.y(), transform->rotation.z()),
6✔
29
                   Vec3f(transform->scale.x(), transform->scale.y(), transform->scale.z()));
12✔
30

31
}
32

33
void computeNodeTransform(const fastgltf::Node& currentNode,
12✔
34
                          const std::optional<Transform>& parentTransform,
35
                          const std::vector<fastgltf::Node>& nodes,
36
                          std::vector<std::optional<Transform>>& transforms) {
37
  ZoneScopedN("[GltfLoad]::computeNodeTransform");
38

39
  if (!currentNode.meshIndex.has_value())
12✔
40
    return;
3✔
41

42
  const std::size_t currentMeshIndex = *currentNode.meshIndex;
9✔
43

44
  if (currentMeshIndex >= transforms.size()) {
9✔
45
    Logger::error("[GltfLoad] Unexpected node mesh index");
×
46
    return;
×
47
  }
48

49
  std::optional<Transform>& currentTransform = transforms[currentMeshIndex];
9✔
50

51
  if (!currentTransform.has_value())
9✔
52
    currentTransform = loadTransform(currentNode);
6✔
53

54
  if (parentTransform.has_value()) {
9✔
55
    currentTransform->setPosition(parentTransform->getPosition() + parentTransform->getRotation() * (currentTransform->getPosition() * parentTransform->getScale()));
3✔
56
    currentTransform->setRotation((parentTransform->getRotation() * currentTransform->getRotation()).normalize());
3✔
57
    currentTransform->scale(parentTransform->getScale());
3✔
58
  }
59

60
  for (const std::size_t childIndex : currentNode.children)
12✔
61
    computeNodeTransform(nodes[childIndex], currentTransform, nodes, transforms);
3✔
62
}
63

64
std::vector<std::optional<Transform>> loadTransforms(const std::vector<fastgltf::Node>& nodes, std::size_t meshCount) {
4✔
65
  ZoneScopedN("[GltfLoad]::loadTransforms");
66

67
  std::vector<std::optional<Transform>> transforms;
4✔
68
  transforms.resize(meshCount);
4✔
69

70
  for (const fastgltf::Node& node : nodes)
13✔
71
    computeNodeTransform(node, std::nullopt, nodes, transforms);
9✔
72

73
  return transforms;
4✔
74
}
×
75

76
template <typename T, typename FuncT>
77
void loadVertexData(const fastgltf::Asset& asset,
15✔
78
                    const fastgltf::Primitive& primitive,
79
                    std::string_view attribName,
80
                    FuncT&& callback) {
81
  static_assert(std::is_invocable_v<FuncT, T, std::size_t>);
82

83
  ZoneScopedN("[GltfLoad]::loadVertexData");
84

85
  const auto attribIter = primitive.findAttribute(attribName);
15✔
86

87
  if (attribIter == primitive.attributes.end())
15✔
88
    return;
2✔
89

90
  fastgltf::iterateAccessorWithIndex<T>(asset, asset.accessors[attribIter->accessorIndex], std::forward<FuncT>(callback));
13✔
91
}
92

93
void loadVertices(const fastgltf::Asset& asset,
6✔
94
                  const fastgltf::Primitive& primitive,
95
                  const std::optional<Transform>& transform,
96
                  Submesh& submesh) {
97
  ZoneScopedN("[GltfLoad]::loadVertices");
98

99
  Logger::debug("[GltfLoad] Loading vertices...");
6✔
100

101
  const auto positionIter = primitive.findAttribute("POSITION");
6✔
102

103
  if (positionIter == primitive.attributes.end())
6✔
104
    throw std::invalid_argument("Error: Required 'POSITION' attribute not found in the glTF file.");
×
105

106
  const fastgltf::Accessor& positionAccessor = asset.accessors[positionIter->accessorIndex];
6✔
107

108
  if (!positionAccessor.bufferViewIndex.has_value())
6✔
109
    return;
×
110

111
  std::vector<Vertex>& vertices = submesh.getVertices();
6✔
112
  vertices.resize(positionAccessor.count);
6✔
113

114
  fastgltf::iterateAccessorWithIndex<fastgltf::math::fvec3>(asset, positionAccessor, [&vertices] (fastgltf::math::fvec3 position, std::size_t vertexIndex) noexcept {
6✔
115
    vertices[vertexIndex].position = Vec3f(position.x(), position.y(), position.z());
180✔
116
  });
180✔
117

118
  loadVertexData<fastgltf::math::fvec2>(asset, primitive, "TEXCOORD_0", [&vertices] (fastgltf::math::fvec2 uv, std::size_t vertexIndex) noexcept {
6✔
119
    // The texcoords can be outside the [0; 1] range; they're normalized according to the REPEAT mode. This may be subject to change
120
    // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_wrapping
121
    vertices[vertexIndex].texcoords = Vec2f(uv.x(), uv.y());
132✔
122

123
    for (float& elt : vertices[vertexIndex].texcoords.getData()) {
396✔
124
      if (elt < -1.f || elt > 1.f) elt = std::fmod(elt, 1.f);
264✔
125
      if (elt < 0.f) elt += 1.f;
264✔
126
    }
127
  });
132✔
128

129
  loadVertexData<fastgltf::math::fvec3>(asset, primitive, "NORMAL", [&vertices] (fastgltf::math::fvec3 normal, std::size_t vertexIndex) noexcept {
6✔
130
    vertices[vertexIndex].normal = Vec3f(normal.x(), normal.y(), normal.z());
180✔
131
  });
180✔
132

133
  const bool hasTangents = (primitive.findAttribute("TANGENT") != primitive.attributes.end());
6✔
134

135
  if (hasTangents) {
6✔
136
    loadVertexData<fastgltf::math::fvec4>(asset, primitive, "TANGENT", [&vertices] (fastgltf::math::fvec4 tangent, std::size_t vertexIndex) noexcept {
3✔
137
      // The tangent's input W component is either 1 or -1 and represents the handedness
138
      // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview
139
      vertices[vertexIndex].tangent = Vec3f(tangent.x(), tangent.y(), tangent.z()) * tangent.w();
108✔
140
    });
108✔
141
  } else {
142
    submesh.computeTangents();
3✔
143
  }
144

145
  if (transform.has_value()) {
6✔
146
    for (Vertex& vert : submesh.getVertices()) {
186✔
147
      vert.position = transform->getPosition() + transform->getRotation() * (vert.position * transform->getScale());
180✔
148
      vert.normal   = (transform->getRotation() * vert.normal).normalize();
180✔
149
      vert.tangent  = (transform->getRotation() * vert.tangent).normalize();
180✔
150
    }
151
  }
152

153
  Logger::debug("[GltfLoad] Loaded vertices");
12✔
154
}
155

156
void loadIndices(const fastgltf::Asset& asset,
6✔
157
                 const fastgltf::Accessor& indicesAccessor,
158
                 std::vector<unsigned int>& indices) {
159
  ZoneScopedN("[GltfLoad]::loadIndices");
160

161
  Logger::debug("[GltfLoad] Loading indices...");
6✔
162

163
  if (!indicesAccessor.bufferViewIndex.has_value())
6✔
164
    throw std::invalid_argument("Error: Missing glTF buffer to load indices from.");
×
165

166
  indices.resize(indicesAccessor.count);
6✔
167
  fastgltf::copyFromAccessor<unsigned int>(asset, indicesAccessor, indices.data());
6✔
168

169
  Logger::debug("[GltfLoad] Loaded indices");
6✔
170
}
6✔
171

172
std::pair<Mesh, MeshRenderer> loadMeshes(const fastgltf::Asset& asset,
4✔
173
                                         const std::vector<std::optional<Transform>>& transforms) {
174
  ZoneScopedN("[GltfLoad]::loadMeshes");
175

176
  const std::vector<fastgltf::Mesh>& meshes = asset.meshes;
4✔
177

178
  Logger::debug("[GltfLoad] Loading {} mesh(es)...", meshes.size());
4✔
179

180
  Mesh loadedMesh;
4✔
181
  MeshRenderer loadedMeshRenderer;
4✔
182

183
  for (std::size_t meshIndex = 0; meshIndex < meshes.size(); ++meshIndex) {
10✔
184
    for (const fastgltf::Primitive& primitive : meshes[meshIndex].primitives) {
12✔
185
      if (!primitive.indicesAccessor.has_value())
6✔
186
        throw std::invalid_argument("Error: The glTF file requires having indexed geometry.");
×
187

188
      Submesh& submesh = loadedMesh.addSubmesh();
6✔
189
      SubmeshRenderer& submeshRenderer = loadedMeshRenderer.addSubmeshRenderer();
6✔
190

191
      // Indices must be loaded first as they are needed to compute the tangents if necessary
192
      loadIndices(asset, asset.accessors[*primitive.indicesAccessor], submesh.getTriangleIndices());
6✔
193
      loadVertices(asset, primitive, transforms[meshIndex], submesh);
6✔
194

195
      submeshRenderer.load(submesh, (primitive.type == fastgltf::PrimitiveType::Triangles ? RenderMode::TRIANGLE : RenderMode::POINT));
6✔
196
      submeshRenderer.setMaterialIndex(primitive.materialIndex.value_or(0));
6✔
197
    }
198
  }
199

200
  Logger::debug("[GltfLoad] Loaded mesh(es)");
4✔
201

202
  return { std::move(loadedMesh), std::move(loadedMeshRenderer) };
8✔
203
}
4✔
204

205
std::vector<std::optional<Image>> loadImages(const std::vector<fastgltf::Image>& images,
4✔
206
                                             const std::vector<fastgltf::Buffer>& buffers,
207
                                             const std::vector<fastgltf::BufferView>& bufferViews,
208
                                             const FilePath& rootFilePath) {
209
  ZoneScopedN("[GltfLoad]::loadImages");
210

211
  Logger::debug("[GltfLoad] Loading {} image(s)...", images.size());
4✔
212

213
  std::vector<std::optional<Image>> loadedImages;
4✔
214
  loadedImages.reserve(images.size());
4✔
215

216
  const auto loadFailure = [&loadedImages] (const auto&) {
×
217
    Logger::error("[GltfLoad] Cannot find a suitable way of loading an image");
×
218
    loadedImages.emplace_back(std::nullopt);
×
219
  };
4✔
220

221
  for (const fastgltf::Image& img : images) {
10✔
222
    std::visit(fastgltf::visitor {
6✔
223
      [&loadedImages, &rootFilePath] (const fastgltf::sources::URI& imgPath) {
5✔
224
        loadedImages.emplace_back(ImageFormat::load(rootFilePath + imgPath.uri.path()));
5✔
225
      },
5✔
226
      [&loadedImages] (const fastgltf::sources::Vector& imgData) {
×
227
        const auto* imgBytes = reinterpret_cast<const unsigned char*>(imgData.bytes.data());
×
228
        loadedImages.emplace_back(ImageFormat::loadFromData(imgBytes, imgData.bytes.size()));
×
229
      },
×
230
      [&bufferViews, &buffers, &loadedImages, &loadFailure] (const fastgltf::sources::BufferView& bufferViewSource) {
1✔
231
        const fastgltf::BufferView& imgView = bufferViews[bufferViewSource.bufferViewIndex];
1✔
232
        const fastgltf::Buffer& imgBuffer   = buffers[imgView.bufferIndex];
1✔
233

234
        std::visit(fastgltf::visitor {
2✔
235
          [&loadedImages, &imgView] (const fastgltf::sources::Array& imgData) {
1✔
236
            const auto* imgBytes = reinterpret_cast<const unsigned char*>(imgData.bytes.data());
1✔
237
            loadedImages.emplace_back(ImageFormat::loadFromData(imgBytes + imgView.byteOffset, imgView.byteLength));
1✔
238
          },
1✔
239
          loadFailure
240
        }, imgBuffer.data);
1✔
241
      },
1✔
242
      loadFailure
243
    }, img.data);
6✔
244
  }
245

246
  Logger::debug("[GltfLoad] Loaded image(s)");
4✔
247

248
  return loadedImages;
8✔
249
}
×
250

251
Image extractAmbientOcclusionImage(const Image& occlusionImg) {
1✔
252
  Image ambientImg(occlusionImg.getWidth(), occlusionImg.getHeight(), ImageColorspace::GRAY, occlusionImg.getDataType());
1✔
253

254
  for (std::size_t i = 0; i < occlusionImg.getWidth() * occlusionImg.getHeight(); ++i) {
5✔
255
    const std::size_t finalIndex = i * occlusionImg.getChannelCount();
4✔
256

257
    // The occlusion is located in the red (1st) channel
258
    // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_material_occlusiontexture
259
    if (occlusionImg.getDataType() == ImageDataType::BYTE)
4✔
260
      static_cast<uint8_t*>(ambientImg.getDataPtr())[i] = static_cast<const uint8_t*>(occlusionImg.getDataPtr())[finalIndex];
4✔
261
    else
262
      static_cast<float*>(ambientImg.getDataPtr())[i] = static_cast<const float*>(occlusionImg.getDataPtr())[finalIndex];
×
263
  }
264

265
  return ambientImg;
1✔
266
}
267

268
std::pair<Image, Image> extractMetalnessRoughnessImages(const Image& metalRoughImg) {
1✔
269
  Image metalnessImg(metalRoughImg.getWidth(), metalRoughImg.getHeight(), ImageColorspace::GRAY, metalRoughImg.getDataType());
1✔
270
  Image roughnessImg(metalRoughImg.getWidth(), metalRoughImg.getHeight(), ImageColorspace::GRAY, metalRoughImg.getDataType());
1✔
271

272
  for (std::size_t i = 0; i < metalRoughImg.getWidth() * metalRoughImg.getHeight(); ++i) {
5✔
273
    const std::size_t finalIndex = i * metalRoughImg.getChannelCount();
4✔
274

275
    // The metalness & roughness are located respectively in the blue (3rd) & green (2nd) channels
276
    // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_material_pbrmetallicroughness_metallicroughnesstexture
277
    if (metalRoughImg.getDataType() == ImageDataType::BYTE) {
4✔
278
      static_cast<uint8_t*>(metalnessImg.getDataPtr())[i] = static_cast<const uint8_t*>(metalRoughImg.getDataPtr())[finalIndex + 2];
4✔
279
      static_cast<uint8_t*>(roughnessImg.getDataPtr())[i] = static_cast<const uint8_t*>(metalRoughImg.getDataPtr())[finalIndex + 1];
4✔
280
    } else {
281
      static_cast<float*>(metalnessImg.getDataPtr())[i] = static_cast<const float*>(metalRoughImg.getDataPtr())[finalIndex + 2];
×
282
      static_cast<float*>(roughnessImg.getDataPtr())[i] = static_cast<const float*>(metalRoughImg.getDataPtr())[finalIndex + 1];
×
283
    }
284
  }
285

286
  return { std::move(metalnessImg), std::move(roughnessImg) };
2✔
287
}
1✔
288

289
Image mergeImages(const Image& image1, const Image& image2) {
1✔
290
  if (image1.isEmpty())
1✔
291
    return image2;
×
292

293
  if (image2.isEmpty() || image1 == image2)
1✔
294
    return image1;
×
295

296
  if (image1.getWidth() != image2.getWidth()
1✔
297
   || image1.getHeight() != image2.getHeight()
1✔
298
   || image1.getDataType() != image2.getDataType()) {
2✔
299
    throw std::invalid_argument("[GltfLoad] The images' attributes need to be the same in order to be merged");
×
300
  }
301

302
  if (image1.getDataType() != ImageDataType::BYTE)
1✔
303
    throw std::invalid_argument("[GltfLoad] Images with a floating-point data type cannot be merged");
×
304

305
  // TODO: the channels to copy from each image should be definable
306
  const uint8_t totalChannelCount = image1.getChannelCount() + image2.getChannelCount();
1✔
307
  assert("Error: There shouldn't be only one channel to be merged." && totalChannelCount > 1);
1✔
308

309
  if (totalChannelCount > 4)
1✔
310
    throw std::invalid_argument("[GltfLoad] Too many channels to merge images into");
×
311

312
  const bool isSrgb = (image1.getColorspace() == ImageColorspace::SRGB || image2.getColorspace() == ImageColorspace::SRGB);
1✔
313
  const ImageColorspace colorspace = (totalChannelCount == 2 ? ImageColorspace::GRAY_ALPHA
2✔
314
                                   : (totalChannelCount == 3 ? ImageColorspace::RGB
2✔
315
                                   : (isSrgb                 ? ImageColorspace::SRGBA
1✔
316
                                                             : ImageColorspace::RGBA)));
317

318
  Image res(image1.getWidth(), image1.getHeight(), colorspace, image1.getDataType());
1✔
319

320
  for (unsigned int heightIndex = 0; heightIndex < image1.getHeight(); ++heightIndex) {
3✔
321
    for (unsigned int widthIndex = 0; widthIndex < image1.getWidth(); ++widthIndex) {
6✔
322
      for (uint8_t channelIndex = 0; channelIndex < image1.getChannelCount(); ++channelIndex)
16✔
323
        res.setByteValue(widthIndex, heightIndex, channelIndex, image1.recoverByteValue(widthIndex, heightIndex, channelIndex));
12✔
324

325
      for (uint8_t channelIndex = 0; channelIndex < image2.getChannelCount(); ++channelIndex)
8✔
326
        res.setByteValue(widthIndex, heightIndex, channelIndex + image1.getChannelCount(), image2.recoverByteValue(widthIndex, heightIndex, channelIndex));
4✔
327
    }
328
  }
329

330
  return res;
1✔
331
}
1✔
332

333
template <template <typename> typename OptionalT, typename TextureInfoT, typename FuncT>
334
void loadTexture(const OptionalT<TextureInfoT>& textureInfo,
22✔
335
                 const std::vector<fastgltf::Texture>& textures,
336
                 const std::vector<std::optional<Image>>& images,
337
                 const FuncT& callback) {
338
  static_assert(std::is_base_of_v<fastgltf::TextureInfo, TextureInfoT>);
339

340
  ZoneScopedN("[GltfLoad]::loadTexture");
341

342
  if (!textureInfo.has_value())
22✔
343
    return;
14✔
344

345
  const fastgltf::Optional<std::size_t>& imgIndex = textures[textureInfo->textureIndex].imageIndex;
8✔
346

347
  if (!imgIndex.has_value() || !images[*imgIndex].has_value())
8✔
348
    return;
×
349

350
  callback(*images[*imgIndex]);
8✔
351
}
352

353
void loadSheen(const fastgltf::MaterialSheen& matSheen,
1✔
354
               const std::vector<fastgltf::Texture>& textures,
355
               const std::vector<std::optional<Image>>& images,
356
               RenderShaderProgram& matProgram) {
357
  ZoneScopedN("[GltfLoad]::loadSheen");
358

359
  const Vec4f sheenFactors(matSheen.sheenColorFactor.x(), matSheen.sheenColorFactor.y(), matSheen.sheenColorFactor.z(), matSheen.sheenRoughnessFactor);
1✔
360
  matProgram.setAttribute(sheenFactors, MaterialAttribute::Sheen);
2✔
361

362
  if (!matSheen.sheenColorTexture.has_value() && !matSheen.sheenRoughnessTexture.has_value())
1✔
363
    return;
×
364

365
  // If the textures are the same, load either of them
366
  if (matSheen.sheenColorTexture && matSheen.sheenRoughnessTexture
2✔
367
   && matSheen.sheenColorTexture->textureIndex == matSheen.sheenRoughnessTexture->textureIndex) {
2✔
368
    loadTexture(matSheen.sheenColorTexture, textures, images, [&matProgram] (const Image& img) {
×
369
      matProgram.setTexture(Texture2D::create(img, true, true), MaterialTexture::Sheen);
×
370
    });
×
371

372
    return;
×
373
  }
374

375
  // If either only one texture is set or they are different, merge them
376
  Image sheenColorImg;
1✔
377
  Image sheenRoughnessImg;
1✔
378
  loadTexture(matSheen.sheenColorTexture, textures, images, [&sheenColorImg] (Image img) {
1✔
379
    sheenColorImg = std::move(img);
1✔
380
  });
1✔
381
  loadTexture(matSheen.sheenRoughnessTexture, textures, images, [&sheenRoughnessImg] (Image img) {
1✔
382
    sheenRoughnessImg = std::move(img);
1✔
383
  });
1✔
384
  matProgram.setTexture(Texture2D::create(mergeImages(sheenColorImg, sheenRoughnessImg), true, true), MaterialTexture::Sheen);
2✔
385
}
1✔
386

387
void loadMaterials(const std::vector<fastgltf::Material>& materials,
4✔
388
                   const std::vector<fastgltf::Texture>& textures,
389
                   const std::vector<std::optional<Image>>& images,
390
                   MeshRenderer& meshRenderer) {
391
  ZoneScopedN("[GltfLoad]::loadMaterials");
392

393
  Logger::debug("[GltfLoad] Loading {} material(s)...", materials.size());
4✔
394

395
  meshRenderer.getMaterials().clear();
4✔
396

397
  for (const fastgltf::Material& mat : materials) {
8✔
398
    Material& loadedMat = meshRenderer.addMaterial();
4✔
399
    RenderShaderProgram& matProgram = loadedMat.getProgram();
4✔
400

401
    matProgram.setAttribute(Vec3f(mat.pbrData.baseColorFactor[0],
8✔
402
                                  mat.pbrData.baseColorFactor[1],
403
                                  mat.pbrData.baseColorFactor[2]), MaterialAttribute::BaseColor);
404
    matProgram.setAttribute(Vec3f(mat.emissiveFactor[0],
16✔
405
                                  mat.emissiveFactor[1],
406
                                  mat.emissiveFactor[2]) * mat.emissiveStrength, MaterialAttribute::Emissive);
12✔
407
    matProgram.setAttribute(mat.pbrData.metallicFactor, MaterialAttribute::Metallic);
8✔
408
    matProgram.setAttribute(mat.pbrData.roughnessFactor, MaterialAttribute::Roughness);
8✔
409

410
    loadTexture(mat.pbrData.baseColorTexture, textures, images, [&matProgram] (const Image& img) {
4✔
411
      matProgram.setTexture(Texture2D::create(img, true, true), MaterialTexture::BaseColor);
4✔
412
    });
2✔
413

414
    loadTexture(mat.emissiveTexture, textures, images, [&matProgram] (const Image& img) {
4✔
415
      matProgram.setTexture(Texture2D::create(img, true, true), MaterialTexture::Emissive);
2✔
416
    });
1✔
417

418
    loadTexture(mat.occlusionTexture, textures, images, [&matProgram] (const Image& img) {
4✔
419
      const Image ambientOcclusionImg = extractAmbientOcclusionImage(img);
1✔
420
      matProgram.setTexture(Texture2D::create(ambientOcclusionImg), MaterialTexture::Ambient);
2✔
421
    });
1✔
422

423
    loadTexture(mat.normalTexture, textures, images, [&matProgram] (const Image& img) {
4✔
424
      matProgram.setTexture(Texture2D::create(img), MaterialTexture::Normal);
2✔
425
    });
1✔
426

427
    loadTexture(mat.pbrData.metallicRoughnessTexture, textures, images, [&matProgram] (const Image& img) {
4✔
428
      const auto [metalnessImg, roughnessImg] = extractMetalnessRoughnessImages(img);
1✔
429
      matProgram.setTexture(Texture2D::create(metalnessImg), MaterialTexture::Metallic);
2✔
430
      matProgram.setTexture(Texture2D::create(roughnessImg), MaterialTexture::Roughness);
2✔
431
    });
1✔
432

433
    if (mat.sheen)
4✔
434
      loadSheen(*mat.sheen, textures, images, matProgram);
1✔
435

436
    loadedMat.loadType(MaterialType::COOK_TORRANCE);
4✔
437
  }
438

439
  Logger::debug("[GltfLoad] Loaded material(s)");
4✔
440
}
4✔
441

442
} // namespace
443

444
std::pair<Mesh, MeshRenderer> load(const FilePath& filePath) {
4✔
445
  ZoneScopedN("GltfFormat::load");
446
  ZoneTextF("Path: %s", filePath.toUtf8().c_str());
447

448
  Logger::debug("[GltfLoad] Loading glTF file ('{}')...", filePath);
4✔
449

450
  if (!FileUtils::isReadable(filePath))
4✔
NEW
451
    throw std::invalid_argument(std::format("Error: The glTF file '{}' either does not exist or cannot be opened", filePath));
×
452

453
  fastgltf::Expected<fastgltf::GltfDataBuffer> data = fastgltf::GltfDataBuffer::FromPath(filePath.getPath());
4✔
454

455
  if (data.error() != fastgltf::Error::None)
4✔
456
    throw std::invalid_argument("Error: Could not load the glTF file.");
×
457

458
  const FilePath parentPath = filePath.recoverPathToFile();
4✔
459

460
  constexpr fastgltf::Extensions extensions = fastgltf::Extensions::KHR_materials_sheen;
4✔
461
  fastgltf::Parser parser(extensions);
4✔
462

463
  fastgltf::Expected<fastgltf::Asset> asset = parser.loadGltf(data.get(),
4✔
464
                                                              parentPath.getPath(),
4✔
465
                                                              fastgltf::Options::LoadExternalBuffers | fastgltf::Options::DecomposeNodeMatrices);
12✔
466

467
  if (asset.error() != fastgltf::Error::None)
4✔
NEW
468
    throw std::invalid_argument(std::format("Error: Failed to load glTF: {}", fastgltf::getErrorMessage(asset.error())));
×
469

470
  const std::vector<std::optional<Transform>> transforms = loadTransforms(asset->nodes, asset->meshes.size());
4✔
471
  auto [mesh, meshRenderer] = loadMeshes(asset.get(), transforms);
4✔
472

473
  const std::vector<std::optional<Image>> images = loadImages(asset->images, asset->buffers, asset->bufferViews, parentPath);
4✔
474
  loadMaterials(asset->materials, asset->textures, images, meshRenderer);
4✔
475

476
  Logger::debug("[GltfLoad] Loaded glTF file ({} submesh(es), {} vertices, {} triangles, {} material(s))",
4✔
477
                mesh.getSubmeshes().size(), mesh.recoverVertexCount(), mesh.recoverTriangleCount(), meshRenderer.getMaterials().size());
4✔
478

479
  return { std::move(mesh), std::move(meshRenderer) };
8✔
480
}
4✔
481

482
} // namespace Raz::GltfFormat
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc