GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 284, 0 excluded
0.0%
Functions:
0 of 41, 0 excluded
0.0%
Branches:
0 of 338, 0 excluded
0.0%

libs/core/src/eu/core/geom.builder.cc
Line Branch Exec Source
1 #include "eu/core/geom.h"
2
3 #include "eu/assert/assert.h"
4 #include "eu/base/cint.h"
5 #include "eu/core/hash.h"
6
7 #include "eu/core/geom.builder.h"
8
9 #include <fstream>
10 #include <map>
11
12 namespace eu::core::geom
13 {
14
15 namespace
16 {
17 struct Combo
18 {
19 Index position;
20 Index texture;
21 Index normal;
22 Index color;
23
24 ✗ explicit Combo(const Vertex& v)
25 ✗ : position(v.position)
26 ✗ , texture(v.texture)
27 ✗ , normal(v.normal)
28 ✗ , color(v.color)
29 {
30 ✗ }
31 };
32
33 ✗ bool operator==(const Combo& lhs, const Combo& rhs)
34 {
35 ✗ return lhs.position == rhs.position && lhs.texture == rhs.texture && lhs.normal == rhs.normal
36 ✗ && lhs.color == rhs.color;
37 }
38 }
39
40 } // namespace eu::core::geom
41
42 ✗ HASH_DEF_BEGIN(eu::core::geom::Combo)
43 ✗ HASH_DEF(position)
44 ✗ HASH_DEF(texture)
45 ✗ HASH_DEF(normal)
46 ✗ HASH_DEF(color)
47 ✗ HASH_DEF_END()
48
49 namespace eu::core::geom
50 {
51
52 ✗ Vertex::Vertex(Index pnt, Index clr)
53 ✗ : position(pnt)
54 ✗ , normal(pnt)
55 ✗ , texture(pnt)
56 ✗ , color(clr)
57 {
58 ✗ }
59
60 ✗ Vertex::Vertex(Index a_position, Index a_normal, Index a_texture, Index a_color)
61 ✗ : position(a_position)
62 ✗ , normal(a_normal)
63 ✗ , texture(a_texture)
64 ✗ , color(a_color)
65 {
66 ✗ }
67
68 ✗ Triangle::Triangle(const Vertex& a, const Vertex& b, const Vertex& c)
69 ✗ : v0(a)
70 ✗ , v1(b)
71 ✗ , v2(c)
72 {
73 ✗ }
74
75 // ==================================================================================================================================
76
77 namespace
78 {
79 template<typename T>
80 ✗ Index add_vec(std::vector<T>* v, T t)
81 {
82 ✗ v->emplace_back(t);
83 ✗ return v->size() - 1;
84 }
85
86 ✗ float get_distance_squared(const v3& lhs, const v3& rhs)
87 {
88 ✗ return v3::from_to(lhs, rhs).get_length_squared();
89 }
90 ✗ float get_distance_squared(const v2& lhs, const v2& rhs)
91 {
92 ✗ return v2::from_to(lhs, rhs).get_length_squared();
93 }
94
95 template<typename T>
96 ✗ std::optional<Index> find_vec(const std::vector<T>& v, T t, float diff)
97 {
98 ✗ const auto d2 = diff * diff;
99 ✗ Index index = 0;
100 ✗ for (const auto& r: v)
101 {
102 ✗ if (get_distance_squared(r, t) < d2)
103 {
104 ✗ return index;
105 }
106 ✗ index += 1;
107 }
108
109 ✗ return std::nullopt;
110 }
111
112 template<typename T>
113 ✗ Index find_or_add_vec(std::vector<T>* v, T t, float diff)
114 {
115 ✗ const auto found = find_vec(*v, t, diff);
116 ✗ if (found)
117 {
118 ✗ return *found;
119 }
120
121 ✗ return add_vec(v, t);
122 }
123 } // namespace
124
125 ✗ Index Builder::add_text_coord(const v2& tc)
126 {
127 ✗ return add_vec(&texcoords, tc);
128 }
129
130 ✗ Index Builder::add_position(const v3& pos)
131 {
132 ✗ return add_vec(&positions, pos);
133 }
134
135 ✗ Index Builder::add_normal(const n3& norm)
136 {
137 ✗ return add_vec(&normals, norm);
138 }
139
140 ✗ Index Builder::add_color(const Lin_rgb& c)
141 {
142 ✗ return add_vec(&lin_colors, { c.r, c.g, c.b });
143 }
144
145 ✗ Builder& Builder::add_triangle(const Triangle& t)
146 {
147 ✗ add_face(std::vector{ t.v0, t.v1, t.v2 });
148 ✗ return *this;
149 }
150
151 ✗ void Builder::add_influence(const Influence4& influence)
152 {
153 ✗ influences.emplace_back(influence);
154 ✗ }
155
156 ✗ Index Builder::foa_text_coord(const v2& v, float max_diff)
157 {
158 ✗ return find_or_add_vec(&texcoords, v, max_diff);
159 }
160
161 ✗ Index Builder::foa_position(const v3& pos, float max_diff)
162 {
163 ✗ return find_or_add_vec(&positions, pos, max_diff);
164 }
165
166 ✗ Index Builder::foa_normal(const n3& norm, float max_diff)
167 {
168 ✗ return find_or_add_vec(&normals, norm, max_diff);
169 }
170
171 ✗ Index Builder::foa_color(const Lin_rgb& c, float max_diff)
172 {
173 ✗ return find_or_add_vec(&lin_colors, { c.r, c.g, c.b }, max_diff);
174 }
175
176
177 ✗ Builder& Builder::add_quad(bool ccw, const Vertex& v0, const Vertex& v1, const Vertex& v2, const Vertex& v3)
178 {
179 ✗ if (ccw)
180 {
181 // add counter clock wise
182 ✗ add_face({v0, v1, v2, v3});
183 }
184 else
185 {
186 // add clock wise
187 ✗ add_face({v0, v3, v2, v1});
188 }
189
190 ✗ return *this;
191 }
192
193 ✗ Builder& Builder::move(const v3& dir)
194 {
195 ✗ for (v3& p: positions)
196 {
197 ✗ p += dir;
198 }
199
200 ✗ return *this;
201 }
202
203 ✗ Builder& Builder::scale(float scale)
204 {
205 ✗ for (v3& p: positions)
206 {
207 ✗ p *= scale;
208 }
209
210 ✗ return *this;
211 }
212
213 ✗ Builder& Builder::invert_normals()
214 {
215 ✗ for (v3& p: normals)
216 {
217 ✗ p = -p;
218 }
219
220 ✗ return *this;
221 }
222
223 ✗ Builder& Builder::add_face(const std::vector<Vertex>& vertices)
224 {
225 ✗ ASSERT(vertices.size() >= 3);
226 ✗ faces.emplace_back(vertices);
227 ✗ return *this;
228 }
229
230 ✗ void Builder::add_weight(const v4& weight)
231 {
232 ✗ weights.emplace_back(weight);
233 ✗ }
234
235 ✗ Geom Builder::to_geom() const
236 {
237 ✗ std::unordered_map<Combo, u32> combinations;
238
239 // skeleton warning
240 {
241 ✗ const auto has_influences = influences.empty() == false;
242 ✗ const auto has_weights = weights.empty() == false;
243
244 ✗ const std::string influence_message = has_influences ? "has influences" : "has no influences";
245 ✗ const std::string weight_message = has_weights ? "has weights" : "has no weights";
246
247 ✗ if (has_influences || has_weights)
248 {
249 ✗ LOG_WARN("Mesh has skeleton data, but it is currently ignored when converting to Geom. The mesh {} and {}.", influence_message, weight_message);
250 }
251 ✗ }
252
253 // foreach triangle
254 ✗ std::vector<eu::core::Vertex> final_vertices;
255 ✗ std::vector<eu::core::Face> final_tris;
256
257 ✗ auto convert_vert = [&](const Vertex& vert) -> u32
258 {
259 ✗ const Combo c(vert);
260 ✗ auto result = combinations.find(c);
261 ✗ if (result != combinations.end())
262 {
263 ✗ return result->second;
264 }
265 else
266 {
267 ✗ constexpr float default_gamma = 1.0f;
268 ✗ const auto missing_color = linear_from_srgb(colors::white, default_gamma);
269
270 ✗ const v3 pos = positions[c.position];
271 ✗ const v2 text = texcoords.empty() ? v2(0, 0) : texcoords[c.texture];
272 ✗ const v3 col = lin_colors.empty() ? v3{missing_color.r, missing_color.g, missing_color.b} : lin_colors[c.color];
273 ✗ const n3 normal = normals.empty() == false ? normals[c.normal] : kk::up;
274 ✗ const auto ind = final_vertices.size();
275 ✗ final_vertices.emplace_back(core::Vertex{.position = pos, .normal = normal, .uv = text, .color = col});
276 ✗ combinations.insert({c, u32_from_sizet(ind)});
277 ✗ return u32_from_sizet(ind);
278 }
279 ✗ };
280
281 ✗ for (const auto& src_face: faces)
282 {
283 ✗ const auto v0 = convert_vert(src_face[0]);
284
285 // for a quad (4 vertices):
286 // (start) (index) (index+1)
287 // 0 1 2
288 // 0 2 3
289 // ----------------------------
290 // 0 3 4 (error)
291 ✗ for (std::size_t triangle_base = 1; triangle_base < src_face.size()-1; triangle_base += 1)
292 {
293 ✗ const auto v1 = convert_vert(src_face[triangle_base]);
294 ✗ const auto v2 = convert_vert(src_face[triangle_base+1]);
295
296 // add triangle to geom
297 ✗ final_tris.emplace_back(Face{.a = v0, .b = v1, .c = v2});
298 }
299 }
300
301 return {
302 ✗ .vertices = std::move(final_vertices),
303 ✗ .faces = std::move(final_tris)
304 ✗ };
305 ✗ }
306
307 ✗ Builder& Builder::write_obj(const std::string& path)
308 {
309 ✗ std::ofstream f(path.c_str());
310 ✗ if (f.good() == false)
311 {
312 ✗ DIE("Failed to create file");
313 ✗ return *this;
314 }
315
316 ✗ f << "# Vertices\n";
317 ✗ for (const auto& p: positions)
318 {
319 ✗ f << "v " << p.x << " " << p.y << " " << p.z << '\n';
320 }
321 ✗ f << '\n';
322
323 ✗ f << "# Normals\n";
324 ✗ for (const auto& p: normals)
325 {
326 ✗ f << "vn " << p.x << " " << p.y << " " << p.z << '\n';
327 }
328 ✗ f << '\n';
329
330 ✗ f << "# Texcoords\n";
331 ✗ for (const auto& p: texcoords)
332 {
333 ✗ f << "vt " << p.x << " " << p.y << '\n';
334 }
335 ✗ f << '\n';
336
337 ✗ f << "# Triangles\n";
338 ✗ for (const auto& face: faces)
339 {
340 ✗ f << "f";
341 ✗ for (const auto& v: face)
342 {
343 ✗ f << ' ' << (v.position + 1) << '/' << (v.texture + 1) << '/' << (v.normal + 1);
344 }
345 ✗ f << '\n';
346 }
347 ✗ f << '\n';
348
349 ✗ return *this;
350 ✗ }
351
352 // ==================================================================================================================================
353
354 // todo(Gustav): make this a default argument...?
355 /// we assume that when building a mesh, this is the gamma they use for colors.
356 constexpr float artist_gamma = 2.2f;
357 namespace
358 {
359 // position and texture coord struct
360 struct Pt
361 {
362 v3 pos;
363 v2 tex;
364 };
365
366 ✗ void add_quad_to_builder(Builder& b, bool invert, const Rgb& color, n3 normal, const std::array<Pt, 4>& p)
367 {
368 ✗ constexpr float pd = 0.1f;
369 ✗ constexpr float td = 0.01f;
370 ✗ const auto ci = b.foa_color(linear_from_srgb(color, artist_gamma), 0.001f);
371 ✗ const auto no = b.add_normal(normal);
372
373 ✗ const auto v0 = Vertex{b.foa_position(p[0].pos, pd), no, b.foa_text_coord(p[0].tex, td), ci};
374 ✗ const auto v1 = Vertex{b.foa_position(p[1].pos, pd), no, b.foa_text_coord(p[1].tex, td), ci};
375 ✗ const auto v2 = Vertex{b.foa_position(p[2].pos, pd), no, b.foa_text_coord(p[2].tex, td), ci};
376 ✗ const auto v3 = Vertex{b.foa_position(p[3].pos, pd), no, b.foa_text_coord(p[3].tex, td), ci};
377
378 ✗ b.add_quad(invert, v0, v1, v2, v3);
379 ✗ }
380 }
381
382 ✗ Builder create_box(float x, float y, float z, NormalsFacing normals_facing, const Rgb& color)
383 {
384 ✗ const auto invert = normals_facing == NormalsFacing::In;
385 ✗ Builder b;
386
387 // texel scale
388 ✗ const float ts = 1.0f;
389
390 // half sizes
391 ✗ const float hx = x * 0.5f;
392 ✗ const float hy = y * 0.5f;
393 ✗ const float hz = z * 0.5f;
394
395 ✗ const float s = invert ? -1.0f : 1.0f;
396
397 // front
398 ✗ add_quad_to_builder(b, invert, color, {0, 0, -s},
399 {
400 Pt{.pos = {-hx, -hy, -hz}, .tex = {0.0f, 0.0f}},
401 Pt{.pos = {hx, -hy, -hz}, .tex = {x * ts, 0.0f}},
402 Pt{.pos = {hx, hy, -hz}, .tex = {x * ts, y * ts}},
403 Pt{.pos = {-hx, hy, -hz}, .tex = {0.0f, y * ts}}
404 }
405 );
406
407 // back
408 ✗ add_quad_to_builder(b, invert, color, {0, 0, s},
409 {
410 Pt{.pos = {-hx, -hy, hz}, .tex = {0.0f, 0.0f}},
411 Pt{.pos = {-hx, hy, hz}, .tex = {0.0f, y * ts}},
412 Pt{.pos = {hx, hy, hz}, .tex = {x * ts, y * ts}},
413 Pt{.pos = {hx, -hy, hz}, .tex = {x * ts, 0.0f}}
414 }
415 );
416
417 // left
418 ✗ add_quad_to_builder(b, invert, color,{-s, 0, 0},
419 {
420 Pt{.pos = {-hx, hy, -hz}, .tex = {y * ts, 0.0f}},
421 Pt{.pos = {-hx, hy, hz}, .tex = {y * ts, z * ts}},
422 Pt{.pos = {-hx, -hy, hz}, .tex = {0.0f, z * ts}},
423 Pt{.pos = {-hx, -hy, -hz}, .tex = {0.0f, 0.0f}}
424 }
425 );
426
427 // right
428 ✗ add_quad_to_builder(b, invert, color,{s, 0, 0},
429 {
430 Pt{.pos = {hx, hy, hz}, .tex = {z * ts, y * ts}},
431 Pt{.pos = {hx, hy, -hz}, .tex = {0.0f, y * ts}},
432 Pt{.pos = {hx, -hy, -hz}, .tex = {0.0f, 0.0f}},
433 Pt{.pos = {hx, -hy, hz}, .tex = {z * ts, 0.0f}}
434 }
435 );
436
437 // bottom
438 ✗ add_quad_to_builder(b, invert, color,{0, -s, 0},
439 {
440 Pt{.pos = {-hx, -hy, -hz}, .tex = {0.0f, 0.0f}},
441 Pt{.pos = {-hx, -hy, hz}, .tex = {0.0f, z * ts}},
442 Pt{.pos = {hx, -hy, hz}, .tex = {x * ts, z * ts}},
443 Pt{.pos = {hx, -hy, -hz}, .tex = {x * ts, 0.0f}}
444 }
445 );
446
447 // top
448 ✗ add_quad_to_builder(b, invert, color,{0, s, 0},
449 {
450 Pt{.pos = {-hx, hy, -hz}, .tex = {0.0f, 0.0f}},
451 Pt{.pos = {hx, hy, -hz}, .tex = {x * ts, 0.0f}},
452 Pt{.pos = {hx, hy, hz}, .tex = {x * ts, z * ts}},
453 Pt{.pos = {-hx, hy, hz}, .tex = {0.0f, z * ts}}
454 }
455 );
456
457 ✗ return b;
458 ✗ }
459
460 ✗ Builder create_xz_plane(float x, float z, bool invert, const Rgb& color)
461 {
462 ✗ Builder b;
463
464 // texel scale
465 ✗ const float ts = 1.0f;
466
467 // half sizes
468 ✗ const float hx = x * 0.5f;
469 ✗ const float hz = z * 0.5f;
470
471 ✗ const float s = invert ? -1.0f : 1.0f;
472 ✗ add_quad_to_builder(b, invert, color,{0, s, 0},
473 {
474 Pt{.pos = {-hx, 0.0f, -hz}, .tex = {0.0f, 0.0f}},
475 Pt{.pos = {hx, 0.0f, -hz}, .tex = {x * ts, 0.0f}},
476 Pt{.pos = {hx, 0.0f, hz}, .tex = {x * ts, z * ts}},
477 Pt{.pos = {-hx, 0.0f, hz}, .tex = {0.0f, z * ts}}
478 }
479 );
480
481 ✗ return b;
482 ✗ }
483
484
485 ✗ Builder create_xy_plane(float x, float y, SideCount two_sided, const Rgb& color)
486 {
487 ✗ Builder b;
488
489 ✗ const bool invert = false;
490
491 // texel scale
492 ✗ const float ts = 1.0f;
493
494 // half sizes
495 ✗ const float hx = x * 0.5f;
496 ✗ const float hy = y * 0.5f;
497 ✗ const float hz = 0.0f;
498
499 ✗ const float s = invert ? -1.0f : 1.0f;
500
501 // front
502 ✗ add_quad_to_builder(b, invert, color,{0, 0, -s},
503 {
504 Pt{.pos = {-hx, -hy, hz}, .tex = {0.0f, 0.0f}},
505 Pt{.pos = {hx, -hy, hz}, .tex = {ts, 0.0f}},
506 Pt{.pos = {hx, hy, hz}, .tex = {ts, ts}},
507 Pt{.pos = {-hx, hy, hz}, .tex = {0.0f, ts}}
508 }
509 );
510
511 // back
512 ✗ if (two_sided == SideCount::two_sided)
513 {
514 ✗ add_quad_to_builder(b, invert, color,{0, 0, s},
515 {
516 Pt{.pos = {-hx, -hy, hz}, .tex = {0.0f, 0.0f}},
517 Pt{.pos = {-hx, hy, hz}, .tex = {0.0f, ts}},
518 Pt{.pos = {hx, hy, hz}, .tex = {ts, ts}},
519 Pt{.pos = {hx, -hy, hz}, .tex = {ts, 0.0f}}
520 }
521 );
522 }
523
524 ✗ return b;
525 ✗ }
526
527 // ==================================================================================================================================
528
529
530 // based on https://gist.github.com/Pikachuxxxx/5c4c490a7d7679824e0e18af42918efc
531 ✗ Builder create_uv_sphere(float diameter, int longitude_count, int latitude_count, NormalsFacing normals_facing, const Rgb& color)
532 {
533 ✗ ASSERT(longitude_count >= 3);
534 ✗ ASSERT(latitude_count >= 2);
535
536 ✗ Builder ret;
537 ✗ ret.add_color(linear_from_srgb(color, artist_gamma));
538
539 ✗ const auto radius = diameter / 2;
540
541 ✗ const auto delta_lat = pi / float_from_int(latitude_count);
542 ✗ const auto delta_lon = 2 * pi / float_from_int(longitude_count);
543
544 ✗ const auto invert = normals_facing == NormalsFacing::In;
545
546 ✗ for (int latitude_index = 0; latitude_index <= latitude_count; ++latitude_index)
547 {
548 ✗ const auto lat_angle = pi / 2 - float_from_int(latitude_index) * delta_lat;
549 ✗ const auto xy = std::cos(lat_angle);
550 ✗ const auto z = std::sin(lat_angle);
551
552 ✗ for (int longitude_index = 0; longitude_index <= longitude_count; ++longitude_index)
553 {
554 ✗ const auto lon_angle = float_from_int(longitude_index) * delta_lon;
555
556 ✗ const auto normal_x = xy * std::cos(lon_angle);
557 ✗ const auto normal_y = xy * std::sin(lon_angle);
558 ✗ const auto normal_z = z;
559
560 ✗ const auto vertex_s = float_from_int(longitude_index) / float_from_int(longitude_count);
561 ✗ const auto vertex_t = float_from_int(latitude_index) / float_from_int(latitude_count);
562 ✗ ret.add_position({radius * normal_x, radius * normal_y, radius * normal_z});
563 ✗ ret.add_text_coord({vertex_s, vertex_t});
564
565 ✗ const auto normal_scale = invert ? -1.0f : 1.0f;
566 ✗ ret.add_normal({normal_scale * normal_x, normal_scale * normal_y, normal_scale * normal_z});
567 }
568 }
569
570
571 // Indices
572 // k1--k1+1 a----b
573 // | / | | / |
574 // | / | | / |
575 // k2--k2+1 c----d
576 ✗ for (Index latitude_index = 0; std::cmp_less(latitude_index, latitude_count); ++latitude_index)
577 {
578 ✗ for (Index longitude_index = 0; std::cmp_less(longitude_index, longitude_count); ++longitude_index)
579 {
580 ✗ const auto k1 = latitude_index * (static_cast<Index>(longitude_count) + 1) + longitude_index;
581 ✗ const auto k2 = k1 + static_cast<Index>(longitude_count) + 1;
582
583 ✗ const auto a = k1;
584 ✗ const auto b = k1 + 1;
585 ✗ const auto c = k2;
586 ✗ const auto d = k2 + 1;
587
588 ✗ if (latitude_index != 0)
589 {
590 ✗ if (invert)
591 {
592 ✗ ret.add_triangle({{a, 0}, {b, 0}, {c, 0}}); // cw
593 }
594 else
595 {
596 ✗ ret.add_triangle({{a, 0}, {c, 0}, {b, 0}}); // ccw
597 }
598 }
599
600 ✗ if (std::cmp_not_equal(latitude_index, latitude_count - 1))
601 {
602 ✗ if (invert)
603 {
604 ✗ ret.add_triangle({{b, 0}, {d, 0}, {c, 0}}); // cw
605 }
606 else
607 {
608 ✗ ret.add_triangle({{b, 0}, {c, 0}, {d, 0}}); // ccw
609 }
610 }
611 }
612 }
613
614 ✗ return ret;
615 ✗ }
616
617
618 } // namespace eu::core::geom
619