From db49039b9da32b39c79067ec62622904942817ad Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 31 Jul 2026 19:15:17 +0200 Subject: [PATCH] Put the editor viewport into a top-down orthographic plan view Opening the Plan tool now switches the whole colour pass to an orthographic projection and pins the camera looking straight down, so the plan is drawn against the loaded scenery rather than in a window of its own. Closing the tool hands the camera's orientation back; leaving the editor clears the view outright. The wheel changes how much ground fits on screen instead of moving the camera, which with no perspective would change nothing but the clipping. Both configured backends follow: the experimental renderer swaps the pass projection (and fits its shadow cascades to it), the modern GL renderer uses its existing ortho helpers. Everything reading the pass projection - the mouse ray, the frustum test - picks the change up on its own. The experimental renderer also publishes its colour pass camera now, which it previously left at the interface defaults, so overlays can place world points on screen. --- application/editormode.cpp | 32 +++++++++++++++++ application/editormode.h | 9 ++--- application/planpanel.cpp | 11 ++++++ application/planpanel.h | 1 + .../renderer/include/nvrenderer/nvrenderer.h | 16 +++++++++ betterRenderer/renderer/source/nvrenderer.cpp | 35 +++++++++++++++---- rendering/opengl33renderer.cpp | 14 +++++++- utilities/Globals.h | 3 ++ 8 files changed, 109 insertions(+), 12 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 8358ce11..31b78e82 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1307,10 +1307,41 @@ void editor_mode::render_gizmo() } } +void editor_mode::on_scroll(double const Xoffset, double const Yoffset) +{ + if (false == Global.editor_ortho) + { + return; + } + // in the plan view the wheel decides how much ground fits on screen; the camera itself stays put, + // because with no perspective its height changes nothing but the clipping + Global.editor_ortho_extent = std::clamp(Global.editor_ortho_extent * std::pow(0.85f, static_cast(Yoffset)), 5.0f, 20000.0f); +} + void editor_mode::update_camera(double const Deltatime) { Camera.Update(); + // the plan view looks straight down. the camera keeps its ground position, so panning works just + // as it does in the perspective view, but the orientation is pinned and handed back on the way out + if (Global.editor_ortho) + { + if (false == m_orthoactive) + { + m_orthoangle = Camera.Angle; + m_orthoactive = true; + } + Camera.Angle.x = glm::radians(-90.0f); + Camera.Angle.z = 0.0f; + // high enough to clear anything the scenery may have under the cursor + Camera.Pos.y = std::max(Camera.Pos.y, 500.0); + } + else if (m_orthoactive) + { + Camera.Angle = m_orthoangle; + m_orthoactive = false; + } + // focus animation runs after Camera.Update() so it overrides any residual velocity/rotation; // it smoothly drives both position and orientation toward the framed object if (m_focus_active) @@ -1391,6 +1422,7 @@ void editor_mode::enter() void editor_mode::exit() { EditorModeFlag = false; + Global.editor_ortho = false; // the plan view must not follow us out of the editor Global.ControlPicking = m_statebackup.picking; FreeFlyModeFlag = m_statebackup.freefly; Global.pCamera = m_statebackup.camera; diff --git a/application/editormode.h b/application/editormode.h index 29b1ad69..0aad5fb4 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -39,10 +39,7 @@ class editor_mode : public application_mode void on_key(int Key, int Scancode, int Action, int Mods) override; void on_cursor_pos(double Horizontal, double Vertical) override; void on_mouse_button(int Button, int Action, int Mods) override; - void on_scroll(double const Xoffset, double const Yoffset) override - { - ; - } + void on_scroll(double const Xoffset, double const Yoffset) override; void on_window_resize(int w, int h) override { ; @@ -108,6 +105,10 @@ class editor_mode : public application_mode editormode_input m_input; static TCamera Camera; + // camera orientation from before the plan view took over, to be handed back when it is left + bool m_orthoactive{false}; + glm::vec3 m_orthoangle{0.0f}; + // focus (smooth camera fly-to) state static bool m_focus_active; glm::dvec3 m_focus_start_pos{0.0,0.0,0.0}; diff --git a/application/planpanel.cpp b/application/planpanel.cpp index bb104c1f..87e31124 100644 --- a/application/planpanel.cpp +++ b/application/planpanel.cpp @@ -83,8 +83,19 @@ plan_panel::plan_panel(std::string const &Name, bool const Isopen) : ui_panel(Na solve(); } +void plan_panel::update() +{ + // the plan belongs on the scenery itself, so opening the tool puts the viewport into the + // distortion-free view from above and closing it hands the camera back + Global.editor_ortho = is_open; +} + void plan_panel::render_contents() { + ImGui::Text("view: top-down, %.0f m across", Global.editor_ortho_extent * 2.0f); + ImGui::SameLine(); + ImGui::TextDisabled("(wheel over the scenery zooms it)"); + render_toolbar(); render_canvas(); render_gaps(); diff --git a/application/planpanel.h b/application/planpanel.h index 4a40f63c..61d751bf 100644 --- a/application/planpanel.h +++ b/application/planpanel.h @@ -24,6 +24,7 @@ class plan_panel : public ui_panel public: plan_panel(std::string const &Name, bool const Isopen); + void update() override; void render_contents() override; private: diff --git a/betterRenderer/renderer/include/nvrenderer/nvrenderer.h b/betterRenderer/renderer/include/nvrenderer/nvrenderer.h index a572c6dc..26a99fca 100644 --- a/betterRenderer/renderer/include/nvrenderer/nvrenderer.h +++ b/betterRenderer/renderer/include/nvrenderer/nvrenderer.h @@ -129,6 +129,16 @@ class NvRenderer : public gfx_renderer, public MaResourceRegistry { virtual TSubModel const *Pick_Control() const override; virtual scene::basic_node const *Pick_Node() const override; virtual glm::dvec3 Mouse_Position() const override; + // matrices of the most recent colour pass, for overlays drawn on top of the rendered world + virtual glm::mat4 Camera_View_Matrix() const override { + return static_cast(m_camera_view); + } + virtual glm::mat4 Camera_Projection_Matrix() const override { + return static_cast(m_camera_projection); + } + virtual glm::dvec3 Camera_Position() const override { + return m_camera_position; + } virtual void Update(double const Deltatime) override; virtual void Update_Pick_Control() override; virtual void Update_Pick_Node() override; @@ -222,6 +232,12 @@ class NvRenderer : public gfx_renderer, public MaResourceRegistry { glm::dvec3 m_mouse_ro; glm::dvec3 m_mouse_rd; + + // colour pass camera, kept so overlays can place world points on screen. the view matrix is + // camera-relative (rotation only), matching the camera-relative rendering used throughout + glm::dmat4 m_camera_view{1.}; + glm::dmat4 m_camera_projection{1.}; + glm::dvec3 m_camera_position{0.}; TSubModel const *m_picked_submodel = nullptr; using section_sequence = std::vector; diff --git a/betterRenderer/renderer/source/nvrenderer.cpp b/betterRenderer/renderer/source/nvrenderer.cpp index 926666d5..d4f04c82 100644 --- a/betterRenderer/renderer/source/nvrenderer.cpp +++ b/betterRenderer/renderer/source/nvrenderer.cpp @@ -352,9 +352,26 @@ bool NvRenderer::Render() { double fov = glm::radians(static_cast(Global.FieldOfView) / Global.ZoomFactor); - glm::dmat4 projection = glm::perspectiveFovRH_ZO( - fov, static_cast(Global.window_size.x), - static_cast(Global.window_size.y), pass.m_draw_range, .1); + + // the track plan is drawn on a distortion-free view from above, so the whole pass switches to + // an orthographic projection. everything downstream - the mouse ray below, the frustum test, + // the shadow fitting - reads this matrix, so nothing else needs to know which one it is + double const orthoheight = Global.editor_ortho_extent; + double const orthowidth = + orthoheight * static_cast(Global.window_size.x) / + std::max(1., static_cast(Global.window_size.y)); + glm::dmat4 projection = + Global.editor_ortho + ? glm::orthoRH_ZO(-orthowidth, orthowidth, -orthoheight, + orthoheight, pass.m_draw_range, .1) + : glm::perspectiveFovRH_ZO( + fov, static_cast(Global.window_size.x), + static_cast(Global.window_size.y), + pass.m_draw_range, .1); + + m_camera_view = transform; + m_camera_projection = projection; + m_camera_position = pass.m_origin; SingleFrustumTester frustum_tester{projection, transform}; DrawConstants draw_constants{}; @@ -471,11 +488,15 @@ bool NvRenderer::Render() { glm::vec3 light_color, light_direction; m_sky->CalcLighting(light_direction, light_color); + // cascades are fitted to what the camera actually sees, so they follow the plan view too m_shadow_map->CalcCascades( - glm::perspectiveFovRH_ZO(fov, - static_cast(Global.window_size.x), - static_cast(Global.window_size.y), - .1, pass.m_draw_range), + Global.editor_ortho + ? glm::orthoRH_ZO(-orthowidth, orthowidth, -orthoheight, + orthoheight, .1, pass.m_draw_range) + : glm::perspectiveFovRH_ZO( + fov, static_cast(Global.window_size.x), + static_cast(Global.window_size.y), .1, + pass.m_draw_range), transform, -light_direction); m_shadow_map->UpdateConstants(command_list); m_gbuffer_shadow->Clear(command_list); diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 576cf404..426925df 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -1591,7 +1591,19 @@ void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config auto const zfar = ( Zfar > 1.f ? Zfar : Config.draw_range * Zfar ); auto const znear = ( Znear > 1.f ? Znear : Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor); - camera.projection() = perspective_projection(Viewport.projection, znear, zfar, frustumtest_proj); + // the editor's top-down plan view drops the perspective, so distances read the same wherever + // they sit on screen; the off-axis virtual screen above has no meaning then + if (Global.editor_ortho && Viewport.main && Viewport.proj_type == viewport_config::normal) + { + auto const halfheight = Global.editor_ortho_extent; + auto const halfwidth = halfheight * std::max(1.f, (float)Global.window_size.x) / std::max(1.f, (float)Global.window_size.y); + camera.projection() = ortho_projection(-halfwidth, halfwidth, -halfheight, halfheight, znear, zfar); + frustumtest_proj = ortho_frustumtest_projection(-halfwidth, halfwidth, -halfheight, halfheight, znear, zfar); + } + else + { + camera.projection() = perspective_projection(Viewport.projection, znear, zfar, frustumtest_proj); + } break; } case rendermode::shadows: diff --git a/utilities/Globals.h b/utilities/Globals.h index 0ee9cce5..4ad3c0eb 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -79,6 +79,9 @@ struct global_settings { std::string SceneryFile; // uruchomienie prosto do edytora, z pominięciem symulacji; edytor jest wtedy trybem podstawowym bool editor_startup{ false }; + // rzut z góry bez perspektywy, do kreślenia planu torowiska na tle wczytanej scenerii + bool editor_ortho{ false }; + float editor_ortho_extent{ 250.f }; // połowa wysokości widoku w metrach, czyli zoom rzutu std::string local_start_vehicle{ "EU07-424" }; int iConvertModels{ 0 }; // tworzenie plików binarnych int iConvertIndexRange{ 1000 }; // range of duplicate vertex scan