added a composite image

This commit is contained in:
grimsace
2026-04-22 13:22:39 -05:00
parent fc399095bd
commit 5ad1253b90
2 changed files with 103 additions and 17 deletions
+55 -17
View File
@@ -18,16 +18,18 @@ enum LeftTab {
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
enum PreviewFocus { enum PreviewFocus {
TerrainComposite,
TerrainColor,
Heightmap, Heightmap,
Bumpmap, Bumpmap,
TerrainColor,
} }
#[derive(Clone)] #[derive(Clone)]
struct TextureSet { struct TextureSet {
terrain_composite: TextureHandle,
terrain_color: TextureHandle,
heightmap: TextureHandle, heightmap: TextureHandle,
bumpmap: TextureHandle, bumpmap: TextureHandle,
terrain_color: TextureHandle,
} }
pub struct TerrainApp { pub struct TerrainApp {
@@ -49,7 +51,7 @@ impl TerrainApp {
Self { Self {
params: GenerationParams::default(), params: GenerationParams::default(),
generation_tab: LeftTab::Generation, generation_tab: LeftTab::Generation,
preview_focus: PreviewFocus::TerrainColor, preview_focus: PreviewFocus::TerrainComposite,
hovered_preview: None, hovered_preview: None,
textures: None, textures: None,
generated: None, generated: None,
@@ -90,7 +92,7 @@ impl TerrainApp {
match receiver.try_recv() { match receiver.try_recv() {
Ok(Ok(images)) => { Ok(Ok(images)) => {
self.status_message = format!( self.status_message = format!(
"Generated {}x{} heightmap, bumpmap, and terrain color.", "Generated {}x{} heightmap, bumpmap, terrain color, and composite.",
images.width, images.height images.width, images.height
); );
self.install_textures(ctx, &images); self.install_textures(ctx, &images);
@@ -144,16 +146,22 @@ impl TerrainApp {
fn install_textures(&mut self, ctx: &Context, images: &GeneratedImages) { fn install_textures(&mut self, ctx: &Context, images: &GeneratedImages) {
let heightmap = gray_to_color_image(&images.heightmap_image); let heightmap = gray_to_color_image(&images.heightmap_image);
let bumpmap = gray_to_color_image(&images.bumpmap_image); let bumpmap = gray_to_color_image(&images.bumpmap_image);
let terrain_composite = rgba_to_color_image(&images.terrain_composite_image);
let terrain_color = rgba_to_color_image(&images.terrain_color_image); let terrain_color = rgba_to_color_image(&images.terrain_color_image);
self.textures = Some(TextureSet { self.textures = Some(TextureSet {
heightmap: ctx.load_texture("heightmap_preview", heightmap, TextureOptions::default()), terrain_composite: ctx.load_texture(
bumpmap: ctx.load_texture("bumpmap_preview", bumpmap, TextureOptions::default()), "terrain_composite_preview",
terrain_composite,
TextureOptions::default(),
),
terrain_color: ctx.load_texture( terrain_color: ctx.load_texture(
"terrain_color_preview", "terrain_color_preview",
terrain_color, terrain_color,
TextureOptions::default(), TextureOptions::default(),
), ),
heightmap: ctx.load_texture("heightmap_preview", heightmap, TextureOptions::default()),
bumpmap: ctx.load_texture("bumpmap_preview", bumpmap, TextureOptions::default()),
}); });
} }
@@ -170,18 +178,24 @@ impl TerrainApp {
let image = preview_image_data(generated, preview); let image = preview_image_data(generated, preview);
let success_message = match preview { let success_message = match preview {
PreviewFocus::TerrainComposite => {
String::from("Terrain composite copied to clipboard.")
}
PreviewFocus::TerrainColor => String::from("Terrain color copied to clipboard."),
PreviewFocus::Heightmap => String::from("Heightmap copied to clipboard."), PreviewFocus::Heightmap => String::from("Heightmap copied to clipboard."),
PreviewFocus::Bumpmap => String::from("Bumpmap copied to clipboard."), PreviewFocus::Bumpmap => String::from("Bumpmap copied to clipboard."),
PreviewFocus::TerrainColor => String::from("Terrain color copied to clipboard."),
}; };
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
self.copy_receiver = Some(rx); self.copy_receiver = Some(rx);
self.copy_in_progress = true; self.copy_in_progress = true;
self.status_message = match preview { self.status_message = match preview {
PreviewFocus::TerrainComposite => {
String::from("Copying terrain composite to clipboard...")
}
PreviewFocus::TerrainColor => String::from("Copying terrain color to clipboard..."),
PreviewFocus::Heightmap => String::from("Copying heightmap to clipboard..."), PreviewFocus::Heightmap => String::from("Copying heightmap to clipboard..."),
PreviewFocus::Bumpmap => String::from("Copying bumpmap to clipboard..."), PreviewFocus::Bumpmap => String::from("Copying bumpmap to clipboard..."),
PreviewFocus::TerrainColor => String::from("Copying terrain color to clipboard..."),
}; };
thread::spawn(move || { thread::spawn(move || {
@@ -377,18 +391,20 @@ impl TerrainApp {
}); });
return; return;
}; };
let terrain_composite_texture = textures.terrain_composite.clone();
let terrain_color_texture = textures.terrain_color.clone();
let heightmap_texture = textures.heightmap.clone(); let heightmap_texture = textures.heightmap.clone();
let bumpmap_texture = textures.bumpmap.clone(); let bumpmap_texture = textures.bumpmap.clone();
let terrain_color_texture = textures.terrain_color.clone();
let available = ui.available_size(); let available = ui.available_size();
let bottom_strip_height = (available.y * 0.24).clamp(120.0, 220.0); let bottom_strip_height = (available.y * 0.24).clamp(120.0, 220.0);
let main_height = (available.y - bottom_strip_height - 8.0).max(100.0); let main_height = (available.y - bottom_strip_height - 8.0).max(100.0);
let main_texture = match preview_focus { let main_texture = match preview_focus {
PreviewFocus::TerrainComposite => &terrain_composite_texture,
PreviewFocus::TerrainColor => &terrain_color_texture,
PreviewFocus::Heightmap => &heightmap_texture, PreviewFocus::Heightmap => &heightmap_texture,
PreviewFocus::Bumpmap => &bumpmap_texture, PreviewFocus::Bumpmap => &bumpmap_texture,
PreviewFocus::TerrainColor => &terrain_color_texture,
}; };
let main_response = let main_response =
@@ -411,9 +427,10 @@ impl TerrainApp {
ui.horizontal(|ui| { ui.horizontal(|ui| {
for secondary_focus in secondary_previews { for secondary_focus in secondary_previews {
let secondary_texture = match secondary_focus { let secondary_texture = match secondary_focus {
PreviewFocus::TerrainComposite => &terrain_composite_texture,
PreviewFocus::TerrainColor => &terrain_color_texture,
PreviewFocus::Heightmap => &heightmap_texture, PreviewFocus::Heightmap => &heightmap_texture,
PreviewFocus::Bumpmap => &bumpmap_texture, PreviewFocus::Bumpmap => &bumpmap_texture,
PreviewFocus::TerrainColor => &terrain_color_texture,
}; };
let secondary_label = preview_label(secondary_focus); let secondary_label = preview_label(secondary_focus);
@@ -507,25 +524,46 @@ fn rgba_to_color_image(image: &image::RgbaImage) -> ColorImage {
fn preview_image_data(images: &GeneratedImages, preview: PreviewFocus) -> ImageData<'static> { fn preview_image_data(images: &GeneratedImages, preview: PreviewFocus) -> ImageData<'static> {
match preview { match preview {
PreviewFocus::TerrainComposite => {
rgba_image_to_clipboard_data(&images.terrain_composite_image)
}
PreviewFocus::TerrainColor => rgba_image_to_clipboard_data(&images.terrain_color_image),
PreviewFocus::Heightmap => gray_image_to_clipboard_data(&images.heightmap_image), PreviewFocus::Heightmap => gray_image_to_clipboard_data(&images.heightmap_image),
PreviewFocus::Bumpmap => gray_image_to_clipboard_data(&images.bumpmap_image), PreviewFocus::Bumpmap => gray_image_to_clipboard_data(&images.bumpmap_image),
PreviewFocus::TerrainColor => rgba_image_to_clipboard_data(&images.terrain_color_image),
} }
} }
fn preview_label(preview: PreviewFocus) -> &'static str { fn preview_label(preview: PreviewFocus) -> &'static str {
match preview { match preview {
PreviewFocus::TerrainComposite => "Composite",
PreviewFocus::TerrainColor => "Terrain Color",
PreviewFocus::Heightmap => "Heightmap", PreviewFocus::Heightmap => "Heightmap",
PreviewFocus::Bumpmap => "Bumpmap", PreviewFocus::Bumpmap => "Bumpmap",
PreviewFocus::TerrainColor => "Terrain Color",
} }
} }
fn secondary_previews(preview: PreviewFocus) -> [PreviewFocus; 2] { fn secondary_previews(preview: PreviewFocus) -> [PreviewFocus; 3] {
match preview { match preview {
PreviewFocus::Heightmap => [PreviewFocus::TerrainColor, PreviewFocus::Bumpmap], PreviewFocus::TerrainComposite => [
PreviewFocus::Bumpmap => [PreviewFocus::TerrainColor, PreviewFocus::Heightmap], PreviewFocus::TerrainColor,
PreviewFocus::TerrainColor => [PreviewFocus::Heightmap, PreviewFocus::Bumpmap], PreviewFocus::Heightmap,
PreviewFocus::Bumpmap,
],
PreviewFocus::TerrainColor => [
PreviewFocus::TerrainComposite,
PreviewFocus::Heightmap,
PreviewFocus::Bumpmap,
],
PreviewFocus::Heightmap => [
PreviewFocus::TerrainComposite,
PreviewFocus::TerrainColor,
PreviewFocus::Bumpmap,
],
PreviewFocus::Bumpmap => [
PreviewFocus::TerrainComposite,
PreviewFocus::TerrainColor,
PreviewFocus::Heightmap,
],
} }
} }
+48
View File
@@ -50,6 +50,7 @@ pub struct GeneratedImages {
pub heightmap_image: GrayImage, pub heightmap_image: GrayImage,
pub bumpmap_image: GrayImage, pub bumpmap_image: GrayImage,
pub terrain_color_image: RgbaImage, pub terrain_color_image: RgbaImage,
pub terrain_composite_image: RgbaImage,
#[allow(dead_code)] #[allow(dead_code)]
pub grass_mask_image: GrayImage, pub grass_mask_image: GrayImage,
#[allow(dead_code)] #[allow(dead_code)]
@@ -402,6 +403,8 @@ pub fn generate(params: &GenerationParams) -> GeneratedImages {
params.snowline_height, params.snowline_height,
params.rock_slope_angle_degrees, params.rock_slope_angle_degrees,
); );
let terrain_composite_image =
build_soft_light_composite(&terrain_layers.terrain_color_image, &bumpmap_image);
GeneratedImages { GeneratedImages {
width, width,
@@ -409,6 +412,7 @@ pub fn generate(params: &GenerationParams) -> GeneratedImages {
heightmap_image, heightmap_image,
bumpmap_image, bumpmap_image,
terrain_color_image: terrain_layers.terrain_color_image, terrain_color_image: terrain_layers.terrain_color_image,
terrain_composite_image,
grass_mask_image: terrain_layers.grass_mask_image, grass_mask_image: terrain_layers.grass_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,
@@ -515,6 +519,50 @@ fn build_terrain_layers(
} }
} }
fn build_soft_light_composite(color_image: &RgbaImage, bumpmap_image: &GrayImage) -> RgbaImage {
let mut composite = RgbaImage::new(color_image.width(), color_image.height());
for y in 0..color_image.height() {
for x in 0..color_image.width() {
let base = color_image.get_pixel(x, y).0;
let blend = 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 g = soft_light_channel(base[1] as f32 / 255.0, blend);
let b = soft_light_channel(base[2] as f32 / 255.0, blend);
composite.put_pixel(
x,
y,
Rgba([
(r * 255.0).round() as u8,
(g * 255.0).round() as u8,
(b * 255.0).round() as u8,
base[3],
]),
);
}
}
composite
}
fn soft_light_channel(base: f32, blend: f32) -> f32 {
let base = clamp01(base);
let blend = clamp01(blend);
let result = if blend <= 0.5 {
base - (1.0 - 2.0 * blend) * base * (1.0 - base)
} else {
let d = if base <= 0.25 {
((16.0 * base - 12.0) * base + 4.0) * base
} else {
base.sqrt()
};
base + (2.0 * blend - 1.0) * (d - base)
};
clamp01(result)
}
fn apply_shading(heightmap: &[f32], width: usize, height: usize, strength: f32) -> Vec<f32> { fn apply_shading(heightmap: &[f32], width: usize, height: usize, strength: f32) -> Vec<f32> {
let mut output = vec![0.0; width * height]; let mut output = vec![0.0; width * height];