TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
200 / 206
97.1%
Branches:
136 / 176
77.3%
Functions:
13 / 13
100.0%

mesh/io/gltf.cpp
Line Branch Exec Source
1 #include "terraingen/mesh/io/formats.h"
2
3 #define CGLTF_IMPLEMENTATION
4 #define CGLTF_WRITE_IMPLEMENTATION
5 #pragma GCC diagnostic push
6 #pragma GCC diagnostic ignored "-Wformat-truncation"
7 #include <cgltf_write.h>
8 #pragma GCC diagnostic pop
9
10 #include <algorithm>
11 #include <bit>
12 #include <cstring>
13 #include <deque>
14 #include <limits>
15 #include <memory>
16 #include <stdexcept>
17 #include <string>
18
19 namespace terraingen::detail {
20
21 namespace {
22
23 static_assert(std::endian::native == std::endian::little,
24 "The glTF writer assumes a little-endian host");
25
26 /// Appends `value`'s bytes, little-endian on the hosts supported.
27 3914106 template <typename T> void Put(std::string& bytes, T value) {
28 3914106 bytes.append(reinterpret_cast<const char*>(&value), sizeof(T));
29 3914106 }
30
31 /// The cgltf document of a scene: every array sized before any pointer into it is taken, and the
32 /// binary buffer every accessor reads.
33 class GltfDocument {
34 public:
35 52 GltfDocument(const std::vector<SceneMesh>& meshes, const std::vector<SceneNode>& nodes)
36
3/3
✓ Branch 2 taken 52 times.
✓ Branch 6 taken 52 times.
✓ Branch 10 taken 52 times.
312 : views_(ViewCount(meshes)), accessors_(views_.size()), attributes_(2 * meshes.size()),
37
3/3
✓ Branch 2 taken 52 times.
✓ Branch 6 taken 52 times.
✓ Branch 10 taken 52 times.
312 materials_(meshes.size()), primitives_(meshes.size()), meshes_(meshes.size()),
38
3/3
✓ Branch 2 taken 52 times.
✓ Branch 6 taken 52 times.
✓ Branch 10 taken 52 times.
260 nodes_(nodes.size()), roots_(nodes.size()) {
39 52 std::size_t view = 0;
40
2/2
✓ Branch 1 taken 316 times.
✓ Branch 2 taken 52 times.
368 for (std::size_t m = 0; m < meshes.size(); ++m) {
41
1/1
✓ Branch 2 taken 316 times.
316 AddMesh(m, meshes[m], view);
42 }
43
2/2
✓ Branch 1 taken 360 times.
✓ Branch 2 taken 52 times.
412 for (std::size_t n = 0; n < nodes.size(); ++n) {
44
1/1
✓ Branch 2 taken 360 times.
360 AddNode(n, nodes[n]);
45 }
46 52 }
47
48 /// Writes the document as .glb, or as .gltf with its buffer in a .bin beside it.
49 52 void Write(const std::filesystem::path& path) {
50
13/24
✓ Branch 1 taken 52 times.
✓ Branch 4 taken 52 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 49 times.
✓ Branch 10 taken 3 times.
✓ Branch 13 taken 3 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 3 times.
✓ Branch 18 taken 3 times.
✓ Branch 19 taken 49 times.
✓ Branch 21 taken 3 times.
✓ Branch 22 taken 49 times.
✓ Branch 24 taken 52 times.
✗ Branch 25 not taken.
✓ Branch 27 taken 52 times.
✗ Branch 28 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
52 const bool binary = path.extension() == ".glb" || path.extension() == ".GLB";
51 52 cgltf_buffer buffer{};
52 52 buffer.size = binary_.size();
53 52 std::string bin;
54
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 49 times.
52 if (!binary) {
55
1/1
✓ Branch 1 taken 3 times.
3 std::filesystem::path binPath = path;
56
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 binPath.replace_extension(".bin");
57
1/1
✓ Branch 1 taken 3 times.
3 Save(binPath, binary_);
58
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 bin = binPath.filename().string();
59 3 buffer.uri = bin.data();
60 3 }
61
2/2
✓ Branch 4 taken 916 times.
✓ Branch 5 taken 52 times.
968 for (cgltf_buffer_view& view : views_) {
62 916 view.buffer = &buffer;
63 }
64 52 cgltf_scene scene{};
65 52 scene.nodes = roots_.data();
66 52 scene.nodes_count = roots_.size();
67 52 cgltf_data data{};
68
1/1
✓ Branch 1 taken 52 times.
104 std::string version = "2.0";
69
1/1
✓ Branch 1 taken 52 times.
52 std::string generator = "TerrainGen";
70 52 data.asset.version = version.data();
71 52 data.asset.generator = generator.data();
72 52 data.buffers = &buffer;
73 52 data.buffers_count = 1;
74 52 data.buffer_views = views_.data();
75 52 data.buffer_views_count = views_.size();
76 52 data.accessors = accessors_.data();
77 52 data.accessors_count = accessors_.size();
78 52 data.materials = materials_.data();
79 52 data.materials_count = materials_.size();
80 52 data.meshes = meshes_.data();
81 52 data.meshes_count = meshes_.size();
82 52 data.nodes = nodes_.data();
83 52 data.nodes_count = nodes_.size();
84 52 data.scenes = &scene;
85 52 data.scenes_count = 1;
86 52 data.scene = &scene;
87
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 3 times.
52 if (binary) {
88 49 data.bin = binary_.data();
89 49 data.bin_size = binary_.size();
90 }
91 52 cgltf_options options{};
92
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 3 times.
52 options.type = binary ? cgltf_file_type_glb : cgltf_file_type_gltf;
93
4/4
✓ Branch 1 taken 52 times.
✓ Branch 5 taken 52 times.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 51 times.
52 if (cgltf_write_file(&options, path.string().c_str(), &data) != cgltf_result_success) {
94
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("Cannot write " + path.string());
95 }
96 54 }
97
98 private:
99 52 static std::size_t ViewCount(const std::vector<SceneMesh>& meshes) {
100 52 std::size_t count = 0;
101
2/2
✓ Branch 4 taken 316 times.
✓ Branch 5 taken 52 times.
368 for (const SceneMesh& scene : meshes) {
102
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 284 times.
316 count += scene.normals.empty() ? 2u : 3u;
103 }
104 52 return count;
105 }
106
107 916 char* Name(const std::string& name) {
108 916 return names_.emplace_back(name).data();
109 }
110
111 /// Ends a view and its accessor at `view` over the bytes added since `start`.
112 916 cgltf_accessor* AddAccessor(std::size_t& view, std::size_t start, cgltf_buffer_view_type type,
113 cgltf_component_type component, cgltf_type kind,
114 std::size_t count) {
115 916 cgltf_buffer_view& v = views_[view];
116 916 v.offset = start;
117 916 v.size = binary_.size() - start;
118 916 v.type = type;
119 916 cgltf_accessor& a = accessors_[view];
120 916 a.component_type = component;
121 916 a.type = kind;
122 916 a.count = count;
123 916 a.buffer_view = &v;
124 916 ++view;
125 916 return &a;
126 }
127
128 600 cgltf_accessor* AddVectors(std::size_t& view, const std::vector<Vec3d>& vectors, bool bounds) {
129 600 const std::size_t start = binary_.size();
130 600 constexpr float kInf = std::numeric_limits<float>::infinity();
131 600 std::array<float, 3> low{kInf, kInf, kInf};
132 600 std::array<float, 3> high{-kInf, -kInf, -kInf};
133
2/2
✓ Branch 5 taken 637048 times.
✓ Branch 6 taken 600 times.
637648 for (const Vec3d& vector : vectors) {
134 637048 const Vec3d p = YUp(vector);
135 637048 const std::array<float, 3> stored{static_cast<float>(p.x), static_cast<float>(p.y),
136 637048 static_cast<float>(p.z)};
137
2/2
✓ Branch 0 taken 1911144 times.
✓ Branch 1 taken 637048 times.
2548192 for (std::size_t c = 0; c < 3; ++c) {
138
1/1
✓ Branch 2 taken 1911144 times.
1911144 Put(binary_, stored[c]);
139 1911144 low[c] = std::min(low[c], stored[c]);
140 1911144 high[c] = std::max(high[c], stored[c]);
141 }
142 }
143 cgltf_accessor* accessor =
144 600 AddAccessor(view, start, cgltf_buffer_view_type_vertices, cgltf_component_type_r_32f,
145 cgltf_type_vec3, vectors.size());
146
2/2
✓ Branch 0 taken 316 times.
✓ Branch 1 taken 284 times.
600 if (bounds) {
147 316 accessor->has_min = accessor->has_max = 1;
148
1/1
✓ Branch 1 taken 316 times.
316 std::copy(low.begin(), low.end(), accessor->min);
149
1/1
✓ Branch 1 taken 316 times.
316 std::copy(high.begin(), high.end(), accessor->max);
150 }
151 600 return accessor;
152 }
153
154 316 void AddMesh(std::size_t m, const SceneMesh& scene, std::size_t& view) {
155 316 cgltf_attribute* attributes = &attributes_[2 * m];
156
2/2
✓ Branch 1 taken 316 times.
✓ Branch 4 taken 316 times.
316 attributes[0] = {Name("POSITION"), cgltf_attribute_type_position, 0,
157
1/1
✓ Branch 1 taken 316 times.
316 AddVectors(view, scene.mesh.positions, true)};
158 316 std::size_t count = 1;
159
2/2
✓ Branch 1 taken 284 times.
✓ Branch 2 taken 32 times.
316 if (!scene.normals.empty()) {
160
2/2
✓ Branch 1 taken 284 times.
✓ Branch 4 taken 284 times.
568 attributes[1] = {Name("NORMAL"), cgltf_attribute_type_normal, 0,
161
1/1
✓ Branch 1 taken 284 times.
284 AddVectors(view, scene.normals, false)};
162 284 count = 2;
163 }
164 316 const std::size_t start = binary_.size();
165
2/2
✓ Branch 5 taken 667654 times.
✓ Branch 6 taken 316 times.
667970 for (const auto& t : scene.mesh.triangles) {
166
2/2
✓ Branch 2 taken 2002962 times.
✓ Branch 3 taken 667654 times.
2670616 for (const std::uint32_t v : t) {
167
1/1
✓ Branch 1 taken 2002962 times.
2002962 Put(binary_, v);
168 }
169 }
170 316 cgltf_material& material = materials_[m];
171 316 material.name = Name(scene.material.name);
172 316 material.has_pbr_metallic_roughness = 1;
173 316 auto& pbr = material.pbr_metallic_roughness;
174
2/2
✓ Branch 0 taken 948 times.
✓ Branch 1 taken 316 times.
1264 for (std::size_t c = 0; c < 3; ++c) {
175 948 pbr.base_color_factor[c] = static_cast<float>(scene.material.colour[c]);
176 }
177 316 pbr.base_color_factor[3] = 1.0f;
178 316 pbr.metallic_factor = 0.0f;
179 316 pbr.roughness_factor = 1.0f;
180 316 cgltf_primitive& primitive = primitives_[m];
181 316 primitive.type = cgltf_primitive_type_triangles;
182 316 primitive.indices =
183 316 AddAccessor(view, start, cgltf_buffer_view_type_indices, cgltf_component_type_r_32u,
184 316 cgltf_type_scalar, 3 * scene.mesh.triangles.size());
185 316 primitive.material = &material;
186 316 primitive.attributes = attributes;
187 316 primitive.attributes_count = count;
188 316 meshes_[m].primitives = &primitive;
189 316 meshes_[m].primitives_count = 1;
190 316 }
191
192 360 void AddNode(std::size_t n, const SceneNode& node) {
193 360 cgltf_node& out = nodes_[n];
194 360 out.mesh = &meshes_[node.mesh];
195
1/1
✓ Branch 2 taken 360 times.
360 const auto q = RotationQuaternion(YUp(node.pose.rotation));
196 360 const Vec3d t = YUp(node.pose.translation);
197 360 out.has_translation = out.has_rotation = out.has_scale = 1;
198 360 const std::array<double, 3> translation{t.x, t.y, t.z};
199 360 const std::array<double, 3> scale{node.scale.x, node.scale.z, node.scale.y};
200
2/2
✓ Branch 0 taken 1080 times.
✓ Branch 1 taken 360 times.
1440 for (std::size_t c = 0; c < 3; ++c) {
201 1080 out.translation[c] = static_cast<float>(translation[c]);
202 1080 out.scale[c] = static_cast<float>(scale[c]);
203 }
204
2/2
✓ Branch 0 taken 1440 times.
✓ Branch 1 taken 360 times.
1800 for (std::size_t c = 0; c < 4; ++c) {
205 1440 out.rotation[c] = static_cast<float>(q[c]);
206 }
207 360 roots_[n] = &out;
208 360 }
209
210 std::string binary_;
211 std::deque<std::string> names_;
212 std::vector<cgltf_buffer_view> views_;
213 std::vector<cgltf_accessor> accessors_;
214 std::vector<cgltf_attribute> attributes_;
215 std::vector<cgltf_material> materials_;
216 std::vector<cgltf_primitive> primitives_;
217 std::vector<cgltf_mesh> meshes_;
218 std::vector<cgltf_node> nodes_;
219 std::vector<cgltf_node*> roots_;
220 };
221
222 /// Adds the triangles of `node` and its children, placed by their world transforms, to `read`.
223 89 void AddNode(const cgltf_node& node, SceneMesh& read) {
224
1/2
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
89 if (node.mesh != nullptr) {
225 cgltf_float world[16];
226 89 cgltf_node_transform_world(&node, world);
227
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 89 times.
178 for (std::size_t p = 0; p < node.mesh->primitives_count; ++p) {
228 89 const cgltf_primitive& primitive = node.mesh->primitives[p];
229 89 const cgltf_accessor* positions = nullptr;
230
2/2
✓ Branch 0 taken 172 times.
✓ Branch 1 taken 89 times.
261 for (std::size_t a = 0; a < primitive.attributes_count; ++a) {
231
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 83 times.
172 if (primitive.attributes[a].type == cgltf_attribute_type_position) {
232 89 positions = primitive.attributes[a].data;
233 }
234 }
235
2/4
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 89 times.
89 if (primitive.type != cgltf_primitive_type_triangles || positions == nullptr) {
236 ✗ continue;
237 }
238 89 const auto offset = static_cast<std::uint32_t>(read.mesh.positions.size());
239
2/2
✓ Branch 0 taken 15101 times.
✓ Branch 1 taken 89 times.
15190 for (std::size_t v = 0; v < positions->count; ++v) {
240 15101 cgltf_float q[3] = {};
241
1/1
✓ Branch 1 taken 15101 times.
15101 cgltf_accessor_read_float(positions, v, q, 3);
242 15101 const Vec3d placed{world[0] * q[0] + world[4] * q[1] + world[8] * q[2] + world[12],
243 15101 world[1] * q[0] + world[5] * q[1] + world[9] * q[2] + world[13],
244 15101 world[2] * q[0] + world[6] * q[1] + world[10] * q[2] +
245 15101 world[14]};
246
1/1
✓ Branch 2 taken 15101 times.
15101 read.mesh.positions.push_back(ZUp(placed));
247 }
248 89 const std::size_t count =
249
1/2
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
89 primitive.indices ? primitive.indices->count : positions->count;
250
2/2
✓ Branch 0 taken 24852 times.
✓ Branch 1 taken 89 times.
24941 for (std::size_t i = 0; i + 2 < count; i += 3) {
251 24852 std::array<std::uint32_t, 3> triangle{};
252
2/2
✓ Branch 0 taken 74556 times.
✓ Branch 1 taken 24852 times.
99408 for (std::size_t c = 0; c < 3; ++c) {
253 const std::size_t index =
254
1/2
✓ Branch 0 taken 74556 times.
✗ Branch 1 not taken.
74556 primitive.indices ? cgltf_accessor_read_index(primitive.indices, i + c)
255 74556 : i + c;
256 74556 triangle[c] = offset + static_cast<std::uint32_t>(index);
257 }
258
1/1
✓ Branch 1 taken 24852 times.
24852 read.mesh.triangles.push_back(triangle);
259 }
260
9/20
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 89 times.
✓ Branch 6 taken 89 times.
✓ Branch 8 taken 13 times.
✓ Branch 9 taken 76 times.
✓ Branch 10 taken 89 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 89 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 13 times.
✓ Branch 16 taken 76 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
267 if (primitive.material != nullptr && read.material == MeshMaterial{}) {
261 13 const cgltf_material& material = *primitive.material;
262
2/3
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 13 times.
13 read.material.name = material.name ? material.name : "default";
263
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 13 times.
52 for (std::size_t c = 0; c < 3; ++c) {
264 39 read.material.colour[c] = material.pbr_metallic_roughness.base_color_factor[c];
265 }
266 }
267 }
268 }
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 for (std::size_t c = 0; c < node.children_count; ++c) {
270 ✗ AddNode(*node.children[c], read);
271 }
272 89 }
273
274 } // namespace
275
276 52 void WriteGltf(const std::filesystem::path& path, const std::vector<SceneMesh>& meshes,
277 const std::vector<SceneNode>& nodes) {
278
2/2
✓ Branch 1 taken 52 times.
✓ Branch 4 taken 51 times.
52 GltfDocument(meshes, nodes).Write(path);
279 51 }
280
281 10 SceneMesh ReadGltf(const std::filesystem::path& path) {
282
1/1
✓ Branch 1 taken 10 times.
10 const std::string file = path.string();
283 10 cgltf_options options{};
284 10 cgltf_data* parsed = nullptr;
285
3/3
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 9 times.
10 if (cgltf_parse_file(&options, file.c_str(), &parsed) != cgltf_result_success) {
286
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
1 throw std::runtime_error("Cannot read glTF " + file);
287 }
288 9 const std::unique_ptr<cgltf_data, decltype(&cgltf_free)> data(parsed, cgltf_free);
289
3/5
✓ Branch 3 taken 9 times.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 9 times.
18 if (cgltf_load_buffers(&options, data.get(), file.c_str()) != cgltf_result_success ||
290
2/3
✓ Branch 2 taken 9 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 9 times.
9 cgltf_validate(data.get()) != cgltf_result_success) {
291
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw std::runtime_error("Cannot load the buffers of glTF " + file);
292 }
293
1/1
✓ Branch 1 taken 9 times.
9 SceneMesh read;
294
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 if (data->scene != nullptr) {
295
2/2
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 9 times.
98 for (std::size_t n = 0; n < data->scene->nodes_count; ++n) {
296
1/1
✓ Branch 2 taken 89 times.
89 AddNode(*data->scene->nodes[n], read);
297 }
298 } else {
299
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (std::size_t n = 0; n < data->nodes_count; ++n) {
300
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (data->nodes[n].parent == nullptr) {
301
0/1
✗ Branch 2 not taken.
✗ AddNode(data->nodes[n], read);
302 }
303 }
304 }
305 18 return read;
306 10 }
307
308 } // namespace terraingen::detail
309