layers/dunes.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /// @file | ||
| 2 | /// @brief `DuneLayer`, dune fields of barchans, domes or crest-line dunes, with ripples on them. | ||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "terraingen/layers/fade.h" | ||
| 6 | #include "terraingen/layers/layer.h" | ||
| 7 | #include "terraingen/layers/material.h" | ||
| 8 | #include "terraingen/util/geometry.h" | ||
| 9 | |||
| 10 | #include <cstdint> | ||
| 11 | |||
| 12 | namespace terraingen { | ||
| 13 | |||
| 14 | /// The kind of dune a `DuneLayer` builds. | ||
| 15 | enum class DuneShape { | ||
| 16 | Barchan, ///< Isolated crescents with a slip face, horns downwind; domes below `minWidth`. | ||
| 17 | Dome, ///< Isolated rounded heaps without a slip face. | ||
| 18 | Transverse, ///< Continuous crests across the wind, each with a slip face. | ||
| 19 | Barchanoid, ///< Transverse crests bent into linked crescents. | ||
| 20 | Linear, ///< Symmetric crests along the wind, as under a bimodal wind on scarce sand. | ||
| 21 | }; | ||
| 22 | |||
| 23 | /// Ripples superposed on dunes, their crests bent by the dune's slope: each rises along a straight | ||
| 24 | /// stoss to a sharp crest and falls along a straight lee. | ||
| 25 | struct DuneRipples { | ||
| 26 | 6 | double wavelength = 0.0; ///< Crest to crest on flat sand (m); 0 for no ripples. | |
| 27 | 6 | double height = 0.0; ///< Trough to crest on flat sand (m). | |
| 28 | /// Share of a wavelength the lee face takes, in (0, 1): 0.5 is symmetric, less is steeper. | ||
| 29 | 6 | double leeFraction = 0.5; | |
| 30 | 6 | double sinuosity = 0.3; ///< How far a crest wanders along its length, in wavelengths. | |
| 31 | /// How strongly the dune's slope turns and tightens the ripples: their wave vector is the wind | ||
| 32 | /// minus this times the dune's gradient, so they shorten and lower on steep lee faces and turn | ||
| 33 | /// on flanks; they never grow above `height`. | ||
| 34 | 6 | double slopeCoupling = 1.0; | |
| 35 | 6 | double interduneCover = 0.0; ///< Share of the ripples' height kept off the dunes, in [0, 1]. | |
| 36 | /// Defect density: pairs of crest ends (forked crests, Y-junctions) per metre of crest, as | ||
| 37 | /// Ewing et al. (2010) measure it; 0 for none. | ||
| 38 | 6 | double defectDensity = 0.0; | |
| 39 | /// How far crests straighten off the stoss, in [0, 1]: 0 keeps `sinuosity` everywhere, 1 | ||
| 40 | /// keeps it on flat sand and stoss slopes and takes it away on flanks and lee slopes as steep | ||
| 41 | /// as the stoss. | ||
| 42 | 6 | double flankStraightening = 0.0; | |
| 43 | 6 | ResolutionFade fade{}; ///< How ripples too fine for the spacing fade out. | |
| 44 | |||
| 45 | 6 | bool operator==(const DuneRipples&) const = default; | |
| 46 | }; | ||
| 47 | |||
| 48 | /// Grainflows on slip faces: avalanches that leave a theatre-headed scarp below the brink and a | ||
| 49 | /// tongue-shaped lobe of sand below it. Each is drawn on a lattice aligned with the wind, its sizes | ||
| 50 | /// uniform between the bounds, and shown only where the ground faces downwind near the slip face | ||
| 51 | /// angle. Lengths are in metres, along the slope; widths across it. | ||
| 52 | struct DuneGrainflows { | ||
| 53 | 6 | double cover = 0.0; ///< Chance, in [0, 1], that each lattice cell holds one; 0 for none. | |
| 54 | 6 | double minScarpWidth = 0.5; | |
| 55 | 6 | double maxScarpWidth = 0.5; | |
| 56 | 6 | double minScarpLength = 1.0; | |
| 57 | 6 | double maxScarpLength = 1.0; | |
| 58 | 6 | double minLobeWidth = 0.5; | |
| 59 | 6 | double maxLobeWidth = 0.5; | |
| 60 | 6 | double minLobeLength = 3.0; | |
| 61 | 6 | double maxLobeLength = 3.0; | |
| 62 | 6 | double thickness = 0.015; ///< Depth of a scarp and height of a lobe (m). | |
| 63 | 6 | ResolutionFade fade{}; ///< How grainflows too narrow for the spacing fade out. | |
| 64 | |||
| 65 | 6 | bool operator==(const DuneGrainflows&) const = default; | |
| 66 | }; | ||
| 67 | |||
| 68 | /// The dunes a `DuneLayer` builds, their sizes and slopes. Lengths are in metres, angles in | ||
| 69 | /// radians. | ||
| 70 | struct DuneOptions { | ||
| 71 | 6 | DuneShape shape = DuneShape::Barchan; | |
| 72 | /// Where the wind blows toward, radians counter-clockwise from +x; slip faces face it. | ||
| 73 | 6 | double windAzimuth = 0.0; | |
| 74 | 6 | double azimuthSpread = 0.0; ///< Each barchan or dome's wind differs by up to this. | |
| 75 | 6 | double slipFaceAngle = 0.5585053606381855; ///< Lee slip face slope; 32 deg. | |
| 76 | 6 | double stossSlope = 0.17453292519943295; ///< Mean windward slope; 10 deg. | |
| 77 | 6 | double stossExponent = 3.0; ///< Exponent of the cos^alpha windward profile of a dune. | |
| 78 | 6 | double domeExponent = 1.8; ///< Exponent of the cos^alpha profile of a dome. | |
| 79 | |||
| 80 | /// Barchans and domes: one candidate per lattice cell of `spacing`. | ||
| 81 | 6 | double spacing = 800.0; | |
| 82 | 6 | double density = 0.5; ///< Chance a cell holds a dune, in [0, 1]. | |
| 83 | 6 | double width = 400.0; ///< Mean width, horn to horn (m). | |
| 84 | 6 | double widthSpread = 0.3; ///< Each width varies from (1 - it) to (1 + it) times `width`. | |
| 85 | 6 | double minWidth = 200.0; ///< Width below which a barchan is a dome without a slip face. | |
| 86 | 6 | double heightRatio = 1.0 / 15.0; ///< Height over width. | |
| 87 | 6 | double hornLength = 9.1; ///< How far the horns reach downwind of the crest, in heights. | |
| 88 | 6 | double domeLength = 2.0; ///< Length of a dome over its width. | |
| 89 | |||
| 90 | /// Transverse, barchanoid and linear dunes: crest-to-crest spacing (m). | ||
| 91 | 6 | double wavelength = 550.0; | |
| 92 | 6 | double height = 20.0; ///< Crest height (m). | |
| 93 | 6 | double sinuosity = 0.3; ///< How far a crest wanders along its length, in wavelengths. | |
| 94 | /// Share of the height that varies along crests, ending some, in [0, 1]. | ||
| 95 | 6 | double defects = 0.3; | |
| 96 | /// Crest-line dunes: pairs of crest ends per metre of crest, as Ewing et al. (2010) measure it; | ||
| 97 | /// crests break into segments that stand above half their height for 1 / `defectDensity` | ||
| 98 | /// metres on average, falling to nothing over a wavelength either side of each break. 0 for | ||
| 99 | /// none. | ||
| 100 | 6 | double defectDensity = 0.0; | |
| 101 | 6 | double flankSlope = 0.3490658503988659; ///< Mean flank slope of linear dunes; 20 deg. | |
| 102 | |||
| 103 | 6 | std::uint32_t seed = 1; ///< Varies the dunes and ripples. | |
| 104 | /// On a body, the surface point whose tangent frame the dunes are laid out in; true to scale | ||
| 105 | /// near it, and no dunes on the far hemisphere. | ||
| 106 | 6 | Vec2d anchor{}; | |
| 107 | ResolutionFade | ||
| 108 | 6 | fade{}; ///< How dunes too fine for the spacing fade out, by samples across them. | |
| 109 | 6 | DuneRipples ripples{}; | |
| 110 | 6 | DuneGrainflows grainflows{}; ///< Avalanches on the slip faces; none by default. | |
| 111 | |||
| 112 | 6 | bool operator==(const DuneOptions&) const = default; | |
| 113 | }; | ||
| 114 | |||
| 115 | /// Checks dune options. | ||
| 116 | /// @throws std::invalid_argument for non-positive sizes or slopes, a slip face or flank of 90 deg | ||
| 117 | /// or more, a stoss slope not below the slip face, a density, defects or interdune cover outside | ||
| 118 | /// [0, 1], a width spread outside [0, 1), negative spreads or sinuosity, or invalid ripples or | ||
| 119 | /// fade. | ||
| 120 | void ValidateDuneOptions(const DuneOptions& options); | ||
| 121 | |||
| 122 | /// Martian large wind ripples to lay on dunes: 2.1 m wavelength, 0.2 m height and a 30 deg lee; | ||
| 123 | /// 0.099 pairs of crest ends per metre, as Olympia Undae's 2.1 m wind ripples (Ewing et al. 2010); | ||
| 124 | /// sinuous on stoss slopes and straighter on flanks and lee slopes (Lapotre et al. 2016; Ewing et | ||
| 125 | /// al. 2017). | ||
| 126 | DuneRipples MarsDuneRipples(); | ||
| 127 | |||
| 128 | /// The grainflows of Namib Dune's lee slope (Ewing et al. 2017, Table 1): scarps 0.54 to 0.67 m | ||
| 129 | /// wide and 1.0 to 1.6 m long, lobes 0.57 to 0.66 m wide and 2.9 to 6 m long. Their thickness is | ||
| 130 | /// not measured on Mars; 0.015 m is that of a Namibian barchan's grainflows (Nield et al. 2017, in | ||
| 131 | /// Ewing et al. 2017). Every cell holds one, as palimpsest grainflows are abundant on that slope. | ||
| 132 | DuneGrainflows MarsDuneGrainflows(); | ||
| 133 | |||
| 134 | /// Martian barchans with large ripples: 29 deg slip faces, 8 deg stoss slopes, 370 m wide, domes | ||
| 135 | /// below 200 m wide and twice as long as wide; spacing and density are placeholders to set per | ||
| 136 | /// site. | ||
| 137 | DuneOptions MarsBarchans(); | ||
| 138 | |||
| 139 | /// The transverse dunes of Mars's north polar erg with large ripples: 248 m apart and 28 m high, | ||
| 140 | /// with 29 deg slip faces and 8 deg stoss slopes, and the defect density of Olympia Undae's primary | ||
| 141 | /// crests, 0.0027 pairs of ends per metre (Ewing et al. 2010). | ||
| 142 | DuneOptions MarsTransverseDunes(); | ||
| 143 | |||
| 144 | /// Wind-blown dunes added to the terrain. | ||
| 145 | /// | ||
| 146 | /// Barchans are crescents: cross-wind slices of height H(1 - (y/Y)^2), a cos^alpha windward | ||
| 147 | /// profile up to a brink bent downwind toward the horns, and a slip face falling away from the | ||
| 148 | /// brink at `slipFaceAngle`; overlapping dunes join as their upper envelope. Crest-line dunes | ||
| 149 | /// follow a warped crest phase whose lee width keeps the slope at the slip face angle, to within a | ||
| 150 | /// few degrees where crests bend. A function | ||
| 151 | /// of position only, so grids, tiles and points agree. | ||
| 152 | class DuneLayer final : public FormulaLayer, public MaterialSource { | ||
| 153 | public: | ||
| 154 | /// On a body, dunes are laid out in the tangent frame of `options.anchor`, with the azimuth | ||
| 155 | /// counter-clockwise from local east. | ||
| 156 | /// @throws std::invalid_argument as `ValidateDuneOptions`. | ||
| 157 | explicit DuneLayer(DuneOptions options, Surface surface = {}); | ||
| 158 | |||
| 159 | /// The dunes' height at world (x, y), without ripples (m). | ||
| 160 | double DuneHeightAt(double x, double y) const; | ||
| 161 | |||
| 162 | /// The options the layer was made with. | ||
| 163 | 1 | const DuneOptions& Options() const { | |
| 164 | 1 | return options_; | |
| 165 | } | ||
| 166 | |||
| 167 | 434 | MaterialClass Material() const override { | |
| 168 | 434 | return MaterialClass::Sand; | |
| 169 | } | ||
| 170 | /// How fully dune sand covers (x, y): the dunes, and with ripples their `interduneCover`. | ||
| 171 | double MaterialCoverAt(double x, double y, double spacing, const Ground* ground) const override; | ||
| 172 | |||
| 173 | protected: | ||
| 174 | float HeightAt(float height, double x, double y, double spacing, | ||
| 175 | const Ground* ground) const override; | ||
| 176 | |||
| 177 | private: | ||
| 178 | /// (x, y) in the plane the dunes are laid out in (m), or false on a body's far hemisphere. | ||
| 179 | bool Planar(double x, double y, Vec2d& planar) const; | ||
| 180 | double Height(const Vec2d& q) const; | ||
| 181 | double Barchans(const Vec2d& q) const; | ||
| 182 | double Crests(const Vec2d& q) const; | ||
| 183 | double Ripples(const Vec2d& q, double dune) const; | ||
| 184 | /// How fully a dune `dune` metres high covers the ground with sand, in [0, 1]. | ||
| 185 | double SandOf(double dune) const; | ||
| 186 | /// How much of crest `crest` stands at `along` metres along it, in [0, 1]: 0 at its breaks. | ||
| 187 | double CrestBreaks(std::int64_t crest, double along) const; | ||
| 188 | /// The phase that forked crests `wavelength` metres apart, `density` pairs of ends per metre of | ||
| 189 | /// crest, add near planar point `q`, in wavelengths; `core` becomes the weight in [0, 1] that | ||
| 190 | /// fades the crests out at the forks' ends. | ||
| 191 | double Forks(const Vec2d& q, double wavelength, double density, double& core) const; | ||
| 192 | /// The dunes' gradient at planar point `q`, by central differences `step` metres apart. | ||
| 193 | Vec2d Gradient(const Vec2d& q, double step) const; | ||
| 194 | /// How far the ground with gradient `g` is a slip face, in [0, 1]: facing downwind near the | ||
| 195 | /// slip face angle. | ||
| 196 | double SlipFaceWeight(const Vec2d& g) const; | ||
| 197 | /// The grainflows' relief at planar point `q` (m), on the slip face share `face`. | ||
| 198 | double Grainflows(const Vec2d& q, double face) const; | ||
| 199 | |||
| 200 | DuneOptions options_; | ||
| 201 | Vec2d wind_{1.0, 0.0}; | ||
| 202 | std::int64_t rings_ = 1; | ||
| 203 | Vec3d anchorDirection_{}; | ||
| 204 | }; | ||
| 205 | |||
| 206 | } // namespace terraingen | ||
| 207 |