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