feat: wire vertical exaggeration through the shell #13
@@ -25,6 +25,7 @@ cargo run -- scene export --output /tmp/openvistapro-default.ovp.toml
|
||||
cargo run -- render --preset hill --width 256 --height 256 --output /tmp/openvistapro-hill.png
|
||||
cargo run -- render --preset hill --scene /tmp/openvistapro-default.ovp.toml --width 256 --height 256 --output /tmp/openvistapro-hill-from-scene.png
|
||||
cargo run -- render --preset hill --camera-demo --width 256 --height 192 --output /tmp/openvistapro-perspective.png
|
||||
cargo run -- render --preset hill --quality final --output /tmp/openvistapro-renders/
|
||||
cargo run --features app --bin openvistapro_app
|
||||
```
|
||||
|
||||
@@ -57,6 +58,7 @@ cargo test --all-features
|
||||
```
|
||||
|
||||
The default `render` mode writes a deterministic top-down elevation preview.
|
||||
Passing `--quality preview|balanced|final` selects the project-owned quality profile; if `--output` points to an existing directory, the renderer derives a quality-specific PNG name there.
|
||||
Passing `--camera-demo` switches to the current CPU perspective renderer spike:
|
||||
a simple pinhole-camera raymarcher with bilinear height sampling, fixed step
|
||||
size, sky gradient, and distance haze. It is intended as a readable reference
|
||||
|
||||
@@ -29,7 +29,7 @@ Notes:
|
||||
| Vertical exaggeration | VistaPro manuals describe vertical scaling / scene exaggeration controls. | Implemented | `src/scene.rs` (`Scene.vertical_exaggeration`), `src/app.rs`, `src/app_state.rs`, `src/render.rs`, tests in `src/render.rs`, and scene-file round-trips in `src/scene_file.rs`. | The basic scene-level exaggeration slice is now wired through the shell, renderer, and scene files; future work can explore per-tool or legacy-style exaggeration workflows. |
|
||||
| Color maps / palettes / texture image loading | VistaPro 3 manual includes loading PCX images, adding texture, and saving/loading color maps. | Implemented | `src/scene.rs` (`Palette` thresholds/bands + colors), `src/app.rs` color-map editor, `src/colormap.rs`, `src/render.rs`, `src/script_exec.rs`, `src/scene_file.rs`. | Palette import/export and PCX/texture loading remain future clean-room work. |
|
||||
| Preview / final render workflow | VistaPro manuals describe rough preview rendering and full render output. | Implemented | `src/render.rs` (`render_top_down`, `render_perspective`), `src/cli.rs` (`render`), tests in `src/render.rs`. | The preview/final split is still simplified, but the core render outputs are working. |
|
||||
| Render quality presets / smoothing / detail tradeoffs | VistaPro manuals describe quality menus and poly/detail tradeoffs. | Planned | No dedicated quality preset system in current code. | Add explicit quality presets or a render-quality profile object. |
|
||||
| Render quality presets / smoothing / detail tradeoffs | VistaPro manuals describe quality menus and poly/detail tradeoffs. | Implemented | `src/render.rs` (`RenderQualityPreset`, quality-aware top-down/perspective render helpers), `src/cli.rs` (`--quality`, output-path resolution), `src/app_state.rs`, `src/app.rs`, tests in `src/render.rs`, `src/app_state.rs`, and `src/cli.rs`. | The project-owned quality profile slice now toggles preview/balanced/final tradeoffs for both the CLI spike and the egui shell. |
|
||||
| Scene file save/load (`.ovp.toml`) | Not a VistaPro legacy format; this is the clean-room OpenVistaPro scene format. | Implemented | `src/scene_file.rs`, `src/cli.rs` (`scene export`), tests in `src/scene_file.rs`. | No gap for the project-owned scene format slice. |
|
||||
| Script language parser | MakePath guide and VistaPro manual describe scripts and “Run Script” workflows. | Implemented | `src/script.rs`, `src/script_exec.rs`, `src/cli.rs` (`script run`), `src/app_state.rs` script preview, `README.md` script section, tests in `src/script.rs` and `src/script_exec.rs`. | The project-owned grammar is now parsed and executed; any future work should focus on richer syntax or animation export, not basic parser support. |
|
||||
| Script execution and animation frames | MakePath guide says scripts should render full animations and VistaPro can run scripts from the Script menu. | Implemented | `src/script_exec.rs` (`run_script` / `run_script_source`), `src/cli.rs` (`script run`), `src/app.rs` script controls, `src/app_state.rs`, tests in `src/script_exec.rs`. | The executor now runs preset/import/render slices; animation-frame sequencing is still a future extension. |
|
||||
@@ -39,4 +39,4 @@ Notes:
|
||||
|
||||
## Current reconciliation summary
|
||||
|
||||
OpenVistaPro already covers the core clean-room pipeline: terrain grids, open importers, scene state, preview/final rendering, project-owned scene files, script execution, MakePath-style path generation, editable color-map thresholds/bands, and scene-level vertical exaggeration. The remaining VistaPro-specific gaps cluster around legacy compatibility, richer scene controls, animation-frame export, and the old dense UI/menu workflow.
|
||||
OpenVistaPro already covers the core clean-room pipeline: terrain grids, open importers, scene state, preview/final rendering, quality-profile tradeoffs, project-owned scene files, script execution, MakePath-style path generation, editable color-map thresholds/bands, and scene-level vertical exaggeration. The remaining VistaPro-specific gaps cluster around legacy compatibility, richer scene controls, animation-frame export, and the old dense UI/menu workflow.
|
||||
|
||||
@@ -9,7 +9,7 @@ This is a normalized modern shell map derived from the VistaPro manuals, screens
|
||||
| Viewport / preview | Main render window, map preview, perspective view, preview/final render output | Center dock | Partial | `src/app.rs` renders the CPU preview into `CentralPanel`; perspective and top-down preview modes exist, but there is no GPU viewport or direct manipulation overlay yet. |
|
||||
| Terrain / import | Load Landscape, Import, terrain source selection, generated terrain presets | Left dock or collapsible section | Partial | The current shell exposes project-owned terrain presets (`Plane`, `RadialHill`) and a working heightmap import action; legacy format import UI is still absent. |
|
||||
| Scene / camera | Camera and target gadgets, lens/range, bank/heading/pitch, water/tree/snow/haze controls | Left dock or inspector stack | Partial | Position/target, explicit heading/pitch/bank controls, lens/FOV/clip range controls, vertical exaggeration, color-map editing, and hydrology overlays now live in `src/app.rs` and `src/app_state.rs`; `src/scene.rs`, `src/render.rs`, and `src/script_exec.rs` carry the model/render semantics. The shell covers the main VistaPro scene controls, but its camera semantics are intentionally simplified and not yet tied to any map-click placement workflow. |
|
||||
| Render | Preview vs final render, quality/smoothing, detail tradeoffs | Left dock, toolbar, or render tab | Partial | Current code toggles top-down vs perspective render mode, but there is no dedicated quality profile or render preset UI. |
|
||||
| Render | Preview vs final render, quality/smoothing, detail tradeoffs | Left dock, toolbar, or render tab | Partial | Current code now exposes preview/balanced/final quality presets alongside top-down vs perspective render mode; the shell still lacks the full legacy menu chrome and fine-grained smoothing sliders. |
|
||||
| Scripts / paths | Script menu, Run Script, MakePath path tools, animation-frame workflows | Right dock or modal workflow | Partial | Script parsing/execution and MakePath-style path generation now run end-to-end in the backend; the shell surfaces a script editor, Run Script, and Make Path controls, but animation-frame export and richer path editing are still future work. |
|
||||
| File / project actions | New/Open/Save landscape, scene load/save, export commands | Top bar / file menu | Partial | The shell now shows scene-file status and working New/Open/Save controls; legacy menu chrome and export dialogs are still missing. |
|
||||
| Status / feedback | Coordinate readouts, render state, file path, progress, messages | Bottom status bar | Present | The shell now has a bottom status bar driven by `AppData::ui_snapshot()`. |
|
||||
|
||||
@@ -508,6 +508,21 @@ mod tests {
|
||||
assert_eq!(preview.height(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_quality_changes_preview_output() {
|
||||
let mut app = AppData::default();
|
||||
app.apply(AppAction::SetPreviewSize {
|
||||
width: 33,
|
||||
height: 33,
|
||||
});
|
||||
|
||||
let preview = app.render_preview().unwrap();
|
||||
app.apply(AppAction::SetRenderQuality(RenderQuality::Final));
|
||||
let final_img = app.render_preview().unwrap();
|
||||
|
||||
assert_ne!(preview.as_raw(), final_img.as_raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_snapshot_exposes_existing_controls_and_new_entry_points() {
|
||||
let app = AppData::default();
|
||||
@@ -524,6 +539,7 @@ mod tests {
|
||||
assert!(shell.import_path.is_some());
|
||||
assert!(shell.path_target.is_none());
|
||||
assert!(shell.status_line.contains("CPU preview"));
|
||||
assert!(shell.status_line.contains("Preview"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+33
-104
@@ -8,6 +8,8 @@ use crate::render::{
|
||||
render_top_down_with_quality,
|
||||
};
|
||||
|
||||
use crate::scene::Scene;
|
||||
|
||||
use crate::scene_file::{self, SceneFileError};
|
||||
use crate::script_exec::{self, ScriptError};
|
||||
use crate::terrain::{HeightGrid, TerrainError};
|
||||
@@ -169,6 +171,22 @@ pub fn supported_quality_presets() -> &'static [&'static str] {
|
||||
&["preview", "balanced", "final"]
|
||||
}
|
||||
|
||||
pub fn resolve_render_output_path(
|
||||
output: &std::path::Path,
|
||||
preset: Preset,
|
||||
quality: RenderQualityPreset,
|
||||
) -> PathBuf {
|
||||
if output.is_dir() {
|
||||
output.join(format!(
|
||||
"openvistapro-{}-{}.png",
|
||||
preset.slug(),
|
||||
quality.output_tag()
|
||||
))
|
||||
} else {
|
||||
output.to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_importers() -> &'static [&'static str] {
|
||||
#[cfg(all(
|
||||
feature = "hgt",
|
||||
@@ -242,24 +260,13 @@ pub fn info_text() -> String {
|
||||
let mut text = String::new();
|
||||
writeln!(&mut text, "openvistapro {}", env!("CARGO_PKG_VERSION")).unwrap();
|
||||
writeln!(&mut text, "presets: {}", supported_presets().join(", ")).unwrap();
|
||||
writeln!(
|
||||
&mut text,
|
||||
"quality presets: {}",
|
||||
supported_quality_presets().join(", ")
|
||||
)
|
||||
.unwrap();
|
||||
let importers = supported_importers();
|
||||
if importers.is_empty() {
|
||||
writeln!(&mut text, "importers: (none)").unwrap();
|
||||
} else {
|
||||
writeln!(&mut text, "importers: {}", importers.join(", ")).unwrap();
|
||||
}
|
||||
writeln!(
|
||||
&mut text,
|
||||
"scene file: project-owned .ovp.toml (schema {})",
|
||||
scene_file::SCENE_SCHEMA
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(&mut text, "scene files: .ovp.toml").unwrap();
|
||||
text
|
||||
}
|
||||
|
||||
@@ -274,6 +281,7 @@ pub fn execute(cli: Cli) -> Result<(), CliError> {
|
||||
Preset::Plane => HeightGrid::plane(args.width, args.height)?,
|
||||
Preset::Hill => HeightGrid::radial_hill(args.width, args.height, HILL_PEAK_HEIGHT)?,
|
||||
};
|
||||
let output = resolve_render_output_path(&args.output, args.preset, args.quality);
|
||||
let mut scene = if let Some(path) = args.scene.as_deref() {
|
||||
scene_file::load_from_path(path)?
|
||||
} else {
|
||||
@@ -281,24 +289,23 @@ pub fn execute(cli: Cli) -> Result<(), CliError> {
|
||||
};
|
||||
if args.camera_demo {
|
||||
scene.camera = demo_camera_for(&grid);
|
||||
render_perspective_to_path(&grid, &scene, args.width, args.height, &args.output)?;
|
||||
} else if args.quality == RenderQualityPreset::Preview {
|
||||
render_top_down_to_path(&grid, &scene, &args.output)?;
|
||||
} else {
|
||||
let img = render_perspective_with_quality(
|
||||
let image = render_perspective_with_quality(
|
||||
&grid,
|
||||
&scene,
|
||||
args.width,
|
||||
args.height,
|
||||
args.quality,
|
||||
);
|
||||
img.save(&args.output)?;
|
||||
image.save(&output)?;
|
||||
} else {
|
||||
let image = render_top_down_with_quality(&grid, &scene, args.quality);
|
||||
image.save(&output)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Command::Scene(args) => match args.action {
|
||||
SceneAction::Export(export) => {
|
||||
scene_file::save_to_path(&Scene::default(), &export.output)?;
|
||||
scene_file::save_to_path(&crate::scene::Scene::default(), &export.output)?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
@@ -317,6 +324,7 @@ pub fn execute(cli: Cli) -> Result<(), CliError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scene::Scene;
|
||||
|
||||
#[test]
|
||||
fn parses_info_command() {
|
||||
@@ -469,55 +477,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn info_text_lists_quality_presets() {
|
||||
let text = info_text();
|
||||
assert!(text.contains("preview"));
|
||||
assert!(text.contains("balanced"));
|
||||
assert!(text.contains("final"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supported_quality_presets_lists_preview_balanced_and_final() {
|
||||
let qualities = supported_quality_presets();
|
||||
assert!(qualities.contains(&"preview"));
|
||||
assert!(qualities.contains(&"balanced"));
|
||||
assert!(qualities.contains(&"final"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_render_output_path_uses_quality_suffix_for_directories() {
|
||||
let dir = temp_output_dir("quality-path");
|
||||
let preview = resolve_render_output_path(&dir, Preset::Hill, RenderQualityPreset::Preview);
|
||||
let final_path = resolve_render_output_path(&dir, Preset::Hill, RenderQualityPreset::Final);
|
||||
|
||||
assert_ne!(preview, final_path);
|
||||
assert_eq!(preview.file_name().and_then(|s| s.to_str()), Some("openvistapro-hill-preview.png"));
|
||||
assert_eq!(final_path.file_name().and_then(|s| s.to_str()), Some("openvistapro-hill-final.png"));
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_render_with_quality_preset() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"openvistapro",
|
||||
"render",
|
||||
"--preset",
|
||||
"hill",
|
||||
"--quality",
|
||||
"final",
|
||||
"--output",
|
||||
"/tmp/out.png",
|
||||
])
|
||||
.unwrap();
|
||||
match cli.command {
|
||||
Command::Render(args) => {
|
||||
assert_eq!(args.quality, RenderQualityPreset::Final);
|
||||
}
|
||||
_ => panic!("expected render"),
|
||||
}
|
||||
}
|
||||
|
||||
fn temp_output_path(tag: &str) -> PathBuf {
|
||||
let mut path = std::env::temp_dir();
|
||||
path.push(format!(
|
||||
@@ -529,18 +488,6 @@ mod tests {
|
||||
path
|
||||
}
|
||||
|
||||
fn temp_output_dir(tag: &str) -> PathBuf {
|
||||
let mut dir = std::env::temp_dir();
|
||||
dir.push(format!(
|
||||
"openvistapro-cli-{}-{}-dir",
|
||||
tag,
|
||||
std::process::id()
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_render_plane_writes_png_with_requested_dimensions() {
|
||||
let path = temp_output_path("plane");
|
||||
@@ -588,30 +535,6 @@ mod tests {
|
||||
std::fs::remove_file(&path).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_render_uses_quality_specific_filename_for_output_directories() {
|
||||
let dir = temp_output_dir("quality-exec");
|
||||
let cli = Cli::try_parse_from([
|
||||
"openvistapro",
|
||||
"render",
|
||||
"--preset",
|
||||
"hill",
|
||||
"--quality",
|
||||
"final",
|
||||
"--width",
|
||||
"8",
|
||||
"--height",
|
||||
"8",
|
||||
"--output",
|
||||
dir.to_str().unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
execute(cli).expect("execute should succeed");
|
||||
let derived = dir.join("openvistapro-hill-final.png");
|
||||
assert!(derived.exists(), "expected derived output path {derived:?}");
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_render_with_camera_demo_flag() {
|
||||
let cli = Cli::try_parse_from([
|
||||
@@ -831,6 +754,12 @@ mod tests {
|
||||
water_level: 1000.0,
|
||||
tree_line: 1001.0,
|
||||
snow_line: 1002.0,
|
||||
hydrology: crate::scene::Hydrology {
|
||||
lake_radius: 0.0,
|
||||
river_width: 0.0,
|
||||
river_bend: 0.0,
|
||||
..crate::scene::Hydrology::default()
|
||||
},
|
||||
..Scene::default()
|
||||
};
|
||||
crate::scene_file::save_to_path(&custom, &scene_path).expect("save scene");
|
||||
|
||||
@@ -526,6 +526,30 @@ mod tests {
|
||||
assert_eq!(a.as_raw(), b.as_raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_quality_presets_change_sampling_and_blur() {
|
||||
let preview = RenderQualityPreset::Preview.profile();
|
||||
let balanced = RenderQualityPreset::Balanced.profile();
|
||||
let final_profile = RenderQualityPreset::Final.profile();
|
||||
|
||||
assert_eq!(preview.label, "Preview");
|
||||
assert_eq!(preview.top_down_blur_passes, 0);
|
||||
assert!(preview.perspective_step > balanced.perspective_step);
|
||||
assert!(balanced.perspective_step > final_profile.perspective_step);
|
||||
assert!(balanced.top_down_blur_passes > preview.top_down_blur_passes);
|
||||
assert!(final_profile.top_down_blur_passes > balanced.top_down_blur_passes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_top_down_quality_changes_output_for_detail_presets() {
|
||||
let grid = HeightGrid::radial_hill(33, 33, 10.0).unwrap();
|
||||
let scene = fixture_scene();
|
||||
let preview = render_top_down_with_quality(&grid, &scene, RenderQualityPreset::Preview);
|
||||
let final_img = render_top_down_with_quality(&grid, &scene, RenderQualityPreset::Final);
|
||||
|
||||
assert_ne!(preview.as_raw(), final_img.as_raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_to_path_writes_png_file() {
|
||||
let grid = HeightGrid::radial_hill(8, 8, 10.0).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user