GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 145, 0 excluded
0.0%
Functions:
0 of 12, 0 excluded
0.0%
Branches:
0 of 222, 0 excluded
0.0%

libs/io/src/eu/io/mesh.cc
Line Branch Exec Source
1 #include "eu/io/mesh.h"
2
3 #include "eu/core/geom.builder.h"
4
5 #include "eu/io/file.h"
6
7 #include "cgltf.h"
8
9 namespace eu::io
10 {
11
12 struct GltfFile
13 {
14 cgltf_data* data = nullptr;
15
16 void clear()
17 {
18 if (data == nullptr)
19 {
20 return;
21 }
22
23 cgltf_free(data);
24 data = nullptr;
25 }
26
27 bool load_gltf_file(const std::string& path)
28 {
29 cgltf_options options = {};
30 data = nullptr;
31
32 // const auto bytes = bytes_from_file(path);
33
34 cgltf_result result = cgltf_parse_file(&options, path.c_str(), &data);
35 // cgltf_result result = cgltf_parse(&options, bytes.data(), bytes.size(), &data);
36 if (result != cgltf_result_success)
37 {
38 clear();
39 LOG_ERR("Could not parse file: {}", path);
40 return false;
41 }
42
43 result = cgltf_load_buffers(&options, data, path.c_str());
44 if (result != cgltf_result_success)
45 {
46 clear();
47 LOG_ERR("Could not load buffers for: {}", path);
48 return false;
49 }
50
51 result = cgltf_validate(data);
52 if (result != cgltf_result_success)
53 {
54 clear();
55 LOG_ERR("GLTF file validation FAILED: {}", path);
56 return false;
57 }
58 return true;
59 }
60 };
61
62
63 std::optional<std::size_t> find_node_index(
64 cgltf_node* target, cgltf_node* allNodes, std::size_t numNodes
65 )
66 {
67 if (target == 0)
68 {
69 return std::nullopt;
70 }
71 for (std::size_t i = 0; i < numNodes; ++i)
72 {
73 if (target == &allNodes[i])
74 {
75 return i;
76 }
77 }
78 return std::nullopt;
79 }
80
81 std::vector<float> scalar_values_from_accessor(
82 std::size_t component_count, const cgltf_accessor& accessor
83 )
84 {
85 std::vector<float> outScalars;
86 outScalars.resize(accessor.count * component_count);
87
88 for (cgltf_size i = 0; i < accessor.count; ++i)
89 {
90 cgltf_accessor_read_float(&accessor, i, &outScalars[i * component_count], component_count);
91 }
92 return outScalars;
93 }
94
95 void extract_mesh_from_attribute(
96 core::geom::Builder& outMesh,
97 cgltf_attribute& attribute,
98 cgltf_skin* skin,
99 cgltf_node* nodes,
100 unsigned int nodeCount
101 )
102 {
103 cgltf_attribute_type attribType = attribute.type;
104 cgltf_accessor& accessor = *attribute.data;
105
106 unsigned int componentCount = 0;
107 if (accessor.type == cgltf_type_vec2)
108 {
109 componentCount = 2;
110 }
111 else if (accessor.type == cgltf_type_vec3)
112 {
113 componentCount = 3;
114 }
115 else if (accessor.type == cgltf_type_vec4)
116 {
117 componentCount = 4;
118 }
119 const auto values = scalar_values_from_accessor(componentCount, accessor);
120 const auto acessorCount = accessor.count;
121
122 for (std::size_t i = 0; i < acessorCount; ++i)
123 {
124 const auto index = i * componentCount;
125 switch (attribType)
126 {
127 case cgltf_attribute_type_position:
128 outMesh.add_position(v3(values[index + 0], values[index + 1], values[index + 2])
129 );
130 break;
131 case cgltf_attribute_type_texcoord:
132 // note: eu 2d coordinate system is bottom left going up, gltf is top left going down, `1-y` fixes this
133 outMesh.add_text_coord(v2(values[index + 0], 1-values[index + 1]));
134 break;
135 case cgltf_attribute_type_weights:
136 outMesh.add_weight(v4(values[index + 0], values[index + 1], values[index + 2], values[index + 3]));
137 break;
138 case cgltf_attribute_type_normal:
139 {
140 const auto normal = v3(values[index + 0], values[index + 1], values[index + 2]);
141 outMesh.add_normal(normal.get_normalized().value_or(kk::up));
142 }
143 break;
144 case cgltf_attribute_type_joints:
145 {
146 if (skin != nullptr)
147 {
148 const auto joint_at = [&](std::size_t ii) -> std::size_t
149 {
150 const auto found = static_cast<int>(values[index + ii] + 0.5f);
151 const auto ret = static_cast<int>(std::max<std::size_t>(
152 0, find_node_index(skin->joints[found], nodes, nodeCount).value_or(0)
153 ));
154 return ret;
155 };
156 outMesh.add_influence({joint_at(0), joint_at(1), joint_at(2), joint_at(3)});
157 }
158 else
159 {
160 LOG_WARN("Mesh has joints but is missing skin");
161 }
162 }
163 break;
164 default:
165 //ignore
166 break;
167 }
168 }
169 }
170
171 core::geom::Vertex vert(std::size_t index)
172 {
173 return core::geom::Vertex{index, index};
174 }
175
176 core::Geom create_geom_from_gltf_primitive(const cgltf_data& data, const cgltf_node& node, const cgltf_primitive& prim)
177 {
178 core::geom::Builder mesh;
179
180 cgltf_node* nodes = data.nodes;
181 const auto nodeCount = data.nodes_count;
182
183 const auto numAttributes = prim.attributes_count;
184 for (unsigned int k = 0; k < numAttributes; ++k)
185 {
186 cgltf_attribute* attribute = &prim.attributes[k];
187 extract_mesh_from_attribute(
188 mesh, *attribute, node.skin, nodes, static_cast<unsigned int>(nodeCount)
189 );
190 }
191 if (prim.indices != nullptr)
192 {
193 mesh.faces.reserve(prim.indices->count / 3);
194 const auto indexCount = prim.indices->count;
195
196 for (std::size_t k = 0; k < indexCount; k += 3)
197 {
198 const auto a1 = cgltf_accessor_read_index(prim.indices, k);
199 const auto b2 = cgltf_accessor_read_index(prim.indices, k + 1);
200 const auto c3 = cgltf_accessor_read_index(prim.indices, k + 2);
201 mesh.add_face({ vert(a1), vert(b2), vert(c3) });
202 }
203 }
204
205 return mesh.to_geom();
206 }
207
208 m4 transform_from_node_rec(const cgltf_node& node)
209 {
210 const m4 parent = node.parent ? transform_from_node_rec(*node.parent) : m4_identity;
211 const m4 translate = node.has_translation ? m4::from_translation(v3(node.translation)) : m4_identity;
212 const m4 rotate = node.has_rotation ? m4::from(Q(node.rotation)).value_or(m4_identity) : m4_identity;
213 const m4 scale = node.has_scale ? m4::from_scale(v3(node.scale)) : m4_identity;
214
215 const auto transform = translate * rotate * scale;
216 return parent * transform;
217 }
218
219 core::Mesh extract_meshes_from_gltf(const cgltf_data& data, const std::string& file)
220 {
221 core::Mesh result;
222
223 for (std::size_t i = 0; i < data.nodes_count; ++i)
224 {
225 const cgltf_node& node = data.nodes[i];
226 if (node.mesh == nullptr)
227 {
228 continue;
229 }
230
231 core::TransformedMesh transform;
232 transform.name = node.mesh->name != nullptr ? node.mesh->name : "unnamed_mesh";
233 transform.transform = transform_from_node_rec(node);
234
235 for (std::size_t primtive_index = 0; primtive_index < node.mesh->primitives_count; ++primtive_index)
236 {
237 const auto mesh = create_geom_from_gltf_primitive(data, node, node.mesh->primitives[primtive_index]);
238 if (mesh.faces.empty() || mesh.vertices.empty())
239 {
240 LOG_WARN("Mesh has no mesh data in gltf file: {}", file);
241 }
242 else
243 {
244 transform.geoms.emplace_back(mesh);
245 }
246 }
247
248 result.meshes.emplace_back(std::move(transform));
249 }
250
251 return result;
252 }
253
254 core::Mesh unable_to_load_geom()
255 {
256 return
257 {
258 .meshes = {
259 core::TransformedMesh
260 {
261 .name = "unable_to_load_geom",
262 .transform = m4_identity,
263 .geoms = {
264 core::MeshGeom
265 {
266 .geom = eu::core::geom::create_box(1.0f, 1.0f, 1.0f, core::geom::NormalsFacing::Out).to_geom()
267 }
268 }
269 }
270 }
271 };
272 }
273
274 core::Mesh mesh_from_file(const std::string& file)
275 {
276 GltfFile gltf;
277 if (gltf.load_gltf_file(file) == false)
278 {
279 LOG_ERR("Failed to load gltf file: {}", file);
280 return unable_to_load_geom();
281 }
282
283 const auto ret = extract_meshes_from_gltf(*gltf.data, file);
284 if (ret.meshes.empty())
285 {
286 LOG_ERR("No meshes found in gltf file: {}", file);
287 return unable_to_load_geom();
288 }
289
290 return ret;
291 }
292
293 }
294
295