diff --git a/src/app.rs b/src/app.rs index 731026b..722e293 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use std::sync::mpsc::{self, Receiver, TryRecvError}; use std::thread; use eframe::egui::{ - self, Color32, ColorImage, Context, DragValue, RichText, Sense, Slider, TextureHandle, + self, Color32, ColorImage, Context, DragValue, Frame, RichText, Sense, Slider, TextureHandle, TextureOptions, Ui, Vec2, }; use rand::Rng; @@ -42,7 +42,7 @@ impl TerrainApp { Self { params: GenerationParams::default(), generation_tab: LeftTab::Generation, - preview_focus: PreviewFocus::Heightmap, + preview_focus: PreviewFocus::Bumpmap, textures: None, generated: None, generation_receiver: None, @@ -127,11 +127,16 @@ impl TerrainApp { .default_width(320.0) .min_width(280.0) .show(ctx, |ui| { - ui.heading("Terrain Generator"); + ui.heading("Advanced Erosion Terrain Generator"); ui.add_space(8.0); ui.horizontal(|ui| { - Self::tab_button(ui, &mut self.generation_tab, LeftTab::Generation, "Generation"); + Self::tab_button( + ui, + &mut self.generation_tab, + LeftTab::Generation, + "Generation", + ); Self::tab_button(ui, &mut self.generation_tab, LeftTab::Advanced, "Advanced"); }); @@ -231,46 +236,46 @@ impl TerrainApp { let bottom_strip_height = (available.y * 0.24).max(120.0).min(220.0); let main_height = (available.y - bottom_strip_height - 8.0).max(100.0); - let (main_texture, bottom_texture) = match self.preview_focus { - PreviewFocus::Heightmap => (&textures.heightmap, &textures.bumpmap), - PreviewFocus::Bumpmap => (&textures.bumpmap, &textures.heightmap), + let main_texture = match self.preview_focus { + PreviewFocus::Heightmap => &textures.heightmap, + PreviewFocus::Bumpmap => &textures.bumpmap, }; render_clickable_image(ui, main_texture, Vec2::new(available.x, main_height), false); ui.add_space(8.0); + let thumb_width = (available.x * 0.28).max(120.0).min(240.0); + let panel_width = (thumb_width + 32.0).min(available.x); + let secondary_texture = match self.preview_focus { + PreviewFocus::Heightmap => &textures.bumpmap, + PreviewFocus::Bumpmap => &textures.heightmap, + }; + let secondary_focus = match self.preview_focus { + PreviewFocus::Heightmap => PreviewFocus::Bumpmap, + PreviewFocus::Bumpmap => PreviewFocus::Heightmap, + }; + let secondary_label = match secondary_focus { + PreviewFocus::Heightmap => "Heightmap", + PreviewFocus::Bumpmap => "Bumpmap", + }; + ui.horizontal_centered(|ui| { - let thumb_width = ((available.x - 24.0) * 0.28).max(100.0).min(220.0); - let spacer = (available.x - thumb_width * 2.0).max(0.0); - - let main_thumb_clicked = render_clickable_image( - ui, - match self.preview_focus { - PreviewFocus::Heightmap => &textures.heightmap, - PreviewFocus::Bumpmap => &textures.bumpmap, - }, - Vec2::new(thumb_width, bottom_strip_height), - true, - ); - - ui.add_space(spacer); - - let secondary_clicked = - render_clickable_image(ui, bottom_texture, Vec2::new(thumb_width, bottom_strip_height), true); - - if main_thumb_clicked { - self.preview_focus = match self.preview_focus { - PreviewFocus::Heightmap => PreviewFocus::Heightmap, - PreviewFocus::Bumpmap => PreviewFocus::Bumpmap, - }; - } - - if secondary_clicked { - self.preview_focus = match self.preview_focus { - PreviewFocus::Heightmap => PreviewFocus::Bumpmap, - PreviewFocus::Bumpmap => PreviewFocus::Heightmap, - }; - } + Frame::group(ui.style()).show(ui, |ui| { + ui.set_min_width(panel_width); + ui.set_max_width(panel_width); + ui.vertical_centered(|ui| { + ui.label(RichText::new(secondary_label).strong()); + ui.add_space(6.0); + if render_clickable_image( + ui, + secondary_texture, + Vec2::new(thumb_width, bottom_strip_height - 32.0), + true, + ) { + self.preview_focus = secondary_focus; + } + }); + }); }); }); } @@ -294,7 +299,12 @@ fn gray_to_color_image(image: &image::GrayImage) -> ColorImage { ColorImage::new(size, pixels) } -fn labeled_int_slider(ui: &mut Ui, label: &str, value: &mut usize, range: std::ops::RangeInclusive) { +fn labeled_int_slider( + ui: &mut Ui, + label: &str, + value: &mut usize, + range: std::ops::RangeInclusive, +) { ui.label(label); let mut slider_value = *value as u32; if ui @@ -320,12 +330,21 @@ fn labeled_float_slider( ui.add(slider); } -fn render_clickable_image(ui: &mut Ui, texture: &TextureHandle, max_size: Vec2, clickable: bool) -> bool { +fn render_clickable_image( + ui: &mut Ui, + texture: &TextureHandle, + max_size: Vec2, + clickable: bool, +) -> bool { let image_size = texture.size_vec2(); let scale = (max_size.x / image_size.x).min(max_size.y / image_size.y); let desired_size = image_size * scale.max(0.01); let image = egui::Image::new(texture) .fit_to_exact_size(desired_size) - .sense(if clickable { Sense::click() } else { Sense::hover() }); + .sense(if clickable { + Sense::click() + } else { + Sense::hover() + }); ui.add(image).clicked() } diff --git a/src/generator.rs b/src/generator.rs index f279eac..ebde71a 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -28,7 +28,7 @@ impl Default for GenerationParams { Self { image_width: 512, image_height: 512, - generation_scale: 6.0, + generation_scale: 1.0, erosion_scale: 0.15, random_seed: 0, erosion_strength: 0.05, @@ -124,9 +124,10 @@ fn clamp01(x: f32) -> f32 { } fn hash22(p: Vec2, random_seed: u64) -> Vec2 { - let seed = random_seed as f64; - let seed_x = seed * 0.754_877_666_246_692_7; - let seed_y = seed * 0.569_840_290_998_053_2; + let scrambled_x = splitmix64(random_seed ^ 0x9E37_79B9_7F4A_7C15); + let scrambled_y = splitmix64(random_seed ^ 0xBF58_476D_1CE4_E5B9); + let seed_x = u64_to_unit_f64(scrambled_x) * 1024.0; + let seed_y = u64_to_unit_f64(scrambled_y) * 1024.0; let kx = 0.318_309_9_f64; let ky = 0.367_879_4_f64; @@ -140,6 +141,18 @@ fn hash22(p: Vec2, random_seed: u64) -> Vec2 { Vec2::new(rx as f32, ry as f32) } +fn splitmix64(mut x: u64) -> u64 { + x = x.wrapping_add(0x9E37_79B9_7F4A_7C15); + x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + x ^ (x >> 31) +} + +fn u64_to_unit_f64(value: u64) -> f64 { + const SCALE: f64 = 1.0 / (u64::MAX as f64); + (value as f64) * SCALE +} + fn noised(p: Vec2, random_seed: u64) -> (f32, Vec2) { let i = Vec2::new(p.x.floor(), p.y.floor()); let f = Vec2::new(p.x - i.x, p.y - i.y);