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