From e910615bf6ab42ac5002700732b4dfd044f60347 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 9 Oct 2026 15:37:23 +0200 Subject: [PATCH] removed draw limit in track editor --- application/editormode.cpp | 7 +++- application/editormodetrack.cpp | 23 ++++++----- application/editormodetrackux.cpp | 63 ++++++++++++------------------- rendering/opengl33renderer.cpp | 56 +++++++++++++++++++++++---- rendering/openglrenderer.cpp | 44 +++++++++++++++++++-- utilities/Globals.h | 1 + 6 files changed, 131 insertions(+), 63 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index a3373e73..cdd95327 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -41,6 +41,7 @@ extern char **environ; #include "rendering/renderer.h" #include "model/AnimModel.h" #include "model/Model3d.h" +#include "world/Track.h" #include "utilities/Float3d.h" #include "scene/scene.h" #include "scene/scenelayers.h" @@ -1192,6 +1193,7 @@ void editor_mode::redo_one() bool editor_mode::update() { + Global.editor_tracks = (current_work_area() == work_area::tracks); selftest_step(); if (m_vehicle.leave) { @@ -4544,6 +4546,7 @@ void editor_mode::enter() void editor_mode::exit() { + Global.editor_tracks = false; EditorModeFlag = false; Global.ControlPicking = m_statebackup.picking; FreeFlyModeFlag = m_statebackup.freefly; @@ -5012,8 +5015,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const } } - // ignore picks that are beyond allowed placement distance - if (node) { + // models stay within placement distance; tracks in the track workspace have no range cap + if (node && false == (Global.editor_tracks && dynamic_cast(node) != nullptr)) { double const dist = glm::distance(node->location(), glm::dvec3{Global.pCamera.Pos}); if (dist > static_cast(kMaxPlacementDistance)) { diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 61c130d3..0b264b78 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -222,10 +222,6 @@ void editor_mode::select_track(scene::basic_node *Node) m_track_point = {}; return; } - glm::dvec3 const camera{Global.pCamera.Pos}; - if (glm::distance(glm::dvec3(track->get_nearest_point(camera)), camera) > static_cast(kMaxPlacementDistance)) - return; - if (track != m_node) { m_track_point = {}; @@ -3795,14 +3791,21 @@ void editor_mode::update_build_tools() lay.preview_length = 0.0; if (false == lay.active) return; - auto mouse{ground}; + // geometric ray, same as extend: Mouse_Position() is a delayed depth pick and lags the cursor + screen_projection const lay_projection; + double const height{false == lay.points.empty() ? lay.points.back().y : lay.start.track != nullptr ? lay.start.position.y : 0.0}; + glm::dvec3 mouse{ground}; + if (m_cursor_override.has_value()) + mouse = glm::dvec3{m_cursor_override->x, height, m_cursor_override->z}; + else if (false == lay_projection.on_level(io.MousePos, height, mouse)) + mouse = ground; glm::dvec2 heading; bool const straight{io.KeyShift && lay_heading(heading)}; if (straight) { auto const &last{lay.points.back()}; - auto const along{std::max(0.0, glm::dot(glm::dvec2{ground.x - last.x, ground.z - last.z}, heading))}; - mouse = {last.x + heading.x * along, ground.y, last.z + heading.y * along}; + auto const along{std::max(0.0, glm::dot(glm::dvec2{mouse.x - last.x, mouse.z - last.z}, heading))}; + mouse = {last.x + heading.x * along, mouse.y, last.z + heading.y * along}; } lay.mouse = typed_lay(mouse); lay.mouse_snap = straight ? editor_track::snap_target{} : m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {}); @@ -3907,7 +3910,7 @@ glm::dvec3 editor_mode::cursor_level(double const Height) const glm::dvec3 const ground{cursor_ground()}; screen_projection const projection; glm::dvec3 level; - if (false == projection.on_level(ImGui::GetIO().MousePos, Height, level) || glm::distance(level, glm::dvec3{Global.pCamera.Pos}) > static_cast(kMaxPlacementDistance)) + if (false == projection.on_level(ImGui::GetIO().MousePos, Height, level) || (false == Global.editor_tracks && glm::distance(level, glm::dvec3{Global.pCamera.Pos}) > static_cast(kMaxPlacementDistance))) return ground; return level; } @@ -5049,15 +5052,11 @@ void editor_mode::draw_build_overlay() const previous = next; } }; - glm::dvec3 const camera{Global.pCamera.Pos}; auto const &display{ImGui::GetIO().DisplaySize}; for (auto const *track : simulation::Paths.sequence()) { if (track == nullptr || track->m_editorremoved || track->m_paths.empty()) continue; - auto const &first{track->m_paths.front().points}; - if (glm::distance(first[segment_data::point::start], camera) > kJointRange && glm::distance(first[segment_data::point::end], camera) > kJointRange) - continue; for (auto const &path : track->m_paths) for (auto const point : {segment_data::point::start, segment_data::point::end}) { diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index d9dbb3f5..9e19d1b2 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -40,8 +40,6 @@ namespace using geometry::bezier; float const kHoverPixels{12.0f}; -double const kHoverReachMin{15.0}; -double const kHoverReachMax{250.0}; double const kHoverLabelDelay{0.35}; // s the cursor rests on a path before its data shows up double const kFocusDistance{25.0}; auto const kContextClick{std::chrono::milliseconds(350)}; @@ -98,50 +96,40 @@ editor_mode::track_hover editor_mode::track_under_cursor() const { track_hover result; auto const &io{ImGui::GetIO()}; - glm::dvec3 const camera{Global.pCamera.Pos}; - glm::dvec3 const ground{cursor_ground()}; - auto const reach{std::clamp(glm::distance(camera, ground) * 0.1, kHoverReachMin, kHoverReachMax)}; screen_projection const projection; glm::vec2 const mouse{io.MousePos.x, io.MousePos.y}; double best{kHoverPixels * kHoverPixels}; - for (auto *section : simulation::Region->sections(ground, static_cast(reach))) + for (auto *track : simulation::Paths.sequence()) { - for (auto const &cell : section->m_cells) + if (track == nullptr || false == rail(*track) || track->m_editorremoved) + continue; + for (int i = 0; i < static_cast(track->m_paths.size()); ++i) { - for (auto *track : cell.m_directories.paths) + bezier const curve{track->m_paths[i]}; + auto const count{std::clamp(static_cast(curve.plan_length() / 3.0), 6, 64)}; + auto previous{curve.point(0.0)}; + ImVec2 from; + bool visible{projection.project(previous, from)}; + for (int k = 1; k <= count; ++k) { - if (track == nullptr || false == rail(*track)) - continue; - for (int i = 0; i < static_cast(track->m_paths.size()); ++i) + auto const next{curve.point(static_cast(k) / count)}; + ImVec2 to; + bool const shown{projection.project(next, to)}; + if (visible && shown) { - bezier const curve{track->m_paths[i]}; - auto const count{std::clamp(static_cast(curve.plan_length() / 3.0), 6, 64)}; - auto previous{curve.point(0.0)}; - ImVec2 from; - bool visible{projection.project(previous, from)}; - for (int k = 1; k <= count; ++k) + double t; + auto const distance{segment_distance2(mouse, {from.x, from.y}, {to.x, to.y}, t)}; + if (distance < best) { - auto const next{curve.point(static_cast(k) / count)}; - ImVec2 to; - bool const shown{projection.project(next, to)}; - if (visible && shown) - { - double t; - auto const distance{segment_distance2(mouse, {from.x, from.y}, {to.x, to.y}, t)}; - auto const point{glm::mix(previous, next, t)}; - if (distance < best && glm::distance(point, camera) <= static_cast(kMaxPlacementDistance)) - { - best = distance; - result.track = track; - result.path = i; - result.point = point; - } - } - previous = next; - from = to; - visible = shown; + best = distance; + result.track = track; + result.path = i; + result.point = glm::mix(previous, next, t); } } + previous = next; + from = to; + visible = shown; } } } @@ -908,14 +896,13 @@ void editor_mode::finish_track_box() glm::vec2 const low{std::min(box.from.x, box.to.x), std::min(box.from.y, box.to.y)}; glm::vec2 const high{std::max(box.from.x, box.to.x), std::max(box.from.y, box.to.y)}; screen_projection const projection; - glm::dvec3 const camera{Global.pCamera.Pos}; track_set_prune(); if (m_track_set.empty()) if (auto *selected{selected_track()}; selected != nullptr) m_track_set.push_back(selected); for (auto *track : simulation::Paths.sequence()) { - if (track == nullptr || false == rail(*track) || in_track_set(track) || glm::distance(track->location(), camera) > static_cast(Global.BaseDrawRange)) + if (track == nullptr || false == rail(*track) || in_track_set(track)) continue; bool inside{false}; for (auto const &path : track->m_paths) diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index d5d403f8..c429c88f 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -31,6 +31,28 @@ int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of r namespace { +float +editor_track_view_range(float const Range, bool const Apply) +{ + if (false == Apply || false == Global.editor_tracks) + return Range; + float extra = 50000.0f; + if (Global.EditorOrtho) + extra = Global.EditorOrthoExtent * 3.0f; + return Range > extra ? Range : extra; +} + +bool +editor_far_tracks_only(bool const Apply, scene::basic_cell const *Cell, glm::dvec3 const &Camera, double const SceneryLimit) +{ + if (false == Apply) + return false; + double nearest = glm::length(Cell->m_area.center - Camera) - static_cast(Cell->m_area.radius); + if (nearest < 0.0) + nearest = 0.0; + return nearest > SceneryLimit; +} + // returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound material_handle submodel_material( TSubModel const *Submodel ) { @@ -1642,8 +1664,10 @@ void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config Config.draw_range = std::max( 2000.f, Config.draw_range ); // projection - auto const zfar = ( Zfar > 1.f ? Zfar : Config.draw_range * Zfar ); + auto 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); + if (EditorModeFlag && Viewport.main) + zfar = editor_track_view_range(zfar, true); camera.projection() = perspective_projection(Viewport.projection, znear, zfar, frustumtest_proj); editor_ortho_projection(Viewport, zfar, camera.projection(), frustumtest_proj); @@ -1726,6 +1750,8 @@ void opengl33_renderer::setup_pass(viewport_config &Viewport, renderpass_config // projection float znear = 0.1f * Global.ZoomFactor; float zfar = Config.draw_range * Global.fDistanceFactor; + if (EditorModeFlag && Viewport.main && Mode == rendermode::pickscenery) + zfar = editor_track_view_range(zfar, true); if (vr) { glm::mat4 transform = vr->get_pick_transform(); @@ -2447,7 +2473,9 @@ void opengl33_renderer::Render(scene::basic_region *Region) glm::vec3 const cameraposition{m_renderpass.pass_camera.position()}; auto const camerax = static_cast(std::floor(cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2)); auto const cameraz = static_cast(std::floor(cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2)); - int const segmentcount = 2 * static_cast(std::ceil(m_renderpass.draw_range * Global.fDistanceFactor / scene::EU07_SECTIONSIZE)); + float section_range = m_renderpass.draw_range * Global.fDistanceFactor; + section_range = editor_track_view_range(section_range, EditorModeFlag && (m_renderpass.draw_mode == rendermode::color || m_renderpass.draw_mode == rendermode::pickscenery)); + int const segmentcount = 2 * static_cast(std::ceil(section_range / scene::EU07_SECTIONSIZE)); int const originx = camerax - segmentcount / 2; int const originz = cameraz - segmentcount / 2; @@ -2643,6 +2671,9 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite // cache initial iterator for the second sweep auto first{First}; + auto const scenery_limit{static_cast(Global.BaseDrawRange) * Global.fDistanceFactor + 250.0}; + bool const far_paths{EditorModeFlag && Global.editor_tracks && (m_renderpass.draw_mode == rendermode::color || m_renderpass.draw_mode == rendermode::pickscenery)}; + glm::dvec3 const camera{m_renderpass.pass_camera.position()}; // first pass draws elements which we know are located in section banks, to reduce vbo switching while (First != Last) { @@ -2650,6 +2681,7 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite auto *cell = First->second; // przeliczenia animacji torów w sektorze cell->RaAnimate(m_framestamp); + bool const tracks_only{editor_far_tracks_only(far_paths, cell, camera, scenery_limit)}; switch (m_renderpass.draw_mode) { @@ -2662,9 +2694,12 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite // render // opaque non-instanced shapes - for (auto const &shape : cell->m_shapesopaque) + if (false == tracks_only) { - Render(shape, false); + for (auto const &shape : cell->m_shapesopaque) + { + Render(shape, false); + } } // tracks // TODO: update after path node refactoring @@ -2720,9 +2755,12 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite // render // opaque non-instanced shapes // non-interactive scenery elements get neutral colour - model_ubs.param[0] = colors::none; - for (auto const &shape : cell->m_shapesopaque) - Render(shape, false); + if (false == tracks_only) + { + model_ubs.param[0] = colors::none; + for (auto const &shape : cell->m_shapesopaque) + Render(shape, false); + } // tracks for (auto *path : cell->m_paths) { @@ -2753,6 +2791,8 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite case rendermode::color: case rendermode::shadows: { + if (editor_far_tracks_only(far_paths, cell, camera, scenery_limit)) + break; // TBD, TODO: refactor in to a method to reuse in branch below? // opaque parts of instanced models -- accumulate this cell's buckets // into the frame-level map; the actual Render_Instanced() calls are @@ -2833,6 +2873,8 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite } case rendermode::pickscenery: { + if (editor_far_tracks_only(far_paths, cell, camera, scenery_limit)) + break; // opaque parts of instanced models // same procedure like with regular render, but each node receives custom colour used for picking. // picking always uses the per-instance path because each instance needs a unique pick colour; diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index b8a73cfb..59152c64 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -33,6 +33,28 @@ int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of r namespace { +float +editor_track_view_range(float const Range, bool const Apply) +{ + if( false == Apply || false == Global.editor_tracks ) + return Range; + float extra = 50000.0f; + if( Global.EditorOrtho ) + extra = Global.EditorOrthoExtent * 3.0f; + return Range > extra ? Range : extra; +} + +bool +editor_far_tracks_only(bool const Apply, scene::basic_cell const *Cell, glm::dvec3 const &Camera, double const SceneryLimit) +{ + if( false == Apply ) + return false; + double nearest = glm::length( Cell->m_area.center - Camera ) - static_cast( Cell->m_area.radius ); + if( nearest < 0.0 ) + nearest = 0.0; + return nearest > SceneryLimit; +} + // returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound material_handle submodel_material( TSubModel const *Submodel ) { @@ -1875,7 +1897,9 @@ opengl_renderer::Render( scene::basic_region *Region ) { glm::vec3 const cameraposition { m_renderpass.camera.position() }; auto const camerax = static_cast( std::floor( cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); auto const cameraz = static_cast( std::floor( cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) ); - int const segmentcount = 2 * static_cast( std::ceil( m_renderpass.draw_range * Global.fDistanceFactor / scene::EU07_SECTIONSIZE ) ); + float section_range = m_renderpass.draw_range * Global.fDistanceFactor; + section_range = editor_track_view_range( section_range, EditorModeFlag && ( m_renderpass.draw_mode == rendermode::color || m_renderpass.draw_mode == rendermode::pickscenery ) ); + int const segmentcount = 2 * static_cast( std::ceil( section_range / scene::EU07_SECTIONSIZE ) ); int const originx = camerax - segmentcount / 2; int const originz = cameraz - segmentcount / 2; @@ -2040,12 +2064,16 @@ opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator // cache initial iterator for the second sweep auto first { First }; + auto const scenery_limit{ static_cast( Global.BaseDrawRange ) * Global.fDistanceFactor + 250.0 }; + bool const far_paths{ EditorModeFlag && Global.editor_tracks && ( m_renderpass.draw_mode == rendermode::color || m_renderpass.draw_mode == rendermode::pickscenery ) }; + glm::dvec3 const camera{ m_renderpass.camera.position() }; // first pass draws elements which we know are located in section banks, to reduce vbo switching while( First != Last ) { auto *cell = First->second; // przeliczenia animacji torów w sektorze cell->RaAnimate( m_framestamp ); + bool const tracks_only{ editor_far_tracks_only( far_paths, cell, camera, scenery_limit ) }; switch( m_renderpass.draw_mode ) { case rendermode::color: { @@ -2056,7 +2084,9 @@ opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator // render // opaque non-instanced shapes - for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + if( false == tracks_only ) { + for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + } // tracks // TODO: update after path node refactoring Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) ); @@ -2158,8 +2188,10 @@ opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator // render // opaque non-instanced shapes // non-interactive scenery elements get neutral colour - ::glColor3fv( glm::value_ptr( colors::none ) ); - for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + if( false == tracks_only ) { + ::glColor3fv( glm::value_ptr( colors::none ) ); + for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); } + } // tracks for( auto *path : cell->m_paths ) { ::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) ); @@ -2188,6 +2220,8 @@ opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator case rendermode::color: case rendermode::shadows: case rendermode::cabshadows: { + if( editor_far_tracks_only( far_paths, cell, camera, scenery_limit ) ) + break; // TBD, TODO: refactor in to a method to reuse in branch below? // opaque parts of instanced models for( auto *instance : cell->m_instancesopaque ) { @@ -2230,6 +2264,8 @@ opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator break; } case rendermode::pickscenery: { + if( editor_far_tracks_only( far_paths, cell, camera, scenery_limit ) ) + break; // opaque parts of instanced models // same procedure like with regular render, but each node receives custom colour used for picking for( auto *instance : cell->m_instancesopaque ) { diff --git a/utilities/Globals.h b/utilities/Globals.h index 08707d8c..8f5748c3 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -86,6 +86,7 @@ struct global_settings { std::string local_start_vehicle{ "EU07-424" }; std::string editor_enter_vehicle; // vehicle placed in the editor, to be entered when the driving goes on bool editor_session{ false }; // scenery was opened for editing (-edit): loader keeps track of scenery layers and hands over to the editor + bool editor_tracks{ false }; // F3 track workspace: draw and pick tracks without the scenery draw-range cap int iConvertModels{ 0 }; // tworzenie plików binarnych int iConvertIndexRange{ 1000 }; // range of duplicate vertex scan bool file_binary_terrain{ true }; // enable binary terrain (de)serialization