From 96e23ae2d425b582184219ddb3b612fce09d0ecf Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Sep 2021 18:35:43 +0100 Subject: [PATCH] Handle oceans and steep rivers gracefully, update changelog --- CHANGELOG.md | 5 ++ world/src/column/mod.rs | 116 +++++++++++++++++++++++-------------- world/src/layer/scatter.rs | 27 +++++---- world/src/sim/mod.rs | 10 +++- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a55d9a66..d511fdf01e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a crafting station icon to the crafting menu sidebar for items that could be crafted at a crafting station - Added a setting to disable the hotkey hints - Added a credits screen in the main menu which shows attributions for assets +- Shrubs, a system for spawning smaller tree-like plants into the world. +- Waterfalls +- Sailing boat (currently requires spawning in) ### Changed - Made dungeon tiers 3, 4, and 5 more common - Put date at the begining of the log file instead of the end to allow MIME type recognition - Tweaked CR and exp calculation formula +- Sprite spawn rates ### Removed @@ -27,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The menu map now properly handles dragging the map, zooming, and setting the waypoint when hovering icons - Falling through an airship in flight should no longer be possible (although many issues with airship physics remain) - Avoided black hexagons when bloom is enabled by suppressing NaN/Inf pixels during the first bloom blur pass +- Many know water generation problems ## [0.11.0] - 2021-09-11 diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index e6f54d7793..aa68944b69 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -106,7 +106,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let gradient = sim.get_gradient_approx(chunk_pos); - let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 6.0; + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 5.0; let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| { let kind = match river.river_kind { Some(kind) => kind, @@ -210,7 +210,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // Lakes get a special distance function to avoid cookie-cutter edges if matches!( downhill_chunk.river.river_kind, - Some(RiverKind::Lake { .. }) + Some(RiverKind::Lake { .. } | RiverKind::Ocean) ) { let water_chunk = posj.map(|e| e as f64); let lake_width_noise = sim @@ -262,18 +262,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }, RiverKind::Ocean => { - let ndist = wposf.distance_squared(neighbor_wpos); - let (closest_pos, closest_dist, closest_t) = (neighbor_wpos, ndist, 0.0); + let water_chunk = posj.map(|e| e as f64); + let lake_width_noise = sim + .gen_ctx + .small_nz + .get((wposf.map(|e| e as f64).div(32.0)).into_array()); + let water_aabr = Aabr { + min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0, + max: (water_chunk + 1.0) * neighbor_coef - 4.0 + lake_width_noise * 8.0, + }; + let pos = water_aabr.projected_point(wposf); ( direction, coeffs, - sim.get(closest_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { - e as i32 / sz as i32 - })) - .expect("Must already work"), - closest_t, - closest_pos, - closest_dist.sqrt(), + sim.get(posj).expect("Must already work"), + 0.5, + pos, + pos.distance(wposf), ) }, }; @@ -378,8 +383,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { default }; let res = self.min.map_or(res, |m| m.min(res)); - let res = self.max.map_or(res, |m| m.max(res)); - res + self.max.map_or(res, |m| m.max(res)) } } @@ -413,7 +417,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Lerp::lerp(a, b, t) } - let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? + // Use this to temporarily alter the sea level + let base_sea_level = CONFIG.sea_level + 0.01; // What's going on here? // @@ -437,12 +442,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // refinement and I ask that you do not take that effort lightly. let (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level) = neighbor_river_data.clone().fold( ( - WeightedSum::default().with_max(actual_sea_level), + WeightedSum::default().with_max(base_sea_level), false, - WeightedSum::default().with_max(actual_sea_level), + WeightedSum::default().with_max(base_sea_level), 10000.0f32, None, - WeightedSum::default().with_max(actual_sea_level), + WeightedSum::default().with_max(base_sea_level), ), |(mut river_water_level, mut in_river, mut lake_water_level, mut lake_dist, water_dist, mut unbounded_water_level), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { ( @@ -479,15 +484,19 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }, // Slightly wider threshold is chosen in case the lake bounds are a bit wrong - RiverKind::Lake { .. } => { - let lake_water_alt = river_water_alt( - river_chunk.alt.max(river_chunk.water_alt), - downhill_chunk.alt.max(downhill_chunk.water_alt), - river_t as f32, - is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), - ); + RiverKind::Lake { .. } | RiverKind::Ocean => { + let lake_water_alt = if matches!(kind, RiverKind::Ocean) { + base_sea_level + } else { + river_water_alt( + river_chunk.alt.max(river_chunk.water_alt), + downhill_chunk.alt.max(downhill_chunk.water_alt), + river_t as f32, + is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), + ) + }; - if river_edge_dist > 0.0 && river_width > lake_width * 0.99 /* !matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))*/ { + if river_edge_dist > 0.0 && river_width > lake_width * 0.99 { let unbounded_water_alt = lake_water_alt - ((river_edge_dist - 8.0).max(0.0) / 5.0).powf(2.0); unbounded_water_level = unbounded_water_level .with(unbounded_water_alt, 1.0 / (1.0 + river_edge_dist * 5.0)) @@ -506,7 +515,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist)); } }, - RiverKind::Ocean => {}, }; let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32; @@ -517,7 +525,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { (_, _) => (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level), }, ); - let unbounded_water_level = unbounded_water_level.eval_or(actual_sea_level); + let unbounded_water_level = unbounded_water_level.eval_or(base_sea_level); // Calculate a final, canonical altitude for the water in this column by // combining and clamping the attributes we found while iterating over // nearby bodies of water. @@ -528,11 +536,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .filter(|_| lake_dist <= 0.0 || in_river), ) { (Some(r), Some(l)) => r.max(l), - (r, l) => r - .or(l) - .unwrap_or(actual_sea_level) - .max(unbounded_water_level), - }; + (r, l) => r.or(l).unwrap_or(base_sea_level).max(unbounded_water_level), + } + .max(base_sea_level); let riverless_alt = alt; @@ -586,7 +592,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ); Some((river_water_alt, cross_section.y as f32, None)) }, - RiverKind::Lake { .. } => { + RiverKind::Lake { .. } | RiverKind::Ocean => { let lake_water_alt = river_water_alt( river_chunk.alt.max(river_chunk.water_alt), downhill_chunk.alt.max(downhill_chunk.water_alt), @@ -604,8 +610,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let min_alt = Lerp::lerp( riverless_alt, lake_water_alt, - ((river_dist - 8.5) / (river_width * 0.5 - 8.5).max(0.01)) - .clamped(0.0, 1.0) as f32, + ((river_dist / (river_width * 0.5) - 0.5) * 2.0).clamped(0.0, 1.0) + as f32, ); Some(( @@ -618,7 +624,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(min_alt), )) }, - RiverKind::Ocean => None, }; const BANK_STRENGTH: f32 = 100.0; @@ -631,7 +636,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .cos() .add(1.0) .mul(0.5); - // Waterfalls 'boost' the depth of the river to prevent artifacts + // Waterfalls 'boost' the depth of the river to prevent artifacts. This + // is also necessary when rivers become very + // steep without explicitly being waterfalls. // TODO: Come up with a more principled way of doing this without // guessing magic numbers let waterfall_boost = @@ -640,7 +647,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) / 20.0 } else { - 0.0 + // Handle very steep rivers gracefully + (river_chunk.alt - downhill_chunk.alt).max(0.0) * 2.0 + / TerrainChunkSize::RECT_SIZE.x as f32 }; let riverbed_depth = near_centre * water_depth + MIN_DEPTH + waterfall_boost; @@ -666,16 +675,28 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, - (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), + power((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64, 2.0) + as f32, ); let alt = alt.with(water_alt + GORGE, weight); - let alt = if lake_dist > 0.0 && water_level < unbounded_water_level { - alt.with_max(unbounded_water_level) + let alt = if matches!(kind, RiverKind::Ocean) { + alt + } else if (0.0..1.5).contains(&river_edge_dist) + && water_dist.map_or(false, |d| d >= 0.0) + { + alt.with_max(water_alt + GORGE) } else { alt }; - alt + + if matches!(kind, RiverKind::Ocean) { + alt + } else if lake_dist > 0.0 && water_level < unbounded_water_level { + alt.with_max(unbounded_water_level) + } else { + alt + } } } else { alt @@ -684,7 +705,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { (_, _) => alt, }, ); - let alt = alt.eval_or(riverless_alt); + let alt = alt + .eval_or(riverless_alt) + .max(if water_dist.map_or(true, |d| d > 0.0) { + // Terrain below sea level breaks things, so force it to never happen + base_sea_level + 0.5 + } else { + f32::MIN + }); let riverless_alt_delta = (sim.gen_ctx.small_nz.get( (wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(), @@ -1024,7 +1052,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)), // Land ground, - ((alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0), + ((alt - base_sea_level) / 12.0).clamped(0.0, 1.0), ), surface_veg, ), diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 5d90140600..01fef8e36a 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -393,7 +393,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 300.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 18.0 { 1.0 @@ -408,7 +408,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ( MUSH_FACT * 600.0 - * if col.water_level < CONFIG.sea_level && (col.water_level - col.alt) < 3.0 { + * if col.water_level <= CONFIG.sea_level && (col.water_level - col.alt) < 3.0 { 1.0 } else { 0.0 @@ -422,7 +422,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 50.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0 { 1.0 @@ -438,7 +438,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, 1.0, 0.95) * MUSH_FACT * 50.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0 { 1.0 @@ -453,7 +453,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ( MUSH_FACT * 250.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0 { 1.0 @@ -468,7 +468,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ( MUSH_FACT * 250.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0 { 1.0 @@ -484,7 +484,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, 1.0, 0.95) * MUSH_FACT * 500.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0 { 1.0 @@ -500,7 +500,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 125.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0 { 1.0 @@ -516,7 +516,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 220.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0 { 1.0 @@ -532,7 +532,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, CONFIG.temperate_temp, 0.7) * MUSH_FACT * 300.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 3.0 { 1.0 @@ -548,7 +548,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, 1.0, 0.9) * MUSH_FACT * 160.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0 { 1.0 @@ -564,7 +564,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { close(col.temp, 1.0, 0.9) * MUSH_FACT * 120.0 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0 { 1.0 @@ -579,7 +579,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ( (c.rockiness - 0.5).max(0.0) * 1.0e-3 - * if col.water_level < CONFIG.sea_level + * if col.water_level <= CONFIG.sea_level && col.alt < col.water_level - DEPTH_WATER_NORM + 20.0 { 1.0 @@ -604,7 +604,6 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ]; canvas.foreach_col(|canvas, wpos2d, col| { - // TODO: Why do we need to add 1.0 here? Idk... let underwater = col.water_level.floor() > col.alt; let kind = scatter diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 0e430315b3..01e9984374 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2324,11 +2324,15 @@ impl SimChunk { const SOIL_SCALE: f32 = 16.0; let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt(); - // Prevent dunes pushing the altitude underwater - if alt + dune + soil < water_alt { + let warp_factor = ((alt - CONFIG.sea_level) / 16.0).clamped(0.0, 1.0); + + let warp = (dune + soil) * warp_factor; + + // Prevent warping pushing the altitude underwater + if alt + warp < water_alt { alt } else { - alt + dune + soil + alt + warp } };