From 5e846018b6f3d60e05c6f297225e0c45dee2ce5c Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 23 Oct 2026 13:35:07 +0200 Subject: [PATCH] feat(editor): far out the top view shows the base map of OpenStreetMap from geoportal.gov.pl in place of the imagery, tiles as large as the view needs, the ground not drawn under it nor streamed as far as the view; the tracks over it in the colour of the rails, blue by default, and the joints of the pieces left out far out --- application/editormode.cpp | 19 ++- application/editormodetrack.cpp | 4 + application/editormodetrackux.cpp | 2 +- editor/editorOrthophoto.cpp | 201 +++++++++++++++++++++++++++--- editor/editorOrthophoto.hpp | 14 ++- editor/editorSettings.hpp | 2 +- editor/editorTerrainStreamer.cpp | 2 +- lang/editor_pl.po | 3 + rendering/opengl33renderer.cpp | 3 +- utilities/Globals.h | 1 + 10 files changed, 229 insertions(+), 22 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 9572dfed..32af41a2 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1470,6 +1470,14 @@ bool editor_mode::update() draw_terrain_overlay(); // --- geoportal orthophoto: streamed around the camera, drawn beneath the other overlays --- + // far out the top view shows the base map in place of the imagery, and the ground isn't drawn under it + { + auto const &io{ImGui::GetIO()}; + auto const metres{Global.EditorOrtho && io.DisplaySize.y > 0.0f ? 2.0 * static_cast(Global.EditorOrthoExtent) / io.DisplaySize.y : 0.0}; + auto const aspect{io.DisplaySize.y > 0.0f ? static_cast(io.DisplaySize.x / io.DisplaySize.y) : 1.0}; + Global.EditorOrthoMap = m_orthophoto.enabled() && metres >= (m_orthophoto.overview() ? 1.6 : 2.0); + m_orthophoto.overview(Global.EditorOrthoMap ? metres : 0.0, static_cast(Global.EditorOrthoExtent) * std::sqrt(1.0 + aspect * aspect)); + } m_orthophoto.update(Camera.Pos); nmt_update(); draw_orthophoto(); @@ -1486,7 +1494,7 @@ bool editor_mode::update() draw_track_labels(); draw_track_legend(); } - else if (Global.EditorOrtho && Global.EditorOrthoTracks > 1) + else if (Global.EditorOrtho && (Global.EditorOrthoTracks > 1 || Global.EditorOrthoMap)) draw_track_lines(); if (selected_track()) draw_track_overlay(); @@ -2494,6 +2502,15 @@ void editor_mode::draw_orthophoto() ImGuiIO const &io = ImGui::GetIO(); float const aspect = io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f; m_orthophoto.draw(editor_mode::projection_matrix(aspect) * view, Camera.Pos, io.DisplaySize.x, io.DisplaySize.y); + if (false == m_orthophoto.overview()) + return; + char const *credit{STR_C("(c) OpenStreetMap contributors, geoportal.gov.pl")}; + auto const size{ImGui::CalcTextSize(credit)}; + auto const corner{editor_ui::view_max()}; + ImVec2 const from{corner.x - size.x - 12.0f, corner.y - size.y - 44.0f}; + auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; + drawlist->AddRectFilled(ImVec2(from.x - 4.0f, from.y - 2.0f), ImVec2(from.x + size.x + 4.0f, from.y + size.y + 2.0f), IM_COL32(255, 255, 255, 190), 3.0f); + drawlist->AddText(from, IM_COL32(40, 40, 40, 255), credit); } void editor_mode::draw_ortho_compass() const diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 383aeac3..8301bd94 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -2201,8 +2201,12 @@ void editor_mode::draw_build_overlay() const // in the 3D view only near the camera, where they can be told apart; from above all of them glm::dvec3 const eye{Global.pCamera.Pos}; auto const in_reach = [&](glm::dvec3 const &Point) { return Global.EditorOrtho || glm::distance(Point, eye) <= kDotsRange; }; + // far out the joints of the pieces would cover the tracks, only the free ends are told + auto const joints{false == Global.EditorOrtho || display.y <= 0.0f || 2.0 * static_cast(Global.EditorOrthoExtent) / display.y <= 1.0}; for (auto const *track : simulation::Paths.sequence()) { + if (false == joints) + break; if (track == nullptr || track->m_editorremoved || track->m_paths.empty()) continue; for (auto const &path : track->m_paths) diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index fc127955..ee669f04 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -204,7 +204,7 @@ void editor_mode::draw_track_lines() const auto const metres{ortho_metres_per_pixel()}; auto const colouring{ui()->mode() == nodebank_panel::TRACK ? m_track_colouring : track_colouring::plain}; // the top view of the rails alone draws them in a colour of their own, as on a plan: both rails where they're apart on the screen - auto const rails{metres > 0.0 && Global.EditorOrthoTracks > 1 && colouring == track_colouring::plain}; + auto const rails{metres > 0.0 && (Global.EditorOrthoTracks > 1 || Global.EditorOrthoMap) && colouring == track_colouring::plain}; if (metres < kLinesFrom && colouring == track_colouring::plain && false == rails) return; auto const &io{ImGui::GetIO()}; diff --git a/editor/editorOrthophoto.cpp b/editor/editorOrthophoto.cpp index f9349777..cfd024d3 100644 --- a/editor/editorOrthophoto.cpp +++ b/editor/editorOrthophoto.cpp @@ -40,6 +40,9 @@ namespace fs = std::filesystem; constexpr int standard_pixels{1024}; // ~25 cm/px for a 256 m tile, matches the standard orthophoto constexpr int hires_pixels{4096}; // ~6 cm/px, the WMS MaxWidth/MaxHeight of the geoportal services constexpr int probe_pixels{64}; // coverage test of the high-resolution product +constexpr int map_pixels{1024}; // a side of a tile of the map, whatever its size in metres +constexpr int map_stamp{32}; // rows under a tile of the map the service writes its credit into, cut off +constexpr char const *map_service{"https://mapy.geoportal.gov.pl/wss/ext/OSM/BaseMap/service"}; constexpr int worker_count{3}; // concurrent requests; the service is slow but shouldn't be hammered constexpr std::size_t upload_budget{16u << 20}; // bytes of texture data uploaded per frame (always at least one tile) constexpr std::size_t vertex_budget{60000}; // ImGui uses 16-bit indices and not every backend supports vertex offsets @@ -336,6 +339,31 @@ bool load_tile(http_client &Client, tile_request const &Request, image &Out, boo return true; } +// the base map of OpenStreetMap from geoportal.gov.pl, in the metres of PUWG 1992 as the imagery. the service writes its credit into +// the corner of every image: the image reaches a little further south and the rows with it are cut off +bool load_map(http_client &Client, int const Level, int const Ix, int const Iy, image &Out, std::string &Error, stop_check const &Stop) +{ + auto const size{static_cast(editor_orthophoto::tile_size) << Level}; + auto const below{size * map_stamp / map_pixels}; + auto const url{std::string{map_service} + "?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&LAYERS=osm&STYLES=&SRS=EPSG:2180&FORMAT=image/png&WIDTH=" + std::to_string(map_pixels) + + "&HEIGHT=" + std::to_string(map_pixels + map_stamp) + "&BBOX=" + std::to_string(Ix * size) + "," + std::to_string(Iy * size - below) + "," + std::to_string((Ix + 1) * size) + "," + + std::to_string((Iy + 1) * size)}; + std::vector data; + auto const path{cache_root() / "osm" / (std::to_string(size) + "_" + std::to_string(Iy) + "_" + std::to_string(Ix) + ".png")}; + if (!fetch(Client, url, path, data, Error, Stop) || !decode(data, Out, Error)) + return false; + if (Out.width != map_pixels || Out.height != map_pixels + map_stamp) + { + Error = "unexpected size of the map " + std::to_string(Out.width) + "x" + std::to_string(Out.height); + std::error_code ec; + fs::remove(path, ec); + return false; + } + Out.height = map_pixels; + Out.rgba.resize(static_cast(map_pixels) * map_pixels * 4); + return true; +} + // the services cover Poland only (EPSG:2180 extent from their capabilities, plus a margin); no point asking about other places bool in_service_area(int Ix, int Iy) { @@ -550,6 +578,9 @@ void editor_orthophoto::enabled(bool State) for (auto &entry : m_tiles) release_tile(entry.second); m_tiles.clear(); + for (auto &entry : m_maps) + release_tile(entry.second); + m_maps.clear(); ++m_generation; // results still in flight are now stale std::lock_guard lock(m_mutex); m_done.clear(); @@ -604,6 +635,8 @@ void editor_orthophoto::cancel_pending() // jobs already picked up by a worker still deliver their result, which is accepted if the tile is still wanted for (auto &entry : m_tiles) entry.second.requested = 0; + for (auto &entry : m_maps) + entry.second.requested = 0; } void editor_orthophoto::clear_cache() @@ -612,6 +645,9 @@ void editor_orthophoto::clear_cache() for (auto &entry : m_tiles) release_tile(entry.second); m_tiles.clear(); + for (auto &entry : m_maps) + release_tile(entry.second); + m_maps.clear(); ++m_generation; { std::lock_guard lock(m_mutex); @@ -628,21 +664,28 @@ void editor_orthophoto::retry_failed() entry.second.failures = 0; entry.second.retry_at = 0.0; } + for (auto &entry : m_maps) + { + entry.second.failures = 0; + entry.second.retry_at = 0.0; + } } editor_orthophoto::statistics editor_orthophoto::stats() const { statistics out; - for (auto const &entry : m_tiles) - { - tile const &t = entry.second; + auto const count = [&](tile const &t) { if (t.level > 0 && !t.empty && t.slot.texture != null_handle) ++out.resident; if (t.requested != 0) ++out.loading; else if (t.failures > 0 && t.level == 0) ++out.failed; - } + }; + for (auto const &entry : m_tiles) + count(entry.second); + for (auto const &entry : m_maps) + count(entry.second); return out; } @@ -706,12 +749,12 @@ void editor_orthophoto::release_tile(tile &Tile) Tile.slot = texture_slot{}; } -void editor_orthophoto::schedule(tile_key const &Key, tile &Tile, int Level, std::uint8_t Alpha) +void editor_orthophoto::schedule(tile_key const &Key, tile &Tile, int Level, std::uint8_t Alpha, int const Map) { { std::lock_guard lock(m_mutex); - m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [&](job const &Job) { return Job.key == Key; }), m_queue.end()); - m_queue.push_back(job{Key, Level, Alpha, source{m_config.year, m_config.hires}, m_generation.load()}); + m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [&](job const &Job) { return Job.key == Key && Job.map == Map; }), m_queue.end()); + m_queue.push_back(job{Key, Map, Level, Alpha, source{m_config.year, m_config.hires}, m_generation.load()}); } Tile.requested = Level; Tile.requested_alpha = Alpha; @@ -728,10 +771,17 @@ std::size_t editor_orthophoto::apply(result &Result, double Now) { if (Result.generation != m_generation.load()) return 0; - auto const lookup = m_tiles.find(Result.key); - if (lookup == m_tiles.end()) + tile *found{nullptr}; + if (Result.map > 0) + { + if (auto const lookup = m_maps.find({Result.map, Result.key}); lookup != m_maps.end()) + found = &lookup->second; + } + else if (auto const lookup = m_tiles.find(Result.key); lookup != m_tiles.end()) + found = &lookup->second; + if (found == nullptr) return 0; // left the area in the meantime; the imagery stays in the disk cache - tile &t = lookup->second; + tile &t = *found; bool const current = (t.requested == Result.level && t.requested_alpha == Result.alpha); if (current) t.requested = 0; @@ -1104,6 +1154,19 @@ void editor_orthophoto::update(glm::dvec3 const &Camera) std::lock_guard lock(m_mutex); m_focus = centre; } + if (m_map_level > 0) + { + update_map(Camera, now); + return; + } + if (false == m_maps.empty()) + { + for (auto &entry : m_maps) + release_tile(entry.second); + m_maps.clear(); + std::lock_guard lock(m_mutex); + m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [](job const &Job) { return Job.map > 0; }), m_queue.end()); + } int const radius = m_config.radius; auto const distance = [&](tile_key const &Key) { return std::max(std::abs(Key.first - centre.first), std::abs(Key.second - centre.second)); }; @@ -1133,7 +1196,7 @@ void editor_orthophoto::update(glm::dvec3 const &Camera) if (evicted) { std::lock_guard lock(m_mutex); - m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [&](job const &Job) { return distance(Job.key) > radius; }), m_queue.end()); + m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [&](job const &Job) { return Job.map == 0 && distance(Job.key) > radius; }), m_queue.end()); } // tiles which need (different) imagery @@ -1175,6 +1238,59 @@ void editor_orthophoto::update(glm::dvec3 const &Camera) update_scene(); } +void editor_orthophoto::overview(double const Scale, double const Reach) +{ + // a pixel of a tile of the map is as large as one of the screen, or up to twice as small + m_map_level = Scale > 0.0 ? std::clamp(static_cast(std::floor(std::log2(Scale * map_pixels / tile_size))), 2, 9) : 0; + m_map_reach = Reach; +} + +void editor_orthophoto::update_map(glm::dvec3 const &Camera, double const Now) +{ + // the imagery gives way to the map, its tiles come back from the disk cache once the view is near again + if (false == m_tiles.empty()) + { + for (auto &entry : m_tiles) + release_tile(entry.second); + m_tiles.clear(); + std::lock_guard lock(m_mutex); + m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [](job const &Job) { return Job.map == 0; }), m_queue.end()); + } + double const east = m_config.east - Camera.x; + double const north = m_config.north + Camera.z; + auto const in_view = [&](map_key const &Key) { + auto const size{tile_size * static_cast(1 << Key.first)}; + return (Key.second.first + 1) * size > east - m_map_reach && Key.second.first * size < east + m_map_reach && (Key.second.second + 1) * size > north - m_map_reach && + Key.second.second * size < north + m_map_reach; + }; + // the tiles of the other levels stay under the ones of the level shown until these come + auto const size{tile_size * static_cast(1 << m_map_level)}; + auto complete{true}; + for (int iy = static_cast(std::floor((north - m_map_reach) / size)); iy <= static_cast(std::floor((north + m_map_reach) / size)); ++iy) + for (int ix = static_cast(std::floor((east - m_map_reach) / size)); ix <= static_cast(std::floor((east + m_map_reach) / size)); ++ix) + { + map_key const key{m_map_level, {ix, iy}}; + tile &t = m_maps[key]; + if (t.level == 0 && false == t.empty) + complete = false; + if (t.empty || t.level == map_pixels || t.requested == map_pixels || Now < t.retry_at) + continue; + schedule(key.second, t, map_pixels, 255, m_map_level); + } + for (auto it = m_maps.begin(); it != m_maps.end();) + { + if (false == in_view(it->first) || std::abs(it->first.first - m_map_level) > 1 || (complete && it->first.first != m_map_level)) + { + release_tile(it->second); + it = m_maps.erase(it); + } + else + ++it; + } + std::lock_guard lock(m_mutex); + m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [&](job const &Job) { return Job.map > 0 && m_maps.count({Job.map, Job.key}) == 0; }), m_queue.end()); +} + void editor_orthophoto::worker() { stbi_set_flip_vertically_on_load_thread(0); // the texture loader flips globally; tiles keep the north edge in the first row @@ -1194,8 +1310,10 @@ void editor_orthophoto::worker() long long bestdistance = LLONG_MAX; for (auto it = m_queue.begin(); it != m_queue.end(); ++it) { - long long const dx = it->key.first - m_focus.first; - long long const dy = it->key.second - m_focus.second; + // a tile of the map is that many tiles of the imagery a side + long long const scale = 1ll << it->map; + long long const dx = it->key.first * scale + scale / 2 - m_focus.first; + long long const dy = it->key.second * scale + scale / 2 - m_focus.second; long long const d = dx * dx + dy * dy; if (d < bestdistance) { @@ -1209,6 +1327,7 @@ void editor_orthophoto::worker() result out; out.key = current.key; + out.map = current.map; out.level = current.level; out.generation = current.generation; out.alpha = current.alpha; @@ -1216,11 +1335,11 @@ void editor_orthophoto::worker() image picture; std::string error; tile_request const request{current.key.first, current.key.second, current.level, current.src.year, current.src.hires}; - out.ok = load_tile(client, request, picture, out.empty, error, stopping); + out.ok = current.map > 0 ? load_map(client, current.map, current.key.first, current.key.second, picture, error, stopping) : load_tile(client, request, picture, out.empty, error, stopping); if (m_stop) return; if (!out.ok) - ErrorLog("Editor orthophoto: tile " + std::to_string(current.key.second) + "_" + std::to_string(current.key.first) + " failed: " + error); + ErrorLog(std::string("Editor orthophoto: ") + (current.map > 0 ? "map " + std::to_string(current.map) + " " : std::string{}) + "tile " + std::to_string(current.key.second) + "_" + std::to_string(current.key.first) + " failed: " + error); if (out.ok && current.alpha < 255) for (std::size_t i = 3; i < picture.rgba.size(); i += 4) picture.rgba[i] = static_cast((picture.rgba[i] * current.alpha + 127u) / 255u); @@ -1235,7 +1354,14 @@ void editor_orthophoto::worker() void editor_orthophoto::draw(glm::mat4 const &ViewProjection, glm::dvec3 const &CameraPos, float ScreenWidth, float ScreenHeight) const { - if (!m_enabled || m_config.in_scene || m_config.opacity <= 0.0f || ScreenWidth <= 0.0f || ScreenHeight <= 0.0f) + if (!m_enabled || ScreenWidth <= 0.0f || ScreenHeight <= 0.0f) + return; + if (m_map_level > 0) + { + draw_map(ViewProjection, CameraPos, ScreenWidth, ScreenHeight); + return; + } + if (m_config.in_scene || m_config.opacity <= 0.0f) return; ImDrawList *drawlist = ImGui::GetBackgroundDrawList(ImGui::GetMainViewport()); @@ -1380,3 +1506,46 @@ void editor_orthophoto::draw(glm::mat4 const &ViewProjection, glm::dvec3 const & drawlist->PopTextureID(); } } + +void editor_orthophoto::draw_map(glm::mat4 const &ViewProjection, glm::dvec3 const &CameraPos, float ScreenWidth, float ScreenHeight) const +{ + // the map takes the place of the ground, opaque; the top view sees it straight down, a tile is one quad + ImDrawList *drawlist = ImGui::GetBackgroundDrawList(ImGui::GetMainViewport()); + auto const toscreen = [&](glm::vec4 const &Clip) { return ImVec2((Clip.x / Clip.w * 0.5f + 0.5f) * ScreenWidth, (0.5f - Clip.y / Clip.w * 0.5f) * ScreenHeight); }; + float constexpr guard{1.02f}; + for (auto const shown : {false, true}) + for (auto const &entry : m_maps) + { + if ((entry.first.first == m_map_level) != shown) + continue; + tile const &t = entry.second; + if (t.level == 0 || t.empty || t.slot.texture == null_handle) + continue; + std::size_t const textureid = GfxRenderer->Texture(t.slot.texture).get_id(); + if (textureid == 0 || textureid == static_cast(static_cast(-1))) + continue; + auto const size{tile_size * static_cast(1 << entry.first.first)}; + glm::dvec2 const corner{m_config.east - entry.first.second.first * size, (entry.first.second.second + 1) * size - m_config.north}; + auto const at = [&](double U, double V) { return ViewProjection * glm::vec4(glm::vec3(glm::dvec3(corner.x - U * size, CameraPos.y - 1.0, corner.y - V * size) - CameraPos), 1.0f); }; + clip_vertex buffer0[12] = {{at(0.0, 0.0), {0.0f, 0.0f}}, {at(1.0, 0.0), {1.0f, 0.0f}}, {at(1.0, 1.0), {1.0f, 1.0f}}, {at(0.0, 1.0), {0.0f, 1.0f}}}; + clip_vertex buffer1[12]; + int count = clip_polygon(buffer0, 4, buffer1, [&](glm::vec4 const &v) { return v.w * guard - v.x; }); + count = clip_polygon(buffer1, count, buffer0, [&](glm::vec4 const &v) { return v.w * guard + v.x; }); + count = clip_polygon(buffer0, count, buffer1, [&](glm::vec4 const &v) { return v.w * guard - v.y; }); + count = clip_polygon(buffer1, count, buffer0, [&](glm::vec4 const &v) { return v.w * guard + v.y; }); + if (count < 3) + continue; + drawlist->PushTextureID((ImTextureID)(intptr_t)(textureid)); + drawlist->PrimReserve((count - 2) * 3, count); + auto const base = static_cast(drawlist->_VtxCurrentIdx); + for (int k = 1; k + 1 < count; ++k) + { + drawlist->PrimWriteIdx(base); + drawlist->PrimWriteIdx(static_cast(base + k)); + drawlist->PrimWriteIdx(static_cast(base + k + 1)); + } + for (int k = 0; k < count; ++k) + drawlist->PrimWriteVtx(toscreen(buffer0[k].position), ImVec2(buffer0[k].uv.x, buffer0[k].uv.y), IM_COL32_WHITE); + drawlist->PopTextureID(); + } +} diff --git a/editor/editorOrthophoto.hpp b/editor/editorOrthophoto.hpp index 1a808d04..32f5156c 100644 --- a/editor/editorOrthophoto.hpp +++ b/editor/editorOrthophoto.hpp @@ -106,6 +106,10 @@ class editor_orthophoto // true for geometry created by the layer, so ground queries can skip it static bool owns(gfx::geometry_handle const &Geometry); + // a top view of Scale metres a pixel shows the base map of OpenStreetMap in place of the imagery, as far as Reach from the + // camera; 0: the imagery + void overview(double const Scale, double const Reach); + bool overview() const { return m_map_level > 0; } // main thread, once per frame: schedules tiles around the camera and uploads finished ones void update(glm::dvec3 const &Camera); // main thread, inside an ImGui frame: emits the overlay into the background draw list. @@ -135,6 +139,7 @@ class editor_orthophoto struct job { tile_key key; + int map{0}; // level of a tile of the map, 256 m << map a side; 0: of the imagery int level{0}; // requested texture size in pixels std::uint8_t alpha{255}; // baked into the texture, for translucency in the scene source src; @@ -144,6 +149,7 @@ class editor_orthophoto struct result { tile_key key; + int map{0}; int level{0}; std::uint8_t alpha{255}; std::uint32_t generation{0}; @@ -188,7 +194,7 @@ class editor_orthophoto static int level_for(int Distance, bool Hires); tile_key camera_tile(glm::dvec3 const &Camera) const; glm::dvec2 tile_corner(tile_key const &Key) const; // world (x,z) of the north-west corner - void schedule(tile_key const &Key, tile &Tile, int Level, std::uint8_t Alpha); + void schedule(tile_key const &Key, tile &Tile, int Level, std::uint8_t Alpha, int const Map = 0); std::uint8_t texture_alpha() const; // alpha the textures should carry with the current settings std::size_t apply(result &Result, double Now); // returns bytes uploaded void release_tile(tile &Tile); @@ -198,6 +204,8 @@ class editor_orthophoto void remove_shape(tile &Tile); void release_geometry(tile &Tile); void update_scene(); + void update_map(glm::dvec3 const &Camera, double const Now); + void draw_map(glm::mat4 const &ViewProjection, glm::dvec3 const &CameraPos, float ScreenWidth, float ScreenHeight) const; void start_workers(); void worker(); @@ -205,6 +213,10 @@ class editor_orthophoto bool m_enabled{false}; std::map m_tiles; // main thread only tile_key m_camera_tile{0, 0}; + using map_key = std::pair; // level, then the place on the grid of the tiles of that level + std::map m_maps; // main thread only + int m_map_level{0}; // of the map shown, 0: the imagery + double m_map_reach{0.0}; // m from the camera the view of the map reaches std::vector m_free_slots; // pool of textures, reused between tiles int m_texture_count{0}; ground_query m_ground; diff --git a/editor/editorSettings.hpp b/editor/editorSettings.hpp index de7628b8..e0e9f001 100644 --- a/editor/editorSettings.hpp +++ b/editor/editorSettings.hpp @@ -90,7 +90,7 @@ private: bool m_terrain_offer{true}; bool m_track_tools_more{false}; int m_top_tracks{0}; // the railway in the top view: whole, without the ballast, the rails alone - std::uint32_t m_top_rails{0xffd246ff}; // colour of the rails alone, as ImGui keeps it: ABGR + std::uint32_t m_top_rails{0xffff6e28}; // colour of the rails alone, as ImGui keeps it: ABGR track_norms m_norms{track_norms::ask}; }; diff --git a/editor/editorTerrainStreamer.cpp b/editor/editorTerrainStreamer.cpp index 91067226..feec0a1a 100644 --- a/editor/editorTerrainStreamer.cpp +++ b/editor/editorTerrainStreamer.cpp @@ -598,7 +598,7 @@ void terrain_streamer::update(glm::dvec3 const &Camera) } // 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 top{Global.EditorOrtho && Global.editor_tracks && false == Global.EditorOrthoMap}; 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; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index d57b4c01..c3d704cf 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -11499,3 +11499,6 @@ msgstr "Jak widok z góry rysuje tory" msgid "The colour of the rails, to stand out on the imagery and the map" msgstr "Kolor szyn, żeby odcinały się od ortofotomapy i mapy" + +msgid "(c) OpenStreetMap contributors, geoportal.gov.pl" +msgstr "(c) współtwórcy OpenStreetMap, geoportal.gov.pl" diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 5d89a8bb..3d6a3004 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -2649,7 +2649,8 @@ void opengl33_renderer::Render(section_sequence::iterator First, section_sequenc case rendermode::shadows: case rendermode::pickscenery: { - if (false == section->m_shapes.empty()) + // the top view of the editor far out has the map in place of the ground + if (false == section->m_shapes.empty() && false == (EditorModeFlag && Global.EditorOrtho && Global.EditorOrthoMap)) { // since all shapes of the section share center point we can optimize out a few calls here ::glPushMatrix(); diff --git a/utilities/Globals.h b/utilities/Globals.h index 36be95b6..50c5118a 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -175,6 +175,7 @@ struct global_settings { bool EditorOrtho{ false }; float EditorOrthoExtent{ 150.f }; int EditorOrthoTracks{ 0 }; // the railway in the top view of the editor: 0 whole, 1 without the ballast, 2 the rails alone + bool EditorOrthoMap{ false }; // the top view of the editor far out shows the base map in place of the ground, which isn't drawn then 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