feat: wire vertical exaggeration through the shell #13

Merged
moldybits merged 25 commits from feat/terrain-gen-abstraction into main 2026-05-20 23:00:29 -04:00
5 changed files with 93 additions and 35 deletions
Showing only changes of commit 039eac04bf - Show all commits
+50 -23
View File
@@ -205,6 +205,13 @@ impl OpenVistaProApp {
ui.separator(); ui.separator();
ui.label("Hydrology"); ui.label("Hydrology");
ui.horizontal(|ui| {
if ui.button("Reset hydrology").clicked() {
self.data.apply(AppAction::ResetHydrology);
changed = true;
}
ui.label("Lake and river overlays are clean-room defaults");
});
let mut river_level = self.data.scene.hydrology.river_level; let mut river_level = self.data.scene.hydrology.river_level;
let mut lake_level = self.data.scene.hydrology.lake_level; let mut lake_level = self.data.scene.hydrology.lake_level;
let mut drainage = self.data.scene.hydrology.drainage; let mut drainage = self.data.scene.hydrology.drainage;
@@ -217,39 +224,59 @@ impl OpenVistaProApp {
changed |= ui changed |= ui
.add(egui::Slider::new(&mut drainage, 0.0..=5.0).text("Drainage")) .add(egui::Slider::new(&mut drainage, 0.0..=5.0).text("Drainage"))
.changed(); .changed();
ui.label("Lake placement");
let mut lake_center_x = self.data.scene.hydrology.lake_center_x;
let mut lake_center_z = self.data.scene.hydrology.lake_center_z;
let mut lake_radius = self.data.scene.hydrology.lake_radius;
ui.horizontal(|ui| {
ui.label("Center X/Z");
changed |= ui
.add(
egui::DragValue::new(&mut lake_center_x)
.speed(0.01)
.range(0.0..=1.0),
)
.changed();
changed |= ui
.add(
egui::DragValue::new(&mut lake_center_z)
.speed(0.01)
.range(0.0..=1.0),
)
.changed();
});
changed |= ui
.add(egui::Slider::new(&mut lake_radius, 0.0..=0.5).text("Radius"))
.changed();
ui.label("River placement");
let mut river_center_x = self.data.scene.hydrology.river_center_x;
let mut river_width = self.data.scene.hydrology.river_width;
let mut river_bend = self.data.scene.hydrology.river_bend;
changed |= ui
.add(egui::Slider::new(&mut river_center_x, 0.0..=1.0).text("Center X"))
.changed();
changed |= ui
.add(egui::Slider::new(&mut river_width, 0.0..=0.25).text("Width"))
.changed();
changed |= ui
.add(egui::Slider::new(&mut river_bend, 0.0..=0.35).text("Bend"))
.changed();
self.data.apply(AppAction::SetHydrology { self.data.apply(AppAction::SetHydrology {
river_level, river_level,
lake_level, lake_level,
drainage, drainage,
lake_center_x,
lake_center_z,
lake_radius,
river_center_x,
river_width,
river_bend,
}); });
ui.separator(); ui.separator();
ui.label("Color map"); ui.label("Color map");
ui.label("Core elevation bands"); ui.label("Core elevation bands");
let mut palette = self.data.scene.palette; let mut palette = self.data.scene.palette;
changed |= ui
.add(egui::Slider::new(&mut palette.water_level, -5.0..=15.0).text("Water level"))
.changed();
changed |= ui
.add(egui::Slider::new(&mut palette.tree_line, -5.0..=15.0).text("Tree line"))
.changed();
changed |= ui
.add(egui::Slider::new(&mut palette.snow_line, -5.0..=15.0).text("Snow line"))
.changed();
if palette.tree_line < palette.water_level + 0.1 {
palette.tree_line = palette.water_level + 0.1;
changed = true;
}
if palette.snow_line < palette.tree_line + 0.1 {
palette.snow_line = palette.tree_line + 0.1;
changed = true;
}
if palette.water_level > palette.tree_line - 0.1 {
palette.water_level = palette.tree_line - 0.1;
changed = true;
}
ui.separator();
ui.label("Band colors");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Water"); ui.label("Water");
changed |= ui.color_edit_button_srgb(&mut palette.water).changed(); changed |= ui.color_edit_button_srgb(&mut palette.water).changed();
+1 -1
View File
@@ -4,7 +4,7 @@ use image::RgbImage;
use crate::path::{CameraPath, build_demo_path}; use crate::path::{CameraPath, build_demo_path};
use crate::render::{ use crate::render::{
render_perspective_with_quality, render_top_down_with_quality, RenderQualityPreset, RenderQualityPreset, render_perspective_with_quality, render_top_down_with_quality,
}; };
use crate::scene::{Scene, Vec3}; use crate::scene::{Scene, Vec3};
use crate::scene_file::{self, SceneFileError}; use crate::scene_file::{self, SceneFileError};
+20 -1
View File
@@ -3,7 +3,9 @@ use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum}; use clap::{Args, Parser, Subcommand, ValueEnum};
use image::ImageError; use image::ImageError;
use crate::render::{demo_camera_for, render_perspective_to_path, render_top_down_to_path}; use crate::render::{
RenderQualityPreset, demo_camera_for, render_perspective_to_path, render_top_down_to_path,
};
use crate::scene::Scene; use crate::scene::Scene;
use crate::scene_file::{self, SceneFileError}; use crate::scene_file::{self, SceneFileError};
use crate::script_exec::{self, ScriptError}; use crate::script_exec::{self, ScriptError};
@@ -55,6 +57,9 @@ pub struct RenderArgs {
/// the top-down preview. /// the top-down preview.
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub camera_demo: bool, pub camera_demo: bool,
/// Render-quality preset for the CPU spike.
#[arg(long, value_enum, default_value_t = RenderQualityPreset::Preview)]
pub quality: RenderQualityPreset,
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
@@ -146,10 +151,23 @@ impl From<ScriptError> for CliError {
} }
} }
impl Preset {
pub fn slug(self) -> &'static str {
match self {
Preset::Plane => "plane",
Preset::Hill => "hill",
}
}
}
pub fn supported_presets() -> &'static [&'static str] { pub fn supported_presets() -> &'static [&'static str] {
&["plane", "hill"] &["plane", "hill"]
} }
pub fn supported_quality_presets() -> &'static [&'static str] {
&["preview", "balanced", "final"]
}
pub fn supported_importers() -> &'static [&'static str] { pub fn supported_importers() -> &'static [&'static str] {
#[cfg(all( #[cfg(all(
feature = "hgt", feature = "hgt",
@@ -223,6 +241,7 @@ pub fn info_text() -> String {
let mut text = String::new(); let mut text = String::new();
writeln!(&mut text, "openvistapro {}", env!("CARGO_PKG_VERSION")).unwrap(); writeln!(&mut text, "openvistapro {}", env!("CARGO_PKG_VERSION")).unwrap();
writeln!(&mut text, "presets: {}", supported_presets().join(", ")).unwrap(); writeln!(&mut text, "presets: {}", supported_presets().join(", ")).unwrap();
writeln!(&mut text, "quality: {}", supported_quality_presets().join(", ")).unwrap();
let importers = supported_importers(); let importers = supported_importers();
if importers.is_empty() { if importers.is_empty() {
writeln!(&mut text, "importers: (none)").unwrap(); writeln!(&mut text, "importers: (none)").unwrap();
+6 -8
View File
@@ -184,7 +184,11 @@ fn hydrology_overlay_color(scene: &Scene, nx: f32, nz: f32) -> Option<[u8; 3]> {
}; };
let colored = mix_color(scene.palette.water, tint, coverage); let colored = mix_color(scene.palette.water, tint, coverage);
Some(if coverage > 0.72 { Some(if coverage > 0.72 {
mix_color(colored, [245, 250, 255], (coverage - 0.72).clamp(0.0, 0.28) * 3.0) mix_color(
colored,
[245, 250, 255],
(coverage - 0.72).clamp(0.0, 0.28) * 3.0,
)
} else { } else {
colored colored
}) })
@@ -321,13 +325,7 @@ pub fn demo_camera_for(grid: &HeightGrid) -> Camera {
/// Coordinate convention: world X and Z map onto the grid's `(x, y)` indices, /// Coordinate convention: world X and Z map onto the grid's `(x, y)` indices,
/// world Y is elevation, up = +Y. /// world Y is elevation, up = +Y.
pub fn render_perspective(grid: &HeightGrid, scene: &Scene, width: u32, height: u32) -> RgbImage { pub fn render_perspective(grid: &HeightGrid, scene: &Scene, width: u32, height: u32) -> RgbImage {
render_perspective_with_quality( render_perspective_with_quality(grid, scene, width, height, RenderQualityPreset::Preview)
grid,
scene,
width,
height,
RenderQualityPreset::Preview,
)
} }
pub fn render_perspective_with_quality( pub fn render_perspective_with_quality(
+16 -2
View File
@@ -88,7 +88,11 @@ impl From<toml::de::Error> for SceneFileError {
/// Serialize `scene` into a TOML string with schema and version headers. /// Serialize `scene` into a TOML string with schema and version headers.
pub fn to_toml_string(scene: &Scene) -> Result<String, SceneFileError> { pub fn to_toml_string(scene: &Scene) -> Result<String, SceneFileError> {
let file = SceneFile::new(*scene); let mut scene = *scene;
scene
.palette
.sync_thresholds_from_scene(scene.water_level, scene.tree_line, scene.snow_line);
let file = SceneFile::new(scene);
Ok(toml::to_string_pretty(&file)?) Ok(toml::to_string_pretty(&file)?)
} }
@@ -101,7 +105,11 @@ pub fn from_toml_str(text: &str) -> Result<Scene, SceneFileError> {
if file.version != SCENE_VERSION { if file.version != SCENE_VERSION {
return Err(SceneFileError::UnsupportedVersion(file.version)); return Err(SceneFileError::UnsupportedVersion(file.version));
} }
Ok(file.scene) let mut scene = file.scene;
scene
.palette
.sync_thresholds_from_scene(scene.water_level, scene.tree_line, scene.snow_line);
Ok(scene)
} }
/// Write `scene` to `path` as an OpenVistaPro `.ovp.toml` scene file. /// Write `scene` to `path` as an OpenVistaPro `.ovp.toml` scene file.
@@ -150,6 +158,12 @@ mod tests {
river_level: 0.8, river_level: 0.8,
lake_level: 1.1, lake_level: 1.1,
drainage: 0.2, drainage: 0.2,
lake_center_x: 0.42,
lake_center_z: 0.58,
lake_radius: 0.11,
river_center_x: 0.6,
river_width: 0.09,
river_bend: 0.04,
}, },
palette: Palette { palette: Palette {
water_level: 0.5, water_level: 0.5,