layers/craters/secondaries.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/layers/craters/secondaries.h" | ||
| 2 | |||
| 3 | #include "terraingen/layers/fade.h" | ||
| 4 | #include "terraingen/noise/value.h" | ||
| 5 | #include "terraingen/random/philox.h" | ||
| 6 | #include "terraingen/random/samplers.h" | ||
| 7 | #include "terraingen/random/stream.h" | ||
| 8 | #include "terraingen/raster/grid.h" | ||
| 9 | #include "terraingen/util/checks.h" | ||
| 10 | |||
| 11 | #include <algorithm> | ||
| 12 | #include <cmath> | ||
| 13 | #include <limits> | ||
| 14 | #include <numbers> | ||
| 15 | #include <stdexcept> | ||
| 16 | #include <string> | ||
| 17 | #include <unordered_map> | ||
| 18 | #include <utility> | ||
| 19 | #include <vector> | ||
| 20 | |||
| 21 | namespace terraingen { | ||
| 22 | |||
| 23 | namespace { | ||
| 24 | |||
| 25 | constexpr std::uint32_t kSecondaryStream = 0x53454344u; | ||
| 26 | |||
| 27 | /// The primary's ID mixed with a cell's indices into a secondary's ID. | ||
| 28 | 16440 | std::uint64_t SecondaryId(std::uint64_t primary, int band, std::int64_t ring, std::int64_t sector, | |
| 29 | std::uint32_t index) { | ||
| 30 | 16440 | const IdWords id = SplitId(primary); | |
| 31 | 16440 | const U32x4 block = Philox4x32( | |
| 32 | 16440 | {id.low, id.high, | |
| 33 | 16440 | static_cast<std::uint32_t>(band) ^ (static_cast<std::uint32_t>(ring) << 8), index}, | |
| 34 | {static_cast<std::uint32_t>(sector), kSecondaryStream}); | ||
| 35 | 16440 | return (static_cast<std::uint64_t>(block.x) << 32) | block.y; | |
| 36 | } | ||
| 37 | |||
| 38 | /// The smallest and largest distances, and the directions (radians counter-clockwise from east, | ||
| 39 | /// as an interval around `mid` of half-width `half`), from `centre` to `region` on `surface`; | ||
| 40 | /// the whole circle when `region` holds the centre. | ||
| 41 | struct Footprint { | ||
| 42 | double near = 0.0; | ||
| 43 | double far = 0.0; | ||
| 44 | double mid = 0.0; | ||
| 45 | double half = std::numbers::pi; | ||
| 46 | }; | ||
| 47 | |||
| 48 | 1073 | Footprint FootprintOf(const Vec2d& centre, const Bounds2d& region, const Surface& surface) { | |
| 49 | 1073 | Footprint footprint; | |
| 50 | 1073 | std::vector<Vec2d> corners; | |
| 51 | 3219 | for (const double x : {region.minX, region.maxX}) { | |
| 52 | 6438 | for (const double y : {region.minY, region.maxY}) { | |
| 53 | 4292 | corners.push_back(surface.Offset(centre, {x, y})); | |
| 54 | } | ||
| 55 | } | ||
| 56 | 1073 | double minX = INFINITY; | |
| 57 | 1073 | double maxX = -INFINITY; | |
| 58 | 1073 | double minY = INFINITY; | |
| 59 | 1073 | double maxY = -INFINITY; | |
| 60 | 5365 | for (const Vec2d& c : corners) { | |
| 61 | 4292 | minX = std::fmin(minX, c.x); | |
| 62 | 4292 | maxX = std::fmax(maxX, c.x); | |
| 63 | 4292 | minY = std::fmin(minY, c.y); | |
| 64 | 4292 | maxY = std::fmax(maxY, c.y); | |
| 65 | 4292 | footprint.far = std::fmax(footprint.far, std::hypot(c.x, c.y)); | |
| 66 | } | ||
| 67 | 1073 | const double dx = std::fmax(0.0, std::fmax(minX, -maxX)); | |
| 68 | 1073 | const double dy = std::fmax(0.0, std::fmax(minY, -maxY)); | |
| 69 | 1073 | footprint.near = std::hypot(dx, dy); | |
| 70 | 1073 | if (footprint.near == 0.0) { | |
| 71 | 20 | return footprint; | |
| 72 | } | ||
| 73 | 1053 | footprint.mid = std::atan2(0.5 * (minY + maxY), 0.5 * (minX + maxX)); | |
| 74 | 1053 | double half = 0.0; | |
| 75 | 5265 | for (const Vec2d& c : corners) { | |
| 76 | 4212 | half = std::fmax(half, std::fabs(std::remainder(std::atan2(c.y, c.x) - footprint.mid, | |
| 77 | 2.0 * std::numbers::pi))); | ||
| 78 | } | ||
| 79 | 1053 | footprint.half = std::fmin(std::numbers::pi, half); | |
| 80 | 1053 | return footprint; | |
| 81 | 1073 | } | |
| 82 | |||
| 83 | /// The sectors [first, last) of a ring of `sectors` sectors of angle `width` at inner radius `r0` | ||
| 84 | /// that can reach a footprint, in cells of side `cell`; indices may run past a turn. | ||
| 85 | 6742 | std::pair<std::int64_t, std::int64_t> SectorRange(const Footprint& footprint, double cell, | |
| 86 | double r0, double width, std::int64_t sectors) { | ||
| 87 | 6742 | if (footprint.half < std::numbers::pi) { | |
| 88 | 6386 | const double margin = cell / r0 + width; | |
| 89 | 6386 | const double from = footprint.mid - footprint.half - margin; | |
| 90 | 6386 | const double to = footprint.mid + footprint.half + margin; | |
| 91 | 6386 | if (to - from < 2.0 * std::numbers::pi) { | |
| 92 | 12772 | return {static_cast<std::int64_t>(std::floor(from / width)), | |
| 93 | 6386 | static_cast<std::int64_t>(std::ceil(to / width))}; | |
| 94 | } | ||
| 95 | } | ||
| 96 | 356 | return {0, sectors}; | |
| 97 | } | ||
| 98 | |||
| 99 | /// One primary, with what its secondaries depend on. | ||
| 100 | struct Primary { | ||
| 101 | Crater crater; | ||
| 102 | double onset = 0.0; ///< `SecondaryOnsetRange`. | ||
| 103 | double outer = 0.0; ///< The outer range. | ||
| 104 | double density0 = 0.0; ///< Secondaries per m^2 at the onset at least `reference` across. | ||
| 105 | double reference = 0.0; ///< `countFraction` times the diameter. | ||
| 106 | Footprint footprint; ///< The region secondaries are wanted in, seen from the primary. | ||
| 107 | /// Where its secondaries gather; none, without rays, spreads them evenly. | ||
| 108 | std::optional<SecondaryRayField> rays; | ||
| 109 | }; | ||
| 110 | |||
| 111 | /// One ring of a size band of a primary's polar cells. | ||
| 112 | struct Ring { | ||
| 113 | int band = 0; | ||
| 114 | double low = 0.0; ///< Smallest diameter of the band. | ||
| 115 | double bandShare = 0.0; ///< The band's share of the cumulative count at `reference`. | ||
| 116 | std::int64_t index = 0; | ||
| 117 | double r0 = 0.0; | ||
| 118 | double r1 = 0.0; | ||
| 119 | double r = 0.0; ///< Chapman's distance, 1 + (r0 - onset) / D. | ||
| 120 | double width = 0.0; ///< Angle of a sector. | ||
| 121 | double mean = 0.0; ///< Secondaries expected in a sector. | ||
| 122 | double peak = 1.0; ///< `SecondaryRayField::Peak` over the ring; 1 without rays. | ||
| 123 | }; | ||
| 124 | |||
| 125 | } // namespace | ||
| 126 | |||
| 127 | 2156 | double SecondaryStretch(const SecondaryShapes& shapes) { | |
| 128 | 2156 | return shapes.elongatedShare > 0.0 ? std::sqrt(shapes.ellipticity.Highest()) : 1.0; | |
| 129 | } | ||
| 130 | |||
| 131 | namespace { | ||
| 132 | |||
| 133 | /// Draws the secondaries of primaries into one list, keeping count of those expected. | ||
| 134 | class Scatter { | ||
| 135 | public: | ||
| 136 | 1077 | Scatter(const SecondaryCraterLayer& layer, const Surface& surface, const Bounds2d& region, | |
| 137 | const Bounds2d& wanted, double reachRadii, double skipBelow) | ||
| 138 | 1077 | : layer_(layer), options_(layer.Options()), surface_(surface), region_(region), | |
| 139 | 1077 | wanted_(wanted), reachRadii_(reachRadii), skipBelow_(skipBelow), | |
| 140 | 1077 | maxStretch_(SecondaryStretch(layer.Options().shapes)) {} | |
| 141 | |||
| 142 | /// Draws the secondaries `crater` throws, if it is a selected primary. | ||
| 143 | 479837 | void Throws(const Crater& crater) { | |
| 144 | 479837 | if (!options_.selection.Includes(crater)) { | |
| 145 | 478764 | return; | |
| 146 | } | ||
| 147 | 479834 | const double diameter = crater.diameter; | |
| 148 | 479834 | Primary primary; | |
| 149 | 479834 | primary.crater = crater; | |
| 150 | 479834 | primary.onset = SecondaryOnsetRange(diameter); | |
| 151 | 479834 | primary.outer = layer_.OuterRange(diameter); | |
| 152 | 479834 | if (!(primary.outer > primary.onset)) { | |
| 153 | 478761 | return; | |
| 154 | } | ||
| 155 | 1073 | primary.footprint = FootprintOf({crater.x, crater.y}, wanted_, surface_); | |
| 156 | 1073 | primary.density0 = *options_.onsetCount / (0.25 * std::numbers::pi * diameter * diameter); | |
| 157 | 1073 | primary.reference = options_.countFraction * diameter; | |
| 158 | 1073 | primary.rays = SecondaryRayField::Make(crater, options_.rays); | |
| 159 | 1073 | const double biggest = std::fmin( | |
| 160 | 1073 | options_.maxDiameter, std::fmax(SingerMaxSecondaryDiameter(diameter, primary.onset), | |
| 161 | SingerMaxSecondaryDiameter(diameter, primary.outer))); | ||
| 162 | 1073 | const int firstBand = std::ilogb(std::fmax(options_.minDiameter, 1e-9)); | |
| 163 | 1073 | const int lastBand = std::ilogb(biggest); | |
| 164 | 5335 | for (int band = firstBand; band <= lastBand; ++band) { | |
| 165 | 4263 | DrawBand(primary, band); | |
| 166 | } | ||
| 167 | 479834 | } | |
| 168 | |||
| 169 | 1076 | CraterList Take() { | |
| 170 | 1076 | return std::move(secondaries_); | |
| 171 | } | ||
| 172 | |||
| 173 | /// The outlines of the elongated secondaries drawn, by ID. | ||
| 174 | 1076 | std::unordered_map<std::uint64_t, SecondaryOutline> TakeOutlines() { | |
| 175 | 1076 | return std::move(outlines_); | |
| 176 | } | ||
| 177 | |||
| 178 | private: | ||
| 179 | 4263 | void DrawBand(const Primary& primary, int band) { | |
| 180 | 4263 | const double low = std::fmax(options_.minDiameter, std::ldexp(1.0, band)); | |
| 181 | 4263 | const double high = std::fmin(options_.maxDiameter, std::ldexp(1.0, band + 1)); | |
| 182 | 4263 | if (!(high > low) || high <= skipBelow_) { | |
| 183 | 1050 | return; | |
| 184 | } | ||
| 185 | 3219 | const double diameter = primary.crater.diameter; | |
| 186 | 3219 | const double far = std::fmin(primary.outer, SingerRangeForSize(diameter, low)); | |
| 187 | 3219 | const double cell = options_.cellSizeFactor * high; | |
| 188 | 3219 | const double inner = std::fmax(primary.onset, primary.footprint.near - cell); | |
| 189 | 3219 | const double outerEdge = std::fmin(far, primary.footprint.far + cell); | |
| 190 | 3219 | if (!(outerEdge > inner)) { | |
| 191 | 6 | return; | |
| 192 | } | ||
| 193 | 3213 | const auto firstRing = | |
| 194 | 3213 | static_cast<std::int64_t>(std::floor((inner - primary.onset) / cell)); | |
| 195 | 3213 | const auto lastRing = | |
| 196 | 3213 | static_cast<std::int64_t>(std::ceil((outerEdge - primary.onset) / cell)); | |
| 197 | 3213 | const double s = options_.sizeSlope; | |
| 198 | 3213 | Ring ring; | |
| 199 | 3213 | ring.band = band; | |
| 200 | 3213 | ring.low = low; | |
| 201 | 3213 | ring.bandShare = | |
| 202 | 3213 | std::pow(low / primary.reference, -s) - std::pow(high / primary.reference, -s); | |
| 203 | 9954 | for (ring.index = firstRing; ring.index < lastRing; ++ring.index) { | |
| 204 | 6742 | ring.r0 = primary.onset + static_cast<double>(ring.index) * cell; | |
| 205 | 6742 | ring.r1 = std::fmin(far, ring.r0 + cell); | |
| 206 | 6742 | if (!(ring.r1 > ring.r0)) { | |
| 207 | ✗ | continue; | |
| 208 | } | ||
| 209 | 6742 | DrawRing(primary, ring, cell); | |
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | 6742 | void DrawRing(const Primary& primary, Ring& ring, double cell) { | |
| 214 | 6742 | const auto sectors = std::max<std::int64_t>( | |
| 215 | 13484 | 1, static_cast<std::int64_t>( | |
| 216 | 6742 | std::ceil(2.0 * std::numbers::pi * 0.5 * (ring.r0 + ring.r1) / cell))); | |
| 217 | 6742 | ring.width = 2.0 * std::numbers::pi / static_cast<double>(sectors); | |
| 218 | 6742 | const auto [first, last] = | |
| 219 | 6742 | SectorRange(primary.footprint, cell, ring.r0, ring.width, sectors); | |
| 220 | 6742 | ring.r = 1.0 + (ring.r0 - primary.onset) / primary.crater.diameter; | |
| 221 | 6742 | const double area = 0.5 * ring.width * (ring.r1 * ring.r1 - ring.r0 * ring.r0); | |
| 222 | 6742 | ring.mean = primary.density0 / (ring.r * ring.r) * ring.bandShare * area; | |
| 223 | 6742 | if (primary.rays) { | |
| 224 | 6251 | ring.peak = primary.rays->Peak(ring.r0, ring.r1); | |
| 225 | 6251 | ring.mean *= ring.peak; | |
| 226 | } | ||
| 227 | 68186 | for (std::int64_t raw = first; raw < last; ++raw) { | |
| 228 | 61445 | expected_ += ring.mean; | |
| 229 | 61445 | if (expected_ > options_.maxExpected) { | |
| 230 | 1 | throw std::invalid_argument("Expected secondary count exceeds maxExpected"); | |
| 231 | } | ||
| 232 | 61444 | DrawSector(primary, ring, ((raw % sectors) + sectors) % sectors); | |
| 233 | } | ||
| 234 | 6741 | } | |
| 235 | |||
| 236 | 61444 | void DrawSector(const Primary& primary, const Ring& ring, std::int64_t sector) { | |
| 237 | 61444 | const Crater& crater = primary.crater; | |
| 238 | 61444 | const IdWords id = SplitId(crater.id); | |
| 239 | 61444 | RandomStream random(options_.seed, kSecondaryStream, id.low ^ id.high, | |
| 240 | 61444 | static_cast<std::uint32_t>(ring.band) ^ | |
| 241 | 61444 | (static_cast<std::uint32_t>(ring.index) << 8), | |
| 242 | 61444 | static_cast<std::uint32_t>(sector)); | |
| 243 | 61444 | const double s = options_.sizeSlope; | |
| 244 | 61444 | const double reference = primary.reference; | |
| 245 | 61444 | const std::uint64_t count = SamplePoisson(random, ring.mean); | |
| 246 | 3208317 | for (std::uint64_t index = 0; index < count; ++index) { | |
| 247 | 3146873 | const double range = std::sqrt( | |
| 248 | 3146873 | ring.r0 * ring.r0 + random.NextDouble() * (ring.r1 * ring.r1 - ring.r0 * ring.r0)); | |
| 249 | 3146873 | const double azimuth = (static_cast<double>(sector) + random.NextDouble()) * ring.width; | |
| 250 | 3146873 | const double u = random.NextDouble(); | |
| 251 | const double size = | ||
| 252 | reference * | ||
| 253 | 3146873 | std::pow(std::pow(ring.low / reference, -s) - u * ring.bandShare, -1.0 / s); | |
| 254 | 3146873 | const double keep = random.NextDouble(); | |
| 255 | 3146873 | const double rayKeep = primary.rays ? random.NextDouble() : 0.0; | |
| 256 | 3146873 | const SecondaryShapes& shapes = options_.shapes; | |
| 257 | 3146873 | const double e0 = shapes.elongatedShare > 0.0 ? random.NextDouble() : 1.0; | |
| 258 | 3146873 | const double e1 = shapes.elongatedShare > 0.0 ? random.NextDouble() : 0.0; | |
| 259 | 3146873 | const double e2 = shapes.elongatedShare > 0.0 ? random.NextDouble() : 0.0; | |
| 260 | 3146873 | const double here = 1.0 + (range - primary.onset) / crater.diameter; | |
| 261 | 5811804 | if (keep >= (ring.r * ring.r) / (here * here) || | |
| 262 | 2664931 | size >= SingerMaxSecondaryDiameter(crater.diameter, range)) { | |
| 263 | 3130433 | continue; | |
| 264 | } | ||
| 265 | 2652350 | if (primary.rays && rayKeep * ring.peak >= primary.rays->Density(range, azimuth)) { | |
| 266 | 2372684 | continue; | |
| 267 | } | ||
| 268 | ✗ | const Vec2d at = surface_.Locate( | |
| 269 | 279666 | {crater.x, crater.y}, {range * std::cos(azimuth), range * std::sin(azimuth)}); | |
| 270 | 279666 | const double margin = 0.5 * reachRadii_ * maxStretch_ * size; | |
| 271 | 279666 | if (!surface_.Contains(surface_.Expand(region_, margin), at)) { | |
| 272 | 263226 | continue; | |
| 273 | } | ||
| 274 | 16440 | const std::uint64_t secondary = SecondaryId(crater.id, ring.band, ring.index, sector, | |
| 275 | 16440 | static_cast<std::uint32_t>(index)); | |
| 276 | 16440 | secondaries_.Append({at.x, at.y, size, crater.ageGyr, secondary}); | |
| 277 | 16440 | if (e0 < shapes.elongatedShare) { | |
| 278 | const double toPrimary = | ||
| 279 | 7315 | std::clamp((primary.outer - range) / (primary.outer - primary.onset), 0.0, 1.0); | |
| 280 | 7315 | const Vec2d back = surface_.Offset(at, {crater.x, crater.y}); | |
| 281 | 7315 | outlines_[secondary] = {1.0 + (shapes.ellipticity.Sample(e1, e2) - 1.0) * toPrimary, | |
| 282 | 7315 | std::atan2(back.y, back.x)}; | |
| 283 | } | ||
| 284 | } | ||
| 285 | 61444 | } | |
| 286 | |||
| 287 | const SecondaryCraterLayer& layer_; | ||
| 288 | const SecondaryCraterOptions& options_; | ||
| 289 | const Surface& surface_; | ||
| 290 | const Bounds2d& region_; | ||
| 291 | const Bounds2d& wanted_; | ||
| 292 | double reachRadii_; | ||
| 293 | double skipBelow_; | ||
| 294 | CraterList secondaries_; | ||
| 295 | std::unordered_map<std::uint64_t, SecondaryOutline> outlines_; | ||
| 296 | double expected_ = 0.0; | ||
| 297 | double maxStretch_ = 1.0; | ||
| 298 | }; | ||
| 299 | |||
| 300 | } // namespace | ||
| 301 | |||
| 302 | 46 | SecondaryCraterLayer::SecondaryCraterLayer(std::shared_ptr<const CraterLayer> primaries, | |
| 303 | CraterShapeModels models, SecondaryCraterOptions options, | ||
| 304 | 46 | CraterApplyOptions apply) | |
| 305 | 46 | : primaries_(std::move(primaries)), models_(std::move(models)), options_(options), | |
| 306 | 92 | apply_(apply) { | |
| 307 | 46 | if (!primaries_ || !models_.profile || !models_.degradation) { | |
| 308 | 2 | throw std::invalid_argument("Secondary craters need primaries, a profile and degradation"); | |
| 309 | } | ||
| 310 | 44 | ValidateSecondaryCraterOptions(options_); | |
| 311 | 34 | ValidateCraterApplyOptions(apply_); | |
| 312 | 34 | if (apply_.datum != CraterDatum::None) { | |
| 313 | 1 | throw std::invalid_argument("Secondary craters are additive; datums are not supported"); | |
| 314 | } | ||
| 315 | 33 | apply_.surface = primaries_->Options().surface; | |
| 316 | 98 | } | |
| 317 | |||
| 318 | 480921 | double SecondaryCraterLayer::OuterRange(double diameter) const { | |
| 319 | 480921 | return SecondaryOuterRange(diameter, options_); | |
| 320 | } | ||
| 321 | |||
| 322 | 130 | CraterAttributes SecondaryCraterLayer::Attributes(const Crater& crater) const { | |
| 323 | 130 | return ComputeCraterAttributes(crater, models_.degradation->State(crater), *models_.profile); | |
| 324 | } | ||
| 325 | |||
| 326 | 8 | CraterFeatures SecondaryCraterLayer::CratersOver(const RasterGrid& grid, double spacing) const { | |
| 327 | 8 | CraterFeatures placed{ | |
| 328 | 8 | Secondaries(grid.Bounds(), std::max(spacing, grid.Spacing(apply_.surface))), {}}; | |
| 329 | 136 | for (std::size_t n = 0; n < placed.craters.Size(); ++n) { | |
| 330 | 128 | placed.attributes.push_back(Attributes(placed.craters.At(n))); | |
| 331 | } | ||
| 332 | 8 | return placed; | |
| 333 | } | ||
| 334 | |||
| 335 | 1078 | double SecondaryCraterLayer::MaxFeatureDiameter() const { | |
| 336 | 1078 | const double largest = primaries_->MaxFeatureDiameter(); | |
| 337 | 1078 | if (!(largest > 0.0)) { | |
| 338 | ✗ | return 0.0; | |
| 339 | } | ||
| 340 | 1078 | return std::fmin(options_.maxDiameter, | |
| 341 | 1078 | SingerMaxSecondaryDiameter(largest, SecondaryOnsetRange(largest))); | |
| 342 | } | ||
| 343 | |||
| 344 | 29 | CraterList SecondaryCraterLayer::Secondaries(const Bounds2d& region, double spacing) const { | |
| 345 | 29 | std::unordered_map<std::uint64_t, SecondaryOutline> outlines; | |
| 346 | 57 | return Secondaries(region, spacing, outlines); | |
| 347 | 29 | } | |
| 348 | |||
| 349 | 1077 | CraterList SecondaryCraterLayer::Secondaries( | |
| 350 | const Bounds2d& region, double spacing, | ||
| 351 | std::unordered_map<std::uint64_t, SecondaryOutline>& outlines) const { | ||
| 352 | 1077 | const Surface& surface = apply_.surface; | |
| 353 | 1077 | const double reachRadii = CraterReachRadii(*models_.profile, apply_); | |
| 354 | 1077 | const double largest = primaries_->MaxFeatureDiameter(); | |
| 355 | 1077 | const Bounds2d wanted = surface.Expand( | |
| 356 | 1077 | region, 0.5 * reachRadii * SecondaryStretch(options_.shapes) * MaxFeatureDiameter()); | |
| 357 | const CraterList primaries = | ||
| 358 | 1077 | primaries_->Craters(surface.Expand(wanted, OuterRange(largest)), 0.0); | |
| 359 | 1077 | const double skipBelow = spacing > 0.0 ? apply_.fade.startSamples * spacing : 0.0; | |
| 360 | 1077 | Scatter scatter(*this, surface, region, wanted, reachRadii, skipBelow); | |
| 361 | 480913 | for (std::size_t n = 0; n < primaries.Size(); ++n) { | |
| 362 | 479837 | scatter.Throws(primaries.At(n)); | |
| 363 | } | ||
| 364 | 1076 | outlines = scatter.TakeOutlines(); | |
| 365 | 2152 | return scatter.Take(); | |
| 366 | 1078 | } | |
| 367 | |||
| 368 | 1047 | void SecondaryCraterLayer::ApplySamples(SampleBlock& samples, const LayerContext& context) const { | |
| 369 | 1047 | const Raster<float>* grid = samples.Grid(); | |
| 370 | const double spacing = | ||
| 371 | grid != nullptr | ||
| 372 | 1047 | ? std::max(context.spacing, RasterGrid::FromRaster(*grid).Spacing(apply_.surface)) | |
| 373 | 1047 | : context.spacing; | |
| 374 | 1047 | std::unordered_map<std::uint64_t, SecondaryOutline> outlines; | |
| 375 | 1047 | const CraterList secondaries = Secondaries(samples.Bounds(), spacing, outlines); | |
| 376 | 1047 | CraterSampling sampling{spacing, context.threads}; | |
| 377 | 1047 | if (!outlines.empty()) { | |
| 378 | 2 | sampling.reshape = [&outlines](const Crater& crater, CraterOutline& outline) { | |
| 379 | 1480 | const auto found = outlines.find(crater.id); | |
| 380 | 1480 | if (found != outlines.end()) { | |
| 381 | 1183 | outline.ellipticity = found->second.ellipticity; | |
| 382 | 1183 | outline.heading = found->second.heading; | |
| 383 | } | ||
| 384 | 1482 | }; | |
| 385 | } | ||
| 386 | 1047 | ApplyCraters(samples, secondaries, models_, apply_, sampling); | |
| 387 | 1047 | } | |
| 388 | |||
| 389 | } // namespace terraingen | ||
| 390 |