TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
264 / 266
99.2%
Branches:
249 / 294
84.7%
Functions:
53 / 53
100.0%

config/generate.cpp
Line Branch Exec Source
1 #include "terraingen/config/generate.h"
2
3 #include "terraingen/config/build.h"
4 #include "terraingen/config/json.h"
5 #include "terraingen/mesh/io/icq.h"
6 #include "terraingen/mesh/io/mesh_io.h"
7 #include "terraingen/pipeline/body_mesh.h"
8 #include "terraingen/pipeline/height_mesh.h"
9 #include "terraingen/sources/geotiff_source.h"
10 #include "terraingen/sources/source_chain.h"
11 #include "terraingen/util/log.h"
12
13 #include <algorithm>
14 #include <cmath>
15 #include <iomanip>
16 #include <numbers>
17 #include <optional>
18 #include <sstream>
19 #include <stdexcept>
20 #include <string>
21 #include <type_traits>
22 #include <variant>
23 #include <vector>
24
25 namespace terraingen {
26
27 namespace {
28
29 /// The projection of a terrain's raster base, or geographic keys on a body without one.
30 153 GeoKeys KeysOf(const Terrain& terrain) {
31
3/11
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 142 times.
✓ Branch 5 taken 11 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
153 GeoKeys keys = terrain.source ? terrain.source->Keys() : GeoKeys{};
32
5/6
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 140 times.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 140 times.
153 if (terrain.surface.IsBody() && keys.Empty()) {
33
1/1
✓ Branch 2 taken 13 times.
13 keys = GeographicSphereKeys(terrain.surface.Radius());
34 }
35 153 return keys;
36 }
37
38 /// The grid of the terrain's base, or none without one.
39 257 std::optional<RasterGrid> BaseGrid(const Terrain& terrain) {
40
3/3
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 244 times.
✓ Branch 5 taken 13 times.
257 return terrain.source ? std::optional<RasterGrid>(terrain.source->Grid()) : std::nullopt;
41 }
42
43 /// The sample spacing in metres an output lists features at; 0 for its grid's.
44 187 double SpacingOf(const OutputSpec& output) {
45
1/1
✓ Branch 1 taken 187 times.
187 return std::visit(
46 187 [](const auto& o) {
47 if constexpr (config::FeatureOutput<std::decay_t<decltype(o)>>) {
48 64 return o.spacing.value_or(0.0);
49 }
50 123 return 0.0;
51 },
52 374 output);
53 }
54
55 /// The boulders placed over one grid at one spacing, shared by the outputs that list them.
56 struct PlacedBoulderSet {
57 RasterGrid grid;
58 double spacing = 0.0;
59 BoulderFeatures features;
60 };
61
62 /// One step of generating a terrain and the time it took over every grid.
63 struct StepTime {
64 std::string name;
65 double milliseconds = 0.0;
66 };
67
68 /// A sample spacing in metres to three significant figures, or in kilometres from 1 km.
69 145 std::string FormatSpacing(double metres) {
70
1/1
✓ Branch 1 taken 145 times.
145 std::ostringstream text;
71 145 text << std::setprecision(3);
72
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 145 times.
145 if (metres >= 1000.0) {
73
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ text << metres / 1000.0 << " km";
74 } else {
75
2/2
✓ Branch 1 taken 145 times.
✓ Branch 4 taken 145 times.
145 text << metres << " m";
76 }
77
1/1
✓ Branch 1 taken 145 times.
290 return text.str();
78 145 }
79
80 /// Logs the time of each step that ran and its share of `total` milliseconds.
81 142 void LogStepTimes(const std::vector<StepTime>& steps, std::size_t grids, double total) {
82 142 std::size_t width = 0;
83
2/2
✓ Branch 4 taken 576 times.
✓ Branch 5 taken 142 times.
718 for (const StepTime& step : steps) {
84 576 width = std::max(width, step.name.size());
85 }
86
3/3
✓ Branch 0 taken 139 times.
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 142 times.
142 LogInfo("pipeline", "Time per step over ", grids, grids == 1 ? " grid" : " grids", ", of ",
87
1/1
✓ Branch 1 taken 142 times.
284 FormatDuration(total), ":");
88
2/2
✓ Branch 5 taken 576 times.
✓ Branch 6 taken 142 times.
718 for (const StepTime& step : steps) {
89
2/2
✓ Branch 0 taken 261 times.
✓ Branch 1 taken 315 times.
576 if (step.milliseconds <= 0.0) {
90 261 continue;
91 }
92
1/1
✓ Branch 1 taken 315 times.
315 std::ostringstream line;
93
4/4
✓ Branch 1 taken 315 times.
✓ Branch 4 taken 315 times.
✓ Branch 9 taken 315 times.
✓ Branch 12 taken 315 times.
315 line << " " << std::left << std::setw(static_cast<int>(width)) << step.name << std::right
94
2/2
✓ Branch 3 taken 315 times.
✓ Branch 6 taken 315 times.
630 << std::setw(10) << FormatDuration(step.milliseconds) << std::setw(5)
95
2/2
✓ Branch 1 taken 315 times.
✓ Branch 4 taken 315 times.
315 << std::llround(100.0 * step.milliseconds / std::fmax(total, 1e-9)) << "%";
96
2/2
✓ Branch 1 taken 315 times.
✓ Branch 5 taken 315 times.
315 LogInfo("pipeline", line.str());
97 315 }
98 142 }
99
100 /// What `WriteOutputs` writes one output from.
101 struct OutputContext {
102 const GeneratedTerrain& generated;
103 std::optional<RasterGrid> grid; ///< The grid the output covers; none for a mesh.
104 double spacing = 0.0; ///< The output's sample spacing; 0 for its grid's.
105 const TerrainConfig* config = nullptr; ///< The configuration the outputs come from.
106 std::vector<PlacedBoulderSet>* placed = nullptr; ///< Boulders placed for earlier outputs.
107
108 /// The boulders placed over the output's grid, placed once for every output that lists them.
109 34 const BoulderFeatures& Boulders() const {
110
2/2
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 24 times.
34 for (const PlacedBoulderSet& set : *placed) {
111
3/6
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
10 if (set.grid == *grid && set.spacing == spacing) {
112 10 return set.features;
113 }
114 }
115
1/1
✓ Branch 2 taken 24 times.
48 placed->push_back({*grid, spacing, PlacedBoulders(generated.terrain, *grid, spacing)});
116 24 return placed->back().features;
117
1/1
✓ Branch 2 taken 24 times.
24 }
118
119 /// The heights and albedo generated over the output's grid.
120 /// @throws std::runtime_error if they were not generated.
121 115 const GeneratedGrid& Generated(const std::filesystem::path& path) const {
122
1/2
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
115 const GeneratedGrid* found = grid ? generated.Find(*grid) : nullptr;
123
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 114 times.
115 if (!found) {
124
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("No heights generated for " + path.string());
125 }
126 114 return *found;
127 }
128
129 /// The centre of the output's grid, or the origin for a whole-body list.
130 14 Vec2d Centre() const {
131
1/1
✓ Branch 2 taken 14 times.
14 const Bounds2d bounds = grid->Bounds();
132 14 return {0.5 * (bounds.minX + bounds.maxX), 0.5 * (bounds.minY + bounds.maxY)};
133 }
134 };
135
136 /// Each `Write` returns whether it wrote the file.
137 87 bool Write(const HeightmapOutput& output, const std::filesystem::path& path,
138 const OutputContext& context) {
139
1/1
✓ Branch 1 taken 86 times.
86 WriteGeoTiff(path, context.Generated(path).heights, context.generated.geoKeys,
140
1/1
✓ Branch 1 taken 86 times.
87 {.compression = output.compression, .tileSize = output.tileSize});
141 86 return true;
142 }
143
144 13 bool Write(const AlbedoOutput& output, const std::filesystem::path& path,
145 const OutputContext& context) {
146 13 const std::vector<Raster<float>>& albedo = context.Generated(path).albedo;
147
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 if (albedo.empty()) {
148
0/3
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ throw std::runtime_error("No albedo to write to " + path.string());
149 }
150 26 WriteGeoTiff(path, albedo, context.generated.geoKeys,
151
1/1
✓ Branch 1 taken 13 times.
13 {.compression = output.compression, .tileSize = output.tileSize});
152 13 return true;
153 }
154
155 /// The material rasters generated over the output's grid.
156 /// @throws std::runtime_error if they were not generated.
157 12 const std::vector<Raster<float>>& MaterialsOf(const OutputContext& context,
158 const std::filesystem::path& path) {
159 12 const std::vector<Raster<float>>& materials = context.Generated(path).materials;
160
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
12 if (materials.empty()) {
161
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
1 throw std::runtime_error("No materials to write to " + path.string());
162 }
163 11 return materials;
164 }
165
166 7 bool Write(const MaterialsOutput& output, const std::filesystem::path& path,
167 const OutputContext& context) {
168
1/1
✓ Branch 1 taken 6 times.
6 WriteGeoTiff(path, MaterialsOf(context, path), context.generated.geoKeys,
169
1/1
✓ Branch 1 taken 6 times.
7 {.compression = output.compression, .tileSize = output.tileSize});
170 6 return true;
171 }
172
173 5 bool Write(const PhotometryOutput& output, const std::filesystem::path& path,
174 const OutputContext& context) {
175
1/1
✓ Branch 1 taken 5 times.
5 const std::vector<Raster<float>>& materials = MaterialsOf(context, path);
176 const MaterialPhotometry photometry =
177
1/1
✓ Branch 2 taken 5 times.
5 config::BuildPhotometry(context.config->materials->photometry);
178
1/1
✓ Branch 1 taken 5 times.
5 WriteGeoTiff(path, EvaluatePhotometry(materials, photometry), context.generated.geoKeys,
179
1/1
✓ Branch 1 taken 5 times.
5 {.compression = output.compression, .tileSize = output.tileSize});
180 5 return true;
181 }
182
183 30 bool Write(const CraterListOutput& output, const std::filesystem::path& path,
184 const OutputContext& context) {
185 30 const Stopwatch stopwatch;
186 const CraterFeatures placed =
187
1/1
✓ Branch 2 taken 30 times.
30 PlacedCraters(context.generated.terrain, *context.grid, context.spacing);
188
1/1
✓ Branch 1 taken 30 times.
30 WriteCraterList(placed.craters, path, output.format, placed.attributes);
189
2/2
✓ Branch 1 taken 30 times.
✓ Branch 6 taken 30 times.
30 LogInfo("pipeline", "Wrote ", placed.craters.Size(), " craters to ", path.string(), " in ",
190
2/2
✓ Branch 1 taken 30 times.
✓ Branch 4 taken 30 times.
60 FormatDuration(stopwatch.Milliseconds()));
191 30 return true;
192 30 }
193
194 22 bool Write(const BoulderListOutput& output, const std::filesystem::path& path,
195 const OutputContext& context) {
196 22 const Stopwatch stopwatch;
197
1/1
✓ Branch 1 taken 22 times.
22 const std::vector<Boulder>& boulders = context.Boulders().boulders;
198
1/1
✓ Branch 1 taken 22 times.
22 WriteBoulderList(boulders, path, output.format);
199
2/2
✓ Branch 1 taken 22 times.
✓ Branch 6 taken 22 times.
22 LogInfo("pipeline", "Wrote ", boulders.size(), " boulders to ", path.string(), " in ",
200
2/2
✓ Branch 1 taken 22 times.
✓ Branch 4 taken 22 times.
44 FormatDuration(stopwatch.Milliseconds()));
201 22 return true;
202 }
203
204 12 bool Write(const BoulderModelsOutput& output, const std::filesystem::path& path,
205 const OutputContext& context) {
206 12 const Stopwatch stopwatch;
207
1/1
✓ Branch 1 taken 12 times.
12 const std::vector<BoulderModel>& models = context.Boulders().models;
208
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
12 if (models.empty()) {
209
2/2
✓ Branch 1 taken 1 time.
✓ Branch 5 taken 1 time.
1 LogWarning("pipeline", "No boulder models to write to ", path.string());
210 1 return false;
211 }
212
1/1
✓ Branch 1 taken 11 times.
11 WriteBoulderModels(models, path, output.creaseAngle, context.generated.terrain.surface,
213
1/1
✓ Branch 1 taken 11 times.
11 context.Centre(), output.instanced);
214
2/2
✓ Branch 1 taken 11 times.
✓ Branch 6 taken 11 times.
11 LogInfo("pipeline", "Wrote ", models.size(), " boulder models to ", path.string(), " in ",
215
2/2
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
22 FormatDuration(stopwatch.Milliseconds()));
216 11 return true;
217 }
218
219 7 bool Write(const MeshOutput& output, const std::filesystem::path& path,
220 const OutputContext& context) {
221 7 const Stopwatch stopwatch;
222
3/3
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 5 times.
7 if (IsIcqPath(path)) {
223
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 WriteIcq(path, BodyIcq(context.generated.terrain, output.cellsPerFace,
224 2 output.spacing.value_or(0.0)));
225
1/1
✓ Branch 2 taken 2 times.
2 LogInfo("pipeline", "Wrote an ICQ shape model of Q = ", output.cellsPerFace, " to ",
226
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
4 path.string(), " in ", FormatDuration(stopwatch.Milliseconds()));
227 2 return true;
228 }
229 const TriangleMesh mesh =
230
1/1
✓ Branch 2 taken 5 times.
5 BodyMesh(context.generated.terrain, output.cellsPerFace, output.spacing.value_or(0.0));
231
1/1
✓ Branch 1 taken 5 times.
5 ShadedMesh shaded = WithCreases(mesh, std::numbers::pi);
232 5 WriteMesh(path, {std::move(shaded.mesh), std::move(shaded.normals)});
233
1/1
✓ Branch 3 taken 5 times.
5 LogInfo("pipeline", "Wrote a body mesh of ", mesh.triangles.size(), " triangles to ",
234
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
10 path.string(), " in ", FormatDuration(stopwatch.Milliseconds()));
235 5 return true;
236
3/9
✓ Branch 5 taken 5 times.
✓ Branch 8 taken 5 times.
✓ Branch 11 taken 5 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
20 }
237
238 3 bool Write(const TerrainMeshOutput& /*output*/, const std::filesystem::path& path,
239 const OutputContext& context) {
240 3 const Stopwatch stopwatch;
241 6 TriangleMesh mesh = HeightMesh(context.Generated(path).heights,
242
3/3
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
3 context.generated.terrain.surface, context.Centre());
243 3 const std::size_t triangles = mesh.triangles.size();
244
1/1
✓ Branch 1 taken 3 times.
3 std::vector<Vec3d> normals = VertexNormals(mesh);
245 3 WriteMesh(path, {std::move(mesh), std::move(normals), {"terrain", {0.5, 0.5, 0.5}}});
246
2/2
✓ Branch 1 taken 3 times.
✓ Branch 5 taken 3 times.
3 LogInfo("pipeline", "Wrote a terrain mesh of ", triangles, " triangles to ", path.string(),
247
1/1
✓ Branch 1 taken 3 times.
6 " in ", FormatDuration(stopwatch.Milliseconds()));
248 3 return true;
249
3/9
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
✓ Branch 11 taken 3 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
12 }
250
251 /// Whether an output writes the material map or its photometry.
252 350 bool MapsMaterials(const OutputSpec& output) {
253
4/4
✓ Branch 1 taken 332 times.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 317 times.
682 return std::holds_alternative<MaterialsOutput>(output) ||
254 682 std::holds_alternative<PhotometryOutput>(output);
255 }
256
257 /// Whether an output writes the heights, albedo or materials over its grid.
258 502 bool WritesGrid(const OutputSpec& output) {
259
2/2
✓ Branch 1 taken 186 times.
✓ Branch 2 taken 34 times.
722 return std::holds_alternative<HeightmapOutput>(output) ||
260
2/2
✓ Branch 1 taken 176 times.
✓ Branch 2 taken 10 times.
406 std::holds_alternative<AlbedoOutput>(output) ||
261
4/4
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 282 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 154 times.
908 std::holds_alternative<TerrainMeshOutput>(output) || MapsMaterials(output);
262 }
263
264 } // namespace
265
266 118 const GeneratedGrid* GeneratedTerrain::Find(const RasterGrid& grid) const {
267
2/2
✓ Branch 5 taken 120 times.
✓ Branch 6 taken 2 times.
122 for (const GeneratedGrid& generated : grids) {
268
3/3
✓ Branch 1 taken 120 times.
✓ Branch 4 taken 116 times.
✓ Branch 5 taken 4 times.
120 if (RasterGrid::FromRaster(generated.heights) == grid) {
269 116 return &generated;
270 }
271 }
272 2 return nullptr;
273 }
274
275 154 GeneratedTerrain GenerateTerrain(const TerrainConfig& config,
276 const std::filesystem::path& directory, unsigned threads,
277 std::optional<Backend> backend) {
278
1/1
✓ Branch 1 taken 153 times.
154 GeneratedTerrain generated{BuildTerrain(config, directory)};
279
2/2
✓ Branch 1 taken 153 times.
✓ Branch 5 taken 153 times.
153 generated.backend = ResolveBackend(backend.value_or(DefaultBackend()));
280 153 const Terrain& terrain = generated.terrain;
281 153 const Stopwatch stopwatch;
282
1/1
✓ Branch 1 taken 153 times.
153 const std::optional<RasterGrid> base = BaseGrid(terrain);
283 153 std::vector<RasterGrid> grids;
284
2/2
✓ Branch 5 taken 251 times.
✓ Branch 6 taken 153 times.
404 for (const OutputSpec& output : config.outputs) {
285
2/2
✓ Branch 1 taken 174 times.
✓ Branch 2 taken 77 times.
251 if (WritesGrid(output)) {
286
1/1
✓ Branch 1 taken 174 times.
174 const RasterGrid grid = *config::OutputGrid(output, base, terrain.surface);
287
3/3
✓ Branch 4 taken 174 times.
✓ Branch 7 taken 145 times.
✓ Branch 8 taken 29 times.
174 if (std::find(grids.begin(), grids.end(), grid) == grids.end()) {
288
1/1
✓ Branch 1 taken 145 times.
145 grids.push_back(grid);
289 }
290 }
291 }
292 153 std::vector<StepTime> steps;
293
2/2
✓ Branch 1 taken 309 times.
✓ Branch 2 taken 153 times.
462 for (std::size_t n = 0; n < config.layers.size(); ++n) {
294
4/4
✓ Branch 1 taken 309 times.
✓ Branch 4 taken 309 times.
✓ Branch 7 taken 309 times.
✓ Branch 10 taken 309 times.
618 steps.push_back({"layer " + std::to_string(n + 1) + " " + LayerType(config.layers[n])});
295 }
296 153 const std::size_t albedoStep = steps.size();
297 153 steps.push_back({"albedo"});
298 153 steps.push_back({"materials"});
299 321 const auto timed = [&](std::size_t step, double milliseconds) {
300 321 steps[step].milliseconds += milliseconds;
301
2/2
✓ Branch 1 taken 321 times.
✓ Branch 6 taken 321 times.
321 LogInfo("pipeline", " ", steps[step].name, " in ", FormatDuration(milliseconds));
302 321 };
303 174 const auto generatedOver = [&](const RasterGrid& grid) -> GeneratedGrid& {
304
2/2
✓ Branch 5 taken 32 times.
✓ Branch 6 taken 145 times.
177 for (GeneratedGrid& existing : generated.grids) {
305
3/3
✓ Branch 1 taken 32 times.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 3 times.
32 if (RasterGrid::FromRaster(existing.heights) == grid) {
306 29 return existing;
307 }
308 }
309
1/1
✓ Branch 3 taken 145 times.
145 LogInfo("pipeline", "Grid ", generated.grids.size() + 1, " of ", grids.size(), ": ",
310
2/2
✓ Branch 1 taken 145 times.
✓ Branch 4 taken 145 times.
290 grid.width, "x", grid.height, " at ", FormatSpacing(grid.Spacing(terrain.surface)));
311 145 EvaluationOptions options{0.0, threads, generated.backend};
312 145 options.onLayer = timed;
313 145 generated.grids.push_back({Evaluate(terrain, grid, options), {}});
314 145 return generated.grids.back();
315
2/6
✓ Branch 1 taken 145 times.
✓ Branch 5 taken 145 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
290 };
316 153 std::optional<Albedo> albedo;
317
2/2
✓ Branch 4 taken 251 times.
✓ Branch 5 taken 153 times.
404 for (const OutputSpec& output : config.outputs) {
318
2/2
✓ Branch 1 taken 77 times.
✓ Branch 2 taken 174 times.
251 if (!WritesGrid(output)) {
319 77 continue;
320 }
321
1/1
✓ Branch 1 taken 174 times.
174 const RasterGrid grid = *config::OutputGrid(output, base, terrain.surface);
322
1/1
✓ Branch 1 taken 174 times.
174 GeneratedGrid& target = generatedOver(grid);
323
5/6
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 157 times.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 17 times.
✓ Branch 7 taken 157 times.
174 if (std::holds_alternative<AlbedoOutput>(output) && target.albedo.empty()) {
324 17 const Stopwatch albedoTime;
325
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 if (!albedo) {
326
1/1
✓ Branch 1 taken 17 times.
17 albedo = BuildAlbedo(config, terrain, directory);
327 }
328
1/1
✓ Branch 2 taken 17 times.
17 const TerrainGround ground(terrain, terrain.layers.size());
329
1/1
✓ Branch 2 taken 17 times.
34 target.albedo = EvaluateAlbedo(*albedo, grid,
330 17 {.spacing = 0.0, .threads = threads, .ground = &ground});
331
2/2
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
17 timed(albedoStep, albedoTime.Milliseconds());
332 17 }
333
6/6
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 163 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 168 times.
174 if (MapsMaterials(output) && target.materials.empty()) {
334 6 const Stopwatch materialsTime;
335
1/1
✓ Branch 2 taken 6 times.
12 target.materials = EvaluateMaterials(terrain, config.materials->options, grid,
336 18 EvaluationOptions{0.0, threads});
337
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 timed(albedoStep + 1, materialsTime.Milliseconds());
338 }
339 }
340
1/1
✓ Branch 1 taken 153 times.
153 generated.geoKeys = KeysOf(terrain);
341
1/1
✓ Branch 1 taken 153 times.
153 generated.seconds = stopwatch.Milliseconds() / 1000.0;
342
2/2
✓ Branch 1 taken 142 times.
✓ Branch 2 taken 11 times.
153 if (!generated.grids.empty()) {
343
2/2
✓ Branch 1 taken 142 times.
✓ Branch 5 taken 142 times.
142 LogStepTimes(steps, generated.grids.size(), stopwatch.Milliseconds());
344 }
345 306 return generated;
346
6/12
✓ Branch 2 taken 309 times.
✓ Branch 5 taken 309 times.
✓ Branch 8 taken 153 times.
✓ Branch 11 taken 153 times.
✓ Branch 14 taken 153 times.
✓ Branch 17 taken 153 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
1536 }
347
348 88 std::string Summary(const GeneratedTerrain& terrain) {
349
1/1
✓ Branch 1 taken 88 times.
88 std::ostringstream text;
350 88 text << std::setprecision(3);
351
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 85 times.
88 if (terrain.grids.empty()) {
352
1/1
✓ Branch 1 taken 3 times.
6 return "Built the terrain; no output grids to evaluate before writing";
353 }
354
2/2
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 2 times.
85 if (terrain.grids.size() == 1) {
355
3/3
✓ Branch 1 taken 83 times.
✓ Branch 6 taken 83 times.
✓ Branch 9 taken 83 times.
83 text << "Generated a " << terrain.grids[0].heights.Width() << "x"
356
2/2
✓ Branch 3 taken 83 times.
✓ Branch 6 taken 83 times.
83 << terrain.grids[0].heights.Height() << " terrain";
357 } else {
358
3/3
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
2 text << "Generated " << terrain.grids.size() << " grids (";
359
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 for (std::size_t i = 0; i < terrain.grids.size(); ++i) {
360
5/5
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 4 times.
✓ Branch 8 taken 4 times.
✓ Branch 11 taken 4 times.
4 text << (i > 0 ? ", " : "") << terrain.grids[i].heights.Width() << "x"
361
1/1
✓ Branch 3 taken 4 times.
4 << terrain.grids[i].heights.Height();
362 }
363
1/1
✓ Branch 1 taken 2 times.
2 text << ")";
364 }
365
5/5
✓ Branch 1 taken 85 times.
✓ Branch 4 taken 85 times.
✓ Branch 7 taken 85 times.
✓ Branch 10 taken 85 times.
✓ Branch 13 taken 85 times.
85 text << " in " << terrain.seconds << " s on " << DescribeBackend(terrain.backend);
366
1/1
✓ Branch 1 taken 85 times.
85 return text.str();
367 88 }
368
369 104 std::vector<std::filesystem::path> WriteOutputs(const TerrainConfig& config,
370 const GeneratedTerrain& terrain,
371 const std::filesystem::path& directory) {
372
1/1
✓ Branch 1 taken 104 times.
104 const std::optional<RasterGrid> base = BaseGrid(terrain.terrain);
373 104 std::vector<std::filesystem::path> written;
374 104 std::vector<PlacedBoulderSet> placed;
375
2/2
✓ Branch 5 taken 188 times.
✓ Branch 6 taken 100 times.
288 for (const OutputSpec& spec : config.outputs) {
376 const std::string& relative =
377 188 std::visit([](const auto& output) -> const std::string& { return output.path; }, spec);
378
1/1
✓ Branch 1 taken 188 times.
188 const std::filesystem::path path = config::Resolve(relative, directory);
379
1/2
✓ Branch 1 taken 188 times.
✗ Branch 2 not taken.
188 if (path.has_parent_path()) {
380
2/2
✓ Branch 1 taken 188 times.
✓ Branch 4 taken 187 times.
189 std::filesystem::create_directories(path.parent_path());
381 }
382 const OutputContext context{terrain,
383 187 config::OutputGrid(spec, base, terrain.terrain.surface),
384
2/2
✓ Branch 1 taken 187 times.
✓ Branch 4 taken 187 times.
187 SpacingOf(spec), &config, &placed};
385
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 186 times.
187 if (std::holds_alternative<MeshOutput>(spec) == context.grid.has_value()) {
386
4/4
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
✓ Branch 11 taken 1 time.
1 throw std::runtime_error("No grid to write " + path.string() + " over");
387 }
388 186 if (std::visit([&](const auto& output) { return Write(output, path, context); }, spec)) {
389
1/1
✓ Branch 1 taken 183 times.
183 written.push_back(path);
390 }
391 188 }
392 200 return written;
393 108 }
394
395 } // namespace terraingen
396