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