GCC Code Coverage Report


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