added a composite image
This commit is contained in:
+55
-17
@@ -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,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user