1mod diffusion;
2mod erosion;
3mod location;
4mod map;
5mod util;
6mod way;
7
8use 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
78const 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
90struct 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 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 pub humid_nz: Billow<Perlin>,
118 pub small_nz: BasicMulti<Perlin>,
120 pub rock_nz: HybridMulti<Perlin>,
121 pub tree_nz: BasicMulti<Perlin>,
122
123 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 Generate(GenOpts),
167 Save(PathBuf, GenOpts),
170 LoadOrGenerate {
174 name: String,
175 #[serde(default)]
176 opts: GenOpts,
177 #[serde(default)]
178 overwrite: bool,
179 },
180 LoadLegacy(PathBuf),
185 Load(PathBuf),
187 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 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 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 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 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 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 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 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 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#[derive(Serialize, Deserialize)]
472#[repr(C)]
473pub struct WorldFileLegacy {
474 pub alt: Box<[Alt]>,
476 pub basement: Box<[Alt]>,
478}
479
480#[derive(Serialize, Deserialize)]
482#[repr(C)]
483pub struct WorldMap_0_5_0 {
484 pub alt: Box<[Alt]>,
486 pub basement: Box<[Alt]>,
488}
489
490#[derive(Serialize, Deserialize)]
492#[repr(C)]
493pub struct WorldMap_0_7_0 {
494 pub map_size_lg: Vec2<u32>,
496 pub continent_scale_hack: f64,
501 pub alt: Box<[Alt]>,
503 pub basement: Box<[Alt]>,
505}
506
507#[derive(Debug)]
511pub enum WorldFileError {
512 WorldSizeInvalid,
514}
515
516#[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
554pub type ModernMap = WorldMap_0_7_0;
557
558pub const DEFAULT_WORLD_MAP: &str = "world.map.veloren_0_18_0_0";
576pub const DEFAULT_WORLD_SEED: u32 = 130626853;
582
583impl WorldFileLegacy {
584 #[inline]
585 pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
594 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 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 pub fn new(map: ModernMap) -> Self { WorldFile::Veloren0_7_0(map) }
653
654 #[inline]
655 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 Erosion(f64),
669}
670
671pub struct WorldSim {
672 pub seed: u32,
673 map_size_lg: MapSizeLg,
675 pub max_height: f32,
678 pub(crate) chunks: Vec<SimChunk>,
679 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; let world_file = opts.world_file;
761
762 let (parsed_world_file, map_size_lg, gen_opts) = world_file.load_content();
764 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 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 .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 .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 let grid_scale = 1.0f64 / (4.0 / gen_opts.scale);
843
844 let k_fs_scale = |theta, n| grid_scale.powf(-2.0 * (theta * n) as f64);
854
855 let k_da_scale = |q| grid_scale.powf(-2.0 * q);
857 let height_scale = |n: f32| grid_scale.powf(n as f64) as Alt;
865 let time_scale = |n: f32| grid_scale.powf(n as f64);
867 let delta_t_scale = time_scale;
871 let alpha_scale = |n: f32| height_scale(n).recip() as f32;
873 let k_d_scale = |n: f32| grid_scale.powi(2) / (time_scale(n));
877 let epsilon_0_scale = |n| (height_scale(n) / time_scale(n) as Alt) as f32;
879
880 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 let logit = |x: f64| x.ln() - (-x).ln_1p();
890 let logistic_2_base = 3.0f64.sqrt() * std::f64::consts::FRAC_2_PI;
892 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 let ((alt_base, _), (chaos, _)) = threadpool.join(
902 || {
903 uniform_noise(map_size_lg, |_, wposf| {
904 match gen_opts.map_kind {
905 MapKind::Square => {
906 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 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 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 .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 .add(0.2 * hill)
997 .max(0.12)) as f32,
999 )
1000 })
1001 },
1002 );
1003
1004 let (alt_old, _) = uniform_noise(map_size_lg, |posi, wposf| {
1012 let alt_main = {
1031 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.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 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 let is_ocean = get_oceans(map_size_lg, |posi: usize| alt_old[posi].1);
1086 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 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 = 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 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 1.0
1193 };
1194 let epsilon_0_func = |posi| {
1195 let epsilon_0_scale_i = epsilon_0_scale(n_func(posi));
1199 if is_ocean_fn(posi) {
1200 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 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 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 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 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 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 &rock_strength_nz,
1347 alt_func,
1349 alt_func,
1350 is_ocean_fn,
1351 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 height_scale,
1362 k_d_scale(n_approx),
1363 k_da_scale,
1364 threadpool,
1365 report_erosion,
1366 );
1367
1368 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 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 let ModernMap {
1409 continent_scale_hack: _,
1410 map_size_lg: _,
1411 alt,
1412 basement,
1413 } = map.into_modern().unwrap();
1414
1415 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 = flux_old.clone();
1471
1472 let water_height_initial = |chunk_idx| {
1473 let indirection_idx = indirection[chunk_idx];
1474 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 0.0
1489 } else {
1490 let pass_idx = -indirection[lake_idx] as usize;
1499 let pass_height_i = alt[pass_idx];
1500 let neighbor_pass_idx = dh[pass_idx];
1503 let pass_height_j = alt[neighbor_pass_idx as usize];
1505 pass_height_i.max(pass_height_j) };
1509 let chunk_alt = alt[chunk_idx];
1512 chunk_alt.max(chunk_water_alt)
1513 };
1514
1515 let water_alt = fill_sinks(map_size_lg, water_height_initial, is_ocean_fn);
1521 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 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 0.0
1556 } else {
1557 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, None => false,
1568 };
1569
1570 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 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 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 Some(gen_ctx.temp_nz.get((wposf).into_array()) as f32)
1627 }
1628 })
1629 },
1630 || {
1631 uniform_noise(map_size_lg, |posi, wposf| {
1632 if pure_water(posi) {
1634 None
1635 } else {
1636 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 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 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 let scale_angle = |angle: Alt| {
1739 (angle .atan()
1741 * <Alt as FloatConst>::FRAC_2_PI()
1742 * 255.0)
1743 .floor() as u8
1744 };
1745 let scale_height = |height: Alt| {
1746 (heightas 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 Some(sample)
1767 },
1768 )
1769 .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 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 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 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; 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(), pois: Vec::new(), possible_starting_sites: Vec::new(), 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(); } 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 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 (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 let mut loc_clone = locations
1911 .iter()
1912 .map(|l| l.center)
1913 .enumerate()
1914 .collect::<Vec<_>>();
1915 (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 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 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 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 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 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 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 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 (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 let s_a = secant(a, b);
2184 let s_b = secant(b, c);
2185 let s_c = secant(c, d);
2186 let p_b = parabola(a, c);
2188 let p_c = parabola(b, d);
2189 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 let co0 = slope_b + slope_c - s_b * 2.0;
2196 let co1 = s_b * 3.0 - slope_b * 2.0 - slope_c;
2199 let co2 = slope_b;
2202 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 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 let pos = pos.as_::<f64>().wpos_to_cpos();
2241
2242 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 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 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 pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
2421 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 let (flux_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 const TEMP_WEIGHTS: [f32; 3] = [1.0, 2.0, 1.0];
2538 let temp = cdf_irwin_hall(
2539 &TEMP_WEIGHTS,
2540 [
2541 temp_uniform,
2542 1.0 - alt_uniform, (gen_ctx.rock_nz.get((wposf.div(50000.0)).into_array()) as f32 * 2.5 + 1.0) * 0.5,
2544 ],
2545 )
2546 .sub(0.5)
2548 .mul(2.0);
2549
2550 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 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 let logit = |x: f64| x.ln() - x.neg().ln_1p();
2583 let logistic_2_base = 3.0f64.sqrt().mul(std::f64::consts::FRAC_2_PI);
2585 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, 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 }
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 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 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 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 logistic_cdf(logit(tree_density))
2639 }
2640 .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 .mul((humidity - MIN_TREE_HUM).max(0.0).mul(1.0 + MIN_TREE_HUM) / temp.max(0.75))
2648 .mul(0.25 + flux * 0.05)
2652 .min(1.0);
2654
2655 let alt =
2657 if river.near_water() {
2659 alt
2660 } else {
2661 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 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 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 .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() },
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 {
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 {
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}