mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 17:04:53 +01:00
fix(editor): the orthophoto which comes before the terrain under it lies where the terrain will be, at the heights of the terrain model with the offset of the scenery, or level with the tiles beside along its edges, not flat at the height of the layer far below; the squares of the model asked for, and the tile laid onto the terrain or the model once either comes
mapstate logs how many tiles lie on the ground and how many at guessed heights.
This commit is contained in:
@@ -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<double>(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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<std::string>{} : 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")
|
||||
|
||||
@@ -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<std::size_t, std::size_t> editor_orthophoto::grounded() const
|
||||
{
|
||||
std::pair<std::size_t, std::size_t> 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<double>(ground_grid))};
|
||||
auto const v{std::clamp((corner.y - Z) / step, 0.0, static_cast<double>(ground_grid))};
|
||||
auto const i{std::min(static_cast<int>(u), ground_grid - 1)};
|
||||
auto const j{std::min(static_cast<int>(v), ground_grid - 1)};
|
||||
auto const at = [&](int const I, int const J) { return static_cast<double>(heights[static_cast<std::size_t>(J) * samples + I]); };
|
||||
auto const fu{u - i};
|
||||
auto const fv{v - j};
|
||||
Height = static_cast<float>((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<std::size_t>(j) * samples + i] = static_cast<float>(y);
|
||||
if (std::count(heights.begin(), heights.end(), no_height) == static_cast<std::ptrdiff_t>(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<std::size_t>(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<float> next = heights;
|
||||
|
||||
@@ -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<std::size_t, std::size_t> 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<glm::dvec3> 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<std::size_t, gfx::geometry_handle> 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
|
||||
|
||||
Reference in New Issue
Block a user