veloren_world/sim/
mod.rs

1mod diffusion;
2mod erosion;
3mod location;
4mod map;
5mod util;
6mod way;
7
8// Reexports
9use self::erosion::Compute;
10pub use self::{
11    diffusion::diffusion,
12    location::Location,
13    map::{sample_pos, sample_wpos},
14    util::get_horizon_map,
15    way::{Path, Way},
16};
17pub(crate) use self::{
18    erosion::{
19        Alt, RiverData, RiverKind, do_erosion, fill_sinks, get_lakes, get_multi_drainage,
20        get_multi_rec, get_rivers,
21    },
22    util::{
23        InverseCdf, cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor,
24        uniform_noise, uphill,
25    },
26};
27
28use crate::{
29    CONFIG, IndexRef,
30    all::{Environment, ForestKind, TreeAttr},
31    block::BlockGen,
32    civ::{Place, PointOfInterest},
33    column::ColumnGen,
34    site::Site,
35    util::{
36        CARDINALS, DHashSet, FastNoise, FastNoise2d, LOCALITY, NEIGHBORS, RandomField, Sampler,
37        StructureGen2d, seed_expan,
38    },
39};
40use bincode::{
41    config::legacy,
42    serde::{decode_from_std_read, encode_into_std_write},
43};
44use common::{
45    assets::{AssetExt, BoxedError, FileAsset, load_bincode_legacy},
46    calendar::Calendar,
47    grid::Grid,
48    lottery::Lottery,
49    resources::MapKind,
50    spiral::Spiral2d,
51    spot::Spot,
52    store::{Id, Store},
53    terrain::{
54        BiomeKind, CoordinateConversions, MapSizeLg, TerrainChunk, TerrainChunkSize,
55        map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx,
56    },
57    vol::RectVolSize,
58};
59use common_base::prof_span;
60use common_net::msg::WorldMapMsg;
61use noise::{
62    BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, Perlin, RidgedMulti, SuperSimplex,
63    core::worley::distance_functions,
64};
65use num::{Float, Signed, traits::FloatConst};
66use rand::{Rng, SeedableRng};
67use rand_chacha::ChaChaRng;
68use rayon::prelude::*;
69use serde::{Deserialize, Serialize};
70use std::{
71    borrow::Cow,
72    f32,
73    fs::File,
74    io::{BufReader, BufWriter},
75    ops::{Add, Div, Mul, Neg, Sub},
76    path::PathBuf,
77    sync::Arc,
78};
79use strum::IntoEnumIterator;
80use tracing::{debug, info, warn};
81use vek::*;
82
83/// Default base two logarithm of the world size, in chunks, per dimension.
84///
85/// Currently, our default map dimensions are 2^10 × 2^10 chunks,
86/// mostly for historical reasons.  It is likely that we will increase this
87/// default at some point.
88const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
89    if let Ok(map_size_lg) = MapSizeLg::new(Vec2 { x: 10, y: 10 }) {
90        map_size_lg
91    } else {
92        panic!("Default world chunk size does not satisfy required invariants.");
93    };
94
95/// A structure that holds cached noise values and cumulative distribution
96/// functions for the input that led to those values.  See the definition of
97/// InverseCdf for a description of how to interpret the types of its fields.
98struct GenCdf {
99    humid_base: InverseCdf,
100    temp_base: InverseCdf,
101    chaos: InverseCdf,
102    alt: Box<[Alt]>,
103    basement: Box<[Alt]>,
104    water_alt: Box<[f32]>,
105    dh: Box<[isize]>,
106    /// NOTE: Until we hit 4096 × 4096, this should suffice since integers with
107    /// an absolute value under 2^24 can be exactly represented in an f32.
108    flux: Box<[Compute]>,
109    pure_flux: InverseCdf<Compute>,
110    alt_no_water: InverseCdf,
111    rivers: Box<[RiverData]>,
112}
113
114pub(crate) struct GenCtx {
115    pub turb_x_nz: SuperSimplex,
116    pub turb_y_nz: SuperSimplex,
117    pub chaos_nz: RidgedMulti<Perlin>,
118    pub alt_nz: util::HybridMulti<Perlin>,
119    pub hill_nz: SuperSimplex,
120    pub temp_nz: Fbm<Perlin>,
121    // Humidity noise
122    pub humid_nz: Billow<Perlin>,
123    // Small amounts of noise for simulating rough terrain.
124    pub small_nz: BasicMulti<Perlin>,
125    pub rock_nz: HybridMulti<Perlin>,
126    pub tree_nz: BasicMulti<Perlin>,
127
128    // TODO: unused, remove??? @zesterer
129    pub _cave_0_nz: SuperSimplex,
130    pub _cave_1_nz: SuperSimplex,
131
132    pub structure_gen: StructureGen2d,
133    pub _big_structure_gen: StructureGen2d,
134    pub _region_gen: StructureGen2d,
135
136    pub _fast_turb_x_nz: FastNoise,
137    pub _fast_turb_y_nz: FastNoise,
138
139    pub _town_gen: StructureGen2d,
140    pub river_seed: RandomField,
141    pub rock_strength_nz: Fbm<Perlin>,
142    pub uplift_nz: util::Worley,
143}
144
145#[derive(Clone, Debug, Deserialize, Serialize)]
146#[serde(default)]
147pub struct GenOpts {
148    pub x_lg: u32,
149    pub y_lg: u32,
150    pub scale: f64,
151    pub map_kind: MapKind,
152    pub erosion_quality: f32,
153}
154
155impl Default for GenOpts {
156    fn default() -> Self {
157        Self {
158            x_lg: 10,
159            y_lg: 10,
160            scale: 2.0,
161            map_kind: MapKind::Square,
162            erosion_quality: 1.0,
163        }
164    }
165}
166
167#[derive(Clone, Debug, Deserialize, Serialize)]
168pub enum FileOpts {
169    /// If set, generate the world map and do not try to save to or load from
170    /// file (default).
171    Generate(GenOpts),
172    /// If set, generate the world map and save the world file (path is created
173    /// the same way screenshot paths are).
174    Save(PathBuf, GenOpts),
175    /// Combination of Save and Load.
176    /// Load map if exists or generate the world map and save the
177    /// world file.
178    LoadOrGenerate {
179        name: String,
180        #[serde(default)]
181        opts: GenOpts,
182        #[serde(default)]
183        overwrite: bool,
184    },
185    /// If set, load the world file from this path in legacy format (errors if
186    /// path not found).  This option may be removed at some point, since it
187    /// only applies to maps generated before map saving was merged into
188    /// master.
189    LoadLegacy(PathBuf),
190    /// If set, load the world file from this path (errors if path not found).
191    Load(PathBuf),
192    /// If set, look for  the world file at this asset specifier (errors if
193    /// asset is not found).
194    ///
195    /// NOTE: Could stand to merge this with `Load` and construct an enum that
196    /// can handle either a PathBuf or an asset specifier, at some point.
197    LoadAsset(String),
198}
199
200impl Default for FileOpts {
201    fn default() -> Self { Self::Generate(GenOpts::default()) }
202}
203
204impl FileOpts {
205    fn load_content(&self) -> (Option<ModernMap>, MapSizeLg, GenOpts) {
206        let parsed_world_file = self.try_load_map();
207
208        let mut gen_opts = self.gen_opts().unwrap_or_default();
209
210        let map_size_lg = if let Some(map) = &parsed_world_file {
211            MapSizeLg::new(map.map_size_lg)
212                .expect("World size of loaded map does not satisfy invariants.")
213        } else {
214            self.map_size()
215        };
216
217        // NOTE: Change 1.0 to 4.0 for a 4x
218        // improvement in world detail.  We also use this to automatically adjust
219        // grid_scale (multiplying by 4.0) and multiply mins_per_sec by
220        // 1.0 / (4.0 * 4.0) in ./erosion.rs, in order to get a similar rate of river
221        // formation.
222        //
223        // FIXME: This is a hack!  At some point we will have a more principled way of
224        // dealing with this.
225        if let Some(map) = &parsed_world_file {
226            gen_opts.scale = map.continent_scale_hack;
227        };
228
229        (parsed_world_file, map_size_lg, gen_opts)
230    }
231
232    fn gen_opts(&self) -> Option<GenOpts> {
233        match self {
234            Self::Generate(opts) | Self::Save(_, opts) | Self::LoadOrGenerate { opts, .. } => {
235                Some(opts.clone())
236            },
237            _ => None,
238        }
239    }
240
241    // TODO: this should return Option so that caller can choose fallback
242    fn map_size(&self) -> MapSizeLg {
243        match self {
244            Self::Generate(opts) | Self::Save(_, opts) | Self::LoadOrGenerate { opts, .. } => {
245                MapSizeLg::new(Vec2 {
246                    x: opts.x_lg,
247                    y: opts.y_lg,
248                })
249                .unwrap_or_else(|e| {
250                    warn!("World size does not satisfy invariants: {:?}", e);
251                    DEFAULT_WORLD_CHUNKS_LG
252                })
253            },
254            _ => DEFAULT_WORLD_CHUNKS_LG,
255        }
256    }
257
258    // TODO: This should probably return a Result, so that caller can choose
259    // whether to log error
260    fn try_load_map(&self) -> Option<ModernMap> {
261        let map = match self {
262            Self::LoadLegacy(path) => {
263                let file = match File::open(path) {
264                    Ok(file) => file,
265                    Err(e) => {
266                        warn!(?e, ?path, "Couldn't read path for maps");
267                        return None;
268                    },
269                };
270
271                let mut reader = BufReader::new(file);
272                let map: WorldFileLegacy = match decode_from_std_read(&mut reader, legacy()) {
273                    Ok(map) => map,
274                    Err(e) => {
275                        warn!(
276                            ?e,
277                            "Couldn't parse legacy map.  Maybe you meant to try a regular load?"
278                        );
279                        return None;
280                    },
281                };
282
283                map.into_modern()
284            },
285            Self::Load(path) => {
286                let file = match File::open(path) {
287                    Ok(file) => file,
288                    Err(e) => {
289                        warn!(?e, ?path, "Couldn't read path for maps");
290                        return None;
291                    },
292                };
293
294                let mut reader = BufReader::new(file);
295                let map: WorldFile = match decode_from_std_read(&mut reader, legacy()) {
296                    Ok(map) => map,
297                    Err(e) => {
298                        warn!(
299                            ?e,
300                            "Couldn't parse modern map.  Maybe you meant to try a legacy load?"
301                        );
302                        return None;
303                    },
304                };
305
306                map.into_modern()
307            },
308            Self::LoadAsset(specifier) => match WorldFile::load_owned(specifier) {
309                Ok(map) => map.into_modern(),
310                Err(err) => {
311                    match err.reason().downcast_ref::<std::io::Error>() {
312                        Some(e) => {
313                            warn!(?e, ?specifier, "Couldn't read asset specifier for maps");
314                        },
315                        None => {
316                            warn!(
317                                ?err,
318                                "Couldn't parse modern map.  Maybe you meant to try a legacy load?"
319                            );
320                        },
321                    }
322                    return None;
323                },
324            },
325            Self::LoadOrGenerate {
326                opts, overwrite, ..
327            } => {
328                // `unwrap` is safe here, because LoadOrGenerate has its path
329                // always defined
330                let path = self.map_path().unwrap();
331
332                let file = match File::open(&path) {
333                    Ok(file) => file,
334                    Err(e) => {
335                        warn!(?e, ?path, "Couldn't find needed map. Generating...");
336                        return None;
337                    },
338                };
339
340                let mut reader = BufReader::new(file);
341                let map: WorldFile = match decode_from_std_read(&mut reader, legacy()) {
342                    Ok(map) => map,
343                    Err(e) => {
344                        warn!(
345                            ?e,
346                            "Couldn't parse modern map.  Maybe you meant to try a legacy load?"
347                        );
348                        return None;
349                    },
350                };
351
352                // FIXME:
353                // We check if we need to generate new map by comparing gen opts.
354                // But we also have another generation paramater that currently
355                // passed outside and used for both worldsim and worldgen.
356                //
357                // Ideally, we need to figure out how we want to use seed, i. e.
358                // moving worldgen seed to gen opts and use different sim seed from
359                // server config or grab sim seed from world file.
360                //
361                // NOTE: we intentionally use pattern-matching here to get
362                // options, so that when gen opts get another field, compiler
363                // will force you to update following logic
364                let GenOpts {
365                    x_lg, y_lg, scale, ..
366                } = opts;
367                let map = match map {
368                    WorldFile::Veloren0_7_0(map) => map,
369                    WorldFile::Veloren0_5_0(_) => {
370                        panic!("World file v0.5.0 isn't supported with LoadOrGenerate.")
371                    },
372                };
373
374                if map.continent_scale_hack != *scale || map.map_size_lg != Vec2::new(*x_lg, *y_lg)
375                {
376                    if *overwrite {
377                        warn!(
378                            "{}\n{}",
379                            "Specified options don't correspond to these in loaded map.",
380                            "Map will be regenerated and overwritten."
381                        );
382                    } else {
383                        panic!(
384                            "{}\n{}",
385                            "Specified options don't correspond to these in loaded map.",
386                            "Use 'ovewrite' option, if you wish to regenerate map."
387                        );
388                    }
389
390                    return None;
391                }
392
393                map.into_modern()
394            },
395            Self::Generate { .. } | Self::Save { .. } => return None,
396        };
397
398        match map {
399            Ok(map) => Some(map),
400            Err(e) => {
401                match e {
402                    WorldFileError::WorldSizeInvalid => {
403                        warn!("World size of map is invalid.");
404                    },
405                }
406                None
407            },
408        }
409    }
410
411    fn map_path(&self) -> Option<PathBuf> {
412        // TODO: Work out a nice bincode file extension.
413        match self {
414            Self::Save(path, _) => Some(PathBuf::from(&path)),
415            Self::LoadOrGenerate { name, .. } => {
416                const MAP_DIR: &str = "./maps";
417                let file_name = format!("{}.bin", name);
418                Some(std::path::Path::new(MAP_DIR).join(file_name))
419            },
420            _ => None,
421        }
422    }
423
424    fn save(&self, map: &WorldFile) {
425        let path = if let Some(path) = self.map_path() {
426            path
427        } else {
428            return;
429        };
430
431        // Check if folder exists and create it if it does not
432        let map_dir = path.parent().expect("failed to get map directory");
433        if !map_dir.exists()
434            && let Err(e) = std::fs::create_dir_all(map_dir)
435        {
436            warn!(?e, ?map_dir, "Couldn't create folder for map");
437            return;
438        }
439
440        let file = match File::create(path.clone()) {
441            Ok(file) => file,
442            Err(e) => {
443                warn!(?e, ?path, "Couldn't create file for maps");
444                return;
445            },
446        };
447
448        let mut writer = BufWriter::new(file);
449        if let Err(e) = encode_into_std_write(map, &mut writer, legacy()) {
450            warn!(?e, "Couldn't write map");
451        }
452        if let Ok(p) = std::fs::canonicalize(path) {
453            info!("Map saved at {}", p.to_string_lossy());
454        }
455    }
456}
457
458pub struct WorldOpts {
459    /// Set to false to disable seeding elements during worldgen.
460    pub seed_elements: bool,
461    pub world_file: FileOpts,
462    pub calendar: Option<Calendar>,
463}
464
465impl Default for WorldOpts {
466    fn default() -> Self {
467        Self {
468            seed_elements: true,
469            world_file: Default::default(),
470            calendar: None,
471        }
472    }
473}
474
475/// LEGACY: Remove when people stop caring.
476#[derive(Serialize, Deserialize)]
477#[repr(C)]
478pub struct WorldFileLegacy {
479    /// Saved altitude height map.
480    pub alt: Box<[Alt]>,
481    /// Saved basement height map.
482    pub basement: Box<[Alt]>,
483}
484
485/// Version of the world map intended for use in Veloren 0.5.0.
486#[derive(Serialize, Deserialize)]
487#[repr(C)]
488pub struct WorldMap_0_5_0 {
489    /// Saved altitude height map.
490    pub alt: Box<[Alt]>,
491    /// Saved basement height map.
492    pub basement: Box<[Alt]>,
493}
494
495/// Version of the world map intended for use in Veloren 0.7.0.
496#[derive(Serialize, Deserialize)]
497#[repr(C)]
498pub struct WorldMap_0_7_0 {
499    /// Saved map size.
500    pub map_size_lg: Vec2<u32>,
501    /// Saved continent_scale hack, to try to better approximate the correct
502    /// seed according to varying map size.
503    ///
504    /// TODO: Remove when generating new maps becomes more principled.
505    pub continent_scale_hack: f64,
506    /// Saved altitude height map.
507    pub alt: Box<[Alt]>,
508    /// Saved basement height map.
509    pub basement: Box<[Alt]>,
510}
511
512/// Errors when converting a map to the most recent type (currently,
513/// shared by the various map types, but at some point we might switch to
514/// version-specific errors if it feels worthwhile).
515#[derive(Debug)]
516pub enum WorldFileError {
517    /// Map size was invalid, and it can't be converted to a valid one.
518    WorldSizeInvalid,
519}
520
521/// WORLD MAP.
522///
523/// A way to store certain components between runs of map generation.  Only
524/// intended for development purposes--no attempt is made to detect map
525/// invalidation or make sure that the map is synchronized with updates to
526/// noise-rs, changes to other parameters, etc.
527///
528/// The map is versioned to enable format detection between versions of Veloren,
529/// so that when we update the map format we don't break existing maps (or at
530/// least, we will try hard not to break maps between versions; if we can't
531/// avoid it, we can at least give a reasonable error message).
532///
533/// NOTE: We rely somewhat heavily on the implementation specifics of bincode
534/// to make sure this is backwards compatible.  When adding new variants here,
535/// Be very careful to make sure tha the old variants are preserved in the
536/// correct order and with the correct names and indices, and make sure to keep
537/// the #[repr(u32)]!
538///
539/// All non-legacy versions of world files should (ideally) fit in this format.
540/// Since the format contains a version and is designed to be extensible
541/// backwards-compatibly, the only reason not to use this forever would be if we
542/// decided to move away from BinCode, or store data across multiple files (or
543/// something else weird I guess).
544///
545/// Update this when you add a new map version.
546#[derive(Serialize, Deserialize)]
547#[repr(u32)]
548pub enum WorldFile {
549    Veloren0_5_0(WorldMap_0_5_0) = 0,
550    Veloren0_7_0(WorldMap_0_7_0) = 1,
551}
552
553impl FileAsset for WorldFile {
554    const EXTENSION: &'static str = "bin";
555
556    fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> { load_bincode_legacy(&bytes) }
557}
558
559/// Data for the most recent map type.  Update this when you add a new map
560/// version.
561pub type ModernMap = WorldMap_0_7_0;
562
563/// The default world map.
564///
565/// TODO: Consider using some naming convention to automatically change this
566/// with changing versions, or at least keep it in a constant somewhere that's
567/// easy to change.
568// Generation parameters:
569//
570// gen_opts: (
571//     erosion_quality: 1.0,
572//     map_kind: Circle,
573//     scale: 2.157574498096227,
574//     x_lg: 10,
575//     y_lg: 10,
576// )
577// seed: 3582734543
578//
579// The biome seed can found below
580pub const DEFAULT_WORLD_MAP: &str = "world.map.veloren_0_18_0_0";
581/// This is *not* the seed used to generate the default map, this seed was used
582/// to generate a better set of biomes on it as the original ones were
583/// unsuitable.
584///
585/// See DEFAULT_WORLD_MAP to get the original worldgen parameters.
586pub const DEFAULT_WORLD_SEED: u32 = 130626853;
587
588impl WorldFileLegacy {
589    #[inline]
590    /// Idea: each map type except the latest knows how to transform
591    /// into the the subsequent map version, and each map type including the
592    /// latest exposes an "into_modern()" method that converts this map type
593    /// to the modern map type.  Thus, to migrate a map from an old format to a
594    /// new format, we just need to transform the old format to the
595    /// subsequent map version, and then call .into_modern() on that--this
596    /// should construct a call chain that ultimately ends up with a modern
597    /// version.
598    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
599        // NOTE: At this point, we assume that any remaining legacy maps were 1024 ×
600        // 1024.
601        if self.alt.len() != self.basement.len() || self.alt.len() != 1024 * 1024 {
602            return Err(WorldFileError::WorldSizeInvalid);
603        }
604
605        let map = WorldMap_0_5_0 {
606            alt: self.alt,
607            basement: self.basement,
608        };
609
610        map.into_modern()
611    }
612}
613
614impl WorldMap_0_5_0 {
615    #[inline]
616    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
617        let pow_size = (self.alt.len().trailing_zeros()) / 2;
618        let two_coord_size = 1 << (2 * pow_size);
619        if self.alt.len() != self.basement.len() || self.alt.len() != two_coord_size {
620            return Err(WorldFileError::WorldSizeInvalid);
621        }
622
623        // The recommended continent scale for maps from version 0.5.0 is (in all
624        // existing cases) just 1.0 << (f64::from(pow_size) - 10.0).
625        let continent_scale_hack = (f64::from(pow_size) - 10.0).exp2();
626
627        let map = WorldMap_0_7_0 {
628            map_size_lg: Vec2::new(pow_size, pow_size),
629            continent_scale_hack,
630            alt: self.alt,
631            basement: self.basement,
632        };
633
634        map.into_modern()
635    }
636}
637
638impl WorldMap_0_7_0 {
639    #[inline]
640    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
641        if self.alt.len() != self.basement.len()
642            || self.alt.len() != (1 << (self.map_size_lg.x + self.map_size_lg.y))
643            || self.continent_scale_hack <= 0.0
644        {
645            return Err(WorldFileError::WorldSizeInvalid);
646        }
647
648        Ok(self)
649    }
650}
651
652impl WorldFile {
653    /// Turns map data from the latest version into a versioned WorldFile ready
654    /// for serialization. Whenever a new map is updated, just change the
655    /// variant we construct here to make sure we're using the latest map
656    /// version.
657    pub fn new(map: ModernMap) -> Self { WorldFile::Veloren0_7_0(map) }
658
659    #[inline]
660    /// Turns a WorldFile into the latest version.  Whenever a new map version
661    /// is added, just add it to this match statement.
662    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
663        match self {
664            WorldFile::Veloren0_5_0(map) => map.into_modern(),
665            WorldFile::Veloren0_7_0(map) => map.into_modern(),
666        }
667    }
668}
669
670#[derive(Debug)]
671pub enum WorldSimStage {
672    // TODO: Add more stages
673    Erosion {
674        progress: f64,
675        estimate: Option<std::time::Duration>,
676    },
677}
678
679pub struct WorldSim {
680    pub seed: u32,
681    /// Base 2 logarithm of the map size.
682    map_size_lg: MapSizeLg,
683    /// Maximum height above sea level of any chunk in the map (not including
684    /// post-erosion warping, cliffs, and other things like that).
685    pub max_height: f32,
686    pub(crate) chunks: Vec<SimChunk>,
687    //TODO: remove or use this property
688    pub(crate) _locations: Vec<Location>,
689
690    pub(crate) gen_ctx: GenCtx,
691    pub rng: ChaChaRng,
692
693    pub(crate) calendar: Option<Calendar>,
694}
695
696impl WorldSim {
697    pub fn empty() -> Self {
698        let gen_ctx = GenCtx {
699            turb_x_nz: SuperSimplex::new(0),
700            turb_y_nz: SuperSimplex::new(0),
701            chaos_nz: RidgedMulti::new(0),
702            hill_nz: SuperSimplex::new(0),
703            alt_nz: util::HybridMulti::new(0),
704            temp_nz: Fbm::new(0),
705
706            small_nz: BasicMulti::new(0),
707            rock_nz: HybridMulti::new(0),
708            tree_nz: BasicMulti::new(0),
709            _cave_0_nz: SuperSimplex::new(0),
710            _cave_1_nz: SuperSimplex::new(0),
711
712            structure_gen: StructureGen2d::new(0, 24, 10),
713            _big_structure_gen: StructureGen2d::new(0, 768, 512),
714            _region_gen: StructureGen2d::new(0, 400, 96),
715            humid_nz: Billow::new(0),
716
717            _fast_turb_x_nz: FastNoise::new(0),
718            _fast_turb_y_nz: FastNoise::new(0),
719
720            _town_gen: StructureGen2d::new(0, 2048, 1024),
721            river_seed: RandomField::new(0),
722            rock_strength_nz: Fbm::new(0),
723            uplift_nz: util::Worley::new(0),
724        };
725        Self {
726            seed: 0,
727            map_size_lg: MapSizeLg::new(Vec2::one()).unwrap(),
728            max_height: 0.0,
729            chunks: vec![SimChunk {
730                chaos: 0.0,
731                alt: 0.0,
732                basement: 0.0,
733                water_alt: 0.0,
734                downhill: None,
735                flux: 0.0,
736                temp: 0.0,
737                humidity: 0.0,
738                rockiness: 0.0,
739                tree_density: 0.0,
740                forest_kind: ForestKind::Dead,
741                spawn_rate: 0.0,
742                river: RiverData::default(),
743                surface_veg: 0.0,
744                sites: vec![],
745                place: None,
746                poi: None,
747                path: Default::default(),
748                cliff_height: 0.0,
749                spot: None,
750                contains_waypoint: false,
751            }],
752            _locations: Vec::new(),
753            gen_ctx,
754            rng: rand_chacha::ChaCha20Rng::from_seed([0; 32]),
755            calendar: None,
756        }
757    }
758
759    pub fn generate(
760        seed: u32,
761        opts: WorldOpts,
762        threadpool: &rayon::ThreadPool,
763        stage_report: &dyn Fn(WorldSimStage),
764    ) -> Self {
765        prof_span!("WorldSim::generate");
766        let calendar = opts.calendar; // separate lifetime of elements
767        let world_file = opts.world_file;
768
769        // Parse out the contents of various map formats into the values we need.
770        let (parsed_world_file, map_size_lg, gen_opts) = world_file.load_content();
771        // Currently only used with LoadOrGenerate to know if we need to
772        // overwrite world file
773        let fresh = parsed_world_file.is_none();
774
775        let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
776        let continent_scale = gen_opts.scale
777            * 5_000.0f64
778                .div(32.0)
779                .mul(TerrainChunkSize::RECT_SIZE.x as f64);
780        let rock_lacunarity = 2.0;
781        let uplift_scale = 128.0;
782        let uplift_turb_scale = uplift_scale / 4.0;
783
784        info!("Starting world generation");
785
786        // NOTE: Changing order will significantly change WorldGen, so try not to!
787        let gen_ctx = GenCtx {
788            turb_x_nz: SuperSimplex::new(rng.random()),
789            turb_y_nz: SuperSimplex::new(rng.random()),
790            chaos_nz: RidgedMulti::new(rng.random()).set_octaves(7).set_frequency(
791                RidgedMulti::<Perlin>::DEFAULT_FREQUENCY * (5_000.0 / continent_scale),
792            ),
793            hill_nz: SuperSimplex::new(rng.random()),
794            alt_nz: util::HybridMulti::new(rng.random())
795                .set_octaves(8)
796                .set_frequency(10_000.0 / continent_scale)
797                // persistence = lacunarity^(-(1.0 - fractal increment))
798                .set_lacunarity(util::HybridMulti::<Perlin>::DEFAULT_LACUNARITY)
799                .set_persistence(util::HybridMulti::<Perlin>::DEFAULT_LACUNARITY.powi(-1))
800                .set_offset(0.0),
801            temp_nz: Fbm::new(rng.random())
802                .set_octaves(6)
803                .set_persistence(0.5)
804                .set_frequency(1.0 / (((1 << 6) * 64) as f64))
805                .set_lacunarity(2.0),
806
807            small_nz: BasicMulti::new(rng.random()).set_octaves(2),
808            rock_nz: HybridMulti::new(rng.random()).set_persistence(0.3),
809            tree_nz: BasicMulti::new(rng.random())
810                .set_octaves(12)
811                .set_persistence(0.75),
812            _cave_0_nz: SuperSimplex::new(rng.random()),
813            _cave_1_nz: SuperSimplex::new(rng.random()),
814
815            structure_gen: StructureGen2d::new(rng.random(), 24, 10),
816            _big_structure_gen: StructureGen2d::new(rng.random(), 768, 512),
817            _region_gen: StructureGen2d::new(rng.random(), 400, 96),
818            humid_nz: Billow::new(rng.random())
819                .set_octaves(9)
820                .set_persistence(0.4)
821                .set_frequency(0.2),
822
823            _fast_turb_x_nz: FastNoise::new(rng.random()),
824            _fast_turb_y_nz: FastNoise::new(rng.random()),
825
826            _town_gen: StructureGen2d::new(rng.random(), 2048, 1024),
827            river_seed: RandomField::new(rng.random()),
828            rock_strength_nz: Fbm::new(rng.random())
829                .set_octaves(10)
830                .set_lacunarity(rock_lacunarity)
831                // persistence = lacunarity^(-(1.0 - fractal increment))
832                // NOTE: In paper, fractal increment is roughly 0.25.
833                .set_persistence(rock_lacunarity.powf(-0.75))
834                .set_frequency(
835                    1.0 * (5_000.0 / continent_scale)
836                        / (2.0 * TerrainChunkSize::RECT_SIZE.x as f64 * 2.0.powi(10 - 1)),
837                ),
838            uplift_nz: util::Worley::new(rng.random())
839                .set_frequency(1.0 / (TerrainChunkSize::RECT_SIZE.x as f64 * uplift_scale))
840                .set_distance_function(distance_functions::euclidean),
841        };
842
843        let river_seed = &gen_ctx.river_seed;
844        let rock_strength_nz = &gen_ctx.rock_strength_nz;
845
846        // Suppose the old world has grid spacing Δx' = Δy', new Δx = Δy.
847        // We define grid_scale such that Δx = height_scale * Δx' ⇒
848        //  grid_scale = Δx / Δx'.
849        let grid_scale = 1.0f64 / (4.0 / gen_opts.scale)/*1.0*/;
850
851        // Now, suppose we want to generate a world with "similar" topography, defined
852        // in this case as having roughly equal slopes at steady state, with the
853        // simulation taking roughly as many steps to get to the point the
854        // previous world was at when it finished being simulated.
855        //
856        // Some computations with our coupled SPL/debris flow give us (for slope S
857        // constant) the following suggested scaling parameters to make this
858        // work:   k_fs_scale ≡ (K𝑓 / K𝑓') = grid_scale^(-2m) =
859        // grid_scale^(-2θn)
860        let k_fs_scale = |theta, n| grid_scale.powf(-2.0 * (theta * n) as f64);
861
862        //   k_da_scale ≡ (K_da / K_da') = grid_scale^(-2q)
863        let k_da_scale = |q| grid_scale.powf(-2.0 * q);
864        //
865        // Some other estimated parameters are harder to come by and *much* more
866        // dubious, not being accurate for the coupled equation. But for the SPL
867        // only one we roughly find, for h the height at steady state and time τ
868        // = time to steady state, with Hack's Law estimated b = 2.0 and various other
869        // simplifying assumptions, the estimate:
870        //   height_scale ≡ (h / h') = grid_scale^(n)
871        let height_scale = |n: f32| grid_scale.powf(n as f64) as Alt;
872        //   time_scale ≡ (τ / τ') = grid_scale^(n)
873        let time_scale = |n: f32| grid_scale.powf(n as f64);
874        //
875        // Based on this estimate, we have:
876        //   delta_t_scale ≡ (Δt / Δt') = time_scale
877        let delta_t_scale = time_scale;
878        //   alpha_scale ≡ (α / α') = height_scale^(-1)
879        let alpha_scale = |n: f32| height_scale(n).recip() as f32;
880        //
881        // Slightly more dubiously (need to work out the math better) we find:
882        //   k_d_scale ≡ (K_d / K_d') = grid_scale^2 / (/*height_scale * */ time_scale)
883        let k_d_scale = |n: f32| grid_scale.powi(2) / (/* height_scale(n) * */time_scale(n));
884        //   epsilon_0_scale ≡ (ε₀ / ε₀') = height_scale(n) / time_scale(n)
885        let epsilon_0_scale = |n| (height_scale(n) / time_scale(n) as Alt) as f32;
886
887        // Approximate n for purposes of computation of parameters above over the whole
888        // grid (when a chunk isn't available).
889        let n_approx = 1.0;
890        let max_erosion_per_delta_t = 64.0 * delta_t_scale(n_approx);
891        let n_steps = (100.0 * gen_opts.erosion_quality) as usize;
892        let n_small_steps = 0;
893        let n_post_load_steps = 0;
894
895        // Logistic regression.  Make sure x ∈ (0, 1).
896        let logit = |x: f64| x.ln() - (-x).ln_1p();
897        // 0.5 + 0.5 * tanh(ln(1 / (1 - 0.1) - 1) / (2 * (sqrt(3)/pi)))
898        let logistic_2_base = 3.0f64.sqrt() * std::f64::consts::FRAC_2_PI;
899        // Assumes μ = 0, σ = 1
900        let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
901
902        let map_size_chunks_len_f64 = map_size_lg.chunks().map(f64::from).product();
903        let min_epsilon = 1.0 / map_size_chunks_len_f64.max(f64::EPSILON * 0.5);
904        let max_epsilon = (1.0 - 1.0 / map_size_chunks_len_f64).min(1.0 - f64::EPSILON * 0.5);
905
906        // No NaNs in these uniform vectors, since the original noise value always
907        // returns Some.
908        let ((alt_base, _), (chaos, _)) = threadpool.join(
909            || {
910                uniform_noise(map_size_lg, |_, wposf| {
911                    match gen_opts.map_kind {
912                        MapKind::Square => {
913                            // "Base" of the chunk, to be multiplied by CONFIG.mountain_scale
914                            // (multiplied value is from -0.35 *
915                            // (CONFIG.mountain_scale * 1.05) to
916                            // 0.35 * (CONFIG.mountain_scale * 0.95), but value here is from -0.3675
917                            // to 0.3325).
918                            Some(
919                                (gen_ctx
920                                    .alt_nz
921                                    .get((wposf.div(10_000.0)).into_array())
922                                    .clamp(-1.0, 1.0))
923                                .sub(0.05)
924                                .mul(0.35),
925                            )
926                        },
927                        MapKind::Circle => {
928                            let world_sizef = map_size_lg.chunks().map(|e| e as f64)
929                                * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
930                            Some(
931                                (gen_ctx
932                                    .alt_nz
933                                    .get((wposf.div(5_000.0 * gen_opts.scale)).into_array())
934                                    .clamp(-1.0, 1.0))
935                                .add(
936                                    0.2 - ((wposf / world_sizef) * 2.0 - 1.0)
937                                        .magnitude_squared()
938                                        .powf(0.75)
939                                        .clamped(0.0, 1.0)
940                                        .powf(1.0)
941                                        * 0.6,
942                                )
943                                .mul(0.5),
944                            )
945                        },
946                    }
947                })
948            },
949            || {
950                uniform_noise(map_size_lg, |_, wposf| {
951                    // From 0 to 1.6, but the distribution before the max is from -1 and 1.6, so
952                    // there is a 50% chance that hill will end up at 0.3 or
953                    // lower, and probably a very high change it will be exactly
954                    // 0.
955                    let hill = (0.0f64
956                        + gen_ctx
957                            .hill_nz
958                            .get(
959                                (wposf
960                                    .mul(32.0)
961                                    .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
962                                    .div(1_500.0))
963                                .into_array(),
964                            )
965                            .clamp(-1.0, 1.0)
966                            .mul(1.0)
967                        + gen_ctx
968                            .hill_nz
969                            .get(
970                                (wposf
971                                    .mul(32.0)
972                                    .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
973                                    .div(400.0))
974                                .into_array(),
975                            )
976                            .clamp(-1.0, 1.0)
977                            .mul(0.3))
978                    .add(0.3)
979                    .max(0.0);
980
981                    // chaos produces a value in [0.12, 1.32].  It is a meta-level factor intended
982                    // to reflect how "chaotic" the region is--how much weird
983                    // stuff is going on on this terrain.
984                    Some(
985                        ((gen_ctx
986                            .chaos_nz
987                            .get((wposf.div(3_000.0)).into_array())
988                            .clamp(-1.0, 1.0))
989                        .add(1.0)
990                        .mul(0.5)
991                        // [0, 1] * [0.4, 1] = [0, 1] (but probably towards the lower end)
992                        .mul(
993                            (gen_ctx
994                                .chaos_nz
995                                .get((wposf.div(6_000.0)).into_array())
996                                .clamp(-1.0, 1.0))
997                            .abs()
998                                .clamp(0.4, 1.0),
999                        )
1000                        // Chaos is always increased by a little when we're on a hill (but remember
1001                        // that hill is 0.3 or less about 50% of the time).
1002                        // [0, 1] + 0.2 * [0, 1.6] = [0, 1.32]
1003                        .add(0.2 * hill)
1004                        // We can't have *no* chaos!
1005                        .max(0.12)) as f32,
1006                    )
1007                })
1008            },
1009        );
1010
1011        // We ignore sea level because we actually want to be relative to sea level here
1012        // and want things in CONFIG.mountain_scale units, but otherwise this is
1013        // a correct altitude calculation.  Note that this is using the
1014        // "unadjusted" temperature.
1015        //
1016        // No NaNs in these uniform vectors, since the original noise value always
1017        // returns Some.
1018        let (alt_old, _) = uniform_noise(map_size_lg, |posi, wposf| {
1019            // This is the extension upwards from the base added to some extra noise from -1
1020            // to 1.
1021            //
1022            // The extra noise is multiplied by alt_main (the mountain part of the
1023            // extension) powered to 0.8 and clamped to [0.15, 1], to get a
1024            // value between [-1, 1] again.
1025            //
1026            // The sides then receive the sequence (y * 0.3 + 1.0) * 0.4, so we have
1027            // [-1*1*(1*0.3+1)*0.4, 1*(1*0.3+1)*0.4] = [-0.52, 0.52].
1028            //
1029            // Adding this to alt_main thus yields a value between -0.4 (if alt_main = 0 and
1030            // gen_ctx = -1, 0+-1*(0*.3+1)*0.4) and 1.52 (if alt_main = 1 and gen_ctx = 1).
1031            // Most of the points are above 0.
1032            //
1033            // Next, we add again by a sin of alt_main (between [-1, 1])^pow, getting
1034            // us (after adjusting for sign) another value between [-1, 1], and then this is
1035            // multiplied by 0.045 to get [-0.045, 0.045], which is added to [-0.4, 0.52] to
1036            // get [-0.445, 0.565].
1037            let alt_main = {
1038                // Extension upwards from the base.  A positive number from 0 to 1 curved to be
1039                // maximal at 0.  Also to be multiplied by CONFIG.mountain_scale.
1040                let alt_main = (gen_ctx
1041                    .alt_nz
1042                    .get((wposf.div(2_000.0)).into_array())
1043                    .clamp(-1.0, 1.0))
1044                .abs()
1045                .powf(1.35);
1046
1047                fn spring(x: f64, pow: f64) -> f64 { x.abs().powf(pow) * x.signum() }
1048
1049                0.0 + alt_main
1050                    + (gen_ctx
1051                        .small_nz
1052                        .get(
1053                            (wposf
1054                                .mul(32.0)
1055                                .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1056                                .div(300.0))
1057                            .into_array(),
1058                        )
1059                        .clamp(-1.0, 1.0))
1060                    .mul(alt_main.powf(0.8).max(/* 0.25 */ 0.15))
1061                    .mul(0.3)
1062                    .add(1.0)
1063                    .mul(0.4)
1064                    + spring(alt_main.abs().sqrt().min(0.75).mul(60.0).sin(), 4.0).mul(0.045)
1065            };
1066
1067            // Now we can compute the final altitude using chaos.
1068            // We multiply by chaos clamped to [0.1, 1.32] to get a value between [0.03,
1069            // 2.232] for alt_pre, then multiply by CONFIG.mountain_scale and
1070            // add to the base and sea level to get an adjusted value, then
1071            // multiply the whole thing by map_edge_factor (TODO: compute final
1072            // bounds).
1073            //
1074            // [-.3675, .3325] + [-0.445, 0.565] * [0.12, 1.32]^1.2
1075            // ~ [-.3675, .3325] + [-0.445, 0.565] * [0.07, 1.40]
1076            // = [-.3675, .3325] + ([-0.5785, 0.7345])
1077            // = [-0.946, 1.067]
1078            Some(
1079                ((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)))
1080                    .mul(map_edge_factor(map_size_lg, posi) as f64)
1081                    .add(
1082                        (CONFIG.sea_level as f64)
1083                            .div(CONFIG.mountain_scale as f64)
1084                            .mul(map_edge_factor(map_size_lg, posi) as f64),
1085                    )
1086                    .sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
1087                    as f32,
1088            )
1089        });
1090
1091        // Calculate oceans.
1092        let is_ocean = get_oceans(map_size_lg, |posi: usize| alt_old[posi].1);
1093        // NOTE: Uncomment if you want oceans to exclusively be on the border of the
1094        // map.
1095        /* let is_ocean = (0..map_size_lg.chunks())
1096        .into_par_iter()
1097        .map(|i| map_edge_factor(map_size_lg, i) == 0.0)
1098        .collect::<Vec<_>>(); */
1099        let is_ocean_fn = |posi: usize| is_ocean[posi];
1100
1101        let turb_wposf_div = 8.0;
1102        let n_func = |posi| {
1103            if is_ocean_fn(posi) {
1104                return 1.0;
1105            }
1106            1.0
1107        };
1108        let old_height = |posi: usize| {
1109            alt_old[posi].1 * CONFIG.mountain_scale * height_scale(n_func(posi)) as f32
1110        };
1111
1112        // NOTE: Needed if you wish to use the distance to the point defining the Worley
1113        // cell, not just the value within that cell.
1114        // let uplift_nz_dist = gen_ctx.uplift_nz.clone().enable_range(true);
1115
1116        // Recalculate altitudes without oceans.
1117        // NaNs in these uniform vectors wherever is_ocean_fn returns true.
1118        let (alt_old_no_ocean, _) = uniform_noise(map_size_lg, |posi, _| {
1119            if is_ocean_fn(posi) {
1120                None
1121            } else {
1122                Some(old_height(posi))
1123            }
1124        });
1125        let (uplift_uniform, _) = uniform_noise(map_size_lg, |posi, _wposf| {
1126            if is_ocean_fn(posi) {
1127                None
1128            } else {
1129                let oheight = alt_old_no_ocean[posi].0 as f64 - 0.5;
1130                let height = (oheight + 0.5).powi(2);
1131                Some(height)
1132            }
1133        });
1134
1135        let alt_old_min_uniform = 0.0;
1136        let alt_old_max_uniform = 1.0;
1137
1138        let inv_func = |x: f64| x;
1139        let alt_exp_min_uniform = inv_func(min_epsilon);
1140        let alt_exp_max_uniform = inv_func(max_epsilon);
1141
1142        let erosion_factor = |x: f64| {
1143            (inv_func(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform)
1144        };
1145        let rock_strength_div_factor = (2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0;
1146        let theta_func = |_posi| 0.4;
1147        let kf_func = {
1148            |posi| {
1149                let kf_scale_i = k_fs_scale(theta_func(posi), n_func(posi));
1150                if is_ocean_fn(posi) {
1151                    return 1.0e-4 * kf_scale_i;
1152                }
1153
1154                let kf_i = // kf = 1.5e-4: high-high (plateau [fan sediment])
1155                // kf = 1e-4: high (plateau)
1156                // kf = 2e-5: normal (dike [unexposed])
1157                // kf = 1e-6: normal-low (dike [exposed])
1158                // kf = 2e-6: low (mountain)
1159                // --
1160                // kf = 2.5e-7 to 8e-7: very low (Cordonnier papers on plate tectonics)
1161                // ((1.0 - uheight) * (1.5e-4 - 2.0e-6) + 2.0e-6) as f32
1162                //
1163                // ACTUAL recorded values worldwide: much lower...
1164                1.0e-6
1165                ;
1166                kf_i * kf_scale_i
1167            }
1168        };
1169        let kd_func = {
1170            |posi| {
1171                let n = n_func(posi);
1172                let kd_scale_i = k_d_scale(n);
1173                if is_ocean_fn(posi) {
1174                    let kd_i = 1.0e-2 / 4.0;
1175                    return kd_i * kd_scale_i;
1176                }
1177                // kd = 1e-1: high (mountain, dike)
1178                // kd = 1.5e-2: normal-high (plateau [fan sediment])
1179                // kd = 1e-2: normal (plateau)
1180                let kd_i = 1.0e-2 / 4.0;
1181                kd_i * kd_scale_i
1182            }
1183        };
1184        let g_func = |posi| {
1185            if map_edge_factor(map_size_lg, posi) == 0.0 {
1186                return 0.0;
1187            }
1188            // G = d* v_s / p_0, where
1189            //  v_s is the settling velocity of sediment grains
1190            //  p_0 is the mean precipitation rate
1191            //  d* is the sediment concentration ratio (between concentration near riverbed
1192            //  interface, and average concentration over the water column).
1193            //  d* varies with Rouse number which defines relative contribution of bed,
1194            // suspended,  and washed loads.
1195            //
1196            // G is typically on the order of 1 or greater.  However, we are only guaranteed
1197            // to converge for G ≤ 1, so we keep it in the chaos range of [0.12,
1198            // 1.32].
1199            1.0
1200        };
1201        let epsilon_0_func = |posi| {
1202            // epsilon_0_scale is roughly [using Hack's Law with b = 2 and SPL without
1203            // debris flow or hillslopes] equal to the ratio of the old to new
1204            // area, to the power of -n_i.
1205            let epsilon_0_scale_i = epsilon_0_scale(n_func(posi));
1206            if is_ocean_fn(posi) {
1207                // marine: ε₀ = 2.078e-3
1208                let epsilon_0_i = 2.078e-3 / 4.0;
1209                return epsilon_0_i * epsilon_0_scale_i;
1210            }
1211            let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
1212                * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
1213            .map(|e| e as f64);
1214            let turb_wposf = wposf
1215                .mul(5_000.0 / continent_scale)
1216                .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1217                .div(turb_wposf_div);
1218            let turb = Vec2::new(
1219                gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
1220                gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
1221            ) * uplift_turb_scale
1222                * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
1223            let turb_wposf = wposf + turb;
1224            let uheight = gen_ctx
1225                .uplift_nz
1226                .get(turb_wposf.into_array())
1227                .clamp(-1.0, 1.0)
1228                .mul(0.5)
1229                .add(0.5);
1230            let wposf3 = Vec3::new(
1231                wposf.x,
1232                wposf.y,
1233                uheight * CONFIG.mountain_scale as f64 * rock_strength_div_factor,
1234            );
1235            let rock_strength = gen_ctx
1236                .rock_strength_nz
1237                .get(wposf3.into_array())
1238                .clamp(-1.0, 1.0)
1239                .mul(0.5)
1240                .add(0.5);
1241            let center = 0.4;
1242            let dmin = center - 0.05;
1243            let dmax = center + 0.05;
1244            let log_odds = |x: f64| logit(x) - logit(center);
1245            let ustrength = logistic_cdf(
1246                1.0 * logit(rock_strength.clamp(1e-7, 1.0f64 - 1e-7))
1247                    + 1.0 * log_odds(uheight.clamp(dmin, dmax)),
1248            );
1249            // marine: ε₀ = 2.078e-3
1250            // San Gabriel Mountains: ε₀ = 3.18e-4
1251            // Oregon Coast Range: ε₀ = 2.68e-4
1252            // Frogs Hollow (peak production = 0.25): ε₀ = 1.41e-4
1253            // Point Reyes: ε₀ = 8.1e-5
1254            // Nunnock River (fractured granite, least weathered?): ε₀ = 5.3e-5
1255            let epsilon_0_i = ((1.0 - ustrength) * (2.078e-3 - 5.3e-5) + 5.3e-5) as f32 / 4.0;
1256            epsilon_0_i * epsilon_0_scale_i
1257        };
1258        let alpha_func = |posi| {
1259            let alpha_scale_i = alpha_scale(n_func(posi));
1260            if is_ocean_fn(posi) {
1261                // marine: α = 3.7e-2
1262                return 3.7e-2 * alpha_scale_i;
1263            }
1264            let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
1265                * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
1266            .map(|e| e as f64);
1267            let turb_wposf = wposf
1268                .mul(5_000.0 / continent_scale)
1269                .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1270                .div(turb_wposf_div);
1271            let turb = Vec2::new(
1272                gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
1273                gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
1274            ) * uplift_turb_scale
1275                * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
1276            let turb_wposf = wposf + turb;
1277            let uheight = gen_ctx
1278                .uplift_nz
1279                .get(turb_wposf.into_array())
1280                .clamp(-1.0, 1.0)
1281                .mul(0.5)
1282                .add(0.5);
1283            let wposf3 = Vec3::new(
1284                wposf.x,
1285                wposf.y,
1286                uheight * CONFIG.mountain_scale as f64 * rock_strength_div_factor,
1287            );
1288            let rock_strength = gen_ctx
1289                .rock_strength_nz
1290                .get(wposf3.into_array())
1291                .clamp(-1.0, 1.0)
1292                .mul(0.5)
1293                .add(0.5);
1294            let center = 0.4;
1295            let dmin = center - 0.05;
1296            let dmax = center + 0.05;
1297            let log_odds = |x: f64| logit(x) - logit(center);
1298            let ustrength = logistic_cdf(
1299                1.0 * logit(rock_strength.clamp(1e-7, 1.0f64 - 1e-7))
1300                    + 1.0 * log_odds(uheight.clamp(dmin, dmax)),
1301            );
1302            // Frog Hollow (peak production = 0.25): α = 4.2e-2
1303            // San Gabriel Mountains: α = 3.8e-2
1304            // marine: α = 3.7e-2
1305            // Oregon Coast Range: α = 3e-2
1306            // Nunnock river (fractured granite, least weathered?): α = 2e-3
1307            // Point Reyes: α = 1.6e-2
1308            // The stronger  the rock, the faster the decline in soil production.
1309            let alpha_i = (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32;
1310            alpha_i * alpha_scale_i
1311        };
1312        let uplift_fn = |posi| {
1313            if is_ocean_fn(posi) {
1314                return 0.0;
1315            }
1316            let height = (uplift_uniform[posi].1 - alt_old_min_uniform)
1317                / (alt_old_max_uniform - alt_old_min_uniform);
1318
1319            let height = height.mul(max_epsilon - min_epsilon).add(min_epsilon);
1320            let height = erosion_factor(height);
1321            assert!(height >= 0.0);
1322            assert!(height <= 1.0);
1323
1324            // u = 1e-3: normal-high (dike, mountain)
1325            // u = 5e-4: normal (mid example in Yuan, average mountain uplift)
1326            // u = 2e-4: low (low example in Yuan; known that lagoons etc. may have u ~
1327            // 0.05). u = 0: low (plateau [fan, altitude = 0.0])
1328
1329            height.mul(max_erosion_per_delta_t)
1330        };
1331        let alt_func = |posi| {
1332            if is_ocean_fn(posi) {
1333                old_height(posi)
1334            } else {
1335                (old_height(posi) as f64 / CONFIG.mountain_scale as f64) as f32 - 0.5
1336            }
1337        };
1338
1339        // Perform some erosion.
1340
1341        let mut last = None;
1342        let mut all_samples = std::time::Duration::default();
1343        let mut sample_count = 0;
1344        let report_erosion: &mut dyn FnMut(f64) = &mut move |progress: f64| {
1345            let now = std::time::Instant::now();
1346            let estimate = if let Some((last_instant, last_progress)) = last {
1347                if last_progress > progress {
1348                    None
1349                } else {
1350                    if last_progress < progress {
1351                        let sample = now
1352                            .duration_since(last_instant)
1353                            .div_f64(progress - last_progress);
1354                        all_samples += sample;
1355                        sample_count += 1;
1356                    }
1357
1358                    Some((all_samples / sample_count).mul_f64(100.0 - progress))
1359                }
1360            } else {
1361                None
1362            };
1363            last = Some((now, progress));
1364            stage_report(WorldSimStage::Erosion { progress, estimate })
1365        };
1366
1367        let (alt, basement) = if let Some(map) = parsed_world_file {
1368            (map.alt, map.basement)
1369        } else {
1370            let (alt, basement) = do_erosion(
1371                map_size_lg,
1372                max_erosion_per_delta_t as f32,
1373                n_steps,
1374                river_seed,
1375                // varying conditions
1376                &rock_strength_nz,
1377                // initial conditions
1378                alt_func,
1379                alt_func,
1380                is_ocean_fn,
1381                // empirical constants
1382                uplift_fn,
1383                n_func,
1384                theta_func,
1385                kf_func,
1386                kd_func,
1387                g_func,
1388                epsilon_0_func,
1389                alpha_func,
1390                // scaling factors
1391                height_scale,
1392                k_d_scale(n_approx),
1393                k_da_scale,
1394                threadpool,
1395                report_erosion,
1396            );
1397
1398            // Quick "small scale" erosion cycle in order to lower extreme angles.
1399            do_erosion(
1400                map_size_lg,
1401                1.0f32,
1402                n_small_steps,
1403                river_seed,
1404                &rock_strength_nz,
1405                |posi| alt[posi] as f32,
1406                |posi| basement[posi] as f32,
1407                is_ocean_fn,
1408                |posi| uplift_fn(posi) * (1.0 / max_erosion_per_delta_t),
1409                n_func,
1410                theta_func,
1411                kf_func,
1412                kd_func,
1413                g_func,
1414                epsilon_0_func,
1415                alpha_func,
1416                height_scale,
1417                k_d_scale(n_approx),
1418                k_da_scale,
1419                threadpool,
1420                report_erosion,
1421            )
1422        };
1423
1424        // Save map, if necessary.
1425        // NOTE: We wll always save a map with latest version.
1426        let map = WorldFile::new(ModernMap {
1427            continent_scale_hack: gen_opts.scale,
1428            map_size_lg: map_size_lg.vec(),
1429            alt,
1430            basement,
1431        });
1432        if fresh {
1433            world_file.save(&map);
1434        }
1435
1436        // Skip validation--we just performed a no-op conversion for this map, so it had
1437        // better be valid!
1438        let ModernMap {
1439            continent_scale_hack: _,
1440            map_size_lg: _,
1441            alt,
1442            basement,
1443        } = map.into_modern().unwrap();
1444
1445        // Additional small-scale erosion after map load, only used during testing.
1446        let (alt, basement) = if n_post_load_steps == 0 {
1447            (alt, basement)
1448        } else {
1449            do_erosion(
1450                map_size_lg,
1451                1.0f32,
1452                n_post_load_steps,
1453                river_seed,
1454                &rock_strength_nz,
1455                |posi| alt[posi] as f32,
1456                |posi| basement[posi] as f32,
1457                is_ocean_fn,
1458                |posi| uplift_fn(posi) * (1.0 / max_erosion_per_delta_t),
1459                n_func,
1460                theta_func,
1461                kf_func,
1462                kd_func,
1463                g_func,
1464                epsilon_0_func,
1465                alpha_func,
1466                height_scale,
1467                k_d_scale(n_approx),
1468                k_da_scale,
1469                threadpool,
1470                report_erosion,
1471            )
1472        };
1473
1474        let is_ocean = get_oceans(map_size_lg, |posi| alt[posi]);
1475        let is_ocean_fn = |posi: usize| is_ocean[posi];
1476        let mut dh = downhill(map_size_lg, |posi| alt[posi], is_ocean_fn);
1477        let (boundary_len, indirection, water_alt_pos, maxh) =
1478            get_lakes(map_size_lg, |posi| alt[posi], &mut dh);
1479        debug!(?maxh, "Max height");
1480        let (mrec, mstack, mwrec) = {
1481            let mut wh = vec![0.0; map_size_lg.chunks_len()];
1482            get_multi_rec(
1483                map_size_lg,
1484                |posi| alt[posi],
1485                &dh,
1486                &water_alt_pos,
1487                &mut wh,
1488                usize::from(map_size_lg.chunks().x),
1489                usize::from(map_size_lg.chunks().y),
1490                TerrainChunkSize::RECT_SIZE.x as Compute,
1491                TerrainChunkSize::RECT_SIZE.y as Compute,
1492                maxh,
1493                threadpool,
1494            )
1495        };
1496        let flux_old = get_multi_drainage(map_size_lg, &mstack, &mrec, &mwrec, boundary_len);
1497        // let flux_rivers = get_drainage(map_size_lg, &water_alt_pos, &dh,
1498        // boundary_len); TODO: Make rivers work with multi-direction flux as
1499        // well.
1500        let flux_rivers = flux_old.clone();
1501
1502        let water_height_initial = |chunk_idx| {
1503            let indirection_idx = indirection[chunk_idx];
1504            // Find the lake this point is flowing into.
1505            let lake_idx = if indirection_idx < 0 {
1506                chunk_idx
1507            } else {
1508                indirection_idx as usize
1509            };
1510            let chunk_water_alt = if dh[lake_idx] < 0 {
1511                // This is either a boundary node (dh[chunk_idx] == -2, i.e. water is at sea
1512                // level) or part of a lake that flows directly into the ocean.
1513                // In the former case, water is at sea level so we just return
1514                // 0.0.  In the latter case, the lake bottom must have been a
1515                // boundary node in the first place--meaning this node flows directly
1516                // into the ocean.  In that case, its lake bottom is ocean, meaning its water is
1517                // also at sea level.  Thus, we return 0.0 in both cases.
1518                0.0
1519            } else {
1520                // This chunk is draining into a body of water that isn't the ocean (i.e., a
1521                // lake). Then we just need to find the pass height of the
1522                // surrounding lake in order to figure out the initial water
1523                // height (which fill_sinks will then extend to make
1524                // sure it fills the entire basin).
1525
1526                // Find the height of "our" side of the pass (the part of it that drains into
1527                // this chunk's lake).
1528                let pass_idx = -indirection[lake_idx] as usize;
1529                let pass_height_i = alt[pass_idx];
1530                // Find the pass this lake is flowing into (i.e. water at the lake bottom gets
1531                // pushed towards the point identified by pass_idx).
1532                let neighbor_pass_idx = dh[pass_idx/*lake_idx*/];
1533                // Find the height of the pass into which our lake is flowing.
1534                let pass_height_j = alt[neighbor_pass_idx as usize];
1535                // Find the maximum of these two heights.
1536                // Use the pass height as the initial water altitude.
1537                pass_height_i.max(pass_height_j) /*pass_height*/
1538            };
1539            // Use the maximum of the pass height and chunk height as the parameter to
1540            // fill_sinks.
1541            let chunk_alt = alt[chunk_idx];
1542            chunk_alt.max(chunk_water_alt)
1543        };
1544
1545        // NOTE: If for for some reason you need to avoid the expensive `fill_sinks`
1546        // step here, and we haven't yet replaced it with a faster version, you
1547        // may comment out this line and replace it with the commented-out code
1548        // below; however, there are no guarantees that this
1549        // will work correctly.
1550        let water_alt = fill_sinks(map_size_lg, water_height_initial, is_ocean_fn);
1551        /* let water_alt = (0..map_size_lg.chunks_len())
1552        .into_par_iter()
1553        .map(|posi| water_height_initial(posi))
1554        .collect::<Vec<_>>(); */
1555
1556        let rivers = get_rivers(
1557            map_size_lg,
1558            gen_opts.scale,
1559            &water_alt_pos,
1560            &water_alt,
1561            &dh,
1562            &indirection,
1563            &flux_rivers,
1564        );
1565
1566        let water_alt = indirection
1567            .par_iter()
1568            .enumerate()
1569            .map(|(chunk_idx, &indirection_idx)| {
1570                // Find the lake this point is flowing into.
1571                let lake_idx = if indirection_idx < 0 {
1572                    chunk_idx
1573                } else {
1574                    indirection_idx as usize
1575                };
1576                if dh[lake_idx] < 0 {
1577                    // This is either a boundary node (dh[chunk_idx] == -2, i.e. water is at sea
1578                    // level) or part of a lake that flows directly into the
1579                    // ocean.  In the former case, water is at sea level so we
1580                    // just return 0.0.  In the latter case, the lake bottom must
1581                    // have been a boundary node in the first place--meaning this node flows
1582                    // directly into the ocean.  In that case, its lake bottom
1583                    // is ocean, meaning its water is also at sea level.  Thus,
1584                    // we return 0.0 in both cases.
1585                    0.0
1586                } else {
1587                    // This is not flowing into the ocean, so we can use the existing water_alt.
1588                    water_alt[chunk_idx] as f32
1589                }
1590            })
1591            .collect::<Vec<_>>()
1592            .into_boxed_slice();
1593
1594        let is_underwater = |chunk_idx: usize| match rivers[chunk_idx].river_kind {
1595            Some(RiverKind::Ocean) | Some(RiverKind::Lake { .. }) => true,
1596            Some(RiverKind::River { .. }) => false, // TODO: inspect width
1597            None => false,
1598        };
1599
1600        // Check whether any tiles around this tile are not water (since Lerp will
1601        // ensure that they are included).
1602        let pure_water = |posi: usize| {
1603            let pos = uniform_idx_as_vec2(map_size_lg, posi);
1604            for x in pos.x - 1..(pos.x + 1) + 1 {
1605                for y in pos.y - 1..(pos.y + 1) + 1 {
1606                    if x >= 0
1607                        && y >= 0
1608                        && x < map_size_lg.chunks().x as i32
1609                        && y < map_size_lg.chunks().y as i32
1610                    {
1611                        let posi = vec2_as_uniform_idx(map_size_lg, Vec2::new(x, y));
1612                        if !is_underwater(posi) {
1613                            return false;
1614                        }
1615                    }
1616                }
1617            }
1618            true
1619        };
1620
1621        // NaNs in these uniform vectors wherever pure_water() returns true.
1622        let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = threadpool
1623            .join(
1624                || {
1625                    threadpool.join(
1626                        || {
1627                            uniform_noise(map_size_lg, |posi, _| {
1628                                if pure_water(posi) {
1629                                    None
1630                                } else {
1631                                    // A version of alt that is uniform over *non-water* (or
1632                                    // land-adjacent water) chunks.
1633                                    Some(alt[posi] as f32)
1634                                }
1635                            })
1636                        },
1637                        || {
1638                            uniform_noise(map_size_lg, |posi, _| {
1639                                if pure_water(posi) {
1640                                    None
1641                                } else {
1642                                    Some(flux_old[posi])
1643                                }
1644                            })
1645                        },
1646                    )
1647                },
1648                || {
1649                    threadpool.join(
1650                        || {
1651                            uniform_noise(map_size_lg, |posi, wposf| {
1652                                if pure_water(posi) {
1653                                    None
1654                                } else {
1655                                    // -1 to 1.
1656                                    Some(gen_ctx.temp_nz.get((wposf).into_array()) as f32)
1657                                }
1658                            })
1659                        },
1660                        || {
1661                            uniform_noise(map_size_lg, |posi, wposf| {
1662                                // Check whether any tiles around this tile are water.
1663                                if pure_water(posi) {
1664                                    None
1665                                } else {
1666                                    // 0 to 1, hopefully.
1667                                    Some(
1668                                        (gen_ctx.humid_nz.get(wposf.div(1024.0).into_array())
1669                                            as f32)
1670                                            .add(1.0)
1671                                            .mul(0.5),
1672                                    )
1673                                }
1674                            })
1675                        },
1676                    )
1677                },
1678            );
1679
1680        let gen_cdf = GenCdf {
1681            humid_base,
1682            temp_base,
1683            chaos,
1684            alt,
1685            basement,
1686            water_alt,
1687            dh,
1688            flux: flux_old,
1689            pure_flux,
1690            alt_no_water,
1691            rivers,
1692        };
1693
1694        let chunks = (0..map_size_lg.chunks_len())
1695            .into_par_iter()
1696            .map(|i| SimChunk::generate(map_size_lg, i, &gen_ctx, &gen_cdf))
1697            .collect::<Vec<_>>();
1698
1699        let mut this = Self {
1700            seed,
1701            map_size_lg,
1702            max_height: maxh as f32,
1703            chunks,
1704            _locations: Vec::new(),
1705            gen_ctx,
1706            rng,
1707            calendar,
1708        };
1709
1710        this.generate_cliffs();
1711
1712        if opts.seed_elements {
1713            this.seed_elements();
1714        }
1715
1716        this
1717    }
1718
1719    #[inline(always)]
1720    pub const fn map_size_lg(&self) -> MapSizeLg { self.map_size_lg }
1721
1722    pub fn get_size(&self) -> Vec2<u32> { self.map_size_lg().chunks().map(u32::from) }
1723
1724    pub fn get_aabr(&self) -> Aabr<i32> {
1725        let size = self.get_size();
1726        Aabr {
1727            min: Vec2 { x: 0, y: 0 },
1728            max: Vec2 {
1729                x: size.x as i32,
1730                y: size.y as i32,
1731            },
1732        }
1733    }
1734
1735    pub fn generate_oob_chunk(&self) -> TerrainChunk {
1736        TerrainChunk::water(CONFIG.sea_level as i32)
1737    }
1738
1739    pub fn approx_chunk_terrain_normal(&self, chunk_pos: Vec2<i32>) -> Option<Vec3<f32>> {
1740        let curr_chunk = self.get(chunk_pos)?;
1741        let downhill_chunk_pos = curr_chunk.downhill?.wpos_to_cpos();
1742        let downhill_chunk = self.get(downhill_chunk_pos)?;
1743        // special case if chunks are flat
1744        if (curr_chunk.alt - downhill_chunk.alt) == 0. {
1745            return Some(Vec3::unit_z());
1746        }
1747        let curr = chunk_pos.cpos_to_wpos_center().as_().with_z(curr_chunk.alt);
1748        let down = downhill_chunk_pos
1749            .cpos_to_wpos_center()
1750            .as_()
1751            .with_z(downhill_chunk.alt);
1752        let downwards = curr - down;
1753        let flat = downwards.with_z(down.z);
1754        let mut res = downwards.cross(flat).cross(downwards);
1755        res.normalize();
1756        Some(res)
1757    }
1758
1759    /// Draw a map of the world based on chunk information.  Returns a buffer of
1760    /// u32s.
1761    pub fn get_map(&self, index: IndexRef, calendar: Option<&Calendar>) -> WorldMapMsg {
1762        prof_span!("WorldSim::get_map");
1763        let mut map_config = MapConfig::orthographic(
1764            self.map_size_lg(),
1765            core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),
1766        );
1767        // Build a horizon map.
1768        let scale_angle = |angle: Alt| {
1769            (/* 0.0.max( */angle /* ) */
1770                .atan()
1771                * <Alt as FloatConst>::FRAC_2_PI()
1772                * 255.0)
1773                .floor() as u8
1774        };
1775        let scale_height = |height: Alt| {
1776            (/* 0.0.max( */height/*)*/ as Alt * 255.0 / self.max_height as Alt).floor() as u8
1777        };
1778
1779        let samples_data = {
1780            prof_span!("samples data");
1781            let column_sample = ColumnGen::new(self);
1782            (0..self.map_size_lg().chunks_len())
1783                .into_par_iter()
1784                .map_init(
1785                    || Box::new(BlockGen::new(ColumnGen::new(self))),
1786                    |_block_gen, posi| {
1787                        let sample = column_sample.get(
1788                            (
1789                                uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
1790                                index,
1791                                calendar,
1792                            )
1793                        )?;
1794                        // sample.water_level = CONFIG.sea_level.max(sample.water_level);
1795
1796                        Some(sample)
1797                    },
1798                )
1799                /* .map(|posi| {
1800                    let mut sample = column_sample.get(
1801                        uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
1802                    );
1803                }) */
1804                .collect::<Vec<_>>()
1805                .into_boxed_slice()
1806        };
1807
1808        let horizons = get_horizon_map(
1809            self.map_size_lg(),
1810            Aabr {
1811                min: Vec2::zero(),
1812                max: self.map_size_lg().chunks().map(|e| e as i32),
1813            },
1814            CONFIG.sea_level,
1815            CONFIG.sea_level + self.max_height,
1816            |posi| {
1817                /* let chunk = &self.chunks[posi];
1818                chunk.alt.max(chunk.water_alt) as Alt */
1819                let sample = samples_data[posi].as_ref();
1820                sample
1821                    .map(|s| s.alt.max(s.water_level))
1822                    .unwrap_or(CONFIG.sea_level)
1823            },
1824            |a| scale_angle(a.into()),
1825            |h| scale_height(h.into()),
1826        )
1827        .unwrap();
1828
1829        let mut v = vec![0u32; self.map_size_lg().chunks_len()];
1830        let mut alts = vec![0u32; self.map_size_lg().chunks_len()];
1831        // TODO: Parallelize again.
1832        map_config.is_shaded = false;
1833
1834        map_config.generate(
1835            |pos| sample_pos(&map_config, self, index, Some(&samples_data), pos),
1836            |pos| sample_wpos(&map_config, self, pos),
1837            |pos, (r, g, b, _a)| {
1838                // We currently ignore alpha and replace it with the height at pos, scaled to
1839                // u8.
1840                let alt = sample_wpos(
1841                    &map_config,
1842                    self,
1843                    pos.map(|e| e as i32) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
1844                );
1845                let a = 0; //(alt.min(1.0).max(0.0) * 255.0) as u8;
1846
1847                // NOTE: Safe by invariants on map_size_lg.
1848                let posi = (pos.y << self.map_size_lg().vec().x) | pos.x;
1849                v[posi] = u32::from_le_bytes([r, g, b, a]);
1850                alts[posi] = (((alt.clamp(0.0, 1.0) * 8191.0) as u32) & 0x1FFF) << 3;
1851            },
1852        );
1853        WorldMapMsg {
1854            dimensions_lg: self.map_size_lg().vec(),
1855            max_height: self.max_height,
1856            rgba: Grid::from_raw(self.get_size().map(|e| e as i32), v),
1857            alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts),
1858            horizons,
1859            sites: Vec::new(),                   // Will be substituted later
1860            pois: Vec::new(),                    // Will be substituted later
1861            possible_starting_sites: Vec::new(), // Will be substituted later
1862            default_chunk: Arc::new(self.generate_oob_chunk()),
1863        }
1864    }
1865
1866    pub fn generate_cliffs(&mut self) {
1867        let mut rng = self.rng.clone();
1868
1869        for _ in 0..self.get_size().product() / 10 {
1870            let mut pos = self.get_size().map(|e| rng.random_range(0..e) as i32);
1871
1872            let mut cliffs = DHashSet::default();
1873            let mut cliff_path = Vec::new();
1874
1875            for _ in 0..64 {
1876                if self.get_gradient_approx(pos).is_some_and(|g| g > 1.5) {
1877                    if !cliffs.insert(pos) {
1878                        break;
1879                    }
1880                    cliff_path.push((pos, 0.0));
1881
1882                    pos += CARDINALS
1883                        .iter()
1884                        .copied()
1885                        .max_by_key(|rpos| {
1886                            self.get_gradient_approx(pos + rpos)
1887                                .map_or(0, |g| (g * 1000.0) as i32)
1888                        })
1889                        .unwrap(); // Can't fail
1890                } else {
1891                    break;
1892                }
1893            }
1894
1895            for cliff in cliffs {
1896                Spiral2d::new()
1897                    .take((4usize * 2 + 1).pow(2))
1898                    .for_each(|rpos| {
1899                        let dist = rpos.map(|e| e as f32).magnitude();
1900                        if let Some(c) = self.get_mut(cliff + rpos) {
1901                            let warp = 1.0 / (1.0 + dist);
1902                            if !c.river.near_water() {
1903                                c.tree_density *= 1.0 - warp;
1904                                c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5);
1905                            }
1906                        }
1907                    });
1908            }
1909        }
1910    }
1911
1912    /// Prepare the world for simulation
1913    pub fn seed_elements(&mut self) {
1914        let mut rng = self.rng.clone();
1915
1916        let cell_size = 16;
1917        let grid_size = self.map_size_lg().chunks().map(usize::from) / cell_size;
1918        let loc_count = 100;
1919
1920        let mut loc_grid = vec![None; grid_size.product()];
1921        let mut locations = Vec::new();
1922
1923        // Seed the world with some locations
1924        (0..loc_count).for_each(|_| {
1925            let cell_pos = Vec2::new(
1926                (self.rng.random::<u64>() as usize) % grid_size.x,
1927                (self.rng.random::<u64>() as usize) % grid_size.y,
1928            );
1929            let wpos = (cell_pos * cell_size + cell_size / 2)
1930                .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
1931                    e as i32 * sz as i32 + sz as i32 / 2
1932                });
1933
1934            locations.push(Location::generate(wpos, &mut rng));
1935
1936            loc_grid[cell_pos.y * grid_size.x + cell_pos.x] = Some(locations.len() - 1);
1937        });
1938
1939        // Find neighbours
1940        let mut loc_clone = locations
1941            .iter()
1942            .map(|l| l.center)
1943            .enumerate()
1944            .collect::<Vec<_>>();
1945        // NOTE: We assume that usize is 8 or fewer bytes.
1946        (0..locations.len()).for_each(|i| {
1947            let pos = locations[i].center.map(|e| e as i64);
1948
1949            loc_clone.sort_by_key(|(_, l)| l.map(|e| e as i64).distance_squared(pos));
1950
1951            loc_clone.iter().skip(1).take(2).for_each(|(j, _)| {
1952                locations[i].neighbours.insert(*j as u64);
1953                locations[*j].neighbours.insert(i as u64);
1954            });
1955        });
1956
1957        // Simulate invasion!
1958        let invasion_cycles = 25;
1959        (0..invasion_cycles).for_each(|_| {
1960            (0..grid_size.y).for_each(|j| {
1961                (0..grid_size.x).for_each(|i| {
1962                    if loc_grid[j * grid_size.x + i].is_none() {
1963                        const R_COORDS: [i32; 5] = [-1, 0, 1, 0, -1];
1964                        let idx = (self.rng.random::<u64>() % 4) as usize;
1965                        let new_i = i as i32 + R_COORDS[idx];
1966                        let new_j = j as i32 + R_COORDS[idx + 1];
1967                        if new_i >= 0 && new_j >= 0 {
1968                            let loc = Vec2::new(new_i as usize, new_j as usize);
1969                            loc_grid[j * grid_size.x + i] =
1970                                loc_grid.get(loc.y * grid_size.x + loc.x).cloned().flatten();
1971                        }
1972                    }
1973                });
1974            });
1975        });
1976
1977        // Place the locations onto the world
1978        /*
1979        let gen = StructureGen2d::new(self.seed, cell_size as u32, cell_size as u32 / 2);
1980
1981        self.chunks
1982            .par_iter_mut()
1983            .enumerate()
1984            .for_each(|(ij, chunk)| {
1985                let chunk_pos = uniform_idx_as_vec2(self.map_size_lg(), ij);
1986                let i = chunk_pos.x as usize;
1987                let j = chunk_pos.y as usize;
1988                let block_pos = Vec2::new(
1989                    chunk_pos.x * TerrainChunkSize::RECT_SIZE.x as i32,
1990                    chunk_pos.y * TerrainChunkSize::RECT_SIZE.y as i32,
1991                );
1992                let _cell_pos = Vec2::new(i / cell_size, j / cell_size);
1993
1994                // Find the distance to each region
1995                let near = gen.get(chunk_pos);
1996                let mut near = near
1997                    .iter()
1998                    .map(|(pos, seed)| RegionInfo {
1999                        chunk_pos: *pos,
2000                        block_pos: pos
2001                            .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e * sz as i32),
2002                        dist: (pos - chunk_pos).map(|e| e as f32).magnitude(),
2003                        seed: *seed,
2004                    })
2005                    .collect::<Vec<_>>();
2006
2007                // Sort regions based on distance
2008                near.sort_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap());
2009
2010                let nearest_cell_pos = near[0].chunk_pos;
2011                if nearest_cell_pos.x >= 0 && nearest_cell_pos.y >= 0 {
2012                    let nearest_cell_pos = nearest_cell_pos.map(|e| e as usize) / cell_size;
2013                    chunk.location = loc_grid
2014                        .get(nearest_cell_pos.y * grid_size.x + nearest_cell_pos.x)
2015                        .cloned()
2016                        .unwrap_or(None)
2017                        .map(|loc_idx| LocationInfo { loc_idx, near });
2018                }
2019            });
2020        */
2021
2022        // Create waypoints
2023        const WAYPOINT_EVERY: usize = 16;
2024        let this = &self;
2025        let waypoints = (0..this.map_size_lg().chunks().x)
2026            .step_by(WAYPOINT_EVERY)
2027            .flat_map(|i| {
2028                (0..this.map_size_lg().chunks().y)
2029                    .step_by(WAYPOINT_EVERY)
2030                    .map(move |j| (i, j))
2031            })
2032            .collect::<Vec<_>>()
2033            .into_par_iter()
2034            .filter_map(|(i, j)| {
2035                let mut pos = Vec2::new(i as i32, j as i32);
2036                let mut chunk = this.get(pos)?;
2037
2038                if chunk.is_underwater() {
2039                    return None;
2040                }
2041                // Slide the waypoints down hills
2042                const MAX_ITERS: usize = 64;
2043                for _ in 0..MAX_ITERS {
2044                    let downhill_pos = match chunk.downhill {
2045                        Some(downhill) => {
2046                            downhill.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32))
2047                        },
2048                        None => return Some(pos),
2049                    };
2050
2051                    let new_chunk = this.get(downhill_pos)?;
2052                    const SLIDE_THRESHOLD: f32 = 5.0;
2053                    if new_chunk.river.near_water() || new_chunk.alt + SLIDE_THRESHOLD < chunk.alt {
2054                        break;
2055                    } else {
2056                        chunk = new_chunk;
2057                        pos = downhill_pos;
2058                    }
2059                }
2060                Some(pos)
2061            })
2062            .collect::<Vec<_>>();
2063
2064        for waypoint in waypoints {
2065            self.get_mut(waypoint).map(|sc| sc.contains_waypoint = true);
2066        }
2067
2068        self.rng = rng;
2069        self._locations = locations;
2070    }
2071
2072    pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
2073        if chunk_pos
2074            .map2(self.map_size_lg().chunks(), |e, sz| e >= 0 && e < sz as i32)
2075            .reduce_and()
2076        {
2077            Some(&self.chunks[vec2_as_uniform_idx(self.map_size_lg(), chunk_pos)])
2078        } else {
2079            None
2080        }
2081    }
2082
2083    pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
2084        let a = self.get(chunk_pos)?;
2085        if let Some(downhill) = a.downhill {
2086            let b = self.get(downhill.wpos_to_cpos())?;
2087            Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32)
2088        } else {
2089            Some(0.0)
2090        }
2091    }
2092
2093    /// Get the altitude of the surface, could be water or ground.
2094    pub fn get_surface_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
2095        self.get_interpolated(wpos, |chunk| chunk.alt)
2096            .zip(self.get_interpolated(wpos, |chunk| chunk.water_alt))
2097            .map(|(alt, water_alt)| alt.max(water_alt))
2098            .unwrap_or(CONFIG.sea_level)
2099    }
2100
2101    pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
2102        self.get_interpolated(wpos, |chunk| chunk.alt)
2103    }
2104
2105    pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {
2106        self.get(wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2107            e.div_euclid(sz as i32)
2108        }))
2109    }
2110
2111    pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
2112        let map_size_lg = self.map_size_lg();
2113        if chunk_pos
2114            .map2(map_size_lg.chunks(), |e, sz| e >= 0 && e < sz as i32)
2115            .reduce_and()
2116        {
2117            Some(&mut self.chunks[vec2_as_uniform_idx(map_size_lg, chunk_pos)])
2118        } else {
2119            None
2120        }
2121    }
2122
2123    pub fn get_base_z(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
2124        let in_bounds = chunk_pos
2125            .map2(self.map_size_lg().chunks(), |e, sz| {
2126                e > 0 && e < sz as i32 - 2
2127            })
2128            .reduce_and();
2129        if !in_bounds {
2130            return None;
2131        }
2132
2133        let chunk_idx = vec2_as_uniform_idx(self.map_size_lg(), chunk_pos);
2134        local_cells(self.map_size_lg(), chunk_idx)
2135            .flat_map(|neighbor_idx| {
2136                let neighbor_pos = uniform_idx_as_vec2(self.map_size_lg(), neighbor_idx);
2137                let neighbor_chunk = self.get(neighbor_pos);
2138                let river_kind = neighbor_chunk.and_then(|c| c.river.river_kind);
2139                let has_water = river_kind.is_some() && river_kind != Some(RiverKind::Ocean);
2140                if (neighbor_pos - chunk_pos).reduce_partial_max() <= 1 || has_water {
2141                    neighbor_chunk.map(|c| c.get_base_z())
2142                } else {
2143                    None
2144                }
2145            })
2146            .fold(None, |a: Option<f32>, x| a.map(|a| a.min(x)).or(Some(x)))
2147    }
2148
2149    pub fn get_interpolated<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2150    where
2151        T: Copy + Default + Add<Output = T> + Mul<f32, Output = T>,
2152        F: FnMut(&SimChunk) -> T,
2153    {
2154        let pos = pos.as_::<f64>().wpos_to_cpos();
2155
2156        let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T {
2157            let x2 = x * x;
2158
2159            // Catmull-Rom splines
2160            let co0 = a * -0.5 + b * 1.5 + c * -1.5 + d * 0.5;
2161            let co1 = a + b * -2.5 + c * 2.0 + d * -0.5;
2162            let co2 = a * -0.5 + c * 0.5;
2163            let co3 = b;
2164
2165            co0 * x2 * x + co1 * x2 + co2 * x + co3
2166        };
2167
2168        let mut x = [T::default(); 4];
2169
2170        for (x_idx, j) in (-1..3).enumerate() {
2171            let y0 = f(self.get(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
2172            let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
2173            let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
2174            let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
2175
2176            x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
2177        }
2178
2179        Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32))
2180    }
2181
2182    /// M. Steffen splines.
2183    ///
2184    /// A more expensive cubic interpolation function that can preserve
2185    /// monotonicity between points.  This is useful if you rely on relative
2186    /// differences between endpoints being preserved at all interior
2187    /// points.  For example, we use this with riverbeds (and water
2188    /// height on along rivers) to maintain the invariant that the rivers always
2189    /// flow downhill at interior points (not just endpoints), without
2190    /// needing to flatten out the river.
2191    pub fn get_interpolated_monotone<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2192    where
2193        T: Copy + Default + Signed + Float + Add<Output = T> + Mul<f32, Output = T>,
2194        F: FnMut(&SimChunk) -> T,
2195    {
2196        // See http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1990A%26A...239..443S&defaultprint=YES&page_ind=0&filetype=.pdf
2197        //
2198        // Note that these are only guaranteed monotone in one dimension; fortunately,
2199        // that is sufficient for our purposes.
2200        let pos = pos.as_::<f64>().wpos_to_cpos();
2201
2202        let secant = |b: T, c: T| c - b;
2203
2204        let parabola = |a: T, c: T| -a * 0.5 + c * 0.5;
2205
2206        let slope = |_a: T, _b: T, _c: T, s_a: T, s_b: T, p_b: T| {
2207            // ((b - a).signum() + (c - b).signum()) * s
2208            (s_a.signum() + s_b.signum()) * (s_a.abs().min(s_b.abs()).min(p_b.abs() * 0.5))
2209        };
2210
2211        let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T {
2212            // Compute secants.
2213            let s_a = secant(a, b);
2214            let s_b = secant(b, c);
2215            let s_c = secant(c, d);
2216            // Computing slopes from parabolas.
2217            let p_b = parabola(a, c);
2218            let p_c = parabola(b, d);
2219            // Get slopes (setting distance between neighbors to 1.0).
2220            let slope_b = slope(a, b, c, s_a, s_b, p_b);
2221            let slope_c = slope(b, c, d, s_b, s_c, p_c);
2222            let x2 = x * x;
2223
2224            // Interpolating splines.
2225            let co0 = slope_b + slope_c - s_b * 2.0;
2226            // = a * -0.5 + c * 0.5 + b * -0.5 + d * 0.5 - 2 * (c - b)
2227            // = a * -0.5 + b * 1.5 - c * 1.5 + d * 0.5;
2228            let co1 = s_b * 3.0 - slope_b * 2.0 - slope_c;
2229            // = (3.0 * (c - b) - 2.0 * (a * -0.5 + c * 0.5) - (b * -0.5 + d * 0.5))
2230            // = a + b * -2.5 + c * 2.0 + d * -0.5;
2231            let co2 = slope_b;
2232            // = a * -0.5 + c * 0.5;
2233            let co3 = b;
2234
2235            co0 * x2 * x + co1 * x2 + co2 * x + co3
2236        };
2237
2238        let mut x = [T::default(); 4];
2239
2240        for (x_idx, j) in (-1..3).enumerate() {
2241            let y0 = f(self.get(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
2242            let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
2243            let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
2244            let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
2245
2246            x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
2247        }
2248
2249        Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32))
2250    }
2251
2252    /// Bilinear interpolation.
2253    ///
2254    /// Linear interpolation in both directions (i.e. quadratic interpolation).
2255    pub fn get_interpolated_bilinear<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2256    where
2257        T: Copy + Default + Signed + Float + Add<Output = T> + Mul<f32, Output = T>,
2258        F: FnMut(&SimChunk) -> T,
2259    {
2260        // (i) Find downhill for all four points.
2261        // (ii) Compute distance from each downhill point and do linear interpolation on
2262        // their heights. (iii) Compute distance between each neighboring point
2263        // and do linear interpolation on       their distance-interpolated
2264        // heights.
2265
2266        // See http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1990A%26A...239..443S&defaultprint=YES&page_ind=0&filetype=.pdf
2267        //
2268        // Note that these are only guaranteed monotone in one dimension; fortunately,
2269        // that is sufficient for our purposes.
2270        let pos = pos.as_::<f64>().wpos_to_cpos();
2271
2272        // Orient the chunk in the direction of the most downhill point of the four.  If
2273        // there is no "most downhill" point, then we don't care.
2274        let x0 = pos.map2(Vec2::new(0, 0), |e, q| e.max(0.0) as i32 + q);
2275        let p0 = self.get(x0)?;
2276        let y0 = f(p0);
2277
2278        let x1 = pos.map2(Vec2::new(1, 0), |e, q| e.max(0.0) as i32 + q);
2279        let p1 = self.get(x1)?;
2280        let y1 = f(p1);
2281
2282        let x2 = pos.map2(Vec2::new(0, 1), |e, q| e.max(0.0) as i32 + q);
2283        let p2 = self.get(x2)?;
2284        let y2 = f(p2);
2285
2286        let x3 = pos.map2(Vec2::new(1, 1), |e, q| e.max(0.0) as i32 + q);
2287        let p3 = self.get(x3)?;
2288        let y3 = f(p3);
2289
2290        let z0 = y0
2291            .mul(1.0 - pos.x.fract() as f32)
2292            .mul(1.0 - pos.y.fract() as f32);
2293        let z1 = y1.mul(pos.x.fract() as f32).mul(1.0 - pos.y.fract() as f32);
2294        let z2 = y2.mul(1.0 - pos.x.fract() as f32).mul(pos.y.fract() as f32);
2295        let z3 = y3.mul(pos.x.fract() as f32).mul(pos.y.fract() as f32);
2296
2297        Some(z0 + z1 + z2 + z3)
2298    }
2299
2300    pub fn get_nearest_ways<'a, M: Clone + Lerp<Output = M>>(
2301        &'a self,
2302        wpos: Vec2<i32>,
2303        get_way: &'a impl Fn(&SimChunk) -> Option<(Way, M)>,
2304    ) -> impl Iterator<Item = NearestWaysData<M, impl FnOnce() -> Vec2<f32>>> + 'a {
2305        let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2306            e.div_euclid(sz as i32)
2307        });
2308        let get_chunk_centre = |chunk_pos: Vec2<i32>| {
2309            chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2310                e * sz as i32 + sz as i32 / 2
2311            })
2312        };
2313
2314        LOCALITY
2315            .iter()
2316            .filter_map(move |ctrl| {
2317                let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?;
2318                let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32)
2319                    + way.offset.map(|e| e as f32);
2320
2321                let chunk_connections = way.neighbors.count_ones();
2322                if chunk_connections == 0 {
2323                    return None;
2324                }
2325
2326                let (start_pos, start_idx, start_meta) = if chunk_connections != 2 {
2327                    (ctrl_pos, None, meta.clone())
2328                } else {
2329                    let (start_idx, start_rpos) = NEIGHBORS
2330                        .iter()
2331                        .copied()
2332                        .enumerate()
2333                        .find(|(i, _)| way.neighbors & (1 << *i as u8) != 0)
2334                        .unwrap();
2335                    let start_pos_chunk = chunk_pos + *ctrl + start_rpos;
2336                    let (start_way, start_meta) = get_way(self.get(start_pos_chunk)?)?;
2337                    (
2338                        get_chunk_centre(start_pos_chunk).map(|e| e as f32)
2339                            + start_way.offset.map(|e| e as f32),
2340                        Some(start_idx),
2341                        start_meta,
2342                    )
2343                };
2344
2345                Some(
2346                    NEIGHBORS
2347                        .iter()
2348                        .enumerate()
2349                        .filter(move |(i, _)| {
2350                            way.neighbors & (1 << *i as u8) != 0 && Some(*i) != start_idx
2351                        })
2352                        .filter_map(move |(i, end_rpos)| {
2353                            let end_pos_chunk = chunk_pos + *ctrl + end_rpos;
2354                            let (end_way, end_meta) = get_way(self.get(end_pos_chunk)?)?;
2355                            let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32)
2356                                + end_way.offset.map(|e| e as f32);
2357
2358                            let bez = QuadraticBezier2 {
2359                                start: (start_pos + ctrl_pos) / 2.0,
2360                                ctrl: ctrl_pos,
2361                                end: (end_pos + ctrl_pos) / 2.0,
2362                            };
2363                            let nearest_interval = bez
2364                                .binary_search_point_by_steps(wpos.map(|e| e as f32), 16, 0.001)
2365                                .0
2366                                .clamped(0.0, 1.0);
2367                            let pos = bez.evaluate(nearest_interval);
2368                            let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32));
2369                            let meta = if nearest_interval < 0.5 {
2370                                Lerp::lerp(start_meta.clone(), meta.clone(), 0.5 + nearest_interval)
2371                            } else {
2372                                Lerp::lerp(meta.clone(), end_meta, nearest_interval - 0.5)
2373                            };
2374                            Some(NearestWaysData {
2375                                i,
2376                                dist_sqrd,
2377                                pos,
2378                                meta,
2379                                bezier: bez,
2380                                calc_tangent: move || {
2381                                    bez.evaluate_derivative(nearest_interval).normalized()
2382                                },
2383                            })
2384                        }),
2385                )
2386            })
2387            .flatten()
2388    }
2389
2390    /// Return the distance to the nearest way in blocks, along with the
2391    /// closest point on the way, the way metadata, and the tangent vector
2392    /// of that way.
2393    pub fn get_nearest_way<M: Clone + Lerp<Output = M>>(
2394        &self,
2395        wpos: Vec2<i32>,
2396        get_way: impl Fn(&SimChunk) -> Option<(Way, M)>,
2397    ) -> Option<(f32, Vec2<f32>, M, Vec2<f32>)> {
2398        let get_way = &get_way;
2399        self.get_nearest_ways(wpos, get_way)
2400            .min_by_key(|NearestWaysData { dist_sqrd, .. }| (dist_sqrd * 1024.0) as i32)
2401            .map(
2402                |NearestWaysData {
2403                     dist_sqrd,
2404                     pos,
2405                     meta,
2406                     calc_tangent,
2407                     ..
2408                 }| (dist_sqrd.sqrt(), pos, meta, calc_tangent()),
2409            )
2410    }
2411
2412    pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
2413        self.get_nearest_way(wpos, |chunk| Some(chunk.path))
2414    }
2415
2416    /// Create a [`Lottery<Option<ForestKind>>`] that generates [`ForestKind`]s
2417    /// according to the conditions at the given position. If no or fewer
2418    /// trees are appropriate for the conditions, `None` may be generated.
2419    pub fn make_forest_lottery(&self, wpos: Vec2<i32>) -> Lottery<Option<ForestKind>> {
2420        let chunk = if let Some(chunk) = self.get_wpos(wpos) {
2421            chunk
2422        } else {
2423            return Lottery::from(vec![(1.0, None)]);
2424        };
2425        let env = chunk.get_environment();
2426        Lottery::from(
2427            ForestKind::iter()
2428                .enumerate()
2429                .map(|(i, fk)| {
2430                    const CLUSTER_SIZE: f64 = 48.0;
2431                    let nz = (FastNoise2d::new(i as u32 * 37)
2432                        .get(wpos.map(|e| e as f64) / CLUSTER_SIZE)
2433                        + 1.0)
2434                        / 2.0;
2435                    (fk.proclivity(&env) * nz, Some(fk))
2436                })
2437                .chain(std::iter::once((0.001, None)))
2438                .collect::<Vec<_>>(),
2439        )
2440    }
2441
2442    /// WARNING: Not currently used by the tree layer. Needs to be reworked.
2443    /// Return an iterator over candidate tree positions (note that only some of
2444    /// these will become trees since environmental parameters may forbid
2445    /// them spawning).
2446    pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
2447        // Deterministic based on wpos
2448        self.gen_ctx
2449            .structure_gen
2450            .get(wpos)
2451            .into_iter()
2452            .filter_map(move |(wpos, seed)| {
2453                let lottery = self.make_forest_lottery(wpos);
2454                Some(TreeAttr {
2455                    pos: wpos,
2456                    seed,
2457                    scale: 1.0,
2458                    forest_kind: *lottery.choose_seeded(seed).as_ref()?,
2459                    inhabited: false,
2460                })
2461            })
2462    }
2463
2464    pub fn get_area_trees(
2465        &self,
2466        wpos_min: Vec2<i32>,
2467        wpos_max: Vec2<i32>,
2468    ) -> impl Iterator<Item = TreeAttr> + '_ {
2469        self.gen_ctx
2470            .structure_gen
2471            .iter(wpos_min, wpos_max)
2472            .filter_map(move |(wpos, seed)| {
2473                let lottery = self.make_forest_lottery(wpos);
2474                Some(TreeAttr {
2475                    pos: wpos,
2476                    seed,
2477                    scale: 1.0,
2478                    forest_kind: *lottery.choose_seeded(seed).as_ref()?,
2479                    inhabited: false,
2480                })
2481            })
2482    }
2483}
2484
2485#[derive(Debug)]
2486pub struct SimChunk {
2487    pub chaos: f32,
2488    pub alt: f32,
2489    pub basement: f32,
2490    pub water_alt: f32,
2491    pub downhill: Option<Vec2<i32>>,
2492    pub flux: f32,
2493    pub temp: f32,
2494    pub humidity: f32,
2495    pub rockiness: f32,
2496    pub tree_density: f32,
2497    pub forest_kind: ForestKind,
2498    pub spawn_rate: f32,
2499    pub river: RiverData,
2500    pub surface_veg: f32,
2501
2502    pub sites: Vec<Id<Site>>,
2503    pub place: Option<Id<Place>>,
2504    pub poi: Option<Id<PointOfInterest>>,
2505
2506    pub path: (Way, Path),
2507    pub cliff_height: f32,
2508    pub spot: Option<Spot>,
2509
2510    pub contains_waypoint: bool,
2511}
2512
2513#[derive(Copy, Clone)]
2514pub struct RegionInfo {
2515    pub chunk_pos: Vec2<i32>,
2516    pub block_pos: Vec2<i32>,
2517    pub dist: f32,
2518    pub seed: u32,
2519}
2520
2521pub struct NearestWaysData<M, F: FnOnce() -> Vec2<f32>> {
2522    pub i: usize,
2523    pub dist_sqrd: f32,
2524    pub pos: Vec2<f32>,
2525    pub meta: M,
2526    pub bezier: QuadraticBezier2<f32>,
2527    pub calc_tangent: F,
2528}
2529
2530impl SimChunk {
2531    fn generate(map_size_lg: MapSizeLg, posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
2532        let pos = uniform_idx_as_vec2(map_size_lg, posi);
2533        let wposf = (pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)).map(|e| e as f64);
2534
2535        let (_, chaos) = gen_cdf.chaos[posi];
2536        let alt_pre = gen_cdf.alt[posi] as f32;
2537        let basement_pre = gen_cdf.basement[posi] as f32;
2538        let water_alt_pre = gen_cdf.water_alt[posi];
2539        let downhill_pre = gen_cdf.dh[posi];
2540        let flux = gen_cdf.flux[posi] as f32;
2541        let river = gen_cdf.rivers[posi].clone();
2542
2543        // Can have NaNs in non-uniform part where pure_water returned true.  We just
2544        // test one of the four in order to find out whether this is the case.
2545        let (flux_uniform, /* flux_non_uniform */ _) = gen_cdf.pure_flux[posi];
2546        let (alt_uniform, _) = gen_cdf.alt_no_water[posi];
2547        let (temp_uniform, _) = gen_cdf.temp_base[posi];
2548        let (humid_uniform, _) = gen_cdf.humid_base[posi];
2549
2550        /* // Vertical difference from the equator (NOTE: "uniform" with much lower granularity than
2551        // other uniform quantities, but hopefully this doesn't matter *too* much--if it does, we
2552        // can always add a small x component).
2553        //
2554        // Not clear that we want this yet, let's see.
2555        let latitude_uniform = (pos.y as f32 / f32::from(self.map_size_lg().chunks().y)).sub(0.5).mul(2.0);
2556
2557        // Even less granular--if this matters we can make the sign affect the quantity slightly.
2558        let abs_lat_uniform = latitude_uniform.abs(); */
2559
2560        // We also correlate temperature negatively with altitude and absolute latitude,
2561        // using different weighting than we use for humidity.
2562        const TEMP_WEIGHTS: [f32; 3] = [/* 1.5, */ 1.0, 2.0, 1.0];
2563        let temp = cdf_irwin_hall(
2564            &TEMP_WEIGHTS,
2565            [
2566                temp_uniform,
2567                1.0 - alt_uniform, /* 1.0 - abs_lat_uniform*/
2568                (gen_ctx.rock_nz.get((wposf.div(50000.0)).into_array()) as f32 * 2.5 + 1.0) * 0.5,
2569            ],
2570        )
2571        // Convert to [-1, 1]
2572        .sub(0.5)
2573        .mul(2.0);
2574
2575        // Take the weighted average of our randomly generated base humidity, and the
2576        // calculated water flux over this point in order to compute humidity.
2577        const HUMID_WEIGHTS: [f32; 3] = [1.0, 1.0, 0.75];
2578        let humidity = cdf_irwin_hall(&HUMID_WEIGHTS, [humid_uniform, flux_uniform, 1.0]);
2579        // Moisture evaporates more in hot places
2580        let humidity = humidity
2581            * (1.0
2582                - (temp - CONFIG.tropical_temp)
2583                    .max(0.0)
2584                    .div(1.0 - CONFIG.tropical_temp))
2585            .max(0.0);
2586
2587        let mut alt = CONFIG.sea_level.add(alt_pre);
2588        let basement = CONFIG.sea_level.add(basement_pre);
2589        let water_alt = CONFIG.sea_level.add(water_alt_pre);
2590        let (downhill, _gradient) = if downhill_pre == -2 {
2591            (None, 0.0)
2592        } else if downhill_pre < 0 {
2593            panic!("Uh... shouldn't this never, ever happen?");
2594        } else {
2595            (
2596                Some(
2597                    uniform_idx_as_vec2(map_size_lg, downhill_pre as usize)
2598                        * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)
2599                        + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2),
2600                ),
2601                (alt_pre - gen_cdf.alt[downhill_pre as usize] as f32).abs()
2602                    / TerrainChunkSize::RECT_SIZE.x as f32,
2603            )
2604        };
2605
2606        // Logistic regression.  Make sure x ∈ (0, 1).
2607        let logit = |x: f64| x.ln() - x.neg().ln_1p();
2608        // 0.5 + 0.5 * tanh(ln(1 / (1 - 0.1) - 1) / (2 * (sqrt(3)/pi)))
2609        let logistic_2_base = 3.0f64.sqrt().mul(std::f64::consts::FRAC_2_PI);
2610        // Assumes μ = 0, σ = 1
2611        let logistic_cdf = |x: f64| x.div(logistic_2_base).tanh().mul(0.5).add(0.5);
2612
2613        let is_underwater = match river.river_kind {
2614            Some(RiverKind::Ocean) | Some(RiverKind::Lake { .. }) => true,
2615            Some(RiverKind::River { .. }) => false, // TODO: inspect width
2616            None => false,
2617        };
2618        let river_xy = Vec2::new(river.velocity.x, river.velocity.y).magnitude();
2619        let river_slope = river.velocity.z / river_xy;
2620        match river.river_kind {
2621            Some(RiverKind::River { cross_section }) => {
2622                if cross_section.x >= 0.5 && cross_section.y >= CONFIG.river_min_height {
2623                    /* println!(
2624                        "Big area! Pos area: {:?}, River data: {:?}, slope: {:?}",
2625                        wposf, river, river_slope
2626                    ); */
2627                }
2628                if river_slope.abs() >= 0.25 && cross_section.x >= 1.0 {
2629                    let pos_area = wposf;
2630                    let river_data = &river;
2631                    debug!(?pos_area, ?river_data, ?river_slope, "Big waterfall!",);
2632                }
2633            },
2634            Some(RiverKind::Lake { .. }) => {
2635                // Forces lakes to be downhill from the land around them, and adds some noise to
2636                // the lake bed to make sure it's not too flat.
2637                let lake_bottom_nz = (gen_ctx.small_nz.get((wposf.div(20.0)).into_array()) as f32)
2638                    .clamp(-1.0, 1.0)
2639                    .mul(3.0);
2640                alt = alt.min(water_alt - 5.0) + lake_bottom_nz;
2641            },
2642            _ => {},
2643        }
2644
2645        // No trees in the ocean, with zero humidity (currently), or directly on
2646        // bedrock.
2647        let tree_density = if is_underwater {
2648            0.0
2649        } else {
2650            let tree_density = Lerp::lerp(
2651                -1.5,
2652                2.5,
2653                gen_ctx.tree_nz.get((wposf.div(1024.0)).into_array()) * 0.5 + 0.5,
2654            )
2655            .clamp(0.0, 1.0);
2656            // Tree density should go (by a lot) with humidity.
2657            if humidity <= 0.0 || tree_density <= 0.0 {
2658                0.0
2659            } else if humidity >= 1.0 || tree_density >= 1.0 {
2660                1.0
2661            } else {
2662                // Weighted logit sum.
2663                logistic_cdf(logit(tree_density))
2664            }
2665            // rescale to (-0.95, 0.95)
2666            .sub(0.5)
2667            .add(0.5)
2668        } as f32;
2669        const MIN_TREE_HUM: f32 = 0.15;
2670        let tree_density = tree_density
2671            // Tree density increases exponentially with humidity...
2672            .mul((humidity - MIN_TREE_HUM).max(0.0).mul(1.0 + MIN_TREE_HUM) / temp.max(0.75))
2673            // Places that are *too* wet (like marshes) also get fewer trees because the ground isn't stable enough for
2674            // them.
2675            //.mul((1.0 - flux * 0.05/*(humidity - 0.9).max(0.0) / 0.1*/).max(0.0))
2676            .mul(0.25 + flux * 0.05)
2677            // ...but is ultimately limited by available sunlight (and our tree generation system)
2678            .min(1.0);
2679
2680        // Add geologically short timescale undulation to the world for various reasons
2681        let alt =
2682            // Don't add undulation to rivers, mainly because this could accidentally result in rivers flowing uphill
2683            if river.near_water() {
2684                alt
2685            } else {
2686                // Sand dunes (formed over a short period of time, so we don't care about erosion sim)
2687                let warp = Vec2::new(
2688                    gen_ctx.turb_x_nz.get(wposf.div(350.0).into_array()) as f32,
2689                    gen_ctx.turb_y_nz.get(wposf.div(350.0).into_array()) as f32,
2690                ) * 200.0;
2691                const DUNE_SCALE: f32 = 24.0;
2692                const DUNE_LEN: f32 = 96.0;
2693                const DUNE_DIR: Vec2<f32> = Vec2::new(1.0, 1.0);
2694                let dune_dist = (wposf.map(|e| e as f32) + warp)
2695                    .div(DUNE_LEN)
2696                    .mul(DUNE_DIR.normalized())
2697                    .sum();
2698                let dune_nz = 0.5 - dune_dist.sin().abs() + 0.5 * (dune_dist + 0.5).sin().abs();
2699                let dune = dune_nz * DUNE_SCALE * (temp - 0.75).clamped(0.0, 0.25) * 4.0;
2700
2701                // Trees bind to soil and their roots result in small accumulating undulations over geologically short
2702                // periods of time. Forest floors are generally significantly bumpier than that of deforested areas.
2703                // This is particularly pronounced in high-humidity areas.
2704                let soil_nz = gen_ctx.hill_nz.get(wposf.div(96.0).into_array()) as f32;
2705                let soil_nz = (soil_nz + 1.0) * 0.5;
2706                const SOIL_SCALE: f32 = 16.0;
2707                let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt();
2708
2709                let warp_factor = ((alt - CONFIG.sea_level) / 16.0).clamped(0.0, 1.0);
2710
2711                let warp = (dune + soil) * warp_factor;
2712
2713                // Prevent warping pushing the altitude underwater
2714                if alt + warp < water_alt {
2715                    alt
2716                } else {
2717                    alt + warp
2718                }
2719            };
2720
2721        Self {
2722            chaos,
2723            flux,
2724            alt,
2725            basement: basement.min(alt),
2726            water_alt,
2727            downhill,
2728            temp,
2729            humidity,
2730            rockiness: if true {
2731                (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
2732                    //.add(if river.near_river() { 20.0 } else { 0.0 })
2733                    .sub(0.1)
2734                    .mul(1.3)
2735                    .max(0.0)
2736            } else {
2737                0.0
2738            },
2739            tree_density,
2740            forest_kind: {
2741                let env = Environment {
2742                    humid: humidity,
2743                    temp,
2744                    near_water: if river.is_lake() || river.near_river() {
2745                        1.0
2746                    } else {
2747                        0.0
2748                    },
2749                };
2750
2751                ForestKind::iter()
2752                    .max_by_key(|fk| (fk.proclivity(&env) * 10000.0) as u32)
2753                    .unwrap() // Can't fail
2754            },
2755            spawn_rate: 1.0,
2756            river,
2757            surface_veg: 1.0,
2758
2759            sites: Vec::new(),
2760            place: None,
2761            poi: None,
2762            path: Default::default(),
2763            cliff_height: 0.0,
2764            spot: None,
2765
2766            contains_waypoint: false,
2767        }
2768    }
2769
2770    pub fn is_underwater(&self) -> bool {
2771        self.water_alt > self.alt || self.river.river_kind.is_some()
2772    }
2773
2774    pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
2775
2776    pub fn get_biome(&self) -> BiomeKind {
2777        let savannah_hum_temp = [0.05..0.55, 0.3..1.6];
2778        let taiga_hum_temp = [0.2..1.4, -0.7..-0.3];
2779        if self.river.is_ocean() {
2780            BiomeKind::Ocean
2781        } else if self.river.is_lake() {
2782            BiomeKind::Lake
2783        } else if self.temp < CONFIG.snow_temp {
2784            BiomeKind::Snowland
2785        } else if self.alt > 500.0 && self.chaos > 0.3 && self.tree_density < 0.6 {
2786            BiomeKind::Mountain
2787        } else if self.temp > CONFIG.desert_temp && self.humidity < CONFIG.desert_hum {
2788            BiomeKind::Desert
2789        } else if self.tree_density > 0.65 && self.humidity > 0.65 && self.temp > 0.45 {
2790            BiomeKind::Jungle
2791        } else if savannah_hum_temp[0].contains(&self.humidity)
2792            && savannah_hum_temp[1].contains(&self.temp)
2793        {
2794            BiomeKind::Savannah
2795        } else if taiga_hum_temp[0].contains(&self.humidity)
2796            && taiga_hum_temp[1].contains(&self.temp)
2797        {
2798            BiomeKind::Taiga
2799        } else if self.tree_density > 0.4 {
2800            BiomeKind::Forest
2801        // } else if self.humidity > 0.8 {
2802        //    BiomeKind::Swamp
2803        //      Swamps don't really exist yet.
2804        } else {
2805            BiomeKind::Grassland
2806        }
2807    }
2808
2809    pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 }
2810
2811    pub fn get_environment(&self) -> Environment {
2812        Environment {
2813            humid: self.humidity,
2814            temp: self.temp,
2815            near_water: if self.river.is_lake()
2816                || self.river.near_river()
2817                || self.alt < CONFIG.sea_level + 6.0
2818            // Close to sea in altitude
2819            {
2820                1.0
2821            } else {
2822                0.0
2823            },
2824        }
2825    }
2826
2827    pub fn get_location_name(
2828        &self,
2829        index_sites: &Store<crate::site::Site>,
2830        civs_pois: &Store<PointOfInterest>,
2831        wpos2d: Vec2<i32>,
2832    ) -> Option<String> {
2833        self.sites
2834            .iter()
2835            .filter(|id| {
2836                index_sites[**id].origin.distance_squared(wpos2d) as f32
2837                    <= index_sites[**id].radius().powi(2)
2838            })
2839            .min_by_key(|id| index_sites[**id].origin.distance_squared(wpos2d))
2840            .and_then(|id| Some(index_sites[*id].name()?.to_string()))
2841            .or_else(|| self.poi.map(|poi| civs_pois[poi].name.clone()))
2842    }
2843}