• 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

98.04
/src/RaZ/Data/ObjSave.cpp
1
#include "RaZ/Data/Image.hpp"
2
#include "RaZ/Data/ImageFormat.hpp"
3
#include "RaZ/Data/Mesh.hpp"
4
#include "RaZ/Data/ObjFormat.hpp"
5
#include "RaZ/Render/Material.hpp"
6
#include "RaZ/Render/MeshRenderer.hpp"
7
#include "RaZ/Utils/FilePath.hpp"
8
#include "RaZ/Utils/Logger.hpp"
9

10
#include "tracy/Tracy.hpp"
11

12
#include <fstream>
13
#include <map>
14

15
namespace Raz::ObjFormat {
16

17
namespace {
18

19
template <typename T, std::size_t Size = 1>
20
inline void writeAttribute(std::ofstream& file, std::string_view tag, const RenderShaderProgram& program, std::string_view uniformName) {
16✔
21
  if (!program.hasAttribute(uniformName.data()))
48✔
22
    return;
6✔
23

24
  file << '\t' << tag;
10✔
25

26
  if constexpr (Size == 1) {
27
    file << ' ' << program.getAttribute<T>(uniformName.data());
6✔
28
  } else {
29
    for (const T& value : program.getAttribute<Vector<T, Size>>(uniformName.data()).getData())
43✔
30
      file << ' ' << value;
22✔
31
  }
32

33
  file << '\n';
10✔
34
}
35

36
inline void writeTexture(std::ofstream& file, std::string_view tag, const std::string& materialName, std::string_view suffix,
20✔
37
                         const RenderShaderProgram& program, std::string_view uniformName) {
38
  ZoneScopedN("[ObjSave]::writeTexture");
39

40
  if (!program.hasTexture(uniformName.data()))
60✔
41
    return;
7✔
42

43
  const auto* texture = dynamic_cast<const Texture2D*>(&program.getTexture(uniformName.data()));
26✔
44

45
  if (texture == nullptr || texture->getWidth() == 0 || texture->getHeight() == 0 || texture->getColorspace() == TextureColorspace::INVALID)
13✔
46
    return;
×
47

48
  const std::string texturePath = materialName + '_' + suffix + ".png";
13✔
49

50
  file << '\t' << tag << ' ' << texturePath << '\n';
13✔
51
  ImageFormat::save(texturePath, texture->recoverImage(), true);
13✔
52
}
13✔
53

54
void saveMtl(const FilePath& mtlFilePath, const std::vector<Material>& materials) {
1✔
55
  ZoneScopedN("[ObjSave]::saveMtl");
56
  ZoneTextF("Path: %s", mtlFilePath.toUtf8().c_str());
57

58
  std::ofstream mtlFile(mtlFilePath, std::ios_base::binary);
1✔
59

60
  mtlFile << "# MTL file created with RaZ - https://github.com/Razakhel/RaZ\n";
1✔
61

62
  const std::string mtlFileName = mtlFilePath.recoverFileName(false).toUtf8();
1✔
63

64
  for (std::size_t matIndex = 0; matIndex < materials.size(); ++matIndex) {
3✔
65
    const RenderShaderProgram& matProgram = materials[matIndex].getProgram();
2✔
66
    const std::string materialName        = mtlFileName + '_' + std::to_string(matIndex);
2✔
67

68
    mtlFile << "\nnewmtl " << materialName << '\n';
2✔
69

70
    writeAttribute<float, 3>(mtlFile, "Kd", matProgram, MaterialAttribute::BaseColor);
2✔
71
    writeAttribute<float, 3>(mtlFile, "Ke", matProgram, MaterialAttribute::Emissive);
2✔
72
    writeAttribute<float, 3>(mtlFile, "Ka", matProgram, MaterialAttribute::Ambient);
2✔
73
    writeAttribute<float, 3>(mtlFile, "Ks", matProgram, MaterialAttribute::Specular);
2✔
74
    writeAttribute<float, 1>(mtlFile, "d",  matProgram, MaterialAttribute::Opacity);
2✔
75
    writeAttribute<float, 1>(mtlFile, "Pm", matProgram, MaterialAttribute::Metallic);
2✔
76
    writeAttribute<float, 1>(mtlFile, "Pr", matProgram, MaterialAttribute::Roughness);
2✔
77
    writeAttribute<float, 4>(mtlFile, "Ps", matProgram, MaterialAttribute::Sheen);
2✔
78

79
#if !defined(USE_OPENGL_ES)
80
    writeTexture(mtlFile, "map_Kd",   materialName, "baseColor", matProgram, MaterialTexture::BaseColor);
2✔
81
    writeTexture(mtlFile, "map_Ke",   materialName, "emissive",  matProgram, MaterialTexture::Emissive);
2✔
82
    writeTexture(mtlFile, "map_Ka",   materialName, "ambient",   matProgram, MaterialTexture::Ambient);
2✔
83
    writeTexture(mtlFile, "map_Ks",   materialName, "specular",  matProgram, MaterialTexture::Specular);
2✔
84
    writeTexture(mtlFile, "map_d",    materialName, "opacity",   matProgram, MaterialTexture::Opacity);
2✔
85
    writeTexture(mtlFile, "map_bump", materialName, "bump",      matProgram, MaterialTexture::Bump);
2✔
86
    writeTexture(mtlFile, "norm",     materialName, "normal",    matProgram, MaterialTexture::Normal);
2✔
87
    writeTexture(mtlFile, "map_Pm",   materialName, "metallic",  matProgram, MaterialTexture::Metallic);
2✔
88
    writeTexture(mtlFile, "map_Pr",   materialName, "roughness", matProgram, MaterialTexture::Roughness);
2✔
89
    writeTexture(mtlFile, "map_Ps",   materialName, "sheen",     matProgram, MaterialTexture::Sheen);
2✔
90
#endif
91
  }
2✔
92
}
1✔
93

94
} // namespace
95

96
void save(const FilePath& filePath, const Mesh& mesh, const MeshRenderer* meshRenderer) {
4✔
97
  ZoneScopedN("ObjFormat::save");
98
  ZoneTextF("Path: %s", filePath.toUtf8().c_str());
99

100
  Logger::debug("[ObjSave] Saving OBJ file ('{}')...", filePath);
4✔
101

102
  std::ofstream file(filePath, std::ios_base::binary);
4✔
103

104
  if (!file)
4✔
NEW
105
    throw std::invalid_argument(std::format("Error: Unable to create an OBJ file as '{}'; path to file must exist", filePath));
×
106

107
  file << "# OBJ file created with RaZ - https://github.com/Razakhel/RaZ\n\n";
4✔
108

109
  if (meshRenderer && !meshRenderer->getMaterials().empty()) {
4✔
110
    const std::string mtlFileName = filePath.recoverFileName(false) + ".mtl";
1✔
111
    const FilePath mtlFilePath    = filePath.recoverPathToFile() + mtlFileName;
1✔
112

113
    file << "mtllib " << mtlFilePath << "\n\n";
1✔
114

115
    saveMtl(mtlFilePath, meshRenderer->getMaterials());
1✔
116
  }
1✔
117

118
  std::map<Vec3f, std::size_t> posCorrespIndices;
4✔
119
  std::map<Vec2f, std::size_t> texCorrespIndices;
4✔
120
  std::map<Vec3f, std::size_t> normCorrespIndices;
4✔
121

122
  for (const Submesh& submesh : mesh.getSubmeshes()) {
10✔
123
    for (const Vertex& vertex : submesh.getVertices()) {
66✔
124
      if (posCorrespIndices.find(vertex.position) == posCorrespIndices.cend()) {
60✔
125
        file << "v " << vertex.position.x() << ' '
28✔
126
                     << vertex.position.y() << ' '
28✔
127
                     << vertex.position.z() << '\n';
28✔
128
        posCorrespIndices.emplace(vertex.position, posCorrespIndices.size() + 1);
28✔
129
      }
130

131
      if (texCorrespIndices.find(vertex.texcoords) == texCorrespIndices.cend()) {
60✔
132
        file << "vt " << vertex.texcoords.x() << ' '
20✔
133
                      << vertex.texcoords.y() << '\n';
20✔
134
        texCorrespIndices.emplace(vertex.texcoords, texCorrespIndices.size() + 1);
20✔
135
      }
136

137
      if (normCorrespIndices.find(vertex.normal) == normCorrespIndices.cend()) {
60✔
138
        file << "vn " << vertex.normal.x() << ' '
18✔
139
                      << vertex.normal.y() << ' '
18✔
140
                      << vertex.normal.z() << '\n';
18✔
141
        normCorrespIndices.emplace(vertex.normal, normCorrespIndices.size() + 1);
18✔
142
      }
143
    }
144
  }
145

146
  const std::string fileName = filePath.recoverFileName(false).toUtf8();
4✔
147

148
  for (std::size_t submeshIndex = 0; submeshIndex < mesh.getSubmeshes().size(); ++submeshIndex) {
10✔
149
    const Submesh& submesh = mesh.getSubmeshes()[submeshIndex];
6✔
150

151
    file << "\no " << fileName << '_' << submeshIndex << '\n';
6✔
152

153
    if (meshRenderer && !meshRenderer->getMaterials().empty())
6✔
154
      file << "usemtl " << fileName << '_' << meshRenderer->getSubmeshRenderers()[submeshIndex].getMaterialIndex() << '\n';
2✔
155

156
    for (std::size_t i = 0; i < submesh.getTriangleIndexCount(); i += 3) {
34✔
157
      file << "f ";
28✔
158

159
      // First vertex
160
      Vertex vertex = submesh.getVertices()[submesh.getTriangleIndices()[i]];
28✔
161

162
      auto posIndex  = posCorrespIndices.find(vertex.position)->second;
28✔
163
      auto texIndex  = texCorrespIndices.find(vertex.texcoords)->second;
28✔
164
      auto normIndex = normCorrespIndices.find(vertex.normal)->second;
28✔
165

166
      file << posIndex  << '/' << texIndex  << '/' << normIndex << ' ';
28✔
167

168
      // Second vertex
169
      vertex = submesh.getVertices()[submesh.getTriangleIndices()[i + 1]];
28✔
170

171
      posIndex  = posCorrespIndices.find(vertex.position)->second;
28✔
172
      texIndex  = texCorrespIndices.find(vertex.texcoords)->second;
28✔
173
      normIndex = normCorrespIndices.find(vertex.normal)->second;
28✔
174

175
      file << posIndex  << '/' << texIndex  << '/' << normIndex << ' ';
28✔
176

177
      // Third vertex
178
      vertex = submesh.getVertices()[submesh.getTriangleIndices()[i + 2]];
28✔
179

180
      posIndex  = posCorrespIndices.find(vertex.position)->second;
28✔
181
      texIndex  = texCorrespIndices.find(vertex.texcoords)->second;
28✔
182
      normIndex = normCorrespIndices.find(vertex.normal)->second;
28✔
183

184
      file << posIndex  << '/' << texIndex  << '/' << normIndex << '\n';
28✔
185
    }
186
  }
187

188
  Logger::debug("[ObjSave] Saved OBJ file");
4✔
189
}
4✔
190

191
} // namespace Raz::ObjFormat
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