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::{Path, Way},
16};
17pub(crate) use self::{
18 erosion::{
19 Alt, RiverData, RiverKind, do_erosion, fill_sinks, get_lakes, get_multi_drainage,
20 get_multi_rec, get_rivers,
21 },
22 util::{
23 InverseCdf, cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor,
24 uniform_noise, uphill,
25 },
26};
27
28use crate::{
29 CONFIG, IndexRef,
30 all::{Environment, ForestKind, TreeAttr},
31 block::BlockGen,
32 civ::{Place, PointOfInterest},
33 column::ColumnGen,
34 site::Site,
35 util::{
36 CARDINALS, DHashSet, FastNoise, FastNoise2d, LOCALITY, NEIGHBORS, RandomField, Sampler,
37 StructureGen2d, seed_expan,
38 },
39};
40use bincode::{
41 config::legacy,
42 serde::{decode_from_std_read, encode_into_std_write},
43};
44use common::{
45 assets::{AssetExt, BoxedError, FileAsset, load_bincode_legacy},
46 calendar::Calendar,
47 grid::Grid,
48 lottery::Lottery,
49 resources::MapKind,
50 spiral::Spiral2d,
51 spot::Spot,
52 store::{Id, Store},
53 terrain::{
54 BiomeKind, CoordinateConversions, MapSizeLg, TerrainChunk, TerrainChunkSize,
55 map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx,
56 },
57 vol::RectVolSize,
58};
59use common_base::prof_span;
60use common_net::msg::WorldMapMsg;
61use noise::{
62 BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, Perlin, RidgedMulti, SuperSimplex,
63 core::worley::distance_functions,
64};
65use num::{Float, Signed, traits::FloatConst};
66use rand::{Rng, SeedableRng};
67use rand_chacha::ChaChaRng;
68use rayon::prelude::*;
69use serde::{Deserialize, Serialize};
70use std::{
71 borrow::Cow,
72 f32,
73 fs::File,
74 io::{BufReader, BufWriter},
75 ops::{Add, Div, Mul, Neg, Sub},
76 path::PathBuf,
77 sync::Arc,
78};
79use strum::IntoEnumIterator;
80use tracing::{debug, info, warn};
81use vek::*;
82
83const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
89 if let Ok(map_size_lg) = MapSizeLg::new(Vec2 { x: 10, y: 10 }) {
90 map_size_lg
91 } else {
92 panic!("Default world chunk size does not satisfy required invariants.");
93 };
94
95struct GenCdf {
99 humid_base: InverseCdf,
100 temp_base: InverseCdf,
101 chaos: InverseCdf,
102 alt: Box<[Alt]>,
103 basement: Box<[Alt]>,
104 water_alt: Box<[f32]>,
105 dh: Box<[isize]>,
106 flux: Box<[Compute]>,
109 pure_flux: InverseCdf<Compute>,
110 alt_no_water: InverseCdf,
111 rivers: Box<[RiverData]>,
112}
113
114pub(crate) struct GenCtx {
115 pub turb_x_nz: SuperSimplex,
116 pub turb_y_nz: SuperSimplex,
117 pub chaos_nz: RidgedMulti<Perlin>,
118 pub alt_nz: util::HybridMulti<Perlin>,
119 pub hill_nz: SuperSimplex,
120 pub temp_nz: Fbm<Perlin>,
121 pub humid_nz: Billow<Perlin>,
123 pub small_nz: BasicMulti<Perlin>,
125 pub rock_nz: HybridMulti<Perlin>,
126 pub tree_nz: BasicMulti<Perlin>,
127
128 pub _cave_0_nz: SuperSimplex,
130 pub _cave_1_nz: SuperSimplex,
131
132 pub structure_gen: StructureGen2d,
133 pub _big_structure_gen: StructureGen2d,
134 pub _region_gen: StructureGen2d,
135
136 pub _fast_turb_x_nz: FastNoise,
137 pub _fast_turb_y_nz: FastNoise,
138
139 pub _town_gen: StructureGen2d,
140 pub river_seed: RandomField,
141 pub rock_strength_nz: Fbm<Perlin>,
142 pub uplift_nz: util::Worley,
143}
144
145#[derive(Clone, Debug, Deserialize, Serialize)]
146#[serde(default)]
147pub struct GenOpts {
148 pub x_lg: u32,
149 pub y_lg: u32,
150 pub scale: f64,
151 pub map_kind: MapKind,
152 pub erosion_quality: f32,
153}
154
155impl Default for GenOpts {
156 fn default() -> Self {
157 Self {
158 x_lg: 10,
159 y_lg: 10,
160 scale: 2.0,
161 map_kind: MapKind::Square,
162 erosion_quality: 1.0,
163 }
164 }
165}
166
167#[derive(Clone, Debug, Deserialize, Serialize)]
168pub enum FileOpts {
169 Generate(GenOpts),
172 Save(PathBuf, GenOpts),
175 LoadOrGenerate {
179 name: String,
180 #[serde(default)]
181 opts: GenOpts,
182 #[serde(default)]
183 overwrite: bool,
184 },
185 LoadLegacy(PathBuf),
190 Load(PathBuf),
192 LoadAsset(String),
198}
199
200impl Default for FileOpts {
201 fn default() -> Self { Self::Generate(GenOpts::default()) }
202}
203
204impl FileOpts {
205 fn load_content(&self) -> (Option<ModernMap>, MapSizeLg, GenOpts) {
206 let parsed_world_file = self.try_load_map();
207
208 let mut gen_opts = self.gen_opts().unwrap_or_default();
209
210 let map_size_lg = if let Some(map) = &parsed_world_file {
211 MapSizeLg::new(map.map_size_lg)
212 .expect("World size of loaded map does not satisfy invariants.")
213 } else {
214 self.map_size()
215 };
216
217 if let Some(map) = &parsed_world_file {
226 gen_opts.scale = map.continent_scale_hack;
227 };
228
229 (parsed_world_file, map_size_lg, gen_opts)
230 }
231
232 fn gen_opts(&self) -> Option<GenOpts> {
233 match self {
234 Self::Generate(opts) | Self::Save(_, opts) | Self::LoadOrGenerate { opts, .. } => {
235 Some(opts.clone())
236 },
237 _ => None,
238 }
239 }
240
241 fn map_size(&self) -> MapSizeLg {
243 match self {
244 Self::Generate(opts) | Self::Save(_, opts) | Self::LoadOrGenerate { opts, .. } => {
245 MapSizeLg::new(Vec2 {
246 x: opts.x_lg,
247 y: opts.y_lg,
248 })
249 .unwrap_or_else(|e| {
250 warn!("World size does not satisfy invariants: {:?}", e);
251 DEFAULT_WORLD_CHUNKS_LG
252 })
253 },
254 _ => DEFAULT_WORLD_CHUNKS_LG,
255 }
256 }
257
258 fn try_load_map(&self) -> Option<ModernMap> {
261 let map = match self {
262 Self::LoadLegacy(path) => {
263 let file = match File::open(path) {
264 Ok(file) => file,
265 Err(e) => {
266 warn!(?e, ?path, "Couldn't read path for maps");
267 return None;
268 },
269 };
270
271 let mut reader = BufReader::new(file);
272 let map: WorldFileLegacy = match decode_from_std_read(&mut reader, legacy()) {
273 Ok(map) => map,
274 Err(e) => {
275 warn!(
276 ?e,
277 "Couldn't parse legacy map. Maybe you meant to try a regular load?"
278 );
279 return None;
280 },
281 };
282
283 map.into_modern()
284 },
285 Self::Load(path) => {
286 let file = match File::open(path) {
287 Ok(file) => file,
288 Err(e) => {
289 warn!(?e, ?path, "Couldn't read path for maps");
290 return None;
291 },
292 };
293
294 let mut reader = BufReader::new(file);
295 let map: WorldFile = match decode_from_std_read(&mut reader, legacy()) {
296 Ok(map) => map,
297 Err(e) => {
298 warn!(
299 ?e,
300 "Couldn't parse modern map. Maybe you meant to try a legacy load?"
301 );
302 return None;
303 },
304 };
305
306 map.into_modern()
307 },
308 Self::LoadAsset(specifier) => match WorldFile::load_owned(specifier) {
309 Ok(map) => map.into_modern(),
310 Err(err) => {
311 match err.reason().downcast_ref::<std::io::Error>() {
312 Some(e) => {
313 warn!(?e, ?specifier, "Couldn't read asset specifier for maps");
314 },
315 None => {
316 warn!(
317 ?err,
318 "Couldn't parse modern map. Maybe you meant to try a legacy load?"
319 );
320 },
321 }
322 return None;
323 },
324 },
325 Self::LoadOrGenerate {
326 opts, overwrite, ..
327 } => {
328 let path = self.map_path().unwrap();
331
332 let file = match File::open(&path) {
333 Ok(file) => file,
334 Err(e) => {
335 warn!(?e, ?path, "Couldn't find needed map. Generating...");
336 return None;
337 },
338 };
339
340 let mut reader = BufReader::new(file);
341 let map: WorldFile = match decode_from_std_read(&mut reader, legacy()) {
342 Ok(map) => map,
343 Err(e) => {
344 warn!(
345 ?e,
346 "Couldn't parse modern map. Maybe you meant to try a legacy load?"
347 );
348 return None;
349 },
350 };
351
352 let GenOpts {
365 x_lg, y_lg, scale, ..
366 } = opts;
367 let map = match map {
368 WorldFile::Veloren0_7_0(map) => map,
369 WorldFile::Veloren0_5_0(_) => {
370 panic!("World file v0.5.0 isn't supported with LoadOrGenerate.")
371 },
372 };
373
374 if map.continent_scale_hack != *scale || map.map_size_lg != Vec2::new(*x_lg, *y_lg)
375 {
376 if *overwrite {
377 warn!(
378 "{}\n{}",
379 "Specified options don't correspond to these in loaded map.",
380 "Map will be regenerated and overwritten."
381 );
382 } else {
383 panic!(
384 "{}\n{}",
385 "Specified options don't correspond to these in loaded map.",
386 "Use 'ovewrite' option, if you wish to regenerate map."
387 );
388 }
389
390 return None;
391 }
392
393 map.into_modern()
394 },
395 Self::Generate { .. } | Self::Save { .. } => return None,
396 };
397
398 match map {
399 Ok(map) => Some(map),
400 Err(e) => {
401 match e {
402 WorldFileError::WorldSizeInvalid => {
403 warn!("World size of map is invalid.");
404 },
405 }
406 None
407 },
408 }
409 }
410
411 fn map_path(&self) -> Option<PathBuf> {
412 match self {
414 Self::Save(path, _) => Some(PathBuf::from(&path)),
415 Self::LoadOrGenerate { name, .. } => {
416 const MAP_DIR: &str = "./maps";
417 let file_name = format!("{}.bin", name);
418 Some(std::path::Path::new(MAP_DIR).join(file_name))
419 },
420 _ => None,
421 }
422 }
423
424 fn save(&self, map: &WorldFile) {
425 let path = if let Some(path) = self.map_path() {
426 path
427 } else {
428 return;
429 };
430
431 let map_dir = path.parent().expect("failed to get map directory");
433 if !map_dir.exists()
434 && let Err(e) = std::fs::create_dir_all(map_dir)
435 {
436 warn!(?e, ?map_dir, "Couldn't create folder for map");
437 return;
438 }
439
440 let file = match File::create(path.clone()) {
441 Ok(file) => file,
442 Err(e) => {
443 warn!(?e, ?path, "Couldn't create file for maps");
444 return;
445 },
446 };
447
448 let mut writer = BufWriter::new(file);
449 if let Err(e) = encode_into_std_write(map, &mut writer, legacy()) {
450 warn!(?e, "Couldn't write map");
451 }
452 if let Ok(p) = std::fs::canonicalize(path) {
453 info!("Map saved at {}", p.to_string_lossy());
454 }
455 }
456}
457
458pub struct WorldOpts {
459 pub seed_elements: bool,
461 pub world_file: FileOpts,
462 pub calendar: Option<Calendar>,
463}
464
465impl Default for WorldOpts {
466 fn default() -> Self {
467 Self {
468 seed_elements: true,
469 world_file: Default::default(),
470 calendar: None,
471 }
472 }
473}
474
475#[derive(Serialize, Deserialize)]
477#[repr(C)]
478pub struct WorldFileLegacy {
479 pub alt: Box<[Alt]>,
481 pub basement: Box<[Alt]>,
483}
484
485#[derive(Serialize, Deserialize)]
487#[repr(C)]
488pub struct WorldMap_0_5_0 {
489 pub alt: Box<[Alt]>,
491 pub basement: Box<[Alt]>,
493}
494
495#[derive(Serialize, Deserialize)]
497#[repr(C)]
498pub struct WorldMap_0_7_0 {
499 pub map_size_lg: Vec2<u32>,
501 pub continent_scale_hack: f64,
506 pub alt: Box<[Alt]>,
508 pub basement: Box<[Alt]>,
510}
511
512#[derive(Debug)]
516pub enum WorldFileError {
517 WorldSizeInvalid,
519}
520
521#[derive(Serialize, Deserialize)]
547#[repr(u32)]
548pub enum WorldFile {
549 Veloren0_5_0(WorldMap_0_5_0) = 0,
550 Veloren0_7_0(WorldMap_0_7_0) = 1,
551}
552
553impl FileAsset for WorldFile {
554 const EXTENSION: &'static str = "bin";
555
556 fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> { load_bincode_legacy(&bytes) }
557}
558
559pub type ModernMap = WorldMap_0_7_0;
562
563pub const DEFAULT_WORLD_MAP: &str = "world.map.veloren_0_18_0_0";
581pub const DEFAULT_WORLD_SEED: u32 = 130626853;
587
588impl WorldFileLegacy {
589 #[inline]
590 pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
599 if self.alt.len() != self.basement.len() || self.alt.len() != 1024 * 1024 {
602 return Err(WorldFileError::WorldSizeInvalid);
603 }
604
605 let map = WorldMap_0_5_0 {
606 alt: self.alt,
607 basement: self.basement,
608 };
609
610 map.into_modern()
611 }
612}
613
614impl WorldMap_0_5_0 {
615 #[inline]
616 pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
617 let pow_size = (self.alt.len().trailing_zeros()) / 2;
618 let two_coord_size = 1 << (2 * pow_size);
619 if self.alt.len() != self.basement.len() || self.alt.len() != two_coord_size {
620 return Err(WorldFileError::WorldSizeInvalid);
621 }
622
623 let continent_scale_hack = (f64::from(pow_size) - 10.0).exp2();
626
627 let map = WorldMap_0_7_0 {
628 map_size_lg: Vec2::new(pow_size, pow_size),
629 continent_scale_hack,
630 alt: self.alt,
631 basement: self.basement,
632 };
633
634 map.into_modern()
635 }
636}
637
638impl WorldMap_0_7_0 {
639 #[inline]
640 pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
641 if self.alt.len() != self.basement.len()
642 || self.alt.len() != (1 << (self.map_size_lg.x + self.map_size_lg.y))
643 || self.continent_scale_hack <= 0.0
644 {
645 return Err(WorldFileError::WorldSizeInvalid);
646 }
647
648 Ok(self)
649 }
650}
651
652impl WorldFile {
653 pub fn new(map: ModernMap) -> Self { WorldFile::Veloren0_7_0(map) }
658
659 #[inline]
660 pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
663 match self {
664 WorldFile::Veloren0_5_0(map) => map.into_modern(),
665 WorldFile::Veloren0_7_0(map) => map.into_modern(),
666 }
667 }
668}
669
670#[derive(Debug)]
671pub enum WorldSimStage {
672 Erosion {
674 progress: f64,
675 estimate: Option<std::time::Duration>,
676 },
677}
678
679pub struct WorldSim {
680 pub seed: u32,
681 map_size_lg: MapSizeLg,
683 pub max_height: f32,
686 pub(crate) chunks: Vec<SimChunk>,
687 pub(crate) _locations: Vec<Location>,
689
690 pub(crate) gen_ctx: GenCtx,
691 pub rng: ChaChaRng,
692
693 pub(crate) calendar: Option<Calendar>,
694}
695
696impl WorldSim {
697 pub fn empty() -> Self {
698 let gen_ctx = GenCtx {
699 turb_x_nz: SuperSimplex::new(0),
700 turb_y_nz: SuperSimplex::new(0),
701 chaos_nz: RidgedMulti::new(0),
702 hill_nz: SuperSimplex::new(0),
703 alt_nz: util::HybridMulti::new(0),
704 temp_nz: Fbm::new(0),
705
706 small_nz: BasicMulti::new(0),
707 rock_nz: HybridMulti::new(0),
708 tree_nz: BasicMulti::new(0),
709 _cave_0_nz: SuperSimplex::new(0),
710 _cave_1_nz: SuperSimplex::new(0),
711
712 structure_gen: StructureGen2d::new(0, 24, 10),
713 _big_structure_gen: StructureGen2d::new(0, 768, 512),
714 _region_gen: StructureGen2d::new(0, 400, 96),
715 humid_nz: Billow::new(0),
716
717 _fast_turb_x_nz: FastNoise::new(0),
718 _fast_turb_y_nz: FastNoise::new(0),
719
720 _town_gen: StructureGen2d::new(0, 2048, 1024),
721 river_seed: RandomField::new(0),
722 rock_strength_nz: Fbm::new(0),
723 uplift_nz: util::Worley::new(0),
724 };
725 Self {
726 seed: 0,
727 map_size_lg: MapSizeLg::new(Vec2::one()).unwrap(),
728 max_height: 0.0,
729 chunks: vec![SimChunk {
730 chaos: 0.0,
731 alt: 0.0,
732 basement: 0.0,
733 water_alt: 0.0,
734 downhill: None,
735 flux: 0.0,
736 temp: 0.0,
737 humidity: 0.0,
738 rockiness: 0.0,
739 tree_density: 0.0,
740 forest_kind: ForestKind::Dead,
741 spawn_rate: 0.0,
742 river: RiverData::default(),
743 surface_veg: 0.0,
744 sites: vec![],
745 place: None,
746 poi: None,
747 path: Default::default(),
748 cliff_height: 0.0,
749 spot: None,
750 contains_waypoint: false,
751 }],
752 _locations: Vec::new(),
753 gen_ctx,
754 rng: rand_chacha::ChaCha20Rng::from_seed([0; 32]),
755 calendar: None,
756 }
757 }
758
759 pub fn generate(
760 seed: u32,
761 opts: WorldOpts,
762 threadpool: &rayon::ThreadPool,
763 stage_report: &dyn Fn(WorldSimStage),
764 ) -> Self {
765 prof_span!("WorldSim::generate");
766 let calendar = opts.calendar; let world_file = opts.world_file;
768
769 let (parsed_world_file, map_size_lg, gen_opts) = world_file.load_content();
771 let fresh = parsed_world_file.is_none();
774
775 let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
776 let continent_scale = gen_opts.scale
777 * 5_000.0f64
778 .div(32.0)
779 .mul(TerrainChunkSize::RECT_SIZE.x as f64);
780 let rock_lacunarity = 2.0;
781 let uplift_scale = 128.0;
782 let uplift_turb_scale = uplift_scale / 4.0;
783
784 info!("Starting world generation");
785
786 let gen_ctx = GenCtx {
788 turb_x_nz: SuperSimplex::new(rng.random()),
789 turb_y_nz: SuperSimplex::new(rng.random()),
790 chaos_nz: RidgedMulti::new(rng.random()).set_octaves(7).set_frequency(
791 RidgedMulti::<Perlin>::DEFAULT_FREQUENCY * (5_000.0 / continent_scale),
792 ),
793 hill_nz: SuperSimplex::new(rng.random()),
794 alt_nz: util::HybridMulti::new(rng.random())
795 .set_octaves(8)
796 .set_frequency(10_000.0 / continent_scale)
797 .set_lacunarity(util::HybridMulti::<Perlin>::DEFAULT_LACUNARITY)
799 .set_persistence(util::HybridMulti::<Perlin>::DEFAULT_LACUNARITY.powi(-1))
800 .set_offset(0.0),
801 temp_nz: Fbm::new(rng.random())
802 .set_octaves(6)
803 .set_persistence(0.5)
804 .set_frequency(1.0 / (((1 << 6) * 64) as f64))
805 .set_lacunarity(2.0),
806
807 small_nz: BasicMulti::new(rng.random()).set_octaves(2),
808 rock_nz: HybridMulti::new(rng.random()).set_persistence(0.3),
809 tree_nz: BasicMulti::new(rng.random())
810 .set_octaves(12)
811 .set_persistence(0.75),
812 _cave_0_nz: SuperSimplex::new(rng.random()),
813 _cave_1_nz: SuperSimplex::new(rng.random()),
814
815 structure_gen: StructureGen2d::new(rng.random(), 24, 10),
816 _big_structure_gen: StructureGen2d::new(rng.random(), 768, 512),
817 _region_gen: StructureGen2d::new(rng.random(), 400, 96),
818 humid_nz: Billow::new(rng.random())
819 .set_octaves(9)
820 .set_persistence(0.4)
821 .set_frequency(0.2),
822
823 _fast_turb_x_nz: FastNoise::new(rng.random()),
824 _fast_turb_y_nz: FastNoise::new(rng.random()),
825
826 _town_gen: StructureGen2d::new(rng.random(), 2048, 1024),
827 river_seed: RandomField::new(rng.random()),
828 rock_strength_nz: Fbm::new(rng.random())
829 .set_octaves(10)
830 .set_lacunarity(rock_lacunarity)
831 .set_persistence(rock_lacunarity.powf(-0.75))
834 .set_frequency(
835 1.0 * (5_000.0 / continent_scale)
836 / (2.0 * TerrainChunkSize::RECT_SIZE.x as f64 * 2.0.powi(10 - 1)),
837 ),
838 uplift_nz: util::Worley::new(rng.random())
839 .set_frequency(1.0 / (TerrainChunkSize::RECT_SIZE.x as f64 * uplift_scale))
840 .set_distance_function(distance_functions::euclidean),
841 };
842
843 let river_seed = &gen_ctx.river_seed;
844 let rock_strength_nz = &gen_ctx.rock_strength_nz;
845
846 let grid_scale = 1.0f64 / (4.0 / gen_opts.scale);
850
851 let k_fs_scale = |theta, n| grid_scale.powf(-2.0 * (theta * n) as f64);
861
862 let k_da_scale = |q| grid_scale.powf(-2.0 * q);
864 let height_scale = |n: f32| grid_scale.powf(n as f64) as Alt;
872 let time_scale = |n: f32| grid_scale.powf(n as f64);
874 let delta_t_scale = time_scale;
878 let alpha_scale = |n: f32| height_scale(n).recip() as f32;
880 let k_d_scale = |n: f32| grid_scale.powi(2) / (time_scale(n));
884 let epsilon_0_scale = |n| (height_scale(n) / time_scale(n) as Alt) as f32;
886
887 let n_approx = 1.0;
890 let max_erosion_per_delta_t = 64.0 * delta_t_scale(n_approx);
891 let n_steps = (100.0 * gen_opts.erosion_quality) as usize;
892 let n_small_steps = 0;
893 let n_post_load_steps = 0;
894
895 let logit = |x: f64| x.ln() - (-x).ln_1p();
897 let logistic_2_base = 3.0f64.sqrt() * std::f64::consts::FRAC_2_PI;
899 let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
901
902 let map_size_chunks_len_f64 = map_size_lg.chunks().map(f64::from).product();
903 let min_epsilon = 1.0 / map_size_chunks_len_f64.max(f64::EPSILON * 0.5);
904 let max_epsilon = (1.0 - 1.0 / map_size_chunks_len_f64).min(1.0 - f64::EPSILON * 0.5);
905
906 let ((alt_base, _), (chaos, _)) = threadpool.join(
909 || {
910 uniform_noise(map_size_lg, |_, wposf| {
911 match gen_opts.map_kind {
912 MapKind::Square => {
913 Some(
919 (gen_ctx
920 .alt_nz
921 .get((wposf.div(10_000.0)).into_array())
922 .clamp(-1.0, 1.0))
923 .sub(0.05)
924 .mul(0.35),
925 )
926 },
927 MapKind::Circle => {
928 let world_sizef = map_size_lg.chunks().map(|e| e as f64)
929 * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
930 Some(
931 (gen_ctx
932 .alt_nz
933 .get((wposf.div(5_000.0 * gen_opts.scale)).into_array())
934 .clamp(-1.0, 1.0))
935 .add(
936 0.2 - ((wposf / world_sizef) * 2.0 - 1.0)
937 .magnitude_squared()
938 .powf(0.75)
939 .clamped(0.0, 1.0)
940 .powf(1.0)
941 * 0.6,
942 )
943 .mul(0.5),
944 )
945 },
946 }
947 })
948 },
949 || {
950 uniform_noise(map_size_lg, |_, wposf| {
951 let hill = (0.0f64
956 + gen_ctx
957 .hill_nz
958 .get(
959 (wposf
960 .mul(32.0)
961 .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
962 .div(1_500.0))
963 .into_array(),
964 )
965 .clamp(-1.0, 1.0)
966 .mul(1.0)
967 + gen_ctx
968 .hill_nz
969 .get(
970 (wposf
971 .mul(32.0)
972 .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
973 .div(400.0))
974 .into_array(),
975 )
976 .clamp(-1.0, 1.0)
977 .mul(0.3))
978 .add(0.3)
979 .max(0.0);
980
981 Some(
985 ((gen_ctx
986 .chaos_nz
987 .get((wposf.div(3_000.0)).into_array())
988 .clamp(-1.0, 1.0))
989 .add(1.0)
990 .mul(0.5)
991 .mul(
993 (gen_ctx
994 .chaos_nz
995 .get((wposf.div(6_000.0)).into_array())
996 .clamp(-1.0, 1.0))
997 .abs()
998 .clamp(0.4, 1.0),
999 )
1000 .add(0.2 * hill)
1004 .max(0.12)) as f32,
1006 )
1007 })
1008 },
1009 );
1010
1011 let (alt_old, _) = uniform_noise(map_size_lg, |posi, wposf| {
1019 let alt_main = {
1038 let alt_main = (gen_ctx
1041 .alt_nz
1042 .get((wposf.div(2_000.0)).into_array())
1043 .clamp(-1.0, 1.0))
1044 .abs()
1045 .powf(1.35);
1046
1047 fn spring(x: f64, pow: f64) -> f64 { x.abs().powf(pow) * x.signum() }
1048
1049 0.0 + alt_main
1050 + (gen_ctx
1051 .small_nz
1052 .get(
1053 (wposf
1054 .mul(32.0)
1055 .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1056 .div(300.0))
1057 .into_array(),
1058 )
1059 .clamp(-1.0, 1.0))
1060 .mul(alt_main.powf(0.8).max(0.15))
1061 .mul(0.3)
1062 .add(1.0)
1063 .mul(0.4)
1064 + spring(alt_main.abs().sqrt().min(0.75).mul(60.0).sin(), 4.0).mul(0.045)
1065 };
1066
1067 Some(
1079 ((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)))
1080 .mul(map_edge_factor(map_size_lg, posi) as f64)
1081 .add(
1082 (CONFIG.sea_level as f64)
1083 .div(CONFIG.mountain_scale as f64)
1084 .mul(map_edge_factor(map_size_lg, posi) as f64),
1085 )
1086 .sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
1087 as f32,
1088 )
1089 });
1090
1091 let is_ocean = get_oceans(map_size_lg, |posi: usize| alt_old[posi].1);
1093 let is_ocean_fn = |posi: usize| is_ocean[posi];
1100
1101 let turb_wposf_div = 8.0;
1102 let n_func = |posi| {
1103 if is_ocean_fn(posi) {
1104 return 1.0;
1105 }
1106 1.0
1107 };
1108 let old_height = |posi: usize| {
1109 alt_old[posi].1 * CONFIG.mountain_scale * height_scale(n_func(posi)) as f32
1110 };
1111
1112 let (alt_old_no_ocean, _) = uniform_noise(map_size_lg, |posi, _| {
1119 if is_ocean_fn(posi) {
1120 None
1121 } else {
1122 Some(old_height(posi))
1123 }
1124 });
1125 let (uplift_uniform, _) = uniform_noise(map_size_lg, |posi, _wposf| {
1126 if is_ocean_fn(posi) {
1127 None
1128 } else {
1129 let oheight = alt_old_no_ocean[posi].0 as f64 - 0.5;
1130 let height = (oheight + 0.5).powi(2);
1131 Some(height)
1132 }
1133 });
1134
1135 let alt_old_min_uniform = 0.0;
1136 let alt_old_max_uniform = 1.0;
1137
1138 let inv_func = |x: f64| x;
1139 let alt_exp_min_uniform = inv_func(min_epsilon);
1140 let alt_exp_max_uniform = inv_func(max_epsilon);
1141
1142 let erosion_factor = |x: f64| {
1143 (inv_func(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform)
1144 };
1145 let rock_strength_div_factor = (2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0;
1146 let theta_func = |_posi| 0.4;
1147 let kf_func = {
1148 |posi| {
1149 let kf_scale_i = k_fs_scale(theta_func(posi), n_func(posi));
1150 if is_ocean_fn(posi) {
1151 return 1.0e-4 * kf_scale_i;
1152 }
1153
1154 let kf_i = 1.0e-6
1165 ;
1166 kf_i * kf_scale_i
1167 }
1168 };
1169 let kd_func = {
1170 |posi| {
1171 let n = n_func(posi);
1172 let kd_scale_i = k_d_scale(n);
1173 if is_ocean_fn(posi) {
1174 let kd_i = 1.0e-2 / 4.0;
1175 return kd_i * kd_scale_i;
1176 }
1177 let kd_i = 1.0e-2 / 4.0;
1181 kd_i * kd_scale_i
1182 }
1183 };
1184 let g_func = |posi| {
1185 if map_edge_factor(map_size_lg, posi) == 0.0 {
1186 return 0.0;
1187 }
1188 1.0
1200 };
1201 let epsilon_0_func = |posi| {
1202 let epsilon_0_scale_i = epsilon_0_scale(n_func(posi));
1206 if is_ocean_fn(posi) {
1207 let epsilon_0_i = 2.078e-3 / 4.0;
1209 return epsilon_0_i * epsilon_0_scale_i;
1210 }
1211 let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
1212 * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
1213 .map(|e| e as f64);
1214 let turb_wposf = wposf
1215 .mul(5_000.0 / continent_scale)
1216 .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1217 .div(turb_wposf_div);
1218 let turb = Vec2::new(
1219 gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
1220 gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
1221 ) * uplift_turb_scale
1222 * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
1223 let turb_wposf = wposf + turb;
1224 let uheight = gen_ctx
1225 .uplift_nz
1226 .get(turb_wposf.into_array())
1227 .clamp(-1.0, 1.0)
1228 .mul(0.5)
1229 .add(0.5);
1230 let wposf3 = Vec3::new(
1231 wposf.x,
1232 wposf.y,
1233 uheight * CONFIG.mountain_scale as f64 * rock_strength_div_factor,
1234 );
1235 let rock_strength = gen_ctx
1236 .rock_strength_nz
1237 .get(wposf3.into_array())
1238 .clamp(-1.0, 1.0)
1239 .mul(0.5)
1240 .add(0.5);
1241 let center = 0.4;
1242 let dmin = center - 0.05;
1243 let dmax = center + 0.05;
1244 let log_odds = |x: f64| logit(x) - logit(center);
1245 let ustrength = logistic_cdf(
1246 1.0 * logit(rock_strength.clamp(1e-7, 1.0f64 - 1e-7))
1247 + 1.0 * log_odds(uheight.clamp(dmin, dmax)),
1248 );
1249 let epsilon_0_i = ((1.0 - ustrength) * (2.078e-3 - 5.3e-5) + 5.3e-5) as f32 / 4.0;
1256 epsilon_0_i * epsilon_0_scale_i
1257 };
1258 let alpha_func = |posi| {
1259 let alpha_scale_i = alpha_scale(n_func(posi));
1260 if is_ocean_fn(posi) {
1261 return 3.7e-2 * alpha_scale_i;
1263 }
1264 let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
1265 * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
1266 .map(|e| e as f64);
1267 let turb_wposf = wposf
1268 .mul(5_000.0 / continent_scale)
1269 .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
1270 .div(turb_wposf_div);
1271 let turb = Vec2::new(
1272 gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
1273 gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
1274 ) * uplift_turb_scale
1275 * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
1276 let turb_wposf = wposf + turb;
1277 let uheight = gen_ctx
1278 .uplift_nz
1279 .get(turb_wposf.into_array())
1280 .clamp(-1.0, 1.0)
1281 .mul(0.5)
1282 .add(0.5);
1283 let wposf3 = Vec3::new(
1284 wposf.x,
1285 wposf.y,
1286 uheight * CONFIG.mountain_scale as f64 * rock_strength_div_factor,
1287 );
1288 let rock_strength = gen_ctx
1289 .rock_strength_nz
1290 .get(wposf3.into_array())
1291 .clamp(-1.0, 1.0)
1292 .mul(0.5)
1293 .add(0.5);
1294 let center = 0.4;
1295 let dmin = center - 0.05;
1296 let dmax = center + 0.05;
1297 let log_odds = |x: f64| logit(x) - logit(center);
1298 let ustrength = logistic_cdf(
1299 1.0 * logit(rock_strength.clamp(1e-7, 1.0f64 - 1e-7))
1300 + 1.0 * log_odds(uheight.clamp(dmin, dmax)),
1301 );
1302 let alpha_i = (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32;
1310 alpha_i * alpha_scale_i
1311 };
1312 let uplift_fn = |posi| {
1313 if is_ocean_fn(posi) {
1314 return 0.0;
1315 }
1316 let height = (uplift_uniform[posi].1 - alt_old_min_uniform)
1317 / (alt_old_max_uniform - alt_old_min_uniform);
1318
1319 let height = height.mul(max_epsilon - min_epsilon).add(min_epsilon);
1320 let height = erosion_factor(height);
1321 assert!(height >= 0.0);
1322 assert!(height <= 1.0);
1323
1324 height.mul(max_erosion_per_delta_t)
1330 };
1331 let alt_func = |posi| {
1332 if is_ocean_fn(posi) {
1333 old_height(posi)
1334 } else {
1335 (old_height(posi) as f64 / CONFIG.mountain_scale as f64) as f32 - 0.5
1336 }
1337 };
1338
1339 let mut last = None;
1342 let mut all_samples = std::time::Duration::default();
1343 let mut sample_count = 0;
1344 let report_erosion: &mut dyn FnMut(f64) = &mut move |progress: f64| {
1345 let now = std::time::Instant::now();
1346 let estimate = if let Some((last_instant, last_progress)) = last {
1347 if last_progress > progress {
1348 None
1349 } else {
1350 if last_progress < progress {
1351 let sample = now
1352 .duration_since(last_instant)
1353 .div_f64(progress - last_progress);
1354 all_samples += sample;
1355 sample_count += 1;
1356 }
1357
1358 Some((all_samples / sample_count).mul_f64(100.0 - progress))
1359 }
1360 } else {
1361 None
1362 };
1363 last = Some((now, progress));
1364 stage_report(WorldSimStage::Erosion { progress, estimate })
1365 };
1366
1367 let (alt, basement) = if let Some(map) = parsed_world_file {
1368 (map.alt, map.basement)
1369 } else {
1370 let (alt, basement) = do_erosion(
1371 map_size_lg,
1372 max_erosion_per_delta_t as f32,
1373 n_steps,
1374 river_seed,
1375 &rock_strength_nz,
1377 alt_func,
1379 alt_func,
1380 is_ocean_fn,
1381 uplift_fn,
1383 n_func,
1384 theta_func,
1385 kf_func,
1386 kd_func,
1387 g_func,
1388 epsilon_0_func,
1389 alpha_func,
1390 height_scale,
1392 k_d_scale(n_approx),
1393 k_da_scale,
1394 threadpool,
1395 report_erosion,
1396 );
1397
1398 do_erosion(
1400 map_size_lg,
1401 1.0f32,
1402 n_small_steps,
1403 river_seed,
1404 &rock_strength_nz,
1405 |posi| alt[posi] as f32,
1406 |posi| basement[posi] as f32,
1407 is_ocean_fn,
1408 |posi| uplift_fn(posi) * (1.0 / max_erosion_per_delta_t),
1409 n_func,
1410 theta_func,
1411 kf_func,
1412 kd_func,
1413 g_func,
1414 epsilon_0_func,
1415 alpha_func,
1416 height_scale,
1417 k_d_scale(n_approx),
1418 k_da_scale,
1419 threadpool,
1420 report_erosion,
1421 )
1422 };
1423
1424 let map = WorldFile::new(ModernMap {
1427 continent_scale_hack: gen_opts.scale,
1428 map_size_lg: map_size_lg.vec(),
1429 alt,
1430 basement,
1431 });
1432 if fresh {
1433 world_file.save(&map);
1434 }
1435
1436 let ModernMap {
1439 continent_scale_hack: _,
1440 map_size_lg: _,
1441 alt,
1442 basement,
1443 } = map.into_modern().unwrap();
1444
1445 let (alt, basement) = if n_post_load_steps == 0 {
1447 (alt, basement)
1448 } else {
1449 do_erosion(
1450 map_size_lg,
1451 1.0f32,
1452 n_post_load_steps,
1453 river_seed,
1454 &rock_strength_nz,
1455 |posi| alt[posi] as f32,
1456 |posi| basement[posi] as f32,
1457 is_ocean_fn,
1458 |posi| uplift_fn(posi) * (1.0 / max_erosion_per_delta_t),
1459 n_func,
1460 theta_func,
1461 kf_func,
1462 kd_func,
1463 g_func,
1464 epsilon_0_func,
1465 alpha_func,
1466 height_scale,
1467 k_d_scale(n_approx),
1468 k_da_scale,
1469 threadpool,
1470 report_erosion,
1471 )
1472 };
1473
1474 let is_ocean = get_oceans(map_size_lg, |posi| alt[posi]);
1475 let is_ocean_fn = |posi: usize| is_ocean[posi];
1476 let mut dh = downhill(map_size_lg, |posi| alt[posi], is_ocean_fn);
1477 let (boundary_len, indirection, water_alt_pos, maxh) =
1478 get_lakes(map_size_lg, |posi| alt[posi], &mut dh);
1479 debug!(?maxh, "Max height");
1480 let (mrec, mstack, mwrec) = {
1481 let mut wh = vec![0.0; map_size_lg.chunks_len()];
1482 get_multi_rec(
1483 map_size_lg,
1484 |posi| alt[posi],
1485 &dh,
1486 &water_alt_pos,
1487 &mut wh,
1488 usize::from(map_size_lg.chunks().x),
1489 usize::from(map_size_lg.chunks().y),
1490 TerrainChunkSize::RECT_SIZE.x as Compute,
1491 TerrainChunkSize::RECT_SIZE.y as Compute,
1492 maxh,
1493 threadpool,
1494 )
1495 };
1496 let flux_old = get_multi_drainage(map_size_lg, &mstack, &mrec, &mwrec, boundary_len);
1497 let flux_rivers = flux_old.clone();
1501
1502 let water_height_initial = |chunk_idx| {
1503 let indirection_idx = indirection[chunk_idx];
1504 let lake_idx = if indirection_idx < 0 {
1506 chunk_idx
1507 } else {
1508 indirection_idx as usize
1509 };
1510 let chunk_water_alt = if dh[lake_idx] < 0 {
1511 0.0
1519 } else {
1520 let pass_idx = -indirection[lake_idx] as usize;
1529 let pass_height_i = alt[pass_idx];
1530 let neighbor_pass_idx = dh[pass_idx];
1533 let pass_height_j = alt[neighbor_pass_idx as usize];
1535 pass_height_i.max(pass_height_j) };
1539 let chunk_alt = alt[chunk_idx];
1542 chunk_alt.max(chunk_water_alt)
1543 };
1544
1545 let water_alt = fill_sinks(map_size_lg, water_height_initial, is_ocean_fn);
1551 let rivers = get_rivers(
1557 map_size_lg,
1558 gen_opts.scale,
1559 &water_alt_pos,
1560 &water_alt,
1561 &dh,
1562 &indirection,
1563 &flux_rivers,
1564 );
1565
1566 let water_alt = indirection
1567 .par_iter()
1568 .enumerate()
1569 .map(|(chunk_idx, &indirection_idx)| {
1570 let lake_idx = if indirection_idx < 0 {
1572 chunk_idx
1573 } else {
1574 indirection_idx as usize
1575 };
1576 if dh[lake_idx] < 0 {
1577 0.0
1586 } else {
1587 water_alt[chunk_idx] as f32
1589 }
1590 })
1591 .collect::<Vec<_>>()
1592 .into_boxed_slice();
1593
1594 let is_underwater = |chunk_idx: usize| match rivers[chunk_idx].river_kind {
1595 Some(RiverKind::Ocean) | Some(RiverKind::Lake { .. }) => true,
1596 Some(RiverKind::River { .. }) => false, None => false,
1598 };
1599
1600 let pure_water = |posi: usize| {
1603 let pos = uniform_idx_as_vec2(map_size_lg, posi);
1604 for x in pos.x - 1..(pos.x + 1) + 1 {
1605 for y in pos.y - 1..(pos.y + 1) + 1 {
1606 if x >= 0
1607 && y >= 0
1608 && x < map_size_lg.chunks().x as i32
1609 && y < map_size_lg.chunks().y as i32
1610 {
1611 let posi = vec2_as_uniform_idx(map_size_lg, Vec2::new(x, y));
1612 if !is_underwater(posi) {
1613 return false;
1614 }
1615 }
1616 }
1617 }
1618 true
1619 };
1620
1621 let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = threadpool
1623 .join(
1624 || {
1625 threadpool.join(
1626 || {
1627 uniform_noise(map_size_lg, |posi, _| {
1628 if pure_water(posi) {
1629 None
1630 } else {
1631 Some(alt[posi] as f32)
1634 }
1635 })
1636 },
1637 || {
1638 uniform_noise(map_size_lg, |posi, _| {
1639 if pure_water(posi) {
1640 None
1641 } else {
1642 Some(flux_old[posi])
1643 }
1644 })
1645 },
1646 )
1647 },
1648 || {
1649 threadpool.join(
1650 || {
1651 uniform_noise(map_size_lg, |posi, wposf| {
1652 if pure_water(posi) {
1653 None
1654 } else {
1655 Some(gen_ctx.temp_nz.get((wposf).into_array()) as f32)
1657 }
1658 })
1659 },
1660 || {
1661 uniform_noise(map_size_lg, |posi, wposf| {
1662 if pure_water(posi) {
1664 None
1665 } else {
1666 Some(
1668 (gen_ctx.humid_nz.get(wposf.div(1024.0).into_array())
1669 as f32)
1670 .add(1.0)
1671 .mul(0.5),
1672 )
1673 }
1674 })
1675 },
1676 )
1677 },
1678 );
1679
1680 let gen_cdf = GenCdf {
1681 humid_base,
1682 temp_base,
1683 chaos,
1684 alt,
1685 basement,
1686 water_alt,
1687 dh,
1688 flux: flux_old,
1689 pure_flux,
1690 alt_no_water,
1691 rivers,
1692 };
1693
1694 let chunks = (0..map_size_lg.chunks_len())
1695 .into_par_iter()
1696 .map(|i| SimChunk::generate(map_size_lg, i, &gen_ctx, &gen_cdf))
1697 .collect::<Vec<_>>();
1698
1699 let mut this = Self {
1700 seed,
1701 map_size_lg,
1702 max_height: maxh as f32,
1703 chunks,
1704 _locations: Vec::new(),
1705 gen_ctx,
1706 rng,
1707 calendar,
1708 };
1709
1710 this.generate_cliffs();
1711
1712 if opts.seed_elements {
1713 this.seed_elements();
1714 }
1715
1716 this
1717 }
1718
1719 #[inline(always)]
1720 pub const fn map_size_lg(&self) -> MapSizeLg { self.map_size_lg }
1721
1722 pub fn get_size(&self) -> Vec2<u32> { self.map_size_lg().chunks().map(u32::from) }
1723
1724 pub fn get_aabr(&self) -> Aabr<i32> {
1725 let size = self.get_size();
1726 Aabr {
1727 min: Vec2 { x: 0, y: 0 },
1728 max: Vec2 {
1729 x: size.x as i32,
1730 y: size.y as i32,
1731 },
1732 }
1733 }
1734
1735 pub fn generate_oob_chunk(&self) -> TerrainChunk {
1736 TerrainChunk::water(CONFIG.sea_level as i32)
1737 }
1738
1739 pub fn approx_chunk_terrain_normal(&self, chunk_pos: Vec2<i32>) -> Option<Vec3<f32>> {
1740 let curr_chunk = self.get(chunk_pos)?;
1741 let downhill_chunk_pos = curr_chunk.downhill?.wpos_to_cpos();
1742 let downhill_chunk = self.get(downhill_chunk_pos)?;
1743 if (curr_chunk.alt - downhill_chunk.alt) == 0. {
1745 return Some(Vec3::unit_z());
1746 }
1747 let curr = chunk_pos.cpos_to_wpos_center().as_().with_z(curr_chunk.alt);
1748 let down = downhill_chunk_pos
1749 .cpos_to_wpos_center()
1750 .as_()
1751 .with_z(downhill_chunk.alt);
1752 let downwards = curr - down;
1753 let flat = downwards.with_z(down.z);
1754 let mut res = downwards.cross(flat).cross(downwards);
1755 res.normalize();
1756 Some(res)
1757 }
1758
1759 pub fn get_map(&self, index: IndexRef, calendar: Option<&Calendar>) -> WorldMapMsg {
1762 prof_span!("WorldSim::get_map");
1763 let mut map_config = MapConfig::orthographic(
1764 self.map_size_lg(),
1765 core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),
1766 );
1767 let scale_angle = |angle: Alt| {
1769 (angle .atan()
1771 * <Alt as FloatConst>::FRAC_2_PI()
1772 * 255.0)
1773 .floor() as u8
1774 };
1775 let scale_height = |height: Alt| {
1776 (heightas Alt * 255.0 / self.max_height as Alt).floor() as u8
1777 };
1778
1779 let samples_data = {
1780 prof_span!("samples data");
1781 let column_sample = ColumnGen::new(self);
1782 (0..self.map_size_lg().chunks_len())
1783 .into_par_iter()
1784 .map_init(
1785 || Box::new(BlockGen::new(ColumnGen::new(self))),
1786 |_block_gen, posi| {
1787 let sample = column_sample.get(
1788 (
1789 uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
1790 index,
1791 calendar,
1792 )
1793 )?;
1794 Some(sample)
1797 },
1798 )
1799 .collect::<Vec<_>>()
1805 .into_boxed_slice()
1806 };
1807
1808 let horizons = get_horizon_map(
1809 self.map_size_lg(),
1810 Aabr {
1811 min: Vec2::zero(),
1812 max: self.map_size_lg().chunks().map(|e| e as i32),
1813 },
1814 CONFIG.sea_level,
1815 CONFIG.sea_level + self.max_height,
1816 |posi| {
1817 let sample = samples_data[posi].as_ref();
1820 sample
1821 .map(|s| s.alt.max(s.water_level))
1822 .unwrap_or(CONFIG.sea_level)
1823 },
1824 |a| scale_angle(a.into()),
1825 |h| scale_height(h.into()),
1826 )
1827 .unwrap();
1828
1829 let mut v = vec![0u32; self.map_size_lg().chunks_len()];
1830 let mut alts = vec![0u32; self.map_size_lg().chunks_len()];
1831 map_config.is_shaded = false;
1833
1834 map_config.generate(
1835 |pos| sample_pos(&map_config, self, index, Some(&samples_data), pos),
1836 |pos| sample_wpos(&map_config, self, pos),
1837 |pos, (r, g, b, _a)| {
1838 let alt = sample_wpos(
1841 &map_config,
1842 self,
1843 pos.map(|e| e as i32) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
1844 );
1845 let a = 0; let posi = (pos.y << self.map_size_lg().vec().x) | pos.x;
1849 v[posi] = u32::from_le_bytes([r, g, b, a]);
1850 alts[posi] = (((alt.clamp(0.0, 1.0) * 8191.0) as u32) & 0x1FFF) << 3;
1851 },
1852 );
1853 WorldMapMsg {
1854 dimensions_lg: self.map_size_lg().vec(),
1855 max_height: self.max_height,
1856 rgba: Grid::from_raw(self.get_size().map(|e| e as i32), v),
1857 alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts),
1858 horizons,
1859 sites: Vec::new(), pois: Vec::new(), possible_starting_sites: Vec::new(), default_chunk: Arc::new(self.generate_oob_chunk()),
1863 }
1864 }
1865
1866 pub fn generate_cliffs(&mut self) {
1867 let mut rng = self.rng.clone();
1868
1869 for _ in 0..self.get_size().product() / 10 {
1870 let mut pos = self.get_size().map(|e| rng.random_range(0..e) as i32);
1871
1872 let mut cliffs = DHashSet::default();
1873 let mut cliff_path = Vec::new();
1874
1875 for _ in 0..64 {
1876 if self.get_gradient_approx(pos).is_some_and(|g| g > 1.5) {
1877 if !cliffs.insert(pos) {
1878 break;
1879 }
1880 cliff_path.push((pos, 0.0));
1881
1882 pos += CARDINALS
1883 .iter()
1884 .copied()
1885 .max_by_key(|rpos| {
1886 self.get_gradient_approx(pos + rpos)
1887 .map_or(0, |g| (g * 1000.0) as i32)
1888 })
1889 .unwrap(); } else {
1891 break;
1892 }
1893 }
1894
1895 for cliff in cliffs {
1896 Spiral2d::new()
1897 .take((4usize * 2 + 1).pow(2))
1898 .for_each(|rpos| {
1899 let dist = rpos.map(|e| e as f32).magnitude();
1900 if let Some(c) = self.get_mut(cliff + rpos) {
1901 let warp = 1.0 / (1.0 + dist);
1902 if !c.river.near_water() {
1903 c.tree_density *= 1.0 - warp;
1904 c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5);
1905 }
1906 }
1907 });
1908 }
1909 }
1910 }
1911
1912 pub fn seed_elements(&mut self) {
1914 let mut rng = self.rng.clone();
1915
1916 let cell_size = 16;
1917 let grid_size = self.map_size_lg().chunks().map(usize::from) / cell_size;
1918 let loc_count = 100;
1919
1920 let mut loc_grid = vec![None; grid_size.product()];
1921 let mut locations = Vec::new();
1922
1923 (0..loc_count).for_each(|_| {
1925 let cell_pos = Vec2::new(
1926 (self.rng.random::<u64>() as usize) % grid_size.x,
1927 (self.rng.random::<u64>() as usize) % grid_size.y,
1928 );
1929 let wpos = (cell_pos * cell_size + cell_size / 2)
1930 .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
1931 e as i32 * sz as i32 + sz as i32 / 2
1932 });
1933
1934 locations.push(Location::generate(wpos, &mut rng));
1935
1936 loc_grid[cell_pos.y * grid_size.x + cell_pos.x] = Some(locations.len() - 1);
1937 });
1938
1939 let mut loc_clone = locations
1941 .iter()
1942 .map(|l| l.center)
1943 .enumerate()
1944 .collect::<Vec<_>>();
1945 (0..locations.len()).for_each(|i| {
1947 let pos = locations[i].center.map(|e| e as i64);
1948
1949 loc_clone.sort_by_key(|(_, l)| l.map(|e| e as i64).distance_squared(pos));
1950
1951 loc_clone.iter().skip(1).take(2).for_each(|(j, _)| {
1952 locations[i].neighbours.insert(*j as u64);
1953 locations[*j].neighbours.insert(i as u64);
1954 });
1955 });
1956
1957 let invasion_cycles = 25;
1959 (0..invasion_cycles).for_each(|_| {
1960 (0..grid_size.y).for_each(|j| {
1961 (0..grid_size.x).for_each(|i| {
1962 if loc_grid[j * grid_size.x + i].is_none() {
1963 const R_COORDS: [i32; 5] = [-1, 0, 1, 0, -1];
1964 let idx = (self.rng.random::<u64>() % 4) as usize;
1965 let new_i = i as i32 + R_COORDS[idx];
1966 let new_j = j as i32 + R_COORDS[idx + 1];
1967 if new_i >= 0 && new_j >= 0 {
1968 let loc = Vec2::new(new_i as usize, new_j as usize);
1969 loc_grid[j * grid_size.x + i] =
1970 loc_grid.get(loc.y * grid_size.x + loc.x).cloned().flatten();
1971 }
1972 }
1973 });
1974 });
1975 });
1976
1977 const WAYPOINT_EVERY: usize = 16;
2024 let this = &self;
2025 let waypoints = (0..this.map_size_lg().chunks().x)
2026 .step_by(WAYPOINT_EVERY)
2027 .flat_map(|i| {
2028 (0..this.map_size_lg().chunks().y)
2029 .step_by(WAYPOINT_EVERY)
2030 .map(move |j| (i, j))
2031 })
2032 .collect::<Vec<_>>()
2033 .into_par_iter()
2034 .filter_map(|(i, j)| {
2035 let mut pos = Vec2::new(i as i32, j as i32);
2036 let mut chunk = this.get(pos)?;
2037
2038 if chunk.is_underwater() {
2039 return None;
2040 }
2041 const MAX_ITERS: usize = 64;
2043 for _ in 0..MAX_ITERS {
2044 let downhill_pos = match chunk.downhill {
2045 Some(downhill) => {
2046 downhill.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32))
2047 },
2048 None => return Some(pos),
2049 };
2050
2051 let new_chunk = this.get(downhill_pos)?;
2052 const SLIDE_THRESHOLD: f32 = 5.0;
2053 if new_chunk.river.near_water() || new_chunk.alt + SLIDE_THRESHOLD < chunk.alt {
2054 break;
2055 } else {
2056 chunk = new_chunk;
2057 pos = downhill_pos;
2058 }
2059 }
2060 Some(pos)
2061 })
2062 .collect::<Vec<_>>();
2063
2064 for waypoint in waypoints {
2065 self.get_mut(waypoint).map(|sc| sc.contains_waypoint = true);
2066 }
2067
2068 self.rng = rng;
2069 self._locations = locations;
2070 }
2071
2072 pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
2073 if chunk_pos
2074 .map2(self.map_size_lg().chunks(), |e, sz| e >= 0 && e < sz as i32)
2075 .reduce_and()
2076 {
2077 Some(&self.chunks[vec2_as_uniform_idx(self.map_size_lg(), chunk_pos)])
2078 } else {
2079 None
2080 }
2081 }
2082
2083 pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
2084 let a = self.get(chunk_pos)?;
2085 if let Some(downhill) = a.downhill {
2086 let b = self.get(downhill.wpos_to_cpos())?;
2087 Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32)
2088 } else {
2089 Some(0.0)
2090 }
2091 }
2092
2093 pub fn get_surface_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
2095 self.get_interpolated(wpos, |chunk| chunk.alt)
2096 .zip(self.get_interpolated(wpos, |chunk| chunk.water_alt))
2097 .map(|(alt, water_alt)| alt.max(water_alt))
2098 .unwrap_or(CONFIG.sea_level)
2099 }
2100
2101 pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
2102 self.get_interpolated(wpos, |chunk| chunk.alt)
2103 }
2104
2105 pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {
2106 self.get(wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2107 e.div_euclid(sz as i32)
2108 }))
2109 }
2110
2111 pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
2112 let map_size_lg = self.map_size_lg();
2113 if chunk_pos
2114 .map2(map_size_lg.chunks(), |e, sz| e >= 0 && e < sz as i32)
2115 .reduce_and()
2116 {
2117 Some(&mut self.chunks[vec2_as_uniform_idx(map_size_lg, chunk_pos)])
2118 } else {
2119 None
2120 }
2121 }
2122
2123 pub fn get_base_z(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
2124 let in_bounds = chunk_pos
2125 .map2(self.map_size_lg().chunks(), |e, sz| {
2126 e > 0 && e < sz as i32 - 2
2127 })
2128 .reduce_and();
2129 if !in_bounds {
2130 return None;
2131 }
2132
2133 let chunk_idx = vec2_as_uniform_idx(self.map_size_lg(), chunk_pos);
2134 local_cells(self.map_size_lg(), chunk_idx)
2135 .flat_map(|neighbor_idx| {
2136 let neighbor_pos = uniform_idx_as_vec2(self.map_size_lg(), neighbor_idx);
2137 let neighbor_chunk = self.get(neighbor_pos);
2138 let river_kind = neighbor_chunk.and_then(|c| c.river.river_kind);
2139 let has_water = river_kind.is_some() && river_kind != Some(RiverKind::Ocean);
2140 if (neighbor_pos - chunk_pos).reduce_partial_max() <= 1 || has_water {
2141 neighbor_chunk.map(|c| c.get_base_z())
2142 } else {
2143 None
2144 }
2145 })
2146 .fold(None, |a: Option<f32>, x| a.map(|a| a.min(x)).or(Some(x)))
2147 }
2148
2149 pub fn get_interpolated<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2150 where
2151 T: Copy + Default + Add<Output = T> + Mul<f32, Output = T>,
2152 F: FnMut(&SimChunk) -> T,
2153 {
2154 let pos = pos.as_::<f64>().wpos_to_cpos();
2155
2156 let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T {
2157 let x2 = x * x;
2158
2159 let co0 = a * -0.5 + b * 1.5 + c * -1.5 + d * 0.5;
2161 let co1 = a + b * -2.5 + c * 2.0 + d * -0.5;
2162 let co2 = a * -0.5 + c * 0.5;
2163 let co3 = b;
2164
2165 co0 * x2 * x + co1 * x2 + co2 * x + co3
2166 };
2167
2168 let mut x = [T::default(); 4];
2169
2170 for (x_idx, j) in (-1..3).enumerate() {
2171 let y0 = f(self.get(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
2172 let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
2173 let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
2174 let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
2175
2176 x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
2177 }
2178
2179 Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32))
2180 }
2181
2182 pub fn get_interpolated_monotone<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2192 where
2193 T: Copy + Default + Signed + Float + Add<Output = T> + Mul<f32, Output = T>,
2194 F: FnMut(&SimChunk) -> T,
2195 {
2196 let pos = pos.as_::<f64>().wpos_to_cpos();
2201
2202 let secant = |b: T, c: T| c - b;
2203
2204 let parabola = |a: T, c: T| -a * 0.5 + c * 0.5;
2205
2206 let slope = |_a: T, _b: T, _c: T, s_a: T, s_b: T, p_b: T| {
2207 (s_a.signum() + s_b.signum()) * (s_a.abs().min(s_b.abs()).min(p_b.abs() * 0.5))
2209 };
2210
2211 let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T {
2212 let s_a = secant(a, b);
2214 let s_b = secant(b, c);
2215 let s_c = secant(c, d);
2216 let p_b = parabola(a, c);
2218 let p_c = parabola(b, d);
2219 let slope_b = slope(a, b, c, s_a, s_b, p_b);
2221 let slope_c = slope(b, c, d, s_b, s_c, p_c);
2222 let x2 = x * x;
2223
2224 let co0 = slope_b + slope_c - s_b * 2.0;
2226 let co1 = s_b * 3.0 - slope_b * 2.0 - slope_c;
2229 let co2 = slope_b;
2232 let co3 = b;
2234
2235 co0 * x2 * x + co1 * x2 + co2 * x + co3
2236 };
2237
2238 let mut x = [T::default(); 4];
2239
2240 for (x_idx, j) in (-1..3).enumerate() {
2241 let y0 = f(self.get(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?);
2242 let y1 = f(self.get(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?);
2243 let y2 = f(self.get(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?);
2244 let y3 = f(self.get(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?);
2245
2246 x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32);
2247 }
2248
2249 Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32))
2250 }
2251
2252 pub fn get_interpolated_bilinear<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
2256 where
2257 T: Copy + Default + Signed + Float + Add<Output = T> + Mul<f32, Output = T>,
2258 F: FnMut(&SimChunk) -> T,
2259 {
2260 let pos = pos.as_::<f64>().wpos_to_cpos();
2271
2272 let x0 = pos.map2(Vec2::new(0, 0), |e, q| e.max(0.0) as i32 + q);
2275 let p0 = self.get(x0)?;
2276 let y0 = f(p0);
2277
2278 let x1 = pos.map2(Vec2::new(1, 0), |e, q| e.max(0.0) as i32 + q);
2279 let p1 = self.get(x1)?;
2280 let y1 = f(p1);
2281
2282 let x2 = pos.map2(Vec2::new(0, 1), |e, q| e.max(0.0) as i32 + q);
2283 let p2 = self.get(x2)?;
2284 let y2 = f(p2);
2285
2286 let x3 = pos.map2(Vec2::new(1, 1), |e, q| e.max(0.0) as i32 + q);
2287 let p3 = self.get(x3)?;
2288 let y3 = f(p3);
2289
2290 let z0 = y0
2291 .mul(1.0 - pos.x.fract() as f32)
2292 .mul(1.0 - pos.y.fract() as f32);
2293 let z1 = y1.mul(pos.x.fract() as f32).mul(1.0 - pos.y.fract() as f32);
2294 let z2 = y2.mul(1.0 - pos.x.fract() as f32).mul(pos.y.fract() as f32);
2295 let z3 = y3.mul(pos.x.fract() as f32).mul(pos.y.fract() as f32);
2296
2297 Some(z0 + z1 + z2 + z3)
2298 }
2299
2300 pub fn get_nearest_ways<'a, M: Clone + Lerp<Output = M>>(
2301 &'a self,
2302 wpos: Vec2<i32>,
2303 get_way: &'a impl Fn(&SimChunk) -> Option<(Way, M)>,
2304 ) -> impl Iterator<Item = NearestWaysData<M, impl FnOnce() -> Vec2<f32>>> + 'a {
2305 let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2306 e.div_euclid(sz as i32)
2307 });
2308 let get_chunk_centre = |chunk_pos: Vec2<i32>| {
2309 chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
2310 e * sz as i32 + sz as i32 / 2
2311 })
2312 };
2313
2314 LOCALITY
2315 .iter()
2316 .filter_map(move |ctrl| {
2317 let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?;
2318 let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32)
2319 + way.offset.map(|e| e as f32);
2320
2321 let chunk_connections = way.neighbors.count_ones();
2322 if chunk_connections == 0 {
2323 return None;
2324 }
2325
2326 let (start_pos, start_idx, start_meta) = if chunk_connections != 2 {
2327 (ctrl_pos, None, meta.clone())
2328 } else {
2329 let (start_idx, start_rpos) = NEIGHBORS
2330 .iter()
2331 .copied()
2332 .enumerate()
2333 .find(|(i, _)| way.neighbors & (1 << *i as u8) != 0)
2334 .unwrap();
2335 let start_pos_chunk = chunk_pos + *ctrl + start_rpos;
2336 let (start_way, start_meta) = get_way(self.get(start_pos_chunk)?)?;
2337 (
2338 get_chunk_centre(start_pos_chunk).map(|e| e as f32)
2339 + start_way.offset.map(|e| e as f32),
2340 Some(start_idx),
2341 start_meta,
2342 )
2343 };
2344
2345 Some(
2346 NEIGHBORS
2347 .iter()
2348 .enumerate()
2349 .filter(move |(i, _)| {
2350 way.neighbors & (1 << *i as u8) != 0 && Some(*i) != start_idx
2351 })
2352 .filter_map(move |(i, end_rpos)| {
2353 let end_pos_chunk = chunk_pos + *ctrl + end_rpos;
2354 let (end_way, end_meta) = get_way(self.get(end_pos_chunk)?)?;
2355 let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32)
2356 + end_way.offset.map(|e| e as f32);
2357
2358 let bez = QuadraticBezier2 {
2359 start: (start_pos + ctrl_pos) / 2.0,
2360 ctrl: ctrl_pos,
2361 end: (end_pos + ctrl_pos) / 2.0,
2362 };
2363 let nearest_interval = bez
2364 .binary_search_point_by_steps(wpos.map(|e| e as f32), 16, 0.001)
2365 .0
2366 .clamped(0.0, 1.0);
2367 let pos = bez.evaluate(nearest_interval);
2368 let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32));
2369 let meta = if nearest_interval < 0.5 {
2370 Lerp::lerp(start_meta.clone(), meta.clone(), 0.5 + nearest_interval)
2371 } else {
2372 Lerp::lerp(meta.clone(), end_meta, nearest_interval - 0.5)
2373 };
2374 Some(NearestWaysData {
2375 i,
2376 dist_sqrd,
2377 pos,
2378 meta,
2379 bezier: bez,
2380 calc_tangent: move || {
2381 bez.evaluate_derivative(nearest_interval).normalized()
2382 },
2383 })
2384 }),
2385 )
2386 })
2387 .flatten()
2388 }
2389
2390 pub fn get_nearest_way<M: Clone + Lerp<Output = M>>(
2394 &self,
2395 wpos: Vec2<i32>,
2396 get_way: impl Fn(&SimChunk) -> Option<(Way, M)>,
2397 ) -> Option<(f32, Vec2<f32>, M, Vec2<f32>)> {
2398 let get_way = &get_way;
2399 self.get_nearest_ways(wpos, get_way)
2400 .min_by_key(|NearestWaysData { dist_sqrd, .. }| (dist_sqrd * 1024.0) as i32)
2401 .map(
2402 |NearestWaysData {
2403 dist_sqrd,
2404 pos,
2405 meta,
2406 calc_tangent,
2407 ..
2408 }| (dist_sqrd.sqrt(), pos, meta, calc_tangent()),
2409 )
2410 }
2411
2412 pub fn get_nearest_path(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Path, Vec2<f32>)> {
2413 self.get_nearest_way(wpos, |chunk| Some(chunk.path))
2414 }
2415
2416 pub fn make_forest_lottery(&self, wpos: Vec2<i32>) -> Lottery<Option<ForestKind>> {
2420 let chunk = if let Some(chunk) = self.get_wpos(wpos) {
2421 chunk
2422 } else {
2423 return Lottery::from(vec![(1.0, None)]);
2424 };
2425 let env = chunk.get_environment();
2426 Lottery::from(
2427 ForestKind::iter()
2428 .enumerate()
2429 .map(|(i, fk)| {
2430 const CLUSTER_SIZE: f64 = 48.0;
2431 let nz = (FastNoise2d::new(i as u32 * 37)
2432 .get(wpos.map(|e| e as f64) / CLUSTER_SIZE)
2433 + 1.0)
2434 / 2.0;
2435 (fk.proclivity(&env) * nz, Some(fk))
2436 })
2437 .chain(std::iter::once((0.001, None)))
2438 .collect::<Vec<_>>(),
2439 )
2440 }
2441
2442 pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
2447 self.gen_ctx
2449 .structure_gen
2450 .get(wpos)
2451 .into_iter()
2452 .filter_map(move |(wpos, seed)| {
2453 let lottery = self.make_forest_lottery(wpos);
2454 Some(TreeAttr {
2455 pos: wpos,
2456 seed,
2457 scale: 1.0,
2458 forest_kind: *lottery.choose_seeded(seed).as_ref()?,
2459 inhabited: false,
2460 })
2461 })
2462 }
2463
2464 pub fn get_area_trees(
2465 &self,
2466 wpos_min: Vec2<i32>,
2467 wpos_max: Vec2<i32>,
2468 ) -> impl Iterator<Item = TreeAttr> + '_ {
2469 self.gen_ctx
2470 .structure_gen
2471 .iter(wpos_min, wpos_max)
2472 .filter_map(move |(wpos, seed)| {
2473 let lottery = self.make_forest_lottery(wpos);
2474 Some(TreeAttr {
2475 pos: wpos,
2476 seed,
2477 scale: 1.0,
2478 forest_kind: *lottery.choose_seeded(seed).as_ref()?,
2479 inhabited: false,
2480 })
2481 })
2482 }
2483}
2484
2485#[derive(Debug)]
2486pub struct SimChunk {
2487 pub chaos: f32,
2488 pub alt: f32,
2489 pub basement: f32,
2490 pub water_alt: f32,
2491 pub downhill: Option<Vec2<i32>>,
2492 pub flux: f32,
2493 pub temp: f32,
2494 pub humidity: f32,
2495 pub rockiness: f32,
2496 pub tree_density: f32,
2497 pub forest_kind: ForestKind,
2498 pub spawn_rate: f32,
2499 pub river: RiverData,
2500 pub surface_veg: f32,
2501
2502 pub sites: Vec<Id<Site>>,
2503 pub place: Option<Id<Place>>,
2504 pub poi: Option<Id<PointOfInterest>>,
2505
2506 pub path: (Way, Path),
2507 pub cliff_height: f32,
2508 pub spot: Option<Spot>,
2509
2510 pub contains_waypoint: bool,
2511}
2512
2513#[derive(Copy, Clone)]
2514pub struct RegionInfo {
2515 pub chunk_pos: Vec2<i32>,
2516 pub block_pos: Vec2<i32>,
2517 pub dist: f32,
2518 pub seed: u32,
2519}
2520
2521pub struct NearestWaysData<M, F: FnOnce() -> Vec2<f32>> {
2522 pub i: usize,
2523 pub dist_sqrd: f32,
2524 pub pos: Vec2<f32>,
2525 pub meta: M,
2526 pub bezier: QuadraticBezier2<f32>,
2527 pub calc_tangent: F,
2528}
2529
2530impl SimChunk {
2531 fn generate(map_size_lg: MapSizeLg, posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
2532 let pos = uniform_idx_as_vec2(map_size_lg, posi);
2533 let wposf = (pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)).map(|e| e as f64);
2534
2535 let (_, chaos) = gen_cdf.chaos[posi];
2536 let alt_pre = gen_cdf.alt[posi] as f32;
2537 let basement_pre = gen_cdf.basement[posi] as f32;
2538 let water_alt_pre = gen_cdf.water_alt[posi];
2539 let downhill_pre = gen_cdf.dh[posi];
2540 let flux = gen_cdf.flux[posi] as f32;
2541 let river = gen_cdf.rivers[posi].clone();
2542
2543 let (flux_uniform, _) = gen_cdf.pure_flux[posi];
2546 let (alt_uniform, _) = gen_cdf.alt_no_water[posi];
2547 let (temp_uniform, _) = gen_cdf.temp_base[posi];
2548 let (humid_uniform, _) = gen_cdf.humid_base[posi];
2549
2550 const TEMP_WEIGHTS: [f32; 3] = [1.0, 2.0, 1.0];
2563 let temp = cdf_irwin_hall(
2564 &TEMP_WEIGHTS,
2565 [
2566 temp_uniform,
2567 1.0 - alt_uniform, (gen_ctx.rock_nz.get((wposf.div(50000.0)).into_array()) as f32 * 2.5 + 1.0) * 0.5,
2569 ],
2570 )
2571 .sub(0.5)
2573 .mul(2.0);
2574
2575 const HUMID_WEIGHTS: [f32; 3] = [1.0, 1.0, 0.75];
2578 let humidity = cdf_irwin_hall(&HUMID_WEIGHTS, [humid_uniform, flux_uniform, 1.0]);
2579 let humidity = humidity
2581 * (1.0
2582 - (temp - CONFIG.tropical_temp)
2583 .max(0.0)
2584 .div(1.0 - CONFIG.tropical_temp))
2585 .max(0.0);
2586
2587 let mut alt = CONFIG.sea_level.add(alt_pre);
2588 let basement = CONFIG.sea_level.add(basement_pre);
2589 let water_alt = CONFIG.sea_level.add(water_alt_pre);
2590 let (downhill, _gradient) = if downhill_pre == -2 {
2591 (None, 0.0)
2592 } else if downhill_pre < 0 {
2593 panic!("Uh... shouldn't this never, ever happen?");
2594 } else {
2595 (
2596 Some(
2597 uniform_idx_as_vec2(map_size_lg, downhill_pre as usize)
2598 * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)
2599 + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2),
2600 ),
2601 (alt_pre - gen_cdf.alt[downhill_pre as usize] as f32).abs()
2602 / TerrainChunkSize::RECT_SIZE.x as f32,
2603 )
2604 };
2605
2606 let logit = |x: f64| x.ln() - x.neg().ln_1p();
2608 let logistic_2_base = 3.0f64.sqrt().mul(std::f64::consts::FRAC_2_PI);
2610 let logistic_cdf = |x: f64| x.div(logistic_2_base).tanh().mul(0.5).add(0.5);
2612
2613 let is_underwater = match river.river_kind {
2614 Some(RiverKind::Ocean) | Some(RiverKind::Lake { .. }) => true,
2615 Some(RiverKind::River { .. }) => false, None => false,
2617 };
2618 let river_xy = Vec2::new(river.velocity.x, river.velocity.y).magnitude();
2619 let river_slope = river.velocity.z / river_xy;
2620 match river.river_kind {
2621 Some(RiverKind::River { cross_section }) => {
2622 if cross_section.x >= 0.5 && cross_section.y >= CONFIG.river_min_height {
2623 }
2628 if river_slope.abs() >= 0.25 && cross_section.x >= 1.0 {
2629 let pos_area = wposf;
2630 let river_data = &river;
2631 debug!(?pos_area, ?river_data, ?river_slope, "Big waterfall!",);
2632 }
2633 },
2634 Some(RiverKind::Lake { .. }) => {
2635 let lake_bottom_nz = (gen_ctx.small_nz.get((wposf.div(20.0)).into_array()) as f32)
2638 .clamp(-1.0, 1.0)
2639 .mul(3.0);
2640 alt = alt.min(water_alt - 5.0) + lake_bottom_nz;
2641 },
2642 _ => {},
2643 }
2644
2645 let tree_density = if is_underwater {
2648 0.0
2649 } else {
2650 let tree_density = Lerp::lerp(
2651 -1.5,
2652 2.5,
2653 gen_ctx.tree_nz.get((wposf.div(1024.0)).into_array()) * 0.5 + 0.5,
2654 )
2655 .clamp(0.0, 1.0);
2656 if humidity <= 0.0 || tree_density <= 0.0 {
2658 0.0
2659 } else if humidity >= 1.0 || tree_density >= 1.0 {
2660 1.0
2661 } else {
2662 logistic_cdf(logit(tree_density))
2664 }
2665 .sub(0.5)
2667 .add(0.5)
2668 } as f32;
2669 const MIN_TREE_HUM: f32 = 0.15;
2670 let tree_density = tree_density
2671 .mul((humidity - MIN_TREE_HUM).max(0.0).mul(1.0 + MIN_TREE_HUM) / temp.max(0.75))
2673 .mul(0.25 + flux * 0.05)
2677 .min(1.0);
2679
2680 let alt =
2682 if river.near_water() {
2684 alt
2685 } else {
2686 let warp = Vec2::new(
2688 gen_ctx.turb_x_nz.get(wposf.div(350.0).into_array()) as f32,
2689 gen_ctx.turb_y_nz.get(wposf.div(350.0).into_array()) as f32,
2690 ) * 200.0;
2691 const DUNE_SCALE: f32 = 24.0;
2692 const DUNE_LEN: f32 = 96.0;
2693 const DUNE_DIR: Vec2<f32> = Vec2::new(1.0, 1.0);
2694 let dune_dist = (wposf.map(|e| e as f32) + warp)
2695 .div(DUNE_LEN)
2696 .mul(DUNE_DIR.normalized())
2697 .sum();
2698 let dune_nz = 0.5 - dune_dist.sin().abs() + 0.5 * (dune_dist + 0.5).sin().abs();
2699 let dune = dune_nz * DUNE_SCALE * (temp - 0.75).clamped(0.0, 0.25) * 4.0;
2700
2701 let soil_nz = gen_ctx.hill_nz.get(wposf.div(96.0).into_array()) as f32;
2705 let soil_nz = (soil_nz + 1.0) * 0.5;
2706 const SOIL_SCALE: f32 = 16.0;
2707 let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt();
2708
2709 let warp_factor = ((alt - CONFIG.sea_level) / 16.0).clamped(0.0, 1.0);
2710
2711 let warp = (dune + soil) * warp_factor;
2712
2713 if alt + warp < water_alt {
2715 alt
2716 } else {
2717 alt + warp
2718 }
2719 };
2720
2721 Self {
2722 chaos,
2723 flux,
2724 alt,
2725 basement: basement.min(alt),
2726 water_alt,
2727 downhill,
2728 temp,
2729 humidity,
2730 rockiness: if true {
2731 (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
2732 .sub(0.1)
2734 .mul(1.3)
2735 .max(0.0)
2736 } else {
2737 0.0
2738 },
2739 tree_density,
2740 forest_kind: {
2741 let env = Environment {
2742 humid: humidity,
2743 temp,
2744 near_water: if river.is_lake() || river.near_river() {
2745 1.0
2746 } else {
2747 0.0
2748 },
2749 };
2750
2751 ForestKind::iter()
2752 .max_by_key(|fk| (fk.proclivity(&env) * 10000.0) as u32)
2753 .unwrap() },
2755 spawn_rate: 1.0,
2756 river,
2757 surface_veg: 1.0,
2758
2759 sites: Vec::new(),
2760 place: None,
2761 poi: None,
2762 path: Default::default(),
2763 cliff_height: 0.0,
2764 spot: None,
2765
2766 contains_waypoint: false,
2767 }
2768 }
2769
2770 pub fn is_underwater(&self) -> bool {
2771 self.water_alt > self.alt || self.river.river_kind.is_some()
2772 }
2773
2774 pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
2775
2776 pub fn get_biome(&self) -> BiomeKind {
2777 let savannah_hum_temp = [0.05..0.55, 0.3..1.6];
2778 let taiga_hum_temp = [0.2..1.4, -0.7..-0.3];
2779 if self.river.is_ocean() {
2780 BiomeKind::Ocean
2781 } else if self.river.is_lake() {
2782 BiomeKind::Lake
2783 } else if self.temp < CONFIG.snow_temp {
2784 BiomeKind::Snowland
2785 } else if self.alt > 500.0 && self.chaos > 0.3 && self.tree_density < 0.6 {
2786 BiomeKind::Mountain
2787 } else if self.temp > CONFIG.desert_temp && self.humidity < CONFIG.desert_hum {
2788 BiomeKind::Desert
2789 } else if self.tree_density > 0.65 && self.humidity > 0.65 && self.temp > 0.45 {
2790 BiomeKind::Jungle
2791 } else if savannah_hum_temp[0].contains(&self.humidity)
2792 && savannah_hum_temp[1].contains(&self.temp)
2793 {
2794 BiomeKind::Savannah
2795 } else if taiga_hum_temp[0].contains(&self.humidity)
2796 && taiga_hum_temp[1].contains(&self.temp)
2797 {
2798 BiomeKind::Taiga
2799 } else if self.tree_density > 0.4 {
2800 BiomeKind::Forest
2801 } else {
2805 BiomeKind::Grassland
2806 }
2807 }
2808
2809 pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 }
2810
2811 pub fn get_environment(&self) -> Environment {
2812 Environment {
2813 humid: self.humidity,
2814 temp: self.temp,
2815 near_water: if self.river.is_lake()
2816 || self.river.near_river()
2817 || self.alt < CONFIG.sea_level + 6.0
2818 {
2820 1.0
2821 } else {
2822 0.0
2823 },
2824 }
2825 }
2826
2827 pub fn get_location_name(
2828 &self,
2829 index_sites: &Store<crate::site::Site>,
2830 civs_pois: &Store<PointOfInterest>,
2831 wpos2d: Vec2<i32>,
2832 ) -> Option<String> {
2833 self.sites
2834 .iter()
2835 .filter(|id| {
2836 index_sites[**id].origin.distance_squared(wpos2d) as f32
2837 <= index_sites[**id].radius().powi(2)
2838 })
2839 .min_by_key(|id| index_sites[**id].origin.distance_squared(wpos2d))
2840 .and_then(|id| Some(index_sites[*id].name()?.to_string()))
2841 .or_else(|| self.poi.map(|poi| civs_pois[poi].name.clone()))
2842 }
2843}