TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
153 / 154
99.4%
Branches:
118 / 129
91.5%
Functions:
14 / 14
100.0%

layers/pavement.cpp
Line Branch Exec Source
1 #include "terraingen/layers/pavement.h"
2
3 #include "terraingen/noise/perlin.h"
4 #include "terraingen/random/stream.h"
5 #include "terraingen/util/checks.h"
6 #include "terraingen/util/surface.h"
7
8 #include <algorithm>
9 #include <cmath>
10 #include <limits>
11 #include <numbers>
12 #include <stdexcept>
13
14 namespace terraingen {
15
16 namespace {
17
18 constexpr std::uint32_t kPavementStream = 0x50415645u;
19 constexpr const char* kExposureReader = "A pavement that reads the exposure field";
20 /// Plate sizes per lattice unit of the coarse and fine edge warp, and the fine warp's share.
21 constexpr double kWarpScale = 3.0;
22 constexpr double kFineWarpScale = 0.75;
23 constexpr double kFineWarp = 0.35;
24 /// Cells searched each way for the nearest plate sites; enough for any jitter in [0, 1].
25 constexpr std::int64_t kRings = 2;
26 /// The most crack octaves.
27 constexpr int kMaxOctaves = 8;
28 /// Seed step between octaves.
29 constexpr std::uint32_t kOctaveSeed = 101u;
30 /// Mixes a parent plate's id into its finer octave's seed, so finer cracks end at its edges.
31 constexpr std::uint32_t kParentMix = 0x9e3779b9u;
32
33 /// One plate: its site and the plane it lies in.
34 struct Site {
35 Vec2d centre;
36 double step = 0.0;
37 double slope = 0.0;
38 Vec2d downhill;
39 std::uint32_t id = 0;
40
41 11042902 double HeightAt(const Vec2d& p) const {
42 11042902 return step + slope * ((p.x - centre.x) * downhill.x + (p.y - centre.y) * downhill.y);
43 }
44 };
45
46 } // namespace
47
48 31 void ValidatePavementOptions(const PavementOptions& options) {
49
1/1
✓ Branch 2 taken 30 times.
31 RequirePositive(options.plateSize, "Pavement plateSize");
50
1/1
✓ Branch 2 taken 29 times.
30 RequirePositive(options.crackWidth, "Pavement crackWidth");
51
1/1
✓ Branch 2 taken 28 times.
29 RequirePositive(options.exposureSpacing, "Pavement exposureSpacing");
52
5/6
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 27 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 27 times.
55 if (!(IsNonNegative(options.crackDepth) && IsNonNegative(options.edgeRounding) &&
53
2/4
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 27 times.
27 IsNonNegative(options.maxStep) && IsNonNegative(options.warp))) {
54 throw std::invalid_argument(
55
1/1
✓ Branch 2 taken 1 time.
1 "Pavement crack depth, edge rounding, step and warp must be finite and not negative");
56 }
57
3/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 26 times.
27 if (!(options.maxTilt >= 0.0 && options.maxTilt < 0.5 * std::numbers::pi)) {
58
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Pavement maxTilt must be in [0, 90) deg");
59 }
60
3/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 25 times.
26 if (!(options.jitter >= 0.0 && options.jitter <= 1.0)) {
61
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Pavement jitter must be in [0, 1]");
62 }
63
8/8
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 19 times.
45 if (!(options.octaves >= 1 && options.octaves <= kMaxOctaves && options.lacunarity > 1.0 &&
64
4/6
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 19 times.
20 std::isfinite(options.lacunarity) && options.gain > 0.0 && options.gain <= 1.0)) {
65 throw std::invalid_argument(
66
1/1
✓ Branch 2 taken 6 times.
6 "Pavement needs 1 to 8 octaves, a lacunarity above 1 and a gain in (0, 1]");
67 }
68
5/6
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 18 times.
19 if (!(std::isfinite(options.anchor.x) && std::isfinite(options.anchor.y))) {
69
1/1
✓ Branch 2 taken 1 time.
1 throw std::invalid_argument("Pavement anchor must be finite");
70 }
71
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 14 times.
18 if (options.exposure) {
72 4 ValidateExposureParams(*options.exposure);
73 }
74 17 ValidateResolutionFade(options.fade);
75 17 }
76
77 5 PavementOptions MarsDesiccationRidges() {
78 5 PavementOptions ridges;
79 5 ridges.plateSize = 0.02;
80 5 ridges.jitter = 0.6;
81 5 ridges.warp = 0.15;
82 5 ridges.raised = true;
83 5 ridges.crackDepth = 0.005;
84 5 ridges.crackWidth = 0.002;
85 5 ridges.edgeRounding = 0.001;
86 5 ridges.maxTilt = 0.0;
87 5 ridges.maxStep = 0.0;
88 5 return ridges;
89 }
90
91 27 PavementLayer::PavementLayer(PavementOptions options, Surface surface)
92 27 : FormulaLayer(surface), options_(options) {
93
1/1
✓ Branch 1 taken 14 times.
27 ValidatePavementOptions(options_);
94
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (options_.exposure) {
95
1/1
✓ Branch 2 taken 2 times.
2 exposure_.emplace(*options_.exposure, surface_);
96 }
97
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 13 times.
14 if (surface_.IsBody()) {
98
1/1
✓ Branch 1 taken 1 time.
1 anchorDirection_ = UnitVector(options_.anchor.x, options_.anchor.y);
99 }
100 14 double size = 1.0;
101 14 double relief = 1.0;
102
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 14 times.
39 for (int k = 0; k < options_.octaves; ++k) {
103 ✗ levels_.push_back({options_.plateSize / size, relief * options_.crackWidth,
104 25 relief * options_.crackDepth, relief * options_.edgeRounding,
105 25 relief * options_.maxTilt, relief * options_.maxStep,
106
1/1
✓ Branch 1 taken 25 times.
25 options_.seed + kOctaveSeed * static_cast<std::uint32_t>(k)});
107 25 size *= options_.lacunarity;
108 25 relief *= options_.gain;
109 }
110 66 }
111
112 2343524 bool PavementLayer::Planar(double x, double y, Vec2d& planar) const {
113
2/2
✓ Branch 1 taken 2343521 times.
✓ Branch 2 taken 3 times.
2343524 if (!surface_.IsBody()) {
114 2343521 planar = {x, y};
115 2343521 return true;
116 }
117
3/3
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 time.
3 if (Dot(UnitVector(x, y), anchorDirection_) <= 0.0) {
118 2 return false;
119 }
120
1/1
✓ Branch 1 taken 1 time.
1 planar = surface_.Offset(options_.anchor, {x, y});
121 1 return true;
122 }
123
124 5521451 PavementLayer::Plate PavementLayer::PlateAt(const Vec2d& q, const Level& level, std::uint32_t seed,
125 double crackWeight) const {
126 5521451 const double size = level.size;
127 22085804 const auto warp = [&](double scale, double ox, double oy, std::uint32_t salt) {
128 return static_cast<double>(
129 22085804 PerlinNoise2Precise(q.x / (scale * size) + ox, q.y / (scale * size) + oy, seed + salt));
130 5521451 };
131 5521451 const double reach = options_.warp * size;
132 5521451 const Vec2d p{q.x + reach * (warp(kWarpScale, 0.0, 0.0, 7u) +
133 5521451 kFineWarp * warp(kFineWarpScale, 5.3, -8.1, 9u)),
134 11042902 q.y + reach * (warp(kWarpScale, 31.7, -12.3, 8u) +
135 5521451 kFineWarp * warp(kFineWarpScale, -17.9, 4.4, 10u))};
136 5521451 const auto ix = static_cast<std::int64_t>(std::floor(p.x / size));
137 5521451 const auto iy = static_cast<std::int64_t>(std::floor(p.y / size));
138 5521451 Site sites[(2 * kRings + 1) * (2 * kRings + 1)];
139 5521451 std::size_t count = 0;
140 5521451 std::size_t nearest = 0;
141 5521451 double best = std::numeric_limits<double>::infinity();
142
2/2
✓ Branch 0 taken 27607255 times.
✓ Branch 1 taken 5521451 times.
33128706 for (std::int64_t gy = iy - kRings; gy <= iy + kRings; ++gy) {
143
2/2
✓ Branch 0 taken 138036275 times.
✓ Branch 1 taken 27607255 times.
165643530 for (std::int64_t gx = ix - kRings; gx <= ix + kRings; ++gx) {
144 RandomStream random(seed, kPavementStream, static_cast<std::uint32_t>(gx),
145 static_cast<std::uint32_t>(gy),
146
1/1
✓ Branch 1 taken 138036275 times.
138036275 static_cast<std::uint32_t>((gx >> 32) ^ ((gy >> 32) << 16)));
147
1/1
✓ Branch 1 taken 138036275 times.
138036275 const double u = random.NextDouble();
148
1/1
✓ Branch 1 taken 138036275 times.
138036275 const double v = random.NextDouble();
149 138036275 Site& site = sites[count];
150 138036275 site.centre = {(static_cast<double>(gx) + 0.5 + options_.jitter * (u - 0.5)) * size,
151 138036275 (static_cast<double>(gy) + 0.5 + options_.jitter * (v - 0.5)) * size};
152
1/1
✓ Branch 1 taken 138036275 times.
138036275 site.step = level.maxStep * (2.0 * random.NextDouble() - 1.0);
153
1/1
✓ Branch 1 taken 138036275 times.
138036275 site.slope = std::tan(level.maxTilt * random.NextDouble());
154
1/1
✓ Branch 1 taken 138036275 times.
138036275 const double turn = 2.0 * std::numbers::pi * random.NextDouble();
155 138036275 site.downhill = {std::cos(turn), std::sin(turn)};
156
1/1
✓ Branch 1 taken 138036275 times.
138036275 site.id = random.NextU32();
157 138036275 const double d = std::hypot(p.x - site.centre.x, p.y - site.centre.y);
158
2/2
✓ Branch 0 taken 35256511 times.
✓ Branch 1 taken 102779764 times.
138036275 if (d < best) {
159 35256511 best = d;
160 35256511 nearest = count;
161 }
162 138036275 ++count;
163 }
164 }
165 5521451 const Site& own = sites[nearest];
166 5521451 double edge = std::numeric_limits<double>::infinity();
167 5521451 std::size_t neighbour = nearest;
168
2/2
✓ Branch 0 taken 138036275 times.
✓ Branch 1 taken 5521451 times.
143557726 for (std::size_t k = 0; k < count; ++k) {
169
2/2
✓ Branch 0 taken 5521451 times.
✓ Branch 1 taken 132514824 times.
138036275 if (k == nearest) {
170 5521451 continue;
171 }
172 132514824 const Vec2d across{sites[k].centre.x - own.centre.x, sites[k].centre.y - own.centre.y};
173 132514824 const double length = std::hypot(across.x, across.y);
174 132514824 const Vec2d middle{0.5 * (sites[k].centre.x + own.centre.x),
175 132514824 0.5 * (sites[k].centre.y + own.centre.y)};
176 const double d =
177 132514824 ((middle.x - p.x) * across.x + (middle.y - p.y) * across.y) / std::max(length, 1e-12);
178
2/2
✓ Branch 0 taken 33826861 times.
✓ Branch 1 taken 98687963 times.
132514824 if (d < edge) {
179 33826861 edge = d;
180 33826861 neighbour = k;
181 }
182 }
183 5521451 const double shoulder = 0.5 * level.crackWidth + level.rounding;
184
1/1
✓ Branch 1 taken 5521451 times.
5521451 const double crack = 1.0 - Smoothstep(edge / shoulder);
185 5521451 const double ownHeight = own.HeightAt(p);
186 5521451 const double plate = ownHeight + 0.5 * crack * (sites[neighbour].HeightAt(p) - ownHeight);
187
2/2
✓ Branch 0 taken 6002 times.
✓ Branch 1 taken 5515449 times.
5521451 const double sign = options_.raised ? 1.0 : -1.0;
188 5521451 return {plate + crackWeight * sign * level.crackDepth * crack, edge, 1.0 - crack, own.id};
189 }
190
191 1846256 double PavementLayer::Relief(const Vec2d& q, double spacing) const {
192 1846256 double relief = 0.0;
193 1846256 double interior = 1.0;
194 1846256 std::uint32_t parent = 0;
195
2/2
✓ Branch 4 taken 5519346 times.
✓ Branch 5 taken 1846239 times.
7365585 for (const Level& level : levels_) {
196 const double plateWeight =
197
3/3
✓ Branch 0 taken 5457676 times.
✓ Branch 1 taken 61670 times.
✓ Branch 3 taken 5457676 times.
5519346 spacing > 0.0 ? ResolutionWeight(level.size, spacing, options_.fade) : 1.0;
198
3/4
✓ Branch 0 taken 5519329 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 5519329 times.
✗ Branch 3 not taken.
5519346 if (plateWeight <= 0.0 || interior <= 0.0) {
199 break;
200 }
201 const double crackWeight =
202 spacing > 0.0
203
3/3
✓ Branch 0 taken 5457659 times.
✓ Branch 1 taken 61670 times.
✓ Branch 3 taken 5457659 times.
5519329 ? ResolutionWeight(level.crackWidth + 2.0 * level.rounding, spacing, options_.fade)
204 5519329 : 1.0;
205
1/1
✓ Branch 1 taken 5519329 times.
5519329 const Plate plate = PlateAt(q, level, level.seed ^ (parent * kParentMix), crackWeight);
206 5519329 relief += interior * plateWeight * plate.relief;
207 5519329 interior *= plate.interior;
208 5519329 parent = plate.id;
209 }
210 1846256 return relief;
211 }
212
213 2314386 double PavementLayer::Gate(double x, double y, const Ground* ground) const {
214
2/2
✓ Branch 1 taken 2436 times.
✓ Branch 2 taken 2311950 times.
2314386 if (!exposure_) {
215 2436 return 1.0;
216 }
217
1/1
✓ Branch 2 taken 2311949 times.
2311950 RequireGround(ground, kExposureReader);
218 2311949 return exposure_->Exposure(*ground, x, y, options_.exposureSpacing);
219 }
220
221 27014 double PavementLayer::ReliefAt(double x, double y) const {
222 27014 Vec2d q;
223
4/4
✓ Branch 1 taken 27014 times.
✓ Branch 3 taken 27013 times.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 27013 times.
54028 return Planar(x, y, q) ? Relief(q, 0.0) : 0.0;
224 }
225
226 2122 double PavementLayer::EdgeDistanceAt(double x, double y) const {
227 2122 Vec2d q;
228
3/4
✓ Branch 1 taken 2122 times.
✓ Branch 3 taken 2122 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 2122 times.
4244 return Planar(x, y, q) ? PlateAt(q, levels_.front(), levels_.front().seed, 1.0).edge
229 4244 : std::numeric_limits<double>::infinity();
230 }
231
232 66054 double PavementLayer::MaterialCoverAt(double x, double y, double /*spacing*/,
233 const Ground* ground) const {
234 66054 Vec2d q;
235
3/4
✓ Branch 1 taken 66054 times.
✓ Branch 3 taken 66054 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 66053 times.
132107 return Planar(x, y, q) ? Gate(x, y, ground) : 0.0;
236 }
237
238 2248334 float PavementLayer::HeightAt(float height, double x, double y, double spacing,
239 const Ground* ground) const {
240 2248334 Vec2d q;
241
8/8
✓ Branch 1 taken 2248334 times.
✓ Branch 3 taken 2248333 times.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 2248333 times.
✓ Branch 8 taken 1 time.
✓ Branch 9 taken 2248332 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 2248332 times.
2248334 if (!Planar(x, y, q) || ResolutionWeight(options_.plateSize, spacing, options_.fade) <= 0.0) {
242 2 return height;
243 }
244
1/1
✓ Branch 1 taken 2248332 times.
2248332 const double gate = Gate(x, y, ground);
245
2/2
✓ Branch 0 taken 429089 times.
✓ Branch 1 taken 1819243 times.
2248332 if (gate <= 0.0) {
246 429089 return height;
247 }
248
1/1
✓ Branch 1 taken 1819243 times.
1819243 return static_cast<float>(static_cast<double>(height) + gate * Relief(q, spacing));
249 }
250
251 2181088 void PavementLayer::CheckContext(const LayerContext& context) const {
252
2/2
✓ Branch 1 taken 2179851 times.
✓ Branch 2 taken 1237 times.
2181088 if (exposure_) {
253
1/1
✓ Branch 2 taken 2179850 times.
2179851 RequireGround(context.ground, kExposureReader);
254 }
255 2181087 }
256
257 } // namespace terraingen
258