diff --git a/application/editormode.cpp b/application/editormode.cpp index f3a145fc..29a3ca72 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -558,6 +558,21 @@ editor_mode::editor_mode() { gather_ground_triangles(Min, Max, true, 50.0f, Out, true, Heightmap); }); m_orthophoto.height_source([](double const X, double const Z, double &Height) { return EditorTerrain.height_at(X, Z, Height); }); + // the imagery often comes before the terrain made from the model: it lies where the terrain will be + m_orthophoto.guess_source([this](double const X, double const Z, double &Height) { + if (nmt_ahead(X, Z, Height)) + return true; + if (false == m_nmt.placed()) + return false; + if (false == m_nmt.height(X, Z, Height)) + { + // asked for, the tile goes onto it once it comes + m_nmt.request({X - 1.0, Z - 1.0}, {X + 1.0, Z + 1.0}); + return false; + } + Height += nmt_offset_at(X, Z); + return true; + }); editor_track::observe(this); } @@ -1481,6 +1496,14 @@ bool editor_mode::update() 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)); } + // chunks of the terrain come from the files and from the model: the imagery lying at guessed heights goes onto them + // and so do the squares of the model it lies on meanwhile + auto const resident{m_streamer.resident()}; + auto const waiting{m_nmt.waiting()}; + if (resident > m_ortho_resident || waiting < m_ortho_waiting) + m_orthophoto.ground_arrived(); + m_ortho_resident = resident; + m_ortho_waiting = waiting; m_orthophoto.update(Camera.Pos); nmt_update(); draw_orthophoto(); diff --git a/application/editormode.h b/application/editormode.h index d55ffc09..a4f9d060 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -685,6 +685,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri void load_orthophoto_settings(); void save_orthophoto_settings(); editor_orthophoto m_orthophoto; + std::size_t m_ortho_resident{0}; // chunks of the terrain in memory, as the imagery last heard of them + std::size_t m_ortho_waiting{0}; // squares of the terrain model asked for and not in yet, the same std::string m_orthophoto_scenery; // scenery whose origin is loaded std::string m_orthophoto_mark; glm::dvec2 m_orthophoto_origin_edit{0.0}; // origin being typed in (northing, easting); applied when editing ends diff --git a/application/editormodeselftestscene.cpp b/application/editormodeselftestscene.cpp index f670abd1..b939d597 100644 --- a/application/editormodeselftestscene.cpp +++ b/application/editormodeselftestscene.cpp @@ -292,7 +292,10 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm if (command == "mapstate") { auto const marked{scene::Layers.empty() ? std::vector{} : scene::Layers.marked(scene::layer_handle{1}, "//$o")}; - WriteLog(format("SELFTEST mapstate orthophoto %d top %d extent %.0f mark %s camera %.1f %.1f %.1f", m_orthophoto.enabled() ? 1 : 0, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent, marked.empty() ? "-" : marked.front().c_str(), Camera.Pos.x, Camera.Pos.y, Camera.Pos.z)); + WriteLog(format("SELFTEST mapstate orthophoto %d top %d extent %.0f camera %.1f %.1f %.1f", m_orthophoto.enabled() ? 1 : 0, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent, Camera.Pos.x, Camera.Pos.y, Camera.Pos.z)); + WriteLog("SELFTEST mapstate mark " + (marked.empty() ? std::string{"-"} : marked.front())); + auto const [grounded, guessed]{m_orthophoto.grounded()}; + WriteLog(format("SELFTEST mapstate tiles on the ground %zu, at guessed heights %zu", grounded, guessed)); return selftest_result::handled; } if (command == "selectmodel") diff --git a/editor/editorOrthophoto.cpp b/editor/editorOrthophoto.cpp index cfd024d3..c7350f15 100644 --- a/editor/editorOrthophoto.cpp +++ b/editor/editorOrthophoto.cpp @@ -617,6 +617,27 @@ void editor_orthophoto::refit() entry.second.grounded = false; } +void editor_orthophoto::ground_arrived() +{ + if (!m_height) + return; + for (auto &entry : m_tiles) + { + auto &t{entry.second}; + if (!t.grounded || !t.guessed) + continue; + // the middle and the corners: a chunk of the terrain is much larger than a tile, it covers some of these when it comes. A tile + // lying flat, with nothing to go by, also goes onto the model once it's in + glm::dvec2 const corner = tile_corner(entry.first); + for (auto const &[u, v] : {std::pair{0.5, 0.5}, std::pair{0.0, 0.0}, std::pair{1.0, 0.0}, std::pair{0.0, 1.0}, std::pair{1.0, 1.0}}) + if (double y; m_height(corner.x - u * tile_size, corner.y - v * tile_size, y) || (t.heights.empty() && m_guess && m_guess(corner.x - u * tile_size, corner.y - v * tile_size, y))) + { + t.grounded = false; + break; + } + } +} + void editor_orthophoto::detach_scene() { for (auto &entry : m_tiles) @@ -819,9 +840,46 @@ std::size_t editor_orthophoto::apply(result &Result, double Now) return Result.pixels.size(); } +std::pair editor_orthophoto::grounded() const +{ + std::pair result{0, 0}; + for (auto const &entry : m_tiles) + if (entry.second.grounded && false == entry.second.heights.empty()) + { + ++result.first; + if (entry.second.guessed) + ++result.second; + } + 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})}; + if (key == Own) + return false; + auto const found{m_tiles.find(key)}; + if (found == m_tiles.end() || !found->second.grounded || found->second.guessed || found->second.heights.empty()) + return false; + auto const &heights{found->second.heights}; + int constexpr samples{ground_grid + 1}; + double constexpr step{tile_size / ground_grid}; + glm::dvec2 const corner = tile_corner(key); + auto const u{std::clamp((corner.x - X) / step, 0.0, static_cast(ground_grid))}; + auto const v{std::clamp((corner.y - Z) / step, 0.0, static_cast(ground_grid))}; + auto const i{std::min(static_cast(u), ground_grid - 1)}; + auto const j{std::min(static_cast(v), ground_grid - 1)}; + auto const at = [&](int const I, int const J) { return static_cast(heights[static_cast(J) * samples + I]); }; + auto const fu{u - i}; + auto const fv{v - j}; + Height = static_cast((at(i, j) * (1.0 - fu) + at(i + 1, j) * fu) * (1.0 - fv) + (at(i, j + 1) * (1.0 - fu) + at(i + 1, j + 1) * fu) * fv); + return true; +} + void editor_orthophoto::sample_ground(tile_key const &Key, tile &Tile) { Tile.grounded = true; + Tile.guessed = false; Tile.scene_dirty = true; Tile.heights.clear(); Tile.surface.clear(); @@ -899,7 +957,32 @@ void editor_orthophoto::sample_ground(tile_key const &Key, tile &Tile) // holes (places without ground) take the average of their neighbours, spreading inwards std::size_t missing = std::count(heights.begin(), heights.end(), no_height); if (missing == heights.size()) - return; // no ground at all: the tile stays flat at the layer height + { + // no ground at all yet: the heights of the terrain model it waits for, else those of the tiles beside along its edges + for (int j = 0; j < samples; ++j) + for (int i = 0; i < samples; ++i) + if (double y; m_guess && m_guess(corner.x - i * step, corner.y - j * step, y)) + heights[static_cast(j) * samples + i] = static_cast(y); + if (std::count(heights.begin(), heights.end(), no_height) == static_cast(heights.size())) + { + double constexpr outward{0.01}; + for (int j = 0; j < samples; ++j) + for (int i = 0; i < samples; ++i) + { + if (i != 0 && i != ground_grid && j != 0 && j != ground_grid) + continue; + auto const x{corner.x - i * step + (i == 0 ? outward : i == ground_grid ? -outward : 0.0)}; + auto const z{corner.y - j * step + (j == 0 ? outward : j == ground_grid ? -outward : 0.0)}; + if (float y; sampled_height(Key, x, z, y)) + heights[static_cast(j) * samples + i] = y; + } + } + // nothing to go by yet: the tile stays flat at the layer height, and is sampled again when something comes + Tile.guessed = true; + missing = std::count(heights.begin(), heights.end(), no_height); + if (missing == heights.size()) + return; + } while (missing > 0) { std::vector next = heights; diff --git a/editor/editorOrthophoto.hpp b/editor/editorOrthophoto.hpp index 32f5156c..67626cb6 100644 --- a/editor/editorOrthophoto.hpp +++ b/editor/editorOrthophoto.hpp @@ -99,8 +99,15 @@ class editor_orthophoto void settings(config const &Config); void ground_source(ground_query Query) { m_ground = std::move(Query); } void height_source(height_query Query) { m_height = std::move(Query); } + // the heights the ground will have where there's none yet, as of the terrain model on its way: a tile laid out before its + // ground comes lies there, or level with the tiles beside, rather than at the height of the layer far below or above + void guess_source(height_query Query) { m_guess = std::move(Query); } // samples the ground again (after it was edited); tiles are refitted progressively void refit(); + // the tiles lying at guessed heights sampled again where the ground has come under them + void ground_arrived(); + // the tiles with the ground sampled under them, and of these the ones at guessed heights + std::pair grounded() 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 @@ -183,6 +190,7 @@ class editor_orthophoto float height_min{0.0f}; float height_max{0.0f}; std::vector surface; // upward facing ground triangles clipped to the tile (3 vertices each) + bool guessed{false}; // no ground under it when sampled: the heights guessed, from the terrain model or the tiles beside // scene geometry scene::basic_section *section{nullptr}; // section holding the shape (nullptr = not in the scene) scene::basic_cell *cell{nullptr}; // set instead of the section's shape list when the shape is translucent @@ -200,6 +208,8 @@ class editor_orthophoto void release_tile(tile &Tile); texture_slot acquire_slot(); void sample_ground(tile_key const &Key, tile &Tile); + // the height of the ground sampled under a tile beside Own at the point, the tiles with guessed heights left out + bool sampled_height(tile_key const &Own, double const X, double const Z, float &Height) const; void build_shape(tile_key const &Key, tile &Tile); void remove_shape(tile &Tile); void release_geometry(tile &Tile); @@ -221,6 +231,7 @@ class editor_orthophoto int m_texture_count{0}; ground_query m_ground; height_query m_height; + height_query m_guess; scene::basic_region *m_region{nullptr}; // region the scene geometry was put into std::multimap m_free_chunks; // geometry chunks by capacity, reused between tiles // the scene opacity is baked into the textures, which means reading the tiles again; applied once the value settles