layers/dunes.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/layers/dunes.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 <numbers> | ||
| 11 | #include <stdexcept> | ||
| 12 | |||
| 13 | namespace terraingen { | ||
| 14 | |||
| 15 | namespace { | ||
| 16 | |||
| 17 | constexpr std::uint32_t kDuneStream = 0x44554e45u; | ||
| 18 | constexpr double kHalfPi = 0.5 * std::numbers::pi; | ||
| 19 | /// Wavelengths per lattice unit of the crest wander and of the defects along crests. | ||
| 20 | constexpr double kWanderWavelengths = 4.0; | ||
| 21 | constexpr double kDefectWavelengths = 3.0; | ||
| 22 | /// Phase by which barchanoid crest centres trail their links, in wavelengths, and the length of | ||
| 23 | /// a crest segment from link to link, in wavelengths. | ||
| 24 | constexpr double kBarchanoidBend = 0.15; | ||
| 25 | constexpr double kBarchanoidSegment = 1.5; | ||
| 26 | /// Newton steps for the closest point on a barchan's brink. | ||
| 27 | constexpr int kBrinkSteps = 6; | ||
| 28 | /// Share of the dune height over which sand ripples come in at a dune's edge. | ||
| 29 | constexpr double kSandEdge = 0.05; | ||
| 30 | /// Share of the ripple height kept on a slip face, where avalanches leave them faint. | ||
| 31 | constexpr double kSlipFaceRipples = 0.2; | ||
| 32 | /// Ripple crest wander and kinks: wavelengths per lattice unit of each, and the kinks' share of | ||
| 33 | /// the wander. | ||
| 34 | constexpr double kRippleWander = 5.0; | ||
| 35 | constexpr double kRippleKinkScale = 1.5; | ||
| 36 | constexpr double kRippleKinks = 0.25; | ||
| 37 | /// Share of the ripple height that fades along crests, and its scale in wavelengths. | ||
| 38 | constexpr double kRippleFading = 0.6; | ||
| 39 | constexpr double kRippleFadeScale = 4.0; | ||
| 40 | /// Forked crests: the smallest side in wavelengths of the cells their pairs are drawn in (cells | ||
| 41 | /// otherwise hold one pair on average), the separation of a pair in wavelengths, and the radius in | ||
| 42 | /// wavelengths within which its phase is whole and beyond which it is gone. | ||
| 43 | constexpr std::uint32_t kForkStream = 0x464f524bu; | ||
| 44 | constexpr double kForkMinCell = 0.5; | ||
| 45 | constexpr double kForkMin = 1.5; | ||
| 46 | constexpr double kForkMax = 3.0; | ||
| 47 | constexpr double kForkInner = 3.2; | ||
| 48 | constexpr double kForkOuter = 4.0; | ||
| 49 | /// Distance in wavelengths over which the ripples fade in from a fork's end. | ||
| 50 | constexpr double kForkCore = 0.5; | ||
| 51 | /// Crest breaks: the stream they are drawn from, and the distance in wavelengths over which a | ||
| 52 | /// crest falls to nothing at a break. | ||
| 53 | constexpr std::uint32_t kCrestBreakStream = 0x42524b53u; | ||
| 54 | constexpr double kBreakTaper = 1.0; | ||
| 55 | /// Grainflows: the share of a scarp's length its theatre head takes to reach full depth, the share | ||
| 56 | /// of a lobe's length it takes to rise, and the share after which its toe rounds off. | ||
| 57 | constexpr std::uint32_t kGrainflowStream = 0x47464c57u; | ||
| 58 | constexpr double kGrainflowHead = 0.15; | ||
| 59 | constexpr double kGrainflowRise = 0.25; | ||
| 60 | constexpr double kGrainflowToe = 0.6; | ||
| 61 | |||
| 62 | /// A cos^alpha bump falling from 1 at s = 0 to 0 at s = 1. | ||
| 63 | 13605102 | double Bump(double s, double alpha) { | |
| 64 | 13605102 | return s >= 1.0 ? 0.0 : std::pow(std::cos(kHalfPi * s), alpha); | |
| 65 | } | ||
| 66 | |||
| 67 | /// The fractional part of `x`, in [0, 1). | ||
| 68 | 11297102 | double Fract(double x) { | |
| 69 | 11297102 | return x - std::floor(x); | |
| 70 | } | ||
| 71 | |||
| 72 | /// One isolated dune: its height, width and how far it is a barchan rather than a dome. | ||
| 73 | struct Dune { | ||
| 74 | double height = 0.0; | ||
| 75 | double width = 0.0; | ||
| 76 | double barchan = 1.0; ///< 1 for a barchan, 0 for a dome, between for the transition. | ||
| 77 | }; | ||
| 78 | |||
| 79 | /// The height of a barchan at (x, y) in its wind frame: x downwind from the brink's apex, y across. | ||
| 80 | 31740372 | double BarchanAt(double x, double y, const Dune& dune, const DuneOptions& options) { | |
| 81 | 31740372 | const double half = 0.5 * dune.width; | |
| 82 | 31740372 | if (std::fabs(y) >= half) { | |
| 83 | 25615282 | return 0.0; | |
| 84 | } | ||
| 85 | 6125090 | const double eta = y / half; | |
| 86 | 6125090 | const double crest = dune.height * (1.0 - eta * eta); | |
| 87 | 6125090 | const double bend = options.hornLength * dune.height / (half * half); | |
| 88 | 6125090 | const double brink = bend * y * y; | |
| 89 | 6125090 | if (x <= brink) { | |
| 90 | 5199478 | const double stoss = dune.height / std::tan(options.stossSlope); | |
| 91 | 5199478 | return crest * Bump((brink - x) / stoss, options.stossExponent); | |
| 92 | } | ||
| 93 | 925612 | double t = y; | |
| 94 | 6479284 | for (int step = 0; step < kBrinkSteps; ++step) { | |
| 95 | 5553672 | const double f = 2.0 * bend * bend * t * t * t + (1.0 - 2.0 * bend * x) * t - y; | |
| 96 | 5553672 | const double df = 6.0 * bend * bend * t * t + 1.0 - 2.0 * bend * x; | |
| 97 | 5553672 | if (df == 0.0) { | |
| 98 | ✗ | break; | |
| 99 | } | ||
| 100 | 5553672 | t = std::clamp(t - f / df, -half, half); | |
| 101 | } | ||
| 102 | 925612 | const double tau = t / half; | |
| 103 | 925612 | const double distance = std::hypot(x - bend * t * t, y - t); | |
| 104 | 1851224 | return std::max(0.0, | |
| 105 | 925612 | dune.height * (1.0 - tau * tau) - std::tan(options.slipFaceAngle) * distance); | |
| 106 | } | ||
| 107 | |||
| 108 | /// The height of a dome at (x, y) about its centre in its wind frame. | ||
| 109 | 3733380 | double DomeAt(double x, double y, const Dune& dune, const DuneOptions& options) { | |
| 110 | 3733380 | const double along = x / (0.5 * options.domeLength * dune.width); | |
| 111 | 3733380 | const double across = y / (0.5 * dune.width); | |
| 112 | 3733380 | return dune.height * Bump(std::hypot(along, across), options.domeExponent); | |
| 113 | } | ||
| 114 | |||
| 115 | /// The farthest a dune of `width` reaches from its centre (m). | ||
| 116 | 31 | double Reach(double width, const DuneOptions& options) { | |
| 117 | 31 | const double height = options.heightRatio * width; | |
| 118 | ✗ | const double barchan = std::max( | |
| 119 | 31 | {height / std::tan(options.stossSlope), | |
| 120 | 31 | options.hornLength * height + height / std::tan(options.slipFaceAngle), 0.5 * width}); | |
| 121 | 31 | const double dome = 0.5 * width * std::max(1.0, options.domeLength); | |
| 122 | 62 | return std::max(barchan, dome); | |
| 123 | } | ||
| 124 | |||
| 125 | /// The ripple profile at phase `t` in [0, 1): a straight rise to a sharp crest, then a straight | ||
| 126 | /// fall over `lee`. | ||
| 127 | 277936 | double RippleProfile(double t, double lee) { | |
| 128 | 277936 | const double upwind = 1.0 - lee; | |
| 129 | 277936 | return t < upwind ? t / upwind : 1.0 - (t - upwind) / lee; | |
| 130 | } | ||
| 131 | |||
| 132 | } // namespace | ||
| 133 | |||
| 134 | 61 | void ValidateDuneOptions(const DuneOptions& options) { | |
| 135 | 61 | const double right = kHalfPi; | |
| 136 | 61 | if (!(options.slipFaceAngle > 0.0 && options.slipFaceAngle < right && | |
| 137 | 59 | options.stossSlope > 0.0 && options.stossSlope < options.slipFaceAngle && | |
| 138 | 56 | options.flankSlope > 0.0 && options.flankSlope < right)) { | |
| 139 | throw std::invalid_argument( | ||
| 140 | 5 | "Dune slopes must be positive and below 90 deg, the stoss below the slip face"); | |
| 141 | } | ||
| 142 | 112 | if (!(IsPositive(options.stossExponent) && IsPositive(options.domeExponent) && | |
| 143 | 56 | IsPositive(options.spacing) && IsPositive(options.width) && | |
| 144 | 54 | IsNonNegative(options.minWidth) && IsPositive(options.heightRatio) && | |
| 145 | 54 | IsNonNegative(options.hornLength) && IsPositive(options.domeLength) && | |
| 146 | 54 | IsPositive(options.wavelength) && IsNonNegative(options.height))) { | |
| 147 | 2 | throw std::invalid_argument("Dune sizes and exponents must be positive"); | |
| 148 | } | ||
| 149 | 54 | if (!(options.density >= 0.0 && options.density <= 1.0 && options.defects >= 0.0 && | |
| 150 | 52 | options.defects <= 1.0 && options.widthSpread >= 0.0 && options.widthSpread < 1.0)) { | |
| 151 | throw std::invalid_argument( | ||
| 152 | 3 | "Dune density and defects must be in [0, 1], and the width spread in [0, 1)"); | |
| 153 | } | ||
| 154 | 101 | if (!(IsNonNegative(options.sinuosity) && IsNonNegative(options.azimuthSpread) && | |
| 155 | 50 | std::isfinite(options.windAzimuth) && std::isfinite(options.anchor.x) && | |
| 156 | 49 | std::isfinite(options.anchor.y))) { | |
| 157 | throw std::invalid_argument( | ||
| 158 | 2 | "Dune sinuosity and azimuth spread must be finite and not negative, the anchor finite"); | |
| 159 | } | ||
| 160 | 49 | const DuneRipples& ripples = options.ripples; | |
| 161 | 98 | if (!(IsNonNegative(ripples.wavelength) && IsNonNegative(ripples.height) && | |
| 162 | 49 | ripples.leeFraction > 0.0 && ripples.leeFraction < 1.0 && | |
| 163 | 48 | IsNonNegative(ripples.sinuosity) && IsNonNegative(ripples.slopeCoupling) && | |
| 164 | 48 | ripples.interduneCover >= 0.0 && ripples.interduneCover <= 1.0 && | |
| 165 | 47 | IsNonNegative(ripples.defectDensity) && IsNonNegative(options.defectDensity) && | |
| 166 | 45 | ripples.flankStraightening >= 0.0 && ripples.flankStraightening <= 1.0)) { | |
| 167 | throw std::invalid_argument( | ||
| 168 | "Dune ripples need sizes and coupling not negative, a lee fraction in (0, 1) and an " | ||
| 169 | "interdune cover and flank straightening in [0, 1], and defect densities not " | ||
| 170 | 5 | "negative"); | |
| 171 | } | ||
| 172 | 44 | ValidateResolutionFade(options.fade); | |
| 173 | 43 | ValidateResolutionFade(ripples.fade); | |
| 174 | 43 | const DuneGrainflows& flows = options.grainflows; | |
| 175 | 161 | const auto range = [](double low, double high) { | |
| 176 | 161 | return IsPositive(low) && high >= low && std::isfinite(high); | |
| 177 | }; | ||
| 178 | 84 | if (!(flows.cover >= 0.0 && flows.cover <= 1.0 && | |
| 179 | 41 | range(flows.minScarpWidth, flows.maxScarpWidth) && | |
| 180 | 40 | range(flows.minScarpLength, flows.maxScarpLength) && | |
| 181 | 40 | range(flows.minLobeWidth, flows.maxLobeWidth) && | |
| 182 | 40 | range(flows.minLobeLength, flows.maxLobeLength) && IsNonNegative(flows.thickness))) { | |
| 183 | throw std::invalid_argument( | ||
| 184 | "Dune grainflows need a cover in [0, 1], positive sizes with each minimum at most its " | ||
| 185 | 5 | "maximum, and a thickness not negative"); | |
| 186 | } | ||
| 187 | 38 | ValidateResolutionFade(flows.fade); | |
| 188 | 37 | } | |
| 189 | |||
| 190 | 27 | DuneRipples MarsDuneRipples() { | |
| 191 | 27 | DuneRipples ripples; | |
| 192 | 27 | ripples.wavelength = 2.1; | |
| 193 | 27 | ripples.height = 0.2; | |
| 194 | 27 | const double leeSlope = std::tan(30.0 * kDegree); | |
| 195 | 27 | ripples.leeFraction = ripples.height / (ripples.wavelength * leeSlope); | |
| 196 | 27 | ripples.sinuosity = 0.8; | |
| 197 | 27 | ripples.defectDensity = 0.099; | |
| 198 | 27 | ripples.flankStraightening = 1.0; | |
| 199 | 27 | return ripples; | |
| 200 | } | ||
| 201 | |||
| 202 | 12 | DuneGrainflows MarsDuneGrainflows() { | |
| 203 | 12 | DuneGrainflows flows; | |
| 204 | 12 | flows.cover = 1.0; | |
| 205 | 12 | flows.minScarpWidth = 0.54; | |
| 206 | 12 | flows.maxScarpWidth = 0.67; | |
| 207 | 12 | flows.minScarpLength = 1.0; | |
| 208 | 12 | flows.maxScarpLength = 1.6; | |
| 209 | 12 | flows.minLobeWidth = 0.57; | |
| 210 | 12 | flows.maxLobeWidth = 0.66; | |
| 211 | 12 | flows.minLobeLength = 2.9; | |
| 212 | 12 | flows.maxLobeLength = 6.0; | |
| 213 | 12 | flows.thickness = 0.015; | |
| 214 | 12 | return flows; | |
| 215 | } | ||
| 216 | |||
| 217 | 8 | DuneOptions MarsBarchans() { | |
| 218 | 8 | DuneOptions dunes; | |
| 219 | 8 | dunes.shape = DuneShape::Barchan; | |
| 220 | 8 | dunes.slipFaceAngle = 29.0 * kDegree; | |
| 221 | 8 | dunes.stossSlope = 8.0 * kDegree; | |
| 222 | 8 | dunes.width = 370.0; | |
| 223 | 8 | dunes.minWidth = 200.0; | |
| 224 | 8 | dunes.domeLength = 2.0; | |
| 225 | 8 | dunes.spacing = 900.0; | |
| 226 | 8 | dunes.density = 0.5; | |
| 227 | 8 | dunes.ripples = MarsDuneRipples(); | |
| 228 | 8 | return dunes; | |
| 229 | } | ||
| 230 | |||
| 231 | 10 | DuneOptions MarsTransverseDunes() { | |
| 232 | 10 | DuneOptions dunes; | |
| 233 | 10 | dunes.shape = DuneShape::Transverse; | |
| 234 | 10 | dunes.slipFaceAngle = 29.0 * kDegree; | |
| 235 | 10 | dunes.stossSlope = 8.0 * kDegree; | |
| 236 | 10 | dunes.wavelength = 248.0; | |
| 237 | 10 | dunes.height = 28.0; | |
| 238 | 10 | dunes.defectDensity = 0.0027; | |
| 239 | 10 | dunes.ripples = MarsDuneRipples(); | |
| 240 | 10 | return dunes; | |
| 241 | } | ||
| 242 | |||
| 243 | 53 | DuneLayer::DuneLayer(DuneOptions options, Surface surface) | |
| 244 | 53 | : FormulaLayer(surface), options_(options) { | |
| 245 | 53 | ValidateDuneOptions(options_); | |
| 246 | 31 | wind_ = {std::cos(options_.windAzimuth), std::sin(options_.windAzimuth)}; | |
| 247 | 31 | const double widest = options_.width * (1.0 + options_.widthSpread); | |
| 248 | 31 | rings_ = static_cast<std::int64_t>(std::ceil(Reach(widest, options_) / options_.spacing)); | |
| 249 | 31 | if (surface_.IsBody()) { | |
| 250 | 1 | anchorDirection_ = UnitVector(options_.anchor.x, options_.anchor.y); | |
| 251 | } | ||
| 252 | 75 | } | |
| 253 | |||
| 254 | 9992116 | bool DuneLayer::Planar(double x, double y, Vec2d& planar) const { | |
| 255 | 9992116 | if (!surface_.IsBody()) { | |
| 256 | 9992113 | planar = {x, y}; | |
| 257 | 9992113 | return true; | |
| 258 | } | ||
| 259 | 3 | if (Dot(UnitVector(x, y), anchorDirection_) <= 0.0) { | |
| 260 | 1 | return false; | |
| 261 | } | ||
| 262 | 2 | planar = surface_.Offset(options_.anchor, {x, y}); | |
| 263 | 2 | return true; | |
| 264 | } | ||
| 265 | |||
| 266 | 3941528 | double DuneLayer::Barchans(const Vec2d& q) const { | |
| 267 | 3941528 | const auto ix = static_cast<std::int64_t>(std::floor(q.x / options_.spacing)); | |
| 268 | 3941528 | const auto iy = static_cast<std::int64_t>(std::floor(q.y / options_.spacing)); | |
| 269 | 3941528 | double highest = 0.0; | |
| 270 | 15766112 | for (std::int64_t gy = iy - rings_; gy <= iy + rings_; ++gy) { | |
| 271 | 47298336 | for (std::int64_t gx = ix - rings_; gx <= ix + rings_; ++gx) { | |
| 272 | 35473752 | RandomStream random(options_.seed, kDuneStream, static_cast<std::uint32_t>(gx), | |
| 273 | static_cast<std::uint32_t>(gy), | ||
| 274 | 35473752 | static_cast<std::uint32_t>((gx >> 32) ^ ((gy >> 32) << 16))); | |
| 275 | 35473752 | if (random.NextDouble() >= options_.density) { | |
| 276 | ✗ | continue; | |
| 277 | } | ||
| 278 | 35473752 | const double cx = (static_cast<double>(gx) + random.NextDouble()) * options_.spacing; | |
| 279 | 35473752 | const double cy = (static_cast<double>(gy) + random.NextDouble()) * options_.spacing; | |
| 280 | 35473752 | Dune dune; | |
| 281 | 35473752 | dune.width = | |
| 282 | 35473752 | options_.width * (1.0 + options_.widthSpread * (2.0 * random.NextDouble() - 1.0)); | |
| 283 | 35473752 | dune.height = options_.heightRatio * dune.width; | |
| 284 | const double azimuth = | ||
| 285 | 35473752 | options_.windAzimuth + options_.azimuthSpread * (2.0 * random.NextDouble() - 1.0); | |
| 286 | 70947504 | dune.barchan = options_.shape == DuneShape::Dome | |
| 287 | 35473752 | ? 0.0 | |
| 288 | 32811075 | : Smoothstep((dune.width - options_.minWidth) / | |
| 289 | 32811075 | (0.5 * std::max(options_.minWidth, 1e-9))); | |
| 290 | 35473752 | const double dx = q.x - cx; | |
| 291 | 35473752 | const double dy = q.y - cy; | |
| 292 | 35473752 | const double c = std::cos(azimuth); | |
| 293 | 35473752 | const double s = std::sin(azimuth); | |
| 294 | 35473752 | const double x = dx * c + dy * s; | |
| 295 | 35473752 | const double y = dy * c - dx * s; | |
| 296 | 35473752 | double h = 0.0; | |
| 297 | 35473752 | if (dune.barchan > 0.0) { | |
| 298 | 31740372 | h += dune.barchan * BarchanAt(x, y, dune, options_); | |
| 299 | } | ||
| 300 | 35473752 | if (dune.barchan < 1.0) { | |
| 301 | 3733380 | h += (1.0 - dune.barchan) * DomeAt(x, y, dune, options_); | |
| 302 | } | ||
| 303 | 35473752 | highest = std::max(highest, h); | |
| 304 | } | ||
| 305 | } | ||
| 306 | 3941528 | return highest; | |
| 307 | } | ||
| 308 | |||
| 309 | 11019166 | double DuneLayer::Crests(const Vec2d& q) const { | |
| 310 | 11019166 | const double lambda = options_.wavelength; | |
| 311 | 11019166 | const Vec2d across{-wind_.y, wind_.x}; | |
| 312 | 11019166 | const bool linear = options_.shape == DuneShape::Linear; | |
| 313 | 11019166 | const Vec2d normal = linear ? across : wind_; | |
| 314 | 8 | const auto segment = [&](const Vec2d& p) { | |
| 315 | 8 | return std::fabs(std::cos(std::numbers::pi * (p.x * across.x + p.y * across.y) / | |
| 316 | 8 | (kBarchanoidSegment * lambda))); | |
| 317 | 11019166 | }; | |
| 318 | 33057498 | const auto phase = [&](const Vec2d& p) { | |
| 319 | double value = | ||
| 320 | 33057498 | (p.x * normal.x + p.y * normal.y) / lambda + | |
| 321 | 66114996 | options_.sinuosity * static_cast<double>(PerlinNoise2Precise( | |
| 322 | 33057498 | p.x / (kWanderWavelengths * lambda), | |
| 323 | 33057498 | p.y / (kWanderWavelengths * lambda), options_.seed)); | |
| 324 | 33057498 | if (options_.shape == DuneShape::Barchanoid) { | |
| 325 | 6 | value += kBarchanoidBend * segment(p); | |
| 326 | } | ||
| 327 | 33057498 | return value; | |
| 328 | 11019166 | }; | |
| 329 | 11019166 | const double psi = phase(q); | |
| 330 | 11019166 | const double step = 0.01 * lambda; | |
| 331 | 11019166 | const double gx = (phase({q.x + step, q.y}) - psi) / step; | |
| 332 | 11019166 | const double gy = (phase({q.x, q.y + step}) - psi) / step; | |
| 333 | 11019166 | const double stretch = std::max(1e-6, std::hypot(gx, gy) * lambda); | |
| 334 | double amplitude = | ||
| 335 | 22038332 | 1.0 - options_.defects * | |
| 336 | 22038332 | (0.5 + 0.5 * static_cast<double>(PerlinNoise2Precise( | |
| 337 | 11019166 | q.x / (kDefectWavelengths * lambda), | |
| 338 | 11019166 | q.y / (kDefectWavelengths * lambda), options_.seed + 1u))); | |
| 339 | 11019166 | if (options_.shape == DuneShape::Barchanoid) { | |
| 340 | 2 | amplitude *= 0.6 + 0.4 * segment(q); | |
| 341 | } | ||
| 342 | 11019166 | if (options_.defectDensity > 0.0) { | |
| 343 | 1512000 | const Vec2d along = linear ? wind_ : across; | |
| 344 | 1512000 | amplitude *= | |
| 345 | 1512000 | CrestBreaks(static_cast<std::int64_t>(std::floor(psi)), q.x * along.x + q.y * along.y); | |
| 346 | } | ||
| 347 | 11019166 | const double height = amplitude * options_.height; | |
| 348 | 11019166 | if (height <= 0.0) { | |
| 349 | ✗ | return 0.0; | |
| 350 | } | ||
| 351 | 11019166 | const double u = Fract(psi); | |
| 352 | 11019166 | if (linear) { | |
| 353 | const double flank = | ||
| 354 | 1445906 | std::min(0.5, height * stretch / (std::tan(options_.flankSlope) * lambda)); | |
| 355 | 1445906 | const double offset = std::fabs(u - 0.5); | |
| 356 | 1445906 | return offset >= flank ? 0.0 : height * Bump(offset / flank, 2.0); | |
| 357 | } | ||
| 358 | 9573260 | const double leeSlope = std::tan(options_.slipFaceAngle); | |
| 359 | 9573260 | const double stossSlope = std::tan(options_.stossSlope); | |
| 360 | 9573260 | const double tallest = options_.height * stretch / lambda; | |
| 361 | 9573260 | const double brink = std::min(1.0 - std::min(1.0, tallest / leeSlope), tallest / stossSlope); | |
| 362 | 9573260 | const double stoss = std::min(brink, height * stretch / (stossSlope * lambda)); | |
| 363 | 9573260 | if (u <= brink) { | |
| 364 | 4286183 | return stoss <= 0.0 ? 0.0 : height * Bump((brink - u) / stoss, options_.stossExponent); | |
| 365 | } | ||
| 366 | 5287077 | const double down = (u - brink) * lambda / stretch; | |
| 367 | 5287077 | return std::max(0.0, height - leeSlope * down); | |
| 368 | } | ||
| 369 | |||
| 370 | 14960694 | double DuneLayer::Height(const Vec2d& q) const { | |
| 371 | 14960694 | switch (options_.shape) { | |
| 372 | 3941528 | case DuneShape::Barchan: | |
| 373 | case DuneShape::Dome: | ||
| 374 | 3941528 | return Barchans(q); | |
| 375 | 11019166 | case DuneShape::Transverse: | |
| 376 | case DuneShape::Barchanoid: | ||
| 377 | case DuneShape::Linear: | ||
| 378 | 11019166 | return Crests(q); | |
| 379 | } | ||
| 380 | ✗ | return 0.0; | |
| 381 | } | ||
| 382 | |||
| 383 | 1512000 | double DuneLayer::CrestBreaks(std::int64_t crest, double along) const { | |
| 384 | 1512000 | const double taper = kBreakTaper * options_.wavelength; | |
| 385 | 1512000 | const double length = 1.0 / options_.defectDensity + taper; | |
| 386 | 1512000 | const auto cell = static_cast<std::int64_t>(std::floor(along / length)); | |
| 387 | 1512000 | double weight = 1.0; | |
| 388 | 6048000 | for (std::int64_t c = cell - 1; c <= cell + 1; ++c) { | |
| 389 | 4536000 | RandomStream random(options_.seed, kCrestBreakStream, static_cast<std::uint32_t>(crest), | |
| 390 | static_cast<std::uint32_t>(c), | ||
| 391 | 4536000 | static_cast<std::uint32_t>((crest >> 32) ^ ((c >> 32) << 16))); | |
| 392 | 4536000 | const double at = (static_cast<double>(c) + random.NextDouble()) * length; | |
| 393 | 4536000 | weight = std::min(weight, Smoothstep(std::fabs(along - at) / taper)); | |
| 394 | } | ||
| 395 | 1512000 | return weight; | |
| 396 | } | ||
| 397 | |||
| 398 | 1242145 | Vec2d DuneLayer::Gradient(const Vec2d& q, double step) const { | |
| 399 | 1242145 | return {(Height({q.x + step, q.y}) - Height({q.x - step, q.y})) / (2.0 * step), | |
| 400 | 1242145 | (Height({q.x, q.y + step}) - Height({q.x, q.y - step})) / (2.0 * step)}; | |
| 401 | } | ||
| 402 | |||
| 403 | 964209 | double DuneLayer::SlipFaceWeight(const Vec2d& g) const { | |
| 404 | 964209 | const double grade = std::hypot(g.x, g.y); | |
| 405 | 964209 | if (grade <= 0.0) { | |
| 406 | 294672 | return 0.0; | |
| 407 | } | ||
| 408 | 669537 | const double facing = -(g.x * wind_.x + g.y * wind_.y) / grade; | |
| 409 | 669537 | return Smoothstep(2.0 * facing) * | |
| 410 | 669537 | Smoothstep(2.0 * grade / std::tan(options_.slipFaceAngle) - 1.0); | |
| 411 | } | ||
| 412 | |||
| 413 | 964209 | double DuneLayer::Grainflows(const Vec2d& q, double face) const { | |
| 414 | 964209 | const DuneGrainflows& flows = options_.grainflows; | |
| 415 | 964209 | if (face <= 0.0) { | |
| 416 | 851609 | return 0.0; | |
| 417 | } | ||
| 418 | 112600 | const Vec2d across{-wind_.y, wind_.x}; | |
| 419 | 112600 | const double u = q.x * wind_.x + q.y * wind_.y; | |
| 420 | 112600 | const double v = q.x * across.x + q.y * across.y; | |
| 421 | 112600 | const double cellAlong = flows.maxScarpLength + flows.maxLobeLength; | |
| 422 | 112600 | const double cellAcross = std::max(flows.maxScarpWidth, flows.maxLobeWidth); | |
| 423 | 112600 | const auto iu = static_cast<std::int64_t>(std::floor(u / cellAlong)); | |
| 424 | 112600 | const auto iv = static_cast<std::int64_t>(std::floor(v / cellAcross)); | |
| 425 | 3896748 | const auto between = [](double low, double high, double t) { return low + (high - low) * t; }; | |
| 426 | 112600 | double relief = 0.0; | |
| 427 | 450400 | for (std::int64_t cu = iu - 1; cu <= iu + 1; ++cu) { | |
| 428 | 1351200 | for (std::int64_t cv = iv - 1; cv <= iv + 1; ++cv) { | |
| 429 | 1013400 | RandomStream random(options_.seed, kGrainflowStream, static_cast<std::uint32_t>(cu), | |
| 430 | static_cast<std::uint32_t>(cv), | ||
| 431 | 1013400 | static_cast<std::uint32_t>((cu >> 32) ^ ((cv >> 32) << 16))); | |
| 432 | 1013400 | if (random.NextDouble() >= flows.cover) { | |
| 433 | 39213 | continue; | |
| 434 | } | ||
| 435 | 974187 | const double head = (static_cast<double>(cu) + random.NextDouble()) * cellAlong; | |
| 436 | 974187 | const double middle = (static_cast<double>(cv) + random.NextDouble()) * cellAcross; | |
| 437 | const double scarpWidth = | ||
| 438 | 974187 | between(flows.minScarpWidth, flows.maxScarpWidth, random.NextDouble()); | |
| 439 | const double scarpLength = | ||
| 440 | 974187 | between(flows.minScarpLength, flows.maxScarpLength, random.NextDouble()); | |
| 441 | const double lobeWidth = | ||
| 442 | 974187 | between(flows.minLobeWidth, flows.maxLobeWidth, random.NextDouble()); | |
| 443 | const double lobeLength = | ||
| 444 | 974187 | between(flows.minLobeLength, flows.maxLobeLength, random.NextDouble()); | |
| 445 | 974187 | const double s = u - head; | |
| 446 | 974187 | const double n = std::fabs(v - middle); | |
| 447 | 974187 | if (s >= 0.0 && s < scarpLength) { | |
| 448 | 57004 | const double half = 0.5 * scarpWidth; | |
| 449 | const double profile = | ||
| 450 | 57004 | Smoothstep(s / (kGrainflowHead * scarpLength)) * (1.0 - s / scarpLength); | |
| 451 | 57004 | relief -= profile * std::max(0.0, 1.0 - (n / half) * (n / half)); | |
| 452 | } | ||
| 453 | 974187 | const double t = s - scarpLength; | |
| 454 | 974187 | if (t >= 0.0 && t < lobeLength) { | |
| 455 | 392724 | const double toe = std::max(0.0, (t - kGrainflowToe * lobeLength) / | |
| 456 | 196362 | ((1.0 - kGrainflowToe) * lobeLength)); | |
| 457 | 196362 | const double half = 0.5 * lobeWidth * std::sqrt(std::max(0.0, 1.0 - toe * toe)); | |
| 458 | 196362 | if (n < half) { | |
| 459 | 54631 | const double profile = Smoothstep(t / (kGrainflowRise * lobeLength)); | |
| 460 | 54631 | relief += profile * (1.0 - (n / half) * (n / half)); | |
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | } | ||
| 465 | 112600 | return face * flows.thickness * relief; | |
| 466 | } | ||
| 467 | |||
| 468 | 277936 | double DuneLayer::Ripples(const Vec2d& q, double dune) const { | |
| 469 | 277936 | const DuneRipples& ripples = options_.ripples; | |
| 470 | 277936 | const double lambda = ripples.wavelength; | |
| 471 | 277936 | const Vec2d g = Gradient(q, 0.25 * lambda); | |
| 472 | 277936 | const double gx = g.x; | |
| 473 | 277936 | const double gy = g.y; | |
| 474 | 277936 | const double kx = wind_.x - ripples.slopeCoupling * gx; | |
| 475 | 277936 | const double ky = wind_.y - ripples.slopeCoupling * gy; | |
| 476 | 277936 | const double tighten = std::max(1e-6, std::hypot(kx, ky)); | |
| 477 | 277936 | double sinuosity = ripples.sinuosity; | |
| 478 | 277936 | if (ripples.flankStraightening > 0.0) { | |
| 479 | 276751 | const double grade = std::hypot(gx, gy); | |
| 480 | const double stoss = | ||
| 481 | 276751 | grade > 0.0 ? std::max(0.0, (gx * wind_.x + gy * wind_.y) / grade) : 1.0; | |
| 482 | 553502 | sinuosity *= 1.0 - ripples.flankStraightening * | |
| 483 | 276751 | Smoothstep(grade / std::tan(options_.stossSlope)) * (1.0 - stoss); | |
| 484 | } | ||
| 485 | 277936 | double core = 1.0; | |
| 486 | const double psi = | ||
| 487 | 555872 | (q.x * wind_.x + q.y * wind_.y - ripples.slopeCoupling * dune) / lambda + | |
| 488 | 833808 | sinuosity * (static_cast<double>(PerlinNoise2Precise(q.x / (kRippleWander * lambda), | |
| 489 | 277936 | q.y / (kRippleWander * lambda), | |
| 490 | 277936 | options_.seed + 2u)) + | |
| 491 | 555872 | kRippleKinks * static_cast<double>(PerlinNoise2Precise( | |
| 492 | 277936 | q.x / (kRippleKinkScale * lambda), | |
| 493 | 277936 | q.y / (kRippleKinkScale * lambda), options_.seed + 3u))) + | |
| 494 | 277936 | Forks(q, lambda, ripples.defectDensity, core); | |
| 495 | const double fading = | ||
| 496 | 1.0 - | ||
| 497 | 555872 | kRippleFading * (0.5 + 0.5 * static_cast<double>(PerlinNoise2Precise( | |
| 498 | 277936 | q.x / (kRippleFadeScale * lambda), | |
| 499 | 277936 | q.y / (kRippleFadeScale * lambda), options_.seed + 4u))); | |
| 500 | 277936 | const double sand = SandOf(dune); | |
| 501 | 277936 | const double steep = std::hypot(gx, gy) / std::tan(options_.slipFaceAngle); | |
| 502 | 277936 | const double avalanche = 1.0 - (1.0 - kSlipFaceRipples) * Smoothstep(2.0 * steep - 1.0); | |
| 503 | 277936 | const double share = | |
| 504 | 277936 | (ripples.interduneCover + (1.0 - ripples.interduneCover) * sand) * avalanche; | |
| 505 | 555872 | return share * fading * core * ripples.height / std::max(1.0, tighten) * | |
| 506 | 555872 | RippleProfile(Fract(psi), ripples.leeFraction); | |
| 507 | } | ||
| 508 | |||
| 509 | 277936 | double DuneLayer::Forks(const Vec2d& q, double wavelength, double density, double& core) const { | |
| 510 | 277936 | if (density <= 0.0) { | |
| 511 | 2370 | return 0.0; | |
| 512 | } | ||
| 513 | 275566 | const double cell = std::max(std::sqrt(wavelength / density), kForkMinCell * wavelength); | |
| 514 | 275566 | const double none = std::exp(-density * cell * cell / wavelength); | |
| 515 | 275566 | const auto rings = static_cast<std::int64_t>(std::ceil(kForkOuter * wavelength / cell)); | |
| 516 | 275566 | const double inner = kForkInner * wavelength; | |
| 517 | 275566 | const double outer = kForkOuter * wavelength; | |
| 518 | 275566 | const auto ix = static_cast<std::int64_t>(std::floor(q.x / cell)); | |
| 519 | 275566 | const auto iy = static_cast<std::int64_t>(std::floor(q.y / cell)); | |
| 520 | 275566 | double phase = 0.0; | |
| 521 | 1665502 | for (std::int64_t gy = iy - rings; gy <= iy + rings; ++gy) { | |
| 522 | 8424358 | for (std::int64_t gx = ix - rings; gx <= ix + rings; ++gx) { | |
| 523 | 7034422 | RandomStream random(options_.seed, kForkStream, static_cast<std::uint32_t>(gx), | |
| 524 | static_cast<std::uint32_t>(gy), | ||
| 525 | 7034422 | static_cast<std::uint32_t>((gx >> 32) ^ ((gy >> 32) << 16))); | |
| 526 | 7034422 | double draw = random.NextDouble(); | |
| 527 | 13859032 | while (draw > none) { | |
| 528 | 6824610 | draw *= random.NextDouble(); | |
| 529 | 6824610 | const Vec2d centre{(static_cast<double>(gx) + random.NextDouble()) * cell, | |
| 530 | 6824610 | (static_cast<double>(gy) + random.NextDouble()) * cell}; | |
| 531 | 6824610 | const double turn = 2.0 * std::numbers::pi * random.NextDouble(); | |
| 532 | const double half = | ||
| 533 | 6824610 | 0.5 * wavelength * (kForkMin + (kForkMax - kForkMin) * random.NextDouble()); | |
| 534 | 6824610 | const double sign = random.NextDouble() < 0.5 ? -1.0 : 1.0; | |
| 535 | 6824610 | const double r = std::hypot(q.x - centre.x, q.y - centre.y); | |
| 536 | 6824610 | const double window = 1.0 - Smoothstep((r - inner) / (outer - inner)); | |
| 537 | 6824610 | if (window <= 0.0) { | |
| 538 | 3979005 | continue; | |
| 539 | } | ||
| 540 | 2845605 | const Vec2d a{centre.x - half * std::cos(turn), centre.y - half * std::sin(turn)}; | |
| 541 | 2845605 | const Vec2d b{centre.x + half * std::cos(turn), centre.y + half * std::sin(turn)}; | |
| 542 | double subtended = | ||
| 543 | 2845605 | std::atan2(q.y - a.y, q.x - a.x) - std::atan2(q.y - b.y, q.x - b.x); | |
| 544 | 2845605 | subtended -= 2.0 * std::numbers::pi * | |
| 545 | 2845605 | std::floor((subtended + std::numbers::pi) / (2.0 * std::numbers::pi)); | |
| 546 | 2845605 | phase += sign * window * subtended / (2.0 * std::numbers::pi); | |
| 547 | const double nearest = | ||
| 548 | 2845605 | std::min(std::hypot(q.x - a.x, q.y - a.y), std::hypot(q.x - b.x, q.y - b.y)); | |
| 549 | 2845605 | core = std::min(core, Smoothstep(nearest / (kForkCore * wavelength))); | |
| 550 | } | ||
| 551 | } | ||
| 552 | } | ||
| 553 | 275566 | return phase; | |
| 554 | } | ||
| 555 | |||
| 556 | 279755 | double DuneLayer::SandOf(double dune) const { | |
| 557 | 279755 | const double reference = | |
| 558 | 4389 | options_.shape == DuneShape::Barchan || options_.shape == DuneShape::Dome | |
| 559 | 284144 | ? options_.heightRatio * options_.width | |
| 560 | : options_.height; | ||
| 561 | 279755 | return Smoothstep(dune / std::max(1e-9, kSandEdge * reference)); | |
| 562 | } | ||
| 563 | |||
| 564 | 1819 | double DuneLayer::MaterialCoverAt(double x, double y, double /*spacing*/, | |
| 565 | const Ground* /*ground*/) const { | ||
| 566 | 1819 | Vec2d q; | |
| 567 | 1819 | if (!Planar(x, y, q)) { | |
| 568 | ✗ | return 0.0; | |
| 569 | } | ||
| 570 | 1819 | const double sand = SandOf(Height(q)); | |
| 571 | 1819 | const DuneRipples& ripples = options_.ripples; | |
| 572 | 1819 | if (ripples.wavelength <= 0.0 || ripples.height <= 0.0) { | |
| 573 | 1414 | return sand; | |
| 574 | } | ||
| 575 | 405 | return ripples.interduneCover + (1.0 - ripples.interduneCover) * sand; | |
| 576 | } | ||
| 577 | |||
| 578 | 7254345 | double DuneLayer::DuneHeightAt(double x, double y) const { | |
| 579 | 7254345 | Vec2d q; | |
| 580 | 14508690 | return Planar(x, y, q) ? Height(q) : 0.0; | |
| 581 | } | ||
| 582 | |||
| 583 | 2735952 | float DuneLayer::HeightAt(float height, double x, double y, double spacing, | |
| 584 | const Ground* /*ground*/) const { | ||
| 585 | 2735952 | Vec2d q; | |
| 586 | 2735952 | if (!Planar(x, y, q)) { | |
| 587 | ✗ | return height; | |
| 588 | } | ||
| 589 | 2735952 | const bool isolated = options_.shape == DuneShape::Barchan || options_.shape == DuneShape::Dome; | |
| 590 | 2735952 | const double size = isolated ? options_.width : options_.wavelength; | |
| 591 | 2735952 | const double duneWeight = ResolutionWeight(size, spacing, options_.fade); | |
| 592 | 2735952 | const DuneRipples& ripples = options_.ripples; | |
| 593 | 1328562 | const double rippleWeight = ripples.wavelength > 0.0 && ripples.height > 0.0 | |
| 594 | 4064514 | ? ResolutionWeight(ripples.wavelength, spacing, ripples.fade) | |
| 595 | 2735952 | : 0.0; | |
| 596 | 2735952 | const DuneGrainflows& flows = options_.grainflows; | |
| 597 | 2014889 | const double flowWeight = flows.cover > 0.0 && flows.thickness > 0.0 | |
| 598 | 4750841 | ? ResolutionWeight(flows.minLobeWidth, spacing, flows.fade) | |
| 599 | 2735952 | : 0.0; | |
| 600 | 2735952 | if (duneWeight <= 0.0 && rippleWeight <= 0.0 && flowWeight <= 0.0) { | |
| 601 | 1 | return height; | |
| 602 | } | ||
| 603 | 2735951 | const double dune = Height(q); | |
| 604 | 2735951 | double relief = duneWeight * dune; | |
| 605 | 2735951 | if (rippleWeight > 0.0) { | |
| 606 | 277936 | relief += rippleWeight * Ripples(q, dune); | |
| 607 | } | ||
| 608 | 2735951 | if (flowWeight > 0.0) { | |
| 609 | 964209 | const Vec2d g = Gradient(q, 0.25 * flows.minScarpLength); | |
| 610 | 964209 | relief += flowWeight * Grainflows(q, SlipFaceWeight(g)); | |
| 611 | } | ||
| 612 | 2735951 | return static_cast<float>(static_cast<double>(height) + relief); | |
| 613 | } | ||
| 614 | |||
| 615 | } // namespace terraingen | ||
| 616 |