added water

This commit is contained in:
grimsace
2026-04-22 13:22:39 -05:00
parent 5ad1253b90
commit 20c4871971
2 changed files with 83 additions and 14 deletions
+6
View File
@@ -369,6 +369,12 @@ impl TerrainApp {
&mut self.params.snowline_height, &mut self.params.snowline_height,
0..=255, 0..=255,
); );
labeled_int_slider(
ui,
"Water Table Height",
&mut self.params.water_table_height,
0..=255,
);
labeled_float_slider( labeled_float_slider(
ui, ui,
"Rock Slope Angle", "Rock Slope Angle",
+77 -14
View File
@@ -22,6 +22,7 @@ pub struct GenerationParams {
pub erosion_detail: f32, pub erosion_detail: f32,
pub erosion_octaves: usize, pub erosion_octaves: usize,
pub snowline_height: u8, pub snowline_height: u8,
pub water_table_height: u8,
pub rock_slope_angle_degrees: f32, pub rock_slope_angle_degrees: f32,
} }
@@ -38,6 +39,7 @@ impl Default for GenerationParams {
erosion_detail: 1.5, erosion_detail: 1.5,
erosion_octaves: 5, erosion_octaves: 5,
snowline_height: 220, snowline_height: 220,
water_table_height: 0,
rock_slope_angle_degrees: 15.0, rock_slope_angle_degrees: 15.0,
} }
} }
@@ -54,6 +56,8 @@ pub struct GeneratedImages {
#[allow(dead_code)] #[allow(dead_code)]
pub grass_mask_image: GrayImage, pub grass_mask_image: GrayImage,
#[allow(dead_code)] #[allow(dead_code)]
pub water_mask_image: GrayImage,
#[allow(dead_code)]
pub rock_mask_image: GrayImage, pub rock_mask_image: GrayImage,
#[allow(dead_code)] #[allow(dead_code)]
pub snow_mask_image: GrayImage, pub snow_mask_image: GrayImage,
@@ -135,6 +139,10 @@ fn clamp01(x: f32) -> f32 {
clamp(x, 0.0, 1.0) clamp(x, 0.0, 1.0)
} }
fn mix_u8(a: u8, b: u8, t: f32) -> u8 {
mix(a as f32, b as f32, clamp01(t)).round() as u8
}
fn hash22(p: Vec2, random_seed: u64) -> Vec2 { fn hash22(p: Vec2, random_seed: u64) -> Vec2 {
let scrambled_x = splitmix64(random_seed ^ 0x9E37_79B9_7F4A_7C15); let scrambled_x = splitmix64(random_seed ^ 0x9E37_79B9_7F4A_7C15);
let scrambled_y = splitmix64(random_seed ^ 0xBF58_476D_1CE4_E5B9); let scrambled_y = splitmix64(random_seed ^ 0xBF58_476D_1CE4_E5B9);
@@ -401,10 +409,15 @@ pub fn generate(params: &GenerationParams) -> GeneratedImages {
width, width,
height, height,
params.snowline_height, params.snowline_height,
params.water_table_height,
params.rock_slope_angle_degrees, params.rock_slope_angle_degrees,
); );
let terrain_composite_image = let terrain_composite_image = build_soft_light_composite(
build_soft_light_composite(&terrain_layers.terrain_color_image, &bumpmap_image); &terrain_layers.terrain_color_image,
&bumpmap_image,
&heightmap_image,
&terrain_layers.water_mask_image,
);
GeneratedImages { GeneratedImages {
width, width,
@@ -414,6 +427,7 @@ pub fn generate(params: &GenerationParams) -> GeneratedImages {
terrain_color_image: terrain_layers.terrain_color_image, terrain_color_image: terrain_layers.terrain_color_image,
terrain_composite_image, terrain_composite_image,
grass_mask_image: terrain_layers.grass_mask_image, grass_mask_image: terrain_layers.grass_mask_image,
water_mask_image: terrain_layers.water_mask_image,
rock_mask_image: terrain_layers.rock_mask_image, rock_mask_image: terrain_layers.rock_mask_image,
snow_mask_image: terrain_layers.snow_mask_image, snow_mask_image: terrain_layers.snow_mask_image,
} }
@@ -422,6 +436,7 @@ pub fn generate(params: &GenerationParams) -> GeneratedImages {
struct TerrainLayers { struct TerrainLayers {
terrain_color_image: RgbaImage, terrain_color_image: RgbaImage,
grass_mask_image: GrayImage, grass_mask_image: GrayImage,
water_mask_image: GrayImage,
rock_mask_image: GrayImage, rock_mask_image: GrayImage,
snow_mask_image: GrayImage, snow_mask_image: GrayImage,
} }
@@ -460,22 +475,28 @@ fn build_terrain_layers(
width: usize, width: usize,
height: usize, height: usize,
snowline_height: u8, snowline_height: u8,
water_table_height: u8,
rock_slope_angle_degrees: f32, rock_slope_angle_degrees: f32,
) -> TerrainLayers { ) -> TerrainLayers {
let mut terrain_color_image = RgbaImage::new(width as u32, height as u32); let mut terrain_color_image = RgbaImage::new(width as u32, height as u32);
let mut grass_mask_image = GrayImage::new(width as u32, height as u32); let mut grass_mask_image = GrayImage::new(width as u32, height as u32);
let mut water_mask_image = GrayImage::new(width as u32, height as u32);
let mut rock_mask_image = GrayImage::new(width as u32, height as u32); let mut rock_mask_image = GrayImage::new(width as u32, height as u32);
let mut snow_mask_image = GrayImage::new(width as u32, height as u32); let mut snow_mask_image = GrayImage::new(width as u32, height as u32);
let grass_color = Rgba([92, 140, 66, 255]); let low_grass_color = [78, 146, 64];
let high_grass_color = [150, 156, 88];
let water_color = Rgba([40, 70, 150, 255]);
let sand_color = [210, 195, 145];
let rock_color = Rgba([122, 112, 98, 255]); let rock_color = Rgba([122, 112, 98, 255]);
let snow_color = Rgba([245, 248, 252, 255]); let snow_color = Rgba([255, 250, 250, 255]);
let slope_threshold_radians = rock_slope_angle_degrees.clamp(0.0, 89.9).to_radians(); let slope_threshold_radians = rock_slope_angle_degrees.clamp(0.0, 89.9).to_radians();
for y in 0..height { for y in 0..height {
for x in 0..width { for x in 0..width {
let idx = y * width + x; let idx = y * width + x;
let height_value = heightmap[idx].clamp(0.0, 1.0); let height_value = heightmap[idx].clamp(0.0, 1.0);
let u8_height = (height_value * 255.0).round() as u8;
let left = sample_height(heightmap, width, height, x.saturating_sub(1), y); let left = sample_height(heightmap, width, height, x.saturating_sub(1), y);
let right = sample_height(heightmap, width, height, (x + 1).min(width - 1), y); let right = sample_height(heightmap, width, height, (x + 1).min(width - 1), y);
@@ -486,26 +507,56 @@ fn build_terrain_layers(
let dy = (down - up) * 0.5 * height as f32; let dy = (down - up) * 0.5 * height as f32;
let slope_angle = dx.hypot(dy).atan(); let slope_angle = dx.hypot(dy).atan();
let grass_t = clamp01((height_value - 0.08) / 0.52);
let grass_color = [
mix_u8(low_grass_color[0], high_grass_color[0], grass_t),
mix_u8(low_grass_color[1], high_grass_color[1], grass_t),
mix_u8(low_grass_color[2], high_grass_color[2], grass_t),
];
let is_water = if water_table_height == 0 {
false
} else {
u8_height < water_table_height
};
let sand_width = 4.0;
let sand_t = if water_table_height == 0 || is_water {
0.0
} else {
let diff = (u8_height - water_table_height) as f32;
(1.0 - (diff / sand_width)).clamp(0.0, 1.0)
};
let base_color = Rgba([
mix_u8(grass_color[0], sand_color[0], sand_t),
mix_u8(grass_color[1], sand_color[1], sand_t),
mix_u8(grass_color[2], sand_color[2], sand_t),
255,
]);
let is_snow = if snowline_height == 0 { let is_snow = if snowline_height == 0 {
true true
} else if snowline_height == u8::MAX { } else if snowline_height == u8::MAX {
false false
} else { } else {
(height_value * 255.0).round() > snowline_height as f32 u8_height > snowline_height
}; };
let is_rock = slope_angle >= slope_threshold_radians; let is_rock = slope_angle >= slope_threshold_radians;
grass_mask_image.put_pixel(x as u32, y as u32, Luma([255])); grass_mask_image.put_pixel(x as u32, y as u32, Luma([255]));
water_mask_image.put_pixel(x as u32, y as u32, Luma([if is_water { 255 } else { 0 }]));
rock_mask_image.put_pixel(x as u32, y as u32, Luma([if is_rock { 255 } else { 0 }])); rock_mask_image.put_pixel(x as u32, y as u32, Luma([if is_rock { 255 } else { 0 }]));
snow_mask_image.put_pixel(x as u32, y as u32, Luma([if is_snow { 255 } else { 0 }])); snow_mask_image.put_pixel(x as u32, y as u32, Luma([if is_snow { 255 } else { 0 }]));
let mut color = grass_color; let color = if is_water {
if is_rock { water_color
color = rock_color; } else if is_snow {
} snow_color
if is_snow { } else if is_rock {
color = snow_color; rock_color
} } else {
base_color
};
terrain_color_image.put_pixel(x as u32, y as u32, color); terrain_color_image.put_pixel(x as u32, y as u32, color);
} }
@@ -514,18 +565,30 @@ fn build_terrain_layers(
TerrainLayers { TerrainLayers {
terrain_color_image, terrain_color_image,
grass_mask_image, grass_mask_image,
water_mask_image,
rock_mask_image, rock_mask_image,
snow_mask_image, snow_mask_image,
} }
} }
fn build_soft_light_composite(color_image: &RgbaImage, bumpmap_image: &GrayImage) -> RgbaImage { fn build_soft_light_composite(
color_image: &RgbaImage,
bumpmap_image: &GrayImage,
heightmap_image: &GrayImage,
water_mask_image: &GrayImage,
) -> RgbaImage {
let mut composite = RgbaImage::new(color_image.width(), color_image.height()); let mut composite = RgbaImage::new(color_image.width(), color_image.height());
for y in 0..color_image.height() { for y in 0..color_image.height() {
for x in 0..color_image.width() { for x in 0..color_image.width() {
let base = color_image.get_pixel(x, y).0; let base = color_image.get_pixel(x, y).0;
let blend = bumpmap_image.get_pixel(x, y).0[0] as f32 / 255.0; let is_water = water_mask_image.get_pixel(x, y).0[0] > 0;
let blend = if is_water {
heightmap_image.get_pixel(x, y).0[0] as f32 / 255.0
} else {
bumpmap_image.get_pixel(x, y).0[0] as f32 / 255.0
};
let r = soft_light_channel(base[0] as f32 / 255.0, blend); let r = soft_light_channel(base[0] as f32 / 255.0, blend);
let g = soft_light_channel(base[1] as f32 / 255.0, blend); let g = soft_light_channel(base[1] as f32 / 255.0, blend);