GCC Code Coverage Report


Directory: ./
File: src/merger/wavefront_obj.cpp
Date: 2026-04-01 15:09:43
Exec Total Coverage
Lines: 0 77 0.0%
Functions: 0 2 0.0%
Branches: 0 126 0.0%

Line Branch Exec Source
1 #include <assimp/Importer.hpp> // C++ importer interface
2 #include <assimp/Exporter.hpp>
3 #include <assimp/scene.h> // Output data structure
4 #include <assimp/postprocess.h> // Post processing flags
5 #include <spdlog/spdlog.h>
6
7 #include "merger.hpp"
8
9 namespace assmpq::merger {
10
11 // NOLINTBEGIN(cppcoreguidelines-owning-memory, cppcoreguidelines-pro-bounds-pointer-arithmetic)
12
13 auto load_model(const std::string& path, assmpq::merger::MeshGroups& mesh_groups)-> bool
14 {
15 Assimp::Importer importer;
16
17 // Read the file. aiProcess_Triangulate ensures all faces are triangles.
18 // aiProcess_GenSmoothNormals generates smooth normals if not present.
19 const aiScene *scene_ptr = importer.ReadFile(path,
20 static_cast<unsigned>(aiProcess_Triangulate) |
21 static_cast<unsigned>(aiProcess_GenBoundingBoxes) |
22 static_cast<unsigned>(aiProcess_GenSmoothNormals) |
23 static_cast<unsigned>(aiProcess_JoinIdenticalVertices));
24
25 // Check for errors
26 if ((scene_ptr == nullptr) || ((scene_ptr->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0U) || scene_ptr->mRootNode == nullptr) {
27 spdlog::error("ERROR::ASSIMP::Importer: {}", importer.GetErrorString());
28 return false;
29 }
30
31 // Process all meshes in the scene
32 for (size_t i = 0; i < scene_ptr->mNumMeshes; ++i) {
33 const aiMesh* mesh_ptr = scene_ptr->mMeshes[i];
34
35 assmpq::merger::MeshData meshData;
36
37 // Process vertices
38 for (size_t j = 0; j < mesh_ptr->mNumVertices; ++j) {
39 meshData.vertices.emplace_back(mesh_ptr->mVertices[j].x, mesh_ptr->mVertices[j].y, mesh_ptr->mVertices[j].z);
40
41 // Process normals
42 if (mesh_ptr->HasNormals()) {
43 meshData.normals.emplace_back(mesh_ptr->mNormals[j].x, mesh_ptr->mNormals[j].y, mesh_ptr->mNormals[j].z);
44 }
45
46 // Process texture coordinates
47 if (mesh_ptr->HasTextureCoords(0)) { // Assuming one set of UVs
48 meshData.uvs.emplace_back(mesh_ptr->mTextureCoords[0][j].x, mesh_ptr->mTextureCoords[0][j].y);
49 }
50 }
51
52 // Process all faces
53 for (size_t j = 0; j < mesh_ptr->mNumFaces; ++j) {
54 aiFace face = mesh_ptr->mFaces[j];
55
56 if (face.mNumIndices != 3) {
57 spdlog::error("ERROR::ASSIMP::Importer::Face with {} indices not supported", face.mNumIndices);
58 return false;
59 }
60
61 meshData.faces.push_back(assmpq::merger::TriFace{
62 .i0 = face.mIndices[0], .i1 = face.mIndices[1], .i2 = face.mIndices[2]
63 });
64 }
65
66 meshData.aabb = mesh_ptr->mAABB;
67
68 mesh_groups.push_back(meshData);
69 }
70
71 return true;
72 }
73
74 auto save_model(const std::string& path, const MeshGroups& mesh_groups)-> bool
75 {
76 aiScene scene;
77 scene.mRootNode = new aiNode(); // Create a root node
78
79 scene.mRootNode->mNumMeshes = static_cast<unsigned>(mesh_groups.size());
80 scene.mRootNode->mMeshes = new unsigned int[mesh_groups.size()];
81
82 scene.mNumMeshes = static_cast<unsigned>(mesh_groups.size());
83 scene.mMeshes = new aiMesh*[mesh_groups.size()];
84
85 scene.mNumMaterials = static_cast<unsigned>(mesh_groups.size());
86 scene.mMaterials = new aiMaterial*[mesh_groups.size()];
87 for (size_t i = 0; i < scene.mNumMaterials; ++i) {
88 scene.mMaterials[i] = new aiMaterial;
89 }
90
91 for(size_t i = 0; i < mesh_groups.size(); ++i) {
92 const MeshData& meshData = mesh_groups[i];
93
94 aiMesh* mesh = scene.mMeshes[i] = new aiMesh();
95 mesh->mName = meshData.name;
96 mesh->mMaterialIndex = static_cast<unsigned>(i);
97
98 // Process vertices
99 mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
100 mesh->mNumVertices = static_cast<unsigned>(meshData.vertices.size());
101 mesh->mVertices = new aiVector3D[meshData.vertices.size()];
102
103 for(size_t j = 0; j < meshData.vertices.size(); ++j) {
104 mesh->mVertices[j] = meshData.vertices[j];
105 }
106
107 // Process normals
108 if(!meshData.normals.empty()) {
109 mesh->mNormals = new aiVector3D[meshData.vertices.size()];
110 for(size_t j = 0; j < meshData.vertices.size(); ++j) {
111 mesh->mNormals[j] = meshData.normals[j];
112 }
113 } else {
114 mesh->mNormals = nullptr;
115 }
116
117 // Process texture coordinates
118 if(!meshData.uvs.empty()) {
119 mesh->mNumUVComponents[0] = 2;
120 mesh->mTextureCoords[0] = new aiVector3D[meshData.vertices.size()];
121 for(size_t j = 0; j < meshData.vertices.size(); ++j) {
122 mesh->mTextureCoords[0][j] = aiVector3D(meshData.uvs[j].x, meshData.uvs[j].y, 0.0F);
123 }
124 } else {
125 mesh->mTextureCoords[0] = nullptr;
126 }
127
128 // Process faces
129 const size_t numFaces = meshData.faces.size(); // triangles only
130 mesh->mNumFaces = static_cast<unsigned>(numFaces);
131 mesh->mFaces = new aiFace[numFaces];
132
133 for(size_t j = 0; j < numFaces; ++j) {
134 mesh->mFaces[j].mNumIndices = 3;
135 mesh->mFaces[j].mIndices = new unsigned int[3];
136 mesh->mFaces[j].mIndices[0] = meshData.faces[j].i0;
137 mesh->mFaces[j].mIndices[1] = meshData.faces[j].i1;
138 mesh->mFaces[j].mIndices[2] = meshData.faces[j].i2;
139 }
140
141 // Link mesh to root node
142 scene.mRootNode->mMeshes[i] = static_cast<unsigned>(i); // Index of the mesh in scene.mMeshes
143 }
144
145 // Write the scene to a file
146 Assimp::Exporter exporter;
147 const aiReturn ret = exporter.Export(&scene, "obj", path);
148 if(ret == aiReturn_SUCCESS) {
149 return true;
150 } else {
151 spdlog::error("ERROR::ASSIMP::Exporter: {}", exporter.GetErrorString());
152 }
153
154 return false;
155 }
156
157 // NOLINTEND(cppcoreguidelines-owning-memory, cppcoreguidelines-pro-bounds-pointer-arithmetic)
158
159 } // namespace assmpq::merger
160