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