layers/bedforms.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/layers/bedforms.h" | ||
| 2 | |||
| 3 | #include "terraingen/noise/perlin.h" | ||
| 4 | #include "terraingen/random/stream.h" | ||
| 5 | #include "terraingen/raster/cell_loops.h" | ||
| 6 | #include "terraingen/raster/grid.h" | ||
| 7 | #include "terraingen/util/checks.h" | ||
| 8 | |||
| 9 | #include <algorithm> | ||
| 10 | #include <cmath> | ||
| 11 | #include <numbers> | ||
| 12 | #include <stdexcept> | ||
| 13 | |||
| 14 | namespace terraingen { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | constexpr std::uint32_t kBedformStream = 0x42454446u; | ||
| 19 | constexpr std::uint32_t kBedformSphereStream = 0x42454453u; | ||
| 20 | constexpr double kRadiusMin = 0.40; | ||
| 21 | constexpr double kRadiusMax = 0.70; | ||
| 22 | constexpr double kRagged = 0.30; | ||
| 23 | constexpr double kEdgeShare = 0.35; | ||
| 24 | constexpr double kWavelengthMin = 0.8; | ||
| 25 | constexpr double kWavelengthSpan = 0.4; | ||
| 26 | constexpr double kWanderWavelengths = 5.0; | ||
| 27 | constexpr double kRagRadii = 0.5; | ||
| 28 | |||
| 29 | constexpr const char* kExposureReader = "Bedforms that read the exposure field"; | ||
| 30 | |||
| 31 | } // namespace | ||
| 32 | |||
| 33 | 41 | void ValidateBedformOptions(const BedformOptions& options) { | |
| 34 | 41 | if (!(IsPositive(options.wavelength) && IsPositive(options.fieldSize))) { | |
| 35 | 1 | throw std::invalid_argument("Bedform wavelength and field size must be positive"); | |
| 36 | } | ||
| 37 | 40 | if (!IsNonNegative(options.height)) { | |
| 38 | 1 | throw std::invalid_argument("Bedform height must not be negative"); | |
| 39 | } | ||
| 40 | 39 | if (!(options.leeFraction > 0.0 && options.leeFraction < 1.0)) { | |
| 41 | 2 | throw std::invalid_argument("Bedform leeFraction must be in (0, 1)"); | |
| 42 | } | ||
| 43 | 37 | if (!(options.cover >= 0.0 && options.cover <= 0.9)) { | |
| 44 | 1 | throw std::invalid_argument("Bedform cover must be in [0, 0.9]"); | |
| 45 | } | ||
| 46 | 71 | if (!(IsNonNegative(options.sinuosity) && IsNonNegative(options.azimuthSpread) && | |
| 47 | 35 | std::isfinite(options.windAzimuth))) { | |
| 48 | throw std::invalid_argument( | ||
| 49 | 1 | "Bedform sinuosity and azimuth spread must be finite and not negative"); | |
| 50 | } | ||
| 51 | 35 | if (!IsPositive(options.exposureSpacing)) { | |
| 52 | 1 | throw std::invalid_argument("Bedform exposureSpacing must be positive"); | |
| 53 | } | ||
| 54 | 34 | if (options.exposure) { | |
| 55 | 7 | ValidateExposureParams(*options.exposure); | |
| 56 | } | ||
| 57 | 33 | ValidateResolutionFade(options.fade); | |
| 58 | 32 | } | |
| 59 | |||
| 60 | 8 | BedformOptions MarsLargeRipples() { | |
| 61 | 8 | BedformOptions ripples; | |
| 62 | 8 | ripples.wavelength = 2.0; | |
| 63 | 8 | ripples.height = 0.2; | |
| 64 | 8 | const double leeSlope = std::tan(30.0 * std::numbers::pi / 180.0); | |
| 65 | 8 | ripples.leeFraction = std::numbers::pi * ripples.height / (2.0 * ripples.wavelength * leeSlope); | |
| 66 | 8 | ripples.sinuosity = 0.6; | |
| 67 | 8 | ripples.cover = 0.15; | |
| 68 | 8 | ripples.fieldSize = 80.0; | |
| 69 | 8 | return ripples; | |
| 70 | } | ||
| 71 | |||
| 72 | 4 | BedformOptions MarsTransverseAeolianRidges() { | |
| 73 | 4 | BedformOptions ridges; | |
| 74 | 4 | ridges.wavelength = 25.8; | |
| 75 | 4 | ridges.height = 1.3; | |
| 76 | 4 | ridges.leeFraction = 0.5; | |
| 77 | 4 | ridges.sinuosity = 0.2; | |
| 78 | 4 | ridges.cover = 0.07; | |
| 79 | 4 | ridges.fieldSize = 250.0; | |
| 80 | 4 | return ridges; | |
| 81 | } | ||
| 82 | |||
| 83 | 33 | BedformLayer::BedformLayer(BedformOptions options, Surface surface) | |
| 84 | 33 | : FormulaLayer(surface), options_(options) { | |
| 85 | 33 | ValidateBedformOptions(options_); | |
| 86 | 25 | cell_ = options_.fieldSize / (kRadiusMin + kRadiusMax); | |
| 87 | 25 | const double meanArea = std::numbers::pi * | |
| 88 | (std::pow(kRadiusMax, 3.0) - std::pow(kRadiusMin, 3.0)) / | ||
| 89 | (3.0 * (kRadiusMax - kRadiusMin)); | ||
| 90 | 25 | density_ = std::min(1.0, -std::log1p(-options_.cover) / meanArea); | |
| 91 | 25 | if (options_.exposure) { | |
| 92 | 5 | exposure_.emplace(*options_.exposure, surface_); | |
| 93 | } | ||
| 94 | 25 | if (surface_.IsBody()) { | |
| 95 | 2 | cells_.emplace(surface_); | |
| 96 | } | ||
| 97 | 57 | } | |
| 98 | |||
| 99 | 8410993 | void BedformLayer::AddField(Point& point, RandomStream& random, const Vec2d& centre, | |
| 100 | const Vec2d& offset, const Ground* ground) const { | ||
| 101 | 8410993 | const double barren = random.NextDouble(); | |
| 102 | 8410993 | const auto ox = static_cast<float>(97.0 * random.NextDouble()); | |
| 103 | 8410993 | const auto oy = static_cast<float>(97.0 * random.NextDouble()); | |
| 104 | 8410993 | const double radius = (kRadiusMin + (kRadiusMax - kRadiusMin) * random.NextDouble()) * cell_; | |
| 105 | 8410993 | const double scale = kWavelengthMin + kWavelengthSpan * random.NextDouble(); | |
| 106 | const double azimuth = | ||
| 107 | 8410993 | options_.windAzimuth + options_.azimuthSpread * (2.0 * random.NextDouble() - 1.0); | |
| 108 | 8410993 | const double phase = random.NextDouble(); | |
| 109 | 8410993 | const double dx = offset.x; | |
| 110 | 8410993 | const double dy = offset.y; | |
| 111 | const double rag = static_cast<double>( | ||
| 112 | 8410993 | PerlinNoise2(static_cast<float>(dx / (kRagRadii * radius)) + ox, | |
| 113 | 8410993 | static_cast<float>(dy / (kRagRadii * radius)) + oy, options_.seed)); | |
| 114 | 8410993 | const double inside = 1.0 - std::hypot(dx, dy) / (radius * (1.0 + kRagged * rag)); | |
| 115 | 8410993 | if (inside <= 0.0) { | |
| 116 | 7497172 | return; | |
| 117 | } | ||
| 118 | 1483517 | if (exposure_ && | |
| 119 | 1483517 | barren < exposure_->Exposure(*ground, centre.x, centre.y, options_.exposureSpacing)) { | |
| 120 | 96867 | return; | |
| 121 | } | ||
| 122 | 816954 | const double cover = Smoothstep(inside / kEdgeShare); | |
| 123 | 816954 | const double wavelength = options_.wavelength * scale; | |
| 124 | 816954 | const double wander = static_cast<double>(PerlinNoise2( | |
| 125 | 816954 | static_cast<float>(dx / (kWanderWavelengths * wavelength)) + oy, | |
| 126 | 816954 | static_cast<float>(dy / (kWanderWavelengths * wavelength)) + ox, options_.seed + 1u)); | |
| 127 | 816954 | const double along = (dx * std::cos(azimuth) + dy * std::sin(azimuth)) / wavelength + | |
| 128 | 816954 | options_.sinuosity * wander + phase; | |
| 129 | 816954 | const double t = along - std::floor(along); | |
| 130 | 816954 | const double upwind = 1.0 - options_.leeFraction; | |
| 131 | 816954 | const double profile = | |
| 132 | 816954 | t < upwind ? 0.5 * (1.0 - std::cos(std::numbers::pi * t / upwind)) | |
| 133 | 383180 | : 0.5 * (1.0 + std::cos(std::numbers::pi * (t - upwind) / options_.leeFraction)); | |
| 134 | 816954 | point.cover += cover; | |
| 135 | 816954 | point.relief += cover * options_.height * scale * profile; | |
| 136 | } | ||
| 137 | |||
| 138 | 1134671 | BedformLayer::Point BedformLayer::Evaluate(double x, double y, const Ground* ground) const { | |
| 139 | 1134671 | Point point; | |
| 140 | 1134671 | if (density_ <= 0.0) { | |
| 141 | 40001 | return point; | |
| 142 | } | ||
| 143 | 1094670 | if (exposure_) { | |
| 144 | 552362 | RequireGround(ground, kExposureReader); | |
| 145 | } | ||
| 146 | 1094668 | if (cells_) { | |
| 147 | 10612 | const CellIndex home = cells_->CellOf(x, y, cell_); | |
| 148 | 275912 | for (const CellIndex& cell : cells_->Neighbours(home, cell_, 2)) { | |
| 149 | 265300 | RandomStream random(options_.seed, kBedformSphereStream, | |
| 150 | 265300 | static_cast<std::uint32_t>(cell.i), | |
| 151 | 265300 | static_cast<std::uint32_t>(cell.j)); | |
| 152 | 265300 | const double chance = density_ * cells_->CellArea(cell, cell_) / (cell_ * cell_); | |
| 153 | 265300 | if (random.NextDouble() >= chance) { | |
| 154 | 165111 | continue; | |
| 155 | } | ||
| 156 | 100189 | const double u = random.NextDouble(); | |
| 157 | 100189 | const double v = random.NextDouble(); | |
| 158 | 100189 | const Vec2d centre = cells_->SamplePoint(u, v, cell, cell_); | |
| 159 | 100189 | AddField(point, random, centre, surface_.Offset(centre, {x, y}), ground); | |
| 160 | 10612 | } | |
| 161 | 10612 | return point; | |
| 162 | } | ||
| 163 | 1084056 | const auto ix = static_cast<std::int64_t>(std::floor(x / cell_)); | |
| 164 | 1084056 | const auto iy = static_cast<std::int64_t>(std::floor(y / cell_)); | |
| 165 | 4336224 | for (std::int64_t gy = iy - 1; gy <= iy + 1; ++gy) { | |
| 166 | 13008672 | for (std::int64_t gx = ix - 1; gx <= ix + 1; ++gx) { | |
| 167 | 9756504 | RandomStream random(options_.seed, kBedformStream, static_cast<std::uint32_t>(gx), | |
| 168 | static_cast<std::uint32_t>(gy), | ||
| 169 | 9756504 | static_cast<std::uint32_t>((gx >> 32) ^ ((gy >> 32) << 16))); | |
| 170 | 9756504 | if (random.NextDouble() >= density_) { | |
| 171 | 1445700 | continue; | |
| 172 | } | ||
| 173 | 8310804 | const double cx = (static_cast<double>(gx) + random.NextDouble()) * cell_; | |
| 174 | 8310804 | const double cy = (static_cast<double>(gy) + random.NextDouble()) * cell_; | |
| 175 | 8310804 | AddField(point, random, {cx, cy}, {x - cx, y - cy}, ground); | |
| 176 | } | ||
| 177 | } | ||
| 178 | 1084056 | return point; | |
| 179 | } | ||
| 180 | |||
| 181 | 203166 | double BedformLayer::CoverAt(double x, double y, const Ground* ground) const { | |
| 182 | 203166 | return std::min(1.0, Evaluate(x, y, ground).cover); | |
| 183 | } | ||
| 184 | |||
| 185 | 1194724 | float BedformLayer::HeightAt(float height, double x, double y, double spacing, | |
| 186 | const Ground* ground) const { | ||
| 187 | 1194724 | const double weight = ResolutionWeight(options_.wavelength, spacing, options_.fade); | |
| 188 | 1194724 | if (weight <= 0.0) { | |
| 189 | 263219 | return height; | |
| 190 | } | ||
| 191 | 931505 | const Point point = Evaluate(x, y, ground); | |
| 192 | 931505 | if (point.cover <= 0.0) { | |
| 193 | 345556 | return height; | |
| 194 | } | ||
| 195 | 1171898 | return static_cast<float>(static_cast<double>(height) + | |
| 196 | 585949 | weight * point.relief / std::max(1.0, point.cover)); | |
| 197 | } | ||
| 198 | |||
| 199 | 40466 | void BedformLayer::CheckContext(const LayerContext& context) const { | |
| 200 | 40466 | if (exposure_) { | |
| 201 | 20401 | RequireGround(context.ground, kExposureReader); | |
| 202 | } | ||
| 203 | 40465 | } | |
| 204 | |||
| 205 | } // namespace terraingen | ||
| 206 |