TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
95 / 98
96.9%
Branches:
62 / 71
87.3%
Functions:
13 / 13
100.0%

mesh/io/stl.cpp
Line Branch Exec Source
1 #include "terraingen/mesh/io/formats.h"
2
3 #include <array>
4 #include <cstdint>
5 #include <cstring>
6 #include <fstream>
7 #include <iterator>
8 #include <map>
9 #include <sstream>
10 #include <stdexcept>
11 #include <string>
12
13 namespace terraingen::detail {
14
15 namespace {
16
17 constexpr std::size_t kHeaderBytes = 80;
18 constexpr std::size_t kTriangleBytes = 50;
19
20 /// Appends `value`'s bytes, little-endian on the hosts supported.
21 1035927 template <typename T> void Put(std::string& bytes, T value) {
22 1035927 bytes.append(reinterpret_cast<const char*>(&value), sizeof(T));
23 1035927 }
24
25 318744 void PutVector(std::string& bytes, const Vec3d& v) {
26
2/2
✓ Branch 2 taken 956232 times.
✓ Branch 3 taken 318744 times.
1274976 for (const double c : {v.x, v.y, v.z}) {
27
1/1
✓ Branch 1 taken 956232 times.
956232 Put(bytes, static_cast<float>(c));
28 }
29 318744 }
30
31 /// The unit normal of a triangle, or zero for a degenerate one.
32 79686 Vec3d FaceNormal(const Vec3d& a, const Vec3d& b, const Vec3d& c) {
33 79686 const Vec3d u = b - a;
34 79686 const Vec3d v = c - a;
35 79686 const Vec3d n{u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x};
36 79686 const double length = Length(n);
37
1/2
✓ Branch 0 taken 79686 times.
✗ Branch 1 not taken.
79686 return length > 0.0 ? n * (1.0 / length) : Vec3d{};
38 }
39
40 /// Collects triangle corners into a mesh, joining corners at the same position into one vertex.
41 class Welder {
42 public:
43 55 void AddTriangle(const std::array<Vec3d, 3>& corners) {
44 55 std::array<std::uint32_t, 3> triangle{};
45
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 55 times.
220 for (std::size_t c = 0; c < 3; ++c) {
46
1/1
✓ Branch 2 taken 165 times.
165 triangle[c] = Vertex(corners[c]);
47 }
48
1/1
✓ Branch 1 taken 55 times.
55 mesh_.triangles.push_back(triangle);
49 55 }
50
51 4 TriangleMesh Take() {
52 4 return std::move(mesh_);
53 }
54
55 private:
56 165 std::uint32_t Vertex(const Vec3d& p) {
57 165 const auto [found, added] =
58 ✗ index_.emplace(std::array<double, 3>{p.x, p.y, p.z},
59
1/1
✓ Branch 2 taken 165 times.
165 static_cast<std::uint32_t>(mesh_.positions.size()));
60
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 130 times.
165 if (added) {
61
1/1
✓ Branch 1 taken 35 times.
35 mesh_.positions.push_back(p);
62 }
63 330 return found->second;
64 }
65
66 TriangleMesh mesh_;
67 std::map<std::array<double, 3>, std::uint32_t> index_;
68 };
69
70 162 Vec3d GetVector(const std::string& bytes, std::size_t offset) {
71 162 std::array<float, 3> v{};
72 162 std::memcpy(v.data(), bytes.data() + offset, sizeof(v));
73 162 return {v[0], v[1], v[2]};
74 }
75
76 3 TriangleMesh ReadBinary(const std::string& bytes) {
77 3 std::uint32_t count = 0;
78 3 std::memcpy(&count, bytes.data() + kHeaderBytes, sizeof(count));
79 3 Welder welder;
80
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 3 times.
57 for (std::size_t t = 0; t < count; ++t) {
81 54 const std::size_t start = kHeaderBytes + 4 + t * kTriangleBytes + 12;
82
1/1
✓ Branch 1 taken 54 times.
54 welder.AddTriangle(
83 54 {GetVector(bytes, start), GetVector(bytes, start + 12), GetVector(bytes, start + 24)});
84 }
85 6 return welder.Take();
86 3 }
87
88 1 TriangleMesh ReadAscii(const std::string& text) {
89
1/1
✓ Branch 1 taken 1 time.
1 std::istringstream in(text);
90 1 Welder welder;
91 1 std::array<Vec3d, 3> corners{};
92 1 std::size_t corner = 0;
93 1 std::string word;
94
4/4
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 1 time.
17 while (in >> word) {
95
3/3
✓ Branch 1 taken 16 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 3 times.
16 if (word != "vertex") {
96 13 continue;
97 }
98 3 Vec3d& p = corners[corner];
99
5/6
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
✓ Branch 10 taken 3 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 3 times.
3 if (!(in >> p.x >> p.y >> p.z)) {
100
0/1
✗ Branch 2 not taken.
✗ throw std::runtime_error("A malformed vertex in an ASCII STL");
101 }
102
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (++corner == 3) {
103
1/1
✓ Branch 1 taken 1 time.
1 welder.AddTriangle(corners);
104 1 corner = 0;
105 }
106 }
107 2 return welder.Take();
108 1 }
109
110 } // namespace
111
112 9 void WriteStl(const std::filesystem::path& path, const std::vector<SceneMesh>& meshes,
113 const std::vector<SceneNode>& nodes) {
114 9 std::size_t count = 0;
115
2/2
✓ Branch 4 taken 113 times.
✓ Branch 5 taken 9 times.
122 for (const SceneNode& node : nodes) {
116 113 count += meshes[node.mesh].mesh.triangles.size();
117 }
118
1/1
✓ Branch 1 taken 9 times.
9 std::string bytes = "TerrainGen";
119
1/1
✓ Branch 1 taken 9 times.
9 bytes.resize(kHeaderBytes, ' ');
120
1/1
✓ Branch 1 taken 9 times.
9 Put(bytes, static_cast<std::uint32_t>(count));
121
2/2
✓ Branch 5 taken 113 times.
✓ Branch 6 taken 9 times.
122 for (const SceneNode& node : nodes) {
122 113 const TriangleMesh& mesh = meshes[node.mesh].mesh;
123
2/2
✓ Branch 5 taken 79686 times.
✓ Branch 6 taken 113 times.
79799 for (const auto& t : mesh.triangles) {
124
1/1
✓ Branch 3 taken 79686 times.
79686 const std::array<Vec3d, 3> corners{Placed(node, mesh.positions[t[0]]),
125
1/1
✓ Branch 3 taken 79686 times.
79686 Placed(node, mesh.positions[t[1]]),
126
1/1
✓ Branch 3 taken 79686 times.
79686 Placed(node, mesh.positions[t[2]])};
127
1/1
✓ Branch 5 taken 79686 times.
79686 PutVector(bytes, FaceNormal(corners[0], corners[1], corners[2]));
128
2/2
✓ Branch 2 taken 239058 times.
✓ Branch 3 taken 79686 times.
318744 for (const Vec3d& corner : corners) {
129
1/1
✓ Branch 1 taken 239058 times.
239058 PutVector(bytes, corner);
130 }
131
1/1
✓ Branch 1 taken 79686 times.
79686 Put(bytes, static_cast<std::uint16_t>(0));
132 }
133 }
134
1/1
✓ Branch 1 taken 9 times.
9 Save(path, bytes);
135 9 }
136
137 4 SceneMesh ReadStl(const std::filesystem::path& path) {
138
1/1
✓ Branch 1 taken 4 times.
4 std::ifstream file(path, std::ios::binary);
139
2/3
✓ Branch 1 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if (!file) {
140
0/3
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ throw std::runtime_error("Cannot read " + path.string());
141 }
142
1/1
✓ Branch 3 taken 4 times.
8 const std::string bytes{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
143
1/1
✓ Branch 1 taken 4 times.
4 SceneMesh read;
144 4 std::uint32_t count = 0;
145
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (bytes.size() >= kHeaderBytes + 4) {
146 4 std::memcpy(&count, bytes.data() + kHeaderBytes, sizeof(count));
147 }
148 const bool binary =
149
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 bytes.size() >= kHeaderBytes + 4 &&
150
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
4 bytes.size() == kHeaderBytes + 4 + static_cast<std::size_t>(count) * kTriangleBytes;
151
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 6 taken 1 time.
4 read.mesh = binary ? ReadBinary(bytes) : ReadAscii(bytes);
152 8 return read;
153 4 }
154
155 } // namespace terraingen::detail
156