1use crate::{
2 CONFIG, IndexRef,
3 all::ForestKind,
4 sim::{Cave, 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::SliceRandom;
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 let spawn_rules = sim_chunk
113 .sites
114 .iter()
115 .map(|site| index.sites[*site].spawn_rules(wpos))
116 .fold(SpawnRules::default(), |a, b| a.combine(b));
117
118 const SAMP_RES: i32 = 8;
119 let altx0 = sim.get_interpolated(wpos - Vec2::new(1, 0) * SAMP_RES, |chunk| chunk.alt);
120 let altx1 = sim.get_interpolated(wpos + Vec2::new(1, 0) * SAMP_RES, |chunk| chunk.alt);
121 let alty0 = sim.get_interpolated(wpos - Vec2::new(0, 1) * SAMP_RES, |chunk| chunk.alt);
122 let alty1 = sim.get_interpolated(wpos + Vec2::new(0, 1) * SAMP_RES, |chunk| chunk.alt);
123 let gradient =
124 altx0
125 .zip(altx1)
126 .zip_with(alty0.zip(alty1), |(altx0, altx1), (alty0, alty1)| {
127 Vec2::new(altx1 - altx0, alty1 - alty0)
128 .map(f32::abs)
129 .magnitude()
130 / SAMP_RES as f32
131 });
132
133 let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
134
135 let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
136 .powi(3)
137 .add(1.0)
138 .mul(0.5);
139 let marble_mid = (sim.gen_ctx.hill_nz.get((wposf3d.div(12.0)).into_array()) as f32)
140 .mul(0.75)
141 .add(1.0)
142 .mul(0.5);
143 let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32)
145 .mul(0.75)
146 .add(1.0)
147 .mul(0.5);
148 let marble_mixed = marble
149 .add(marble_mid.sub(0.5).mul(0.5))
150 .add(marble_small.sub(0.5).mul(0.25));
151
152 let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * 2.0f64.sqrt()) + 6.0;
153 let neighbor_river_data = neighbor_river_data
154 .map(|(posj, chunkj, river)| {
155 let kind = match river.river_kind {
156 Some(kind) => kind,
157 None => {
158 return (posj, chunkj, river, None);
159 },
160 };
161 let downhill_pos = if let Some(pos) = chunkj.downhill {
162 pos
163 } else {
164 match kind {
165 RiverKind::River { .. } => {
166 error!(?river, ?posj, "What?");
167 panic!("How can a river have no downhill?");
168 },
169 RiverKind::Lake { .. } => {
170 return (posj, chunkj, river, None);
171 },
172 RiverKind::Ocean => posj,
173 }
174 };
175 let downhill_wpos = downhill_pos.map(|e| e as f64);
176 let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
177 e.div_euclid(sz as i32)
178 });
179 let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5;
180 let direction = neighbor_wpos - downhill_wpos;
181 let river_width_min = if let RiverKind::River { cross_section } = kind {
182 cross_section.x as f64
183 } else {
184 lake_width
185 };
186 let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?");
187 let coeffs = river_spline_coeffs(
188 neighbor_wpos,
189 chunkj.river.spline_derivative,
190 downhill_wpos,
191 );
192 let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind
193 {
194 RiverKind::River { .. } => {
195 if let Some((t, pt, dist)) = quadratic_nearest_point(
196 &coeffs,
197 wposf,
198 Vec2::new(neighbor_wpos, downhill_wpos),
199 ) {
200 let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) {
201 (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos))
202 } else if dist > wposf.distance_squared(downhill_wpos) {
203 (1.0, downhill_wpos, wposf.distance_squared(downhill_wpos))
204 } else {
205 (t, pt, dist)
206 };
207 (direction, coeffs, downhill_chunk, t, pt, dist.sqrt())
208 } else {
209 let ndist = wposf.distance_squared(neighbor_wpos);
210 let ddist = wposf.distance_squared(downhill_wpos);
211 let (closest_pos, closest_dist, closest_t) = if ndist <= ddist {
212 (neighbor_wpos, ndist, 0.0)
213 } else {
214 (downhill_wpos, ddist, 1.0)
215 };
216 (
217 direction,
218 coeffs,
219 downhill_chunk,
220 closest_t,
221 closest_pos,
222 closest_dist.sqrt(),
223 )
224 }
225 },
226 RiverKind::Lake { neighbor_pass_pos } => {
227 let pass_dist = neighbor_pass_pos
228 .map2(
229 neighbor_wpos
230 .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
231 |e, (f, g)| ((e - f) / g).abs(),
232 )
233 .reduce_partial_max();
234 let spline_derivative = river.spline_derivative;
235 let neighbor_pass_pos = if pass_dist <= 1 {
236 neighbor_pass_pos
237 } else {
238 downhill_wpos.map(|e| e as i32)
239 };
240 let pass_dist = neighbor_pass_pos
241 .map2(
242 neighbor_wpos
243 .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
244 |e, (f, g)| ((e - f) / g).abs(),
245 )
246 .reduce_partial_max();
247 if pass_dist > 1 {
248 return (posj, chunkj, river, None);
249 }
250 let neighbor_pass_wpos =
251 neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5;
252 let neighbor_pass_pos = neighbor_pass_pos.wpos_to_cpos();
253 let coeffs = river_spline_coeffs(
254 neighbor_wpos,
255 spline_derivative,
256 neighbor_pass_wpos,
257 );
258 let direction = neighbor_wpos - neighbor_pass_wpos;
259
260 if matches!(
262 downhill_chunk.river.river_kind,
263 Some(RiverKind::Lake { .. } | RiverKind::Ocean)
264 ) {
265 let water_chunk = posj.map(|e| e as f64);
266 let lake_width_noise =
267 sim.gen_ctx.small_nz.get(wposf.div(32.0).into_array());
268 let water_aabr = Aabr {
269 min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0,
270 max: (water_chunk + 1.0) * neighbor_coef - 4.0
271 + lake_width_noise * 8.0,
272 };
273 let pos = water_aabr.projected_point(wposf);
274 (
275 direction,
276 coeffs,
277 sim.get(neighbor_pass_pos).expect("Must already work"),
278 0.5,
279 pos,
280 pos.distance(wposf),
281 )
282 } else if let Some((t, pt, dist)) = quadratic_nearest_point(
283 &coeffs,
284 wposf,
285 Vec2::new(neighbor_wpos, neighbor_pass_wpos),
286 ) {
287 (
288 direction,
289 coeffs,
290 sim.get(neighbor_pass_pos).expect("Must already work"),
291 t,
292 pt,
293 dist.sqrt(),
294 )
295 } else {
296 let ndist = wposf.distance_squared(neighbor_wpos);
297 let (closest_pos, closest_dist, closest_t) = {
299 (neighbor_wpos, ndist, 0.0)
300 } ;
303 (
304 direction,
305 coeffs,
306 sim.get(neighbor_pass_pos).expect("Must already work"),
307 closest_t,
308 closest_pos,
309 closest_dist.sqrt(),
310 )
311 }
312 },
313 RiverKind::Ocean => {
314 let water_chunk = posj.map(|e| e as f64);
315 let lake_width_noise =
316 sim.gen_ctx.small_nz.get(wposf.div(32.0).into_array());
317 let water_aabr = Aabr {
318 min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0,
319 max: (water_chunk + 1.0) * neighbor_coef - 4.0 + lake_width_noise * 8.0,
320 };
321 let pos = water_aabr.projected_point(wposf);
322 (
323 direction,
324 coeffs,
325 sim.get(posj).expect("Must already work"),
326 0.5,
327 pos,
328 pos.distance(wposf),
329 )
330 },
331 };
332 let river_width_max = if let Some(RiverKind::River { cross_section }) =
333 downhill_chunk.river.river_kind
334 {
335 (cross_section.x as f64).min(river_width_min * 1.75)
338 } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind {
339 Lerp::lerp(cross_section.x as f64, lake_width, 0.5)
340 } else {
341 lake_width * 0.5
344 };
345 let river_width_noise =
346 (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array()))
347 .clamp(-1.0, 1.0)
348 .mul(0.5)
349 .sub(0.5);
350 let river_width = Lerp::lerp(
351 river_width_min,
352 river_width_max,
353 river_t.clamped(0.0, 1.0).powf(3.0),
354 );
355
356 let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3);
357 let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0));
364 (
365 posj,
366 chunkj,
367 river,
368 Some((
369 direction,
370 res,
371 river_width,
372 (river_t, (river_pos, coeffs), downhill_chunk),
373 )),
374 )
375 })
376 .collect::<Vec<_>>();
377
378 debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level);
379
380 #[derive(Default)]
382 struct WeightedSum<T> {
383 sum: T,
384 weight: T,
385 min: Option<T>,
386 max: Option<T>,
387 }
388 impl WeightedSum<f32> {
389 fn with(self, value: f32, weight: f32) -> Self {
391 Self {
392 sum: self.sum + value * weight,
393 weight: self.weight + weight,
394 ..self
395 }
396 }
397
398 fn with_min(self, min: f32) -> Self {
400 Self {
401 min: Some(self.min.unwrap_or(min).min(min)),
402 ..self
403 }
404 }
405
406 fn with_max(self, max: f32) -> Self {
408 Self {
409 max: Some(self.max.unwrap_or(max).max(max)),
410 ..self
411 }
412 }
413
414 fn eval(&self) -> Option<f32> {
416 if self.weight > 0.0 {
417 let res = self.sum / self.weight;
418 let res = self.min.map_or(res, |m| m.min(res));
419 let res = self.max.map_or(res, |m| m.max(res));
420 Some(res)
421 } else {
422 None
423 }
424 }
425
426 fn eval_or(&self, default: f32) -> f32 {
429 let res = if self.weight > 0.0 {
430 self.sum / self.weight
431 } else {
432 default
433 };
434 let res = self.min.map_or(res, |m| m.min(res));
435 self.max.map_or(res, |m| m.max(res))
436 }
437 }
438
439 fn is_waterfall(
441 chunk_pos: Vec2<i32>,
442 river_chunk: &SimChunk,
443 downhill_chunk: &SimChunk,
444 ) -> bool {
445 (RandomField::new(3119).chance(chunk_pos.with_z(0), 0.1)
450 || matches!(
451 downhill_chunk.river.river_kind,
452 Some(RiverKind::Lake { .. })
453 ))
454 && (river_chunk.water_alt > downhill_chunk.water_alt + 0.0)
455 }
456
457 fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 {
460 let t = if is_waterfall {
461 power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32
463 } else {
464 t
465 };
466 Lerp::lerp(a, b, t)
467 }
468
469 let base_sea_level = CONFIG.sea_level - 1.0 + 0.01;
471
472 let (
493 river_water_level,
494 in_river,
495 lake_water_level,
496 lake_dist,
497 water_dist,
498 unbounded_water_level,
499 ) = neighbor_river_data.iter().copied().fold(
500 (
501 WeightedSum::default().with_max(base_sea_level),
502 false,
503 WeightedSum::default().with_max(base_sea_level),
504 10000.0f32,
505 None,
506 WeightedSum::default().with_max(base_sea_level),
507 ),
508 |(
509 mut river_water_level,
510 mut in_river,
511 lake_water_level,
512 mut lake_dist,
513 water_dist,
514 mut unbounded_water_level,
515 ),
516 (river_chunk_idx, river_chunk, river, dist_info)| match (
517 river.river_kind,
518 dist_info,
519 ) {
520 (
521 Some(kind),
522 Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))),
523 ) => {
524 let river_dist = river_pos.distance(wposf);
526 let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32;
528 let near_center = ((river_dist / (river_width * 0.5)) as f32)
530 .min(1.0)
531 .mul(std::f32::consts::PI)
532 .cos()
533 .add(1.0)
534 .mul(0.5);
535
536 match kind {
537 RiverKind::River { .. } => {
538 let river_water_alt = river_water_alt(
541 river_chunk.alt.max(river_chunk.water_alt),
542 downhill_chunk.alt.max(downhill_chunk.water_alt),
543 river_t as f32,
544 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
545 );
546
547 river_water_level =
548 river_water_level.with(river_water_alt, near_center);
549
550 if river_edge_dist <= 0.0 {
551 in_river = true;
552 }
553 },
554 RiverKind::Lake { .. } | RiverKind::Ocean => {
557 let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
558 base_sea_level
559 } else {
560 river_water_alt(
561 river_chunk.alt.max(river_chunk.water_alt),
562 downhill_chunk.alt.max(downhill_chunk.water_alt),
563 river_t as f32,
564 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
565 )
566 };
567
568 if river_edge_dist > 0.0 && river_width > lake_width * 0.99 {
569 let unbounded_water_alt = lake_water_alt
570 - ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0);
571 unbounded_water_level = unbounded_water_level
572 .with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0));
573 }
575
576 river_water_level = river_water_level.with(lake_water_alt, near_center);
577
578 lake_dist = lake_dist.min(river_edge_dist);
579
580 let off = 0.0;
583 let len = 3.0;
584 if river_edge_dist <= off {
585 river_water_level = river_water_level.with_min(
591 lake_water_alt
592 + ((((river_dist - river_width * 0.5) as f32 + len - off)
593 .max(0.0))
594 / len)
595 .powf(1.5)
596 * 32.0,
597 );
598 }
599 },
600 };
601
602 let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32;
603 let water_dist = Some(
604 water_dist
605 .unwrap_or(river_edge_dist_unclamped)
606 .min(river_edge_dist_unclamped),
607 );
608
609 (
610 river_water_level,
611 in_river,
612 lake_water_level,
613 lake_dist,
614 water_dist,
615 unbounded_water_level,
616 )
617 },
618 (_, _) => (
619 river_water_level,
620 in_river,
621 lake_water_level,
622 lake_dist,
623 water_dist,
624 unbounded_water_level,
625 ),
626 },
627 );
628 let unbounded_water_level = unbounded_water_level.eval_or(base_sea_level);
629 let water_level = match (
633 river_water_level.eval(),
634 lake_water_level
635 .eval()
636 .filter(|_| lake_dist <= 0.0 || in_river),
637 ) {
638 (Some(r), Some(l)) => r.max(l),
639 (r, l) => r.or(l).unwrap_or(base_sea_level).max(unbounded_water_level),
640 }
641 .max(base_sea_level);
642
643 let riverless_alt = alt;
644
645 let alt = neighbor_river_data.into_iter().fold(
670 WeightedSum::default().with(riverless_alt, 1.0),
671 |alt, (river_chunk_idx, river_chunk, river, dist_info)| match (
672 river.river_kind,
673 dist_info,
674 ) {
675 (
676 Some(kind),
677 Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))),
678 ) => {
679 let river_dist = river_pos.distance(wposf);
681 let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32;
683
684 let water_alt = match kind {
685 RiverKind::River { cross_section } => {
686 let river_water_alt = river_water_alt(
688 river_chunk.alt.max(river_chunk.water_alt),
689 downhill_chunk.alt.max(downhill_chunk.water_alt),
690 river_t as f32,
691 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
692 );
693 Some((river_water_alt, cross_section.y, None))
694 },
695 RiverKind::Lake { .. } | RiverKind::Ocean => {
696 let lake_water_alt = if matches!(kind, RiverKind::Ocean) {
697 base_sea_level
698 } else {
699 river_water_alt(
700 river_chunk.alt.max(river_chunk.water_alt),
701 downhill_chunk.alt.max(downhill_chunk.water_alt),
702 river_t as f32,
703 is_waterfall(river_chunk_idx, river_chunk, downhill_chunk),
704 )
705 };
706
707 let depth = water_level
708 - Lerp::lerp(
709 riverless_alt.min(water_level),
710 water_level - 4.0,
711 0.5,
712 );
713
714 let min_alt = Lerp::lerp(
715 riverless_alt,
716 lake_water_alt,
717 ((river_dist / (river_width * 0.5) - 0.5) * 2.0).clamped(0.0, 1.0)
718 as f32,
719 );
720
721 Some((
722 lake_water_alt,
723 depth,
728 Some(min_alt),
729 ))
730 },
731 };
732
733 const BANK_STRENGTH: f32 = 100.0;
734 if let Some((water_alt, water_depth, min_alt)) = water_alt {
735 if river_edge_dist <= 0.0 {
736 const MIN_DEPTH: f32 = 1.0;
737 let near_center = ((river_dist / (river_width * 0.5)) as f32)
738 .min(1.0)
739 .mul(std::f32::consts::PI)
740 .cos()
741 .add(1.0)
742 .mul(0.5);
743 let waterfall_boost =
749 if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) {
750 (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0)
751 * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5)
752 / 20.0
753 } else {
754 (river_chunk.alt - downhill_chunk.alt).max(0.0) * 2.0
756 / TerrainChunkSize::RECT_SIZE.x as f32
757 };
758 let riverbed_depth =
759 near_center * water_depth + MIN_DEPTH + waterfall_boost;
760 let riverbed_alt = (water_alt - riverbed_depth)
763 .max(riverless_alt.min(base_sea_level - MIN_DEPTH));
764 alt.with(
765 min_alt.unwrap_or(riverbed_alt).min(riverbed_alt),
766 near_center * BANK_STRENGTH,
767 )
768 .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt))
769 } else {
770 const GORGE: f32 = 0.25;
771 const BANK_SCALE: f32 = 24.0;
772 let weight = Lerp::lerp(
777 BANK_STRENGTH
778 / (1.0
779 + (river_edge_dist - 3.0).max(0.0) * BANK_STRENGTH
780 / BANK_SCALE),
781 0.0,
782 power((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64, 2.0)
783 as f32,
784 );
785 let alt = alt.with(water_alt + GORGE, weight);
786
787 let alt = if matches!(kind, RiverKind::Ocean) {
788 alt
789 } else if (0.0..1.5).contains(&river_edge_dist)
790 && water_dist.is_some_and(|d| d >= 0.0)
791 {
792 alt.with_max(water_alt + GORGE)
793 } else {
794 alt
795 };
796
797 if matches!(kind, RiverKind::Ocean) {
798 alt
799 } else if lake_dist > 0.0 && water_level < unbounded_water_level {
800 alt.with_max(unbounded_water_level)
801 } else {
802 alt
803 }
804 }
805 } else {
806 alt
807 }
808 },
809 (_, _) => alt,
810 },
811 );
812 let alt = alt
813 .eval_or(riverless_alt)
814 .max(if water_dist.is_none_or(|d| d > 0.0) {
815 base_sea_level + 0.5
817 } else {
818 f32::MIN
819 });
820
821 let riverless_alt_delta = (sim.gen_ctx.small_nz.get(
822 (wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(),
823 ) as f32)
824 .clamp(-1.0, 1.0)
825 .abs()
826 .mul(3.0)
827 + (sim.gen_ctx.small_nz.get(
828 (wposf_turb.div(400.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64)))
829 .into_array(),
830 ) as f32)
831 .clamp(-1.0, 1.0)
832 .abs()
833 .mul(3.0);
834
835 let cliff_factor = (alt
837 + self.sim.gen_ctx.hill_nz.get(wposf.div(64.0).into_array()) as f32 * 8.0
838 + self.sim.gen_ctx.hill_nz.get(wposf.div(350.0).into_array()) as f32 * 128.0)
839 .rem_euclid(200.0)
840 / 64.0
841 - 1.0;
842 let cliff_scale =
843 ((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75)
844 + self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1)
845 .clamped(0.0, 1.0)
846 .powf(2.0);
847 let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale;
848 let cliff = if cliff_factor < 0.0 {
849 cliff_factor.abs().powf(1.5)
850 } else {
851 0.0
852 } * (1.0 - near_water * 3.0).max(0.0).powi(2);
853 let cliff_offset = cliff * cliff_height;
854 let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height;
855 let basement_sub_alt =
856 sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
857
858 let warp_factor = water_dist.map_or(1.0, |d| ((d - 0.0) / 64.0).clamped(0.0, 1.0));
859
860 let warp_factor = warp_factor * spawn_rules.max_warp;
864
865 let surface_rigidity = 1.0 - temp.max(0.0) * (1.0 - tree_density);
866 let surface_rigidity =
867 surface_rigidity.max(((basement_sub_alt + 3.0) / 1.5).clamped(0.0, 2.0));
868 let warp = ((marble_mid * 0.2 + marble * 0.8) * 2.0 - 1.0)
869 * (10.0 + rockiness * 15.0)
870 * gradient.unwrap_or(0.0).min(1.0)
871 * surface_rigidity
872 * warp_factor;
873
874 let mesa = 1.0f32
875 .min(30.0 / (1.0 + -basement_sub_alt.min(0.0)))
876 .min(1.0 - humidity * 4.0)
877 .min(temp)
878 .min(Lerp::lerp(-0.4, 1.0, gradient.unwrap_or(0.0)).max(0.0))
879 .max(0.0);
880
881 let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
882 let alt = alt + riverless_alt_delta + warp;
883 let basement = alt + basement_sub_alt;
884 let rock_density = rockiness
886 + water_dist
887 .filter(|wd| *wd > 2.0)
888 .map(|wd| (1.0 - wd / 32.0).clamped(0.0, 1.0).powf(0.5) * 10.0)
889 .unwrap_or(0.0);
890
891 let temp = Lerp::lerp(
894 Lerp::lerp(temp, 0.0, 0.1),
895 temp,
896 water_dist
897 .map(|water_dist| water_dist / 20.0)
898 .unwrap_or(1.0)
899 .clamped(0.0, 1.0),
900 );
901 let humidity = Lerp::lerp(
903 Lerp::lerp(humidity, 1.0, 0.25),
904 humidity,
905 water_dist
906 .map(|water_dist| water_dist / 20.0)
907 .unwrap_or(1.0)
908 .clamped(0.0, 1.0),
909 );
910
911 let Colors {
913 cold_grass,
914 warm_grass,
915 dark_grass,
916 wet_grass,
917 cold_stone,
918 hot_stone,
919 warm_stone,
920 beach_sand,
921 desert_sand,
922 snow,
923 snow_moss,
924 stone_col,
925 dirt_low,
926 dirt_high,
927 snow_high,
928 warm_stone_high,
929 grass_high,
930 tropical_high,
931 mesa_layers,
932 } = &index.colors.column;
933
934 let cold_grass = (*cold_grass).into();
935 let warm_grass = (*warm_grass).into();
936 let dark_grass = (*dark_grass).into();
937 let wet_grass = (*wet_grass).into();
938 let cold_stone = (*cold_stone).into();
939 let hot_stone = (*hot_stone).into();
940 let warm_stone: Rgb<f32> = (*warm_stone).into();
941 let beach_sand = (*beach_sand).into();
942 let desert_sand = (*desert_sand).into();
943 let snow = (*snow).into();
944 let snow_moss = (*snow_moss).into();
945 let stone_col = (*stone_col).into();
946 let dirt_low: Rgb<f32> = (*dirt_low).into();
947 let dirt_high = (*dirt_high).into();
948 let snow_high = (*snow_high).into();
949 let warm_stone_high = (*warm_stone_high).into();
950 let grass_high = (*grass_high).into();
951 let tropical_high = (*tropical_high).into();
952
953 let dirt = Lerp::lerp(dirt_low, dirt_high, marble_mixed);
954 let tundra = Lerp::lerp(snow, snow_high, 0.4 + marble_mixed * 0.6);
955 let dead_tundra = Lerp::lerp(warm_stone, warm_stone_high, marble_mixed);
956 let cliff = Rgb::lerp(cold_stone, hot_stone, marble_mixed);
957
958 let grass = Rgb::lerp(
959 cold_grass,
960 warm_grass,
961 marble_mixed
962 .sub(0.5)
963 .add(1.0.sub(humidity).mul(0.5))
964 .powf(1.5),
965 );
966 let snow_moss = Rgb::lerp(snow_moss, cold_grass, 0.4 + marble_mixed.powf(1.5) * 0.6);
967 let moss = Rgb::lerp(dark_grass, cold_grass, marble_mixed.powf(1.5));
968 let rainforest = Rgb::lerp(wet_grass, warm_grass, marble_mixed.powf(1.5));
969 let sand = Rgb::lerp(beach_sand, desert_sand, marble_mixed);
970
971 let tropical = Rgb::lerp(
972 Rgb::lerp(
973 grass,
974 grass_high,
975 marble_small
976 .sub(0.5)
977 .mul(0.2)
978 .add(0.75.mul(1.0.sub(humidity)))
979 .powf(0.667),
980 ),
981 tropical_high,
982 marble_mixed.powf(1.5).sub(0.5).mul(4.0),
983 );
984
985 let ground = Lerp::lerp(
988 Lerp::lerp(
989 dead_tundra,
990 sand,
991 temp.sub(CONFIG.snow_temp)
992 .div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
993 .mul(0.5),
994 ),
995 dirt,
996 humidity
997 .sub(CONFIG.desert_hum)
998 .div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
999 .mul(1.0),
1000 );
1001
1002 let sub_surface_color = Lerp::lerp(cliff, ground, alt.sub(basement).mul(0.25));
1003
1004 let ground = Rgb::lerp(
1007 ground,
1008 Rgb::lerp(
1009 Rgb::lerp(
1010 Rgb::lerp(
1011 Rgb::lerp(
1012 tundra,
1013 dirt,
1015 temp.sub(CONFIG.snow_temp)
1016 .div(CONFIG.temperate_temp.sub(CONFIG.snow_temp))
1017 .mul(1.0),
1020 ),
1021 grass,
1023 temp.sub(CONFIG.temperate_temp)
1024 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1025 .mul(4.0),
1026 ),
1027 moss,
1029 temp.sub(CONFIG.tropical_temp)
1030 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1031 .mul(1.0),
1032 ),
1033 sand,
1035 temp.sub(CONFIG.desert_temp)
1036 .div(1.0 - CONFIG.desert_temp)
1037 .mul(4.0),
1038 ),
1039 humidity
1040 .sub(CONFIG.desert_hum)
1041 .div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
1042 .mul(1.25),
1043 );
1044 let ground = Rgb::lerp(
1047 ground,
1048 Rgb::lerp(
1049 Rgb::lerp(
1050 Rgb::lerp(
1051 snow_moss,
1052 grass,
1054 temp.sub(CONFIG.temperate_temp)
1055 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1056 .mul(4.0),
1057 ),
1058 tropical,
1060 temp.sub(CONFIG.tropical_temp)
1061 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1062 .mul(1.0),
1063 ),
1064 sand,
1066 temp.sub(CONFIG.desert_temp)
1067 .div(1.0 - CONFIG.desert_temp)
1068 .mul(4.0),
1069 ),
1070 humidity
1071 .sub(CONFIG.forest_hum)
1072 .div(CONFIG.jungle_hum.sub(CONFIG.forest_hum))
1073 .mul(1.0),
1074 );
1075 let ground = Rgb::lerp(
1078 ground,
1079 Rgb::lerp(
1080 Rgb::lerp(
1081 Rgb::lerp(
1082 snow_moss,
1083 rainforest,
1085 temp.sub(CONFIG.temperate_temp)
1086 .div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
1087 .mul(4.0),
1088 ),
1089 tropical,
1091 temp.sub(CONFIG.tropical_temp)
1092 .div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
1093 .mul(4.0),
1094 ),
1095 sand,
1097 temp.sub(CONFIG.desert_temp)
1098 .div(1.0 - CONFIG.desert_temp)
1099 .mul(4.0),
1100 ),
1101 humidity.sub(CONFIG.jungle_hum).mul(1.0),
1102 );
1103
1104 let thematic_snow = calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas));
1106 let snow_factor = temp
1107 .sub(if thematic_snow {
1108 CONFIG.tropical_temp
1109 } else {
1110 CONFIG.snow_temp
1111 })
1112 .max(-humidity.sub(CONFIG.desert_hum))
1113 .mul(4.0)
1114 .max(-0.25)
1115 .add((gradient.unwrap_or(0.0) - 0.5).max(0.0) * 0.1)
1117 .add(((marble - 0.5) / 0.5) * 0.25)
1119 .add(((marble_mid - 0.5) / 0.5) * 0.125)
1120 .add(((marble_small - 0.5) / 0.5) * 0.0625);
1121 let snow_cover = snow_factor <= 0.0;
1122 let (alt, ground, sub_surface_color) = if snow_cover && alt > water_level {
1123 (
1125 alt + 1.0 - snow_factor.max(0.0),
1126 Rgb::lerp(snow, ground, snow_factor),
1127 Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)),
1128 )
1129 } else {
1130 (alt, ground, sub_surface_color)
1131 };
1132
1133 let ground = water_dist
1135 .map(|wd| Lerp::lerp(sub_surface_color, ground, (wd / 3.0).clamped(0.0, 1.0)))
1136 .unwrap_or(ground);
1137
1138 let (sub_surface_color, ground, alt, basement) = if mesa > 0.0 {
1139 let marble_big = (sim.gen_ctx.hill_nz.get((wposf3d.div(128.0)).into_array()) as f32)
1140 .mul(0.75)
1141 .add(1.0)
1142 .mul(0.5);
1143 let cliff_scale = 130.0;
1144 let cliff2_scale = 50.0;
1145 let cliff_offset = |scale: f32| {
1146 let x = (alt * 0.95 * (1.0 / scale) + marble_mixed * 0.07).fract() - 0.75;
1147 if x > 0.0 { x * 3.0 * scale } else { -x * scale }
1148 };
1149 let mesa_alt = alt
1150 + Lerp::lerp(
1151 cliff_offset(cliff_scale),
1152 cliff_offset(cliff2_scale),
1153 ((marble_big - 0.5) * 3.0 + (marble_mixed - 0.5) * 0.0).clamped(-0.5, 0.5)
1154 + 0.5,
1155 ) * 0.9;
1156 let alt = Lerp::lerp(alt, mesa_alt, mesa.powf(2.0) * warp_factor);
1157
1158 let idx = alt * 0.35 + (alt * 0.35 + marble * 10.0).sin();
1159 let mesa_color = Lerp::lerp(
1160 Rgb::from(
1161 mesa_layers
1162 .choose(&mut RandomPerm::new(idx as u32))
1163 .copied()
1164 .unwrap_or_default(),
1165 ),
1166 Rgb::from(
1167 mesa_layers
1168 .choose(&mut RandomPerm::new(idx as u32 + 1))
1169 .copied()
1170 .unwrap_or_default(),
1171 ),
1172 idx.fract(),
1173 );
1174
1175 let sub_surface_color = Lerp::lerp(sub_surface_color, mesa_color, mesa.powf(0.25));
1176
1177 let basement = Lerp::lerp(
1178 basement,
1179 alt,
1180 (mesa * (marble_mixed - 0.35) * 1.5).clamped(0.0, 1.0) * warp_factor,
1181 );
1182
1183 (sub_surface_color, ground, alt, basement)
1184 } else {
1185 (sub_surface_color, ground, alt, basement)
1186 };
1187
1188 let ground = Lerp::lerp(ground, sub_surface_color, marble_mid * tree_density);
1191
1192 let path = if spawn_rules.paths {
1193 sim.get_nearest_path(wpos)
1194 } else {
1195 None
1196 };
1197 let cave = sim.get_nearest_cave(wpos);
1198
1199 let ice_depth = if snow_factor < -0.25
1200 && water_vel.magnitude_squared() < (0.1f32 + marble_mid * 0.2).powi(2)
1201 {
1202 let cliff = (sim.gen_ctx.hill_nz.get((wposf3d.div(180.0)).into_array()) as f32)
1203 .add((marble_mid - 0.5) * 0.2)
1204 .abs()
1205 .powi(3)
1206 .mul(32.0);
1207 let cliff_ctrl = (sim.gen_ctx.hill_nz.get((wposf3d.div(128.0)).into_array()) as f32)
1208 .sub(0.4)
1209 .add((marble_mid - 0.5) * 0.2)
1210 .mul(32.0)
1211 .clamped(0.0, 1.0);
1212
1213 (((1.0 - Lerp::lerp(marble, Lerp::lerp(marble_mid, marble_small, 0.25), 0.5)) * 5.0
1214 - 1.5)
1215 .max(0.0)
1216 + cliff * cliff_ctrl)
1217 .min((water_level - alt).max(0.0))
1218 } else {
1219 0.0
1220 };
1221
1222 Some(ColumnSample {
1223 alt,
1224 riverless_alt,
1225 basement,
1226 chaos,
1227 water_level,
1228 warp_factor,
1229 surface_color: Rgb::lerp(
1230 sub_surface_color,
1231 Rgb::lerp(
1232 Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
1234 ground,
1236 ((alt - base_sea_level) / 12.0).clamped(0.0, 1.0),
1237 ),
1238 surface_veg,
1239 ),
1240 sub_surface_color,
1241 tree_density: if spawn_rules.trees {
1245 Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5))
1246 } else {
1247 0.0
1248 },
1249 forest_kind: sim_chunk.forest_kind,
1250 marble,
1251 marble_mid,
1252 marble_small,
1253 rock_density: if spawn_rules.trees { rock_density } else { 0.0 },
1254 temp,
1255 humidity,
1256 spawn_rate,
1257 stone_col,
1258 water_dist,
1259 gradient,
1260 path,
1261 cave,
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 cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
1297 pub snow_cover: bool,
1298 pub cliff_offset: f32,
1299 pub cliff_height: f32,
1300 pub water_vel: Vec3<f32>,
1301 pub ice_depth: f32,
1302
1303 pub chunk: &'a SimChunk,
1304}
1305
1306impl ColumnSample<'_> {
1307 pub fn get_info(&self) -> ColInfo {
1308 ColInfo {
1309 alt: self.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 basement: f32,
1323 pub cliff_offset: f32,
1324 pub cliff_height: f32,
1325}