TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
125 / 125
100.0%
Branches:
116 / 124
93.5%
Functions:
12 / 12
100.0%

materials/materials.cpp
Line Branch Exec Source
1 #include "terraingen/materials/materials.h"
2
3 #include "terraingen/layers/fade.h"
4 #include "terraingen/util/checks.h"
5 #include "terraingen/util/parallel.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <cstddef>
10 #include <optional>
11 #include <stdexcept>
12 #include <string>
13 #include <utility>
14
15 namespace terraingen {
16
17 namespace {
18
19 /// Width of the patch noise band over which dust comes in.
20 constexpr double kDustEdge = 0.2;
21
22 90 std::size_t Index(MaterialClass material) {
23 90 return static_cast<std::size_t>(material);
24 }
25
26 const char* const kClassNames[kMaterialClasses] = {"soil", "sand", "dust", "bedrock", "rock"};
27
28 /// The layers before the first that lays a material: the bare ground's.
29 13520 std::size_t BareLayers(const Terrain& terrain) {
30 13520 std::size_t n = 0;
31
6/6
✓ Branch 1 taken 17036 times.
✓ Branch 2 taken 10004 times.
✓ Branch 3 taken 13520 times.
✓ Branch 4 taken 3516 times.
✓ Branch 5 taken 13520 times.
✓ Branch 6 taken 13520 times.
44076 while (n < terrain.layers.size() &&
32
1/2
✓ Branch 2 taken 17036 times.
✗ Branch 3 not taken.
17036 dynamic_cast<const MaterialSource*>(terrain.layers[n].get()) == nullptr) {
33 13520 ++n;
34 }
35 13520 return n;
36 }
37
38 /// The shares before any material layer: soil, bedrock where `bare` is exposed and dust patches.
39 356908 MaterialShares GroundShares(const Terrain& terrain, const MaterialOptions& options,
40 const ExposureField* field, const Ground& bare, double x, double y,
41 double spacing) {
42 356908 MaterialShares shares = SoilShares();
43
2/2
✓ Branch 0 taken 290858 times.
✓ Branch 1 taken 66050 times.
356908 if (field != nullptr) {
44 290858 CoverWith(shares, MaterialClass::Bedrock, field->Exposure(bare, x, y, spacing));
45 }
46
2/2
✓ Branch 0 taken 83313 times.
✓ Branch 1 taken 273595 times.
356908 if (options.dustCover > 0.0) {
47 83313 const double noise = PatchNoise(terrain.surface, x, y, options.dustPatch, options.seed);
48 83313 const double threshold = 1.0 - 2.0 * options.dustCover;
49 83313 CoverWith(shares, MaterialClass::Dust, Smoothstep((noise - threshold) / kDustEdge));
50 }
51 356908 return shares;
52 }
53
54 } // namespace
55
56 13540 void ValidateMaterialOptions(const MaterialOptions& options) {
57
3/4
✓ Branch 0 taken 13540 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 13538 times.
13540 if (!(options.dustCover >= 0.0 && options.dustCover < 1.0)) {
58
1/1
✓ Branch 2 taken 2 times.
2 throw std::invalid_argument("Material dustCover must be in [0, 1)");
59 }
60
1/1
✓ Branch 2 taken 13537 times.
13538 RequirePositive(options.dustPatch, "Material dustPatch");
61
2/2
✓ Branch 1 taken 13529 times.
✓ Branch 2 taken 8 times.
13537 if (options.exposure) {
62 13529 ValidateExposureParams(*options.exposure);
63 }
64 13536 }
65
66 13511 MaterialShares MaterialsAt(const Terrain& terrain, const MaterialOptions& options, double x,
67 double y, double spacing) {
68
1/1
✓ Branch 1 taken 13511 times.
13511 ValidateMaterialOptions(options);
69
1/1
✓ Branch 1 taken 13511 times.
13511 ValidateTerrain(terrain);
70
1/1
✓ Branch 2 taken 13510 times.
13511 RequirePositive(spacing, "Material spacing");
71 13510 std::optional<ExposureField> field;
72
2/2
✓ Branch 1 taken 13509 times.
✓ Branch 2 taken 1 time.
13510 if (options.exposure) {
73
1/1
✓ Branch 2 taken 13509 times.
13509 field.emplace(*options.exposure, terrain.surface);
74 }
75
1/1
✓ Branch 2 taken 13510 times.
13510 const TerrainGround bare(terrain, BareLayers(terrain));
76 MaterialShares shares =
77
3/3
✓ Branch 1 taken 13509 times.
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 13510 times.
13510 GroundShares(terrain, options, field ? &*field : nullptr, bare, x, y, spacing);
78
2/2
✓ Branch 1 taken 20089 times.
✓ Branch 2 taken 13510 times.
33599 for (std::size_t n = 0; n < terrain.layers.size(); ++n) {
79
1/2
✓ Branch 2 taken 20089 times.
✗ Branch 3 not taken.
20089 const auto* source = dynamic_cast<const MaterialSource*>(terrain.layers[n].get());
80
2/2
✓ Branch 0 taken 13510 times.
✓ Branch 1 taken 6579 times.
20089 if (source == nullptr) {
81 13510 continue;
82 }
83
1/1
✓ Branch 1 taken 6579 times.
6579 const TerrainGround beneath(terrain, n);
84
3/3
✓ Branch 1 taken 6579 times.
✓ Branch 4 taken 6579 times.
✓ Branch 7 taken 6579 times.
6579 CoverWith(shares, source->Material(), source->MaterialCoverAt(x, y, spacing, &beneath));
85 6579 }
86 27020 return shares;
87 13510 }
88
89 10 std::vector<Raster<float>> EvaluateMaterials(const Terrain& terrain, const MaterialOptions& options,
90 const RasterGrid& grid,
91 const EvaluationOptions& evaluation) {
92
1/1
✓ Branch 1 taken 10 times.
10 ValidateMaterialOptions(options);
93
1/1
✓ Branch 1 taken 10 times.
10 ValidateTerrain(terrain);
94
1/1
✓ Branch 2 taken 10 times.
10 RequireNonNegative(evaluation.spacing, "Material spacing");
95
1/1
✓ Branch 1 taken 10 times.
10 const double spacing = std::max(evaluation.spacing, grid.Spacing(terrain.surface));
96 10 std::vector<std::pair<MaterialClass, Raster<double>>> covers;
97
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 10 times.
34 for (std::size_t n = 0; n < terrain.layers.size(); ++n) {
98
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 const auto* source = dynamic_cast<const MaterialSource*>(terrain.layers[n].get());
99
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 10 times.
24 if (source != nullptr) {
100
1/1
✓ Branch 1 taken 14 times.
14 const TerrainGround beneath(terrain, n);
101 14 covers.emplace_back(
102
2/2
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
14 source->Material(),
103
1/1
✓ Branch 1 taken 14 times.
28 source->MaterialCoverOver(grid, spacing, &beneath, evaluation.threads));
104 14 }
105 }
106 10 std::optional<ExposureField> field;
107
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 1 time.
10 if (options.exposure) {
108
1/1
✓ Branch 2 taken 9 times.
9 field.emplace(*options.exposure, terrain.surface);
109 }
110
1/1
✓ Branch 2 taken 10 times.
10 const TerrainGround bare(terrain, BareLayers(terrain));
111
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 std::vector<Raster<float>> bands(kMaterialClasses, grid.MakeRaster());
112
2/2
✓ Branch 1 taken 10 times.
✓ Branch 4 taken 10 times.
10 ParallelFor(grid.height, evaluation.threads, [&](std::size_t row) {
113
2/2
✓ Branch 0 taken 343398 times.
✓ Branch 1 taken 1488 times.
344886 for (std::size_t col = 0; col < grid.width; ++col) {
114 343398 const Vec2d centre = bands[0].CellCenter(col, row);
115
3/3
✓ Branch 0 taken 277349 times.
✓ Branch 1 taken 66049 times.
✓ Branch 4 taken 343398 times.
343398 MaterialShares shares = GroundShares(terrain, options, field ? &*field : nullptr, bare,
116 343398 centre.x, centre.y, spacing);
117
2/2
✓ Branch 7 taken 610602 times.
✓ Branch 8 taken 343398 times.
954000 for (const auto& [material, cover] : covers) {
118
1/1
✓ Branch 2 taken 610602 times.
610602 CoverWith(shares, material, cover(col, row));
119 }
120
2/2
✓ Branch 0 taken 1716990 times.
✓ Branch 1 taken 343398 times.
2060388 for (std::size_t k = 0; k < kMaterialClasses; ++k) {
121 1716990 bands[k](col, row) = static_cast<float>(shares[k]);
122 }
123 }
124 1488 });
125 20 return bands;
126 10 }
127
128 18 MaterialPhotometry MarsPancamPhotometry() {
129 18 MaterialPhotometry photometry{};
130 18 photometry[Index(MaterialClass::Soil)] = HapkeParameters{0.76, 0.262, 0.715, 15.0};
131 18 photometry[Index(MaterialClass::Sand)] = HapkeParameters{0.52, 0.255, 0.434, 9.0};
132 18 photometry[Index(MaterialClass::Dust)] = HapkeParameters{0.66, 0.449, 0.364, 16.0};
133 18 photometry[Index(MaterialClass::Rock)] = HapkeParameters{0.83, 0.931, 0.065, 7.0};
134 18 photometry[Index(MaterialClass::Bedrock)] = HapkeParameters{0.90, 0.372, 0.202, 17.0};
135 18 return photometry;
136 }
137
138 26 void ValidateMaterialPhotometry(const MaterialPhotometry& photometry) {
139
2/2
✓ Branch 0 taken 124 times.
✓ Branch 1 taken 23 times.
147 for (std::size_t k = 0; k < kMaterialClasses; ++k) {
140
2/2
✓ Branch 2 taken 27 times.
✓ Branch 3 taken 97 times.
124 if (!photometry[k]) {
141 27 continue;
142 }
143 97 const HapkeParameters& p = *photometry[k];
144
3/4
✓ Branch 0 taken 287 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 285 times.
✓ Branch 3 taken 2 times.
287 const auto unit = [](double v) { return v >= 0.0 && v <= 1.0; };
145
7/10
✓ Branch 1 taken 95 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 95 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 95 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 95 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 94 times.
192 if (!(unit(p.albedo) && unit(p.b) && unit(p.c) && p.roughness >= 0.0 &&
146
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 94 times.
95 p.roughness < 90.0)) {
147
3/3
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
6 throw std::invalid_argument(std::string("Hapke parameters of ") + kClassNames[k] +
148 " must have w, b and c in [0, 1] and a roughness in "
149
1/1
✓ Branch 2 taken 3 times.
9 "[0, 90) degrees");
150 }
151 }
152 23 }
153
154 267461 HapkeParameters MixPhotometry(const MaterialShares& shares, const MaterialPhotometry& photometry) {
155 267461 HapkeParameters mixed;
156
2/2
✓ Branch 0 taken 1337303 times.
✓ Branch 1 taken 267459 times.
1604762 for (std::size_t k = 0; k < kMaterialClasses; ++k) {
157
2/2
✓ Branch 1 taken 967503 times.
✓ Branch 2 taken 369800 times.
1337303 if (shares[k] <= 0.0) {
158 967503 continue;
159 }
160
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 369798 times.
369800 if (!photometry[k]) {
161
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
4 throw std::invalid_argument(std::string("No Hapke parameters for ") + kClassNames[k] +
162
1/1
✓ Branch 2 taken 2 times.
6 ", which covers part of the surface");
163 }
164 369798 const HapkeParameters& p = *photometry[k];
165 369798 mixed.albedo += shares[k] * p.albedo;
166 369798 mixed.b += shares[k] * p.b;
167 369798 mixed.c += shares[k] * p.c;
168 369798 mixed.roughness += shares[k] * p.roughness;
169 }
170 267459 return mixed;
171 }
172
173 9 std::vector<Raster<float>> EvaluatePhotometry(const std::vector<Raster<float>>& materials,
174 const MaterialPhotometry& photometry) {
175
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 8 times.
9 if (materials.size() != kMaterialClasses) {
176
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Photometry needs one material raster per class");
177 }
178
1/1
✓ Branch 1 taken 8 times.
8 ValidateMaterialPhotometry(photometry);
179
1/1
✓ Branch 2 taken 8 times.
8 const RasterGrid grid = RasterGrid::FromRaster(materials[0]);
180
2/2
✓ Branch 5 taken 38 times.
✓ Branch 6 taken 7 times.
45 for (const Raster<float>& band : materials) {
181
3/3
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 37 times.
38 if (!(RasterGrid::FromRaster(band) == grid)) {
182
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Material rasters must share one grid");
183 }
184 }
185
2/2
✓ Branch 1 taken 7 times.
✓ Branch 4 taken 7 times.
7 std::vector<Raster<float>> bands(4, grid.MakeRaster());
186
2/2
✓ Branch 2 taken 267457 times.
✓ Branch 3 taken 7 times.
267464 for (std::size_t i = 0; i < materials[0].Size(); ++i) {
187 267457 MaterialShares shares{};
188
2/2
✓ Branch 0 taken 1337285 times.
✓ Branch 1 taken 267457 times.
1604742 for (std::size_t k = 0; k < kMaterialClasses; ++k) {
189 1337285 shares[k] = static_cast<double>(materials[k].Values()[i]);
190 }
191
1/1
✓ Branch 1 taken 267457 times.
267457 const HapkeParameters mixed = MixPhotometry(shares, photometry);
192 267457 bands[0].Values()[i] = static_cast<float>(mixed.albedo);
193 267457 bands[1].Values()[i] = static_cast<float>(mixed.b);
194 267457 bands[2].Values()[i] = static_cast<float>(mixed.c);
195 267457 bands[3].Values()[i] = static_cast<float>(mixed.roughness);
196 }
197 14 return bands;
198 }
199
200 } // namespace terraingen
201