17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 18:14:55 +01:00

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

This commit is contained in:
maj00r
2026-10-23 13:35:07 +02:00
parent a892532fda
commit 5e846018b6
10 changed files with 229 additions and 22 deletions

View File

@@ -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<double>(Global.EditorOrthoExtent) / io.DisplaySize.y : 0.0};
auto const aspect{io.DisplaySize.y > 0.0f ? static_cast<double>(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<double>(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

View File

@@ -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<double>(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)

View File

@@ -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()};

View File

@@ -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<long long>(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<std::uint8_t> 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<std::size_t>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>(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<std::mutex> 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<double>(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<double>(1 << m_map_level)};
auto complete{true};
for (int iy = static_cast<int>(std::floor((north - m_map_reach) / size)); iy <= static_cast<int>(std::floor((north + m_map_reach) / size)); ++iy)
for (int ix = static_cast<int>(std::floor((east - m_map_reach) / size)); ix <= static_cast<int>(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<std::mutex> 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<std::uint8_t>((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<std::size_t>(static_cast<std::uint32_t>(-1)))
continue;
auto const size{tile_size * static_cast<double>(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<ImDrawIdx>(drawlist->_VtxCurrentIdx);
for (int k = 1; k + 1 < count; ++k)
{
drawlist->PrimWriteIdx(base);
drawlist->PrimWriteIdx(static_cast<ImDrawIdx>(base + k));
drawlist->PrimWriteIdx(static_cast<ImDrawIdx>(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();
}
}

View File

@@ -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<tile_key, tile> m_tiles; // main thread only
tile_key m_camera_tile{0, 0};
using map_key = std::pair<int, tile_key>; // level, then the place on the grid of the tiles of that level
std::map<map_key, tile> 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<texture_slot> m_free_slots; // pool of textures, reused between tiles
int m_texture_count{0};
ground_query m_ground;

View File

@@ -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};
};

View File

@@ -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<double>(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;

View File

@@ -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"

View File

@@ -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();

View File

@@ -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