TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
485 / 487
99.6%
Branches:
420 / 451
93.1%
Functions:
116 / 116
100.0%

config/build.cpp
Line Branch Exec Source
1 #include "terraingen/config/build.h"
2
3 #include "terraingen/albedo/crater_albedo.h"
4 #include "terraingen/albedo/raster_albedo.h"
5 #include "terraingen/albedo/rock_albedo.h"
6 #include "terraingen/albedo/slope_albedo.h"
7 #include "terraingen/albedo/synthetic.h"
8 #include "terraingen/config/fields/checks.h"
9 #include "terraingen/config/generate.h"
10 #include "terraingen/config/validate.h"
11 #include "terraingen/distribution/equilibrium.h"
12 #include "terraingen/distribution/ivanov.h"
13 #include "terraingen/distribution/log_polynomial.h"
14 #include "terraingen/distribution/minimum.h"
15 #include "terraingen/distribution/neukum.h"
16 #include "terraingen/distribution/power_law.h"
17 #include "terraingen/distribution/production_surface.h"
18 #include "terraingen/distribution/spliced.h"
19 #include "terraingen/distribution/tabulated.h"
20 #include "terraingen/layers/craters/ages/age_model.h"
21 #include "terraingen/layers/craters/ages/steady_state.h"
22 #include "terraingen/layers/craters/degradation/degradation_model.h"
23 #include "terraingen/layers/craters/degradation/diffusivities.h"
24 #include "terraingen/layers/craters/degradation/freshness.h"
25 #include "terraingen/layers/craters/profiles/martin_parkes_dunstan.h"
26 #include "terraingen/layers/craters/shaping/morphometry.h"
27 #include "terraingen/layers/detail.h"
28 #include "terraingen/layers/noise_relief.h"
29 #include "terraingen/layers/transforms.h"
30 #include "terraingen/mesh/io/icq.h"
31 #include "terraingen/mesh/io/mesh_io.h"
32 #include "terraingen/mesh/shape_model.h"
33 #include "terraingen/sources/geotiff_source.h"
34 #include "terraingen/sources/source_chain.h"
35 #include "terraingen/util/overloaded.h"
36
37 #include <algorithm>
38 #include <cmath>
39 #include <filesystem>
40 #include <limits>
41 #include <map>
42 #include <memory>
43 #include <mutex>
44 #include <optional>
45 #include <span>
46 #include <string>
47 #include <tuple>
48 #include <utility>
49 #include <variant>
50 #include <vector>
51
52 namespace terraingen {
53
54 namespace {
55
56 using config::At;
57 using config::BuildBoulderOptions;
58 using config::BuildRegionShape;
59 using config::Child;
60 using config::Element;
61 using config::Resolve;
62 using SfdPtr = std::shared_ptr<const SizeFrequencyDistribution>;
63 using LayerPtr = std::shared_ptr<const Layer>;
64
65 660 SfdPtr BuildProduction(ProductionFunction production) {
66
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 637 times.
✗ Branch 3 not taken.
660 switch (production) {
67 10 case ProductionFunction::Neukum:
68
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 return std::make_shared<LogPolynomialSfd>(NeukumProductionFunction());
69 13 case ProductionFunction::Ivanov:
70
2/2
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
13 return std::make_shared<LogPolynomialSfd>(IvanovMarsProductionFunction());
71 637 case ProductionFunction::ExtendedNeukum:
72 637 break;
73 }
74
2/2
✓ Branch 1 taken 637 times.
✓ Branch 4 taken 637 times.
637 return std::make_shared<SplicedSfd>(ExtendedNeukumProductionFunction());
75 }
76
77 660 Chronology BuildChronology(ChronologyModel chronology) {
78
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 646 times.
660 return chronology == ChronologyModel::Ivanov ? Chronology(IvanovMarsChronology)
79 660 : Chronology(NeukumLunarChronology);
80 }
81
82 318 SfdPtr BuildEquilibrium(EquilibriumModel equilibrium) {
83
1/1
✓ Branch 1 taken 318 times.
636 return std::make_shared<PowerLawSfd>(
84
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 314 times.
✓ Branch 3 taken 4 times.
✓ Branch 6 taken 314 times.
954 equilibrium == EquilibriumModel::Hartmann ? HartmannEquilibrium() : TraskEquilibrium());
85 }
86
87 1090 SfdPtr BuildDensity(const DensitySpec& spec) {
88 return std::visit(
89 1080 Overloaded{
90 342 [](const ProductionSurfaceSpec& s) -> SfdPtr {
91
2/2
✓ Branch 1 taken 342 times.
✓ Branch 4 taken 339 times.
684 return std::make_shared<ProductionSurface>(BuildProduction(s.production), s.ageGyr,
92 1026 BuildChronology(s.chronology));
93 },
94 326 [](const MinimumSpec& s) -> SfdPtr {
95 326 std::vector<SfdPtr> distributions;
96
2/2
✓ Branch 4 taken 645 times.
✓ Branch 5 taken 325 times.
970 for (const DensitySpec& distribution : s.distributions) {
97
2/2
✓ Branch 1 taken 644 times.
✓ Branch 4 taken 644 times.
645 distributions.push_back(BuildDensity(distribution));
98 }
99
1/1
✓ Branch 2 taken 322 times.
647 return std::make_shared<MinimumSfd>(std::move(distributions));
100 326 },
101 59 [](const PowerLawSpec& s) -> SfdPtr {
102
1/1
✓ Branch 1 taken 59 times.
59 return std::make_shared<PowerLawSfd>(s.coefficient, s.exponent);
103 },
104 5 [](const PiecewisePowerLawSpec& s) -> SfdPtr {
105
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 const double start = s.segments.empty() ? 0.0 : s.segments.front().start;
106 5 return std::make_shared<PiecewisePowerLawSfd>(s.segments, SfdKind::Density,
107
1/1
✓ Branch 2 taken 5 times.
10 DiameterRange{start});
108 },
109 8 [](const TabulatedSpec& s) -> SfdPtr {
110 8 return std::make_shared<TabulatedSfd>(s.diameters, s.cumulative, SfdKind::Density,
111
1/1
✓ Branch 1 taken 5 times.
13 s.diameterUnit, s.areaUnit);
112 },
113 9 [](const PiecewiseUniformSpec& s) -> SfdPtr {
114
1/1
✓ Branch 1 taken 9 times.
9 return std::make_shared<PiecewiseUniformSfd>(s.bands, SfdKind::Density, s.total);
115 },
116 334 [](const TraskSpec&) -> SfdPtr {
117
2/2
✓ Branch 1 taken 334 times.
✓ Branch 4 taken 334 times.
334 return std::make_shared<PowerLawSfd>(TraskEquilibrium());
118 },
119 1 [](const HartmannSpec&) -> SfdPtr {
120
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return std::make_shared<PowerLawSfd>(HartmannEquilibrium());
121 },
122 3 [](const MooreSpec& s) -> SfdPtr {
123
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 return std::make_shared<PiecewisePowerLawSfd>(MooreDistribution(s.terrain));
124 },
125 3 [](const ViperSpec&) -> SfdPtr {
126
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 return std::make_shared<PiecewisePowerLawSfd>(ViperSpecification());
127 },
128 },
129
1/1
✓ Branch 1 taken 1080 times.
2170 spec);
130 }
131
132 419 std::shared_ptr<const CraterAgeModel> BuildAges(const AgesSpec& spec) {
133 using Result = std::shared_ptr<const CraterAgeModel>;
134 return std::visit(
135 415 Overloaded{
136
1/1
✓ Branch 1 taken 5 times.
5 [](const FreshAgesSpec&) -> Result { return std::make_shared<FreshAges>(); },
137 96 [](const UniformAgesSpec& s) -> Result {
138
1/1
✓ Branch 1 taken 94 times.
96 return std::make_shared<UniformAges>(s.lifetimeGyr);
139 },
140 318 [](const SteadyStateAgesSpec& s) -> Result {
141
2/2
✓ Branch 1 taken 318 times.
✓ Branch 4 taken 316 times.
636 return std::make_shared<SteadyStateAges>(BuildProduction(s.production),
142
1/1
✓ Branch 1 taken 318 times.
638 BuildEquilibrium(s.equilibrium), s.ageGyr,
143 954 BuildChronology(s.chronology));
144 },
145 },
146
1/1
✓ Branch 1 taken 415 times.
834 spec);
147 }
148
149 423 FreshMorphometry MorphometryOf(CraterMorphometryFit fit) {
150
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 412 times.
423 return fit == CraterMorphometryFit::Mars ? FreshMorphometry(MarsFreshCraterMorphometry)
151 423 : FreshMorphometry(LunarFreshCraterMorphometry);
152 }
153
154 4 ComplexCraterShape ComplexShapeOf(CraterMorphometryFit fit) {
155
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 return fit == CraterMorphometryFit::Mars ? MarsComplexCraterShape() : LunarComplexCraterShape();
156 }
157
158 31 DiffusionAgeModel BuildDiffusionAges(const DiffusionDegradationSpec& spec, const AgesSpec& ages) {
159
6/7
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
31 switch (spec.diffusivity) {
160 18 case DiffusivityModel::Matched: {
161 18 const auto* steady = std::get_if<SteadyStateAgesSpec>(&ages);
162
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 time.
17 return steady && steady->equilibrium == EquilibriumModel::Trask
163
3/3
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 16 times.
52 ? Fassett2022TraskDiffusivity()
164
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 18 times.
18 : Fassett2022HartmannDiffusivity();
165 }
166 6 case DiffusivityModel::Fassett2022Hartmann:
167
2/2
✓ Branch 1 taken 6 times.
✓ Branch 4 taken 6 times.
6 return Fassett2022HartmannDiffusivity();
168 1 case DiffusivityModel::Fassett2022Trask:
169
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return Fassett2022TraskDiffusivity();
170 1 case DiffusivityModel::FassettThomson2014:
171
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 return FassettThomson2014Diffusivity();
172 1 case DiffusivityModel::FassettThomson2014Calibration:
173 2 return [](double, double ageGyr) { return FassettThomson2014DiffusionAge(ageGyr); };
174 4 case DiffusivityModel::PowerLaw:
175 4 ValidatePowerLawDiffusivity(spec.powerLaw);
176 2 return spec.powerLaw;
177 }
178
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ return Fassett2022HartmannDiffusivity();
179 }
180
181 415 std::shared_ptr<const CraterProfile> BuildProfile(const ProfileSpec& spec,
182 CraterMorphometryFit morphometry) {
183 using Result = std::shared_ptr<const CraterProfile>;
184 415 const auto simple = [](const SimpleProfileSpec& simpleSpec) {
185 415 return std::visit(Overloaded{
186 17 [](const FassettThomsonSpec& s) -> Result {
187
1/1
✓ Branch 1 taken 17 times.
17 return std::make_shared<FassettThomsonProfile>(s.rim);
188 },
189 398 [](const MartinParkesDunstanSpec&) -> Result {
190
1/1
✓ Branch 1 taken 398 times.
398 return std::make_shared<MartinParkesDunstanProfile>();
191 },
192 },
193
1/1
✓ Branch 1 taken 415 times.
830 simpleSpec);
194 };
195 415 return std::visit(Overloaded{
196
1/1
✓ Branch 2 taken 15 times.
15 [&](const FassettThomsonSpec& s) -> Result { return simple(s); },
197
1/1
✓ Branch 2 taken 396 times.
396 [&](const MartinParkesDunstanSpec& s) -> Result { return simple(s); },
198 4 [&](const ComplexProfileSpec& s) -> Result {
199
1/1
✓ Branch 1 taken 4 times.
8 return std::make_shared<ComplexCraterProfile>(
200
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
12 simple(s.simple), ComplexShapeOf(morphometry));
201 },
202 },
203
1/1
✓ Branch 1 taken 415 times.
830 spec);
204 }
205
206 /// Sets `models.degradation` from `craters`; diffusion also wears `models.profile`.
207 415 void AddDegradation(const CraterLayerSpec& craters, CraterModels& models) {
208 415 const FreshMorphometry fresh = MorphometryOf(craters.morphometry);
209 415 std::visit(
210 5 Overloaded{
211 368 [&](const FreshnessDegradationSpec& s) {
212 366 models.degradation =
213
1/1
✓ Branch 1 taken 366 times.
368 std::make_shared<FreshnessDegradation>(models.ages, fresh, s.cutoffDepthRatio);
214 366 },
215 16 [&](const NoDegradationSpec& s) {
216
1/1
✓ Branch 1 taken 16 times.
16 models.degradation = std::make_shared<NoDegradation>(fresh, s.depthRatio);
217 16 },
218 31 [&](const DiffusionDegradationSpec& s) {
219
1/1
✓ Branch 1 taken 29 times.
31 const DiffusionAgeModel ages = BuildDiffusionAges(s, craters.ages);
220
1/1
✓ Branch 1 taken 28 times.
29 auto profile = std::make_shared<DiffusedProfile>(models.profile, s.reachRadii);
221
1/1
✓ Branch 1 taken 28 times.
28 models.degradation = std::make_shared<DiffusionDegradation>(fresh, ages, profile);
222 28 models.profile = std::move(profile);
223 29 },
224 },
225
1/1
✓ Branch 1 taken 410 times.
415 craters.degradation);
226 415 }
227
228 /// The distribution a boulder size spec names, or null for the layer's Golombek-Rapp boulders.
229 249 std::shared_ptr<SizeFrequencyDistribution> BuildBoulderSizes(const BoulderSizesSpec& spec) {
230 using Result = std::shared_ptr<SizeFrequencyDistribution>;
231 20 const auto density = [](const DensitySpec& named) {
232
1/1
✓ Branch 1 taken 17 times.
20 return std::const_pointer_cast<SizeFrequencyDistribution>(BuildDensity(named));
233 };
234 return std::visit(
235 249 Overloaded{
236 207 [](const GolombekRappBouldersSpec&) -> Result { return nullptr; },
237 6 [](const Sp8023IntercraterBouldersSpec& s) -> Result {
238
1/1
✓ Branch 1 taken 6 times.
6 return Sp8023IntercraterBlocks(s.curve);
239 },
240
1/1
✓ Branch 1 taken 14 times.
14 [](const Sp8023CraterBouldersSpec& s) -> Result { return Sp8023CraterBlocks(s.curve); },
241 2 [](const SynthterrainBouldersSpec& s) -> Result {
242
1/1
✓ Branch 1 taken 2 times.
2 return SynthterrainBoulderDistribution(s.model);
243 },
244
1/1
✓ Branch 2 taken 11 times.
11 [&](const PowerLawSpec& s) -> Result { return density(s); },
245
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 [&](const PiecewisePowerLawSpec& s) -> Result { return density(s); },
246
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 2 times.
5 [&](const TabulatedSpec& s) -> Result { return density(s); },
247
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 [&](const PiecewiseUniformSpec& s) -> Result { return density(s); },
248 },
249
1/1
✓ Branch 1 taken 246 times.
495 spec);
250 }
251
252 13 std::shared_ptr<const Source> BuildSource(const SourceSpec& spec,
253 const std::filesystem::path& directory,
254 const Surface& surface) {
255 using Result = std::shared_ptr<const Source>;
256 13 return std::visit(Overloaded{
257 12 [&](const RasterSourceSpec& s) -> Result {
258 12 GeoTiffSourceOptions read = s.read;
259
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
12 if (read.pixelSize) {
260 1 *read.pixelSize /= surface.UnitLength();
261 }
262
2/2
✓ Branch 1 taken 12 times.
✓ Branch 4 taken 12 times.
24 return std::make_shared<GeoTiffSource>(Resolve(s.path, directory),
263 24 read);
264 },
265 1 [&](const ChainSourceSpec& s) -> Result {
266 1 std::vector<Result> sources;
267
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 time.
3 for (const SourceSpec& source : s.sources) {
268
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 sources.push_back(BuildSource(source, directory, surface));
269 }
270
1/1
✓ Branch 2 taken 1 time.
2 return std::make_shared<SourceChain>(std::move(sources));
271 1 },
272 },
273
1/1
✓ Branch 1 taken 13 times.
26 spec);
274 }
275
276 /// The point noise is measured from: the base's reference point, or the origin without a base.
277 388 Vec2d ReferencePoint(const Terrain& terrain) {
278
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 385 times.
388 return terrain.source ? terrain.source->ReferencePoint() : Vec2d{};
279 }
280
281 /// What a layer is built on: the base's reference point, the last top-level crater and boulder
282 /// layers so far (for boulder ejecta, secondaries and sand shadows) and the surface.
283 struct LayerInputs {
284 Vec2d referencePoint;
285 const std::filesystem::path& directory;
286 const std::shared_ptr<const CraterLayer>& craters;
287 const std::shared_ptr<const BoulderLayer>& boulders;
288 const Surface& surface;
289 /// The secondaries later layers throw from a crater layer built with these inputs.
290 std::vector<SecondaryCraterOptions> thrown{};
291 };
292
293 128 LayerPtr BuildCraterLayer(const CraterLayerSpec& craters, const std::string& path,
294 const Surface& surface, const std::filesystem::path& directory,
295 std::vector<SecondaryCraterOptions> thrown) {
296
1/1
✓ Branch 1 taken 128 times.
128 CraterLayerOptions options;
297 128 options.secondaries = std::move(thrown);
298 128 options.generate = craters.generate;
299 128 options.seed = craters.seed;
300 128 options.generation.dMin = craters.minDiameter;
301 128 options.generation.dMax = craters.maxDiameter;
302 128 options.generation.spacing = craters.spacing;
303 128 options.generation.maxCraters = craters.maxCraters;
304
2/2
✓ Branch 5 taken 37 times.
✓ Branch 6 taken 128 times.
165 for (Crater crater : craters.manual) {
305 37 crater.manual = true;
306
1/1
✓ Branch 1 taken 37 times.
37 options.generation.manual.Append(crater);
307 }
308 128 options.apply = craters.apply;
309 128 options.surface = surface;
310
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 126 times.
128 if (craters.densityMap) {
311 options.densityMap =
312
2/2
✓ Branch 2 taken 2 times.
✓ Branch 6 taken 2 times.
2 config::BuildDensityMap(*craters.densityMap, Child(path, "density_map"), directory);
313 }
314
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 126 times.
128 if (craters.completeness) {
315 options.generation.completeness =
316
2/2
✓ Branch 2 taken 2 times.
✓ Branch 6 taken 2 times.
2 config::BuildField(*craters.completeness, Child(path, "completeness"), directory);
317 }
318
1/1
✓ Branch 1 taken 128 times.
128 CraterModels models = BuildCraterModels(craters, path);
319
1/1
✓ Branch 1 taken 128 times.
256 return At(path,
320 384 [&] { return std::make_shared<CraterLayer>(std::move(models), std::move(options)); });
321 128 }
322
323 8 LayerPtr BuildSecondaryLayer(const SecondaryLayerSpec& spec,
324 const std::shared_ptr<const CraterLayer>& primaries) {
325 const CraterShapeModels models{
326 8 primaries->Models().profile,
327
1/3
✓ Branch 3 taken 8 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
8 std::make_shared<NoDegradation>(MorphometryOf(spec.morphometry), spec.depthRatio)};
328
1/1
✓ Branch 1 taken 8 times.
16 return std::make_shared<SecondaryCraterLayer>(primaries, models, spec.secondaries);
329 8 }
330
331 374 LayerPtr BuildLayer(const LayerSpec& spec, const std::string& path, const LayerInputs& context) {
332 374 const Surface& surface = context.surface;
333 return std::visit(
334 374 Overloaded{
335 152 [&](const NoiseLayerSpec& s) -> LayerPtr {
336
2/2
✓ Branch 1 taken 152 times.
✓ Branch 4 taken 152 times.
152 return std::make_shared<NoiseRelief>(s.Params(), context.referencePoint, surface);
337 },
338 128 [&](const CraterLayerSpec& s) -> LayerPtr {
339
2/2
✓ Branch 1 taken 128 times.
✓ Branch 4 taken 128 times.
128 return BuildCraterLayer(s, path, surface, context.directory, context.thrown);
340 },
341 48 [&](const BoulderLayerSpec& s) -> LayerPtr {
342
1/1
✓ Branch 1 taken 48 times.
48 BoulderLayerOptions options = BuildBoulderOptions(s, path, context.directory);
343
1/1
✓ Branch 1 taken 48 times.
48 const bool throws = options.ejecta.MinCraterDiameter() > 0.0;
344
1/1
✓ Branch 2 taken 48 times.
96 return std::make_shared<BoulderLayer>(std::move(options),
345
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 37 times.
192 throws ? context.craters : nullptr, surface);
346 48 },
347 7 [&](const RoughnessLayerSpec& s) -> LayerPtr {
348
1/1
✓ Branch 1 taken 7 times.
7 return std::make_shared<RoughnessLayer>(s.roughness, surface);
349 },
350 2 [&](const DuneLayerSpec& s) -> LayerPtr {
351
1/1
✓ Branch 1 taken 2 times.
2 return std::make_shared<DuneLayer>(s.dunes, surface);
352 },
353 1 [&](const PavementLayerSpec& s) -> LayerPtr {
354
1/1
✓ Branch 1 taken 1 time.
1 return std::make_shared<PavementLayer>(s.pavement, surface);
355 },
356 1 [&](const StrataLayerSpec& s) -> LayerPtr {
357
1/1
✓ Branch 1 taken 1 time.
1 return std::make_shared<StrataLayer>(s.strata, surface);
358 },
359 1 [&](const SandShadowLayerSpec& s) -> LayerPtr {
360
1/1
✓ Branch 1 taken 1 time.
1 return std::make_shared<SandShadowLayer>(context.boulders, s.shadows, surface);
361 },
362 4 [&](const SandFillLayerSpec& s) -> LayerPtr {
363
1/1
✓ Branch 1 taken 4 times.
4 return std::make_shared<SandFillLayer>(s.sandFill, surface);
364 },
365 6 [&](const BedformLayerSpec& s) -> LayerPtr {
366
1/1
✓ Branch 1 taken 6 times.
6 return std::make_shared<BedformLayer>(s.bedforms, surface);
367 },
368 5 [&](const ScaleLayerSpec& s) -> LayerPtr {
369
1/1
✓ Branch 1 taken 5 times.
5 return std::make_shared<ScaleLayer>(s.scale, s.offset);
370 },
371 4 [&](const ClampLayerSpec& s) -> LayerPtr {
372
1/1
✓ Branch 1 taken 4 times.
4 return std::make_shared<ClampLayer>(s.min, s.max);
373 },
374 6 [&](const RegionLayerSpec& s) -> LayerPtr {
375
1/1
✓ Branch 1 taken 6 times.
12 return std::make_shared<RegionLayer>(
376
2/2
✓ Branch 2 taken 6 times.
✓ Branch 6 taken 6 times.
12 BuildLayer(*s.layer, Child(path, "layer"), context), BuildRegionShape(s.shape),
377
1/1
✓ Branch 1 taken 6 times.
18 s.feather, s.blend, surface);
378 },
379 8 [&](const SecondaryLayerSpec& s) -> LayerPtr {
380 8 return BuildSecondaryLayer(s, context.craters);
381 },
382 1 [&](const DemDetailLayerSpec& s) -> LayerPtr {
383
1/1
✓ Branch 1 taken 1 time.
1 const auto file = Resolve(s.path, context.directory);
384 1 GeoTiffSourceOptions full = s.read;
385 1 full.lowPass = 0.0;
386
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
2 return std::make_shared<DetailLayer>(std::make_shared<GeoTiffSource>(file, full),
387 1 std::make_shared<GeoTiffSource>(file, s.read),
388
1/1
✓ Branch 1 taken 1 time.
3 s.weight, surface);
389 1 },
390 },
391
1/1
✓ Branch 1 taken 374 times.
748 spec);
392 }
393
394 /// The bands of a raster albedo map, scaled as its metadata or the spec says.
395 2 std::vector<Raster<float>> LoadAlbedoBands(const RasterAlbedoSpec& spec,
396 const std::filesystem::path& directory) {
397
1/1
✓ Branch 1 taken 2 times.
2 const std::filesystem::path path = Resolve(spec.path, directory);
398
1/1
✓ Branch 1 taken 2 times.
2 const GeoTiffInfo info = ReadGeoTiffInfo(path);
399 2 std::vector<Raster<float>> bands;
400
2/2
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 2 times.
4 for (const std::size_t band : spec.bands) {
401
1/1
✓ Branch 1 taken 2 times.
2 Raster<float> raster = ReadGeoTiff(path, band).raster;
402 2 const BandScaling scaling{spec.scale.value_or(info.bandScaling[band].scale),
403 2 spec.offset.value_or(info.bandScaling[band].offset)};
404
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!scaling.Identity()) {
405
2/2
✓ Branch 6 taken 80 times.
✓ Branch 7 taken 1 time.
81 for (float& value : raster.Values()) {
406
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 if (!raster.IsNoData(value)) {
407 80 value = static_cast<float>(static_cast<double>(value) * scaling.scale +
408 80 scaling.offset);
409 }
410 }
411 }
412
1/1
✓ Branch 2 taken 2 times.
2 bands.push_back(std::move(raster));
413 2 }
414 4 return bands;
415 2 }
416
417 22 std::shared_ptr<const AlbedoSource> BuildAlbedoSource(const AlbedoSourceSpec& spec,
418 const Terrain& terrain,
419 const std::filesystem::path& directory) {
420 using Result = std::shared_ptr<const AlbedoSource>;
421 22 return std::visit(Overloaded{
422 20 [&](const SyntheticAlbedoSpec& s) -> Result {
423
1/1
✓ Branch 1 taken 20 times.
40 return std::make_shared<SyntheticAlbedo>(
424
2/2
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
60 s.Params(), ReferencePoint(terrain), terrain.surface);
425 },
426 2 [&](const RasterAlbedoSpec& s) -> Result {
427
1/1
✓ Branch 1 taken 2 times.
4 return std::make_shared<RasterAlbedo>(LoadAlbedoBands(s, directory),
428
1/1
✓ Branch 1 taken 2 times.
6 s.options, terrain.surface);
429 },
430 },
431
1/1
✓ Branch 1 taken 22 times.
44 spec);
432 }
433
434 } // namespace
435
436 namespace config {
437
438 274 std::filesystem::path Resolve(const std::string& path, const std::filesystem::path& directory) {
439
1/1
✓ Branch 1 taken 274 times.
274 const std::filesystem::path resolved = path;
440
6/6
✓ Branch 1 taken 270 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 267 times.
✓ Branch 5 taken 3 times.
✓ Branch 7 taken 267 times.
✓ Branch 10 taken 7 times.
548 return resolved.is_relative() && !directory.empty() ? directory / resolved : resolved;
441 274 }
442
443 125 BoulderLayerOptions BuildBoulderOptions(const BoulderLayerSpec& spec, const std::string& path,
444 const std::filesystem::path& directory) {
445
1/1
✓ Branch 1 taken 125 times.
125 BoulderLayerOptions options = spec.boulders;
446 249 const auto sizes = [&](const BoulderSizesSpec& named, const std::string& key) {
447
2/2
✓ Branch 2 taken 249 times.
✓ Branch 5 taken 246 times.
498 auto built = At(Child(path, key), [&] { return BuildBoulderSizes(named); });
448
6/6
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 207 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 22 times.
✓ Branch 6 taken 224 times.
246 if (built && spec.extrapolate) {
449 22 built->SetAllowExtrapolation(true);
450 }
451 246 return built;
452 125 };
453
2/2
✓ Branch 1 taken 125 times.
✓ Branch 4 taken 124 times.
250 options.sizes.distribution = sizes(spec.sizes, "sizes");
454
2/2
✓ Branch 1 taken 124 times.
✓ Branch 4 taken 122 times.
126 options.ejecta.distribution = sizes(spec.ejectaSizes, "ejecta_sizes");
455
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 121 times.
124 for (std::size_t i = 0; i < spec.shapeFiles.size(); ++i) {
456
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
3 const std::string file = Element(Child(path, "shape_files"), i);
457 try {
458
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
3 options.rendering.shapes.library.push_back(std::make_shared<const SceneMesh>(
459
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 2 times.
6 ReadMesh(Resolve(spec.shapeFiles[i], directory))));
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 } catch (const std::exception& error) {
461
3/3
✓ Branch 3 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 9 taken 1 time.
3 throw ConfigError(file, error.what());
462 1 }
463 3 }
464
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 115 times.
121 if (spec.densityMap) {
465 options.densityMap =
466
2/2
✓ Branch 2 taken 6 times.
✓ Branch 6 taken 6 times.
6 BuildDensityMap(*spec.densityMap, Child(path, "density_map"), directory);
467 }
468
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 115 times.
121 if (spec.slopeMap) {
469 6 const SizeSlopeMapSpec& s = *spec.slopeMap;
470
1/1
✓ Branch 2 taken 6 times.
6 const std::string at = Child(path, "slope_map");
471 options.slopeMap =
472
2/2
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 6 times.
12 SizeSlopeMap{BuildField(s.field, Child(at, "field"), directory), s.measure,
473 6 s.referenceDiameter, s.minExponent, s.maxExponent};
474
1/1
✓ Branch 1 taken 5 times.
12 At(at, [&] { ValidateSizeSlopeMap(*options.slopeMap); });
475 6 }
476
2/2
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 120 times.
127 for (Crater boulder : spec.manual) {
477 7 boulder.manual = true;
478
1/1
✓ Branch 1 taken 7 times.
7 options.manual.push_back(boulder);
479 }
480
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 115 times.
120 if (spec.completeness) {
481 options.completeness =
482
2/2
✓ Branch 2 taken 5 times.
✓ Branch 6 taken 5 times.
5 BuildField(*spec.completeness, Child(path, "completeness"), directory);
483 }
484 240 return options;
485 5 }
486
487 27 std::shared_ptr<const SurfaceField> BuildField(const FieldSpec& spec, const std::string& path,
488 const std::filesystem::path& directory) {
489
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 3 times.
27 if (spec.path.empty()) {
490
1/1
✓ Branch 1 taken 24 times.
24 return std::make_shared<ConstantField>(spec.value);
491 }
492 3 RasterFieldOptions read;
493 3 read.method = spec.method;
494 3 read.scale = spec.scale;
495 3 read.fallback = spec.fallback.value_or(std::numeric_limits<double>::quiet_NaN());
496 3 read.periodicLongitude = spec.periodicLongitude;
497 try {
498
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 1 time.
5 return ReadRasterField(Resolve(spec.path, directory), read, spec.band);
499
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } catch (const std::exception& error) {
500
3/3
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
6 throw ConfigError(path, error.what());
501 2 }
502 }
503
504 10 DensityMap BuildDensityMap(const DensityMapSpec& spec, const std::string& path,
505 const std::filesystem::path& directory) {
506 10 DensityMap map;
507
2/2
✓ Branch 2 taken 10 times.
✓ Branch 5 taken 10 times.
10 map.field = BuildField(spec.field, Child(path, "field"), directory);
508 10 map.measure = spec.measure;
509 10 map.minDiameter = spec.minDiameter;
510 10 map.maxDiameter = spec.maxDiameter.value_or(std::numeric_limits<double>::infinity());
511 10 map.areaUnit = spec.areaUnit;
512 10 map.total = spec.total;
513
1/1
✓ Branch 1 taken 9 times.
20 At(path, [&] { ValidateDensityMap(map); });
514 9 return map;
515 1 }
516
517 1343 RasterGrid RegionGrid(const GridRegionSpec& region, const Surface& surface) {
518 1343 const double cell = region.pixelSize / surface.UnitLength();
519 1343 const double halfX = 0.5 * static_cast<double>(region.width) * cell;
520 1343 const double halfY = 0.5 * static_cast<double>(region.height) * cell;
521 1343 return {region.width, region.height,
522 1343 GeoTransform::NorthUp(region.centreX - halfX, region.centreY + halfY, cell)};
523 }
524
525 10 RasterGrid WholeBodyGrid(const Surface& surface, double spacing) {
526 const auto rows =
527 10 static_cast<std::size_t>(std::max(1.0, std::ceil(180.0 * surface.UnitLength() / spacing)));
528 10 return {2 * rows, rows, GeoTransform::NorthUp(-180.0, 90.0, 180.0 / static_cast<double>(rows))};
529 }
530
531 993 std::optional<RasterGrid> OutputGrid(const OutputSpec& output,
532 const std::optional<RasterGrid>& base,
533 const Surface& surface) {
534 978 const auto covered = [&](const std::optional<GridRegionSpec>& region) {
535
2/2
✓ Branch 1 taken 918 times.
✓ Branch 2 taken 60 times.
978 return region ? std::optional<RasterGrid>(RegionGrid(*region, surface)) : base;
536 993 };
537 154 const auto listed = [&](const auto& list) -> std::optional<RasterGrid> {
538
17/20
std::optional<terraingen::RasterGrid> terraingen::config::OutputGrid(std::variant<terraingen::HeightmapOutput, terraingen::CraterListOutput, terraingen::BoulderListOutput, terraingen::BoulderModelsOutput, terraingen::AlbedoOutput, terraingen::MeshOutput, terraingen::TerrainMeshOutput, terraingen::MaterialsOutput, terraingen::PhotometryOutput> const&, std::optional<terraingen::RasterGrid> const&, terraingen::Surface const&)::{lambda(auto:1 const&)#1}::operator()<terraingen::BoulderModelsOutput>(terraingen::BoulderModelsOutput const&) const:
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 24 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 time.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 time.
✓ Branch 12 taken 24 times.
✓ Branch 13 taken 1 time.
std::optional<terraingen::RasterGrid> terraingen::config::OutputGrid(std::variant<terraingen::HeightmapOutput, terraingen::CraterListOutput, terraingen::BoulderListOutput, terraingen::BoulderModelsOutput, terraingen::AlbedoOutput, terraingen::MeshOutput, terraingen::TerrainMeshOutput, terraingen::MaterialsOutput, terraingen::PhotometryOutput> const&, std::optional<terraingen::RasterGrid> const&, terraingen::Surface const&)::{lambda(auto:1 const&)#1}::operator()<terraingen::ListOutput>(terraingen::ListOutput const&) const:
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 117 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 2 times.
✓ Branch 7 taken 7 times.
✓ Branch 8 taken 3 times.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 6 times.
✓ Branch 12 taken 123 times.
✓ Branch 13 taken 6 times.
154 if (list.region || base || !surface.IsBody() || !list.spacing) {
539 147 return covered(list.region);
540 }
541
2/2
std::optional<terraingen::RasterGrid> terraingen::config::OutputGrid(std::variant<terraingen::HeightmapOutput, terraingen::CraterListOutput, terraingen::BoulderListOutput, terraingen::BoulderModelsOutput, terraingen::AlbedoOutput, terraingen::MeshOutput, terraingen::TerrainMeshOutput, terraingen::MaterialsOutput, terraingen::PhotometryOutput> const&, std::optional<terraingen::RasterGrid> const&, terraingen::Surface const&)::{lambda(auto:1 const&)#1}::operator()<terraingen::BoulderModelsOutput>(terraingen::BoulderModelsOutput const&) const:
✓ Branch 2 taken 1 time.
std::optional<terraingen::RasterGrid> terraingen::config::OutputGrid(std::variant<terraingen::HeightmapOutput, terraingen::CraterListOutput, terraingen::BoulderListOutput, terraingen::BoulderModelsOutput, terraingen::AlbedoOutput, terraingen::MeshOutput, terraingen::TerrainMeshOutput, terraingen::MaterialsOutput, terraingen::PhotometryOutput> const&, std::optional<terraingen::RasterGrid> const&, terraingen::Surface const&)::{lambda(auto:1 const&)#1}::operator()<terraingen::ListOutput>(terraingen::ListOutput const&) const:
✓ Branch 2 taken 6 times.
7 return WholeBodyGrid(surface, *list.spacing);
542 };
543 993 return std::visit(
544
1/1
✓ Branch 1 taken 993 times.
1986 Overloaded{
545 811 [&](const GeoTiffOutput& o) { return covered(o.region); },
546 20 [&](const TerrainMeshOutput& o) { return covered(o.region); },
547 129 [&](const ListOutput& o) { return listed(o); },
548 25 [&](const BoulderModelsOutput& o) { return listed(o); },
549 8 [](const MeshOutput&) -> std::optional<RasterGrid> { return std::nullopt; },
550 },
551 1986 output);
552 }
553
554 20 MaterialPhotometry BuildPhotometry(const PhotometrySpec& spec) {
555 20 MaterialPhotometry photometry = spec.reference == PhotometryReference::MarsPancam
556
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 7 times.
20 ? MarsPancamPhotometry()
557 7 : MaterialPhotometry{};
558
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 20 times.
120 for (std::size_t k = 0; k < kMaterialClasses; ++k) {
559
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 91 times.
100 if (spec.classes[k]) {
560 9 photometry[k] = spec.classes[k];
561 }
562 }
563 20 return photometry;
564 }
565
566 23 RegionShape BuildRegionShape(const RegionShapeSpec& spec) {
567
1/1
✓ Branch 1 taken 23 times.
23 return std::visit(
568 23 Overloaded{
569 13 [](const RectangleRegionSpec& s) {
570 13 return RegionShape::Rectangle({s.minX, s.minY, s.maxX, s.maxY});
571 },
572 10 [](const CircleRegionSpec& s) { return RegionShape::Circle({s.x, s.y}, s.radius); },
573 },
574 46 spec);
575 }
576
577 } // namespace config
578
579 425 CraterModels BuildCraterModels(const CraterLayerSpec& craters, const std::string& path) {
580 425 CraterModels models;
581
2/2
✓ Branch 2 taken 425 times.
✓ Branch 5 taken 419 times.
856 models.density = At(Child(path, "density"), [&] { return BuildDensity(craters.density); });
582
2/2
✓ Branch 2 taken 419 times.
✓ Branch 5 taken 415 times.
842 models.ages = At(Child(path, "ages"), [&] { return BuildAges(craters.ages); });
583
2/2
✓ Branch 2 taken 415 times.
✓ Branch 5 taken 415 times.
830 models.profile = At(Child(path, "profile"),
584 830 [&] { return BuildProfile(craters.profile, craters.morphometry); });
585
2/2
✓ Branch 2 taken 415 times.
✓ Branch 5 taken 410 times.
835 At(Child(path, "degradation"), [&] { AddDegradation(craters, models); });
586 410 return models;
587 15 }
588
589 namespace {
590
591 /// The radial shape a shape model spec describes, loaded once per file, modification time, Q and
592 /// unit, as validating and building a terrain both need it.
593 /// @throws ConfigError at "body.path", "body.q" or "body.metres_per_unit".
594 12 std::shared_ptr<const RadialShape> LoadShapeModel(const ShapeModelSurfaceSpec& spec,
595 const std::filesystem::path& directory) {
596
2/2
✓ Branch 1 taken 12 times.
✓ Branch 4 taken 11 times.
13 config::RequireNonEmpty(spec.path, "body.path");
597
8/8
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 6 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 9 times.
11 if (spec.q && (*spec.q == 0 || *spec.q > 4096)) {
598
3/3
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 8 taken 2 times.
10 throw ConfigError("body.q", "must be 1 to 4096");
599 }
600
7/8
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 8 times.
9 if (spec.metresPerUnit && !(std::isfinite(*spec.metresPerUnit) && *spec.metresPerUnit > 0.0)) {
601
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
5 throw ConfigError("body.metres_per_unit", "must be positive");
602 }
603
1/1
✓ Branch 1 taken 8 times.
8 const std::filesystem::path file = Resolve(spec.path, directory);
604 8 std::error_code error;
605 8 const auto modified = std::filesystem::last_write_time(file, error);
606
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 7 times.
8 if (error) {
607
4/4
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
✓ Branch 11 taken 1 time.
3 throw ConfigError("body.path", "cannot read " + file.string());
608 }
609 using Key = std::tuple<std::string, std::filesystem::file_time_type, std::size_t, double>;
610 static std::mutex mutex;
611
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
7 static std::map<Key, std::shared_ptr<const RadialShape>> loaded;
612
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
14 const Key key{std::filesystem::absolute(file).string(), modified, spec.q.value_or(0),
613 7 spec.metresPerUnit.value_or(0.0)};
614
1/1
✓ Branch 1 taken 7 times.
7 const std::lock_guard<std::mutex> lock(mutex);
615
3/3
✓ Branch 1 taken 7 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 6 times.
7 if (const auto found = loaded.find(key); found != loaded.end()) {
616 1 return found->second;
617 }
618
1/1
✓ Branch 1 taken 6 times.
8 const auto shape = At("body.path", [&] {
619 try {
620
3/3
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
6 if (IsIcqPath(file)) {
621
1/1
✓ Branch 2 taken 3 times.
4 const IcqModel model = ReadIcq(file, spec.metresPerUnit.value_or(1000.0));
622
5/8
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 2 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
6 return spec.q && *spec.q != model.q ? ShapeModelFromMesh(IcqToMesh(model), *spec.q)
623
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 2 times.
6 : ToRadialShape(model);
624 3 }
625 2 const double scale = spec.metresPerUnit.value_or(1.0);
626
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
2 const TriangleMesh mesh = Scaled(ReadMesh(file).mesh, {scale, scale, scale});
627
1/1
✓ Branch 2 taken 1 time.
2 return ShapeModelFromMesh(mesh, spec.q.value_or(256));
628
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 } catch (const std::runtime_error& failure) {
629
1/1
✓ Branch 3 taken 2 times.
2 throw std::invalid_argument(failure.what());
630 2 }
631
1/1
✓ Branch 1 taken 4 times.
14 });
632
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 return loaded.emplace(key, std::make_shared<const RadialShape>(shape)).first->second;
633 12 }
634
635 } // namespace
636
637 585 Surface BuildSurface(const SurfaceSpec& spec, const std::filesystem::path& directory) {
638 return std::visit(
639 563 Overloaded{
640 483 [](const PlaneSurfaceSpec&) -> Surface { return Surface::Plane(); },
641 75 [](const SphereSurfaceSpec& s) -> Surface {
642
6/6
✓ Branch 1 taken 73 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 66 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 66 times.
75 if (!(std::isfinite(s.radius) && s.radius > 0.0)) {
643
3/3
✓ Branch 2 taken 9 times.
✓ Branch 5 taken 9 times.
✓ Branch 8 taken 9 times.
45 throw ConfigError("body.radius", "must be positive: the body's radius");
644 }
645 66 return Surface::Sphere(s.radius);
646 },
647 15 [](const EllipsoidSurfaceSpec& s) -> Surface {
648
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 12 times.
15 if (s.radii.size() != 3) {
649
3/3
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 8 taken 3 times.
15 throw ConfigError("body.radii", "must hold exactly three semi-axes a, b, c");
650 }
651
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 9 times.
43 for (std::size_t i = 0; i < 3; ++i) {
652
6/6
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 1 time.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 31 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 31 times.
34 if (!(std::isfinite(s.radii[i]) && s.radii[i] > 0.0)) {
653
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
6 throw ConfigError(Element("body.radii", i),
654
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
15 "must be positive: a semi-axis in metres");
655 }
656 }
657 9 return Surface::Ellipsoid(s.radii[0], s.radii[1], s.radii[2]);
658 },
659 12 [&](const ShapeModelSurfaceSpec& s) -> Surface {
660
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
12 return Surface::ShapeModel(LoadShapeModel(s, directory));
661 },
662 },
663
1/1
✓ Branch 1 taken 563 times.
1148 spec);
664 }
665
666 namespace {
667
668 172 Terrain BuildValidTerrain(const TerrainConfig& config, const std::filesystem::path& directory) {
669 172 Terrain terrain;
670
1/1
✓ Branch 1 taken 172 times.
172 terrain.surface = BuildSurface(config.body, directory);
671
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 161 times.
172 if (config.base) {
672
1/1
✓ Branch 2 taken 11 times.
11 terrain.source = BuildSource(*config.base, directory, terrain.surface);
673 }
674 172 std::shared_ptr<const CraterLayer> craters;
675 172 std::shared_ptr<const BoulderLayer> boulders;
676
2/2
✓ Branch 1 taken 368 times.
✓ Branch 2 taken 172 times.
540 for (std::size_t i = 0; i < config.layers.size(); ++i) {
677
1/1
✓ Branch 1 taken 368 times.
368 LayerInputs context{ReferencePoint(terrain), directory, craters, boulders, terrain.surface};
678 425 for (std::size_t j = i + 1;
679
2/2
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 120 times.
608 std::holds_alternative<CraterLayerSpec>(config.layers[i]) &&
680
6/6
✓ Branch 0 taken 183 times.
✓ Branch 1 taken 242 times.
✓ Branch 5 taken 57 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 57 times.
✓ Branch 8 taken 368 times.
608 j < config.layers.size() && !std::holds_alternative<CraterLayerSpec>(config.layers[j]);
681 ++j) {
682
2/2
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 49 times.
57 if (const auto* secondaries = std::get_if<SecondaryLayerSpec>(&config.layers[j])) {
683
1/1
✓ Branch 1 taken 8 times.
8 context.thrown.push_back(secondaries->secondaries);
684 }
685 }
686
4/4
✓ Branch 1 taken 368 times.
✓ Branch 4 taken 368 times.
✓ Branch 8 taken 368 times.
✓ Branch 11 taken 368 times.
736 terrain.layers.push_back(BuildLayer(config.layers[i], Element("layers", i), context));
687
2/2
✓ Branch 2 taken 126 times.
✓ Branch 3 taken 242 times.
368 if (std::holds_alternative<CraterLayerSpec>(config.layers[i])) {
688 126 craters = std::static_pointer_cast<const CraterLayer>(terrain.layers.back());
689 }
690
2/2
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 320 times.
368 if (std::holds_alternative<BoulderLayerSpec>(config.layers[i])) {
691 48 boulders = std::static_pointer_cast<const BoulderLayer>(terrain.layers.back());
692 }
693 368 }
694 344 return terrain;
695 172 }
696
697 /// The top-level boulder layers of a terrain built from `config`, in order, each with the
698 /// ground beneath it.
699 2 std::vector<RockSource> RockSourcesOf(const TerrainConfig& config, const Terrain& terrain) {
700 2 std::vector<RockSource> sources;
701
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 2 times.
10 for (std::size_t i = 0; i < config.layers.size(); ++i) {
702
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
8 if (std::holds_alternative<BoulderLayerSpec>(config.layers[i])) {
703 6 sources.push_back({std::static_pointer_cast<const BoulderLayer>(terrain.layers[i]),
704 SharedGround(terrain, i)});
705 }
706 }
707 2 return sources;
708
2/4
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
3 }
709
710 /// The top-level crater layers of a terrain built from `config`, in order.
711 22 std::vector<std::shared_ptr<const CraterLayer>> CraterLayersOf(const TerrainConfig& config,
712 const Terrain& terrain) {
713 22 std::vector<std::shared_ptr<const CraterLayer>> craterLayers;
714
2/2
✓ Branch 1 taken 47 times.
✓ Branch 2 taken 22 times.
69 for (std::size_t i = 0; i < config.layers.size(); ++i) {
715
2/2
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 26 times.
47 if (std::holds_alternative<CraterLayerSpec>(config.layers[i])) {
716
1/1
✓ Branch 3 taken 21 times.
21 craterLayers.push_back(std::static_pointer_cast<const CraterLayer>(terrain.layers[i]));
717 }
718 }
719 22 return craterLayers;
720 }
721
722 22 Albedo BuildValidAlbedo(const TerrainConfig& config, const Terrain& terrain,
723 const std::filesystem::path& directory) {
724 22 const AlbedoSpec& spec = *config.albedo;
725 22 Albedo albedo;
726 22 albedo.surface = terrain.surface;
727
1/1
✓ Branch 1 taken 22 times.
22 albedo.source = BuildAlbedoSource(spec.source, terrain, directory);
728 const std::vector<std::shared_ptr<const CraterLayer>> craterLayers =
729
1/1
✓ Branch 1 taken 22 times.
22 CraterLayersOf(config, terrain);
730
2/2
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 22 times.
40 for (const AlbedoLayerSpec& layer : spec.layers) {
731
1/1
✓ Branch 1 taken 18 times.
18 std::visit(Overloaded{
732 11 [&](const CraterAlbedoSpec& craters) {
733
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 11 times.
25 for (std::size_t i = 0; i < craterLayers.size(); ++i) {
734
6/6
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 2 times.
14 if (!craters.craterLayer || *craters.craterLayer == i) {
735
2/2
✓ Branch 1 taken 12 times.
✓ Branch 5 taken 12 times.
12 albedo.layers.push_back(std::make_shared<CraterAlbedo>(
736 12 craterLayers[i], craters.options));
737 }
738 }
739 11 },
740 2 [&](const SplotchAlbedoSpec& splotches) {
741
1/1
✓ Branch 2 taken 2 times.
2 albedo.layers.push_back(
742
1/1
✓ Branch 1 taken 2 times.
4 std::make_shared<SplotchAlbedo>(splotches.options, terrain.surface));
743 2 },
744 1 [&](const BlastZoneAlbedoSpec& zones) {
745
1/1
✓ Branch 2 taken 1 time.
1 albedo.layers.push_back(
746
1/1
✓ Branch 1 taken 1 time.
2 std::make_shared<BlastZoneAlbedo>(zones.zones, terrain.surface));
747 1 },
748 2 [&](const RockAlbedoSpec& rocks) {
749
1/1
✓ Branch 1 taken 2 times.
2 std::vector<RockSource> sources = RockSourcesOf(config, terrain);
750
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (rocks.boulderLayer) {
751
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ sources = {sources[*rocks.boulderLayer]};
752 }
753
1/1
✓ Branch 2 taken 2 times.
2 albedo.layers.push_back(
754
1/1
✓ Branch 2 taken 2 times.
4 std::make_shared<RockAlbedo>(rocks.options, std::move(sources)));
755
0/5
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2 },
756 2 [&](const SlopeAlbedoSpec& slopes) {
757
1/1
✓ Branch 2 taken 2 times.
2 albedo.layers.push_back(
758
1/1
✓ Branch 1 taken 2 times.
4 std::make_shared<SlopeAlbedo>(slopes.options, terrain.surface));
759 2 },
760 },
761 layer);
762 }
763 44 return albedo;
764 22 }
765 } // namespace
766
767 174 Terrain BuildTerrain(const TerrainConfig& config, const std::filesystem::path& directory) {
768
1/1
✓ Branch 1 taken 172 times.
174 ValidateConfig(config, directory);
769 172 return BuildValidTerrain(config, directory);
770 }
771
772 24 Albedo BuildAlbedo(const TerrainConfig& config, const Terrain& terrain,
773 const std::filesystem::path& directory) {
774
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 23 times.
24 if (!config.albedo) {
775
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
5 throw ConfigError("albedo", "the configuration has no albedo");
776 }
777 23 ValidateAlbedo(config, directory);
778 22 return BuildValidAlbedo(config, terrain, directory);
779 }
780
781 } // namespace terraingen
782