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