TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
74 / 75
98.7%
Branches:
118 / 127
92.9%
Functions:
9 / 9
100.0%

mesh/io/mesh_io.cpp
Line Branch Exec Source
1 #include "terraingen/mesh/io/mesh_io.h"
2
3 #include "terraingen/mesh/io/formats.h"
4 #include "terraingen/mesh/io/icq.h"
5
6 #include <cctype>
7 #include <cmath>
8 #include <fstream>
9 #include <stdexcept>
10 #include <string>
11
12 namespace terraingen {
13
14 namespace {
15
16 /// The extension of `path`, lower case.
17 224 std::string Extension(const std::filesystem::path& path) {
18
2/2
✓ Branch 1 taken 224 times.
✓ Branch 4 taken 224 times.
224 std::string extension = path.extension().string();
19
2/2
✓ Branch 4 taken 903 times.
✓ Branch 5 taken 224 times.
1127 for (char& c : extension) {
20 903 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
21 }
22 224 return extension;
23 }
24
25 /// Throws for no meshes or nodes, an empty or invalid mesh, mismatched normals, a node naming no
26 /// mesh, or a node scale or pose that cannot be written.
27 77 void CheckScene(const std::vector<SceneMesh>& meshes, const std::vector<SceneNode>& nodes) {
28
5/6
✓ Branch 1 taken 76 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 76 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 76 times.
77 if (meshes.empty() || nodes.empty()) {
29
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("A mesh scene needs meshes and nodes");
30 }
31
2/2
✓ Branch 5 taken 438 times.
✓ Branch 6 taken 74 times.
512 for (const SceneMesh& scene : meshes) {
32
5/6
✓ Branch 1 taken 437 times.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 437 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 437 times.
438 if (scene.mesh.positions.empty() || scene.mesh.triangles.empty()) {
33
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Cannot write an empty mesh");
34 }
35
6/6
✓ Branch 1 taken 391 times.
✓ Branch 2 taken 46 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 390 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 436 times.
437 if (!scene.normals.empty() && scene.normals.size() != scene.mesh.positions.size()) {
36
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Normals must be empty or one per vertex");
37 }
38
1/1
✓ Branch 1 taken 436 times.
436 ValidateMesh(scene.mesh);
39 }
40
2/2
✓ Branch 5 taken 499 times.
✓ Branch 6 taken 71 times.
570 for (const SceneNode& node : nodes) {
41
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 498 times.
499 if (node.mesh >= meshes.size()) {
42
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("A scene node names a mesh the scene does not have");
43 }
44
2/2
✓ Branch 2 taken 1492 times.
✓ Branch 3 taken 497 times.
1989 for (const double s : {node.scale.x, node.scale.y, node.scale.z}) {
45
5/6
✓ Branch 1 taken 1492 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1491 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1491 times.
1492 if (!(std::isfinite(s) && s != 0.0)) {
46
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Node scales must be finite and not zero");
47 }
48 }
49
1/1
✓ Branch 1 taken 496 times.
497 RequireRotation(node.pose.rotation);
50 }
51 71 }
52
53 } // namespace
54
55 namespace detail {
56
57 284137 Vec3d Placed(const SceneNode& node, const Vec3d& p) {
58
1/1
✓ Branch 1 taken 284137 times.
284137 return node.pose.Apply({p.x * node.scale.x, p.y * node.scale.y, p.z * node.scale.z});
59 }
60
61 41957 Vec3d PlacedNormal(const SceneNode& node, const Vec3d& n) {
62 41957 const Vec3d scaled{n.x / node.scale.x, n.y / node.scale.y, n.z / node.scale.z};
63 41957 const auto& r = node.pose.rotation;
64 41957 const Vec3d rotated{r[0] * scaled.x + r[1] * scaled.y + r[2] * scaled.z,
65 41957 r[3] * scaled.x + r[4] * scaled.y + r[5] * scaled.z,
66 83914 r[6] * scaled.x + r[7] * scaled.y + r[8] * scaled.z};
67 41957 return rotated * (1.0 / Length(rotated));
68 }
69
70 32 void Save(const std::filesystem::path& path, const std::string& bytes) {
71
1/1
✓ Branch 1 taken 32 times.
32 std::ofstream file(path, std::ios::binary);
72
1/1
✓ Branch 3 taken 32 times.
32 file.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
73
2/3
✓ Branch 1 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
32 if (!file) {
74
0/3
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ throw std::runtime_error("Cannot write " + path.string());
75 }
76 32 }
77
78 } // namespace detail
79
80 123 bool IsMeshPath(const std::filesystem::path& path) {
81
1/1
✓ Branch 1 taken 123 times.
123 const std::string extension = Extension(path);
82
9/9
✓ Branch 1 taken 123 times.
✓ Branch 3 taken 107 times.
✓ Branch 4 taken 16 times.
✓ Branch 6 taken 107 times.
✓ Branch 8 taken 102 times.
✓ Branch 9 taken 5 times.
✓ Branch 11 taken 102 times.
✓ Branch 13 taken 23 times.
✓ Branch 14 taken 79 times.
146 return extension == ".obj" || extension == ".gltf" || extension == ".glb" ||
83
3/3
✓ Branch 1 taken 23 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 10 times.
146 extension == ".stl";
84 123 }
85
86 79 void WriteScene(const std::filesystem::path& path, const std::vector<SceneMesh>& meshes,
87 const std::vector<SceneNode>& nodes) {
88
3/3
✓ Branch 1 taken 79 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 77 times.
79 if (!IsMeshPath(path)) {
89
1/1
✓ Branch 1 taken 2 times.
4 throw std::invalid_argument("Mesh files must end in .obj, .gltf, .glb or .stl: " +
90
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
6 path.string());
91 }
92
1/1
✓ Branch 1 taken 71 times.
77 CheckScene(meshes, nodes);
93
1/1
✓ Branch 1 taken 71 times.
71 const std::string extension = Extension(path);
94
3/3
✓ Branch 1 taken 71 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 61 times.
71 if (extension == ".obj") {
95
1/1
✓ Branch 1 taken 10 times.
10 detail::WriteObj(path, meshes, nodes);
96
3/3
✓ Branch 1 taken 61 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 52 times.
61 } else if (extension == ".stl") {
97
1/1
✓ Branch 1 taken 9 times.
9 detail::WriteStl(path, meshes, nodes);
98 } else {
99
1/1
✓ Branch 1 taken 51 times.
52 detail::WriteGltf(path, meshes, nodes);
100 }
101 71 }
102
103 51 void WriteMesh(const std::filesystem::path& path, const SceneMesh& mesh) {
104
7/7
✓ Branch 1 taken 51 times.
✓ Branch 4 taken 51 times.
✓ Branch 7 taken 47 times.
✓ Branch 10 taken 47 times.
✓ Branch 11 taken 47 times.
✓ Branch 15 taken 4 times.
✓ Branch 16 taken 4 times.
263 WriteScene(path, {mesh}, {SceneNode{}});
105
3/5
✓ Branch 1 taken 51 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
102 }
106
107 30 SceneMesh ReadMesh(const std::filesystem::path& path) {
108
1/1
✓ Branch 1 taken 30 times.
30 const std::string extension = Extension(path);
109
1/1
✓ Branch 1 taken 30 times.
30 SceneMesh read;
110
3/3
✓ Branch 1 taken 30 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 17 times.
30 if (extension == ".obj") {
111
1/1
✓ Branch 1 taken 11 times.
13 read = detail::ReadObj(path);
112
8/8
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 3 times.
✓ Branch 6 taken 14 times.
✓ Branch 8 taken 7 times.
✓ Branch 9 taken 7 times.
✓ Branch 10 taken 10 times.
✓ Branch 11 taken 7 times.
17 } else if (extension == ".gltf" || extension == ".glb") {
113
1/1
✓ Branch 1 taken 9 times.
10 read = detail::ReadGltf(path);
114
3/3
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
7 } else if (extension == ".stl") {
115
1/1
✓ Branch 1 taken 4 times.
4 read = detail::ReadStl(path);
116
3/3
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
3 } else if (extension == ".icq") {
117
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 read.mesh = IcqToMesh(ReadIcq(path));
118 } else {
119
1/1
✓ Branch 1 taken 1 time.
2 throw std::invalid_argument("Meshes are read from .obj, .gltf, .glb, .stl or .icq: " +
120
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
3 path.string());
121 }
122
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 25 times.
26 if (read.mesh.triangles.empty()) {
123
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("No triangles in " + path.string());
124 }
125
1/1
✓ Branch 1 taken 25 times.
25 ValidateMesh(read.mesh);
126 50 return read;
127 35 }
128
129 } // namespace terraingen
130