1use crate::{
2 CONFIG, IndexRef, Land,
3 all::ForestKind,
4 sim::{Path, RiverKind, SimChunk, WorldSim, local_cells},
5 site::SpawnRules,
6 util::{RandomField, RandomPerm, Sampler},
7};
8use common::{
9 calendar::{Calendar, CalendarEvent},
10 terrain::{
11 CoordinateConversions, TerrainChunkSize, quadratic_nearest_point, river_spline_coeffs,
12 uniform_idx_as_vec2, vec2_as_uniform_idx,
13 },
14 vol::RectVolSize,
15};
16use noise::NoiseFn;
17use rand::seq::IndexedRandom;
18use serde::Deserialize;
19use std::ops::{Add, Div, Mul, Sub};
20use tracing::error;
21use vek::*;
22
23pub struct ColumnGen<'a> {
24 pub sim: &'a WorldSim,
25}
26
27#[derive(Deserialize)]
28pub struct Colors {
29 pub cold_grass: (f32, f32, f32),
30 pub warm_grass: (f32, f32, f32),
31 pub dark_grass: (f32, f32, f32),
32 pub wet_grass: (f32, f32, f32),
33 pub cold_stone: (f32, f32, f32),
34 pub hot_stone: (f32, f32, f32),
35 pub warm_stone: (f32, f32, f32),
36 pub beach_sand: (f32, f32, f32),
37 pub desert_sand: (f32, f32, f32),
38 pub snow: (f32, f32, f32),
39 pub snow_moss: (f32, f32, f32),
40
41 pub stone_col: (u8, u8, u8),
42
43 pub dirt_low: (f32, f32, f32),
44 pub dirt_high: (f32, f32, f32),
45
46 pub snow_high: (f32, f32, f32),
47 pub warm_stone_high: (f32, f32, f32),
48
49 pub grass_high: (f32, f32, f32),
50 pub tropical_high: (f32, f32, f32),
51 pub mesa_layers: Vec<(f32, f32, f32)>,
52}
53
54fn power(x: f64, t: f64) -> f64 {
56 if x < 0.5 {
57 (2.0 * x).powf(t) / 2.0
58 } else {
59 1.0 - (-2.0 * x + 2.0).powf(t) / 2.0
60 }
61}
62
63impl<'a> ColumnGen<'a> {
64 pub fn new(sim: &'a WorldSim) -> Self { Self { sim } }
65}
66
67impl<'a> Sampler<'a> for ColumnGen<'a> {
68 type Index = (Vec2<i32>, IndexRef<'a>, Option<&'a Calendar>);
69 type Sample = Option<ColumnSample<'a>>;
70
71 fn get(&self, (wpos, index, calendar): Self::Index) -> Option<ColumnSample<'a>> {
72 let wposf = wpos.map(|e| e as f64);
73 let chunk_pos = wpos.wpos_to_cpos();
74
75 let sim = &self.sim;
76
77 let wposf_turb = wposf; let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
84 let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
85 let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
86 let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
87 let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
88 let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
89 let near_water =
90 sim.get_interpolated(
91 wpos,
92 |chunk| if chunk.river.near_water() { 1.0 } else { 0.0 },
93 )?;
94 let water_vel = sim.get_interpolated(wpos, |chunk| {
95 if chunk.river.river_kind.is_some() {
96 chunk.river.velocity
97 } else {
98 Vec3::zero()
99 }
100 })?;
101 let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
102 let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
103 let sim_chunk = sim.get(chunk_pos)?;
104 let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
105 let my_chunk_idx = vec2_as_uniform_idx(self.sim.map_size_lg(), chunk_pos);
106 let neighbor_river_data =
107 local_cells(self.sim.map_size_lg(), my_chunk_idx).filter_map(|neighbor_idx: usize| {
108 let neighbor_pos = uniform_idx_as_vec2(self.sim.map_size_lg(), neighbor_idx);
109 let neighbor_chunk = sim.get(neighbor_pos)?;
110 Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river))
111 });
112
113 const SAMP_RES: i32 = 8;
114 let altx0 = sim.get_interpolated(wpos - Vec2::new(1, 0) * SAMP_RES, |chunk| chunk.alt);
115 let altx1 = sim.get_interpolated(wpos + Vec2::new(1, 0) * SAMP_RES, |chunk| chunk.alt);
116 let alty0 = sim.get_interpolated(wpos - Vec2::new(0, 1) * SAMP_RES, |chunk| chunk.alt);
117 let alty1 = sim.get_interpolated(wpos + Vec2::new(0, 1) * SAMP_RES, |chunk| chunk.alt);
118 let gradient =
119 altx0
120 .zip(altx1)
121 .zip_with(alty0.zip(alty1), |(altx0, altx1), (alty0, alty1)| {
122 Vec2::new(altx1 - altx0, alty1 - alty0)
123 .map(f32::abs)
124 .magnitude()
125 / SAMP_RES as f32
126 });
127
128 let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
129
130 let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
131 .powi(3)
132 .add(1.0)
133 .mul(0.5);
134 let marble_mid = (sim.gen_ctx.hill_nz.get((wposf3d.div(12.0)).into_array()) as f32)
135 .mul(0.75)
136 .add(1.0)
137 .mul(0.5);
138 let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32)
140 .mul(0.75)
141 .add(1.0)
142 .mul(0.5);
143 let marble_mixed = marble
144 .add(marble_mid.sub(0.5).mul(0.5))
145 .add(marble_small.sub(0.5).mul(0.25));
146
147 let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * 2.0f64.sqrt()) + 6.0;
148 let neighbor_river_data = neighbor_river_data
149 .map(|(posj, chunkj, river)| {
150 let kind = match river.river_kind {
151 Some(kind) => kind,
152 None => {
153 return (posj, chunkj, river, None);
154 },
155 };
156 let downhill_pos = if let Some(pos) = chunkj.downhill {
157 pos
158 } else {
159 match kind {
160 RiverKind::River { .. } => {
161 error!(?river, ?posj, "What?");
162 panic!("How can a river have no downhill?");
163 },
164 RiverKind::Lake { .. } => {
165 return (posj, chunkj, river, None);
166 },
167 RiverKind::Ocean => posj,
168 }
169 };
170 let downhill_wpos = downhill_pos.map(|e| e as f64);
171 let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
172 e.div_euclid(sz as i32)
173 });
174 let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5;
175 let direction = neighbor_wpos - downhill_wpos;
176 let river_width_min = if let RiverKind::River { cross_section } = kind {
177 cross_section.x as f64
178 } else {
179 lake_width
180 };
181 let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?");
182 let coeffs = river_spline_coeffs(
183 neighbor_wpos,
184 chunkj.river.spline_derivative,
185 downhill_wpos,
186 );
187 let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind
188 {
189 RiverKind::River { .. } => {
190 if let Some((t, pt, dist)) = quadratic_nearest_point(
191 &coeffs,
192 wposf,
193 Vec2::new(neighbor_wpos, downhill_wpos),
194 ) {
195 let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) {
196 (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos))
197 } else if dist > wposf.distance_squared(downhill_wpos) {
198 (1.0, downhill_wpos, wposf.distance_squared(downhill_wpos))
199 } else {
200 (t, pt, dist)
201 };
202 (direction, coeffs, downhill_chunk, t, pt, dist.sqrt())
203 } else {
204 let ndist = wposf.distance_squared(neighbor_wpos);
205 let ddist = wposf.distance_squared(downhill_wpos);
206 let (closest_pos, closest_dist, closest_t) = if ndist <= ddist {
207 (neighbor_wpos, ndist, 0.0)
208 } else {
209 (downhill_wpos, ddist, 1.0)
210 };
211 (
212 direction,
213 coeffs,
214 downhill_chunk,
215 closest_t,
216 closest_pos,
217 closest_dist.sqrt(),
218 )
219 }
220 },
221 RiverKind::Lake { neighbor_pass_pos } => {
222 let pass_dist = neighbor_pass_pos
223 .map2(
224 neighbor_wpos
225 .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
226 |e, (f, g)| ((e - f) / g).abs(),
227 )
228 .reduce_partial_max();
229 let spline_derivative = river.spline_derivative;
230 let neighbor_pass_pos = if pass_dist <= 1 {
231 neighbor_pass_pos
232 } else {
233 downhill_wpos.map(|e| e as i32)
234 };
235 let pass_dist = neighbor_pass_pos
236 .map2(
237 neighbor_wpos
238 .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
239 |e, (f, g)| ((e - f) / g).abs(),
240 )
241 .reduce_partial_max();
242 if pass_dist > 1 {
243 return (posj, chunkj, river, None);
244 }
245 let neighbor_pass_wpos =
246 neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5;
247 let neighbor_pass_pos = neighbor_pass_pos.wpos_to_cpos();
248 let coeffs = river_spline_coeffs(
249 neighbor_wpos,
250 spline_derivative,
251 neighbor_pass_wpos,
252 );
253 let direction = neighbor_wpos - neighbor_pass_wpos;
254
255 if matches!(
257 downhill_chunk.river.river_kind,
258 Some(RiverKind::Lake { .. } | RiverKind::Ocean)
259 ) {
260 let water_chunk = posj.map(|e| e as f64);
261 let lake_width_noise =
262 sim.gen_ctx.small_nz.get(wposf.div(32.0).into_array());
263 let water_aabr = Aabr {
264 min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0,
265 max: (water_chunk + 1.0) * neighbor_coef - 4.0
266 + lake_width_noise * 8.0,
267 };
268 let pos = water_aabr.projected_point(wposf);
269 (
270 direction,
271 coeffs,
272 sim.get(neighbor_pass_pos).expect("Must already work"),
273 0.5,
274 pos,
275 pos.distance(wposf),
276 )
277 } else if let Some((t, pt, dist)) = quadratic_nearest_point(
278 &coeffs,
279 wposf,
280 Vec2::new(neighbor_wpos, neighbor_pass_wpos),
281 ) {
282 (
283 direction,
284 coeffs,
285 sim.get(neighbor_pass_pos).expect("Must already work"),
286 t,
287 pt,
288 dist.sqrt(),
289 )
290 } else {
291 let ndist = wposf.distance_squared(neighbor_wpos);
292 let (closest_pos, closest_dist, closest_t) = {
294 (neighbor_wpos, ndist, 0.0)
295 } ;
298 (
299 direction,
300 coeffs,
301 sim.get(neighbor_pass_pos).expect("Must already work"),
302 closest_t,
303 closest_pos,
304 closest_dist.sqrt(),
305 )
306 }
307 },
308 RiverKind::Ocean => {
309 let water_chunk = posj.map(|e| e as f64);
310 let lake_width_noise =
311 sim.gen_ctx.small_nz.get(wposf.div(32.0).into_array());
312 let water_aabr = Aabr {
313 min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0,
314 max: (water_chunk + 1.0) * neighbor_coef - 4.0 + lake_width_noise * 8.0,
315 };
316 let pos = water_aabr.projected_point(wposf);
317 (
318 direction,
319 coeffs,
320 sim.get(posj).expect("Must already work"),
321 0.5,
322 pos,
323 pos.distance(wposf),
324 )
325 },
326 };
327 let river_width_max = if let Some(RiverKind::River { cross_section }) =
328 downhill_chunk.river.river_kind
329 {
330 (cross_section.x as f64).min(river_width_min * 1.75)
333 } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind {
334 Lerp::lerp(cross_section.x as f64, lake_width, 0.5)
335 } else {
336 lake_width * 0.5
339 };
340 let river_width_noise =
341 (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array()))
342 .clamp(-1.0, 1.0)
343 .mul(0.5)
344 .sub(0.5);
345 let river_width = Lerp::lerp(
346 river_width_min,
347 river_width_max,
348 river_t.clamped(0.0, 1.0).powf(3.0),
349 );
350
351 let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3);
352 let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0));
359 (
360 posj,
361 chunkj,
362 river,
363 Some((
364 direction,
365 res,
366 river_width,
367 (river_t, (river_pos, coeffs), downhill_chunk),
368 )),
369 )
370 })
371 .collect::<Vec<_>>();
372
373 debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level);
374
375 #[derive(Default)]
377 struct WeightedSum<T> {
378 sum: T,
379 weight: T,
380 min: Option<T>,
381 max: Option<T>,
382 }
383 impl WeightedSum<f32> {
384 fn with(self, value: f32, weight: f32) -> Self {
386 Self {
387 sum: self.sum + value * weight,
388 weight: self.weight + weight,
389 ..self
390 }
391 }
392
393 fn with_min(self, min: f32) -> Self {
395 Self {
396 min: Some(self.min.unwrap_or(min).min(min)),
397 ..self
398 }
399 }
400
401 fn with_max(self, max: f32) -> Self {
403 Self {
404 max: Some(self.max.unwrap_or(max).max(max)),
405 ..self
406 }
407 }
408
409 fn eval(&self) -> Option<f32> {
411 if self.weight > 0.0 {
412 let res = self.sum / self.weight;
413 let res = self.min.map_or(res, |m| m.min(res));
414 let res = self.max.map_or(res, |m| m.max(res));
415 Some(res)
416 } else {
417 None
418 }
419 }
420
421 fn eval_or(&self, default: f32) -> f32 {
424 let res = if self.weight > 0.0 {
425 self.sum / self.weight
426 } else {
427 default
428 };
429 let res = self.min.map_or(res, |m| m.min(res));
430 self.max.map_or(res, |m| m.max(res))
431 }
432 }
433
434 fn is_waterfall(
436 chunk_pos: Vec2<i32>,
437 river_chunk: &SimChunk,
438 downhill_chunk: &SimChunk,
439 ) -> bool {
440 (RandomField::new(3119).chance(chunk_pos.with_z(0), 0.1)
445 || matches!(
446 downhill_chunk.river.river_kind,
447 Some(RiverKind::Lake { .. })
448 ))
449 && (river_chunk.water_alt > downhill_chunk.water_alt + 0.0)
450 }
451
452 fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 {
455 let t = if is_waterfall {
456 power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32
458 } else {
459 t
460 };
461 Lerp::lerp(a, b, t)
462 }
463
464 let base_sea_level = CONFIG.sea_level - 1.0 + 0.01;
466
467 let (
488 river_water_level,
489 in_river,
490 lake_water_level,
491 lake_dist,
492 water_dist,
493 unbounded_water_level,
494 ) = neighbor_river_data.iter().copied().fold(
495 (
496 WeightedSum::default().with_max(base_sea_level),
497 false,
498 WeightedSum::default().with_max(base_sea_level),
499 10000.0f32,
500 None,
501 WeightedSum::default().with_max(base_sea_level),
502 ),
503 |(
504 mut river_water_level,
505 mut in_river,
506 lake_water_level,
507 mut lake_dist,
508 water_dist,
509 mut unbounded_water_level,
510 ),
511 (river_chunk_idx, river_chunk, river, dist_info)| match (
512 river.river_kind,
513 dist_info,
514 ) {
515 (
516 Some(kind),
517 Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))),
518 ) => {
519 let river_dist = river_pos.distance(wposf);
521 let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32;
523 let near_center = ((river_dist / (river_width * 0.5)) as f32)
525 .min(1.0)
526 .mul(std::f32::consts::PI)
527 .cos()
528 .add(1.0)
529 .mul(0.5);
530
531 match kind {
532 RiverKind::River { .. } => {
533 let river_water_alt = river_water_alt(
536 river_chunk.alt.max(river_chunk.water_alt),
537 downhill_chunk.alt.max(downhill_chunk.water_alt),
538 river_t as f32,
539 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
540 );
541
542 river_water_level =
543 river_water_level.with(river_water_alt, near_center);
544
545 if river_edge_dist <= 0.0 {
546 in_river = true;
547 }
548 },
549 RiverKind::Lake { .. } | RiverKind::Ocean => {
552 let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
553 base_sea_level
554 } else {
555 river_water_alt(
556 river_chunk.alt.max(river_chunk.water_alt),
557 downhill_chunk.alt.max(downhill_chunk.water_alt),
558 river_t as f32,
559 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
560 )
561 };
562
563 if river_edge_dist > 0.0 && river_width > lake_width * 0.99 {
564 let unbounded_water_alt = lake_water_alt
565 - ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0);
566 unbounded_water_level = unbounded_water_level
567 .with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0));
568 }
570
571 river_water_level = river_water_level.with(lake_water_alt, near_center);
572
573 lake_dist = lake_dist.min(river_edge_dist);
574
575 let off = 0.0;
578 let len = 3.0;
579 if river_edge_dist <= off {
580 river_water_level = river_water_level.with_min(
586 lake_water_alt
587 + ((((river_dist - river_width * 0.5) as f32 + len - off)
588 .max(0.0))
589 / len)
590 .powf(1.5)
591 * 32.0,
592 );
593 }
594 },
595 };
596
597 let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32;
598 let water_dist = Some(
599 water_dist
600 .unwrap_or(river_edge_dist_unclamped)
601 .min(river_edge_dist_unclamped),
602 );
603
604 (
605 river_water_level,
606 in_river,
607 lake_water_level,
608 lake_dist,
609 water_dist,
610 unbounded_water_level,
611 )
612 },
613 (_, _) => (
614 river_water_level,
615 in_river,
616 lake_water_level,
617 lake_dist,
618 water_dist,
619 unbounded_water_level,
620 ),
621 },
622 );
623 let unbounded_water_level = unbounded_water_level.eval_or(base_sea_level);
624 let water_level = match (
628 river_water_level.eval(),
629 lake_water_level
630 .eval()
631 .filter(|_| lake_dist <= 0.0 || in_river),
632 ) {
633 (Some(r), Some(l)) => r.max(l),
634 (r, l) => r.or(l).unwrap_or(base_sea_level).max(unbounded_water_level),
635 }
636 .max(base_sea_level);
637
638 let mut spawn_rules = SpawnRules::default();
639 for site in sim_chunk.sites.iter().map(|site| &index.sites[*site]) {
640 site.spawn_rules(&mut spawn_rules, &Land::from_sim(sim), wpos);
641 }
642 let alt = alt
643 + (spawn_rules.get_preferred_alt().0 - alt)
644 * spawn_rules.get_preferred_alt().1.clamped(0.0, 1.0);
645
646 let riverless_alt = alt;
647
648 let alt = neighbor_river_data.into_iter().fold(
673 WeightedSum::default().with(riverless_alt, 1.0),
674 |alt, (river_chunk_idx, river_chunk, river, dist_info)| match (
675 river.river_kind,
676 dist_info,
677 ) {
678 (
679 Some(kind),
680 Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))),
681 ) => {
682 let river_dist = river_pos.distance(wposf);
684 let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32;
686
687 let water_alt = match kind {
688 RiverKind::River { cross_section } => {
689 let river_water_alt = river_water_alt(
691 river_chunk.alt.max(river_chunk.water_alt),
692 downhill_chunk.alt.max(downhill_chunk.water_alt),
693 river_t as f32,
694 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
695 );
696 Some((river_water_alt, cross_section.y, None))
697 },
698 RiverKind::Lake { .. } | RiverKind::Ocean => {
699 let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
700 base_sea_level
701 } else {
702 river_water_alt(
703 river_chunk.alt.max(river_chunk.water_alt),
704 downhill_chunk.alt.max(downhill_chunk.water_alt),
705 river_t as f32,
706 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
707 )
708 };
709
710 let depth = water_level
711 - Lerp::lerp(
712 riverless_alt.min(water_level),
713 water_level - 4.0,
714 0.5,
715 );
716
717 let min_alt = Lerp::lerp(
718 riverless_alt,
719 lake_water_alt,
720 ((river_dist / (river_width * 0.5) - 0.5) * 2.0).clamped(0.0, 1.0)
721 as f32,
722 );
723
724 Some((
725 lake_water_alt,
726 depth,
731 Some(min_alt),
732 ))
733 },
734 };
735
736 const BANK_STRENGTH: f32 = 100.0;
737 if let Some((water_alt, water_depth, min_alt)) = water_alt {
738 if river_edge_dist <= 0.0 {
739 const MIN_DEPTH: f32 = 1.0;
740 let near_center = ((river_dist / (river_width * 0.5)) as f32)
741 .min(1.0)
742 .mul(std::f32::consts::PI)
743 .cos()
744 .add(1.0)
745 .mul(0.5);
746 let waterfall_boost =
752 if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) {
753 (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0)
754 * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5)
755 / 20.0
756 } else {
757 (river_chunk.alt - downhill_chunk.alt).max(0.0) * 2.0
759 / TerrainChunkSize::RECT_SIZE.x as f32
760 };
761 let riverbed_depth =
762 near_center * water_depth + MIN_DEPTH + waterfall_boost;
763 let riverbed_alt = (water_alt - riverbed_depth)
766 .max(riverless_alt.min(base_sea_level - MIN_DEPTH));
767 alt.with(
768 min_alt.unwrap_or(riverbed_alt).min(riverbed_alt),
769 near_center * BANK_STRENGTH,
770 )
771 .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt))
772 } else {
773 const GORGE: f32 = 0.25;
774 const BANK_SCALE: f32 = 24.0;
775 let weight = Lerp::lerp(
780 BANK_STRENGTH
781 / (1.0
782 + (river_edge_dist - 3.0).max(0.0) * BANK_STRENGTH
783 / BANK_SCALE),
784 0.0,
785 power((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64, 2.0)
786 as f32,
787 );
788 let alt = alt.with(water_alt + GORGE, weight);
789
790 let alt = if matches!(kind, RiverKind::Ocean) {
791 alt
792 } else if (0.0..1.5).contains(&river_edge_dist)
793 && water_dist.is_some_and(|d| d >= 0.0)
794 {
795 alt.with_max(water_alt + GORGE)
796 } else {
797 alt
798 };
799
800 if matches!(kind, RiverKind::Ocean) {
801 alt
802 } else if lake_dist > 0.0 && water_level < unbounded_water_level {
803 alt.with_max(unbounded_water_level)
804 } else {
805 alt
806 }
807 }
808 } else {
809 alt
810 }
811 },
812 (_, _) => alt,
813 },
814 );
815 let alt = alt
816 .eval_or(riverless_alt)
817 .max(if water_dist.is_none_or(|d| d > 0.0) {
818 base_sea_level + 0.5
820 } else {
821 f32::MIN
822 });
823
824 let riverless_alt_delta = (sim.gen_ctx.small_nz.get(
825 (wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(),
826 ) as f32)
827 .clamp(-1.0, 1.0)
828 .abs()
829 .mul(3.0)
830 + (sim.gen_ctx.small_nz.get(
831 (wposf_turb.div(400.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64)))
832 .into_array(),
833 ) as f32)
834 .clamp(-1.0, 1.0)
835 .abs()
836 .mul(3.0);
837
838 let cliff_factor = (alt
840 + self.sim.gen_ctx.hill_nz.get(wposf.div(64.0).into_array()) as f32 * 8.0
841 + self.sim.gen_ctx.hill_nz.get(wposf.div(350.0).into_array()) as f32 * 128.0)
842 .rem_euclid(200.0)
843 / 64.0
844 - 1.0;
845 let cliff_scale =
846 ((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75)
847 + self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1)
848 .clamped(0.0, 1.0)
849 .powf(2.0);
850 let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale;
851 let cliff = if cliff_factor < 0.0 {
852 cliff_factor.abs().powf(1.5)
853 } else {
854 0.0
855 } * (1.0 - near_water * 3.0).max(0.0).powi(2);
856 let cliff_offset = cliff * cliff_height;
857 let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height;
858 let basement_sub_alt =
859 sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
860
861 let surface_rigidity = 1.0 - temp.max(0.0) * (1.0 - tree_density);
862 let surface_rigidity =
863 surface_rigidity.max(((basement_sub_alt + 3.0) / 1.5).clamped(0.0, 2.0));
864 let warp = ((marble_mid * 0.2 + marble * 0.8) * 2.0 - 1.0)
865 * (10.0 + rockiness * 15.0)
866 * gradient.unwrap_or(0.0).min(1.0)
867 * surface_rigidity;
868
869 let warp_factor = water_dist.map_or(1.0, |d| ((d - 0.0) / 64.0).clamped(0.0, 1.0));
870 let warp_factor = warp_factor * spawn_rules.max_warp;
871 let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
874 let alt = alt + riverless_alt_delta;
875 let alt = alt + warp * warp_factor;
876
877 let basement = alt + basement_sub_alt;
878
879 let mesa = 1.0f32
880 .min(30.0 / (1.0 + -basement_sub_alt.min(0.0)))
881 .min(1.0 - humidity * 4.0)
882 .min(temp)
883 .min(Lerp::lerp(-0.4, 1.0, gradient.unwrap_or(0.0)).max(0.0))
884 .max(0.0);
885
886 let rock_density = rockiness
888 + water_dist
889 .filter(|wd| *wd > 2.0)
890 .map(|wd| (1.0 - wd / 32.0).clamped(0.0, 1.0).powf(0.5) * 10.0)
891 .unwrap_or(0.0);
892
893 let temp = Lerp::lerp(
896 Lerp::lerp(temp, 0.0, 0.1),
897 temp,
898 water_dist
899 .map(|water_dist| water_dist / 20.0)
900 .unwrap_or(1.0)
901 .clamped(0.0, 1.0),
902 );
903 let humidity = Lerp::lerp(
905 Lerp::lerp(humidity, 1.0, 0.25),
906 humidity,
907 water_dist
908 .map(|water_dist| water_dist / 20.0)
909 .unwrap_or(1.0)
910 .clamped(0.0, 1.0),
911 );
912
913 let Colors {
915 cold_grass,
916 warm_grass,
917 dark_grass,
918 wet_grass,
919 cold_stone,
920 hot_stone,
921 warm_stone,
922 beach_sand,
923 desert_sand,
924 snow,
925 snow_moss,
926 stone_col,
927 dirt_low,
928 dirt_high,
929 snow_high,
930 warm_stone_high,
931 grass_high,
932 tropical_high,
933 mesa_layers,
934 } = &index.colors.column;
935
936 let cold_grass = (*cold_grass).into();
937 let warm_grass = (*warm_grass).into();
938 let dark_grass = (*dark_grass).into();
939 let wet_grass = (*wet_grass).into();
940 let cold_stone = (*cold_stone).into();
941 let hot_stone = (*hot_stone).into();
942 let warm_stone: Rgb<f32> = (*warm_stone).into();
943 let beach_sand = (*beach_sand).into();
944 let desert_sand = (*desert_sand).into();
945 let snow = (*snow).into();
946 let snow_moss = (*snow_moss).into();
947 let stone_col = (*stone_col).into();
948 let dirt_low: Rgb<f32> = (*dirt_low).into();
949 let dirt_high = (*dirt_high).into();
950 let snow_high = (*snow_high).into();
951 let warm_stone_high = (*warm_stone_high).into();
952 let grass_high = (*grass_high).into();
953 let tropical_high = (*tropical_high).into();
954
955 let dirt = Lerp::lerp(dirt_low, dirt_high, marble_mixed);
956 let tundra = Lerp::lerp(snow, snow_high, 0.4 + marble_mixed * 0.6);
957 let dead_tundra = Lerp::lerp(warm_stone, warm_stone_high, marble_mixed);
958 let cliff = Rgb::lerp(cold_stone, hot_stone, marble_mixed);
959
960 let grass = Rgb::lerp(
961 cold_grass,
962 warm_grass,
963 marble_mixed
964 .sub(0.5)
965 .add(1.0.sub(humidity).mul(0.5))
966 .powf(1.5),
967 );
968 let snow_moss = Rgb::lerp(snow_moss, cold_grass, 0.4 + marble_mixed.powf(1.5) * 0.6);
969 let moss = Rgb::lerp(dark_grass, cold_grass, marble_mixed.powf(1.5));
970 let rainforest = Rgb::lerp(wet_grass, warm_grass, marble_mixed.powf(1.5));
971 let sand = Rgb::lerp(beach_sand, desert_sand, marble_mixed);
972
973 let tropical = Rgb::lerp(
974 Rgb::lerp(
975 grass,
976 grass_high,
977 marble_small
978 .sub(0.5)
979 .mul(0.2)
980 .add(0.75.mul(1.0.sub(humidity)))
981 .powf(0.667),
982 ),
983 tropical_high,
984 marble_mixed.powf(1.5).sub(0.5).mul(4.0),
985 );
986
987 let ground = Lerp::lerp(
990 Lerp::lerp(
991 dead_tundra,
992 sand,
993 temp.sub(CONFIG.snow_temp)
994 .div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
995 .mul(0.5),
996 ),
997 dirt,
998 humidity
999 .sub(CONFIG.desert_hum)
1000 .div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
1001 .mul(1.0),
1002 );
1003
1004 let sub_surface_color = Lerp::lerp(cliff, ground, alt.sub(basement).mul(0.25));
1005
1006 let ground = Rgb::lerp(
1009 ground,
1010 Rgb::lerp(
1011 Rgb::lerp(
1012 Rgb::lerp(
1013 Rgb::lerp(
1014 tundra,
1015 dirt,
1017 temp.sub(CONFIG.snow_temp)
1018 .div(CONFIG.temperate_temp.sub(CONFIG.snow_temp))
1019 .mul(1.0),
1022 ),
1023 grass,
1025 temp.sub(CONFIG.temperate_temp)
1026 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1027 .mul(4.0),
1028 ),
1029 moss,
1031 temp.sub(CONFIG.tropical_temp)
1032 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1033 .mul(1.0),
1034 ),
1035 sand,
1037 temp.sub(CONFIG.desert_temp)
1038 .div(1.0 - CONFIG.desert_temp)
1039 .mul(4.0),
1040 ),
1041 humidity
1042 .sub(CONFIG.desert_hum)
1043 .div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
1044 .mul(1.25),
1045 );
1046 let ground = Rgb::lerp(
1049 ground,
1050 Rgb::lerp(
1051 Rgb::lerp(
1052 Rgb::lerp(
1053 snow_moss,
1054 grass,
1056 temp.sub(CONFIG.temperate_temp)
1057 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1058 .mul(4.0),
1059 ),
1060 tropical,
1062 temp.sub(CONFIG.tropical_temp)
1063 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1064 .mul(1.0),
1065 ),
1066 sand,
1068 temp.sub(CONFIG.desert_temp)
1069 .div(1.0 - CONFIG.desert_temp)
1070 .mul(4.0),
1071 ),
1072 humidity
1073 .sub(CONFIG.forest_hum)
1074 .div(CONFIG.jungle_hum.sub(CONFIG.forest_hum))
1075 .mul(1.0),
1076 );
1077 let ground = Rgb::lerp(
1080 ground,
1081 Rgb::lerp(
1082 Rgb::lerp(
1083 Rgb::lerp(
1084 snow_moss,
1085 rainforest,
1087 temp.sub(CONFIG.temperate_temp)
1088 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1089 .mul(4.0),
1090 ),
1091 tropical,
1093 temp.sub(CONFIG.tropical_temp)
1094 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1095 .mul(4.0),
1096 ),
1097 sand,
1099 temp.sub(CONFIG.desert_temp)
1100 .div(1.0 - CONFIG.desert_temp)
1101 .mul(4.0),
1102 ),
1103 humidity.sub(CONFIG.jungle_hum).mul(1.0),
1104 );
1105
1106 let thematic_snow = calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas));
1108 let snow_factor = temp
1109 .sub(if thematic_snow {
1110 CONFIG.tropical_temp
1111 } else {
1112 CONFIG.snow_temp
1113 })
1114 .max(-humidity.sub(CONFIG.desert_hum))
1115 .mul(4.0)
1116 .max(-0.25)
1117 .add((gradient.unwrap_or(0.0) - 0.5).max(0.0) * 0.1)
1119 .add(((marble - 0.5) / 0.5) * 0.25)
1121 .add(((marble_mid - 0.5) / 0.5) * 0.125)
1122 .add(((marble_small - 0.5) / 0.5) * 0.0625);
1123 let snow_cover = snow_factor <= 0.0;
1124 let (alt, ground, sub_surface_color) = if snow_cover && alt > water_level {
1125 (
1127 alt + 1.0 - snow_factor.max(0.0),
1128 Rgb::lerp(snow, ground, snow_factor),
1129 Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)),
1130 )
1131 } else {
1132 (alt, ground, sub_surface_color)
1133 };
1134
1135 let ground = water_dist
1137 .map(|wd| Lerp::lerp(sub_surface_color, ground, (wd / 3.0).clamped(0.0, 1.0)))
1138 .unwrap_or(ground);
1139
1140 let (sub_surface_color, ground, alt, basement) = if mesa > 0.0 {
1141 let marble_big = (sim.gen_ctx.hill_nz.get((wposf3d.div(128.0)).into_array()) as f32)
1142 .mul(0.75)
1143 .add(1.0)
1144 .mul(0.5);
1145 let cliff_scale = 130.0;
1146 let cliff2_scale = 50.0;
1147 let cliff_offset = |scale: f32| {
1148 let x = (alt * 0.95 * (1.0 / scale) + marble_mixed * 0.07).fract() - 0.75;
1149 if x > 0.0 { x * 3.0 * scale } else { -x * scale }
1150 };
1151 let mesa_alt = alt
1152 + Lerp::lerp(
1153 cliff_offset(cliff_scale),
1154 cliff_offset(cliff2_scale),
1155 ((marble_big - 0.5) * 3.0 + (marble_mixed - 0.5) * 0.0).clamped(-0.5, 0.5)
1156 + 0.5,
1157 ) * 0.9;
1158 let alt = Lerp::lerp(alt, mesa_alt, mesa.powf(2.0) * warp_factor);
1159
1160 let idx = alt * 0.35 + (alt * 0.35 + marble * 10.0).sin();
1161 let mesa_color = Lerp::lerp(
1162 Rgb::from(
1163 mesa_layers
1164 .choose(&mut RandomPerm::new(idx as u32))
1165 .copied()
1166 .unwrap_or_default(),
1167 ),
1168 Rgb::from(
1169 mesa_layers
1170 .choose(&mut RandomPerm::new(idx as u32 + 1))
1171 .copied()
1172 .unwrap_or_default(),
1173 ),
1174 idx.fract(),
1175 );
1176
1177 let sub_surface_color = Lerp::lerp(sub_surface_color, mesa_color, mesa.powf(0.25));
1178
1179 let basement = Lerp::lerp(
1180 basement,
1181 alt,
1182 (mesa * (marble_mixed - 0.35) * 1.5).clamped(0.0, 1.0) * warp_factor,
1183 );
1184
1185 (sub_surface_color, ground, alt, basement)
1186 } else {
1187 (sub_surface_color, ground, alt, basement)
1188 };
1189
1190 let ground = Lerp::lerp(ground, sub_surface_color, marble_mid * tree_density);
1193
1194 let path = if spawn_rules.paths {
1195 sim.get_nearest_path(wpos)
1196 } else {
1197 None
1198 };
1199
1200 let ice_depth = if snow_factor < -0.25
1201 && water_vel.magnitude_squared() < (0.1f32 + marble_mid * 0.2).powi(2)
1202 {
1203 let cliff = (sim.gen_ctx.hill_nz.get((wposf3d.div(180.0)).into_array()) as f32)
1204 .add((marble_mid - 0.5) * 0.2)
1205 .abs()
1206 .powi(3)
1207 .mul(32.0);
1208 let cliff_ctrl = (sim.gen_ctx.hill_nz.get((wposf3d.div(128.0)).into_array()) as f32)
1209 .sub(0.4)
1210 .add((marble_mid - 0.5) * 0.2)
1211 .mul(32.0)
1212 .clamped(0.0, 1.0);
1213
1214 (((1.0 - Lerp::lerp(marble, Lerp::lerp(marble_mid, marble_small, 0.25), 0.5)) * 5.0
1215 - 1.5)
1216 .max(0.0)
1217 + cliff * cliff_ctrl)
1218 .min((water_level - alt).max(0.0))
1219 } else {
1220 0.0
1221 };
1222
1223 Some(ColumnSample {
1224 alt,
1225 riverless_alt,
1226 basement,
1227 chaos,
1228 water_level,
1229 warp_factor,
1230 surface_color: Rgb::lerp(
1231 sub_surface_color,
1232 Rgb::lerp(
1233 Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
1235 ground,
1237 ((alt - base_sea_level) / 12.0).clamped(0.0, 1.0),
1238 ),
1239 surface_veg,
1240 ),
1241 sub_surface_color,
1242 tree_density: if spawn_rules.trees {
1246 Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
1247 } else {
1248 0.0
1249 },
1250 forest_kind: sim_chunk.forest_kind,
1251 marble,
1252 marble_mid,
1253 marble_small,
1254 rock_density: if spawn_rules.trees { rock_density } else { 0.0 },
1255 temp,
1256 humidity,
1257 spawn_rate,
1258 stone_col,
1259 water_dist,
1260 gradient,
1261 path,
1262 snow_cover,
1263 cliff_offset,
1264 cliff_height,
1265 water_vel,
1266 ice_depth,
1267
1268 chunk: sim_chunk,
1269 })
1270 }
1271}
1272
1273#[derive(Clone)]
1274pub struct ColumnSample<'a> {
1275 pub alt: f32,
1276 pub riverless_alt: f32,
1277 pub basement: f32,
1278 pub chaos: f32,
1279 pub water_level: f32,
1280 pub warp_factor: f32,
1281 pub surface_color: Rgb<f32>,
1282 pub sub_surface_color: Rgb<f32>,
1283 pub tree_density: f32,
1284 pub forest_kind: ForestKind,
1285 pub marble: f32,
1286 pub marble_mid: f32,
1287 pub marble_small: f32,
1288 pub rock_density: f32,
1289 pub temp: f32,
1290 pub humidity: f32,
1291 pub spawn_rate: f32,
1292 pub stone_col: Rgb<u8>,
1293 pub water_dist: Option<f32>,
1294 pub gradient: Option<f32>,
1295 pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
1296 pub snow_cover: bool,
1297 pub cliff_offset: f32,
1298 pub cliff_height: f32,
1299 pub water_vel: Vec3<f32>,
1300 pub ice_depth: f32,
1301
1302 pub chunk: &'a SimChunk,
1303}
1304
1305impl ColumnSample<'_> {
1306 pub fn get_info(&self) -> ColInfo {
1307 ColInfo {
1308 alt: self.alt,
1309 riverless_alt: self.riverless_alt,
1310 basement: self.basement,
1311 cliff_offset: self.cliff_offset,
1312 cliff_height: self.cliff_height,
1313 }
1314 }
1315}
1316
1317#[derive(Clone, Default)]
1320pub struct ColInfo {
1321 pub alt: f32,
1322 pub riverless_alt: f32,
1323 pub basement: f32,
1324 pub cliff_offset: f32,
1325 pub cliff_height: f32,
1326}