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
7 changed files with 199 additions and 49 deletions
Showing only changes of commit 28b7386f04 - Show all commits
+5 -3
View File
@@ -13,6 +13,7 @@ This repository currently contains:
- An implementation roadmap under `docs/plans/`. - An implementation roadmap under `docs/plans/`.
- Legal and reference-material hygiene notes under `docs/legal/` and `docs/research/`. - Legal and reference-material hygiene notes under `docs/legal/` and `docs/research/`.
- A clean-room terrain import boundary with project-owned `ovp-text` fixtures, a PNG heightmap script importer, an SRTM/HGT byte importer behind the `hgt` Cargo feature, an ESRI ASCII Grid parser behind the `ascii-grid-import` feature, and a deterministic terrain-generation module in `src/terrain_gen.rs` with `TerrainGenerationSpec` / `DeterministicTerrainGenerator` (see `cargo test terrain_gen` and its determinism/seed note). - A clean-room terrain import boundary with project-owned `ovp-text` fixtures, a PNG heightmap script importer, an SRTM/HGT byte importer behind the `hgt` Cargo feature, an ESRI ASCII Grid parser behind the `ascii-grid-import` feature, and a deterministic terrain-generation module in `src/terrain_gen.rs` with `TerrainGenerationSpec` / `DeterministicTerrainGenerator` (see `cargo test terrain_gen` and its determinism/seed note).
- A project-owned script parser + executor in `src/script.rs` / `src/script_exec.rs`, MakePath-inspired camera path generation in `src/path.rs`, explicit camera heading/pitch/bank plus lens/range and vertical-exaggeration controls in `src/scene.rs`, and an `app` feature shell with working import/script/path/project controls in `src/app_state.rs`, `src/app.rs`, and `src/ui_shell.rs`.
## Development ## Development
@@ -64,9 +65,10 @@ model.
Scene files use the project-owned `.ovp.toml` format. Version 1 stores a Scene files use the project-owned `.ovp.toml` format. Version 1 stores a
top-level `schema = "openvistapro.scene"`, `version = 1`, and a serialized top-level `schema = "openvistapro.scene"`, `version = 1`, and a serialized
`Scene` payload containing camera, light, water, tree-line, snow-line, and haze `Scene` payload containing camera position/target, camera heading-pitch-bank,
settings. The format is intentionally human-readable while the data model is lens/FOV/clip ranges, light, water, tree-line, snow-line, and haze settings.
still evolving. The format is intentionally human-readable while the data model is still
evolving.
## Script language (MVP) ## Script language (MVP)
+11 -11
View File
@@ -3,9 +3,9 @@
This is a normalized reconciliation of the VistaPro manuals, MakePath guide, screenshots, and current OpenVistaPro implementation. This is a normalized reconciliation of the VistaPro manuals, MakePath guide, screenshots, and current OpenVistaPro implementation.
Status counts by normalized feature family: Status counts by normalized feature family:
- Implemented: 7 - Implemented: 11
- Partial: 7 - Partial: 5
- Planned: 6 - Planned: 4
Notes: Notes:
- “Implemented” means the current codebase has a working, tested slice for that family. - “Implemented” means the current codebase has a working, tested slice for that family.
@@ -22,21 +22,21 @@ Notes:
| GeoTIFF terrain import | Modern open terrain source, not a legacy VistaPro format. | Implemented | `src/import/geotiff.rs` behind `import-geotiff`, tests in that module. | Deliberately narrow subset: tiny synthetic single-band raster support only. | | GeoTIFF terrain import | Modern open terrain source, not a legacy VistaPro format. | Implemented | `src/import/geotiff.rs` behind `import-geotiff`, tests in that module. | Deliberately narrow subset: tiny synthetic single-band raster support only. |
| Fractal / synthetic terrain generation | VistaPro overview calls out fractal landscapes and generated terrain. | Partial | `src/terrain.rs` (`plane`, `radial_hill`), `src/app_state.rs` presets. | Current terrain generation is only deterministic fixtures, not a true fractal/noise terrain engine. | | Fractal / synthetic terrain generation | VistaPro overview calls out fractal landscapes and generated terrain. | Partial | `src/terrain.rs` (`plane`, `radial_hill`), `src/app_state.rs` presets. | Current terrain generation is only deterministic fixtures, not a true fractal/noise terrain engine. |
| Camera and target placement | VistaPro 2 / 3 manuals: “Setting Camera and Target”; screenshot workflow uses camera/target gadgets. | Implemented | `src/scene.rs` (`Camera`), `src/app.rs` (camera position/target controls), `src/app_state.rs`. | Only the core position/target slice exists; there is no map-click placement UI yet. | | Camera and target placement | VistaPro 2 / 3 manuals: “Setting Camera and Target”; screenshot workflow uses camera/target gadgets. | Implemented | `src/scene.rs` (`Camera`), `src/app.rs` (camera position/target controls), `src/app_state.rs`. | Only the core position/target slice exists; there is no map-click placement UI yet. |
| Lens / range / orientation controls | VistaPro manuals describe lens/range, bank, heading, and pitch controls. | Partial | `src/scene.rs` (`Camera.fov_degrees`), `src/render.rs` perspective renderer. | No explicit bank/heading/pitch model or legacy lens/range UI yet. | | Lens / range / orientation controls | VistaPro manuals describe lens/range, bank, heading, and pitch controls. | Implemented | `src/scene.rs` (`Camera.orientation`, `fov_degrees`, `near_range`, `far_range`), `src/render.rs` perspective renderer, `src/app.rs` explicit heading/pitch/bank and lens/range controls. | The modern shell keeps the camera model explicit while staying intentionally simpler than the legacy lens matrix and stereo workflows. |
| Water / sea level, tree line, snow line, haze | Manuals repeatedly mention tree line, snow line, water level, haze, and atmospheric tuning. | Implemented | `src/scene.rs`, `src/app.rs` sliders, `src/colormap.rs`, `src/render.rs`. | Rivers/lakes are still missing, but the core elevation-band controls are present. | | Water / sea level, tree line, snow line, haze | Manuals repeatedly mention tree line, snow line, water level, haze, and atmospheric tuning. | Implemented | `src/scene.rs`, `src/app.rs` sliders, `src/colormap.rs`, `src/render.rs`. | Rivers/lakes are still missing, but the core elevation-band controls are present. |
| Rivers and lakes | VistaPro manuals explicitly mention rivers and lakes as adjustable landscape features. | Planned | Not yet represented in `Scene` or renderer code. | Add hydrology controls/data model before claiming this family. | | Rivers and lakes | VistaPro manuals explicitly mention rivers and lakes as adjustable landscape features. | Planned | Not yet represented in `Scene` or renderer code. | Add hydrology controls/data model before claiming this family. |
| Light direction and custom lighting | Manuals discuss sunlight placement and lighting experiments. | Partial | `src/scene.rs` (`Light`), `src/render.rs`, `src/app.rs` (light state exists in the scene model even if UI is minimal). | The current model is much simpler than VistaPros lighting workflow and lacks richer light controls. | | Light direction and custom lighting | Manuals discuss sunlight placement and lighting experiments. | Partial | `src/scene.rs` (`Light`), `src/render.rs`, `src/app.rs` (light state exists in the scene model even if UI is minimal). | The current model is much simpler than VistaPros lighting workflow and lacks richer light controls. |
| Vertical exaggeration | VistaPro manuals describe vertical scaling / scene exaggeration controls. | Planned | No dedicated field or control in the current scene model. | Add an explicit vertical-scale parameter and render integration. | | 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. | Partial | `src/colormap.rs` fixed bands, `src/render.rs` uses scene thresholds. | No color-map editor, no palette import/export, and no PCX/texture loading yet. | | Color maps / palettes / texture image loading | VistaPro 3 manual includes loading PCX images, adding texture, and saving/loading color maps. | Partial | `src/colormap.rs` fixed bands, `src/render.rs` uses scene thresholds. | No color-map editor, no palette import/export, and no PCX/texture loading yet. |
| 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. | | 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. | Planned | No dedicated quality preset system in current code. | Add explicit quality presets or a render-quality profile object. |
| 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. | | 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. | Partial | `src/script.rs` parser, tests in `src/script.rs`, `README.md` script section. | Parser exists, but script execution is intentionally deferred. | | 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. | Planned | No script runner or frame-sequencing engine exists yet. | Add execution semantics once the command model is stable. | | 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. |
| MakePath-style path generation and motion models | MakePath guide describes spline nodes, previewing a path, and vehicle models (jet, glider, dune buggy, motorcycle, helicopter, cruise missile, custom). | Planned | No path generator or motion-model layer exists yet. | This is a separate planner/animation feature, not just a script parser. | | MakePath-style path generation and motion models | MakePath guide describes spline nodes, previewing a path, and vehicle models (jet, glider, dune buggy, motorcycle, helicopter, cruise missile, custom). | Implemented | `src/path.rs`, `src/app_state.rs` (`make_path`), `src/app.rs` path controls, tests in `src/path.rs` and `src/app_state.rs`. | Core camera-path generation is now present; editable nodes, vehicle-specific motion models, and script export remain future expansion points. |
| UI shell, menus, dialogs, and numeric gadgets | VistaPro screenshots/manuals show dense menus, dialogs, map tools, and numeric gadgets. | Partial | `src/app.rs`, `src/app_state.rs`, `src/ui_shell.rs`, `src/bin/openvistapro_app.rs`. | OpenVistaPro now has a durable docked egui shell with stable navigation, sidebar, viewport, inspector, and status chrome; menus/dialogs/numeric gadgets still remain to be filled in. | | UI shell, menus, dialogs, and numeric gadgets | VistaPro screenshots/manuals show dense menus, dialogs, map tools, and numeric gadgets. | Partial | `src/app.rs`, `src/app_state.rs`, `src/ui_shell.rs`, `src/bin/openvistapro_app.rs`, plus the `app` feature shell tests. | OpenVistaPro now has a durable docked egui shell with stable navigation, working import/script/path/project actions, sidebar, viewport, inspector, and status chrome; legacy-style menus/dialogs and more specialized gadgets still remain to be filled in. |
| Legacy image / landscape export formats | VistaPro manuals mention saving rendered images and landscapes in formats like IFF/IFF24/RGB and DEM/binary landscape files. | Planned | Current output is PNG plus project-owned `.ovp.toml` scenes. | Add separate compatibility/export work only after the clean internal pipeline is stable. | | Legacy image / landscape export formats | VistaPro manuals mention saving rendered images and landscapes in formats like IFF/IFF24/RGB and DEM/binary landscape files. | Planned | Current output is PNG plus project-owned `.ovp.toml` scenes. | Separate compatibility/export work remains future scope after the clean internal pipeline is stable. |
## Current reconciliation summary ## Current reconciliation summary
OpenVistaPro already covers the core clean-room pipeline: terrain grids, open importers, scene state, preview/final rendering, project-owned scene files, and a small script parser. The remaining VistaPro-specific gaps cluster around legacy compatibility, richer scene controls, script execution, MakePath-style animation tooling, and the old dense UI/menu workflow. 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, 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.
+16 -16
View File
@@ -6,14 +6,14 @@ This is a normalized modern shell map derived from the VistaPro manuals, screens
| Modern panel | VistaPro surfaces it absorbs | Suggested placement | Current code support | Notes / gaps | | Modern panel | VistaPro surfaces it absorbs | Suggested placement | Current code support | Notes / gaps |
|---|---|---|---|---| |---|---|---|---|---|
| Viewport / preview | Main render window, map preview, perspective view, preview/final render output | Center dock | Partial | `src/app.rs` already renders the CPU preview into `CentralPanel`; perspective and top-down preview modes exist, but there is no GPU viewport or direct manipulation overlay yet. | | 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 placeholder import entry point; legacy format import UI is still absent. | | 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, orientation, lens/range, vertical exaggeration, palette, and hydrology controls now live in `src/app.rs` and `src/app_state.rs`; `src/scene.rs` and `src/render.rs` carry the model/render semantics. The shell now covers the main VistaPro scene controls, but its camera semantics are intentionally simplified and not yet tied to any map-click placement workflow. | | 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, palette, and hydrology controls now live in `src/app.rs` and `src/app_state.rs`; `src/scene.rs` and `src/render.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 toggles top-down vs perspective render mode, but there is no dedicated quality profile or render preset UI. |
| Scripts / paths | Script menu, Run Script, MakePath path tools, animation-frame workflows | Right dock or modal workflow | Partial | Script parsing exists in the codebase, the shell now surfaces a script editor and placeholder path controls, but execution and path generation are still deferred. | | 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 disabled file actions; load/save execution is still wired only in the backend modules. | | 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 | Partial | The shell now has a bottom status bar driven by `AppData::ui_snapshot()`. | | 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()`. |
| Deferred features / legacy compatibility | Dense menu hierarchy, advanced lighting, hydrology, vertical exaggeration, palette editing, legacy image/landscape exports | Right dock, tool drawer, or dialogs | Planned | These are best surfaced as future tabs or dialogs rather than cluttering the initial shell. | | Legacy compatibility / advanced dialogs | Dense menu hierarchy, advanced lighting, hydrology, vertical exaggeration, palette editing, legacy image/landscape exports | Right dock, tool drawer, or dialogs | Partial | These are best surfaced as future tabs or dialogs rather than cluttering the initial shell; some sub-features already exist in the backend, but the legacy-style workflow itself is still incomplete. |
## Recommended shell structure ## Recommended shell structure
@@ -32,19 +32,19 @@ That layout preserves the VistaPro workflow while making room for modern discove
| Panel | Code support today | Implementation evidence | Priority | | Panel | Code support today | Implementation evidence | Priority |
|---|---|---|---| |---|---|---|---|
| Viewport / preview | Present | `src/app.rs` renders the preview into `CentralPanel`; `src/app_state.rs` builds the preview image. | High | | Viewport / preview | Present | `src/app.rs` renders the preview into `CentralPanel`; `src/app_state.rs` builds the preview image. | High |
| Terrain / import | Partial | `TerrainPreset` and `AppAction::SetTerrainPreset` in `src/app_state.rs`; terrain radio buttons in `src/app.rs`. | High | | Terrain / import | Partial | `TerrainPreset` and `AppAction::SetTerrainPreset` in `src/app_state.rs`; terrain radio buttons and import action in `src/app.rs`. | High |
| Scene / camera | Partial | `Scene`, camera position/target controls, and scene-band sliders in `src/app.rs` and `src/app_state.rs`. | High | | Scene / camera | Partial | `Scene`, camera position/target controls, explicit heading/pitch/bank controls, and lens/range sliders in `src/app.rs` and `src/app_state.rs`. | High |
| Render | Partial | `RendererMode` plus preview switching in `src/app_state.rs` and `src/app.rs`. | High | | Render | Partial | `RendererMode` plus preview switching in `src/app_state.rs` and `src/app.rs`. | High |
| Scripts / paths | Not yet | `docs/knowledgebase/feature-inventory.md` marks scripts and path generation as planned. | Medium | | Scripts / paths | Partial | `src/script.rs`, `src/script_exec.rs`, `src/path.rs`, and the script/path controls in `src/app.rs`. | High |
| File / project actions | Not yet | `loaded_scene_path` exists in `AppData`, but there is no visible file/project UI yet. | Medium | | File / project actions | Partial | `loaded_scene_path` plus the working New/Open/Save controls in `src/app.rs` and `src/scene_file.rs`. | Medium |
| Status / feedback | Not yet | No dedicated status widget or state binding is present. | Medium | | Status / feedback | Present | Bottom status bar driven by `AppData::ui_snapshot()` in `src/app.rs`. | Medium |
| Deferred features / legacy compatibility | Not yet | These remain future work in the feature inventory. | Low | | Legacy compatibility / advanced dialogs | Partial | Backend support exists for some sub-features like vertical exaggeration, palette, and hydrology, but the legacy-style menu/dialog workflow is still incomplete. | Low |
## Remaining gaps ## Remaining gaps
- No legacy-style menu/dialog layer for file, export, or script workflows. - No legacy-style menu/dialog layer for file, export, or advanced workflows.
- No docked status bar or live feedback line. - The docked status bar exists now, but richer progress and coordinate feedback are still open.
- No dedicated scripts/paths editor surface. - No dedicated scripts/paths editor surface beyond the current MVP controls.
- Palette import/export and legacy texture loading remain open. - Palette import/export and legacy texture loading remain open.
- Hydrology still lacks routed-water simulation and drainage maps. - Hydrology still lacks routed-water simulation and drainage maps.
- Legacy export and compatibility dialogs remain future work. - Legacy export and compatibility dialogs remain future work.
+2 -12
View File
@@ -721,20 +721,10 @@ Expected: generated script parses successfully.
## Milestone G: WGPU/egui application after CLI stability ## Milestone G: WGPU/egui application after CLI stability
**Status:** Tasks G1G4 have landed. `src/app_state.rs` holds testable app state and **Status:** Tasks G1G4 have landed. `src/app_state.rs` holds testable app state and `AppAction` reducers; `src/app.rs` and `src/bin/openvistapro_app.rs` provide the `app`-feature `eframe`/`egui` shell. The shell now docks terrain, scene/camera, render, import, script, path, and project controls around the CPU top-down/perspective preview, with a bottom status bar and backend-backed import/script/path actions.
`AppAction` reducers; `src/app.rs` and `src/bin/openvistapro_app.rs` provide the
`app`-feature `eframe`/`egui` shell. The shell now docks terrain, scene/camera, and render
controls in a left panel, a scripts/paths panel on the right, a top project bar and a
bottom status bar for file/status chrome, and the CPU top-down/perspective preview in the
center. Still-planned actions — heightmap import, run script, make path, and file
new/open/save — are present as disabled placeholders so the layout is honest about scope.
The shell map is tracked in [`docs/knowledgebase/ui-panel-map.md`](../knowledgebase/ui-panel-map.md). The shell map is tracked in [`docs/knowledgebase/ui-panel-map.md`](../knowledgebase/ui-panel-map.md).
Remaining UI roadmap: Task G5 (WGPU renderer backend) plus the still-open gaps — wiring Remaining UI roadmap: Task G5 (WGPU renderer backend) plus the still-open gaps — legacy menus/dialogs, richer file/project chrome, animation-frame export, deeper script/path editors, a palette editor, and hydrology controls. All of it stays clean-room: no proprietary VistaPro assets, menus, or screenshots enter the repository.
file/import/script/path actions to their backend modules, legacy menus/dialogs,
orientation and lens/range controls, vertical exaggeration, a palette editor, and
hydrology controls. All of it stays clean-room: no proprietary VistaPro assets, menus, or
screenshots enter the repository.
### Task G1: Create app-state crate/module without a window ### Task G1: Create app-state crate/module without a window
+60 -6
View File
@@ -165,24 +165,29 @@ impl OpenVistaProApp {
self.data.apply(AppAction::SetCameraTarget(camera_target)); self.data.apply(AppAction::SetCameraTarget(camera_target));
ui.separator(); ui.separator();
ui.label("Camera orientation and lens"); ui.label("Camera orientation and lens/range");
let mut orientation = self.data.scene.camera.orientation; let mut orientation = self.data.scene.camera.orientation;
changed |= vec3_controls(ui, "Orientation", &mut orientation); changed |= orientation_controls(ui, &mut orientation);
self.data self.data
.apply(AppAction::SetCameraOrientation(orientation)); .apply(AppAction::SetCameraOrientation(orientation));
ui.small(
"Heading turns the camera around world up, pitch tilts it, and bank rolls the horizon.",
);
let mut fov_degrees = self.data.scene.camera.fov_degrees; let mut fov_degrees = self.data.scene.camera.fov_degrees;
let mut near_range = self.data.scene.camera.near_range; let mut near_range = self.data.scene.camera.near_range;
let mut far_range = self.data.scene.camera.far_range; let mut far_range = self.data.scene.camera.far_range;
changed |= ui changed |= ui
.add(egui::Slider::new(&mut fov_degrees, 10.0..=170.0).text("FOV")) .add(egui::Slider::new(&mut fov_degrees, 10.0..=170.0).text("Lens / FOV (°)"))
.changed(); .changed();
changed |= ui changed |= ui
.add(egui::Slider::new(&mut near_range, 0.1..=50.0).text("Near")) .add(egui::Slider::new(&mut near_range, 0.1..=50.0).text("Near clip"))
.changed(); .changed();
changed |= ui changed |= ui
.add(egui::Slider::new(&mut far_range, 1.0..=1000.0).text("Far")) .add(egui::Slider::new(&mut far_range, 1.0..=1000.0).text("Far clip"))
.changed(); .changed();
ui.small("FOV controls lens width; near/far clip distances are in world units.");
self.data.apply(AppAction::SetCameraLens { self.data.apply(AppAction::SetCameraLens {
fov_degrees, fov_degrees,
near_range, near_range,
@@ -219,8 +224,32 @@ impl OpenVistaProApp {
}); });
ui.separator(); ui.separator();
ui.label("Palette"); ui.label("Color map");
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..=palette.tree_line - 0.1)
.text("Water level"),
)
.changed();
changed |= ui
.add(
egui::Slider::new(
&mut palette.tree_line,
palette.water_level + 0.1..=palette.snow_line - 0.1,
)
.text("Tree line"),
)
.changed();
changed |= ui
.add(
egui::Slider::new(&mut palette.snow_line, palette.tree_line + 0.1..=15.0)
.text("Snow line"),
)
.changed();
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();
@@ -392,6 +421,8 @@ impl OpenVistaProApp {
let (width, height) = self.data.preview_size; let (width, height) = self.data.preview_size;
ui.label(format!("Preview: {width} x {height}")); ui.label(format!("Preview: {width} x {height}"));
ui.label(format!("Water level: {:.2}", self.data.scene.water_level)); ui.label(format!("Water level: {:.2}", self.data.scene.water_level));
ui.label(format!("Tree line: {:.2}", self.data.scene.tree_line));
ui.label(format!("Snow line: {:.2}", self.data.scene.snow_line));
}); });
} }
@@ -476,6 +507,29 @@ fn vec3_controls(ui: &mut egui::Ui, label: &str, value: &mut Vec3) -> bool {
changed changed
} }
fn orientation_controls(ui: &mut egui::Ui, value: &mut Vec3) -> bool {
let mut changed = false;
ui.horizontal(|ui| {
ui.label("Heading / yaw");
changed |= ui
.add(egui::DragValue::new(&mut value.x).speed(0.25).suffix("°"))
.changed();
});
ui.horizontal(|ui| {
ui.label("Pitch");
changed |= ui
.add(egui::DragValue::new(&mut value.y).speed(0.25).suffix("°"))
.changed();
});
ui.horizontal(|ui| {
ui.label("Bank / roll");
changed |= ui
.add(egui::DragValue::new(&mut value.z).speed(0.25).suffix("°"))
.changed();
});
changed
}
fn rgb_image_to_color_image(image: &RgbImage) -> egui::ColorImage { fn rgb_image_to_color_image(image: &RgbImage) -> egui::ColorImage {
let size = [image.width() as usize, image.height() as usize]; let size = [image.width() as usize, image.height() as usize];
egui::ColorImage::from_rgb(size, image.as_raw()) egui::ColorImage::from_rgb(size, image.as_raw())
+85
View File
@@ -1,11 +1,60 @@
use std::path::Path; use std::path::Path;
use clap::ValueEnum;
use image::{ImageError, Rgb, RgbImage}; use image::{ImageError, Rgb, RgbImage};
use crate::colormap::scene_color; use crate::colormap::scene_color;
use crate::scene::{Camera, Scene, Vec3}; use crate::scene::{Camera, Scene, Vec3};
use crate::terrain::HeightGrid; use crate::terrain::HeightGrid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum RenderQualityPreset {
Preview,
Balanced,
Final,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RenderQualityProfile {
pub label: &'static str,
pub output_tag: &'static str,
pub perspective_step: f32,
pub top_down_blur_passes: u8,
}
impl RenderQualityPreset {
pub fn profile(self) -> RenderQualityProfile {
match self {
RenderQualityPreset::Preview => RenderQualityProfile {
label: "Preview",
output_tag: "preview",
perspective_step: 0.70,
top_down_blur_passes: 0,
},
RenderQualityPreset::Balanced => RenderQualityProfile {
label: "Balanced",
output_tag: "balanced",
perspective_step: 0.45,
top_down_blur_passes: 1,
},
RenderQualityPreset::Final => RenderQualityProfile {
label: "Final",
output_tag: "final",
perspective_step: 0.25,
top_down_blur_passes: 2,
},
}
}
pub fn label(self) -> &'static str {
self.profile().label
}
pub fn output_tag(self) -> &'static str {
self.profile().output_tag
}
}
pub fn render_top_down(grid: &HeightGrid, scene: &Scene) -> RgbImage { pub fn render_top_down(grid: &HeightGrid, scene: &Scene) -> RgbImage {
let w = grid.width(); let w = grid.width();
let h = grid.height(); let h = grid.height();
@@ -311,6 +360,23 @@ mod tests {
assert_eq!(img.get_pixel(2, 2).0, SNOW_COLOR); assert_eq!(img.get_pixel(2, 2).0, SNOW_COLOR);
} }
#[test]
fn render_top_down_applies_vertical_exaggeration() {
let grid = HeightGrid::new(1, 1, vec![3.0]).unwrap();
let low_scene = fixture_scene();
let high_scene = Scene {
vertical_exaggeration: 2.0,
..fixture_scene()
};
let low_img = render_top_down(&grid, &low_scene);
let high_img = render_top_down(&grid, &high_scene);
assert_eq!(low_img.get_pixel(0, 0).0, LOWLAND_COLOR);
assert_eq!(high_img.get_pixel(0, 0).0, HIGHLAND_COLOR);
assert_ne!(low_img.as_raw(), high_img.as_raw());
}
#[test] #[test]
fn render_is_deterministic() { fn render_is_deterministic() {
let grid = HeightGrid::radial_hill(16, 16, 10.0).unwrap(); let grid = HeightGrid::radial_hill(16, 16, 10.0).unwrap();
@@ -428,6 +494,25 @@ mod tests {
let _img = render_perspective(&plane, &demo_scene(&plane), 16, 16); let _img = render_perspective(&plane, &demo_scene(&plane), 16, 16);
} }
#[test]
fn render_perspective_applies_vertical_exaggeration() {
let grid = HeightGrid::radial_hill(32, 32, 10.0).unwrap();
let low_scene = demo_scene(&grid);
let high_scene = Scene {
vertical_exaggeration: 2.0,
..demo_scene(&grid)
};
let low_img = render_perspective(&grid, &low_scene, 32, 32);
let high_img = render_perspective(&grid, &high_scene, 32, 32);
assert_ne!(
low_img.as_raw(),
high_img.as_raw(),
"vertical exaggeration should alter perspective output"
);
}
#[test] #[test]
fn render_perspective_to_path_writes_png_file() { fn render_perspective_to_path_writes_png_file() {
let grid = HeightGrid::radial_hill(16, 16, 10.0).unwrap(); let grid = HeightGrid::radial_hill(16, 16, 10.0).unwrap();
+20 -1
View File
@@ -24,10 +24,13 @@ impl Vec3 {
pub struct Camera { pub struct Camera {
pub position: Vec3, pub position: Vec3,
pub target: Vec3, pub target: Vec3,
/// Heading, pitch, and bank in degrees. /// Camera heading (yaw), pitch, and bank/roll in degrees.
pub orientation: Vec3, pub orientation: Vec3,
/// Vertical field of view in degrees used by the pinhole camera.
pub fov_degrees: f32, pub fov_degrees: f32,
/// Near clipping distance in world units from the camera.
pub near_range: f32, pub near_range: f32,
/// Far clipping distance in world units from the camera.
pub far_range: f32, pub far_range: f32,
} }
@@ -63,6 +66,9 @@ impl Default for Light {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Palette { pub struct Palette {
pub water_level: f32,
pub tree_line: f32,
pub snow_line: f32,
pub water: [u8; 3], pub water: [u8; 3],
pub lowland: [u8; 3], pub lowland: [u8; 3],
pub highland: [u8; 3], pub highland: [u8; 3],
@@ -72,6 +78,9 @@ pub struct Palette {
impl Default for Palette { impl Default for Palette {
fn default() -> Self { fn default() -> Self {
Self { Self {
water_level: 1.0,
tree_line: 4.0,
snow_line: 7.0,
water: [30, 70, 130], water: [30, 70, 130],
lowland: [70, 130, 50], lowland: [70, 130, 50],
highland: [120, 100, 80], highland: [120, 100, 80],
@@ -80,6 +89,14 @@ impl Default for Palette {
} }
} }
impl Palette {
pub fn sync_thresholds_from_scene(&mut self, water_level: f32, tree_line: f32, snow_line: f32) {
self.water_level = water_level;
self.tree_line = tree_line;
self.snow_line = snow_line;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Hydrology { pub struct Hydrology {
@@ -174,6 +191,8 @@ mod tests {
let s = Scene::default(); let s = Scene::default();
assert!(s.water_level < s.tree_line); assert!(s.water_level < s.tree_line);
assert!(s.tree_line < s.snow_line); assert!(s.tree_line < s.snow_line);
assert!(s.palette.water_level < s.palette.tree_line);
assert!(s.palette.tree_line < s.palette.snow_line);
} }
#[test] #[test]