TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
156 / 159
98.1%
Branches:
158 / 174
90.8%
Functions:
12 / 12
100.0%

mesh/io/icq.cpp
Line Branch Exec Source
1 #include "terraingen/mesh/io/icq.h"
2
3 #include "terraingen/mesh/shape_model.h"
4
5 #include <array>
6 #include <cctype>
7 #include <cmath>
8 #include <cstdint>
9 #include <cstdio>
10 #include <fstream>
11 #include <numbers>
12 #include <sstream>
13 #include <stdexcept>
14 #include <string>
15
16 namespace terraingen {
17
18 namespace {
19
20 28 void CheckModel(const IcqModel& model) {
21
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 27 times.
28 if (model.q == 0) {
22
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("An ICQ model needs Q > 0");
23 }
24 27 const std::size_t side = model.q + 1;
25
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 26 times.
27 if (model.vertices.size() != 6 * side * side) {
26
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("An ICQ model needs 6 (Q + 1)^2 vertices");
27 }
28
2/2
✓ Branch 5 taken 37948 times.
✓ Branch 6 taken 25 times.
37973 for (const Vec3d& v : model.vertices) {
29
6/8
✓ Branch 1 taken 37948 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 37947 times.
✓ Branch 5 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 37947 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 37947 times.
37948 if (!(std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z))) {
30
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("An ICQ model's vertices must be finite");
31 }
32 }
33 25 }
34
35 7932 double CrossSquared(const Vec3d& a, const Vec3d& b) {
36 7932 const Vec3d c = Cross(a, b);
37 7932 return Dot(c, c);
38 }
39
40 } // namespace
41
42 27 bool IsIcqPath(const std::filesystem::path& path) {
43
2/2
✓ Branch 1 taken 27 times.
✓ Branch 4 taken 27 times.
27 std::string extension = path.extension().string();
44
2/2
✓ Branch 4 taken 108 times.
✓ Branch 5 taken 27 times.
135 for (char& c : extension) {
45 108 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
46 }
47
1/1
✓ Branch 1 taken 27 times.
54 return extension == ".icq";
48 27 }
49
50 36446 Vec3d IcqDirection(std::size_t q, std::size_t i, std::size_t j, std::size_t face) {
51 36446 return RadialShape::LabelDirection(q, i, j, face);
52 }
53
54 17 IcqModel ReadIcq(const std::filesystem::path& path, double metresPerUnit) {
55
1/1
✓ Branch 1 taken 17 times.
17 std::ifstream file(path);
56
3/3
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 16 times.
17 if (!file) {
57
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("Cannot read ICQ file " + path.string());
58 }
59 16 std::string line;
60 16 long long q = 0;
61
14/19
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 16 times.
✓ Branch 12 taken 16 times.
✓ Branch 15 taken 16 times.
✓ Branch 17 taken 14 times.
✓ Branch 18 taken 2 times.
✓ Branch 19 taken 13 times.
✓ Branch 20 taken 1 time.
✗ Branch 21 not taken.
✓ Branch 22 taken 13 times.
✓ Branch 23 taken 16 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 3 times.
✓ Branch 27 taken 13 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
16 if (!std::getline(file, line) || !(std::istringstream(line) >> q) || q <= 0 || q > 1000000) {
62
4/4
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✓ Branch 11 taken 3 times.
3 throw std::runtime_error("ICQ file " + path.string() + " does not start with Q > 0");
63 }
64 13 IcqModel model;
65 13 model.q = static_cast<std::size_t>(q);
66 13 const std::size_t count = 6 * (model.q + 1) * (model.q + 1);
67
1/1
✓ Branch 1 taken 13 times.
13 model.vertices.reserve(count);
68 13 std::size_t number = 1;
69
2/2
✓ Branch 1 taken 5463 times.
✓ Branch 2 taken 11 times.
5474 while (model.vertices.size() < count) {
70
4/4
✓ Branch 1 taken 5463 times.
✓ Branch 4 taken 5463 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 5462 times.
5463 if (!std::getline(file, line)) {
71
4/4
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
2 throw std::runtime_error("ICQ file " + path.string() + " has " +
72
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
4 std::to_string(model.vertices.size()) + " of " +
73
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
3 std::to_string(count) + " vertices");
74 }
75 5462 ++number;
76
1/1
✓ Branch 1 taken 5462 times.
5462 std::istringstream values(line);
77 5462 Vec3d v;
78
8/8
✓ Branch 1 taken 5462 times.
✓ Branch 4 taken 5462 times.
✓ Branch 7 taken 5462 times.
✓ Branch 10 taken 5462 times.
✓ Branch 12 taken 5461 times.
✓ Branch 13 taken 1 time.
✓ Branch 14 taken 1 time.
✓ Branch 15 taken 5461 times.
10923 if (!(values >> v.x >> v.y >> v.z) ||
79
3/6
✓ Branch 1 taken 5461 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5461 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 5461 times.
5461 !(std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z))) {
80
4/4
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
2 throw std::runtime_error("ICQ file " + path.string() + " line " +
81
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
3 std::to_string(number) + " is not three finite numbers");
82 }
83
1/1
✓ Branch 1 taken 5461 times.
5461 model.vertices.push_back({v.x * metresPerUnit, v.y * metresPerUnit, v.z * metresPerUnit});
84 5462 }
85 22 return model;
86 24 }
87
88 11 void WriteIcq(const std::filesystem::path& path, const IcqModel& model, double metresPerUnit) {
89
1/1
✓ Branch 1 taken 9 times.
11 CheckModel(model);
90
1/1
✓ Branch 1 taken 9 times.
9 std::ofstream file(path);
91
3/3
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 8 times.
9 if (!file) {
92
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("Cannot write ICQ file " + path.string());
93 }
94 8 std::array<char, 64> buffer{};
95 24 std::snprintf(buffer.data(), buffer.size(), "%12zu\n", model.q);
96
1/1
✓ Branch 1 taken 8 times.
8 file << buffer.data();
97
2/2
✓ Branch 5 taken 28608 times.
✓ Branch 6 taken 8 times.
28616 for (const Vec3d& v : model.vertices) {
98 57216 std::snprintf(buffer.data(), buffer.size(), "%18.9f%18.9f%18.9f\n", v.x / metresPerUnit,
99 28608 v.y / metresPerUnit, v.z / metresPerUnit);
100
1/1
✓ Branch 1 taken 28608 times.
28608 file << buffer.data();
101 }
102
2/3
✓ Branch 1 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if (!file) {
103
0/3
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ throw std::runtime_error("Cannot write ICQ file " + path.string());
104 }
105 9 }
106
107 10 TriangleMesh IcqToMesh(const IcqModel& model) {
108
1/1
✓ Branch 1 taken 9 times.
10 CheckModel(model);
109 9 const std::size_t q = model.q;
110
1/1
✓ Branch 2 taken 9 times.
9 std::vector<std::size_t> n(model.vertices.size());
111
2/2
✓ Branch 1 taken 4824 times.
✓ Branch 2 taken 9 times.
4833 for (std::size_t k = 0; k < n.size(); ++k) {
112 4824 n[k] = k;
113 }
114 41340 const auto at = [&](std::size_t i, std::size_t j, std::size_t f) -> std::size_t& {
115 41340 return n[model.Index(i, j, f)];
116 9 };
117
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 9 times.
67 for (std::size_t i = 1; i < q; ++i) {
118 58 at(i, q, 6) = at(q - i, q, 4);
119 58 at(i, 0, 6) = at(i, q, 2);
120 58 at(i, 0, 5) = at(q, q - i, 1);
121 58 at(i, 0, 4) = at(q - i, 0, 1);
122 58 at(i, 0, 3) = at(0, i, 1);
123 58 at(i, 0, 2) = at(i, q, 1);
124 }
125
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 9 times.
67 for (std::size_t j = 1; j < q; ++j) {
126 58 at(q, j, 6) = at(j, q, 5);
127 58 at(q, j, 5) = at(0, j, 4);
128 58 at(q, j, 4) = at(0, j, 3);
129 58 at(q, j, 3) = at(0, j, 2);
130 58 at(0, j, 6) = at(q - j, q, 3);
131 58 at(0, j, 5) = at(q, j, 2);
132 }
133 9 at(0, 0, 3) = at(0, 0, 1);
134 9 at(q, 0, 4) = at(0, 0, 1);
135 9 at(0, 0, 2) = at(0, q, 1);
136 9 at(q, 0, 3) = at(0, q, 1);
137 9 at(0, 0, 4) = at(q, 0, 1);
138 9 at(q, 0, 5) = at(q, 0, 1);
139 9 at(0, 0, 5) = at(q, q, 1);
140 9 at(q, 0, 2) = at(q, q, 1);
141 9 at(0, 0, 6) = at(0, q, 2);
142 9 at(q, q, 3) = at(0, q, 2);
143 9 at(0, q, 5) = at(q, q, 2);
144 9 at(q, 0, 6) = at(q, q, 2);
145 9 at(q, q, 4) = at(0, q, 3);
146 9 at(0, q, 6) = at(0, q, 3);
147 9 at(q, q, 5) = at(0, q, 4);
148 9 at(q, q, 6) = at(0, q, 4);
149
150 9 TriangleMesh mesh;
151
1/1
✓ Branch 2 taken 9 times.
9 std::vector<std::uint32_t> compact(model.vertices.size(), UINT32_MAX);
152 23796 const auto vertex = [&](std::size_t i, std::size_t j, std::size_t f) {
153 23796 const std::size_t k = at(i, j, f);
154
2/2
✓ Branch 1 taken 3984 times.
✓ Branch 2 taken 19812 times.
23796 if (compact[k] == UINT32_MAX) {
155 3984 compact[k] = static_cast<std::uint32_t>(mesh.positions.size());
156 3984 mesh.positions.push_back(model.vertices[k]);
157 }
158 23796 return compact[k];
159 9 };
160 15864 const auto v = [&](std::size_t i, std::size_t j, std::size_t f) {
161 15864 return model.vertices[at(i, j, f)];
162 9 };
163
1/1
✓ Branch 1 taken 9 times.
9 mesh.triangles.reserve(12 * q * q);
164
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 9 times.
63 for (std::size_t f = 1; f <= 6; ++f) {
165
2/2
✓ Branch 0 taken 402 times.
✓ Branch 1 taken 54 times.
456 for (std::size_t i = 0; i < q; ++i) {
166
2/2
✓ Branch 0 taken 3966 times.
✓ Branch 1 taken 402 times.
4368 for (std::size_t j = 0; j < q; ++j) {
167 3966 const double z1 = CrossSquared(v(i, j, f), v(i + 1, j + 1, f));
168 3966 const double z2 = CrossSquared(v(i + 1, j, f), v(i, j + 1, f));
169
2/2
✓ Branch 0 taken 1985 times.
✓ Branch 1 taken 1981 times.
3966 if (z1 <= z2) {
170 ✗ mesh.triangles.push_back(
171
4/4
✓ Branch 1 taken 1985 times.
✓ Branch 4 taken 1985 times.
✓ Branch 7 taken 1985 times.
✓ Branch 10 taken 1985 times.
1985 {vertex(i, j, f), vertex(i + 1, j + 1, f), vertex(i + 1, j, f)});
172 1985 mesh.triangles.push_back(
173
4/4
✓ Branch 1 taken 1985 times.
✓ Branch 4 taken 1985 times.
✓ Branch 7 taken 1985 times.
✓ Branch 10 taken 1985 times.
1985 {vertex(i, j, f), vertex(i, j + 1, f), vertex(i + 1, j + 1, f)});
174 } else {
175 ✗ mesh.triangles.push_back(
176
4/4
✓ Branch 1 taken 1981 times.
✓ Branch 4 taken 1981 times.
✓ Branch 7 taken 1981 times.
✓ Branch 10 taken 1981 times.
1981 {vertex(i, j, f), vertex(i, j + 1, f), vertex(i + 1, j, f)});
177 1981 mesh.triangles.push_back(
178
4/4
✓ Branch 1 taken 1981 times.
✓ Branch 4 taken 1981 times.
✓ Branch 7 taken 1981 times.
✓ Branch 10 taken 1981 times.
1981 {vertex(i + 1, j, f), vertex(i, j + 1, f), vertex(i + 1, j + 1, f)});
179 }
180 }
181 }
182 }
183 18 return mesh;
184 9 }
185
186 7 RadialShape ToRadialShape(const IcqModel& model) {
187
1/1
✓ Branch 1 taken 7 times.
7 CheckModel(model);
188 7 constexpr double kRadial = 1e-6;
189
1/1
✓ Branch 2 taken 7 times.
7 std::vector<double> radii(model.vertices.size());
190
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 6 times.
43 for (std::size_t face = 1; face <= 6; ++face) {
191
2/2
✓ Branch 0 taken 373 times.
✓ Branch 1 taken 36 times.
409 for (std::size_t j = 0; j <= model.q; ++j) {
192
2/2
✓ Branch 0 taken 3973 times.
✓ Branch 1 taken 372 times.
4345 for (std::size_t i = 0; i <= model.q; ++i) {
193 3973 const std::size_t k = model.Index(i, j, face);
194 3973 const Vec3d& v = model.vertices[k];
195 3973 const double r = Length(v);
196
1/1
✓ Branch 1 taken 3973 times.
3973 const Vec3d d = IcqDirection(model.q, i, j, face);
197
6/8
✓ Branch 0 taken 3973 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 3972 times.
✓ Branch 5 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 3972 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 3972 times.
3973 if (!(r > 0.0) || Length(Cross(v, d)) > kRadial * r || Dot(v, d) < 0.0) {
198
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return ShapeModelFromMesh(IcqToMesh(model), model.q);
199 }
200 3972 radii[k] = r;
201 }
202 }
203 }
204
1/1
✓ Branch 3 taken 6 times.
6 return RadialShape(model.q, std::move(radii));
205 7 }
206
207 3 IcqModel ToIcq(const RadialShape& shape) {
208 3 IcqModel model;
209 3 model.q = shape.Q();
210
1/1
✓ Branch 3 taken 3 times.
3 model.vertices.resize(shape.Radii().size());
211
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 3 times.
21 for (std::size_t face = 1; face <= 6; ++face) {
212
2/2
✓ Branch 0 taken 186 times.
✓ Branch 1 taken 18 times.
204 for (std::size_t j = 0; j <= model.q; ++j) {
213
2/2
✓ Branch 0 taken 1986 times.
✓ Branch 1 taken 186 times.
2172 for (std::size_t i = 0; i <= model.q; ++i) {
214 1986 const std::size_t k = model.Index(i, j, face);
215
1/1
✓ Branch 3 taken 1986 times.
1986 model.vertices[k] = IcqDirection(model.q, i, j, face) * shape.Radii()[k];
216 }
217 }
218 }
219 3 return model;
220 }
221
222 } // namespace terraingen
223