From 2ae10f590144dd9b9260b624b2f7d6b8ccb8bb68 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 24 Oct 2026 18:31:24 +0200 Subject: [PATCH] feat(editor): the chunks waiting for the terrain model and the tiles of the orthophoto asked for and not shown yet outlined on the ground where they will be, pulsing, blue and orange, named where the name fits: at the heights of the terrain beside them or of the model, else level with the nearest chunk or track --- application/editormode.cpp | 1 + application/editormode.h | 3 ++ application/editormodenmt.cpp | 94 +++++++++++++++++++++++++++++++++++ editor/editorOrthophoto.cpp | 14 ++++++ editor/editorOrthophoto.hpp | 2 + lang/editor_pl.po | 6 +++ 6 files changed, 120 insertions(+) diff --git a/application/editormode.cpp b/application/editormode.cpp index 29a3ca72..019b0809 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1507,6 +1507,7 @@ bool editor_mode::update() m_orthophoto.update(Camera.Pos); nmt_update(); draw_orthophoto(); + draw_nmt_pending(); // --- ImGuizmo: in-viewport transform gizmo for the selected node --- render_gizmo(); diff --git a/application/editormode.h b/application/editormode.h index 5b3df340..6f90aacf 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -451,6 +451,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri bool render_nmt_place(bool const Moving, bool const Button); // the work under way: what's left of it and the button to stop it; Compact: one line, for the 3d view void render_nmt_progress(bool const Compact); + // the chunks waiting for the terrain model and the tiles of the imagery asked for outlined where they will be, so it's seen + // what's on its way + void draw_nmt_pending() const; // over the 3d view while the terrain panel isn't seen void render_nmt_banner(); bool nmt_busy() const; diff --git a/application/editormodenmt.cpp b/application/editormodenmt.cpp index ebf76778..782bc703 100644 --- a/application/editormodenmt.cpp +++ b/application/editormodenmt.cpp @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include "application/editormode.h" #include "application/editormodeshared.h" #include "application/editoruilayer.h" +#include "application/editorprojection.h" #include "editor/editorFormat.hpp" #include "editor/editorGeometry.hpp" #include "editor/editorSettings.hpp" @@ -29,6 +30,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include #include #include @@ -1141,6 +1143,98 @@ void editor_mode::render_nmt_progress(bool const Compact) item_tooltip("Stops loading: the terrain made so far stays"); } +void editor_mode::draw_nmt_pending() const +{ + auto const &state{m_nmt_state}; + auto const imagery{m_orthophoto.waiting()}; + if (state.pending.empty() && imagery.empty()) + return; + // where the ground will be: by the terrain there or beside it, else the model with the offset of the scenery, else level + // with the nearest chunk there is, or the nearest track + glm::dvec2 const camera{Camera.Pos.x, Camera.Pos.z}; + double fallback{0.0}; + { + auto nearest{std::numeric_limits::max()}; + std::vector chunks; + m_streamer.collect(chunks); + for (auto const *chunk : chunks) + if (auto const middle{chunk->centre()}; glm::distance(glm::dvec2{middle.x, middle.z}, camera) < nearest) + { + nearest = glm::distance(glm::dvec2{middle.x, middle.z}, camera); + fallback = middle.y; + } + if (chunks.empty()) + for (auto const *track : simulation::Paths.sequence()) + if (track != nullptr && false == track->m_paths.empty()) + if (auto const &point{track->m_paths.front().points[segment_data::point::start]}; glm::distance(geometry::plan_of(point), camera) < nearest) + { + nearest = glm::distance(geometry::plan_of(point), camera); + fallback = point.y; + } + } + auto const height = [&](double const X, double const Z, double &Height) { + if (m_streamer.height_at(X, Z, Height) || nmt_ahead(X, Z, Height)) + return true; + if (m_nmt.placed() && m_nmt.height(X, Z, Height)) + { + Height += nmt_offset_at(X, Z); + return true; + } + Height = fallback; + return true; + }; + screen_projection const projection; + auto *drawlist{ImGui::GetBackgroundDrawList()}; + auto const time{ImGui::GetTime()}; + auto const pulse{static_cast(0.5 + 0.5 * std::sin(time * 4.0))}; + auto const dots{std::string(static_cast(time * 3.0) % 4, '.')}; + int constexpr steps{8}; // points along each edge, the outline following the ground + auto const square = [&](glm::dvec2 const &Low, glm::dvec2 const &High, int const Red, int const Green, int const Blue, std::string const &Label) { + auto const size{High - Low}; + std::vector outline; + for (int side = 0; side < 4; ++side) + for (int i = 0; i < steps; ++i) + { + auto const along{static_cast(i) / steps}; + glm::dvec2 const point{side == 0 ? Low + glm::dvec2{size.x * along, 0.0} : side == 1 ? Low + glm::dvec2{size.x, size.y * along} : side == 2 ? High - glm::dvec2{size.x * along, 0.0} : High - glm::dvec2{size.x, size.y * along}}; + double y; + ImVec2 screen; + if (false == height(point.x, point.y, y) || false == projection.project({point.x, y + 0.5, point.y}, screen)) + return; + outline.push_back(screen); + } + auto const edge{IM_COL32(Red, Green, Blue, static_cast(140.0f + 100.0f * pulse))}; + drawlist->AddConvexPolyFilled(outline.data(), static_cast(outline.size()), IM_COL32(Red, Green, Blue, static_cast(25.0f + 35.0f * pulse))); + drawlist->AddPolyline(outline.data(), static_cast(outline.size()), edge, ImDrawFlags_Closed, 2.0f); + // named where the name fits in + auto const middle{(Low + High) * 0.5}; + auto const text{Label + dots}; + auto const extent{ImGui::CalcTextSize(text.c_str())}; + auto const [left, right]{std::minmax_element(outline.begin(), outline.end(), [](ImVec2 const &A, ImVec2 const &B) { return A.x < B.x; })}; + double y; + ImVec2 label; + if (right->x - left->x > extent.x * 1.1f && height(middle.x, middle.y, y) && projection.project({middle.x, y + 0.5, middle.y}, label)) + drawlist->AddText({label.x - extent.x * 0.5f, label.y - extent.y * 0.5f}, edge, text.c_str()); + }; + // the nearest first, as many as can be drawn without the frame feeling it + auto const nearest = [&](std::vector> Areas, std::size_t const Most) { + auto const away = [&](std::pair const &Area) { return glm::distance((Area.first + Area.second) * 0.5, camera); }; + if (Areas.size() > Most) + { + std::nth_element(Areas.begin(), Areas.begin() + Most, Areas.end(), [&](auto const &A, auto const &B) { return away(A) < away(B); }); + Areas.resize(Most); + } + return Areas; + }; + for (auto const &[low, high] : nearest(imagery, 64)) + square(low, high, 255, 190, 70, STR("Orthophoto loading")); + std::vector> chunks; + for (auto const &key : state.pending) + chunks.emplace_back(heightmap::chunk_corner(key), heightmap::chunk_corner(key) + glm::dvec2{heightmap::chunk_size}); + for (auto const &[low, high] : nearest(chunks, 64)) + square(low, high, 90, 170, 255, STR("Terrain model loading")); +} + void editor_mode::render_nmt_banner() { auto const &state{m_nmt_state}; diff --git a/editor/editorOrthophoto.cpp b/editor/editorOrthophoto.cpp index c7350f15..b8118a91 100644 --- a/editor/editorOrthophoto.cpp +++ b/editor/editorOrthophoto.cpp @@ -853,6 +853,20 @@ std::pair editor_orthophoto::grounded() const return result; } +std::vector> editor_orthophoto::waiting() const +{ + std::vector> result; + if (false == m_enabled || m_map_level > 0) + return result; + for (auto const &entry : m_tiles) + if (entry.second.level == 0 && entry.second.requested > 0 && false == entry.second.empty) + { + glm::dvec2 const corner = tile_corner(entry.first); + result.emplace_back(corner - glm::dvec2{tile_size}, corner); + } + return result; +} + bool editor_orthophoto::sampled_height(tile_key const &Own, double const X, double const Z, float &Height) const { auto const key{camera_tile({X, 0.0, Z})}; diff --git a/editor/editorOrthophoto.hpp b/editor/editorOrthophoto.hpp index 67626cb6..c0fbf15a 100644 --- a/editor/editorOrthophoto.hpp +++ b/editor/editorOrthophoto.hpp @@ -108,6 +108,8 @@ class editor_orthophoto void ground_arrived(); // the tiles with the ground sampled under them, and of these the ones at guessed heights std::pair grounded() const; + // the rectangles (x, z) of the tiles of the imagery asked for and not shown yet + std::vector> waiting() const; // takes the scene geometry out of the region (e.g. when the editor is left); it's re-created on the next update void detach_scene(); // true for geometry created by the layer, so ground queries can skip it diff --git a/lang/editor_pl.po b/lang/editor_pl.po index fd7068ca..d6a3a7b6 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -6725,6 +6725,12 @@ msgstr "Niwelety: dopasowano %zu, zostało %zu torów" msgid "Chunks: %zu of %zu" msgstr "Kafle: %zu z %zu" +msgid "Terrain model loading" +msgstr "Wczytywanie NMT" + +msgid "Orthophoto loading" +msgstr "Wczytywanie ortofotomapy" + msgid "By hand: the model as it is, the tracks stay" msgstr "Ręcznie: NMT bez zmian, tory zostają"