TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
134 / 140
95.7%
Branches:
132 / 159
83.0%
Functions:
12 / 12
100.0%

layers/craters/crater_layer.cpp
Line Branch Exec Source
1 #include "terraingen/layers/craters/crater_layer.h"
2
3 #include "terraingen/distribution/summed.h"
4 #include "terraingen/layers/craters/generation/spacing.h"
5 #include "terraingen/raster/grid.h"
6 #include "terraingen/util/cells.h"
7
8 #include <algorithm>
9 #include <cmath>
10 #include <optional>
11 #include <stdexcept>
12 #include <utility>
13 #include <vector>
14
15 namespace terraingen {
16
17 namespace {
18
19 /// The largest primary (m) searched for the smallest one throwing secondaries a map measures,
20 /// about where Singer et al.'s largest secondary stops growing with the primary.
21 constexpr double kLargestThrower = 5e5;
22
23 } // namespace
24
25 697 CraterLayer::CraterLayer(CraterModels models, CraterLayerOptions options)
26 697 : models_(std::move(models)), options_(std::move(options)) {
27
7/10
✓ Branch 1 taken 697 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 697 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 697 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 time.
✓ Branch 11 taken 696 times.
✓ Branch 12 taken 1 time.
✓ Branch 13 taken 696 times.
697 if (!models_.density || !models_.ages || !models_.degradation || !models_.profile) {
28 throw std::invalid_argument(
29
1/1
✓ Branch 2 taken 1 time.
1 "A crater layer needs a density, ages, degradation and profile");
30 }
31
1/1
✓ Branch 1 taken 695 times.
696 ValidateCraterSpacing(options_.generation.spacing);
32
1/1
✓ Branch 1 taken 694 times.
695 ValidateCraterApplyOptions(options_.apply);
33
1/1
✓ Branch 2 taken 694 times.
694 options_.generation.marginRadii = CraterReachRadii(*models_.profile, options_.apply) *
34
1/1
✓ Branch 1 taken 694 times.
694 MaxOutlineScale(options_.apply.irregularity);
35 694 options_.generation.surface = options_.surface;
36 694 options_.apply.surface = options_.surface;
37
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 662 times.
694 if (options_.surface.IsBody()) {
38
1/1
✓ Branch 1 taken 32 times.
32 cells_ = std::make_shared<CubeSphereCells>(options_.surface);
39 } else {
40
1/1
✓ Branch 1 taken 662 times.
662 cells_ = std::make_shared<PlanarCells>();
41 }
42
2/2
✓ Branch 0 taken 693 times.
✓ Branch 1 taken 1 time.
694 if (options_.cacheCraters > 0) {
43
1/1
✓ Branch 1 taken 693 times.
693 cache_ = std::make_shared<CraterCellCache>(options_.cacheCraters);
44 }
45
2/2
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 694 times.
704 for (const SecondaryCraterOptions& secondaries : options_.secondaries) {
46
1/1
✓ Branch 1 taken 10 times.
10 ValidateSecondaryCraterOptions(secondaries);
47 }
48 694 candidates_ = models_.density;
49
2/2
✓ Branch 1 taken 688 times.
✓ Branch 2 taken 6 times.
694 if (!options_.densityMap) {
50 688 return;
51 }
52
1/1
✓ Branch 3 taken 6 times.
6 scale_ = std::make_shared<const DensityScale>(*options_.densityMap, *models_.density);
53
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 time.
6 if (scale_->Max() > 1.0) {
54 candidates_ =
55
4/6
✓ Branch 2 taken 5 times.
✓ Branch 5 taken 5 times.
✓ Branch 10 taken 5 times.
✓ Branch 11 taken 5 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
20 std::make_shared<SummedSfd>(std::vector<WeightedSfd>{{scale_->Max(), models_.density}});
56 }
57 12 options_.generation.keepShare = [scale = scale_](const Crater& crater) {
58 17758 return scale->At(crater.x, crater.y) / scale->Max();
59
1/1
✓ Branch 1 taken 6 times.
6 };
60 6 const DensityMap& map = *options_.densityMap;
61
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
6 if (!map.total || map.measure == DensityMeasure::Scale) {
62 2 return;
63 }
64
2/2
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 4 times.
6 for (const SecondaryCraterOptions& secondaries : options_.secondaries) {
65 2 const double smallest = std::fmax(map.minDiameter, secondaries.minDiameter);
66 202 const auto throws = [&](double diameter) {
67 202 return LargestSecondary(diameter, secondaries) > smallest;
68 2 };
69 2 double low = std::log(1e-3);
70 2 double high = std::log(kLargestThrower);
71
2/3
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if (!throws(kLargestThrower)) {
72 ✗ continue;
73 }
74
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 2 times.
202 for (int i = 0; i < 100; ++i) {
75 200 const double middle = 0.5 * (low + high);
76
3/3
✓ Branch 1 taken 200 times.
✓ Branch 3 taken 52 times.
✓ Branch 4 taken 148 times.
200 (throws(std::exp(middle)) ? high : low) = middle;
77 }
78 2 throwFrom_ =
79 2 std::fmin(throwFrom_, std::fmax(std::exp(high), secondaries.selection.minDiameter));
80 }
81
6/6
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 3 times.
4 if (std::isfinite(throwFrom_) && options_.generation.maxCraters > 0) {
82 throw std::invalid_argument("Taking secondaries out of a density map needs every crater, "
83
1/1
✓ Branch 2 taken 1 time.
1 "not the maxCraters largest");
84 }
85 3 options_.generation.spacing.outrankFrom = throwFrom_;
86
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
42 }
87
88 3 CraterKeepShare CraterLayer::KeepShare(const Bounds2d& region) const {
89
1/1
✓ Branch 1 taken 3 times.
3 CraterGenerationOptions large = options_.generation;
90 3 large.dMin = std::fmax(large.dMin, throwFrom_);
91 3 large.minResolvedDiameter = 0.0;
92 3 double reach = 0.0;
93
2/2
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
6 for (const SecondaryCraterOptions& secondaries : options_.secondaries) {
94
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 reach = std::fmax(reach, SecondaryOuterRange(MaxFeatureDiameter(), secondaries));
95 }
96
1/1
✓ Branch 1 taken 3 times.
3 const Bounds2d around = options_.surface.Expand(region, reach);
97 3 CraterList throwers;
98
2/3
✓ Branch 2 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
3 if (large.dMin < ResolveDMax(large.dMax, *candidates_)) {
99
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (cache_) {
100 large.cellCraters =
101
0/1
✗ Branch 5 not taken.
✗ CachedCellCraters(*cache_, *cells_, *candidates_, *models_.ages, options_.seed);
102 }
103 throwers =
104
0/1
✗ Branch 4 not taken.
✗ GenerateCraters(*cells_, around, *candidates_, *models_.ages, options_.seed, large);
105 } else {
106
1/1
✓ Branch 2 taken 3 times.
3 AppendManualCraters(large.manual, throwers, [&](const Crater& crater) {
107 3 return options_.surface.Contains(around, {crater.x, crater.y});
108 });
109 }
110 struct Thrower {
111 Vec2d centre;
112 SecondaryDensity density;
113 };
114
1/1
✓ Branch 1 taken 3 times.
3 auto thrown = std::make_shared<std::vector<Thrower>>();
115
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 for (std::size_t n = 0; n < throwers.Size(); ++n) {
116
1/1
✓ Branch 1 taken 2 times.
2 const Crater crater = throwers.At(n);
117
2/2
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 2 times.
4 for (const SecondaryCraterOptions& secondaries : options_.secondaries) {
118
1/1
✓ Branch 1 taken 2 times.
2 SecondaryDensity density(crater, secondaries);
119
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (density.Reach() > 0.0) {
120
1/1
✓ Branch 2 taken 2 times.
4 thrown->push_back({{crater.x, crater.y}, std::move(density)});
121 }
122 2 }
123 }
124 3 const DensityMap& map = *options_.densityMap;
125 3 const bool cover = map.measure == DensityMeasure::ArealFraction;
126
0/6
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
6 return [scale = scale_, map, cover, thrown, from = throwFrom_,
127 3 surface = options_.surface](const Crater& crater) {
128 513392 const double share = scale->At(crater.x, crater.y) / scale->Max();
129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 513392 times.
513392 if (crater.diameter >= from) {
130 ✗ return share;
131 }
132 513392 double value = MapValueAt(map, crater.x, crater.y);
133
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 513392 times.
513392 if (std::isnan(value)) {
134 ✗ return share;
135 }
136
2/2
✓ Branch 6 taken 509549 times.
✓ Branch 7 taken 513392 times.
1022941 for (const Thrower& thrower : *thrown) {
137
1/1
✓ Branch 1 taken 509549 times.
509549 const Vec2d offset = surface.Offset(thrower.centre, {crater.x, crater.y});
138
2/2
✓ Branch 1 taken 279564 times.
✓ Branch 2 taken 229985 times.
509549 if (std::hypot(offset.x, offset.y) < thrower.density.Reach()) {
139
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 279564 times.
✗ Branch 3 not taken.
559128 value -= cover ? thrower.density.CoverAt(offset, map.minDiameter, map.maxDiameter)
140
1/1
✓ Branch 1 taken 279564 times.
279564 : thrower.density.At(offset, map.minDiameter, map.maxDiameter);
141 }
142 }
143 513392 return scale->Scale(value) / scale->Max();
144
1/1
✓ Branch 1 taken 3 times.
6 };
145 5 }
146
147 253829 CraterList CraterLayer::Craters(const Bounds2d& region, double spacing, double minDiameter,
148 double maxDiameter) const {
149
1/1
✓ Branch 1 taken 253829 times.
253829 CraterGenerationOptions generation = options_.generation;
150
2/2
✓ Branch 0 taken 219444 times.
✓ Branch 1 taken 34385 times.
253829 if (options_.generate) {
151
2/2
✓ Branch 0 taken 219423 times.
✓ Branch 1 taken 21 times.
219444 if (spacing > 0.0) {
152 219423 generation.minResolvedDiameter = options_.apply.fade.startSamples * spacing;
153 }
154 219444 generation.minResolvedDiameter = std::fmax(generation.minResolvedDiameter, minDiameter);
155 219444 generation.maxBandDiameter = std::fmin(generation.maxBandDiameter, maxDiameter);
156
2/2
✓ Branch 1 taken 219441 times.
✓ Branch 2 taken 3 times.
219444 if (cache_) {
157 generation.cellCraters =
158
1/1
✓ Branch 5 taken 219441 times.
219441 CachedCellCraters(*cache_, *cells_, *candidates_, *models_.ages, options_.seed);
159 }
160
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 219441 times.
219444 if (std::isfinite(throwFrom_)) {
161
1/1
✓ Branch 1 taken 3 times.
3 generation.keepShare = KeepShare(region);
162 }
163 219444 return GenerateCraters(*cells_, region, *candidates_, *models_.ages, options_.seed,
164
1/1
✓ Branch 1 taken 219444 times.
219444 generation);
165 }
166 34385 CraterList craters;
167
2/2
✓ Branch 1 taken 34385 times.
✓ Branch 4 taken 34382 times.
34388 AppendManualCraters(generation.manual, craters, [&](const Crater& crater) {
168 82908 const double margin = 0.5 * generation.marginRadii * crater.diameter;
169 165816 return options_.surface.Contains(options_.surface.Expand(region, margin),
170
1/1
✓ Branch 1 taken 82908 times.
248724 {crater.x, crater.y});
171 });
172 34382 return craters;
173 253832 }
174
175 244606 void CraterLayer::ApplySamples(SampleBlock& samples, const LayerContext& context) const {
176 244606 const Raster<float>* grid = samples.Grid();
177
4/4
✓ Branch 0 taken 244183 times.
✓ Branch 1 taken 423 times.
✓ Branch 2 taken 5002 times.
✓ Branch 3 taken 239181 times.
244606 if (grid == nullptr && options_.apply.datum != CraterDatum::None) {
178
1/1
✓ Branch 2 taken 5001 times.
5002 RequireGround(context.ground, "Point queries of craters with a datum");
179 }
180
4/4
✓ Branch 0 taken 244182 times.
✓ Branch 1 taken 423 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 244181 times.
244605 if (grid == nullptr && options_.generation.maxCraters > 0) {
181 throw std::invalid_argument(
182
1/1
✓ Branch 2 taken 1 time.
1 "Point queries cannot use maxCraters, which ranks craters over a whole grid");
183 }
184 const double spacing =
185 grid != nullptr
186
4/4
✓ Branch 0 taken 423 times.
✓ Branch 1 taken 244181 times.
✓ Branch 3 taken 423 times.
✓ Branch 6 taken 423 times.
244604 ? std::max(context.spacing, RasterGrid::FromRaster(*grid).Spacing(options_.surface))
187 244604 : context.spacing;
188
1/1
✓ Branch 1 taken 244604 times.
244604 const Bounds2d bounds = samples.Bounds();
189
2/2
✓ Branch 1 taken 244604 times.
✓ Branch 5 taken 244604 times.
244604 const double margin = DatumMargin(options_.apply, *models_.profile, MaxFeatureDiameter());
190
2/2
✓ Branch 2 taken 244604 times.
✓ Branch 5 taken 244604 times.
244604 const CraterList craters = Craters(options_.surface.Expand(bounds, margin), spacing);
191 const HeightSampler ground =
192
2/2
✓ Branch 0 taken 220548 times.
✓ Branch 1 taken 24056 times.
244604 context.ground ? context.ground->Sampler(spacing) : HeightSampler{};
193
1/1
✓ Branch 1 taken 244604 times.
244604 CraterSampling sampling{spacing, context.threads, ground};
194 244604 sampling.cache = context.cache;
195 244604 sampling.owner = this;
196 244604 sampling.backend = context.backend;
197
1/1
✓ Branch 2 taken 244604 times.
244604 ApplyCraters(samples, craters, models_.Shape(), options_.apply, sampling);
198 244604 }
199
200 63437 CraterAttributes CraterLayer::Attributes(const Crater& crater) const {
201
2/2
✓ Branch 3 taken 63437 times.
✓ Branch 6 taken 63437 times.
63437 return ComputeCraterAttributes(crater, models_.degradation->State(crater), *models_.profile);
202 }
203
204 121 CraterFeatures CraterLayer::CratersOver(const RasterGrid& grid, double spacing) const {
205
1/1
✓ Branch 1 taken 121 times.
121 CraterFeatures placed{Craters(grid.Bounds(), std::max(spacing, grid.Spacing(options_.surface))),
206
2/2
✓ Branch 2 taken 121 times.
✓ Branch 5 taken 121 times.
242 {}};
207
2/2
✓ Branch 1 taken 61251 times.
✓ Branch 2 taken 121 times.
61372 for (std::size_t n = 0; n < placed.craters.Size(); ++n) {
208
3/3
✓ Branch 1 taken 61251 times.
✓ Branch 4 taken 61251 times.
✓ Branch 7 taken 61251 times.
61251 placed.attributes.push_back(Attributes(placed.craters.At(n)));
209 }
210 121 return placed;
211 }
212
213 253431 double CraterLayer::MaxFeatureDiameter() const {
214 253431 const CraterGenerationOptions& generation = options_.generation;
215
2/2
✓ Branch 0 taken 219316 times.
✓ Branch 1 taken 34115 times.
253431 double largest = options_.generate ? ResolveDMax(generation.dMax, *models_.density) : 0.0;
216
2/2
✓ Branch 1 taken 85572 times.
✓ Branch 2 taken 253431 times.
339003 for (std::size_t n = 0; n < generation.manual.Size(); ++n) {
217
1/1
✓ Branch 1 taken 85572 times.
85572 largest = std::fmax(largest, generation.manual.At(n).diameter);
218 }
219 253431 return largest;
220 }
221
222 } // namespace terraingen
223