feat: wire vertical exaggeration through the shell #13
+50
-23
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user