diff --git a/application/editormode.cpp b/application/editormode.cpp index 1e2eadc7..ba91f84e 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -636,6 +636,7 @@ editor_ui *editor_mode::ui() const bool editor_mode::init() { EditorSettings.load(); + Global.EditorOrthoBallast = EditorSettings.top_ballast(); EditorModelSets.load(); Camera.Init({0, 15, 0}, {glm::radians(-30.0), glm::radians(180.0), 0}, nullptr); return m_input.init(); @@ -3516,6 +3517,13 @@ void editor_mode::render_view_menu() toggle_ortho(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("The view straight down without perspective, as on a map; again for the camera as it was")); + if (ImGui::MenuItem(STR_C("Ballast in the top view"), nullptr, &Global.EditorOrthoBallast)) + { + EditorSettings.top_ballast(Global.EditorOrthoBallast); + EditorSettings.save(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("Off: the top view draws the rails and sleepers of the tracks without the ballast, so the ground under them shows")); ImGui::MenuItem(STR_C("Show lanes"), nullptr, &m_roadtool.lanes); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("The lanes of the roads drawn as the paths the vehicles take")); @@ -5521,6 +5529,14 @@ void editor_mode::render_gizmo_options() ImGui::SliderFloat(STR_C("Extent (m), wheel / MMB pan"), &Global.EditorOrthoExtent, 5.0f, 50000.0f, "%.0f", ImGuiSliderFlags_Logarithmic); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("Wheel zooms. Hold the wheel and drag to pan.")); + ImGui::SameLine(); + if (ImGui::Checkbox(STR_C("Ballast"), &Global.EditorOrthoBallast)) + { + EditorSettings.top_ballast(Global.EditorOrthoBallast); + EditorSettings.save(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("Off: the top view draws the rails and sleepers of the tracks without the ballast, so the ground under them shows")); } if (ui()->mode() == nodebank_panel::TRACK && m_track_tab != track_tab::path && m_track_tab != track_tab::turnout && m_track_tab != track_tab::turntable && m_track_tab != track_tab::signals && m_track_tab != track_tab::lineside) diff --git a/application/editormodeselftestscene.cpp b/application/editormodeselftestscene.cpp index 4a8cc66e..b74dc2de 100644 --- a/application/editormodeselftestscene.cpp +++ b/application/editormodeselftestscene.cpp @@ -275,13 +275,16 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm if (command == "ortho") { float extent{0.0f}; - words >> extent; + std::string ballast; + words >> extent >> ballast; choose_work_area(work_area::tracks); if (Global.EditorOrtho == extent < 0.0f) toggle_ortho(); if (extent > 0.0f) Global.EditorOrthoExtent = std::clamp(extent, 5.0f, 50000.0f); - WriteLog(format("SELFTEST ortho extent %.0f", Global.EditorOrthoExtent)); + if (false == ballast.empty()) + Global.EditorOrthoBallast = ballast != "noballast"; + WriteLog(format("SELFTEST ortho extent %.0f ballast %d", Global.EditorOrthoExtent, Global.EditorOrthoBallast ? 1 : 0)); return selftest_result::handled; } if (command == "mapstate") diff --git a/application/editormodeselftestterrain.cpp b/application/editormodeselftestterrain.cpp index 8ee06f02..2b49a7bc 100644 --- a/application/editormodeselftestterrain.cpp +++ b/application/editormodeselftestterrain.cpp @@ -53,6 +53,12 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co if (command == "orthophoto") { auto config{m_orthophoto.settings()}; + if (words.str().find(" off") != std::string::npos) + { + m_orthophoto.enabled(false); + WriteLog("SELFTEST orthophoto off"); + return selftest_result::handled; + } int fit{1}; int inscene{0}; words >> config.north >> config.east >> config.radius >> fit >> inscene; diff --git a/editor/editorSettings.cpp b/editor/editorSettings.cpp index 515d5cdd..4a9b8232 100644 --- a/editor/editorSettings.cpp +++ b/editor/editorSettings.cpp @@ -108,6 +108,8 @@ bool editorSettings::load() m_terrain_offer = (value == "1"); else if (key == "track_tools_all") m_track_tools_more = (value == "1"); + else if (key == "top_ballast") + m_top_ballast = (value == "1"); else if (key.rfind(origin_prefix, 0) == 0) { // northing;easting @@ -154,6 +156,7 @@ bool editorSettings::save() stream << "first_steps " << (m_first_steps ? 1 : 0) << "\n"; stream << "terrain_offer " << (m_terrain_offer ? 1 : 0) << "\n"; stream << "track_tools_all " << (m_track_tools_more ? 1 : 0) << "\n"; + stream << "top_ballast " << (m_top_ballast ? 1 : 0) << "\n"; stream << std::fixed << std::setprecision(3); for (auto const &origin : m_orthophoto_origins) stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n"; diff --git a/editor/editorSettings.hpp b/editor/editorSettings.hpp index 84fea1e7..ddee1ab1 100644 --- a/editor/editorSettings.hpp +++ b/editor/editorSettings.hpp @@ -61,6 +61,9 @@ public: void terrain_offer(bool Offer) { m_terrain_offer = Offer; } bool track_tools_more() const { return m_track_tools_more; } void track_tools_more(bool Show) { m_track_tools_more = Show; } + // the ballast of the railway drawn in the top view + bool top_ballast() const { return m_top_ballast; } + void top_ballast(bool Show) { m_top_ballast = Show; } private: movement_scheme m_movement{movement_scheme::wsad}; @@ -74,6 +77,7 @@ private: bool m_first_steps{true}; bool m_terrain_offer{true}; bool m_track_tools_more{false}; + bool m_top_ballast{true}; }; // global editor settings instance diff --git a/editor/editorTerrainStreamer.cpp b/editor/editorTerrainStreamer.cpp index 52fa7995..8f799c46 100644 --- a/editor/editorTerrainStreamer.cpp +++ b/editor/editorTerrainStreamer.cpp @@ -34,6 +34,9 @@ double constexpr upload_budget{0.004}; // seconds of a frame for taking over fin int constexpr materials_per_frame{6}; double constexpr look_ahead{8.0}; // seconds; chunks are loaded around where the camera will be by then double constexpr look_ahead_limit{2000.0}; // metres +double constexpr pixel_distance{1300.0}; // m; a pixel of the top view is as big as one of the 3D view this far away +int constexpr top_radius{200}; // chunks the top view of the tracks loads around its centre at most +double constexpr settled_walk{1.0}; // seconds; the chunks around the camera are looked over this often when nothing is coming double constexpr region_half{scene::EU07_REGIONSIDESECTIONCOUNT * scene::EU07_SECTIONSIZE * 0.5 - heightmap::chunk_size}; double constexpr kPi{3.14159265358979323846}; @@ -135,6 +138,7 @@ void terrain_streamer::close() m_packs.clear(); m_loading.clear(); m_removed.clear(); + m_settled = false; m_palette.clear(); m_recording = 0; m_change = change(); @@ -159,6 +163,7 @@ void terrain_streamer::reset() m_packs.clear(); m_loading.clear(); m_removed.clear(); + m_settled = false; m_palette.clear(); clear_water(false); m_recording = 0; @@ -506,7 +511,7 @@ int terrain_streamer::wanted_stride(editor_terrain const &Chunk, glm::dvec3 cons // distance from the camera to the box of the chunk glm::dvec3 const offset{std::max({Chunk.m_low.x - Camera.x, 0.0, Camera.x - Chunk.m_high.x}), std::max({Chunk.m_low.y - Camera.y, 0.0, Camera.y - Chunk.m_high.y}), std::max({Chunk.m_low.z - Camera.z, 0.0, Camera.z - Chunk.m_high.z})}; - auto const distance{glm::length(offset)}; + auto const distance{m_pixel > 0.0 ? m_pixel * pixel_distance : glm::length(offset)}; auto const spacing{Chunk.data().spacing()}; auto const limit{heightmap::max_stride(Chunk.cells())}; auto const stride_for = [&](double const Distance) { @@ -586,12 +591,19 @@ void terrain_streamer::update(glm::dvec3 const &Camera) m_results.pop_front(); } process(done); + m_settled = false; } + // the top view of the tracks wants the terrain as far as the corners of the screen, as fine as its pixels need + auto const top{Global.EditorOrtho && Global.editor_tracks}; + auto const aspect{Global.window_size.x / std::max(1.0, static_cast(Global.window_size.y))}; + auto const reach{top ? Global.EditorOrthoExtent * std::sqrt(1.0 + aspect * aspect) : 0.0}; + m_pixel = top ? 2.0 * Global.EditorOrthoExtent / std::max(1, Global.window_size.y) : 0.0; + // chunks wanted around the camera and around where it's heading - auto const range{std::max(m_radius * heightmap::chunk_size, static_cast(Global.BaseDrawRange) * std::max(1.0, static_cast(Global.fDistanceFactor))) + heightmap::chunk_size * 0.5}; - auto const radius{std::min(64, static_cast(std::ceil(range / heightmap::chunk_size)))}; - if (m_ringradius != radius) + auto const range{std::max({m_radius * heightmap::chunk_size, static_cast(Global.BaseDrawRange) * std::max(1.0, static_cast(Global.fDistanceFactor)), reach}) + heightmap::chunk_size * 0.5}; + auto const radius{std::min(top ? top_radius : 64, static_cast(std::ceil(range / heightmap::chunk_size)))}; + if (m_ringradius < radius) { m_ring.clear(); for (int dz = -radius; dz <= radius; ++dz) @@ -606,12 +618,31 @@ void terrain_streamer::update(glm::dvec3 const &Camera) std::vector centres{here}; if (there != here) centres.push_back(there); + // a walk which asked for nothing isn't made again until something comes or the camera moves to another chunk + if (m_settled && (here != m_walkhere || there != m_walkthere || radius != m_walkradius || now - m_walktime > settled_walk)) + m_settled = false; + if (false == m_settled) + { + m_walkhere = here; + m_walkthere = there; + m_walkradius = radius; + m_walktime = now; + m_settled = m_loading.empty(); + } + else + centres.clear(); + auto const walked{(radius + 1) * (radius + 1)}; for (auto const ¢re : centres) { for (auto const &offset : m_ring) { - if (m_loading.size() >= max_loading) + if (offset.first * offset.first + offset.second * offset.second > walked) break; + if (m_loading.size() >= max_loading) + { + m_settled = false; + break; + } chunk_key const key{centre.first + offset.first, centre.second + offset.second}; if (m_chunks.count(key) > 0 || m_loading.count(key) > 0 || m_removed.count(key) > 0) continue; @@ -627,8 +658,11 @@ void terrain_streamer::update(glm::dvec3 const &Camera) task.generation = pack.generation; task.priority = distance; submit(std::move(task)); + m_settled = false; continue; } + if (pack.status == pack_state::state::loading) + m_settled = false; if (pack.status != pack_state::state::ready || false == pack.file->has(key)) continue; job task; @@ -640,6 +674,7 @@ void terrain_streamer::update(glm::dvec3 const &Camera) task.priority = distance; m_loading.insert(key); submit(std::move(task)); + m_settled = false; } } diff --git a/editor/editorTerrainStreamer.hpp b/editor/editorTerrainStreamer.hpp index ab76ca81..beff9746 100644 --- a/editor/editorTerrainStreamer.hpp +++ b/editor/editorTerrainStreamer.hpp @@ -295,6 +295,13 @@ class terrain_streamer bool m_hascamera{false}; std::vector m_ring; // offsets of the chunks around a point, the nearest first int m_ringradius{-1}; + double m_pixel{0.0}; // metres a pixel of the top view spans; none in the 3D view + // the last walk over the chunks around the camera, and whether it found nothing more to ask for + bool m_settled{false}; + chunk_key m_walkhere{0, 0}; + chunk_key m_walkthere{0, 0}; + int m_walkradius{0}; + double m_walktime{0.0}; // shared with the workers, guarded by m_mutex std::mutex m_mutex; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 4d64ab93..8f3408bf 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -9889,3 +9889,12 @@ msgid_plural "\nThe terrain followed the tracks: %zu formations laid anew" msgstr[0] "\nTeren poszedł za torami: %zu torowisko ułożone od nowa" msgstr[1] "\nTeren poszedł za torami: %zu torowiska ułożone od nowa" msgstr[2] "\nTeren poszedł za torami: %zu torowisk ułożonych od nowa" + +msgid "Ballast in the top view" +msgstr "Podsypka w widoku z góry" + +msgid "Ballast" +msgstr "Podsypka" + +msgid "Off: the top view draws the rails and sleepers of the tracks without the ballast, so the ground under them shows" +msgstr "Wyłączone: widok z góry rysuje szyny i podkłady bez podsypki, widać teren pod torami" diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 7f2704da..b77731cf 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -31,6 +31,14 @@ int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of r namespace { +// the top view of the editor shows everything this close to the camera, the corners of the screen included +float +editor_ortho_reach() +{ + auto const aspect{Global.window_size.x / std::max(1.f, static_cast(Global.window_size.y))}; + return Global.EditorOrthoExtent * std::sqrt(1.f + aspect * aspect) + 500.f; +} + float editor_track_view_range(float const Range, bool const Apply) { @@ -38,14 +46,22 @@ editor_track_view_range(float const Range, bool const Apply) return Range; float extra = 50000.0f; if (Global.EditorOrtho) - extra = Global.EditorOrthoExtent * 3.0f; + extra = editor_ortho_reach(); return Range > extra ? Range : extra; } +// the top view of the editor may leave the ballast of the railway out, for the ground under the tracks to show +bool +editor_ballast_hidden(TTrack const *Track, bool const Colour) +{ + return Colour && EditorModeFlag && Global.EditorOrtho && false == Global.EditorOrthoBallast && (Track->iCategoryFlag & 15) == 1; +} + +// the top view draws the scenery as far as the tracks; its models keep their own ranges bool editor_far_tracks_only(bool const Apply, scene::basic_cell const *Cell, glm::dvec3 const &Camera, double const SceneryLimit) { - if (false == Apply) + if (false == Apply || Global.EditorOrtho) return false; double nearest = glm::length(Cell->m_area.center - Camera) - static_cast(Cell->m_area.radius); if (nearest < 0.0) @@ -4519,6 +4535,10 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator { continue; } + if (track->eType == tt_Normal && editor_ballast_hidden(track, m_renderpass.draw_mode == rendermode::color)) + { + continue; + } switch (m_renderpass.draw_mode) { @@ -4582,6 +4602,10 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator { continue; } + if (editor_ballast_hidden(track, m_renderpass.draw_mode == rendermode::color)) + { + continue; + } switch (m_renderpass.draw_mode) { diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index d66e7bf6..c4351b9e 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -33,6 +33,14 @@ int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of r namespace { +// the top view of the editor shows everything this close to the camera, the corners of the screen included +float +editor_ortho_reach() +{ + auto const aspect{ Global.window_size.x / std::max( 1.f, static_cast( Global.window_size.y ) ) }; + return Global.EditorOrthoExtent * std::sqrt( 1.f + aspect * aspect ) + 500.f; +} + float editor_track_view_range(float const Range, bool const Apply) { @@ -40,14 +48,15 @@ editor_track_view_range(float const Range, bool const Apply) return Range; float extra = 50000.0f; if( Global.EditorOrtho ) - extra = Global.EditorOrthoExtent * 3.0f; + extra = editor_ortho_reach(); return Range > extra ? Range : extra; } +// the top view draws the scenery as far as the tracks; its models keep their own ranges bool editor_far_tracks_only(bool const Apply, scene::basic_cell const *Cell, glm::dvec3 const &Camera, double const SceneryLimit) { - if( false == Apply ) + if( false == Apply || Global.EditorOrtho ) return false; double nearest = glm::length( Cell->m_area.center - Camera ) - static_cast( Cell->m_area.radius ); if( nearest < 0.0 ) diff --git a/utilities/Globals.h b/utilities/Globals.h index b2ccd941..c63197aa 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -174,6 +174,7 @@ struct global_settings { float FieldOfView{ 45.f }; // vertical field of view for the camera. TODO: move it to the renderer bool EditorOrtho{ false }; float EditorOrthoExtent{ 150.f }; + bool EditorOrthoBallast{ true }; // the ballast of the railway drawn in the top view of the editor GLint iMaxTextureSize{ 4096 }; // maksymalny rozmiar tekstury GLint iMaxCabTextureSize{ 4096 }; // largest allowed texture in vehicle cab int iMultisampling{ 2 }; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px