TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
67 / 68
98.5%
Branches:
65 / 67
97.0%
Functions:
10 / 10
100.0%

layers/density_map.cpp
Line Branch Exec Source
1 #include "terraingen/layers/density_map.h"
2
3 #include "terraingen/util/checks.h"
4
5 #include <algorithm>
6 #include <cmath>
7 #include <numbers>
8 #include <stdexcept>
9 #include <utility>
10
11 namespace terraingen {
12
13 namespace {
14
15 /// Simpson intervals in log diameter for an areal fraction.
16 constexpr int kArealSteps = 2048;
17
18 /// The share of ground covered by features of `distribution` in [`low`, `high`) metres: the
19 /// integral of pi D^2 / 4 dN/dD, taken in log diameter.
20 1 double ArealFraction(const SizeFrequencyDistribution& distribution, double low, double high) {
21 1 const double a = std::log(low);
22 1 const double h = (std::log(high) - a) / kArealSteps;
23 2049 const auto f = [&](int i) {
24 2049 const double d = std::exp(a + h * i);
25 2049 return 0.25 * std::numbers::pi * d * d * d * distribution.Incremental(d);
26 1 };
27
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 double sum = f(0) + f(kArealSteps);
28
2/2
✓ Branch 0 taken 2047 times.
✓ Branch 1 taken 1 time.
2048 for (int i = 1; i < kArealSteps; ++i) {
29
3/3
✓ Branch 0 taken 1024 times.
✓ Branch 1 taken 1023 times.
✓ Branch 3 taken 2047 times.
2047 sum += (i % 2 == 1 ? 4.0 : 2.0) * f(i);
30 }
31 1 return sum * h / 3.0;
32 }
33
34 } // namespace
35
36 78 void ValidateDensityMap(const DensityMap& map) {
37
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 76 times.
78 if (!map.field) {
38
1/1
✓ Branch 2 taken 2 times.
2 throw std::invalid_argument("A density map needs a field");
39 }
40
4/4
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 43 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 73 times.
109 if (map.measure != DensityMeasure::Scale &&
41
4/4
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 30 times.
33 !(IsPositive(map.minDiameter) && map.maxDiameter > map.minDiameter)) {
42
1/1
✓ Branch 2 taken 3 times.
3 throw std::invalid_argument("A density map's diameters must satisfy 0 < min < max");
43 }
44
1/1
✓ Branch 2 taken 72 times.
73 RequirePositive(map.areaUnit, "Density map areaUnit");
45 72 }
46
47 536 double MapMeasure(const DensityMap& map, const SizeFrequencyDistribution& distribution) {
48
3/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 525 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
536 switch (map.measure) {
49 9 case DensityMeasure::Scale:
50 9 return 1.0;
51 525 case DensityMeasure::Count:
52 525 return distribution.ExpectedCount(1.0, map.minDiameter, map.maxDiameter);
53 2 case DensityMeasure::ArealFraction: {
54 2 const double high = std::fmin(map.maxDiameter, distribution.ValidRange().max);
55
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!std::isfinite(high)) {
56
1/1
✓ Branch 2 taken 1 time.
1 throw std::domain_error("An areal fraction needs a finite largest diameter");
57 }
58 1 return ArealFraction(distribution, map.minDiameter, high);
59 }
60 }
61 ✗ return 1.0;
62 }
63
64 25 void ValidateSizeSlopeMap(const SizeSlopeMap& map) {
65
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 24 times.
25 if (!map.field) {
66
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("A size slope map needs a field");
67 }
68
1/1
✓ Branch 2 taken 23 times.
24 RequirePositive(map.referenceDiameter, "Size slope map referenceDiameter");
69
6/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 19 times.
43 if (!(map.minExponent > 0.0 && map.maxExponent > map.minExponent &&
70
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 19 times.
20 std::isfinite(map.maxExponent))) {
71
1/1
✓ Branch 2 taken 4 times.
4 throw std::invalid_argument("A size slope map's exponents must satisfy 0 < min < max");
72 }
73 19 }
74
75 287383 std::optional<double> SizeSlopeAt(const SizeSlopeMap& map, double x, double y) {
76
1/1
✓ Branch 2 taken 287383 times.
287383 const double value = map.field->ValueAt(x, y);
77
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 287382 times.
287383 if (std::isnan(value)) {
78 1 return std::nullopt;
79 }
80 287382 double exponent = value;
81
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 287380 times.
287382 if (map.measure == SizeSlopeMeasure::MeanDiameter) {
82 2 const double above = value - map.referenceDiameter;
83
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 exponent = above > 0.0 ? value / above : map.maxExponent;
84 }
85
1/1
✓ Branch 1 taken 287382 times.
287382 return std::clamp(exponent, map.minExponent, map.maxExponent);
86 }
87
88 2128253 double MapValueAt(const DensityMap& map, double x, double y) {
89 2128253 const double value = map.field->ValueAt(x, y);
90
2/2
✓ Branch 0 taken 1043992 times.
✓ Branch 1 taken 1084261 times.
2128253 return map.measure == DensityMeasure::Count ? value / map.areaUnit : value;
91 }
92
93 12 DensityScale::DensityScale(DensityMap map, const SizeFrequencyDistribution& distribution)
94 12 : map_(std::move(map)) {
95
1/1
✓ Branch 1 taken 12 times.
12 ValidateDensityMap(map_);
96
1/1
✓ Branch 1 taken 12 times.
12 measure_ = MapMeasure(map_, distribution);
97
5/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 11 times.
12 if (!(measure_ > 0.0 && std::isfinite(measure_))) {
98
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("A density map must measure some of its size distribution");
99 }
100
1/1
✓ Branch 2 taken 11 times.
11 const double top = map_.field->Max();
101
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
11 const double largest = map_.measure == DensityMeasure::Count ? top / map_.areaUnit : top;
102
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 if (largest / measure_ > max_) {
103 7 max_ = largest / measure_;
104 }
105 12 }
106
107 531156 double DensityScale::At(double x, double y) const {
108 531156 return Scale(MapValueAt(map_, x, y));
109 }
110
111 1044551 double DensityScale::Scale(double value) const {
112
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1044549 times.
1044551 if (std::isnan(value)) {
113 2 return 1.0;
114 }
115
1/1
✓ Branch 1 taken 1044549 times.
1044549 return std::clamp(value / measure_, 0.0, max_);
116 }
117
118 } // namespace terraingen
119