layers/craters/shaping/irregularity.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /// @file | ||
| 2 | /// @brief Irregular, non-circular crater outlines: elongation and rough rims. | ||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "terraingen/layers/craters/crater.h" | ||
| 6 | #include "terraingen/layers/craters/generation/population.h" | ||
| 7 | #include "terraingen/random/value_distribution.h" | ||
| 8 | #include "terraingen/util/surface.h" | ||
| 9 | |||
| 10 | #include <cstdint> | ||
| 11 | #include <optional> | ||
| 12 | #include <vector> | ||
| 13 | |||
| 14 | namespace terraingen { | ||
| 15 | |||
| 16 | /// How rough crater rims are drawn. | ||
| 17 | enum class RimRoughnessModel { | ||
| 18 | /// Harmonics `minHarmonic` to `maxHarmonic` with amplitudes falling as k^-`spectralSlope`, of | ||
| 19 | /// RMS `roughness`: a simple, configurable spectrum. | ||
| 20 | PowerLaw, | ||
| 21 | /// The fitted rim crest spectra of fresh lunar craters of Du et al. (2024), scattered as | ||
| 22 | /// measured when `scatter`, tabulated at 1024 angles; outlines deviating by more than | ||
| 23 | /// `maxDeviation` radii are scaled down to it. | ||
| 24 | Du2024, | ||
| 25 | }; | ||
| 26 | |||
| 27 | /// Which craters have irregular outlines and the distributions their shapes are drawn from, | ||
| 28 | /// per crater from its ID. | ||
| 29 | /// | ||
| 30 | /// The rim lies at R s(theta) and the whole profile is stretched by s; s is an area-preserving | ||
| 31 | /// ellipse for `elongatedShare` of selected craters, times 1 plus rim harmonics for `roughShare`. | ||
| 32 | struct CraterIrregularity { | ||
| 33 | 39 | bool enabled = false; ///< Off: every crater is round. | |
| 34 | 39 | std::uint32_t seed = 0; ///< Varies the outlines. | |
| 35 | /// The craters that can be irregular; manual craters are left round by default. | ||
| 36 | 39 | CraterSelection selection{.manual = false}; | |
| 37 | |||
| 38 | 39 | double roughShare = 1.0; ///< Share of selected craters with rough outlines. | |
| 39 | 39 | RimRoughnessModel model = RimRoughnessModel::PowerLaw; ///< How rough rims are drawn. | |
| 40 | 39 | bool scatter = true; ///< With `Du2024`, scatter each crater's spectrum as measured. | |
| 41 | 39 | double maxDeviation = 0.3; ///< With `Du2024`, the largest deviation kept, in radii. | |
| 42 | /// With `PowerLaw`, the outline's RMS deviation from a circle, in radii. | ||
| 43 | 39 | ValueDistribution roughness{ValueDistribution::Kind::Uniform, 0.01, 0.03}; | |
| 44 | /// With `PowerLaw`, the exponent amplitudes fall with: A_k ~ k^-slope. | ||
| 45 | 39 | ValueDistribution spectralSlope{ValueDistribution::Kind::Constant, 1.5}; | |
| 46 | 39 | int minHarmonic = 2; ///< With `PowerLaw`, the lowest harmonic. | |
| 47 | 39 | int maxHarmonic = 12; ///< With `PowerLaw`, the highest harmonic. | |
| 48 | |||
| 49 | 39 | double elongatedShare = 0.054; ///< Share of selected craters that are elliptical. | |
| 50 | /// Long over short rim-to-rim diameter of elliptical craters; at least 1. | ||
| 51 | 39 | ValueDistribution ellipticity{ValueDistribution::Kind::Uniform, 1.1, 1.5}; | |
| 52 | /// Heading of the long axis, radians counter-clockwise from +x, spread by up to | ||
| 53 | /// `headingSpread` either way; unset for random headings. | ||
| 54 | 39 | std::optional<double> heading; | |
| 55 | 39 | double headingSpread = 0.0; ///< Spread of `heading` either way, in radians. | |
| 56 | |||
| 57 | /// Share of selected craters with radial marks: the wall's profile is read nearer to or | ||
| 58 | /// farther from the centre by a shift that varies smoothly around the crater, leaving ridges | ||
| 59 | /// and grooves down the wall that are strongest where it is steepest. No published | ||
| 60 | /// measurements of such marks are known, so the defaults are set by eye. | ||
| 61 | 39 | double marksShare = 0.0; | |
| 62 | /// Largest shift of the marks, in radii, drawn per crater; each knot is uniform within it. | ||
| 63 | 39 | ValueDistribution marksAmplitude{ValueDistribution::Kind::Uniform, 0.0, 0.005}; | |
| 64 | 39 | int marksKnots = 45; ///< Random shifts around the crater the marks pass through smoothly. | |
| 65 | 39 | double marksInnerRadii = 0.3; ///< Where the marks reach full strength going out, in radii. | |
| 66 | 39 | double marksOuterRadii = 0.9; ///< Where the marks start to fade going out, in radii. | |
| 67 | 39 | double marksFadeRadii = 0.1; ///< Width of the fades either side, in radii. | |
| 68 | |||
| 69 | 39 | bool operator==(const CraterIrregularity&) const = default; | |
| 70 | }; | ||
| 71 | |||
| 72 | /// Checks irregularity settings. | ||
| 73 | /// @throws std::invalid_argument for invalid distributions or selection, shares outside [0, 1], | ||
| 74 | /// harmonics outside 1 <= min <= max <= 64, a roughness that could fold the rim (its highest value | ||
| 75 | /// times sqrt(2 n), n harmonics, at least 0.5), an ellipticity below 1, a non-finite heading or a | ||
| 76 | /// negative or non-finite spread, a `maxDeviation` outside (0, 0.5], fewer than 3 or more than | ||
| 77 | /// 1024 mark knots, a mark window that is not 0 <= inner - fade, inner <= outer and outer + fade | ||
| 78 | /// <= 1 with a positive fade, or a mark amplitude that is negative or large enough to fold the | ||
| 79 | /// wall (at least fade / 3.75). | ||
| 80 | void ValidateCraterIrregularity(const CraterIrregularity& irregularity); | ||
| 81 | |||
| 82 | /// One crater's outline: the factor its rim radius and profile are stretched by in each | ||
| 83 | /// direction. | ||
| 84 | struct CraterOutline { | ||
| 85 | double ellipticity = 1.0; ///< Long over short axis; 1 for none. | ||
| 86 | double heading = 0.0; ///< Long axis, radians counter-clockwise from +x. | ||
| 87 | std::vector<double> amplitudes; ///< A_k from k = `firstHarmonic`. | ||
| 88 | std::vector<double> phases; ///< phi_k, in step with `amplitudes`. | ||
| 89 | int firstHarmonic = 2; ///< The harmonic of `amplitudes[0]`. | ||
| 90 | /// The rim's deviation from a circle at angles 2 pi n / size, interpolated linearly, in place | ||
| 91 | /// of `amplitudes` when not empty. | ||
| 92 | std::vector<double> table; | ||
| 93 | /// Radial shifts of the wall, in radii, at angles 2 pi n / size, passed through by a periodic | ||
| 94 | /// Catmull-Rom spline; empty for no marks. | ||
| 95 | std::vector<double> marks; | ||
| 96 | double marksInnerRadii = 0.3; ///< Where the marks reach full strength, in radii. | ||
| 97 | double marksOuterRadii = 0.9; ///< Where the marks start to fade, in radii. | ||
| 98 | double marksFadeRadii = 0.1; ///< Width of the fades, in radii. | ||
| 99 | |||
| 100 | /// The factor the rim radius is stretched by toward (`dx`, `dy`) metres east and north of the | ||
| 101 | /// centre; 1 for a round crater. | ||
| 102 | double Scale(double dx, double dy) const; | ||
| 103 | |||
| 104 | /// The shift in radii the marks add at `radii` from the centre toward (`dx`, `dy`); 0 outside | ||
| 105 | /// the marks' window, which ends inside the rim. | ||
| 106 | double MarkShift(double dx, double dy, double radii) const; | ||
| 107 | |||
| 108 | /// An upper bound of `Scale` in any direction. | ||
| 109 | double MaxScale() const; | ||
| 110 | |||
| 111 | /// Whether the outline is a circle: no ellipticity, harmonics, table or marks. | ||
| 112 | 13050373 | bool Round() const { | |
| 113 | 13050373 | return ellipticity == 1.0 && amplitudes.empty() && table.empty() && marks.empty(); | |
| 114 | } | ||
| 115 | }; | ||
| 116 | |||
| 117 | /// The outline `irregularity` gives a crater, seeded by its ID; round unless irregularity is | ||
| 118 | /// enabled and the crater selected. | ||
| 119 | CraterOutline OutlineOf(const Crater& crater, const CraterIrregularity& irregularity); | ||
| 120 | |||
| 121 | /// The largest stretch any crater's outline can have, to widen footprints by; 1 when disabled. | ||
| 122 | double MaxOutlineScale(const CraterIrregularity& irregularity); | ||
| 123 | |||
| 124 | /// The distance in metres at which a crater's round profile is read at surface (x, y): the true | ||
| 125 | /// distance over the stretch of its `outline` (`OutlineOf`) in that direction, shifted by its | ||
| 126 | /// marks. | ||
| 127 | double CraterProfileDistance(const Crater& crater, const CraterOutline& outline, double x, double y, | ||
| 128 | const Surface& surface = {}); | ||
| 129 | |||
| 130 | /// `CraterProfileDistance` for a point `offset` metres east and north of the crater's centre in | ||
| 131 | /// its local frame. | ||
| 132 | double CraterProfileDistance(const Crater& crater, const CraterOutline& outline, | ||
| 133 | const Vec2d& offset); | ||
| 134 | |||
| 135 | } // namespace terraingen | ||
| 136 |