From 263f02dc110f3c8cc17e399d8aa7b26b01d454eb Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 31 Aug 2021 13:33:23 +0100 Subject: [PATCH 01/18] Improved grass scatter --- world/src/layer/scatter.rs | 323 +++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 155 deletions(-) diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 382722c643..3b93b17dd9 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -9,7 +9,8 @@ fn close(x: f32, tgt: f32, falloff: f32) -> f32 { (1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125) } -const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate +const MUSH_FACT: f32 = 1.0e-4; // To balance things around the mushroom spawning rate +const GRASS_FACT: f32 = 1.0e-3; // To balance things around the grass spawning rate const DEPTH_WATER_NORM: f32 = 15.0; // Water depth at which regular underwater sprites start spawning pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { use SpriteKind::*; @@ -18,132 +19,132 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { let scatter: &[( _, bool, - fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32)>), + fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32, f32)>), )] = &[ - // (density, Option<(wavelen, threshold)>) + // (density, Option<(base_density_proportion, wavelen, threshold)>) // Flowers - (BlueFlower, false, |c, col| { + (BlueFlower, false, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.7).min(close( - c.humidity, + close(col.temp, CONFIG.temperate_temp, 0.7).min(close( + col.humidity, CONFIG.jungle_hum, 0.4, )) * col.tree_density * MUSH_FACT * 256.0, - Some((256.0, 0.25)), + Some((0.0, 256.0, 0.25)), ) }), - (PinkFlower, false, |c, col| { + (PinkFlower, false, |_, col| { ( - close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) + close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.1)), + Some((0.0, 100.0, 0.1)), ) }), - (PurpleFlower, false, |c, col| { + (PurpleFlower, false, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.7).min(close( - c.humidity, + close(col.temp, CONFIG.temperate_temp, 0.7).min(close( + col.humidity, CONFIG.jungle_hum, 0.4, )) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.1)), + Some((0.0, 100.0, 0.1)), ) }), - (RedFlower, false, |c, col| { + (RedFlower, false, |_, col| { ( - close(c.temp, CONFIG.tropical_temp, 0.7).min(close( - c.humidity, + close(col.temp, CONFIG.tropical_temp, 0.7).min(close( + col.humidity, CONFIG.jungle_hum, 0.4, )) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.1)), + Some((0.0, 100.0, 0.1)), ) }), - (WhiteFlower, false, |c, col| { + (WhiteFlower, false, |_, col| { ( - close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) + close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.1)), + Some((0.0, 100.0, 0.1)), ) }), - (YellowFlower, false, |c, col| { + (YellowFlower, false, |_, col| { ( - close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) + close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.1)), + Some((0.0, 100.0, 0.1)), ) }), - (Cotton, false, |c, col| { + (Cotton, false, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.7).min(close( - c.humidity, + close(col.temp, CONFIG.temperate_temp, 0.7).min(close( + col.humidity, CONFIG.jungle_hum, 0.4, )) * col.tree_density * MUSH_FACT * 75.0, - Some((256.0, 0.25)), + Some((0.0, 256.0, 0.25)), ) }), - (Sunflower, false, |c, col| { + (Sunflower, false, |_, col| { ( - close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) + close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) * col.tree_density * MUSH_FACT * 350.0, - Some((100.0, 0.15)), + Some((0.0, 100.0, 0.15)), ) }), - (WildFlax, false, |c, col| { + (WildFlax, false, |_, col| { ( - close(c.temp, 0.0, 0.7).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) + close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) * col.tree_density * MUSH_FACT * 600.0, - Some((100.0, 0.15)), + Some((0.0, 100.0, 0.15)), ) }), // Herbs and Spices - (LingonBerry, false, |c, _| { + (LingonBerry, false, |_, col| { ( - close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.5)) + close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.5)) * MUSH_FACT * 2.5, None, ) }), - (LeafyPlant, false, |c, _| { + (LeafyPlant, false, |_, col| { ( - close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.jungle_hum, 0.3)) - * MUSH_FACT + close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.3)) + * GRASS_FACT * 4.0, None, ) }), - (Fern, false, |c, _| { + (Fern, false, |_, col| { ( - close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.5)) - * MUSH_FACT + close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.5)) + * GRASS_FACT * 0.25, - Some((64.0, 0.2)), + Some((0.0, 64.0, 0.2)), ) }), - (Blueberry, false, |c, _| { + (Blueberry, false, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.5).min(close( - c.humidity, + close(col.temp, CONFIG.temperate_temp, 0.5).min(close( + col.humidity, CONFIG.forest_hum, 0.5, )) * MUSH_FACT @@ -151,191 +152,199 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { None, ) }), - (Pumpkin, false, |c, _| { + (Pumpkin, false, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.5).min(close( - c.humidity, + close(col.temp, CONFIG.temperate_temp, 0.5).min(close( + col.humidity, CONFIG.forest_hum, 0.5, )) * MUSH_FACT * 500.0, - Some((512.0, 0.05)), + Some((0.0, 512.0, 0.05)), ) }), // Collectable Objects // Only spawn twigs in temperate forests - (Twigs, false, |c, _| { + (Twigs, false, |_, col| { ( - (c.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * 0.75e-3, + (col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * 0.75e-3, None, ) }), - (Stones, false, |c, _| { - ((c.rockiness - 0.5).max(0.025) * 1.0e-3, None) + (Stones, false, |chunk, _| { + ((chunk.rockiness - 0.5).max(0.025) * 1.0e-3, None) }), - (Copper, false, |c, _| { - ((c.rockiness - 0.5).max(0.0) * 1.5e-3, None) + (Copper, false, |chunk, _| { + ((chunk.rockiness - 0.5).max(0.0) * 1.5e-3, None) }), - (Tin, false, |c, _| { - ((c.rockiness - 0.5).max(0.0) * 1.5e-3, None) + (Tin, false, |chunk, _| { + ((chunk.rockiness - 0.5).max(0.0) * 1.5e-3, None) }), // Don't spawn Mushrooms in snowy regions - (Mushroom, false, |c, _| { + (Mushroom, false, |_, col| { ( - close(c.temp, 0.3, 0.4).min(close(c.humidity, CONFIG.forest_hum, 0.35)) * MUSH_FACT, + close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.35)) + * MUSH_FACT, None, ) }), // Grass - (ShortGrass, false, |c, _| { + (ShortGrass, false, |_, col| { ( - close(c.temp, 0.2, 0.65).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.015, - None, + close(col.temp, 0.2, 0.75).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) + * GRASS_FACT + * 150.0, + Some((0.3, 64.0, 0.3)), ) }), - (MediumGrass, false, |c, _| { + (MediumGrass, false, |_, col| { ( - close(c.temp, 0.2, 0.6).min(close(c.humidity, CONFIG.jungle_hum, 0.4)) * 0.012, - None, + close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4)) + * GRASS_FACT + * 120.0, + Some((0.3, 64.0, 0.3)), ) }), - (LongGrass, false, |c, _| { + (LongGrass, false, |_, col| { ( - close(c.temp, 0.3, 0.35).min(close(c.humidity, CONFIG.jungle_hum, 0.3)) * 0.15, - Some((48.0, 0.2)), + close(col.temp, 0.3, 0.35).min(close(col.humidity, CONFIG.jungle_hum, 0.3)) + * GRASS_FACT + * 150.0, + Some((0.1, 48.0, 0.3)), ) }), // Jungle Sprites // (LongGrass, false, |c, col| { // ( - // close(c.temp, CONFIG.tropical_temp, 0.4).min(close( - // c.humidity, + // close(col.temp, CONFIG.tropical_temp, 0.4).min(close( + // col.humidity, // CONFIG.jungle_hum, // 0.6, // )) * 0.08, - // Some((60.0, 5.0)), + // Some((0.0, 60.0, 5.0)), // ) // }), /*(WheatGreen, false, |c, col| { ( - close(c.temp, 0.4, 0.2).min(close(c.humidity, CONFIG.forest_hum, 0.1)) + close(col.temp, 0.4, 0.2).min(close(col.humidity, CONFIG.forest_hum, 0.1)) * MUSH_FACT * 0.001, None, ) }),*/ - (GrassSnow, false, |c, _| { + (GrassSnow, false, |_, col| { ( - close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close( - c.humidity, + close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close( + col.humidity, CONFIG.forest_hum, 0.5, - )) * 0.01, - Some((48.0, 0.2)), + )) * GRASS_FACT + * 100.0, + Some((0.0, 48.0, 0.2)), ) }), - (Moonbell, false, |c, _| { + (Moonbell, false, |_, col| { ( - close(c.temp, CONFIG.snow_temp - 0.2, 0.4).min(close( - c.humidity, + close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close( + col.humidity, CONFIG.forest_hum, 0.5, )) * 0.003, - Some((48.0, 0.2)), + Some((0.0, 48.0, 0.2)), ) }), // Savanna Plants - (SavannaGrass, false, |c, _| { + (SavannaGrass, false, |_, col| { ( { - let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25)); - let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)); - (savanna - desert * 5.0).max(0.0) + let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25); + let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1); + (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 250.0 }, - Some((0.5, 0.2)), + Some((0.15, 64.0, 0.2)), ) }), - (TallSavannaGrass, false, |c, _| { + (TallSavannaGrass, false, |_, col| { ( { - let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25)); - let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)); - (savanna - desert * 5.0).max(0.0) + let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25); + let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1); + (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 150.0 }, - Some((12.5, 0.25)), + Some((0.1, 48.0, 0.2)), ) }), - (RedSavannaGrass, false, |c, _| { + (RedSavannaGrass, false, |_, col| { ( { - let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25)); - let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)); - (savanna - desert * 5.0).max(0.0) + let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25); + let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1); + (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 120.0 }, - Some((0.1, 0.1)), + Some((0.15, 48.0, 0.25)), ) }), - (SavannaBush, false, |c, _| { + (SavannaBush, false, |_, col| { ( { - let savanna = close(c.temp, 1.0, 0.4).min(close(c.humidity, 0.2, 0.25)); - let desert = close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)); - (savanna - desert * 5.0).max(0.0) + let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25); + let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1); + (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 40.0 }, - Some((0.08, 0.05)), + Some((0.1, 96.0, 0.15)), ) }), // Desert Plants - (DeadBush, false, |c, _| { + (DeadBush, false, |_, col| { ( - close(c.temp, 1.0, 0.95).min(close(c.humidity, 0.0, 0.3)) * MUSH_FACT * 7.5, + close(col.temp, 1.0, 0.95).min(close(col.humidity, 0.0, 0.3)) * MUSH_FACT * 7.5, None, ) }), - (Pyrebloom, false, |c, _| { + (Pyrebloom, false, |_, col| { ( - close(c.temp, 1.0, 0.95).min(close(c.humidity, 0.0, 0.3)) * MUSH_FACT * 0.35, + close(col.temp, 1.0, 0.95).min(close(col.humidity, 0.0, 0.3)) * MUSH_FACT * 0.35, None, ) }), - (LargeCactus, false, |c, _| { + (LargeCactus, false, |_, col| { ( - close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 3.5, + close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 1.5, None, ) }), - (RoundCactus, false, |c, _| { + (RoundCactus, false, |_, col| { ( - close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5, + close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5, None, ) }), - (ShortCactus, false, |c, _| { + (ShortCactus, false, |_, col| { ( - close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5, + close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5, None, ) }), - (MedFlatCactus, false, |c, _| { + (MedFlatCactus, false, |_, col| { ( - close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5, + close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5, None, ) }), - (ShortFlatCactus, false, |c, _| { + (ShortFlatCactus, false, |_, col| { ( - close(c.temp, 1.0, 0.25).min(close(c.humidity, 0.0, 0.1)) * MUSH_FACT * 5.5, + close(col.temp, 1.0, 0.25).min(close(col.humidity, 0.0, 0.1)) * MUSH_FACT * 2.5, None, ) }), - (Reed, false, |c, col| { + (Reed, false, |_, col| { ( - close(c.humidity, CONFIG.jungle_hum, 0.7) + close(col.humidity, CONFIG.jungle_hum, 0.9) * col .water_dist .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0))) .unwrap_or(0.0), - Some((128.0, 0.5)), + Some((0.2, 128.0, 0.5)), ) }), // Underwater chests @@ -374,13 +383,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((100.0, 0.15)), + Some((0.0, 100.0, 0.15)), ) }), // seagrass - (Seagrass, true, |c, col| { + (Seagrass, true, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.8) + close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 300.0 * if col.water_level < CONFIG.sea_level @@ -390,7 +399,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((150.0, 0.3)), + Some((0.0, 150.0, 0.3)), ) }), // seagrass, coastal patches @@ -403,13 +412,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((150.0, 0.4)), + Some((0.0, 150.0, 0.4)), ) }), // scattered seaweed (temperate species) - (SeaweedTemperate, true, |c, col| { + (SeaweedTemperate, true, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.8) + close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 50.0 * if col.water_level < CONFIG.sea_level @@ -419,13 +428,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((500.0, 0.75)), + Some((0.0, 500.0, 0.75)), ) }), // scattered seaweed (tropical species) - (SeaweedTropical, true, |c, col| { + (SeaweedTropical, true, |_, col| { ( - close(c.temp, 1.0, 0.95) + close(col.temp, 1.0, 0.95) * MUSH_FACT * 50.0 * if col.water_level < CONFIG.sea_level @@ -435,7 +444,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((500.0, 0.75)), + Some((0.0, 500.0, 0.75)), ) }), // Caulerpa lentillifera algae patch @@ -450,7 +459,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((100.0, 0.15)), + Some((0.0, 100.0, 0.15)), ) }), // Caulerpa prolifera algae patch @@ -465,13 +474,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((100.0, 0.15)), + Some((0.0, 100.0, 0.15)), ) }), // Mermaids' fan algae patch - (MermaidsFan, true, |c, col| { + (MermaidsFan, true, |_, col| { ( - close(c.temp, 1.0, 0.95) + close(col.temp, 1.0, 0.95) * MUSH_FACT * 500.0 * if col.water_level < CONFIG.sea_level @@ -481,13 +490,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((50.0, 0.10)), + Some((0.0, 50.0, 0.10)), ) }), // Sea anemones - (SeaAnemone, true, |c, col| { + (SeaAnemone, true, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.8) + close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 125.0 * if col.water_level < CONFIG.sea_level @@ -497,13 +506,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((100.0, 0.3)), + Some((0.0, 100.0, 0.3)), ) }), // Giant Kelp - (GiantKelp, true, |c, col| { + (GiantKelp, true, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.8) + close(col.temp, CONFIG.temperate_temp, 0.8) * MUSH_FACT * 220.0 * if col.water_level < CONFIG.sea_level @@ -513,13 +522,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((200.0, 0.4)), + Some((0.0, 200.0, 0.4)), ) }), // Bull Kelp - (BullKelp, true, |c, col| { + (BullKelp, true, |_, col| { ( - close(c.temp, CONFIG.temperate_temp, 0.7) + close(col.temp, CONFIG.temperate_temp, 0.7) * MUSH_FACT * 300.0 * if col.water_level < CONFIG.sea_level @@ -529,13 +538,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((75.0, 0.3)), + Some((0.0, 75.0, 0.3)), ) }), // Stony Corals - (StonyCoral, true, |c, col| { + (StonyCoral, true, |_, col| { ( - close(c.temp, 1.0, 0.9) + close(col.temp, 1.0, 0.9) * MUSH_FACT * 160.0 * if col.water_level < CONFIG.sea_level @@ -545,13 +554,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((120.0, 0.4)), + Some((0.0, 120.0, 0.4)), ) }), // Soft Corals - (SoftCoral, true, |c, col| { + (SoftCoral, true, |_, col| { ( - close(c.temp, 1.0, 0.9) + close(col.temp, 1.0, 0.9) * MUSH_FACT * 120.0 * if col.water_level < CONFIG.sea_level @@ -561,7 +570,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { } else { 0.0 }, - Some((120.0, 0.4)), + Some((0.0, 120.0, 0.4)), ) }), // Seashells @@ -601,9 +610,9 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { .enumerate() .find_map(|(i, (kind, is_underwater, f))| { let (density, patch) = f(canvas.chunk(), col); - let is_patch = patch - .map(|(wavelen, threshold)| { - canvas + let density = patch + .map(|(base_density_prop, wavelen, threshold)| { + if canvas .index() .noise .scatter_nz @@ -614,10 +623,14 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ) .abs() > 1.0 - threshold as f64 + { + density + } else { + density * base_density_prop + } }) - .unwrap_or(true); + .unwrap_or(density); if density > 0.0 - && is_patch && rng.gen::() < density //RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) && underwater == *is_underwater { From 3f7e0a5e52328a8a4ae9c8f916b149eaaa343ee5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 00:44:55 +0100 Subject: [PATCH 02/18] Added shrub layer with example shrub --- assets/world/manifests/shrubs.ron | 8 +++++ assets/world/shrub/1.vox | 3 ++ world/src/layer/mod.rs | 5 ++- world/src/layer/shrub.rs | 57 +++++++++++++++++++++++++++++++ world/src/layer/tree.rs | 6 ++-- world/src/lib.rs | 1 + 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 assets/world/manifests/shrubs.ron create mode 100644 assets/world/shrub/1.vox create mode 100644 world/src/layer/shrub.rs diff --git a/assets/world/manifests/shrubs.ron b/assets/world/manifests/shrubs.ron new file mode 100644 index 0000000000..ffefe8dfe6 --- /dev/null +++ b/assets/world/manifests/shrubs.ron @@ -0,0 +1,8 @@ +#![enable(unwrap_newtypes)] + +[ + ( + specifier: "world.shrub.1", + center: (6, 6, 2), + ), +] diff --git a/assets/world/shrub/1.vox b/assets/world/shrub/1.vox new file mode 100644 index 0000000000..8342c6742c --- /dev/null +++ b/assets/world/shrub/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f08946c4ceebc280dfd30e86eeda045b85f0502620bfbff194e425e9550e64e2 +size 1332 diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 1ed4cc8410..9d3148eb80 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -1,9 +1,12 @@ pub mod scatter; +pub mod shrub; pub mod spot; pub mod tree; pub mod wildlife; -pub use self::{scatter::apply_scatter_to, spot::apply_spots_to, tree::apply_trees_to}; +pub use self::{ + scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to, +}; use crate::{ column::ColumnSample, diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs new file mode 100644 index 0000000000..8b64402766 --- /dev/null +++ b/world/src/layer/shrub.rs @@ -0,0 +1,57 @@ +use crate::{ + util::{seed_expan, RandomPerm, Sampler, StructureGen2d, UnitChooser}, + Canvas, +}; +use common::{ + assets::AssetHandle, + terrain::structure::{Structure, StructuresGroup}, + vol::ReadVol, +}; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use rand::prelude::*; +use rand_chacha::ChaChaRng; +use vek::*; + +lazy_static! { + static ref SHRUBS: AssetHandle = Structure::load_group("shrubs"); +} + +struct Shrub { + wpos: Vec3, + seed: u32, +} + +pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { + let mut shrub_cache = HashMap::new(); + + let shrub_gen = StructureGen2d::new(canvas.index().seed, 8, 4); + + let info = canvas.info(); + canvas.foreach_col(|canvas, wpos2d, col| { + for (wpos, seed) in std::array::IntoIter::new(shrub_gen.get(wpos2d)) { + shrub_cache.entry(wpos).or_insert_with(|| { + let col = info.col_or_gen(wpos)?; + + if RandomPerm::new(seed).chance(37, col.tree_density * 0.3) { + Some(Shrub { + wpos: wpos.with_z(col.alt as i32), + seed, + }) + } else { + None + } + }); + } + }); + + for shrub in shrub_cache.values().filter_map(|s| s.as_ref()) { + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(shrub.seed)); + + let units = UnitChooser::new(shrub.seed).get(shrub.seed).into(); + + let shrubs = SHRUBS.read(); + let structure = shrubs.choose(&mut rng).unwrap(); + canvas.blit_structure(shrub.wpos, structure, shrub.seed, units, true); + } +} diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index a511b4ffad..dfbd12e2fa 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -360,7 +360,7 @@ impl TreeConfig { } pub fn jungle(rng: &mut impl Rng, scale: f32) -> Self { - let scale = scale * (0.9 + rng.gen::().powi(4) * 1.0); + let scale = scale * (0.8 + rng.gen::() * 0.5); let log_scale = 1.0 + scale.log2().max(0.0); Self { @@ -469,14 +469,14 @@ impl TreeConfig { branch_child_len: 0.75, branch_child_radius: 0.75, branch_child_radius_lerp: true, - leaf_radius: 2.0 * log_scale..2.1 * log_scale, + leaf_radius: 1.5 * log_scale..2.0 * log_scale, leaf_radius_scaled: 0.0, straightness: 0.3, max_depth: 5, splits: 3.5..4.25, split_range: 0.5..1.25, branch_len_bias: 0.0, - leaf_vertical_scale: 0.5, + leaf_vertical_scale: 0.65, proportionality: 0.5, inhabited: false, hanging_sprites: &[(0.00007, SpriteKind::Beehive)], diff --git a/world/src/lib.rs b/world/src/lib.rs index 2ebd01a561..72edb90318 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -347,6 +347,7 @@ impl World { }; layer::apply_caves_to(&mut canvas, &mut dynamic_rng); + layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng); layer::apply_trees_to(&mut canvas, &mut dynamic_rng); layer::apply_scatter_to(&mut canvas, &mut dynamic_rng); layer::apply_paths_to(&mut canvas); From 4267a7b7dd4f21955255578f93a74c03fc13c69c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 12:09:15 +0100 Subject: [PATCH 03/18] Improved tree hanging and cedar distribution --- world/src/layer/tree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index dfbd12e2fa..48deea9e45 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -456,6 +456,7 @@ impl TreeConfig { inhabited: false, hanging_sprites: &[(0.00005, SpriteKind::Beehive)], wood_color: Rgb::new(150, 95, 65), + } } From 6284fa0ecdf87c91dc7bb1a5186ab59cf88ac5fd Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 12:49:35 +0100 Subject: [PATCH 04/18] Tree trunk color differences --- world/src/layer/tree.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index 48deea9e45..dfbd12e2fa 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -456,7 +456,6 @@ impl TreeConfig { inhabited: false, hanging_sprites: &[(0.00005, SpriteKind::Beehive)], wood_color: Rgb::new(150, 95, 65), - } } From 4aa8a6bb4275b3ebd67fad72b75f20db82c4d7a5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 15:08:50 +0100 Subject: [PATCH 05/18] Better shrubs --- assets/world/manifests/shrubs.ron | 24 ++++++++++++++++++++++++ assets/world/shrub/jungle-bush-0.vox | 3 +++ assets/world/shrub/jungle-bush-1.vox | 3 +++ assets/world/shrub/jungle-bush-2.vox | 3 +++ assets/world/shrub/jungle-bush-3.vox | 3 +++ assets/world/shrub/jungle-bush-4.vox | 3 +++ assets/world/shrub/jungle-bush-5.vox | 3 +++ world/src/all.rs | 4 ++-- world/src/layer/shrub.rs | 6 +++++- world/src/layer/tree.rs | 8 ++++---- 10 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 assets/world/shrub/jungle-bush-0.vox create mode 100644 assets/world/shrub/jungle-bush-1.vox create mode 100644 assets/world/shrub/jungle-bush-2.vox create mode 100644 assets/world/shrub/jungle-bush-3.vox create mode 100644 assets/world/shrub/jungle-bush-4.vox create mode 100644 assets/world/shrub/jungle-bush-5.vox diff --git a/assets/world/manifests/shrubs.ron b/assets/world/manifests/shrubs.ron index ffefe8dfe6..61cc9d34b6 100644 --- a/assets/world/manifests/shrubs.ron +++ b/assets/world/manifests/shrubs.ron @@ -5,4 +5,28 @@ specifier: "world.shrub.1", center: (6, 6, 2), ), + ( + specifier: "world.shrub.jungle-bush-0", + center: (5, 5, 1), + ), + ( + specifier: "world.shrub.jungle-bush-1", + center: (5, 5, 1), + ), + ( + specifier: "world.shrub.jungle-bush-2", + center: (5, 5, 1), + ), + ( + specifier: "world.shrub.jungle-bush-3", + center: (5, 5, 1), + ), + ( + specifier: "world.shrub.jungle-bush-4", + center: (5, 5, 1), + ), + ( + specifier: "world.shrub.jungle-bush-5", + center: (5, 5, 1), + ), ] diff --git a/assets/world/shrub/jungle-bush-0.vox b/assets/world/shrub/jungle-bush-0.vox new file mode 100644 index 0000000000..f445225da7 --- /dev/null +++ b/assets/world/shrub/jungle-bush-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bc247549fcbc390901805090bfea823569e309346074681efa907fa37b8d590 +size 2360 diff --git a/assets/world/shrub/jungle-bush-1.vox b/assets/world/shrub/jungle-bush-1.vox new file mode 100644 index 0000000000..02fb9d4948 --- /dev/null +++ b/assets/world/shrub/jungle-bush-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a30a82f591fca867ab6287122da46f7e9a55796fea9f352a5faeb932e4671fae +size 1788 diff --git a/assets/world/shrub/jungle-bush-2.vox b/assets/world/shrub/jungle-bush-2.vox new file mode 100644 index 0000000000..dd98838c4c --- /dev/null +++ b/assets/world/shrub/jungle-bush-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e08629bd301f83ed496deba2d1e4c44944e9e96e211d851d9ab161e59129ec4 +size 2456 diff --git a/assets/world/shrub/jungle-bush-3.vox b/assets/world/shrub/jungle-bush-3.vox new file mode 100644 index 0000000000..2944795071 --- /dev/null +++ b/assets/world/shrub/jungle-bush-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c1f54371219692b3f9aedc071d198f4541c6bfdef16626349a3728eaddfc993 +size 1672 diff --git a/assets/world/shrub/jungle-bush-4.vox b/assets/world/shrub/jungle-bush-4.vox new file mode 100644 index 0000000000..58028b2599 --- /dev/null +++ b/assets/world/shrub/jungle-bush-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73a2e29737c98f68f8ff7c6af66fbf0dfb8d8fa4c942bb1d3e474d0935cba6f7 +size 1640 diff --git a/assets/world/shrub/jungle-bush-5.vox b/assets/world/shrub/jungle-bush-5.vox new file mode 100644 index 0000000000..61807b4219 --- /dev/null +++ b/assets/world/shrub/jungle-bush-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3304b2e8270fa81f714e5fc940e616ec81f17ec2b136ee6e9538f3622eea74f4 +size 2664 diff --git a/world/src/all.rs b/world/src/all.rs index 59251a5e62..b5acc3bfb6 100644 --- a/world/src/all.rs +++ b/world/src/all.rs @@ -35,7 +35,7 @@ impl ForestKind { ForestKind::Cedar => 0.275..1.45, ForestKind::Pine => 0.2..1.4, ForestKind::Birch => 0.0..0.6, - ForestKind::Mangrove => 0.65..1.3, + ForestKind::Mangrove => 0.5..1.3, ForestKind::Swamp => 0.5..1.1, _ => 0.0..0.0, } @@ -51,7 +51,7 @@ impl ForestKind { ForestKind::Cedar => -0.65..0.15, ForestKind::Pine => -1.8..-0.2, ForestKind::Birch => -0.7..0.25, - ForestKind::Mangrove => 0.4..1.6, + ForestKind::Mangrove => 0.35..1.6, ForestKind::Swamp => -0.6..0.8, _ => 0.0..0.0, } diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index 8b64402766..510457e46e 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -33,7 +33,11 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { shrub_cache.entry(wpos).or_insert_with(|| { let col = info.col_or_gen(wpos)?; - if RandomPerm::new(seed).chance(37, col.tree_density * 0.3) { + if RandomPerm::new(seed).chance(37, col.tree_density * 0.3) + && col.water_dist.map_or(true, |d| d > 8.0) + && col.spawn_rate > 0.9 + && col.path.map_or(true, |(d, _, _, _)| d > 6.0) + { Some(Shrub { wpos: wpos.with_z(col.alt as i32), seed, diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index dfbd12e2fa..41aa8e0838 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -369,14 +369,14 @@ impl TreeConfig { branch_child_len: 0.35, branch_child_radius: 0.5, branch_child_radius_lerp: true, - leaf_radius: 4.0 * log_scale..4.5 * log_scale, - leaf_radius_scaled: 0.0, + leaf_radius: 10.0 * log_scale..11.5 * log_scale, + leaf_radius_scaled: -8.0 * log_scale, straightness: 0.2, max_depth: 2, splits: 7.5..8.5, - split_range: 0.3..1.25, + split_range: 0.2..1.25, branch_len_bias: 0.5, - leaf_vertical_scale: 0.4, + leaf_vertical_scale: 0.35, proportionality: 0.8, inhabited: false, hanging_sprites: &[(0.00007, SpriteKind::Beehive), (0.015, SpriteKind::Liana)], From bfbca3e517fa0b2d5dc5fa6accb1e170fe89a4f9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 23:48:50 +0100 Subject: [PATCH 06/18] Added sail boat --- assets/server/manifests/ship_manifest.ron | 22 +++++++++++++ assets/server/voxel/sail_boat/structure.vox | 3 ++ assets/world/manifests/shrubs.ron | 12 ++++---- assets/world/shrub/jungle-bush-0.vox | 4 +-- assets/world/shrub/jungle-bush-1.vox | 4 +-- assets/world/shrub/jungle-bush-2.vox | 4 +-- assets/world/shrub/jungle-bush-3.vox | 4 +-- assets/world/shrub/jungle-bush-4.vox | 4 +-- assets/world/shrub/jungle-bush-5.vox | 4 +-- common/src/comp/body.rs | 1 + common/src/comp/body/ship.rs | 34 ++++++++++++++++----- common/src/states/utils.rs | 2 ++ voxygen/anim/src/ship/mod.rs | 18 ++++++++--- 13 files changed, 85 insertions(+), 31 deletions(-) create mode 100644 assets/server/voxel/sail_boat/structure.vox diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron index d964d990b4..36184ce50f 100644 --- a/assets/server/manifests/ship_manifest.ron +++ b/assets/server/manifests/ship_manifest.ron @@ -47,4 +47,26 @@ central: ("air_balloon.rudder"), ), ), + SailBoat: ( + bone0: ( + offset: (-6.5, -15.5, 0.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("sail_boat.structure"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("empty"), + ), + bone2: ( + offset: (0.0, 0.0, 0.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("empty"), + ), + bone3: ( + offset: (-1.5, -6.0, -5.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("empty"), + ), + ), }) diff --git a/assets/server/voxel/sail_boat/structure.vox b/assets/server/voxel/sail_boat/structure.vox new file mode 100644 index 0000000000..023d62b9c6 --- /dev/null +++ b/assets/server/voxel/sail_boat/structure.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17cef56b0257883c2b7ea898a04860706d2c6ec7b18f5c1e9c965164af05ae7c +size 5524 diff --git a/assets/world/manifests/shrubs.ron b/assets/world/manifests/shrubs.ron index 61cc9d34b6..b8cf355e61 100644 --- a/assets/world/manifests/shrubs.ron +++ b/assets/world/manifests/shrubs.ron @@ -7,26 +7,26 @@ ), ( specifier: "world.shrub.jungle-bush-0", - center: (5, 5, 1), + center: (5, 5, 3), ), ( specifier: "world.shrub.jungle-bush-1", - center: (5, 5, 1), + center: (5, 5, 2), ), ( specifier: "world.shrub.jungle-bush-2", - center: (5, 5, 1), + center: (5, 5, 3), ), ( specifier: "world.shrub.jungle-bush-3", - center: (5, 5, 1), + center: (5, 5, 3), ), ( specifier: "world.shrub.jungle-bush-4", - center: (5, 5, 1), + center: (5, 5, 4), ), ( specifier: "world.shrub.jungle-bush-5", - center: (5, 5, 1), + center: (5, 5, 5), ), ] diff --git a/assets/world/shrub/jungle-bush-0.vox b/assets/world/shrub/jungle-bush-0.vox index f445225da7..d6fcb4d105 100644 --- a/assets/world/shrub/jungle-bush-0.vox +++ b/assets/world/shrub/jungle-bush-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bc247549fcbc390901805090bfea823569e309346074681efa907fa37b8d590 -size 2360 +oid sha256:35c228f2bd6b4e4b11e62016e37e910eeb81f51d1cdf9329a9dbfcf6565e1f5e +size 2444 diff --git a/assets/world/shrub/jungle-bush-1.vox b/assets/world/shrub/jungle-bush-1.vox index 02fb9d4948..298ef524d1 100644 --- a/assets/world/shrub/jungle-bush-1.vox +++ b/assets/world/shrub/jungle-bush-1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a30a82f591fca867ab6287122da46f7e9a55796fea9f352a5faeb932e4671fae -size 1788 +oid sha256:dab85c0bf1c791d0b91038723b9cc79b2f58d2a4832f3d60436e0c1ab3c123f9 +size 2192 diff --git a/assets/world/shrub/jungle-bush-2.vox b/assets/world/shrub/jungle-bush-2.vox index dd98838c4c..32894f9c3a 100644 --- a/assets/world/shrub/jungle-bush-2.vox +++ b/assets/world/shrub/jungle-bush-2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e08629bd301f83ed496deba2d1e4c44944e9e96e211d851d9ab161e59129ec4 -size 2456 +oid sha256:3ab454267e0f243faef48831e81f511df30e3ad0589a34180674a37dd509cfea +size 2012 diff --git a/assets/world/shrub/jungle-bush-3.vox b/assets/world/shrub/jungle-bush-3.vox index 2944795071..23d3b19535 100644 --- a/assets/world/shrub/jungle-bush-3.vox +++ b/assets/world/shrub/jungle-bush-3.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c1f54371219692b3f9aedc071d198f4541c6bfdef16626349a3728eaddfc993 -size 1672 +oid sha256:4673b924a97166c519d8e22a1c6d1839cd570c877cd4b7ecd1469adbf88e37eb +size 1968 diff --git a/assets/world/shrub/jungle-bush-4.vox b/assets/world/shrub/jungle-bush-4.vox index 58028b2599..1fe19a7154 100644 --- a/assets/world/shrub/jungle-bush-4.vox +++ b/assets/world/shrub/jungle-bush-4.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73a2e29737c98f68f8ff7c6af66fbf0dfb8d8fa4c942bb1d3e474d0935cba6f7 -size 1640 +oid sha256:280c83933dc0e7d98d17564c33efa95fe2416150171a677975ddc2073d25ce26 +size 2020 diff --git a/assets/world/shrub/jungle-bush-5.vox b/assets/world/shrub/jungle-bush-5.vox index 61807b4219..6b51e26884 100644 --- a/assets/world/shrub/jungle-bush-5.vox +++ b/assets/world/shrub/jungle-bush-5.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3304b2e8270fa81f714e5fc940e616ec81f17ec2b136ee6e9538f3622eea74f4 -size 2664 +oid sha256:9f05fbe323c57abd5ce550b83f807ed7af3ea297f4eb284cd3b42b48f336f88c +size 3784 diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index d09d78b460..45982c3557 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -854,6 +854,7 @@ impl Body { Body::Ship(ship) => match ship { ship::Body::DefaultAirship => [0.0, 0.0, 10.0], ship::Body::AirBalloon => [0.0, 0.0, 5.0], + ship::Body::SailBoat => [-2.0, -5.0, 4.0], }, _ => [0.0, 0.0, 0.0], } diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 09bae038e0..375645db08 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -1,13 +1,13 @@ use crate::{ comp::{Density, Mass}, - consts::AIR_DENSITY, + consts::{AIR_DENSITY, WATER_DENSITY}, make_case_elim, }; use rand::prelude::SliceRandom; use serde::{Deserialize, Serialize}; use vek::Vec3; -pub const ALL_BODIES: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon]; +pub const ALL_BODIES: [Body; 3] = [Body::DefaultAirship, Body::AirBalloon, Body::SailBoat]; make_case_elim!( body, @@ -16,6 +16,7 @@ make_case_elim!( pub enum Body { DefaultAirship = 0, AirBalloon = 1, + SailBoat = 2, } ); @@ -35,6 +36,7 @@ impl Body { match self { Body::DefaultAirship => "airship_human.structure", Body::AirBalloon => "air_balloon.structure", + Body::SailBoat => "sail_boat.structure", } } @@ -42,15 +44,21 @@ impl Body { match self { Body::DefaultAirship => Vec3::new(25.0, 50.0, 40.0), Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0), + Body::SailBoat => Vec3::new(13.0, 31.0, 6.0), } } fn balloon_vol(&self) -> f32 { - let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 { - (std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d - }; - let dim = self.dimensions(); - spheroid_vol(dim.z, dim.y) + match self { + Body::DefaultAirship | Body::AirBalloon => { + let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 { + (std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d + }; + let dim = self.dimensions(); + spheroid_vol(dim.z, dim.y) + }, + _ => 0.0, + } } fn hull_vol(&self) -> f32 { @@ -66,15 +74,25 @@ impl Body { Density(ratio * oak_density + (1.0 - ratio) * AIR_DENSITY) } - pub fn density(&self) -> Density { Density(AIR_DENSITY) } + pub fn density(&self) -> Density { + match self { + Body::DefaultAirship | Body::AirBalloon => Density(AIR_DENSITY), + _ => Density(AIR_DENSITY * 0.75 + WATER_DENSITY * 0.25), // Most boats should be buoyant + } + } pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) } pub fn can_fly(&self) -> bool { match self { Body::DefaultAirship | Body::AirBalloon => true, + _ => false, } } + + pub fn has_water_thrust(&self) -> bool { + !self.can_fly() // TODO: Differentiate this more carefully + } } /// Terrain is 11.0 scale relative to small-scale voxels, diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 9d1d4ffb69..5f7b20790f 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -200,6 +200,8 @@ impl Body { Body::QuadrupedLow(_) => Some(300.0 * self.mass().0), Body::QuadrupedMedium(_) => Some(300.0 * self.mass().0), Body::QuadrupedSmall(_) => Some(300.0 * self.mass().0), + Body::Ship(ship) if ship.has_water_thrust() => Some(500.0 * self.mass().0), + _ => None, } } diff --git a/voxygen/anim/src/ship/mod.rs b/voxygen/anim/src/ship/mod.rs index afecb5e600..a1afe338fa 100644 --- a/voxygen/anim/src/ship/mod.rs +++ b/voxygen/anim/src/ship/mod.rs @@ -31,7 +31,9 @@ impl Skeleton for ShipSkeleton { buf: &mut [FigureBoneData; super::MAX_BONE_COUNT], body: Self::Body, ) -> Offsets { - let bone0_mat = base_mat * Mat4::scaling_3d(1.0 / 11.0) * Mat4::::from(self.bone0); + let scale_mat = Mat4::scaling_3d(1.0 / 11.0); + + let bone0_mat = base_mat * scale_mat * Mat4::::from(self.bone0); *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ make_bone(bone0_mat), @@ -43,10 +45,12 @@ impl Skeleton for ShipSkeleton { lantern: None, // TODO: see quadruped_medium for how to animate this mount_bone: Transform { - position: common::comp::Body::Ship(body) - .mountee_offset() - .into_tuple() - .into(), + position: (base_mat * scale_mat).mul_point( + common::comp::Body::Ship(body) + .mountee_offset() + .into_tuple() + .into(), + ), ..Default::default() }, } @@ -89,18 +93,22 @@ impl<'a> From<&'a Body> for SkeletonAttr { bone0: match body { DefaultAirship => (0.0, 0.0, 0.0), AirBalloon => (0.0, 0.0, 0.0), + SailBoat => (0.0, 0.0, 0.0), }, bone1: match body { DefaultAirship => (-13.0, -25.0, 10.0), AirBalloon => (0.0, 0.0, 0.0), + SailBoat => (0.0, 0.0, 0.0), }, bone2: match body { DefaultAirship => (13.0, -25.0, 10.0), AirBalloon => (0.0, 0.0, 0.0), + SailBoat => (0.0, 0.0, 0.0), }, bone3: match body { DefaultAirship => (0.0, -27.5, 8.5), AirBalloon => (0.0, -9.0, 8.0), + SailBoat => (0.0, 0.0, 0.0), }, } } From 87c7d6e98220f5a2a2b832d3177ae64e797630ca Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 1 Sep 2021 23:49:07 +0100 Subject: [PATCH 07/18] Improved river/lake rendering --- common/src/terrain/map.rs | 2 +- common/src/terrain/mod.rs | 18 + world/src/block/mod.rs | 26 +- world/src/column/mod.rs | 808 +++++++++++++++++++++++-------------- world/src/layer/scatter.rs | 2 +- world/src/layer/shrub.rs | 1 + world/src/layer/tree.rs | 2 +- world/src/sim/mod.rs | 23 +- 8 files changed, 549 insertions(+), 333 deletions(-) diff --git a/common/src/terrain/map.rs b/common/src/terrain/map.rs index 7f6550e02f..b134e40e83 100644 --- a/common/src/terrain/map.rs +++ b/common/src/terrain/map.rs @@ -551,7 +551,7 @@ impl<'a> MapConfig<'a> { downhill_wpos, ); let (_t, _pt, dist) = if let Some((t, pt, dist)) = - quadratic_nearest_point(&coeffs, wposf) + quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) { (t, pt, dist) } else { diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 0bb91f5c9b..8aaf7357c5 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -293,7 +293,25 @@ pub fn river_spline_coeffs( pub fn quadratic_nearest_point( spline: &Vec3>, point: Vec2, + line: Vec2>, ) -> Option<(f64, Vec2, f64)> { + let line = LineSegment2 { + start: line.x, + end: line.y, + }; + let len_sq = line.start.distance_squared(line.end); + let t = ((point - line.start).dot(line.end - line.start) / len_sq).clamped(0.0, 1.0); + let pos = line.start + (line.end - line.start) * t; + return Some((t, pos, pos.distance_squared(point))); + + // let curve = QuadraticBezier2 { + // start: spline.x, + // ctrl: spline.y, + // end: spline.z, + // }; + // let (t, pos) = curve.binary_search_point_by_steps(point, 16, 0.001); + // return Some((t, pos, curve.evaluate(t).distance_squared(point))); + let a = spline.z.x; let b = spline.y.x; let c = spline.x.x; diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index d1ce83457f..84512102f0 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -147,23 +147,27 @@ impl<'a> BlockGen<'a> { } else { Some(Block::new(BlockKind::Earth, col)) } - } else if (wposf.z as f32) < height { + } else if wposf.z as i32 <= height as i32 { let grass_factor = (wposf.z as f32 - (height - grass_depth)) .div(grass_depth) .sqrt(); - let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); // Surface - Some(Block::new( - if snow_cover { + Some(if water_level.floor() > height { + Block::new( + BlockKind::Sand, + sub_surface_color.map(|e| (e * 255.0) as u8), + ) + } else { + let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); + if grass_factor < 0.7 { + Block::new(BlockKind::Earth, col.map(|e| (e * 255.0) as u8)) + } else if snow_cover { //if temp < CONFIG.snow_temp + 0.031 { - BlockKind::Snow - } else if grass_factor > 0.7 { - BlockKind::Grass + Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8)) } else { - BlockKind::Earth - }, - col.map(|e| (e * 255.0) as u8), - )) + Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8)) + } + }) } else { None } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d57e5892d2..e86bbad9b7 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -96,6 +96,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river)) }); + let gradient = sim.get_gradient_approx(chunk_pos); + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 12.0; let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| { let kind = match river.river_kind { @@ -120,9 +122,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let downhill_wpos = downhill_pos.map(|e| e as f64); let downhill_pos = - downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); - let neighbor_pos = posj.map(|e| e as f64) * neighbor_coef; - let direction = neighbor_pos - downhill_wpos; + downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e.div_euclid(sz as i32)); + let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef;// + neighbor_coef * 0.5; + let direction = neighbor_wpos - downhill_wpos; let river_width_min = if let RiverKind::River { cross_section } = kind { cross_section.x as f64 } else { @@ -130,16 +132,25 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?"); let coeffs = - river_spline_coeffs(neighbor_pos, chunkj.river.spline_derivative, downhill_wpos); + river_spline_coeffs(neighbor_wpos, chunkj.river.spline_derivative, downhill_wpos); let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind { RiverKind::River { .. } => { - if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf) { + if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) /*{ + let curve = CubicBezier2 { + start: neighbor_wpos, + ctrl0: neighbor_wpos * 0.75 + downhill_wpos * 0.25 + chunkj.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, + ctrl1: downhill_wpos * 0.75 + neighbor_wpos * 0.25 - downhill_chunk.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, + end: downhill_wpos, + }; + let (t, pos) = curve.binary_search_point_by_steps(wposf, 16, 0.001); + Some((t, pos, curve.evaluate(t).distance_squared(wposf))) + }*/ { (direction, coeffs, downhill_chunk, t, pt, dist.sqrt()) } else { - let ndist = wposf.distance_squared(neighbor_pos); + let ndist = wposf.distance_squared(neighbor_wpos); let ddist = wposf.distance_squared(downhill_wpos); let (closest_pos, closest_dist, closest_t) = if ndist <= ddist { - (neighbor_pos, ndist, 0.0) + (neighbor_wpos, ndist, 0.0) } else { (downhill_wpos, ddist, 1.0) }; @@ -156,7 +167,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { RiverKind::Lake { neighbor_pass_pos } => { let pass_dist = neighbor_pass_pos .map2( - neighbor_pos + neighbor_wpos .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), |e, (f, g)| ((e - f) / g).abs(), ) @@ -169,7 +180,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let pass_dist = neighbor_pass_pos .map2( - neighbor_pos + neighbor_wpos .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), |e, (f, g)| ((e - f) / g).abs(), ) @@ -181,9 +192,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let neighbor_pass_pos = neighbor_pass_pos .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let coeffs = - river_spline_coeffs(neighbor_pos, spline_derivative, neighbor_pass_wpos); - let direction = neighbor_pos - neighbor_pass_wpos; - if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf) { + river_spline_coeffs(neighbor_wpos, spline_derivative, neighbor_pass_wpos); + let direction = neighbor_wpos - neighbor_pass_wpos; + if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, neighbor_pass_wpos)) { ( direction, coeffs, @@ -193,10 +204,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { dist.sqrt(), ) } else { - let ndist = wposf.distance_squared(neighbor_pos); + let ndist = wposf.distance_squared(neighbor_wpos); /* let ddist = wposf.distance_squared(neighbor_pass_wpos); */ let (closest_pos, closest_dist, closest_t) = /*if ndist <= ddist */ { - (neighbor_pos, ndist, 0.0) + (neighbor_wpos, ndist, 0.0) } /* else { (neighbor_pass_wpos, ddist, 1.0) } */; @@ -211,8 +222,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }, RiverKind::Ocean => { - let ndist = wposf.distance_squared(neighbor_pos); - let (closest_pos, closest_dist, closest_t) = (neighbor_pos, ndist, 0.0); + let ndist = wposf.distance_squared(neighbor_wpos); + let (closest_pos, closest_dist, closest_t) = (neighbor_wpos, ndist, 0.0); ( direction, coeffs, @@ -243,12 +254,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { river_t.max(0.0).min(1.0).sqrt(), ); - let river_width = river_width * (1.0 + river_width_noise * 0.3); + let river_width = river_width;// * (1.0 + river_width_noise * 0.3); // To find the distance, we just evaluate the quadratic equation at river_t and // see if it's within width (but we should be able to use it for a // lot more, and this probably isn't the very best approach anyway // since it will bleed out). let river_pos = coeffs.x * river_t * // river_t + coeffs.y * river_t + coeffs.z; + // let river_width = 32.0f64; let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0)); ( posj, @@ -263,26 +275,187 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ) }); - // Cliffs - let cliff_factor = (alt - + self.sim.gen_ctx.hill_nz.get(wposf.div(64.0).into_array()) as f32 * 8.0 - + self.sim.gen_ctx.hill_nz.get(wposf.div(350.0).into_array()) as f32 * 128.0) - .rem_euclid(200.0) - / 64.0 - - 1.0; - let cliff_scale = - ((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75) - + self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1) - .clamped(0.0, 1.0) - .powf(2.0); - let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale; - let cliff = if cliff_factor < 0.0 { - cliff_factor.abs().powf(1.5) - } else { - 0.0 - } * (1.0 - near_water * 3.0).max(0.0).powi(2); - let cliff_offset = cliff * cliff_height; - let alt = alt + (cliff - 0.5) * cliff_height; + let downhill = sim_chunk.downhill; + let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos)); + debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); + + let downhill_water_alt = downhill_pos + .map(|downhill_chunk| { + downhill_chunk + .water_alt + .min(sim_chunk.water_alt) + .max(sim_chunk.alt.min(sim_chunk.water_alt)) + }) + .unwrap_or(CONFIG.sea_level); + + #[derive(Default)] + struct WeightedSum { sum: T, weight: T, min: Option, max: Option } + impl WeightedSum { + fn with(self, value: f32, weight: f32) -> Self { + Self { + sum: self.sum + value * weight, + weight: self.weight + weight, + ..self + } + } + /// With an upper bound + fn with_min(self, min: f32) -> Self { + Self { min: Some(self.min.unwrap_or(min).min(min)), ..self } + } + /// With a lower bound + fn with_max(self, max: f32) -> Self { + Self { max: Some(self.max.unwrap_or(max).max(max)), ..self } + } + fn eval_or(&self, default: f32) -> f32 { + let res = if self.weight > 0.0 { + self.sum / self.weight + } else { + default + }; + let res = match self.min { + Some(min) => res.min(min), + None => res, + }; + let res = match self.max { + Some(max) => res.max(max), + None => res, + }; + res + } + } + + let water_level = neighbor_river_data.clone().fold( + WeightedSum::default(), + |water_level, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + ( + Some(kind/*RiverKind::River { cross_section }*/), + Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + ) => { + // Distance from river center + let river_dist = river_pos.distance(wposf); + // Distance from edge of river + let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; + // 0.0 = not near river, 1.0 = in middle of river + let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + + match kind { + RiverKind::River { .. } if river_edge_dist <= 0.0 => { + // Alt of river water *is* the alt of land + let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + water_level.with(river_water_alt, near_river) + }, + // Slightly wider threshold is chosen in case the lake bounds are a bit wrong + RiverKind::Lake { .. } if river_edge_dist <= 16.0 => { + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_chunk.alt.max(downhill_chunk.water_alt), + river_t as f32, + ); + water_level.with_max(lake_water_alt) + }, + _ => water_level, + } + }, + (_, _) => water_level, + }, + ); + let water_level = water_level.eval_or(CONFIG.sea_level); + + let riverless_alt = alt; + let alt = neighbor_river_data.clone().fold( + WeightedSum::default().with(riverless_alt, 1.0), + |alt, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + ( + Some(kind/*RiverKind::River { cross_section }*/), + Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + ) => { + // Distance from river center + let river_dist = river_pos.distance(wposf); + // Distance from edge of river + let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; + // 0.0 = not near river, 1.0 = in middle of river + let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + + // Maximum depth of the river basin (including banks) + let river_basin = (river_width as f32 + 1.0).ln() * 1.5; + // River basin depth at the current column + let basin_depth = near_river * river_basin; + + let water_alt = match kind { + RiverKind::River { .. } => { + // Alt of river water *is* the alt of land + Some(Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32)) + }, + RiverKind::Lake { .. } => Some(Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_chunk.alt.max(downhill_chunk.water_alt), + river_t as f32, + )), + _ => None, + }; + + if let Some(water_alt) = water_alt { + if river_edge_dist <= 0.0 { + const MIN_DEPTH: f32 = 0.5; + let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; + let riverbed_depth = near_centre * river_width as f32 * 0.35 + MIN_DEPTH; + alt.with_min(water_alt - riverbed_depth) + } else { + const GORGE: f32 = 0.5; + const BANK_SCALE: f32 = 24.0; + const BANK_STRENGTH: f32 = 100.0; + // Weighting of this riverbank on nearby terrain (higher when closer to the river). This + // 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth + // transition from normal terrain to the water. + let weight = Lerp::lerp( + BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 1.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), + 0.0, + (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), + ); + let alt = alt.with(water_alt + GORGE, weight); + // Add "walls" around weirdly back-curving segments to prevent water walls + // let alt = if river_edge_dist <= 1.0 && river_t > 0.0 && river_t < 1.0 { + // alt.with_max(water_alt + GORGE) + // } else { + // alt + // }; + alt + } + } else { + alt + } + + // match kind { + // RiverKind::River { .. } => { + // // Alt of river water *is* the alt of land + // let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + + // if river_edge_dist <= 0.0 { + // const GORGE: f32 = 1.0; + // let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; + // let riverbed_depth = near_centre * river_width as f32 * 0.35 + GORGE; + // alt.with_min(river_water_alt - riverbed_depth) + // } else { + // const BANK_SCALE: f32 = 8.0; + // const BANK_STRENGTH: f32 = 100.0; + // alt.with(river_water_alt, BANK_STRENGTH / (1.0 + river_edge_dist as f32 * BANK_STRENGTH / BANK_SCALE)) + // } + // }, + // RiverKind::Lake { .. } => { + // let lake_water_alt = Lerp::lerp( + // river_chunk.alt.max(river_chunk.water_alt), + // downhill_chunk.alt.max(downhill_chunk.water_alt), + // river_t as f32, + // ); + // alt + // }, + // _ => alt, + // } + }, + (_, _) => alt, + }, + ); + let alt = alt.eval_or(riverless_alt); // Find the average distance to each neighboring body of water. let mut river_count = 0.0f64; @@ -291,6 +464,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let mut river_overlap_distance_product = 0.0f64; let mut max_river = None; let mut max_key = None; + let mut max_dip = 0.0f32; // IDEA: // For every "nearby" chunk, check whether it is a river. If so, find the // closest point on the river segment to wposf (if two point are @@ -318,6 +492,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // 0, the weighted altitude of this point should go from // alt to river_alt. neighbor_river_data.for_each(|(river_chunk_idx, river_chunk, river, dist)| { + return; // TODO: Not this + match river.river_kind { Some(kind) => { if kind.is_river() && dist.is_none() { @@ -325,19 +501,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // closest point between t = 0.0 and t = 1.0). return; } else { - let river_dist = dist.map(|(_, dist, _, (river_t, _, downhill_river))| { + let river_dist = dist.map(|(_, dist, river_width, (river_t, (river_pos, _), downhill_river))| { let downhill_height = if kind.is_river() { - Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river.alt.max(downhill_river.water_alt), - river_t as f32, - ) as f64 + // Lerp::lerp( + // river_chunk.alt.max(river_chunk.water_alt), + // downhill_river.alt.max(downhill_river.water_alt), + // river_t as f32, + // ) as f64 + river_width - (river_pos - wposf).magnitude() } else { let neighbor_pos = river_chunk_idx.map(|e| e as f64) * neighbor_coef; - -(wposf - neighbor_pos).magnitude() + river_width - (wposf - neighbor_pos).magnitude() }; - (Reverse((dist.x, dist.y)), downhill_height) + (Reverse((dist.x, dist.y /*- if kind.is_river() { 0.01 } else { 0.0 }*/)), downhill_height) }); let river_dist = river_dist.or_else(|| { if !kind.is_river() { @@ -363,7 +540,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // between the edge of the river that we intersect, and the remaining distance // until the nearest point in "this" chunk (i.e. the one whose top-left corner // is chunk_pos) that is at least 2 chunks away from the river source. - if let Some((_, dist, _, (river_t, _, downhill_river_chunk))) = dist { + if let Some((_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk))) = dist { let max_distance = if !river.is_river() { /*(*/ TerrainChunkSize::RECT_SIZE.x as f64 /* * (1.0 - (2.0f64.sqrt() / 2.0))) + 4.0*/ - lake_width * 0.5 @@ -371,7 +548,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { TerrainChunkSize::RECT_SIZE.x as f64 }; let scale_factor = max_distance; - let river_dist = dist.y; + let river_dist = if kind.is_river() { + // dist.y + ((river_pos - wposf).magnitude() - river_width * 0.5).max(0.0) + } else { + let water_chunk = river_chunk_idx.map(|e| e as f64); + let water_aabr = Aabr { + min: water_chunk * neighbor_coef, + max: (water_chunk + 1.0) * neighbor_coef, + }; + /*water_aabr.distance_to_point(wposf)*/ + (river_pos - wposf).magnitude() / (TerrainChunkSize::RECT_SIZE.x as f64 * 0.1) + }; if !(dist.x == 0.0 && river_dist < scale_factor) { return; @@ -397,6 +585,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { overlap_count += 1.0 - river_scale; river_count += 1.0; river_distance_product *= river_scale; + max_dip = max_dip.max(-river_alt_diff + if let RiverKind::River { cross_section } = kind { + let river_depth = river_width as f32 * 0.5; + + (cross_section.y * river_depth).max(0.0) + 1.5 + } else { + 0.0 + }); } } None => {} @@ -418,12 +613,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }; - let alt_for_river = alt - + if overlap_count == 0.0 { - 0.0 - } else { - river_overlap_distance_product / overlap_count - } as f32; + let alt_for_river = alt; + // + if overlap_count == 0.0 { + // 0.0 + // } else { + // river_overlap_distance_product / overlap_count + // } as f32; 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(), @@ -441,21 +636,29 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .abs() .mul(3.0); - let downhill = sim_chunk.downhill; - let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos)); - debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); - - let downhill_water_alt = downhill_pos - .map(|downhill_chunk| { - downhill_chunk - .water_alt - .min(sim_chunk.water_alt) - .max(sim_chunk.alt.min(sim_chunk.water_alt)) - }) - .unwrap_or(CONFIG.sea_level); + // Cliffs + let cliff_factor = (alt + + self.sim.gen_ctx.hill_nz.get(wposf.div(64.0).into_array()) as f32 * 8.0 + + self.sim.gen_ctx.hill_nz.get(wposf.div(350.0).into_array()) as f32 * 128.0) + .rem_euclid(200.0) + / 64.0 + - 1.0; + let cliff_scale = + ((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75) + + self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1) + .clamped(0.0, 1.0) + .powf(2.0); + let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale; + let cliff = if cliff_factor < 0.0 { + cliff_factor.abs().powf(1.5) + } else { + 0.0 + } * (1.0 - near_water * 3.0).max(0.0).powi(2); + let cliff_offset = cliff * cliff_height; + let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; let river_gouge = 0.5; - let (_in_water, water_dist, alt_, water_level, _riverless_alt, warp_factor) = if let Some( + let (_in_water, water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = if let Some( (max_border_river_pos, river_chunk, max_border_river, max_border_river_dist), ) = max_river @@ -464,264 +667,248 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // // If we are <= water_alt, we are in the lake; otherwise, we are flowing into // it. - let (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) = - max_border_river - .river_kind - .and_then(|river_kind| { - match river_kind { - RiverKind::River { cross_section } => { - if max_border_river_dist.map(|(_, dist, _, _)| dist) - != Some(Vec2::zero()) - { - return None; - } - let ( - _, - _, - river_width, - (river_t, (river_pos, _), downhill_river_chunk), - ) = max_border_river_dist.unwrap(); - let river_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - let new_alt = river_alt - river_gouge; - let river_dist = wposf.distance(river_pos); - let river_height_factor = river_dist / (river_width * 0.5); + match max_border_river.river_kind { + Some(RiverKind::River { cross_section }) => 'block: { + if max_border_river_dist.map(|(_, dist, _, _)| dist) + != Some(Vec2::zero()) + { + let (_, _, river_width, (_, (river_pos, _), _)) = + max_border_river_dist.unwrap(); + let river_dist = wposf.distance(river_pos); - let valley_alt = Lerp::lerp( - new_alt - cross_section.y.max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ); + // FIXME: Make water altitude accurate. + break 'block ( + river_scale_factor <= 1.0, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip,//alt_for_river, + downhill_water_alt, + alt, //alt_for_river, + river_scale_factor as f32, + ); + } + let ( + _, + _, + river_width, + (river_t, (river_pos, _), downhill_river_chunk), + ) = max_border_river_dist.unwrap(); + let river_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), + river_t as f32, + ); + let new_alt = river_alt - river_gouge; + let river_dist = wposf.distance(river_pos); + let river_height_factor = river_dist / (river_width * 0.5); - Some(( - true, - Some((river_dist - river_width * 0.5) as f32), - valley_alt, - new_alt, - alt, //river_alt + cross_section.y.max(1.0), - 0.0, - )) - }, - _ => None, - } - }) - .unwrap_or_else(|| { - max_border_river - .river_kind - .map(|river_kind| { - match river_kind { - RiverKind::Ocean => { - let ( - _, - dist, - river_width, - (river_t, (river_pos, _), downhill_river_chunk), - ) = if let Some(dist) = max_border_river_dist { - dist - } else { - error!( - ?max_border_river, - ?chunk_pos, - ?max_border_river_pos, - "downhill error details" - ); - panic!( - "Oceans should definitely have a downhill! \ - ...Right?" - ); - }; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ); + // This is not a good way to determine river depth, but we do it + // anyway. TODO: Make the erosion + // sim output better river depths through `cross_section`. + let river_depth = river_width as f32 * 0.5; - if dist == Vec2::zero() { - let river_dist = wposf.distance(river_pos); - let _river_height_factor = - river_dist / (river_width * 0.5); - return ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - .min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt - river_gouge, - alt_for_river.max(lake_water_alt), - 0.0, - ); - } + let valley_alt = Lerp::lerp( + new_alt - (cross_section.y * river_depth).max(1.0), + new_alt - 1.0, + (river_height_factor * river_height_factor) as f32, + ); - ( - river_scale_factor <= 1.0, - Some( - (wposf.distance(river_pos) - river_width * 0.5) - as f32, - ), - alt_for_river, - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ) - }, - RiverKind::Lake { .. } => { - let lake_dist = (max_border_river_pos.map(|e| e as f64) - * neighbor_coef) - .distance(wposf); - let downhill_river_chunk = max_border_river_pos; - let lake_id_dist = downhill_river_chunk - chunk_pos; - let in_bounds = lake_id_dist.x >= -1 - && lake_id_dist.y >= -1 - && lake_id_dist.x <= 1 - && lake_id_dist.y <= 1; - let in_bounds = in_bounds - && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); - let (_, dist, _, (river_t, _, downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else if lake_dist - <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = 0.0; - return ( - in_bounds - || downhill_water_alt - .max(river_chunk.water_alt) - > alt_for_river, - Some(lake_dist as f32), - alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - - river_gouge), - alt_for_river, - river_scale_factor as f32 - * (1.0 - gouge_factor), - ); - } else { - return ( - false, - Some(lake_dist as f32), - alt_for_river, - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ); - }; + ( + true, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip,//valley_alt.min(alt), + new_alt.max(valley_alt.min(alt) + 1.0), + alt, //river_alt + cross_section.y.max(1.0), + river_scale_factor as f32,//0.0, + ) + }, + Some(RiverKind::Ocean) => 'block: { + let ( + _, + dist, + river_width, + (river_t, (river_pos, _), downhill_river_chunk), + ) = if let Some(dist) = max_border_river_dist { + dist + } else { + error!( + ?max_border_river, + ?chunk_pos, + ?max_border_river_pos, + "downhill error details" + ); + panic!( + "Oceans should definitely have a downhill! \ + ...Right?" + ); + }; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk + .alt + .max(downhill_river_chunk.water_alt), + river_t as f32, + ) + 1.0; - let lake_dist = dist.y; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ); - if dist == Vec2::zero() { - return ( - true, - Some(lake_dist as f32), - alt_for_river - .min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt - river_gouge, - alt_for_river.max(lake_water_alt), - 0.0, - ); - } - if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = if in_bounds && lake_dist <= 1.0 { - 1.0 - } else { - 0.0 - }; - let in_bounds_ = lake_dist - <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; - if gouge_factor == 1.0 { - return ( - true, - Some(lake_dist as f32), - alt.min(lake_water_alt - 1.0 - river_gouge), - downhill_water_alt.max(lake_water_alt) - - river_gouge, - alt.max(lake_water_alt), - 0.0, - ); - } else { - return ( - true, - Some(lake_dist as f32), - alt_for_river, - if in_bounds_ { - downhill_water_alt.max(lake_water_alt) - } else { - downhill_water_alt - } - river_gouge, - alt_for_river, - river_scale_factor as f32 - * (1.0 - gouge_factor), - ); - } - } - ( - river_scale_factor <= 1.0, - Some(lake_dist as f32), - alt_for_river, - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ) - }, - RiverKind::River { .. } => { - let (_, _, river_width, (_, (river_pos, _), _)) = - max_border_river_dist.unwrap(); - let river_dist = wposf.distance(river_pos); + if dist == Vec2::zero() { + let river_dist = wposf.distance(river_pos); + let _river_height_factor = + river_dist / (river_width * 0.5); + break 'block ( + true, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip,//alt_for_river, + // .min(lake_water_alt - 1.0 - river_gouge), + lake_water_alt, + alt, //alt_for_river.max(lake_water_alt), + 0.0, + ); + } - // FIXME: Make water altitude accurate. - ( - river_scale_factor <= 1.0, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - river_scale_factor as f32, - ) - }, - } - }) - .unwrap_or(( - false, - None, + ( + river_scale_factor <= 1.0, + Some( + (wposf.distance(river_pos) - river_width * 0.5) + as f32, + ), + alt_for_river - max_dip,//alt_for_river, + downhill_water_alt + 1.0, + alt, //alt_for_river, + river_scale_factor as f32, + ) + }, + Some(RiverKind::Lake { .. }) => 'block: { + let lake_chunk = max_border_river_pos.map(|e| e as f64); + let lake_aabr = Aabr { + min: lake_chunk * neighbor_coef, + max: (lake_chunk + 1.0) * neighbor_coef, + }; + let lake_dist = lake_aabr.distance_to_point(wposf); + let downhill_river_chunk = max_border_river_pos; + let lake_id_dist = downhill_river_chunk - chunk_pos; + let in_bounds = lake_id_dist.x >= -1 + && lake_id_dist.y >= -1 + && lake_id_dist.x <= 1 + && lake_id_dist.y <= 1; + let in_bounds = in_bounds + && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); + let (_, dist, _, (river_t, _, downhill_river_chunk)) = + if let Some(dist) = max_border_river_dist { + dist + } else if lake_dist + <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 + || in_bounds + { + let gouge_factor = 0.0; + break 'block ( + in_bounds + || downhill_water_alt + .max(river_chunk.water_alt) + > alt_for_river, + Some(lake_dist as f32), + alt_for_river - max_dip,//alt_for_river, + (downhill_water_alt.max(river_chunk.water_alt) + - river_gouge), alt_for_river, + river_scale_factor as f32 + * (1.0 - gouge_factor), + ); + } else { + break 'block ( + false, + Some(lake_dist as f32), + alt_for_river - max_dip,//alt_for_river, downhill_water_alt, - alt, //alt_for_river, + alt_for_river, river_scale_factor as f32, - )) - }); - ( - in_water, - water_dist, - new_alt, - new_water_alt, - riverless_alt, - warp_factor, - ) + ); + }; + + let lake_dist = dist.y; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk + .alt + .max(downhill_river_chunk.water_alt), + river_t as f32, + ); + // if dist == Vec2::zero() { + // return ( + // true, + // Some(lake_dist as f32), + // alt_for_river, + // // .min(lake_water_alt - 1.0 - river_gouge), + // lake_water_alt - river_gouge, + // alt_for_river.max(lake_water_alt), + // 0.0, + // ); + // } + if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 + || in_bounds + { + let gouge_factor = if in_bounds && lake_dist <= 1.0 { + 1.0 + } else { + 0.0 + }; + let in_bounds_ = lake_dist + <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; + if gouge_factor == 1.0 { + break 'block ( + true, + Some(lake_dist as f32), + alt_for_river - max_dip,//alt_for_river, + // .min(lake_water_alt - 1.0 - river_gouge), + downhill_water_alt.max(lake_water_alt) + - river_gouge, + alt.max(lake_water_alt), + 0.0,// river_scale_factor as f32* (1.0 - gouge_factor), + ); + } else { + break 'block ( + true, + Some(lake_dist as f32), + alt_for_river - max_dip,//alt_for_river, + if in_bounds_ { + downhill_water_alt.max(lake_water_alt) + } else { + downhill_water_alt//.max(lake_water_alt) + }.max(river_chunk.water_alt) - river_gouge, + alt_for_river, + river_scale_factor as f32 + * (1.0 - gouge_factor), + ); + } + } + ( + river_scale_factor <= 1.0, + Some(lake_dist as f32), + alt_for_river - max_dip,//alt_for_river.max(lake_water_alt).max(river_chunk.water_alt), + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + ) + }, + None => ( + false, + None, + alt_for_river - max_dip,//alt_for_river, + downhill_water_alt, + alt, //alt_for_river, + 1.0, + ), + } } else { ( false, None, - alt_for_river, + alt_for_river - max_dip,//alt_for_river, downhill_water_alt, alt, //alt_for_river, 1.0, ) }; // NOTE: To disable warp, uncomment this line. - // let warp_factor = 0.0; + let warp_factor = 0.0; let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta; @@ -982,18 +1169,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .add(((marble - 0.5) / 0.5) * 0.5) .add(((marble_mid - 0.5) / 0.5) * 0.25) .add(((marble_small - 0.5) / 0.5) * 0.175); - let (alt, ground, sub_surface_color, snow_cover) = if snow_cover <= 0.0 && alt > water_level - { + let (alt, ground, sub_surface_color) = if snow_cover <= 0.0 { // Allow snow cover. ( - alt + 1.0 - snow_cover.max(0.0), + alt + if alt > water_level { 1.0 - snow_cover.max(0.0) } else { 0.0 }, Rgb::lerp(snow, ground, snow_cover), Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)), - true, ) } else { - (alt, ground, sub_surface_color, false) + (alt, ground, sub_surface_color) }; + let snow_cover = snow_cover <= 0.0; // Make river banks not have grass let ground = water_dist @@ -1020,8 +1206,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 5.0 }; - let gradient = sim.get_gradient_approx(chunk_pos); - let path = sim.get_nearest_path(wpos); let cave = sim.get_nearest_cave(wpos); diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 3b93b17dd9..90ccfce307 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -603,7 +603,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ]; canvas.foreach_col(|canvas, wpos2d, col| { - let underwater = col.water_level > col.alt; + let underwater = col.alt < col.water_level; let kind = scatter .iter() diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index 510457e46e..f28a28071a 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -35,6 +35,7 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { if RandomPerm::new(seed).chance(37, col.tree_density * 0.3) && col.water_dist.map_or(true, |d| d > 8.0) + && col.alt > col.water_level && col.spawn_rate > 0.9 && col.path.map_or(true, |(d, _, _, _)| d > 6.0) { diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index 41aa8e0838..05e0f5bc49 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -467,7 +467,7 @@ impl TreeConfig { trunk_len: 13.0 * scale, trunk_radius: 1.65 * scale, branch_child_len: 0.75, - branch_child_radius: 0.75, + branch_child_radius: 0.6, branch_child_radius_lerp: true, leaf_radius: 1.5 * log_scale..2.0 * log_scale, leaf_radius_scaled: 0.0, diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 43005634e3..57573fcc63 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1563,8 +1563,10 @@ impl WorldSim { let dist = rpos.map(|e| e as f32).magnitude(); if let Some(c) = self.get_mut(cliff + rpos) { let warp = 1.0 / (1.0 + dist); - c.tree_density *= 1.0 - warp; - c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5); + if !c.river.near_water() { + c.tree_density *= 1.0 - warp; + c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5); + } } }); } @@ -2219,7 +2221,9 @@ impl SimChunk { } else { Some( uniform_idx_as_vec2(map_size_lg, downhill_pre as usize) - * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + * TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + // + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2) + , ) }; @@ -2295,10 +2299,10 @@ impl SimChunk { .min(1.0); // Add geologically short timescale undulation to the world for various reasons - let alt = alt + let alt = // Don't add undulation to rivers, mainly because this could accidentally result in rivers flowing uphill - + if river.near_water() { - 0.0 + if river.near_water() { + alt } else { // Sand dunes (formed over a short period of time, so we don't care about erosion sim) let warp = Vec2::new( @@ -2323,7 +2327,12 @@ impl SimChunk { const SOIL_SCALE: f32 = 16.0; let soil = soil_nz * SOIL_SCALE * tree_density.sqrt() * humidity.sqrt(); - dune + soil + // Prevent dunes pushing the altitude underwater + if alt + dune + soil < water_alt { + alt + } else { + alt + dune + soil + } }; Self { From f118d15745c1278d6c876c25ec4a4f4f6e88416f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 3 Sep 2021 02:17:47 +0100 Subject: [PATCH 08/18] Fixed oceans --- common/src/terrain/mod.rs | 37 ++++++++++++++++++++++++------------- world/src/block/mod.rs | 2 +- world/src/column/mod.rs | 17 +++++++++++++---- world/src/layer/scatter.rs | 3 ++- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 8aaf7357c5..553c688ce5 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -295,14 +295,14 @@ pub fn quadratic_nearest_point( point: Vec2, line: Vec2>, ) -> Option<(f64, Vec2, f64)> { - let line = LineSegment2 { - start: line.x, - end: line.y, - }; - let len_sq = line.start.distance_squared(line.end); - let t = ((point - line.start).dot(line.end - line.start) / len_sq).clamped(0.0, 1.0); - let pos = line.start + (line.end - line.start) * t; - return Some((t, pos, pos.distance_squared(point))); + // let line = LineSegment2 { + // start: line.x, + // end: line.y, + // }; + // let len_sq = line.start.distance_squared(line.end); + // let t = ((point - line.start).dot(line.end - line.start) / len_sq).clamped(0.0, 1.0); + // let pos = line.start + (line.end - line.start) * t; + // return Some((t, pos, pos.distance_squared(point))); // let curve = QuadraticBezier2 { // start: spline.x, @@ -346,19 +346,25 @@ pub fn quadratic_nearest_point( let min_root = roots .iter() .copied() + // .chain((0..30).map(|i| i as f64 / 30.0)) .filter_map(|root| { let river_point = spline.x * root * root + spline.y * root + spline.z; let river_zero = spline.z; let river_one = spline.x + spline.y + spline.z; if root > 0.0 && root < 1.0 { Some((root, river_point)) - } else if river_point.distance_squared(river_zero) < 0.5 { - Some((root, /*river_point*/ river_zero)) - } else if river_point.distance_squared(river_one) < 0.5 { - Some((root, /*river_point*/ river_one)) } else { - None + let root = root.clamped(0.0, 1.0); + let river_point = spline.x * root * root + spline.y * root + spline.z; + Some((root, river_point)) } + // } else if river_point.distance_squared(river_zero) < 0.5 { + // Some((root, /*river_point*/ river_zero)) + // } else if river_point.distance_squared(river_one) < 0.5 { + // Some((root, /*river_point*/ river_one)) + // } else { + // None + // } }) .map(|(root, river_point)| { let river_distance = river_point.distance_squared(point); @@ -372,4 +378,9 @@ pub fn quadratic_nearest_point( .unwrap() }); min_root + // .map(|(t, pt, dist)| { + // let t = t.clamped(0.0, 1.0); + // let pos = spline.x * t * t + spline.y * t + spline.z; + // (t, pos, pos.distance_squared(point)) + // }) } diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 84512102f0..ad9219d54b 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -152,7 +152,7 @@ impl<'a> BlockGen<'a> { .div(grass_depth) .sqrt(); // Surface - Some(if water_level.floor() > height { + Some(if water_level > height.ceil() { Block::new( BlockKind::Sand, sub_surface_color.map(|e| (e * 255.0) as u8), diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index e86bbad9b7..b75655b015 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -145,6 +145,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let (t, pos) = curve.binary_search_point_by_steps(wposf, 16, 0.001); Some((t, pos, curve.evaluate(t).distance_squared(wposf))) }*/ { + let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) { + (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos)) + } else if dist > wposf.distance_squared(downhill_wpos) { + (1.0, downhill_wpos, wposf.distance_squared(downhill_wpos)) + } else { + (t, pt, dist) + }; (direction, coeffs, downhill_chunk, t, pt, dist.sqrt()) } else { let ndist = wposf.distance_squared(neighbor_wpos); @@ -325,7 +332,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } let water_level = neighbor_river_data.clone().fold( - WeightedSum::default(), + WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 |water_level, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { ( Some(kind/*RiverKind::River { cross_section }*/), @@ -345,7 +352,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { water_level.with(river_water_alt, near_river) }, // Slightly wider threshold is chosen in case the lake bounds are a bit wrong - RiverKind::Lake { .. } if river_edge_dist <= 16.0 => { + RiverKind::Lake { .. } if river_edge_dist <= 8.0 => { let lake_water_alt = Lerp::lerp( river_chunk.alt.max(river_chunk.water_alt), downhill_chunk.alt.max(downhill_chunk.water_alt), @@ -399,7 +406,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { const MIN_DEPTH: f32 = 0.5; let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; let riverbed_depth = near_centre * river_width as f32 * 0.35 + MIN_DEPTH; - alt.with_min(water_alt - riverbed_depth) + // Handle rivers debouching into the ocean nicely by 'flattening' their bottom + let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level)); + alt.with_min(riverbed_alt) } else { const GORGE: f32 = 0.5; const BANK_SCALE: f32 = 24.0; @@ -1223,7 +1232,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // Land ground, // Beach - ((ocean_level - 1.0) / 2.0).max(0.0), + ((ocean_level - 0.0) / 2.0).max(0.0), ), surface_veg, ), diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 90ccfce307..ee221edf32 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -603,7 +603,8 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { ]; canvas.foreach_col(|canvas, wpos2d, col| { - let underwater = col.alt < col.water_level; + // TODO: Why do we need to add 1.0 here? Idk... + let underwater = col.alt.floor() + 1.0 < col.water_level.floor(); let kind = scatter .iter() From 826aff2ea4c434fa0a1be69ae6796675bdc5067e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 3 Sep 2021 12:32:30 +0100 Subject: [PATCH 09/18] Better lake entrances and distance --- common/src/terrain/mod.rs | 36 ++++++++- world/src/column/mod.rs | 152 +++++++++++++++++++++----------------- 2 files changed, 117 insertions(+), 71 deletions(-) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 553c688ce5..6463eef1c4 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -295,6 +295,10 @@ pub fn quadratic_nearest_point( point: Vec2, line: Vec2>, ) -> Option<(f64, Vec2, f64)> { + let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z; + + // Linear + // let line = LineSegment2 { // start: line.x, // end: line.y, @@ -304,13 +308,37 @@ pub fn quadratic_nearest_point( // let pos = line.start + (line.end - line.start) * t; // return Some((t, pos, pos.distance_squared(point))); + // Quadratic + // let curve = QuadraticBezier2 { - // start: spline.x, - // ctrl: spline.y, - // end: spline.z, + // start: line.x, + // ctrl: eval_at(0.5), + // end: line.y, // }; // let (t, pos) = curve.binary_search_point_by_steps(point, 16, 0.001); - // return Some((t, pos, curve.evaluate(t).distance_squared(point))); + // let t = t.clamped(0.0, 1.0); + // let pos = curve.evaluate(t); + // return Some((t, pos, pos.distance_squared(point))); + + // Cubic + + let ctrl_at = |t: f64, end: f64| { + let a = eval_at(end); + let b = eval_at(Lerp::lerp(end, t, 0.1)); + let dir = (b - a).normalized(); + let exact = eval_at(t); + a + dir * exact.distance(a) + }; + let curve = CubicBezier2 { + start: line.x, + ctrl0: ctrl_at(0.33, 0.0), + ctrl1: ctrl_at(0.66, 1.0), + end: line.y, + }; + let (t, pos) = curve.binary_search_point_by_steps(point, 16, 0.001); + let t = t.clamped(0.0, 1.0); + let pos = curve.evaluate(t); + return Some((t, pos, pos.distance_squared(point))); let a = spline.z.x; let b = spline.y.x; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index b75655b015..5f2e48ebf0 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -51,6 +51,22 @@ pub struct Colors { pub tropical_high: (f32, f32, f32), } +fn cubic(x: f64) -> f64 { + if x < 0.5 { + 4.0 * x.powf(3.0) + } else { + 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 + } +} + +fn revcubic(x: f64) -> f64 { + if x < 0.5 { + (x / 4.0).powf(1.0 / 3.0) + } else { + 1.0 - (2.0 - 2.0 * x).powf(1.0 / 3.0) / 2.0 + } +} + impl<'a> ColumnGen<'a> { pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } } @@ -258,7 +274,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_width = Lerp::lerp( river_width_min, river_width_max, - river_t.max(0.0).min(1.0).sqrt(), + cubic(river_t.clamped(0.0, 1.0)),//.sqrt(), ); let river_width = river_width;// * (1.0 + river_width_noise * 0.3); @@ -313,27 +329,36 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { fn with_max(self, max: f32) -> Self { Self { max: Some(self.max.unwrap_or(max).max(max)), ..self } } + fn eval(&self) -> Option { + if self.weight > 0.0 { + let res = self.sum / self.weight; + let res = self.min.map_or(res, |m| m.min(res)); + let res = self.max.map_or(res, |m| m.max(res)); + Some(res) + } else { + None + } + + } fn eval_or(&self, default: f32) -> f32 { let res = if self.weight > 0.0 { self.sum / self.weight } else { default }; - let res = match self.min { - Some(min) => res.min(min), - None => res, - }; - let res = match self.max { - Some(max) => res.max(max), - None => res, - }; + let res = self.min.map_or(res, |m| m.min(res)); + let res = self.max.map_or(res, |m| m.max(res)); res } } - let water_level = neighbor_river_data.clone().fold( - WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 - |water_level, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + let (river_water_level, lake_water_level, water_dist) = neighbor_river_data.clone().fold( + ( + WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 + WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 + None, + ), + |(mut river_water_level, mut lake_water_level, water_dist), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { ( Some(kind/*RiverKind::River { cross_section }*/), Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), @@ -344,29 +369,49 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; // 0.0 = not near river, 1.0 = in middle of river let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + let near_center = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); match kind { - RiverKind::River { .. } if river_edge_dist <= 0.0 => { + RiverKind::River { .. } => { // Alt of river water *is* the alt of land let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); - water_level.with(river_water_alt, near_river) + river_water_level = river_water_level + .with(river_water_alt, near_center * 10.0); + // .with_max(river_water_alt) }, // Slightly wider threshold is chosen in case the lake bounds are a bit wrong - RiverKind::Lake { .. } if river_edge_dist <= 8.0 => { + RiverKind::Lake { .. } => { let lake_water_alt = Lerp::lerp( river_chunk.alt.max(river_chunk.water_alt), downhill_chunk.alt.max(downhill_chunk.water_alt), river_t as f32, ); - water_level.with_max(lake_water_alt) + + river_water_level = river_water_level + .with(lake_water_alt, near_center * 10.0); + + if river_edge_dist <= 0.0 { + lake_water_level = lake_water_level + .with(lake_water_alt, near_center * 10.0); + // .with_max(lake_water_alt); + } else if river_edge_dist <= 12.0 { + // lake_water_level = lake_water_level.with(lake_water_alt, near_river * 10.0); + } }, - _ => water_level, - } + RiverKind::Ocean => {}, + }; + + let water_dist = Some(water_dist.unwrap_or(river_edge_dist).min(river_edge_dist)); + + (river_water_level, lake_water_level, water_dist) }, - (_, _) => water_level, + (_, _) => (river_water_level, lake_water_level, water_dist), }, ); - let water_level = water_level.eval_or(CONFIG.sea_level); + let water_level = match (river_water_level.eval(), lake_water_level.eval()) { + (Some(r), Some(l)) => r.max(l), + (r, l) => r.or(l).unwrap_or(CONFIG.sea_level), + }; let riverless_alt = alt; let alt = neighbor_river_data.clone().fold( @@ -383,32 +428,32 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // 0.0 = not near river, 1.0 = in middle of river let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); - // Maximum depth of the river basin (including banks) - let river_basin = (river_width as f32 + 1.0).ln() * 1.5; - // River basin depth at the current column - let basin_depth = near_river * river_basin; - let water_alt = match kind { RiverKind::River { .. } => { // Alt of river water *is* the alt of land - Some(Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32)) + let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + Some((river_water_alt, None)) }, - RiverKind::Lake { .. } => Some(Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_chunk.alt.max(downhill_chunk.water_alt), - river_t as f32, - )), - _ => None, + RiverKind::Lake { .. } => { + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_chunk.alt.max(downhill_chunk.water_alt), + river_t as f32, + ); + + Some((lake_water_alt, Some(riverless_alt))) + }, + RiverKind::Ocean => Some((riverless_alt, Some(riverless_alt))), }; - if let Some(water_alt) = water_alt { + if let Some((water_alt, min_alt)) = water_alt { if river_edge_dist <= 0.0 { - const MIN_DEPTH: f32 = 0.5; + const MIN_DEPTH: f32 = 1.0; let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; - let riverbed_depth = near_centre * river_width as f32 * 0.35 + MIN_DEPTH; + let riverbed_depth = near_centre * river_width as f32 * 0.15 + MIN_DEPTH; // Handle rivers debouching into the ocean nicely by 'flattening' their bottom let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level)); - alt.with_min(riverbed_alt) + alt.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) } else { const GORGE: f32 = 0.5; const BANK_SCALE: f32 = 24.0; @@ -417,7 +462,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth // transition from normal terrain to the water. let weight = Lerp::lerp( - BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 1.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), + BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 2.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), ); @@ -433,33 +478,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { alt } - - // match kind { - // RiverKind::River { .. } => { - // // Alt of river water *is* the alt of land - // let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); - - // if river_edge_dist <= 0.0 { - // const GORGE: f32 = 1.0; - // let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; - // let riverbed_depth = near_centre * river_width as f32 * 0.35 + GORGE; - // alt.with_min(river_water_alt - riverbed_depth) - // } else { - // const BANK_SCALE: f32 = 8.0; - // const BANK_STRENGTH: f32 = 100.0; - // alt.with(river_water_alt, BANK_STRENGTH / (1.0 + river_edge_dist as f32 * BANK_STRENGTH / BANK_SCALE)) - // } - // }, - // RiverKind::Lake { .. } => { - // let lake_water_alt = Lerp::lerp( - // river_chunk.alt.max(river_chunk.water_alt), - // downhill_chunk.alt.max(downhill_chunk.water_alt), - // river_t as f32, - // ); - // alt - // }, - // _ => alt, - // } }, (_, _) => alt, }, @@ -667,7 +685,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; let river_gouge = 0.5; - let (_in_water, water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = if let Some( + let (_in_water, old_water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = if let Some( (max_border_river_pos, river_chunk, max_border_river, max_border_river_dist), ) = max_river @@ -920,8 +938,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let warp_factor = 0.0; let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); - let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta; - let alt = alt_ + riverless_alt_delta; + // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta; + let alt = alt/*alt_*/ + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; From 3b66af468aef0b0c0a6afa5dec4b7538b9a5715b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 4 Sep 2021 15:31:14 +0100 Subject: [PATCH 10/18] Improved river and lake banks --- common/src/terrain/mod.rs | 34 ++++++------ server/src/cmd.rs | 4 ++ world/src/column/mod.rs | 103 ++++++++++++++++++++----------------- world/src/layer/scatter.rs | 3 +- world/src/sim/mod.rs | 2 +- 5 files changed, 81 insertions(+), 65 deletions(-) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 6463eef1c4..a56aa38e8d 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -322,23 +322,23 @@ pub fn quadratic_nearest_point( // Cubic - let ctrl_at = |t: f64, end: f64| { - let a = eval_at(end); - let b = eval_at(Lerp::lerp(end, t, 0.1)); - let dir = (b - a).normalized(); - let exact = eval_at(t); - a + dir * exact.distance(a) - }; - let curve = CubicBezier2 { - start: line.x, - ctrl0: ctrl_at(0.33, 0.0), - ctrl1: ctrl_at(0.66, 1.0), - end: line.y, - }; - let (t, pos) = curve.binary_search_point_by_steps(point, 16, 0.001); - let t = t.clamped(0.0, 1.0); - let pos = curve.evaluate(t); - return Some((t, pos, pos.distance_squared(point))); + // let ctrl_at = |t: f64, end: f64| { + // let a = eval_at(end); + // let b = eval_at(Lerp::lerp(end, t, 0.1)); + // let dir = (b - a).normalized(); + // let exact = eval_at(t); + // a + dir * exact.distance(a) + // }; + // let curve = CubicBezier2 { + // start: line.x, + // ctrl0: ctrl_at(0.33, 0.0), + // ctrl1: ctrl_at(0.66, 1.0), + // end: line.y, + // }; + // let (t, pos) = curve.binary_search_point_by_steps(point, 12, 0.01); + // let t = t.clamped(0.0, 1.0); + // let pos = curve.evaluate(t); + // return Some((t, pos, pos.distance_squared(point))); let a = spline.z.x; let b = spline.y.x; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 375e4ccc08..7d6cf4156e 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2704,6 +2704,8 @@ temp {:?} humidity {:?} rockiness {:?} tree_density {:?} +in_river {:?} +in_lake {:?} spawn_rate {:?} "#, wpos, alt, @@ -2720,6 +2722,8 @@ spawn_rate {:?} "#, humidity, rockiness, tree_density, + col.in_river, + col.in_lake, spawn_rate )) }; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 5f2e48ebf0..3ba58351d5 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -139,7 +139,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let downhill_wpos = downhill_pos.map(|e| e as f64); let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e.div_euclid(sz as i32)); - let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef;// + neighbor_coef * 0.5; + let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; let direction = neighbor_wpos - downhill_wpos; let river_width_min = if let RiverKind::River { cross_section } = kind { cross_section.x as f64 @@ -150,7 +150,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let coeffs = river_spline_coeffs(neighbor_wpos, chunkj.river.spline_derivative, downhill_wpos); let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind { - RiverKind::River { .. } => { + RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) /*{ let curve = CubicBezier2 { start: neighbor_wpos, @@ -211,7 +211,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if pass_dist > 1 { return (posj, chunkj, river, None); } - let neighbor_pass_wpos = neighbor_pass_pos.map(|e| e as f64); + let neighbor_pass_wpos = neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; let neighbor_pass_pos = neighbor_pass_pos .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let coeffs = @@ -262,9 +262,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let river_width_max = if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind { + (cross_section.x as f64).min(river_width_min * 1.75) // Hack + } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind { cross_section.x as f64 } else { - lake_width + lake_width * 0.5 }; let river_width_noise = (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) .max(-1.0) @@ -274,10 +276,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_width = Lerp::lerp( river_width_min, river_width_max, - cubic(river_t.clamped(0.0, 1.0)),//.sqrt(), + // if matches!(chunkj.river.river_kind, Some(RiverKind::Lake { .. })) { + // (1.0 - (river_t.clamped(0.0, 1.0) * 2.0 - 1.0).powi(2)).sqrt() * 0.5 + // } else { + cubic(river_t.clamped(0.0, 1.0)) + // }, ); - let river_width = river_width;// * (1.0 + river_width_noise * 0.3); + let river_width = river_width.max(2.0f64.sqrt() + 0.1);// * (1.0 + river_width_noise * 0.3); // To find the distance, we just evaluate the quadratic equation at river_t and // see if it's within width (but we should be able to use it for a // lot more, and this probably isn't the very best approach anyway @@ -352,13 +358,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - let (river_water_level, lake_water_level, water_dist) = neighbor_river_data.clone().fold( + let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? + + let (river_water_level, in_river, lake_water_level, lake_dist, water_dist) = neighbor_river_data.clone().fold( ( - WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 - WeightedSum::default().with_max(CONFIG.sea_level + 2.0), // TODO: Don't add 2.0 + WeightedSum::default().with_max(actual_sea_level), // TODO: Don't add 1.0 + false, + WeightedSum::default().with_max(actual_sea_level), // TODO: Don't add 1.0 + 10000.0f32, None, ), - |(mut river_water_level, mut lake_water_level, water_dist), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + |(mut river_water_level, mut in_river, mut lake_water_level, mut lake_dist, water_dist), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { ( Some(kind/*RiverKind::River { cross_section }*/), Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), @@ -372,12 +382,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_center = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); match kind { - RiverKind::River { .. } => { + RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { // Alt of river water *is* the alt of land let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + river_water_level = river_water_level - .with(river_water_alt, near_center * 10.0); // .with_max(river_water_alt) + .with(river_water_alt, near_center); + + if river_edge_dist <= 0.0 { + in_river = true; + } }, // Slightly wider threshold is chosen in case the lake bounds are a bit wrong RiverKind::Lake { .. } => { @@ -388,30 +403,37 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ); river_water_level = river_water_level - .with(lake_water_alt, near_center * 10.0); + .with(lake_water_alt, near_center); - if river_edge_dist <= 0.0 { + lake_dist = lake_dist.min(river_edge_dist); + // if river_edge_dist <= 0.0 { + // in_lake = true; + // } + + // Lake border prevents a lake failing to propagate its altitude to nearby rivers + let border = if river_width >= lake_width * 0.9 { 5.0 } else { 0.0 }; + if river_edge_dist <= border { lake_water_level = lake_water_level - .with(lake_water_alt, near_center * 10.0); + // Make sure the closest lake is prioritised + .with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist)); // .with_max(lake_water_alt); - } else if river_edge_dist <= 12.0 { - // lake_water_level = lake_water_level.with(lake_water_alt, near_river * 10.0); } }, RiverKind::Ocean => {}, }; - let water_dist = Some(water_dist.unwrap_or(river_edge_dist).min(river_edge_dist)); + let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32; + let water_dist = Some(water_dist.unwrap_or(river_edge_dist_unclamped).min(river_edge_dist_unclamped)); - (river_water_level, lake_water_level, water_dist) + (river_water_level, in_river, lake_water_level, lake_dist, water_dist) }, - (_, _) => (river_water_level, lake_water_level, water_dist), + (_, _) => (river_water_level, in_river, lake_water_level, lake_dist, water_dist), }, ); - let water_level = match (river_water_level.eval(), lake_water_level.eval()) { + let water_level = match (river_water_level.eval(), lake_water_level.eval().filter(|_| lake_dist <= 0.0 || in_river)) { (Some(r), Some(l)) => r.max(l), - (r, l) => r.or(l).unwrap_or(CONFIG.sea_level), - }; + (r, l) => r.or(l).unwrap_or(actual_sea_level), + } - 0.1; let riverless_alt = alt; let alt = neighbor_river_data.clone().fold( @@ -429,7 +451,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); let water_alt = match kind { - RiverKind::River { .. } => { + RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { // Alt of river water *is* the alt of land let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); Some((river_water_alt, None)) @@ -443,7 +465,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some((lake_water_alt, Some(riverless_alt))) }, - RiverKind::Ocean => Some((riverless_alt, Some(riverless_alt))), + RiverKind::Ocean => None, }; if let Some((water_alt, min_alt)) = water_alt { @@ -452,7 +474,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; let riverbed_depth = near_centre * river_width as f32 * 0.15 + MIN_DEPTH; // Handle rivers debouching into the ocean nicely by 'flattening' their bottom - let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level)); + let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); alt.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) } else { const GORGE: f32 = 0.5; @@ -464,9 +486,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let weight = Lerp::lerp( BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 2.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, - (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), + cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) as f32, ); - let alt = alt.with(water_alt + GORGE, weight); + let alt = alt + .with(water_alt + GORGE, weight); // Add "walls" around weirdly back-curving segments to prevent water walls // let alt = if river_edge_dist <= 1.0 && river_t > 0.0 && river_t < 1.0 { // alt.with_max(water_alt + GORGE) @@ -1217,22 +1240,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // dirt let ground = Lerp::lerp(ground, sub_surface_color, marble_mid * tree_density); - let near_ocean = max_river.and_then(|(_, _, river_data, _)| { - if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean)) - && alt <= water_level.max(CONFIG.sea_level + 5.0) - { - Some(water_level) - } else { - None - } - }); - - let ocean_level = if let Some(_sea_level) = near_ocean { - alt - CONFIG.sea_level - } else { - 5.0 - }; - let path = sim.get_nearest_path(wpos); let cave = sim.get_nearest_cave(wpos); @@ -1246,11 +1253,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { surface_color: Rgb::lerp( sub_surface_color, Rgb::lerp( + // Beach Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)), // Land ground, - // Beach - ((ocean_level - 0.0) / 2.0).max(0.0), + ((alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0), ), surface_veg, ), @@ -1282,6 +1289,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { snow_cover, cliff_offset, cliff_height, + in_river, + in_lake: lake_dist <= 0.0, chunk: sim_chunk, }) @@ -1314,6 +1323,8 @@ pub struct ColumnSample<'a> { pub snow_cover: bool, pub cliff_offset: f32, pub cliff_height: f32, + pub in_river: bool, + pub in_lake: bool, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index ee221edf32..c98acaa2a3 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -343,7 +343,8 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { * col .water_dist .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0))) - .unwrap_or(0.0), + .unwrap_or(0.0) + * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0), Some((0.2, 128.0, 0.5)), ) }), diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 57573fcc63..342cd053e2 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2222,7 +2222,7 @@ impl SimChunk { Some( uniform_idx_as_vec2(map_size_lg, downhill_pre as usize) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32) - // + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2) + + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2) , ) }; From 22f48b197748806c261e4d7496b040cc891f1cb6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 4 Sep 2021 23:53:45 +0100 Subject: [PATCH 11/18] Added waterfalls and fixed warping issues --- world/src/column/mod.rs | 82 +++++++++++++++++++++++++++++--------- world/src/layer/scatter.rs | 2 +- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 3ba58351d5..162e20fcaf 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -51,14 +51,23 @@ pub struct Colors { pub tropical_high: (f32, f32, f32), } -fn cubic(x: f64) -> f64 { +fn power(x: f64, t: f64) -> f64 { if x < 0.5 { - 4.0 * x.powf(3.0) + (2.0 * x).powf(t) / 2.0 } else { - 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 + 1.0 - (-2.0 * x + 2.0).powf(t) / 2.0 } } +fn cubic(x: f64) -> f64 { + power(x, 3.0) + // if x < 0.5 { + // (2.0 * x).powf(3.0) / 2.0 + // } else { + // 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 + // } +} + fn revcubic(x: f64) -> f64 { if x < 0.5 { (x / 4.0).powf(1.0 / 3.0) @@ -114,7 +123,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())) + 12.0; + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 2.0; let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| { let kind = match river.river_kind { Some(kind) => kind, @@ -283,7 +292,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // }, ); - let river_width = river_width.max(2.0f64.sqrt() + 0.1);// * (1.0 + river_width_noise * 0.3); + let river_width = river_width.max(2.0f64.sqrt() + 0.1) * (1.0 + river_width_noise * 0.3); // To find the distance, we just evaluate the quadratic equation at river_t and // see if it's within width (but we should be able to use it for a // lot more, and this probably isn't the very best approach anyway @@ -358,6 +367,25 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 { + Lerp::lerp( + a, + b, + // if t < 0.5 { + // 0.0 + // } else { + // let t = (t - 0.5) * 2.0; + // t.powf(1.0 + (a - b).max(0.0) / 1.5) + // }, + // t.powf(1.0 / (1.0 + (a - b).max(0.0) / 3.0)), + if is_waterfall { + power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 + } else { + cubic(t as f64) as f32 + }, + ) + } + let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? let (river_water_level, in_river, lake_water_level, lake_dist, water_dist) = neighbor_river_data.clone().fold( @@ -384,7 +412,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { match kind { RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { // Alt of river water *is* the alt of land - let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + let river_water_alt = river_water_alt( + river_chunk.alt, + downhill_chunk.alt, + river_t as f32, + river_chunk_idx.sum() as u32 % 5 == 0, + ); river_water_level = river_water_level // .with_max(river_water_alt) @@ -411,7 +444,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // } // Lake border prevents a lake failing to propagate its altitude to nearby rivers - let border = if river_width >= lake_width * 0.9 { 5.0 } else { 0.0 }; + let border = if river_width >= lake_width * 0.9 { 10.0 } else { 0.0 }; if river_edge_dist <= border { lake_water_level = lake_water_level // Make sure the closest lake is prioritised @@ -433,7 +466,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let water_level = match (river_water_level.eval(), lake_water_level.eval().filter(|_| lake_dist <= 0.0 || in_river)) { (Some(r), Some(l)) => r.max(l), (r, l) => r.or(l).unwrap_or(actual_sea_level), - } - 0.1; + }; let riverless_alt = alt; let alt = neighbor_river_data.clone().fold( @@ -453,7 +486,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let water_alt = match kind { RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { // Alt of river water *is* the alt of land - let river_water_alt = Lerp::lerp(river_chunk.alt, downhill_chunk.alt, river_t as f32); + let river_water_alt = river_water_alt( + river_chunk.alt, + downhill_chunk.alt, + river_t as f32, + river_chunk_idx.sum() as u32 % 5 == 0, + ); Some((river_water_alt, None)) }, RiverKind::Lake { .. } => { @@ -468,18 +506,25 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { RiverKind::Ocean => None, }; + const BANK_STRENGTH: f32 = 100.0; if let Some((water_alt, min_alt)) = water_alt { if river_edge_dist <= 0.0 { const MIN_DEPTH: f32 = 1.0; - let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5);; - let riverbed_depth = near_centre * river_width as f32 * 0.15 + MIN_DEPTH; + let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + let waterfall_boost = if river_chunk_idx.sum() as u32 % 5 == 0 { + (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) / 20.0 + } else { + 0.0 + }; + let riverbed_depth = near_centre * river_width as f32 * 0.15 + MIN_DEPTH + waterfall_boost; // Handle rivers debouching into the ocean nicely by 'flattening' their bottom let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); - alt.with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) + alt + .with(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt), near_centre * BANK_STRENGTH) + .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) } else { const GORGE: f32 = 0.5; const BANK_SCALE: f32 = 24.0; - const BANK_STRENGTH: f32 = 100.0; // Weighting of this riverbank on nearby terrain (higher when closer to the river). This // 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth // transition from normal terrain to the water. @@ -488,11 +533,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 0.0, cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) as f32, ); - let alt = alt - .with(water_alt + GORGE, weight); - // Add "walls" around weirdly back-curving segments to prevent water walls - // let alt = if river_edge_dist <= 1.0 && river_t > 0.0 && river_t < 1.0 { - // alt.with_max(water_alt + GORGE) + let alt = alt.with(water_alt + GORGE, weight); + // let alt = if !in_river && lake_dist > 0.0 { + // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / 10.0) // } else { // alt // }; @@ -958,8 +1001,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ) }; // NOTE: To disable warp, uncomment this line. - let warp_factor = 0.0; + // let warp_factor = 0.0; + let warp_factor = warp_factor.min(water_dist.map_or(1.0, |d| d.max(0.0) / 64.0)); let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta; let alt = alt/*alt_*/ + riverless_alt_delta; diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index c98acaa2a3..2c905b172f 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -605,7 +605,7 @@ 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.alt.floor() + 1.0 < col.water_level.floor(); + let underwater = col.alt.floor() < col.water_level.floor(); let kind = scatter .iter() From 6c6b3e12449397dafbc07fd9f1ae1e7d3030fd3a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 5 Sep 2021 02:06:47 +0100 Subject: [PATCH 12/18] Better lake borders and river depth --- server/src/cmd.rs | 2 + world/src/column/mod.rs | 119 +++++++++++++++++++++++++++---------- world/src/layer/scatter.rs | 2 +- 3 files changed, 89 insertions(+), 34 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7d6cf4156e..b4320967b6 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2706,6 +2706,7 @@ rockiness {:?} tree_density {:?} in_river {:?} in_lake {:?} +unbounded_water_level {:?} spawn_rate {:?} "#, wpos, alt, @@ -2724,6 +2725,7 @@ spawn_rate {:?} "#, tree_density, col.in_river, col.in_lake, + col.unbounded_water_level, spawn_rate )) }; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 162e20fcaf..060643ab12 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -123,7 +123,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())) + 2.0; + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 6.0; let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| { let kind = match river.river_kind { Some(kind) => kind, @@ -226,7 +226,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let coeffs = river_spline_coeffs(neighbor_wpos, spline_derivative, neighbor_pass_wpos); let direction = neighbor_wpos - neighbor_pass_wpos; - if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, neighbor_pass_wpos)) { + + if matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. })) { + 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(neighbor_pass_pos).expect("Must already work"), + 0.5, + pos, + pos.distance(wposf), + ) + } else if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, neighbor_pass_wpos)) { ( direction, coeffs, @@ -273,7 +290,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind { (cross_section.x as f64).min(river_width_min * 1.75) // Hack } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind { - cross_section.x as f64 + Lerp::lerp(cross_section.x as f64, lake_width, 0.5) } else { lake_width * 0.5 }; @@ -367,6 +384,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + fn is_waterfall(chunk_pos: Vec2, river_chunk: &SimChunk, downhill_chunk: &SimChunk) -> bool { + (chunk_pos.sum() as u32 % 19 < 2 || matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))) && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0) + } + fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 { Lerp::lerp( a, @@ -381,22 +402,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if is_waterfall { power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 } else { - cubic(t as f64) as f32 + // cubic(t as f64) as f32 + t }, ) } let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? - let (river_water_level, in_river, lake_water_level, lake_dist, water_dist) = neighbor_river_data.clone().fold( + 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), // TODO: Don't add 1.0 + WeightedSum::default().with_max(actual_sea_level), false, - WeightedSum::default().with_max(actual_sea_level), // TODO: Don't add 1.0 + WeightedSum::default().with_max(actual_sea_level), 10000.0f32, None, + WeightedSum::default().with_max(actual_sea_level), ), - |(mut river_water_level, mut in_river, mut lake_water_level, mut lake_dist, water_dist), (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + |(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) { ( Some(kind/*RiverKind::River { cross_section }*/), Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), @@ -413,12 +436,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { // Alt of river water *is* the alt of land let river_water_alt = river_water_alt( - river_chunk.alt, - downhill_chunk.alt, + river_chunk.alt.max(river_chunk.water_alt), + downhill_chunk.alt.max(downhill_chunk.water_alt), river_t as f32, - river_chunk_idx.sum() as u32 % 5 == 0, + is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), ); + // if river_edge_dist > 0.0 { + // unbounded_water_level = unbounded_water_level + // .with(river_water_alt - (river_edge_dist - 2.0).max(0.0), 1.0 / (1.0 + river_edge_dist * 5.0)) + // .with_max(river_water_alt - (river_edge_dist - 2.0).max(0.0)); + // } + river_water_level = river_water_level // .with_max(river_water_alt) .with(river_water_alt, near_center); @@ -429,12 +458,20 @@ 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 = Lerp::lerp( + 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), ); + if river_edge_dist > 0.0 && river_width > lake_width * 0.99 /* !matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))*/ { + 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)) + .with_max(unbounded_water_alt); + } + river_water_level = river_water_level .with(lake_water_alt, near_center); @@ -444,8 +481,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // } // Lake border prevents a lake failing to propagate its altitude to nearby rivers - let border = if river_width >= lake_width * 0.9 { 10.0 } else { 0.0 }; - if river_edge_dist <= border { + let border = (river_width / lake_width) as f32 * 14.0; + if river_edge_dist <= 0.0/*border*/ { lake_water_level = lake_water_level // Make sure the closest lake is prioritised .with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist)); @@ -458,14 +495,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_edge_dist_unclamped = (river_dist - river_width * 0.5) as f32; let water_dist = Some(water_dist.unwrap_or(river_edge_dist_unclamped).min(river_edge_dist_unclamped)); - (river_water_level, in_river, lake_water_level, lake_dist, water_dist) + (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level) }, - (_, _) => (river_water_level, in_river, lake_water_level, lake_dist, water_dist), + (_, _) => (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 water_level = match (river_water_level.eval(), lake_water_level.eval().filter(|_| lake_dist <= 0.0 || in_river)) { (Some(r), Some(l)) => r.max(l), - (r, l) => r.or(l).unwrap_or(actual_sea_level), + (r, l) => r.or(l).unwrap_or(actual_sea_level).max(unbounded_water_level), }; let riverless_alt = alt; @@ -484,39 +522,45 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); let water_alt = match kind { - RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { + RiverKind::River { cross_section } => { // Alt of river water *is* the alt of land let river_water_alt = river_water_alt( - river_chunk.alt, - downhill_chunk.alt, - river_t as f32, - river_chunk_idx.sum() as u32 % 5 == 0, - ); - Some((river_water_alt, None)) - }, - RiverKind::Lake { .. } => { - let lake_water_alt = Lerp::lerp( 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), + ); + Some((river_water_alt, cross_section.y as f32, None)) + }, + 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), ); - Some((lake_water_alt, Some(riverless_alt))) + let depth = water_level - Lerp::lerp(riverless_alt.min(water_level), water_level - 4.0, 0.5); + + let min_alt = Lerp::lerp(riverless_alt, lake_water_alt, ((river_dist - 7.5) / (river_width * 0.5 - 7.5).max(0.01)).clamped(0.0, 1.0) as f32); + + Some((lake_water_alt, /*river_width as f32 * 0.15*/ depth, Some(min_alt))) }, RiverKind::Ocean => None, }; const BANK_STRENGTH: f32 = 100.0; - if let Some((water_alt, min_alt)) = water_alt { + if let Some((water_alt, water_depth, min_alt)) = water_alt { if river_edge_dist <= 0.0 { const MIN_DEPTH: f32 = 1.0; let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); - let waterfall_boost = if river_chunk_idx.sum() as u32 % 5 == 0 { + let waterfall_boost = if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) { (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) / 20.0 } else { 0.0 }; - let riverbed_depth = near_centre * river_width as f32 * 0.15 + MIN_DEPTH + waterfall_boost; + // let river_depth = cross_section.y as f32 /*river_width as f32 * 0.15*/; + let riverbed_depth = near_centre * water_depth + MIN_DEPTH + waterfall_boost; // Handle rivers debouching into the ocean nicely by 'flattening' their bottom let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); alt @@ -529,11 +573,18 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth // transition from normal terrain to the water. let weight = Lerp::lerp( - BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 2.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), + BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, - cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) as f32, + // cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) as f32, + (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), ); 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) + } else { + alt + }; // let alt = if !in_river && lake_dist > 0.0 { // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / 10.0) // } else { @@ -1335,6 +1386,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { cliff_height, in_river, in_lake: lake_dist <= 0.0, + unbounded_water_level, chunk: sim_chunk, }) @@ -1369,6 +1421,7 @@ pub struct ColumnSample<'a> { pub cliff_height: f32, pub in_river: bool, pub in_lake: bool, + pub unbounded_water_level: f32, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 2c905b172f..5d90140600 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -605,7 +605,7 @@ 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.alt.floor() < col.water_level.floor(); + let underwater = col.water_level.floor() > col.alt; let kind = scatter .iter() From c399fe9a395a4ca671905a10dc1091417b16900c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 6 Sep 2021 18:54:23 +0100 Subject: [PATCH 13/18] No snow on riverbeds --- world/src/column/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 060643ab12..52761482dc 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -542,7 +542,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let depth = water_level - Lerp::lerp(riverless_alt.min(water_level), water_level - 4.0, 0.5); - let min_alt = Lerp::lerp(riverless_alt, lake_water_alt, ((river_dist - 7.5) / (river_width * 0.5 - 7.5).max(0.01)).clamped(0.0, 1.0) as f32); + 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); Some((lake_water_alt, /*river_width as f32 * 0.15*/ depth, Some(min_alt))) }, @@ -1314,10 +1314,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .add(((marble - 0.5) / 0.5) * 0.5) .add(((marble_mid - 0.5) / 0.5) * 0.25) .add(((marble_small - 0.5) / 0.5) * 0.175); - let (alt, ground, sub_surface_color) = if snow_cover <= 0.0 { + let (alt, ground, sub_surface_color) = if snow_cover <= 0.0 && alt > water_level { // Allow snow cover. ( - alt + if alt > water_level { 1.0 - snow_cover.max(0.0) } else { 0.0 }, + alt + 1.0 - snow_cover.max(0.0), Rgb::lerp(snow, ground, snow_cover), Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)), ) From e8021fab2cdb95248a7033058a9a06435c6468ef Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Sep 2021 13:36:05 +0100 Subject: [PATCH 14/18] Cleaned up shrub implementation --- .../{shrubs.ron => shrubs/jungle.ron} | 12 +- .../{jungle-bush-0.vox => jungle/bush-0.vox} | 0 .../{jungle-bush-1.vox => jungle/bush-1.vox} | 0 .../{jungle-bush-2.vox => jungle/bush-2.vox} | 0 .../{jungle-bush-3.vox => jungle/bush-3.vox} | 0 .../{jungle-bush-4.vox => jungle/bush-4.vox} | 0 .../{jungle-bush-5.vox => jungle/bush-5.vox} | 0 common/src/terrain/map.rs | 7 +- common/src/terrain/mod.rs | 16 +- world/src/column/mod.rs | 595 ++++++++++-------- world/src/layer/shrub.rs | 21 +- world/src/sim/mod.rs | 81 ++- 12 files changed, 406 insertions(+), 326 deletions(-) rename assets/world/manifests/{shrubs.ron => shrubs/jungle.ron} (54%) rename assets/world/shrub/{jungle-bush-0.vox => jungle/bush-0.vox} (100%) rename assets/world/shrub/{jungle-bush-1.vox => jungle/bush-1.vox} (100%) rename assets/world/shrub/{jungle-bush-2.vox => jungle/bush-2.vox} (100%) rename assets/world/shrub/{jungle-bush-3.vox => jungle/bush-3.vox} (100%) rename assets/world/shrub/{jungle-bush-4.vox => jungle/bush-4.vox} (100%) rename assets/world/shrub/{jungle-bush-5.vox => jungle/bush-5.vox} (100%) diff --git a/assets/world/manifests/shrubs.ron b/assets/world/manifests/shrubs/jungle.ron similarity index 54% rename from assets/world/manifests/shrubs.ron rename to assets/world/manifests/shrubs/jungle.ron index b8cf355e61..594c8e4d22 100644 --- a/assets/world/manifests/shrubs.ron +++ b/assets/world/manifests/shrubs/jungle.ron @@ -6,27 +6,27 @@ center: (6, 6, 2), ), ( - specifier: "world.shrub.jungle-bush-0", + specifier: "world.shrub.jungle.bush-0", center: (5, 5, 3), ), ( - specifier: "world.shrub.jungle-bush-1", + specifier: "world.shrub.jungle.bush-1", center: (5, 5, 2), ), ( - specifier: "world.shrub.jungle-bush-2", + specifier: "world.shrub.jungle.bush-2", center: (5, 5, 3), ), ( - specifier: "world.shrub.jungle-bush-3", + specifier: "world.shrub.jungle.bush-3", center: (5, 5, 3), ), ( - specifier: "world.shrub.jungle-bush-4", + specifier: "world.shrub.jungle.bush-4", center: (5, 5, 4), ), ( - specifier: "world.shrub.jungle-bush-5", + specifier: "world.shrub.jungle.bush-5", center: (5, 5, 5), ), ] diff --git a/assets/world/shrub/jungle-bush-0.vox b/assets/world/shrub/jungle/bush-0.vox similarity index 100% rename from assets/world/shrub/jungle-bush-0.vox rename to assets/world/shrub/jungle/bush-0.vox diff --git a/assets/world/shrub/jungle-bush-1.vox b/assets/world/shrub/jungle/bush-1.vox similarity index 100% rename from assets/world/shrub/jungle-bush-1.vox rename to assets/world/shrub/jungle/bush-1.vox diff --git a/assets/world/shrub/jungle-bush-2.vox b/assets/world/shrub/jungle/bush-2.vox similarity index 100% rename from assets/world/shrub/jungle-bush-2.vox rename to assets/world/shrub/jungle/bush-2.vox diff --git a/assets/world/shrub/jungle-bush-3.vox b/assets/world/shrub/jungle/bush-3.vox similarity index 100% rename from assets/world/shrub/jungle-bush-3.vox rename to assets/world/shrub/jungle/bush-3.vox diff --git a/assets/world/shrub/jungle-bush-4.vox b/assets/world/shrub/jungle/bush-4.vox similarity index 100% rename from assets/world/shrub/jungle-bush-4.vox rename to assets/world/shrub/jungle/bush-4.vox diff --git a/assets/world/shrub/jungle-bush-5.vox b/assets/world/shrub/jungle/bush-5.vox similarity index 100% rename from assets/world/shrub/jungle-bush-5.vox rename to assets/world/shrub/jungle/bush-5.vox diff --git a/common/src/terrain/map.rs b/common/src/terrain/map.rs index b134e40e83..3f23b0fd53 100644 --- a/common/src/terrain/map.rs +++ b/common/src/terrain/map.rs @@ -551,8 +551,11 @@ impl<'a> MapConfig<'a> { downhill_wpos, ); let (_t, _pt, dist) = if let Some((t, pt, dist)) = - quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) - { + quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, downhill_wpos), + ) { (t, pt, dist) } else { let ndist = wposf.distance_squared(neighbor_wpos); diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index a56aa38e8d..719df4d3a3 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -304,9 +304,9 @@ pub fn quadratic_nearest_point( // end: line.y, // }; // let len_sq = line.start.distance_squared(line.end); - // let t = ((point - line.start).dot(line.end - line.start) / len_sq).clamped(0.0, 1.0); - // let pos = line.start + (line.end - line.start) * t; - // return Some((t, pos, pos.distance_squared(point))); + // let t = ((point - line.start).dot(line.end - line.start) / + // len_sq).clamped(0.0, 1.0); let pos = line.start + (line.end - line.start) + // * t; return Some((t, pos, pos.distance_squared(point))); // Quadratic @@ -406,9 +406,9 @@ pub fn quadratic_nearest_point( .unwrap() }); min_root - // .map(|(t, pt, dist)| { - // let t = t.clamped(0.0, 1.0); - // let pos = spline.x * t * t + spline.y * t + spline.z; - // (t, pos, pos.distance_squared(point)) - // }) + // .map(|(t, pt, dist)| { + // let t = t.clamped(0.0, 1.0); + // let pos = spline.x * t * t + spline.y * t + spline.z; + // (t, pos, pos.distance_squared(point)) + // }) } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 52761482dc..4cc7171c88 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -309,7 +309,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // }, ); - let river_width = river_width.max(2.0f64.sqrt() + 0.1) * (1.0 + river_width_noise * 0.3); + let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3); // To find the distance, we just evaluate the quadratic equation at river_t and // see if it's within width (but we should be able to use it for a // lot more, and this probably isn't the very best approach anyway @@ -344,7 +344,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .unwrap_or(CONFIG.sea_level); #[derive(Default)] - struct WeightedSum { sum: T, weight: T, min: Option, max: Option } + struct WeightedSum { + sum: T, + weight: T, + min: Option, + max: Option, + } impl WeightedSum { fn with(self, value: f32, weight: f32) -> Self { Self { @@ -353,14 +358,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ..self } } + /// With an upper bound fn with_min(self, min: f32) -> Self { - Self { min: Some(self.min.unwrap_or(min).min(min)), ..self } + Self { + min: Some(self.min.unwrap_or(min).min(min)), + ..self + } } + /// With a lower bound fn with_max(self, max: f32) -> Self { - Self { max: Some(self.max.unwrap_or(max).max(max)), ..self } + Self { + max: Some(self.max.unwrap_or(max).max(max)), + ..self + } } + fn eval(&self) -> Option { if self.weight > 0.0 { let res = self.sum / self.weight; @@ -370,8 +384,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { None } - } + fn eval_or(&self, default: f32) -> f32 { let res = if self.weight > 0.0 { self.sum / self.weight @@ -384,8 +398,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - fn is_waterfall(chunk_pos: Vec2, river_chunk: &SimChunk, downhill_chunk: &SimChunk) -> bool { - (chunk_pos.sum() as u32 % 19 < 2 || matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }))) && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0) + fn is_waterfall( + chunk_pos: Vec2, + river_chunk: &SimChunk, + downhill_chunk: &SimChunk, + ) -> bool { + (chunk_pos.sum() as u32 % 19 < 2 + || matches!( + downhill_chunk.river.river_kind, + Some(RiverKind::Lake { .. }) + )) + && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0) } fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 { @@ -501,17 +524,28 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }, ); let unbounded_water_level = unbounded_water_level.eval_or(actual_sea_level); - let water_level = match (river_water_level.eval(), lake_water_level.eval().filter(|_| lake_dist <= 0.0 || in_river)) { + let water_level = match ( + river_water_level.eval(), + lake_water_level + .eval() + .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(actual_sea_level) + .max(unbounded_water_level), }; let riverless_alt = alt; let alt = neighbor_river_data.clone().fold( WeightedSum::default().with(riverless_alt, 1.0), - |alt, (river_chunk_idx, river_chunk, river, dist_info)| match (river.river_kind, dist_info) { + |alt, (river_chunk_idx, river_chunk, river, dist_info)| match ( + river.river_kind, + dist_info, + ) { ( - Some(kind/*RiverKind::River { cross_section }*/), + Some(kind /* RiverKind::River { cross_section } */), Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), ) => { // Distance from river center @@ -519,7 +553,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // Distance from edge of river let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; // 0.0 = not near river, 1.0 = in middle of river - let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + let near_river = ((river_dist / river_width) as f32) + .min(1.0) + .mul(f32::consts::PI) + .cos() + .add(1.0) + .mul(0.5); let water_alt = match kind { RiverKind::River { cross_section } => { @@ -540,11 +579,25 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), ); - let depth = water_level - Lerp::lerp(riverless_alt.min(water_level), water_level - 4.0, 0.5); + let depth = water_level + - Lerp::lerp( + riverless_alt.min(water_level), + water_level - 4.0, + 0.5, + ); - 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); + 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, + ); - Some((lake_water_alt, /*river_width as f32 * 0.15*/ depth, Some(min_alt))) + Some(( + lake_water_alt, + /* river_width as f32 * 0.15 */ depth, + Some(min_alt), + )) }, RiverKind::Ocean => None, }; @@ -553,29 +606,48 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if let Some((water_alt, water_depth, min_alt)) = water_alt { if river_edge_dist <= 0.0 { const MIN_DEPTH: f32 = 1.0; - let near_centre = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); - let waterfall_boost = if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) { - (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) / 20.0 - } else { - 0.0 - }; - // let river_depth = cross_section.y as f32 /*river_width as f32 * 0.15*/; - let riverbed_depth = near_centre * water_depth + MIN_DEPTH + waterfall_boost; - // Handle rivers debouching into the ocean nicely by 'flattening' their bottom - let riverbed_alt = (water_alt - riverbed_depth).max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); - alt - .with(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt), near_centre * BANK_STRENGTH) - .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) + let near_centre = ((river_dist / (river_width * 0.5)) as f32) + .min(1.0) + .mul(f32::consts::PI) + .cos() + .add(1.0) + .mul(0.5); + let waterfall_boost = + if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) { + (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) + * (1.0 - (river_t as f32 - 0.5).abs() * 2.0).powf(3.5) + / 20.0 + } else { + 0.0 + }; + // let river_depth = cross_section.y as f32 /*river_width as f32 * + // 0.15*/; + let riverbed_depth = + near_centre * water_depth + MIN_DEPTH + waterfall_boost; + // Handle rivers debouching into the ocean nicely by 'flattening' their + // bottom + let riverbed_alt = (water_alt - riverbed_depth) + .max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); + alt.with( + min_alt.unwrap_or(riverbed_alt).min(riverbed_alt), + near_centre * BANK_STRENGTH, + ) + .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) } else { const GORGE: f32 = 0.5; const BANK_SCALE: f32 = 24.0; - // Weighting of this riverbank on nearby terrain (higher when closer to the river). This - // 'pulls' the riverbank toward the river's altitude to make sure that we get a smooth + // Weighting of this riverbank on nearby terrain (higher when closer to + // the river). This 'pulls' the riverbank + // toward the river's altitude to make sure that we get a smooth // transition from normal terrain to the water. let weight = Lerp::lerp( - BANK_STRENGTH / (1.0 + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), + BANK_STRENGTH + / (1.0 + + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH + / BANK_SCALE), 0.0, - // cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) as f32, + // cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) + // as f32, (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), ); let alt = alt.with(water_alt + GORGE, weight); @@ -586,8 +658,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { alt }; // let alt = if !in_river && lake_dist > 0.0 { - // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / 10.0) - // } else { + // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / + // 10.0) } else { // alt // }; alt @@ -758,11 +830,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let alt_for_river = alt; - // + if overlap_count == 0.0 { - // 0.0 - // } else { - // river_overlap_distance_product / overlap_count - // } as f32; + // + if overlap_count == 0.0 { + // 0.0 + // } else { + // river_overlap_distance_product / overlap_count + // } as f32; 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(), @@ -802,261 +874,238 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; let river_gouge = 0.5; - let (_in_water, old_water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = if let Some( - (max_border_river_pos, river_chunk, max_border_river, max_border_river_dist), - ) = - max_river - { - // This is flowing into a lake, or a lake, or is at least a non-ocean tile. - // - // If we are <= water_alt, we are in the lake; otherwise, we are flowing into - // it. - match max_border_river.river_kind { - Some(RiverKind::River { cross_section }) => 'block: { - if max_border_river_dist.map(|(_, dist, _, _)| dist) - != Some(Vec2::zero()) - { - let (_, _, river_width, (_, (river_pos, _), _)) = - max_border_river_dist.unwrap(); - let river_dist = wposf.distance(river_pos); + let (_in_water, old_water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = + if let Some(( + max_border_river_pos, + river_chunk, + max_border_river, + max_border_river_dist, + )) = max_river + { + // This is flowing into a lake, or a lake, or is at least a non-ocean tile. + // + // If we are <= water_alt, we are in the lake; otherwise, we are flowing into + // it. + match max_border_river.river_kind { + Some(RiverKind::River { cross_section }) => 'block: { + if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { + let (_, _, river_width, (_, (river_pos, _), _)) = + max_border_river_dist.unwrap(); + let river_dist = wposf.distance(river_pos); - // FIXME: Make water altitude accurate. - break 'block ( - river_scale_factor <= 1.0, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip,//alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - river_scale_factor as f32, - ); - } - let ( - _, - _, - river_width, - (river_t, (river_pos, _), downhill_river_chunk), - ) = max_border_river_dist.unwrap(); - let river_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - let new_alt = river_alt - river_gouge; - let river_dist = wposf.distance(river_pos); - let river_height_factor = river_dist / (river_width * 0.5); - - // This is not a good way to determine river depth, but we do it - // anyway. TODO: Make the erosion - // sim output better river depths through `cross_section`. - let river_depth = river_width as f32 * 0.5; - - let valley_alt = Lerp::lerp( - new_alt - (cross_section.y * river_depth).max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ); - - ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip,//valley_alt.min(alt), - new_alt.max(valley_alt.min(alt) + 1.0), - alt, //river_alt + cross_section.y.max(1.0), - river_scale_factor as f32,//0.0, - ) - }, - Some(RiverKind::Ocean) => 'block: { - let ( - _, - dist, - river_width, - (river_t, (river_pos, _), downhill_river_chunk), - ) = if let Some(dist) = max_border_river_dist { - dist - } else { - error!( - ?max_border_river, - ?chunk_pos, - ?max_border_river_pos, - "downhill error details" - ); - panic!( - "Oceans should definitely have a downhill! \ - ...Right?" - ); - }; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ) + 1.0; - - if dist == Vec2::zero() { - let river_dist = wposf.distance(river_pos); - let _river_height_factor = - river_dist / (river_width * 0.5); - break 'block ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip,//alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt, - alt, //alt_for_river.max(lake_water_alt), - 0.0, - ); - } - - ( - river_scale_factor <= 1.0, - Some( - (wposf.distance(river_pos) - river_width * 0.5) - as f32, - ), - alt_for_river - max_dip,//alt_for_river, - downhill_water_alt + 1.0, - alt, //alt_for_river, - river_scale_factor as f32, - ) - }, - Some(RiverKind::Lake { .. }) => 'block: { - let lake_chunk = max_border_river_pos.map(|e| e as f64); - let lake_aabr = Aabr { - min: lake_chunk * neighbor_coef, - max: (lake_chunk + 1.0) * neighbor_coef, - }; - let lake_dist = lake_aabr.distance_to_point(wposf); - let downhill_river_chunk = max_border_river_pos; - let lake_id_dist = downhill_river_chunk - chunk_pos; - let in_bounds = lake_id_dist.x >= -1 - && lake_id_dist.y >= -1 - && lake_id_dist.x <= 1 - && lake_id_dist.y <= 1; - let in_bounds = in_bounds - && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); - let (_, dist, _, (river_t, _, downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else if lake_dist - <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = 0.0; + // FIXME: Make water altitude accurate. break 'block ( - in_bounds - || downhill_water_alt - .max(river_chunk.water_alt) - > alt_for_river, - Some(lake_dist as f32), - alt_for_river - max_dip,//alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - - river_gouge), - alt_for_river, - river_scale_factor as f32 - * (1.0 - gouge_factor), - ); - } else { - break 'block ( - false, - Some(lake_dist as f32), - alt_for_river - max_dip,//alt_for_river, + river_scale_factor <= 1.0, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip, //alt_for_river, downhill_water_alt, - alt_for_river, + alt, //alt_for_river, river_scale_factor as f32, ); - }; + } + let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = + max_border_river_dist.unwrap(); + let river_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), + river_t as f32, + ); + let new_alt = river_alt - river_gouge; + let river_dist = wposf.distance(river_pos); + let river_height_factor = river_dist / (river_width * 0.5); - let lake_dist = dist.y; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ); - // if dist == Vec2::zero() { - // return ( - // true, - // Some(lake_dist as f32), - // alt_for_river, - // // .min(lake_water_alt - 1.0 - river_gouge), - // lake_water_alt - river_gouge, - // alt_for_river.max(lake_water_alt), - // 0.0, - // ); - // } - if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = if in_bounds && lake_dist <= 1.0 { - 1.0 - } else { - 0.0 - }; - let in_bounds_ = lake_dist - <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; - if gouge_factor == 1.0 { + // This is not a good way to determine river depth, but we do it + // anyway. TODO: Make the erosion + // sim output better river depths through `cross_section`. + let river_depth = river_width as f32 * 0.5; + + let valley_alt = Lerp::lerp( + new_alt - (cross_section.y * river_depth).max(1.0), + new_alt - 1.0, + (river_height_factor * river_height_factor) as f32, + ); + + ( + true, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip, //valley_alt.min(alt), + new_alt.max(valley_alt.min(alt) + 1.0), + alt, //river_alt + cross_section.y.max(1.0), + river_scale_factor as f32, //0.0, + ) + }, + Some(RiverKind::Ocean) => 'block: { + let (_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = + if let Some(dist) = max_border_river_dist { + dist + } else { + error!( + ?max_border_river, + ?chunk_pos, + ?max_border_river_pos, + "downhill error details" + ); + panic!("Oceans should definitely have a downhill! ...Right?"); + }; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), + river_t as f32, + ) + 1.0; + + if dist == Vec2::zero() { + let river_dist = wposf.distance(river_pos); + let _river_height_factor = river_dist / (river_width * 0.5); break 'block ( true, - Some(lake_dist as f32), - alt_for_river - max_dip,//alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - downhill_water_alt.max(lake_water_alt) - - river_gouge, - alt.max(lake_water_alt), - 0.0,// river_scale_factor as f32* (1.0 - gouge_factor), - ); - } else { - break 'block ( - true, - Some(lake_dist as f32), - alt_for_river - max_dip,//alt_for_river, - if in_bounds_ { - downhill_water_alt.max(lake_water_alt) - } else { - downhill_water_alt//.max(lake_water_alt) - }.max(river_chunk.water_alt) - river_gouge, - alt_for_river, - river_scale_factor as f32 - * (1.0 - gouge_factor), + Some((river_dist - river_width * 0.5) as f32), + alt_for_river - max_dip, //alt_for_river, + // .min(lake_water_alt - 1.0 - river_gouge), + lake_water_alt, + alt, //alt_for_river.max(lake_water_alt), + 0.0, ); } - } - ( - river_scale_factor <= 1.0, - Some(lake_dist as f32), - alt_for_river - max_dip,//alt_for_river.max(lake_water_alt).max(river_chunk.water_alt), + + ( + river_scale_factor <= 1.0, + Some((wposf.distance(river_pos) - river_width * 0.5) as f32), + alt_for_river - max_dip, //alt_for_river, + downhill_water_alt + 1.0, + alt, //alt_for_river, + river_scale_factor as f32, + ) + }, + Some(RiverKind::Lake { .. }) => 'block: { + let lake_chunk = max_border_river_pos.map(|e| e as f64); + let lake_aabr = Aabr { + min: lake_chunk * neighbor_coef, + max: (lake_chunk + 1.0) * neighbor_coef, + }; + let lake_dist = lake_aabr.distance_to_point(wposf); + let downhill_river_chunk = max_border_river_pos; + let lake_id_dist = downhill_river_chunk - chunk_pos; + let in_bounds = lake_id_dist.x >= -1 + && lake_id_dist.y >= -1 + && lake_id_dist.x <= 1 + && lake_id_dist.y <= 1; + let in_bounds = in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); + let (_, dist, _, (river_t, _, downhill_river_chunk)) = + if let Some(dist) = max_border_river_dist { + dist + } else if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 + || in_bounds + { + let gouge_factor = 0.0; + break 'block ( + in_bounds + || downhill_water_alt.max(river_chunk.water_alt) + > alt_for_river, + Some(lake_dist as f32), + alt_for_river - max_dip, //alt_for_river, + (downhill_water_alt.max(river_chunk.water_alt) - river_gouge), + alt_for_river, + river_scale_factor as f32 * (1.0 - gouge_factor), + ); + } else { + break 'block ( + false, + Some(lake_dist as f32), + alt_for_river - max_dip, //alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + ); + }; + + let lake_dist = dist.y; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), + river_t as f32, + ); + // if dist == Vec2::zero() { + // return ( + // true, + // Some(lake_dist as f32), + // alt_for_river, + // // .min(lake_water_alt - 1.0 - river_gouge), + // lake_water_alt - river_gouge, + // alt_for_river.max(lake_water_alt), + // 0.0, + // ); + // } + if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 || in_bounds { + let gouge_factor = if in_bounds && lake_dist <= 1.0 { + 1.0 + } else { + 0.0 + }; + let in_bounds_ = + lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; + if gouge_factor == 1.0 { + break 'block ( + true, + Some(lake_dist as f32), + alt_for_river - max_dip, //alt_for_river, + // .min(lake_water_alt - 1.0 - river_gouge), + downhill_water_alt.max(lake_water_alt) - river_gouge, + alt.max(lake_water_alt), + 0.0, // river_scale_factor as f32* (1.0 - gouge_factor), + ); + } else { + break 'block ( + true, + Some(lake_dist as f32), + alt_for_river - max_dip, //alt_for_river, + if in_bounds_ { + downhill_water_alt.max(lake_water_alt) + } else { + downhill_water_alt //.max(lake_water_alt) + } + .max(river_chunk.water_alt) + - river_gouge, + alt_for_river, + river_scale_factor as f32 * (1.0 - gouge_factor), + ); + } + } + ( + river_scale_factor <= 1.0, + Some(lake_dist as f32), + alt_for_river - max_dip, /* alt_for_river.max(lake_water_alt). + * max(river_chunk.water_alt), */ + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + ) + }, + None => ( + false, + None, + alt_for_river - max_dip, //alt_for_river, downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ) - }, - None => ( + alt, //alt_for_river, + 1.0, + ), + } + } else { + ( false, None, - alt_for_river - max_dip,//alt_for_river, + alt_for_river - max_dip, //alt_for_river, downhill_water_alt, alt, //alt_for_river, 1.0, - ), - } - } else { - ( - false, - None, - alt_for_river - max_dip,//alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - 1.0, - ) - }; + ) + }; // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; let warp_factor = warp_factor.min(water_dist.map_or(1.0, |d| d.max(0.0) / 64.0)); let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); - // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta; + // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + + // riverless_alt_delta; let alt = alt/*alt_*/ + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index f28a28071a..da51b5ade9 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -1,4 +1,5 @@ use crate::{ + all::ForestKind, util::{seed_expan, RandomPerm, Sampler, StructureGen2d, UnitChooser}, Canvas, }; @@ -14,12 +15,13 @@ use rand_chacha::ChaChaRng; use vek::*; lazy_static! { - static ref SHRUBS: AssetHandle = Structure::load_group("shrubs"); + static ref JUNGLE_SHRUBS: AssetHandle = Structure::load_group("shrubs.jungle"); } struct Shrub { wpos: Vec3, seed: u32, + kind: ForestKind, } pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { @@ -33,7 +35,10 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { shrub_cache.entry(wpos).or_insert_with(|| { let col = info.col_or_gen(wpos)?; - if RandomPerm::new(seed).chance(37, col.tree_density * 0.3) + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + + const BASE_SHRUB_DENSITY: f64 = 0.15; + if rng.gen_bool((BASE_SHRUB_DENSITY * col.tree_density as f64).clamped(0.0, 1.0)) && col.water_dist.map_or(true, |d| d > 8.0) && col.alt > col.water_level && col.spawn_rate > 0.9 @@ -42,6 +47,11 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { Some(Shrub { wpos: wpos.with_z(col.alt as i32), seed, + kind: *info + .chunks() + .make_forest_lottery(wpos) + .choose_seeded(seed) + .as_ref()?, }) } else { None @@ -55,7 +65,12 @@ pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { let units = UnitChooser::new(shrub.seed).get(shrub.seed).into(); - let shrubs = SHRUBS.read(); + let shrubs = match shrub.kind { + ForestKind::Mangrove => &JUNGLE_SHRUBS, + _ => continue, // TODO: Add more shrub varieties + } + .read(); + let structure = shrubs.choose(&mut rng).unwrap(); canvas.blit_structure(shrub.wpos, structure, shrub.seed, units, true); } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 342cd053e2..0e430315b3 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2056,47 +2056,45 @@ impl WorldSim { self.get_nearest_way(wpos, |chunk| Some(chunk.cave)) } + /// Create a [`Lottery>`] that generates [`ForestKind`]s + /// according to the conditions at the given position. If no or fewer + /// trees are appropriate for the conditions, `None` may be generated. + pub fn make_forest_lottery(&self, wpos: Vec2) -> Lottery> { + let chunk = if let Some(chunk) = self.get_wpos(wpos) { + chunk + } else { + return Lottery::from(vec![(1.0, None)]); + }; + let env = chunk.get_environment(); + Lottery::from( + ForestKind::into_enum_iter() + .enumerate() + .map(|(i, fk)| { + const CLUSTER_SIZE: f64 = 48.0; + let nz = (FastNoise2d::new(i as u32 * 37) + .get(wpos.map(|e| e as f64) / CLUSTER_SIZE) + + 1.0) + / 2.0; + (fk.proclivity(&env) * nz, Some(fk)) + }) + .chain(std::iter::once((0.001, None))) + .collect::>(), + ) + } + /// Return an iterator over candidate tree positions (note that only some of /// these will become trees since environmental parameters may forbid /// them spawning). pub fn get_near_trees(&self, wpos: Vec2) -> impl Iterator + '_ { // Deterministic based on wpos let normal_trees = std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos)) - .filter_map(move |(pos, seed)| { - let chunk = self.get_wpos(pos)?; - let env = Environment { - humid: chunk.humidity, - temp: chunk.temp, - near_water: if chunk.river.is_lake() - || chunk.river.near_river() - || chunk.alt < CONFIG.sea_level + 6.0 - // Close to sea in altitude - { - 1.0 - } else { - 0.0 - }, - }; + .filter_map(move |(wpos, seed)| { + let lottery = self.make_forest_lottery(wpos); Some(TreeAttr { - pos, + pos: wpos, seed, scale: 1.0, - forest_kind: *Lottery::from( - ForestKind::into_enum_iter() - .enumerate() - .map(|(i, fk)| { - const CLUSTER_SIZE: f64 = 48.0; - let nz = (FastNoise2d::new(i as u32 * 37) - .get(pos.map(|e| e as f64) / CLUSTER_SIZE) - + 1.0) - / 2.0; - (fk.proclivity(&env) * nz, Some(fk)) - }) - .chain(std::iter::once((0.001, None))) - .collect::>(), - ) - .choose_seeded(seed) - .as_ref()?, + forest_kind: *lottery.choose_seeded(seed).as_ref()?, inhabited: false, }) }); @@ -2222,8 +2220,7 @@ impl SimChunk { Some( uniform_idx_as_vec2(map_size_lg, downhill_pre as usize) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32) - + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2) - , + + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2), ) }; @@ -2424,4 +2421,20 @@ impl SimChunk { } pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 } + + pub fn get_environment(&self) -> Environment { + Environment { + humid: self.humidity, + temp: self.temp, + near_water: if self.river.is_lake() + || self.river.near_river() + || self.alt < CONFIG.sea_level + 6.0 + // Close to sea in altitude + { + 1.0 + } else { + 0.0 + }, + } + } } From c6d3137612255fdb498a59ec24e366752f91b5d6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Sep 2021 15:16:47 +0100 Subject: [PATCH 15/18] Fixed sail boat thrust, removed dead water code, added comments --- common/src/comp/body/ship.rs | 7 +- common/src/states/utils.rs | 7 +- common/src/terrain/mod.rs | 25 +- server/src/cmd.rs | 6 - world/src/column/mod.rs | 631 +++++++---------------------------- world/src/layer/shrub.rs | 7 +- 6 files changed, 137 insertions(+), 546 deletions(-) diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 375645db08..2165d416cf 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -83,12 +83,7 @@ impl Body { pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) } - pub fn can_fly(&self) -> bool { - match self { - Body::DefaultAirship | Body::AirBalloon => true, - _ => false, - } - } + pub fn can_fly(&self) -> bool { matches!(self, Body::DefaultAirship | Body::AirBalloon) } pub fn has_water_thrust(&self) -> bool { !self.can_fly() // TODO: Differentiate this more carefully diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 5f7b20790f..655f6b3e8f 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -173,6 +173,7 @@ impl Body { quadruped_low::Species::Lavadrake => 1.7, _ => 2.0, }, + Body::Ship(ship) if ship.has_water_thrust() => 0.1, Body::Ship(_) => 0.035, } } @@ -180,7 +181,7 @@ impl Body { /// Returns thrust force if the body type can swim, otherwise None pub fn swim_thrust(&self) -> Option { match self { - Body::Object(_) | Body::Ship(_) => None, + Body::Object(_) => None, Body::BipedLarge(_) | Body::Golem(_) => Some(200.0 * self.mass().0), Body::BipedSmall(_) => Some(100.0 * self.mass().0), Body::BirdMedium(_) => Some(50.0 * self.mass().0), @@ -200,8 +201,8 @@ impl Body { Body::QuadrupedLow(_) => Some(300.0 * self.mass().0), Body::QuadrupedMedium(_) => Some(300.0 * self.mass().0), Body::QuadrupedSmall(_) => Some(300.0 * self.mass().0), - Body::Ship(ship) if ship.has_water_thrust() => Some(500.0 * self.mass().0), - _ => None, + Body::Ship(ship) if ship.has_water_thrust() => Some(750.0 * self.mass().0), + Body::Ship(_) => None, } } diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 719df4d3a3..d5d8d87042 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -293,9 +293,9 @@ pub fn river_spline_coeffs( pub fn quadratic_nearest_point( spline: &Vec3>, point: Vec2, - line: Vec2>, + _line: Vec2>, // Used for alternative distance functions below ) -> Option<(f64, Vec2, f64)> { - let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z; + //let eval_at = |t: f64| spline.x * t * t + spline.y * t + spline.z; // Linear @@ -374,25 +374,15 @@ pub fn quadratic_nearest_point( let min_root = roots .iter() .copied() - // .chain((0..30).map(|i| i as f64 / 30.0)) - .filter_map(|root| { + .map(|root| { let river_point = spline.x * root * root + spline.y * root + spline.z; - let river_zero = spline.z; - let river_one = spline.x + spline.y + spline.z; if root > 0.0 && root < 1.0 { - Some((root, river_point)) + (root, river_point) } else { let root = root.clamped(0.0, 1.0); let river_point = spline.x * root * root + spline.y * root + spline.z; - Some((root, river_point)) + (root, river_point) } - // } else if river_point.distance_squared(river_zero) < 0.5 { - // Some((root, /*river_point*/ river_zero)) - // } else if river_point.distance_squared(river_one) < 0.5 { - // Some((root, /*river_point*/ river_one)) - // } else { - // None - // } }) .map(|(root, river_point)| { let river_distance = river_point.distance_squared(point); @@ -406,9 +396,4 @@ pub fn quadratic_nearest_point( .unwrap() }); min_root - // .map(|(t, pt, dist)| { - // let t = t.clamped(0.0, 1.0); - // let pos = spline.x * t * t + spline.y * t + spline.z; - // (t, pos, pos.distance_squared(point)) - // }) } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b4320967b6..375e4ccc08 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2704,9 +2704,6 @@ temp {:?} humidity {:?} rockiness {:?} tree_density {:?} -in_river {:?} -in_lake {:?} -unbounded_water_level {:?} spawn_rate {:?} "#, wpos, alt, @@ -2723,9 +2720,6 @@ spawn_rate {:?} "#, humidity, rockiness, tree_density, - col.in_river, - col.in_lake, - col.unbounded_water_level, spawn_rate )) }; diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 4cc7171c88..e6f54d7793 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -14,7 +14,6 @@ use common::{ use noise::NoiseFn; use serde::Deserialize; use std::{ - cmp::Reverse, f32, ops::{Add, Div, Mul, Sub}, }; @@ -51,6 +50,7 @@ pub struct Colors { pub tropical_high: (f32, f32, f32), } +/// Generalised power function, pushes values in the range 0-1 to extremes. fn power(x: f64, t: f64) -> f64 { if x < 0.5 { (2.0 * x).powf(t) / 2.0 @@ -59,23 +59,6 @@ fn power(x: f64, t: f64) -> f64 { } } -fn cubic(x: f64) -> f64 { - power(x, 3.0) - // if x < 0.5 { - // (2.0 * x).powf(3.0) / 2.0 - // } else { - // 1.0 - (-2.0 * x + 2.0).powf(3.0) / 2.0 - // } -} - -fn revcubic(x: f64) -> f64 { - if x < 0.5 { - (x / 4.0).powf(1.0 / 3.0) - } else { - 1.0 - (2.0 - 2.0 * x).powf(1.0 / 3.0) / 2.0 - } -} - impl<'a> ColumnGen<'a> { pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } } @@ -146,8 +129,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } }; let downhill_wpos = downhill_pos.map(|e| e as f64); - let downhill_pos = - downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e.div_euclid(sz as i32)); + let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { + e.div_euclid(sz as i32) + }); let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; let direction = neighbor_wpos - downhill_wpos; let river_width_min = if let RiverKind::River { cross_section } = kind { @@ -159,17 +143,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let coeffs = river_spline_coeffs(neighbor_wpos, chunkj.river.spline_derivative, downhill_wpos); let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind { - RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { - if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, downhill_wpos)) /*{ - let curve = CubicBezier2 { - start: neighbor_wpos, - ctrl0: neighbor_wpos * 0.75 + downhill_wpos * 0.25 + chunkj.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, - ctrl1: downhill_wpos * 0.75 + neighbor_wpos * 0.25 - downhill_chunk.river.spline_derivative.map(|e| e as f64).normalized() * 0.0, - end: downhill_wpos, - }; - let (t, pos) = curve.binary_search_point_by_steps(wposf, 16, 0.001); - Some((t, pos, curve.evaluate(t).distance_squared(wposf))) - }*/ { + RiverKind::River { .. } => { + if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, downhill_wpos), + ) { let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) { (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos)) } else if dist > wposf.distance_squared(downhill_wpos) { @@ -220,16 +199,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if pass_dist > 1 { return (posj, chunkj, river, None); } - let neighbor_pass_wpos = neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; + let neighbor_pass_wpos = + neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; let neighbor_pass_pos = neighbor_pass_pos .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let coeffs = river_spline_coeffs(neighbor_wpos, spline_derivative, neighbor_pass_wpos); let direction = neighbor_wpos - neighbor_pass_wpos; - if matches!(downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. })) { + // Lakes get a special distance function to avoid cookie-cutter edges + if matches!( + downhill_chunk.river.river_kind, + Some(RiverKind::Lake { .. }) + ) { 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 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, @@ -243,7 +230,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { pos, pos.distance(wposf), ) - } else if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf, Vec2::new(neighbor_wpos, neighbor_pass_wpos)) { + } else if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, neighbor_pass_wpos), + ) { ( direction, coeffs, @@ -288,10 +279,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let river_width_max = if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind { - (cross_section.x as f64).min(river_width_min * 1.75) // Hack + // Harmless hack that prevents a river growing wildly outside its bounds to + // create water walls + (cross_section.x as f64).min(river_width_min * 1.75) } else if let Some(RiverKind::River { cross_section }) = chunkj.river.river_kind { Lerp::lerp(cross_section.x as f64, lake_width, 0.5) } else { + // 0.5 prevents rivers pooling into lakes having extremely wide bounds, creating + // water walls lake_width * 0.5 }; let river_width_noise = (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) @@ -302,11 +297,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let river_width = Lerp::lerp( river_width_min, river_width_max, - // if matches!(chunkj.river.river_kind, Some(RiverKind::Lake { .. })) { - // (1.0 - (river_t.clamped(0.0, 1.0) * 2.0 - 1.0).powi(2)).sqrt() * 0.5 - // } else { - cubic(river_t.clamped(0.0, 1.0)) - // }, + river_t.clamped(0.0, 1.0).powf(3.0), ); let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3); @@ -330,19 +321,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ) }); - let downhill = sim_chunk.downhill; - let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos)); debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); - let downhill_water_alt = downhill_pos - .map(|downhill_chunk| { - downhill_chunk - .water_alt - .min(sim_chunk.water_alt) - .max(sim_chunk.alt.min(sim_chunk.water_alt)) - }) - .unwrap_or(CONFIG.sea_level); - + /// A type that makes managing surface altitude weighting much simpler. #[derive(Default)] struct WeightedSum { sum: T, @@ -351,6 +332,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { max: Option, } impl WeightedSum { + /// Add a weight to the sum. fn with(self, value: f32, weight: f32) -> Self { Self { sum: self.sum + value * weight, @@ -359,7 +341,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - /// With an upper bound + /// Add an upper bound to the result. fn with_min(self, min: f32) -> Self { Self { min: Some(self.min.unwrap_or(min).min(min)), @@ -367,7 +349,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } - /// With a lower bound + /// Add a lower bound to the result. fn with_max(self, max: f32) -> Self { Self { max: Some(self.max.unwrap_or(max).max(max)), @@ -375,6 +357,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Evaluate the weighted sum, if weightings were applied. fn eval(&self) -> Option { if self.weight > 0.0 { let res = self.sum / self.weight; @@ -386,6 +369,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Evaluate the weighted sum, or use a default value if no + /// weightings were applied. fn eval_or(&self, default: f32) -> f32 { let res = if self.weight > 0.0 { self.sum / self.weight @@ -398,11 +383,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } } + /// Determine whether a river should become a waterfall fn is_waterfall( chunk_pos: Vec2, river_chunk: &SimChunk, downhill_chunk: &SimChunk, ) -> bool { + // Waterfalls are rare, so use some hacky RNG seeded with the position to + // reflect that. Additionally, the river must experience a rapid + // change in elevation. Pooling into a lake produces a rapid. + // TODO: Find a better way to produce rapids along the course of a river? (chunk_pos.sum() as u32 % 19 < 2 || matches!( downhill_chunk.river.river_kind, @@ -411,28 +401,40 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { && (river_chunk.water_alt > downhill_chunk.water_alt + 8.0) } + /// Determine the altitude of a river based on the altitude of the + /// spline ends and a tweening factor. fn river_water_alt(a: f32, b: f32, t: f32, is_waterfall: bool) -> f32 { - Lerp::lerp( - a, - b, - // if t < 0.5 { - // 0.0 - // } else { - // let t = (t - 0.5) * 2.0; - // t.powf(1.0 + (a - b).max(0.0) / 1.5) - // }, - // t.powf(1.0 / (1.0 + (a - b).max(0.0) / 3.0)), - if is_waterfall { - power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 - } else { - // cubic(t as f64) as f32 - t - }, - ) + let t = if is_waterfall { + // Waterfalls bias the water altitude toward extremes + power(t as f64, 3.0 + (a - b).clamped(0.0, 16.0) as f64) as f32 + } else { + t + }; + Lerp::lerp(a, b, t) } let actual_sea_level = CONFIG.sea_level + 2.0; // TODO: Don't add 2.0, why is this required? + // What's going on here? + // + // We're iterating over nearby bodies of water and calculating a weighted sum + // for the river water level, the lake water level, and the 'unbounded + // water level' (the maximum water body altitude, which we use later to + // prevent water walls). In doing so, we also apply various clamping strategies + // to catch lots of nasty edge cases, as well as calculating the + // distance to the nearest body of water. + // + // The clamping strategies employed prevent very specific, annoying artifacts + // such as 'water walls' (vertical columns of water that are physically + // implausible) and 'backflows' (regions where a body of water appears to + // flow upstream due to irregular humps along its course). + // + // It is incredibly difficult to explain exactly what every part of this code is + // doing without visual examples. Needless to say, any changes to this + // code *at all* should be very ruggedly tested to ensure that + // they do not result in artifacts, even in edge cases. The exact configuration + // of this code is the product of hundreds of hours of testing and + // 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), @@ -444,20 +446,24 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ), |(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) { ( - Some(kind/*RiverKind::River { cross_section }*/), - Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + Some(kind), + Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))), ) => { // Distance from river center let river_dist = river_pos.distance(wposf); // Distance from edge of river let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; // 0.0 = not near river, 1.0 = in middle of river - let near_river = ((river_dist / river_width) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); - let near_center = ((river_dist / (river_width * 0.5)) as f32).min(1.0).mul(f32::consts::PI).cos().add(1.0).mul(0.5); + let near_center = ((river_dist / (river_width * 0.5)) as f32) + .min(1.0) + .mul(f32::consts::PI) + .cos() + .add(1.0) + .mul(0.5); match kind { - RiverKind::River { .. } /*| RiverKind::Lake { .. }*/ => { - // Alt of river water *is* the alt of land + RiverKind::River { .. } => { + // Alt of river water *is* the alt of land (ignoring gorge, which gets applied later) let river_water_alt = river_water_alt( river_chunk.alt.max(river_chunk.water_alt), downhill_chunk.alt.max(downhill_chunk.water_alt), @@ -465,14 +471,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), ); - // if river_edge_dist > 0.0 { - // unbounded_water_level = unbounded_water_level - // .with(river_water_alt - (river_edge_dist - 2.0).max(0.0), 1.0 / (1.0 + river_edge_dist * 5.0)) - // .with_max(river_water_alt - (river_edge_dist - 2.0).max(0.0)); - // } - river_water_level = river_water_level - // .with_max(river_water_alt) .with(river_water_alt, near_center); if river_edge_dist <= 0.0 { @@ -499,17 +498,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { .with(lake_water_alt, near_center); lake_dist = lake_dist.min(river_edge_dist); - // if river_edge_dist <= 0.0 { - // in_lake = true; - // } // Lake border prevents a lake failing to propagate its altitude to nearby rivers - let border = (river_width / lake_width) as f32 * 14.0; - if river_edge_dist <= 0.0/*border*/ { + if river_edge_dist <= 0.0 { lake_water_level = lake_water_level // Make sure the closest lake is prioritised .with(lake_water_alt, near_center + 0.1 / (1.0 + river_edge_dist)); - // .with_max(lake_water_alt); } }, RiverKind::Ocean => {}, @@ -524,6 +518,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }, ); let unbounded_water_level = unbounded_water_level.eval_or(actual_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. let water_level = match ( river_water_level.eval(), lake_water_level @@ -538,6 +535,31 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { }; let riverless_alt = alt; + + // What's going on here? + // + // Now that we've figured out the altitude of the water in this column, we can + // determine the altitude of the river banks. This initially appears + // somewhat backward (surely the river basin determines the water level?) + // but it is necessary to prevent backflows. Here, the surface of the water is + // king because we require global information to determine it without + // backflows. The river banks simply reflect the will of the water. We care + // much less about a river bank that's slightly rugged and irregular than we do + // about the surface of the water itself being rugged and irregular (and + // hence physically implausible). From that perspective, it makes sense + // that we determine river banks after the water level because it is the one + // that we are most at liberty to screw up. + // + // Similar to the iteration above, we perform a fold over nearby bodies of water + // and use the distance to the water to come up wight a weighted sum for + // the altitude. The way we determine this altitude differs somewhat + // between rivers, lakes, and the ocean and also whether we are *inside* said + // bodies of water or simply near their edge. + // + // As with the previous iteration, a lot of this code is extremely delicate and + // has been carefully designed to handle innumeral edge cases. Please + // test any changes to this code extremely well to avoid regressions: some + // edge cases are very rare indeed! let alt = neighbor_river_data.clone().fold( WeightedSum::default().with(riverless_alt, 1.0), |alt, (river_chunk_idx, river_chunk, river, dist_info)| match ( @@ -545,20 +567,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { dist_info, ) { ( - Some(kind /* RiverKind::River { cross_section } */), - Some((_, dist, river_width, (river_t, (river_pos, _), downhill_chunk))), + Some(kind), + Some((_, _, river_width, (river_t, (river_pos, _), downhill_chunk))), ) => { // Distance from river center let river_dist = river_pos.distance(wposf); // Distance from edge of river let river_edge_dist = (river_dist - river_width * 0.5).max(0.0) as f32; - // 0.0 = not near river, 1.0 = in middle of river - let near_river = ((river_dist / river_width) as f32) - .min(1.0) - .mul(f32::consts::PI) - .cos() - .add(1.0) - .mul(0.5); let water_alt = match kind { RiverKind::River { cross_section } => { @@ -595,7 +610,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some(( lake_water_alt, - /* river_width as f32 * 0.15 */ depth, + // TODO: The depth given to us by the erosion code is technically + // correct, but it also + // looks terrible. Come up with a good solution to this. + /* river_width as f32 * 0.15 */ + depth, Some(min_alt), )) }, @@ -612,6 +631,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 + // TODO: Come up with a more principled way of doing this without + // guessing magic numbers let waterfall_boost = if is_waterfall(river_chunk_idx, river_chunk, downhill_chunk) { (river_chunk.alt - downhill_chunk.alt).max(0.0).powf(2.0) @@ -620,8 +642,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { 0.0 }; - // let river_depth = cross_section.y as f32 /*river_width as f32 * - // 0.15*/; let riverbed_depth = near_centre * water_depth + MIN_DEPTH + waterfall_boost; // Handle rivers debouching into the ocean nicely by 'flattening' their @@ -646,8 +666,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { + (river_edge_dist as f32 - 3.0).max(0.0) * BANK_STRENGTH / BANK_SCALE), 0.0, - // cubic((river_edge_dist / BANK_SCALE).clamped(0.0, 1.0) as f64) - // as f32, (river_edge_dist / BANK_SCALE).clamped(0.0, 1.0), ); let alt = alt.with(water_alt + GORGE, weight); @@ -657,11 +675,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } else { alt }; - // let alt = if !in_river && lake_dist > 0.0 { - // alt.with_max(water_alt + GORGE - river_edge_dist.powf(2.0) / - // 10.0) } else { - // alt - // }; alt } } else { @@ -673,169 +686,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ); let alt = alt.eval_or(riverless_alt); - // Find the average distance to each neighboring body of water. - let mut river_count = 0.0f64; - let mut overlap_count = 0.0f64; - let mut river_distance_product = 1.0f64; - let mut river_overlap_distance_product = 0.0f64; - let mut max_river = None; - let mut max_key = None; - let mut max_dip = 0.0f32; - // IDEA: - // For every "nearby" chunk, check whether it is a river. If so, find the - // closest point on the river segment to wposf (if two point are - // equidistant, choose the earlier one), calling this point river_pos - // and the length (from 0 to 1) along the river segment for the nearby - // chunk river_t. Let river_dist be the distance from river_pos to wposf. - // - // Let river_alt be the interpolated river height at this point - // (from the alt/water altitude at the river, to the alt/water_altitude of the - // downhill river, increasing with river_t). - // - // Now, if river_dist is <= river_width * 0.5, then we don't care what altitude - // we use, and mark that we are on a river (we decide what river to use - // using a heuristic, and set the solely according to the computed - // river_alt for that point). - // - // Otherwise, we let dist = river_dist - river_width * 0.5. - // - // If dist >= TerrainChunkSize::RECT_SIZE.x, we don't include this river in the - // calculation of the correct altitude for this point. - // - // Otherwise (i.e. dist < TerrainChunkSize::RECT_SIZE.x), we want to bias the - // altitude of this point towards the altitude of the river. - // Specifically, as the dist goes from TerrainChunkSize::RECT_SIZE.x to - // 0, the weighted altitude of this point should go from - // alt to river_alt. - neighbor_river_data.for_each(|(river_chunk_idx, river_chunk, river, dist)| { - return; // TODO: Not this - - match river.river_kind { - Some(kind) => { - if kind.is_river() && dist.is_none() { - // Ostensibly near a river segment, but not "usefully" so (there is no - // closest point between t = 0.0 and t = 1.0). - return; - } else { - let river_dist = dist.map(|(_, dist, river_width, (river_t, (river_pos, _), downhill_river))| { - let downhill_height = if kind.is_river() { - // Lerp::lerp( - // river_chunk.alt.max(river_chunk.water_alt), - // downhill_river.alt.max(downhill_river.water_alt), - // river_t as f32, - // ) as f64 - river_width - (river_pos - wposf).magnitude() - } else { - let neighbor_pos = - river_chunk_idx.map(|e| e as f64) * neighbor_coef; - river_width - (wposf - neighbor_pos).magnitude() - }; - (Reverse((dist.x, dist.y /*- if kind.is_river() { 0.01 } else { 0.0 }*/)), downhill_height) - }); - let river_dist = river_dist.or_else(|| { - if !kind.is_river() { - let neighbor_pos = - river_chunk_idx.map(|e| e as f64) * neighbor_coef; - let dist = (wposf - neighbor_pos).magnitude(); - let dist_upon = - (dist - TerrainChunkSize::RECT_SIZE.x as f64 * 0.5).max(0.0); - let dist_ = if dist == 0.0 { f64::INFINITY } else { -dist }; - Some((Reverse((0.0, dist_upon)), dist_)) - } else { - None - } - }); - let river_key = (river_dist, Reverse(kind)); - if max_key < Some(river_key) { - max_river = Some((river_chunk_idx, river_chunk, river, dist)); - max_key = Some(river_key); - } - } - - // NOTE: we scale by the distance to the river divided by the difference - // between the edge of the river that we intersect, and the remaining distance - // until the nearest point in "this" chunk (i.e. the one whose top-left corner - // is chunk_pos) that is at least 2 chunks away from the river source. - if let Some((_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk))) = dist { - let max_distance = if !river.is_river() { - /*(*/ - TerrainChunkSize::RECT_SIZE.x as f64 /* * (1.0 - (2.0f64.sqrt() / 2.0))) + 4.0*/ - lake_width * 0.5 - } else { - TerrainChunkSize::RECT_SIZE.x as f64 - }; - let scale_factor = max_distance; - let river_dist = if kind.is_river() { - // dist.y - ((river_pos - wposf).magnitude() - river_width * 0.5).max(0.0) - } else { - let water_chunk = river_chunk_idx.map(|e| e as f64); - let water_aabr = Aabr { - min: water_chunk * neighbor_coef, - max: (water_chunk + 1.0) * neighbor_coef, - }; - /*water_aabr.distance_to_point(wposf)*/ - (river_pos - wposf).magnitude() / (TerrainChunkSize::RECT_SIZE.x as f64 * 0.1) - }; - - if !(dist.x == 0.0 && river_dist < scale_factor) { - return; - } - // We basically want to project outwards from river_pos, along the current - // tangent line, to chunks <= river_width * 1.0 away from this - // point. We *don't* want to deal with closer chunks because they - - // NOTE: river_width <= 2 * max terrain chunk size width, so this should not - // lead to division by zero. - // NOTE: If distance = 0.0 this goes to zero, which is desired since it - // means points that actually intersect with rivers will not be interpolated - // with the "normal" height of this point. - // NOTE: We keep the maximum at 1.0 so we don't undo work from another river - // just by being far away. - let river_scale = river_dist / scale_factor; - let river_alt = - Lerp::lerp(river_chunk.alt, downhill_river_chunk.alt, river_t as f32); - let river_alt = Lerp::lerp(river_alt, alt, river_scale as f32); - let river_alt_diff = river_alt - alt; - let river_alt_inv = river_alt_diff as f64; - river_overlap_distance_product += (1.0 - river_scale) * river_alt_inv; - overlap_count += 1.0 - river_scale; - river_count += 1.0; - river_distance_product *= river_scale; - max_dip = max_dip.max(-river_alt_diff + if let RiverKind::River { cross_section } = kind { - let river_depth = river_width as f32 * 0.5; - - (cross_section.y * river_depth).max(0.0) + 1.5 - } else { - 0.0 - }); - } - } - None => {} - } - }); - - let river_scale_factor = if river_count == 0.0 { - 1.0 - } else { - let river_scale_factor = river_distance_product; - if river_scale_factor == 0.0 { - 0.0 - } else { - river_scale_factor.powf(if river_count == 0.0 { - 1.0 - } else { - 1.0 / river_count - }) - } - }; - - let alt_for_river = alt; - // + if overlap_count == 0.0 { - // 0.0 - // } else { - // river_overlap_distance_product / overlap_count - // } as f32; - 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(), ) as f32) @@ -873,240 +723,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let cliff_offset = cliff * cliff_height; let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; - let river_gouge = 0.5; - let (_in_water, old_water_dist, alt_, old_water_level, _riverless_alt, warp_factor) = - if let Some(( - max_border_river_pos, - river_chunk, - max_border_river, - max_border_river_dist, - )) = max_river - { - // This is flowing into a lake, or a lake, or is at least a non-ocean tile. - // - // If we are <= water_alt, we are in the lake; otherwise, we are flowing into - // it. - match max_border_river.river_kind { - Some(RiverKind::River { cross_section }) => 'block: { - if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { - let (_, _, river_width, (_, (river_pos, _), _)) = - max_border_river_dist.unwrap(); - let river_dist = wposf.distance(river_pos); + let warp_factor = water_dist.map_or(1.0, |d| d.max(0.0) / 64.0); - // FIXME: Make water altitude accurate. - break 'block ( - river_scale_factor <= 1.0, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - river_scale_factor as f32, - ); - } - let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = - max_border_river_dist.unwrap(); - let river_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - let new_alt = river_alt - river_gouge; - let river_dist = wposf.distance(river_pos); - let river_height_factor = river_dist / (river_width * 0.5); - - // This is not a good way to determine river depth, but we do it - // anyway. TODO: Make the erosion - // sim output better river depths through `cross_section`. - let river_depth = river_width as f32 * 0.5; - - let valley_alt = Lerp::lerp( - new_alt - (cross_section.y * river_depth).max(1.0), - new_alt - 1.0, - (river_height_factor * river_height_factor) as f32, - ); - - ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //valley_alt.min(alt), - new_alt.max(valley_alt.min(alt) + 1.0), - alt, //river_alt + cross_section.y.max(1.0), - river_scale_factor as f32, //0.0, - ) - }, - Some(RiverKind::Ocean) => 'block: { - let (_, dist, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else { - error!( - ?max_border_river, - ?chunk_pos, - ?max_border_river_pos, - "downhill error details" - ); - panic!("Oceans should definitely have a downhill! ...Right?"); - }; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ) + 1.0; - - if dist == Vec2::zero() { - let river_dist = wposf.distance(river_pos); - let _river_height_factor = river_dist / (river_width * 0.5); - break 'block ( - true, - Some((river_dist - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt, - alt, //alt_for_river.max(lake_water_alt), - 0.0, - ); - } - - ( - river_scale_factor <= 1.0, - Some((wposf.distance(river_pos) - river_width * 0.5) as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt + 1.0, - alt, //alt_for_river, - river_scale_factor as f32, - ) - }, - Some(RiverKind::Lake { .. }) => 'block: { - let lake_chunk = max_border_river_pos.map(|e| e as f64); - let lake_aabr = Aabr { - min: lake_chunk * neighbor_coef, - max: (lake_chunk + 1.0) * neighbor_coef, - }; - let lake_dist = lake_aabr.distance_to_point(wposf); - let downhill_river_chunk = max_border_river_pos; - let lake_id_dist = downhill_river_chunk - chunk_pos; - let in_bounds = lake_id_dist.x >= -1 - && lake_id_dist.y >= -1 - && lake_id_dist.x <= 1 - && lake_id_dist.y <= 1; - let in_bounds = in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); - let (_, dist, _, (river_t, _, downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { - dist - } else if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = 0.0; - break 'block ( - in_bounds - || downhill_water_alt.max(river_chunk.water_alt) - > alt_for_river, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - river_gouge), - alt_for_river, - river_scale_factor as f32 * (1.0 - gouge_factor), - ); - } else { - break 'block ( - false, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ); - }; - - let lake_dist = dist.y; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - // if dist == Vec2::zero() { - // return ( - // true, - // Some(lake_dist as f32), - // alt_for_river, - // // .min(lake_water_alt - 1.0 - river_gouge), - // lake_water_alt - river_gouge, - // alt_for_river.max(lake_water_alt), - // 0.0, - // ); - // } - if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 || in_bounds { - let gouge_factor = if in_bounds && lake_dist <= 1.0 { - 1.0 - } else { - 0.0 - }; - let in_bounds_ = - lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; - if gouge_factor == 1.0 { - break 'block ( - true, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - // .min(lake_water_alt - 1.0 - river_gouge), - downhill_water_alt.max(lake_water_alt) - river_gouge, - alt.max(lake_water_alt), - 0.0, // river_scale_factor as f32* (1.0 - gouge_factor), - ); - } else { - break 'block ( - true, - Some(lake_dist as f32), - alt_for_river - max_dip, //alt_for_river, - if in_bounds_ { - downhill_water_alt.max(lake_water_alt) - } else { - downhill_water_alt //.max(lake_water_alt) - } - .max(river_chunk.water_alt) - - river_gouge, - alt_for_river, - river_scale_factor as f32 * (1.0 - gouge_factor), - ); - } - } - ( - river_scale_factor <= 1.0, - Some(lake_dist as f32), - alt_for_river - max_dip, /* alt_for_river.max(lake_water_alt). - * max(river_chunk.water_alt), */ - downhill_water_alt, - alt_for_river, - river_scale_factor as f32, - ) - }, - None => ( - false, - None, - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - 1.0, - ), - } - } else { - ( - false, - None, - alt_for_river - max_dip, //alt_for_river, - downhill_water_alt, - alt, //alt_for_river, - 1.0, - ) - }; // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; - let warp_factor = warp_factor.min(water_dist.map_or(1.0, |d| d.max(0.0) / 64.0)); let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); - // let riverless_alt = alt + riverless_alt_delta; //riverless_alt + - // riverless_alt_delta; - let alt = alt/*alt_*/ + riverless_alt_delta; + let alt = alt + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; @@ -1433,9 +1056,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { snow_cover, cliff_offset, cliff_height, - in_river, - in_lake: lake_dist <= 0.0, - unbounded_water_level, chunk: sim_chunk, }) @@ -1468,9 +1088,6 @@ pub struct ColumnSample<'a> { pub snow_cover: bool, pub cliff_offset: f32, pub cliff_height: f32, - pub in_river: bool, - pub in_lake: bool, - pub unbounded_water_level: f32, pub chunk: &'a SimChunk, } diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index da51b5ade9..6f054354a2 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -1,12 +1,11 @@ use crate::{ all::ForestKind, - util::{seed_expan, RandomPerm, Sampler, StructureGen2d, UnitChooser}, + util::{seed_expan, Sampler, StructureGen2d, UnitChooser}, Canvas, }; use common::{ assets::AssetHandle, terrain::structure::{Structure, StructuresGroup}, - vol::ReadVol, }; use hashbrown::HashMap; use lazy_static::lazy_static; @@ -24,13 +23,13 @@ struct Shrub { kind: ForestKind, } -pub fn apply_shrubs_to(canvas: &mut Canvas, rng: &mut impl Rng) { +pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { let mut shrub_cache = HashMap::new(); let shrub_gen = StructureGen2d::new(canvas.index().seed, 8, 4); let info = canvas.info(); - canvas.foreach_col(|canvas, wpos2d, col| { + canvas.foreach_col(|_, wpos2d, _| { for (wpos, seed) in std::array::IntoIter::new(shrub_gen.get(wpos2d)) { shrub_cache.entry(wpos).or_insert_with(|| { let col = info.col_or_gen(wpos)?; From 2ae7bca9c0173240183f802a7c3e972e85f2d9d5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Sep 2021 15:40:54 +0100 Subject: [PATCH 16/18] Rebalanced boat speed --- common/src/comp/body/ship.rs | 4 ++-- common/src/states/utils.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 2165d416cf..1c3a28570b 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -44,7 +44,7 @@ impl Body { match self { Body::DefaultAirship => Vec3::new(25.0, 50.0, 40.0), Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0), - Body::SailBoat => Vec3::new(13.0, 31.0, 6.0), + Body::SailBoat => Vec3::new(13.0, 31.0, 3.0), } } @@ -77,7 +77,7 @@ impl Body { pub fn density(&self) -> Density { match self { Body::DefaultAirship | Body::AirBalloon => Density(AIR_DENSITY), - _ => Density(AIR_DENSITY * 0.75 + WATER_DENSITY * 0.25), // Most boats should be buoyant + _ => Density(AIR_DENSITY * 0.8 + WATER_DENSITY * 0.2), // Most boats should be buoyant } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 655f6b3e8f..3b554b4f07 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -201,7 +201,7 @@ impl Body { Body::QuadrupedLow(_) => Some(300.0 * self.mass().0), Body::QuadrupedMedium(_) => Some(300.0 * self.mass().0), Body::QuadrupedSmall(_) => Some(300.0 * self.mass().0), - Body::Ship(ship) if ship.has_water_thrust() => Some(750.0 * self.mass().0), + Body::Ship(ship) if ship.has_water_thrust() => Some(1500.0 * self.mass().0), Body::Ship(_) => None, } } From 96e23ae2d425b582184219ddb3b612fce09d0ecf Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 21 Sep 2021 18:35:43 +0100 Subject: [PATCH 17/18] 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 } }; From f13a8a643b995533aa327060a9533c6decc1dcfe Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 5 Oct 2021 00:01:49 +0100 Subject: [PATCH 18/18] Fixed water light extinction --- assets/voxygen/shaders/terrain-frag.glsl | 2 +- world/src/column/mod.rs | 418 ++++++++++++----------- 2 files changed, 218 insertions(+), 202 deletions(-) diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 6aff7daaf3..aa5b0e89a2 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -204,7 +204,7 @@ void main() { const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2); const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2); // float faces_fluid = faces_fluid && f_pos.z <= floor(f_alt); - float fluid_alt = max(f_pos.z + 1, floor(f_alt)); + float fluid_alt = max(f_pos.z + 1, floor(f_alt + 1)); float R_s = /*(f_pos.z < f_alt)*/faces_fluid /*&& f_pos.z <= fluid_alt*/ ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x); // vec3 surf_color = /*srgb_to_linear*/(f_col); diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index aa68944b69..4fa873f159 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,7 +1,7 @@ use crate::{ all::ForestKind, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim}, - util::Sampler, + util::{RandomField, Sampler}, IndexRef, CONFIG, }; use common::{ @@ -106,112 +106,171 @@ 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())) + 5.0; - let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| { - let kind = match river.river_kind { - Some(kind) => kind, - None => { - return (posj, chunkj, river, None); - }, - }; - let downhill_pos = if let Some(pos) = chunkj.downhill { - pos - } else { - match kind { + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * 2.0f64.sqrt()) + 6.0; + let neighbor_river_data = neighbor_river_data + .map(|(posj, chunkj, river)| { + let kind = match river.river_kind { + Some(kind) => kind, + None => { + return (posj, chunkj, river, None); + }, + }; + let downhill_pos = if let Some(pos) = chunkj.downhill { + pos + } else { + match kind { + RiverKind::River { .. } => { + error!(?river, ?posj, "What?"); + panic!("How can a river have no downhill?"); + }, + RiverKind::Lake { .. } => { + return (posj, chunkj, river, None); + }, + RiverKind::Ocean => posj, + } + }; + let downhill_wpos = downhill_pos.map(|e| e as f64); + let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { + e.div_euclid(sz as i32) + }); + let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; + let direction = neighbor_wpos - downhill_wpos; + let river_width_min = if let RiverKind::River { cross_section } = kind { + cross_section.x as f64 + } else { + lake_width + }; + let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?"); + let coeffs = river_spline_coeffs( + neighbor_wpos, + chunkj.river.spline_derivative, + downhill_wpos, + ); + let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind + { RiverKind::River { .. } => { - error!(?river, ?posj, "What?"); - panic!("How can a river have no downhill?"); - }, - RiverKind::Lake { .. } => { - return (posj, chunkj, river, None); - }, - RiverKind::Ocean => posj, - } - }; - let downhill_wpos = downhill_pos.map(|e| e as f64); - let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { - e.div_euclid(sz as i32) - }); - let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; - let direction = neighbor_wpos - downhill_wpos; - let river_width_min = if let RiverKind::River { cross_section } = kind { - cross_section.x as f64 - } else { - lake_width - }; - let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?"); - let coeffs = - river_spline_coeffs(neighbor_wpos, chunkj.river.spline_derivative, downhill_wpos); - let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind { - RiverKind::River { .. } => { - if let Some((t, pt, dist)) = quadratic_nearest_point( - &coeffs, - wposf, - Vec2::new(neighbor_wpos, downhill_wpos), - ) { - let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) { - (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos)) - } else if dist > wposf.distance_squared(downhill_wpos) { - (1.0, downhill_wpos, wposf.distance_squared(downhill_wpos)) + if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, downhill_wpos), + ) { + let (t, pt, dist) = if dist > wposf.distance_squared(neighbor_wpos) { + (0.0, neighbor_wpos, wposf.distance_squared(neighbor_wpos)) + } else if dist > wposf.distance_squared(downhill_wpos) { + (1.0, downhill_wpos, wposf.distance_squared(downhill_wpos)) + } else { + (t, pt, dist) + }; + (direction, coeffs, downhill_chunk, t, pt, dist.sqrt()) } else { - (t, pt, dist) - }; - (direction, coeffs, downhill_chunk, t, pt, dist.sqrt()) - } else { - let ndist = wposf.distance_squared(neighbor_wpos); - let ddist = wposf.distance_squared(downhill_wpos); - let (closest_pos, closest_dist, closest_t) = if ndist <= ddist { - (neighbor_wpos, ndist, 0.0) + let ndist = wposf.distance_squared(neighbor_wpos); + let ddist = wposf.distance_squared(downhill_wpos); + let (closest_pos, closest_dist, closest_t) = if ndist <= ddist { + (neighbor_wpos, ndist, 0.0) + } else { + (downhill_wpos, ddist, 1.0) + }; + ( + direction, + coeffs, + downhill_chunk, + closest_t, + closest_pos, + closest_dist.sqrt(), + ) + } + }, + RiverKind::Lake { neighbor_pass_pos } => { + let pass_dist = neighbor_pass_pos + .map2( + neighbor_wpos + .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), + |e, (f, g)| ((e - f) / g).abs(), + ) + .reduce_partial_max(); + let spline_derivative = river.spline_derivative; + let neighbor_pass_pos = if pass_dist <= 1 { + neighbor_pass_pos } else { - (downhill_wpos, ddist, 1.0) + downhill_wpos.map(|e| e as i32) }; - ( - direction, - coeffs, - downhill_chunk, - closest_t, - closest_pos, - closest_dist.sqrt(), - ) - } - }, - RiverKind::Lake { neighbor_pass_pos } => { - let pass_dist = neighbor_pass_pos - .map2( - neighbor_wpos - .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), - |e, (f, g)| ((e - f) / g).abs(), - ) - .reduce_partial_max(); - let spline_derivative = river.spline_derivative; - let neighbor_pass_pos = if pass_dist <= 1 { - neighbor_pass_pos - } else { - downhill_wpos.map(|e| e as i32) - }; - let pass_dist = neighbor_pass_pos - .map2( - neighbor_wpos - .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), - |e, (f, g)| ((e - f) / g).abs(), - ) - .reduce_partial_max(); - if pass_dist > 1 { - return (posj, chunkj, river, None); - } - let neighbor_pass_wpos = - neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; - let neighbor_pass_pos = neighbor_pass_pos - .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); - let coeffs = - river_spline_coeffs(neighbor_wpos, spline_derivative, neighbor_pass_wpos); - let direction = neighbor_wpos - neighbor_pass_wpos; + let pass_dist = neighbor_pass_pos + .map2( + neighbor_wpos + .map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)), + |e, (f, g)| ((e - f) / g).abs(), + ) + .reduce_partial_max(); + if pass_dist > 1 { + return (posj, chunkj, river, None); + } + let neighbor_pass_wpos = + neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; + let neighbor_pass_pos = neighbor_pass_pos + .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); + let coeffs = river_spline_coeffs( + neighbor_wpos, + spline_derivative, + neighbor_pass_wpos, + ); + let direction = neighbor_wpos - neighbor_pass_wpos; - // Lakes get a special distance function to avoid cookie-cutter edges - if matches!( - downhill_chunk.river.river_kind, - Some(RiverKind::Lake { .. } | RiverKind::Ocean) - ) { + // Lakes get a special distance function to avoid cookie-cutter edges + if matches!( + downhill_chunk.river.river_kind, + Some(RiverKind::Lake { .. } | RiverKind::Ocean) + ) { + 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(neighbor_pass_pos).expect("Must already work"), + 0.5, + pos, + pos.distance(wposf), + ) + } else if let Some((t, pt, dist)) = quadratic_nearest_point( + &coeffs, + wposf, + Vec2::new(neighbor_wpos, neighbor_pass_wpos), + ) { + ( + direction, + coeffs, + sim.get(neighbor_pass_pos).expect("Must already work"), + t, + pt, + dist.sqrt(), + ) + } else { + let ndist = wposf.distance_squared(neighbor_wpos); + /* let ddist = wposf.distance_squared(neighbor_pass_wpos); */ + let (closest_pos, closest_dist, closest_t) = /*if ndist <= ddist */ { + (neighbor_wpos, ndist, 0.0) + } /* else { + (neighbor_pass_wpos, ddist, 1.0) + } */; + ( + direction, + coeffs, + sim.get(neighbor_pass_pos).expect("Must already work"), + closest_t, + closest_pos, + closest_dist.sqrt(), + ) + } + }, + RiverKind::Ocean => { let water_chunk = posj.map(|e| e as f64); let lake_width_noise = sim .gen_ctx @@ -225,65 +284,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ( direction, coeffs, - sim.get(neighbor_pass_pos).expect("Must already work"), + sim.get(posj).expect("Must already work"), 0.5, pos, pos.distance(wposf), ) - } else if let Some((t, pt, dist)) = quadratic_nearest_point( - &coeffs, - wposf, - Vec2::new(neighbor_wpos, neighbor_pass_wpos), - ) { - ( - direction, - coeffs, - sim.get(neighbor_pass_pos).expect("Must already work"), - t, - pt, - dist.sqrt(), - ) - } else { - let ndist = wposf.distance_squared(neighbor_wpos); - /* let ddist = wposf.distance_squared(neighbor_pass_wpos); */ - let (closest_pos, closest_dist, closest_t) = /*if ndist <= ddist */ { - (neighbor_wpos, ndist, 0.0) - } /* else { - (neighbor_pass_wpos, ddist, 1.0) - } */; - ( - direction, - coeffs, - sim.get(neighbor_pass_pos).expect("Must already work"), - closest_t, - closest_pos, - closest_dist.sqrt(), - ) - } - }, - RiverKind::Ocean => { - 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(posj).expect("Must already work"), - 0.5, - pos, - pos.distance(wposf), - ) - }, - }; - let river_width_max = - if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind { + }, + }; + let river_width_max = if let Some(RiverKind::River { cross_section }) = + downhill_chunk.river.river_kind + { // Harmless hack that prevents a river growing wildly outside its bounds to // create water walls (cross_section.x as f64).min(river_width_min * 1.75) @@ -294,37 +304,39 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // water walls lake_width * 0.5 }; - let river_width_noise = (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) - .max(-1.0) - .min(1.0) - .mul(0.5) - .sub(0.5) as f64; - let river_width = Lerp::lerp( - river_width_min, - river_width_max, - river_t.clamped(0.0, 1.0).powf(3.0), - ); + let river_width_noise = + (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) + .max(-1.0) + .min(1.0) + .mul(0.5) + .sub(0.5) as f64; + let river_width = Lerp::lerp( + river_width_min, + river_width_max, + river_t.clamped(0.0, 1.0).powf(3.0), + ); - let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3); - // To find the distance, we just evaluate the quadratic equation at river_t and - // see if it's within width (but we should be able to use it for a - // lot more, and this probably isn't the very best approach anyway - // since it will bleed out). let river_pos = coeffs.x * river_t * - // river_t + coeffs.y * river_t + coeffs.z; - // let river_width = 32.0f64; - let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0)); - ( - posj, - chunkj, - river, - Some(( - direction, - res, - river_width, - (river_t, (river_pos, coeffs), downhill_chunk), - )), - ) - }); + let river_width = river_width.max(2.0) * (1.0 + river_width_noise * 0.3); + // To find the distance, we just evaluate the quadratic equation at river_t and + // see if it's within width (but we should be able to use it for a + // lot more, and this probably isn't the very best approach anyway + // since it will bleed out). let river_pos = coeffs.x * river_t * + // river_t + coeffs.y * river_t + coeffs.z; + // let river_width = 32.0f64; + let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0)); + ( + posj, + chunkj, + river, + Some(( + direction, + res, + river_width, + (river_t, (river_pos, coeffs), downhill_chunk), + )), + ) + }) + .collect::>(); debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); @@ -397,7 +409,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // reflect that. Additionally, the river must experience a rapid // change in elevation. Pooling into a lake produces a rapid. // TODO: Find a better way to produce rapids along the course of a river? - (chunk_pos.sum() as u32 % 19 < 2 + (RandomField::new(3119).chance(chunk_pos.with_z(0), 0.1) || matches!( downhill_chunk.river.river_kind, Some(RiverKind::Lake { .. }) @@ -418,7 +430,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { } // Use this to temporarily alter the sea level - let base_sea_level = CONFIG.sea_level + 0.01; + let base_sea_level = CONFIG.sea_level - 1.0 + 0.01; // What's going on here? // @@ -440,7 +452,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // they do not result in artifacts, even in edge cases. The exact configuration // of this code is the product of hundreds of hours of testing and // 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( + let (river_water_level, in_river, lake_water_level, lake_dist, water_dist, unbounded_water_level) = neighbor_river_data.iter().copied().fold( ( WeightedSum::default().with_max(base_sea_level), false, @@ -566,7 +578,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { // has been carefully designed to handle innumeral edge cases. Please // test any changes to this code extremely well to avoid regressions: some // edge cases are very rare indeed! - let alt = neighbor_river_data.clone().fold( + let alt = neighbor_river_data.into_iter().fold( WeightedSum::default().with(riverless_alt, 1.0), |alt, (river_chunk_idx, river_chunk, river, dist_info)| match ( river.river_kind, @@ -593,12 +605,16 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { Some((river_water_alt, cross_section.y as f32, None)) }, 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), - river_t as f32, - is_waterfall(river_chunk_idx, river_chunk, downhill_chunk), - ); + 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), + ) + }; let depth = water_level - Lerp::lerp( @@ -630,7 +646,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { if let Some((water_alt, water_depth, min_alt)) = water_alt { if river_edge_dist <= 0.0 { const MIN_DEPTH: f32 = 1.0; - let near_centre = ((river_dist / (river_width * 0.5)) as f32) + let near_center = ((river_dist / (river_width * 0.5)) as f32) .min(1.0) .mul(f32::consts::PI) .cos() @@ -652,14 +668,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { / TerrainChunkSize::RECT_SIZE.x as f32 }; let riverbed_depth = - near_centre * water_depth + MIN_DEPTH + waterfall_boost; + near_center * water_depth + MIN_DEPTH + waterfall_boost; // Handle rivers debouching into the ocean nicely by 'flattening' their // bottom let riverbed_alt = (water_alt - riverbed_depth) - .max(riverless_alt.min(CONFIG.sea_level - MIN_DEPTH)); + .max(riverless_alt.min(base_sea_level - MIN_DEPTH)); alt.with( min_alt.unwrap_or(riverbed_alt).min(riverbed_alt), - near_centre * BANK_STRENGTH, + near_center * BANK_STRENGTH, ) .with_min(min_alt.unwrap_or(riverbed_alt).min(riverbed_alt)) } else {