17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 20:29:04 +01:00
Files
maszyna/editor/editorOrthophoto.hpp
maj00r d6df785b78 feat(editor): the download area drawn on the topo map: a polygon is the boundary of what the editor downloads, the orthophoto and the terrain model alike. Drawn from the settings of the orthophoto (Download area, Draw on the map, the top view on): a click puts a point, a point dragged moves, Shift+click removes it, Backspace takes the last one back, Enter closes the area, Esc gives up; it's kept in the scenery as //$a lines in PUWG 1992, so it stays on the ground when the origin moves. Inside it everything the settings ask for, the standard imagery, the 4K one where it's ticked and covers the place, the squares of the terrain model where the scenery has its terrain, goes to the disk cache in the background, nothing decoded nor uploaded, only while no tile around the camera waits and not while the scenery is driven, with its progress, Pause and Retry under it; outside it nothing is asked for, around the camera, along the tracks or ahead of the laying, nor shown. The workers of the orthophoto are 8, at most 3 of them on the tiles around the camera, each holding an image decoded, the rest downloading the area. The 4K tiles: an image the tile no longer wants once it comes, as a 4K one asked for before the camera moved on, isn't uploaded (the 64 MB and the mipmaps of it), nor decoded where a newer job of the tile waits; no two jobs of one tile run at once; a standard tile coming blank twice is remembered by a .blank file, not downloaded again on each return; a 4K tile is uploaded only in a frame with no other upload. Selftests orthoarea, orthostats, orthohires, orthoareapause; unit tests of the tiles a triangle, a concave polygon, a thin band and a polygon about a corner touch (geometry::squares_in, square_touches)
2026-10-26 18:58:09 +01:00

302 lines
14 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <array>
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <glm/glm.hpp>
#include "interfaces/ITexture.h" // texture_handle
#include "utilities/Classes.h" // material_handle
#include "rendering/geometrybank.h" // gfx::geometry_handle
bool editor_http_get(std::string const &Url, std::vector<std::uint8_t> &Out, std::string &Error);
namespace scene
{
class basic_region;
class basic_section;
class basic_cell;
} // namespace scene
// Editor aid: aerial orthophoto tiles from geoportal.gov.pl (GUGiK WMS), shown around the camera either
// on a horizontal plane or fitted onto the terrain.
//
// Tiles are aligned to an absolute 256 m grid in PUWG 1992 (EPSG:2180), so the disk cache is shared
// between sceneries. The scenery is placed on that grid through the PUWG coordinates of its (0,0,0)
// point; MaSzyna's +Z points north and +X points west, hence easting = E0 - x, northing = N0 + z.
//
// Downloading, cache I/O and image decoding run on worker threads; the main thread only uploads
// finished images (a bounded amount per frame) and emits the geometry. By default the layer goes into
// the ImGui background draw list, like the other editor viewport overlays: translucent and drawn over
// everything. Optionally it is put into the scene instead (shapes in the region sections, like the editor
// terrain, or translucent shapes in the cells when it isn't fully opaque), so tracks, models and terrain in
// front of it cover it.
class editor_orthophoto
{
public:
struct config
{
double north{0.0}; // PUWG 1992 X (northing) of the scenery origin
double east{0.0}; // PUWG 1992 Y (easting) of the scenery origin
int radius{2}; // tiles loaded around the camera tile (Chebyshev distance)
float height{0.0f}; // world Y of the layer (with drape: only where no ground was found)
float opacity{0.6f}; // 0..1
int year{0}; // 0 = newest available imagery, otherwise newest imagery taken up to the end of that year
bool hires{false}; // 4096 px tiles from the high-resolution product next to the camera, where it exists
bool drape{true}; // fit the imagery onto the ground geometry instead of a flat plane
float lift{0.05f}; // with drape: distance above the ground (in the scene it has to clear the ground to be seen)
bool in_scene{false}; // render as opaque scene geometry, covered by whatever is in front of it
};
using world_triangle = std::array<glm::dvec3, 3>;
// supplies the ground triangles overlapping an XZ rectangle (Min/Max are x,z); used to drape the imagery.
// Heightmap: the heightmap terrain as triangles too; without it the terrain is asked through the height query alone
using ground_query = std::function<void(glm::dvec2 const &Min, glm::dvec2 const &Max, bool Heightmap, std::vector<world_triangle> &Out)>;
// height of the heightmap terrain at a point, false where there's none
using height_query = std::function<bool(double X, double Z, double &Height)>;
struct statistics
{
int resident{0}; // tiles with imagery on screen
int loading{0}; // tiles waiting for or being processed by a worker
int failed{0}; // tiles whose last attempt failed
// the download area: its tiles, the ones in the disk cache in every product the settings ask for, the ones which failed
int area_tiles{0};
int area_cached{0};
int area_failed{0};
bool area_paused{false};
int requests{0}; // asked of the services since the start, the disk cache left out
int uploads{0}; // images of tiles sent to the graphics card
int stale{0}; // images which came when the tile no longer wanted them, dropped
};
static constexpr double tile_size{256.0}; // metres per tile side
static constexpr int max_radius{10};
static constexpr int oldest_year{1995}; // first year offered by the archival service
editor_orthophoto() = default;
~editor_orthophoto();
editor_orthophoto(editor_orthophoto const &) = delete;
editor_orthophoto &operator=(editor_orthophoto const &) = delete;
bool enabled() const { return m_enabled; }
// disabling drops the queue and returns the gpu memory of the resident tiles
void enabled(bool State);
config const &settings() const { return m_config; }
static std::string mark(config const &Config, bool Enabled, bool Origin);
static bool read_mark(std::string const &Text, config &Config, bool &Enabled);
// changes of year / resolution invalidate resident tiles, the rest applies immediately
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); }
// the area the imagery is downloaded in, a polygon of PUWG 1992 (easting, northing): its tiles go to the disk cache in the
// background, the products the settings ask for, and no tile outside it is asked for around the camera. Empty: no area, the
// tiles around the camera as before
void area(std::vector<glm::dvec2> Polygon);
std::vector<glm::dvec2> const &area() const { return m_area; }
bool in_area(double const East, double const North) const;
// the download in the background held, or going on
void area_paused(bool const Paused);
// the squares of the tiles of the area in the world (x, z): 0 waiting, 1 in the disk cache, 2 failed
struct area_square
{
glm::dvec2 low{0.0};
glm::dvec2 high{0.0};
int state{0};
};
std::vector<area_square> area_squares() const;
// world (x, z) and PUWG 1992 (easting, northing), by the origin of the scenery
glm::dvec2 to_puwg(glm::dvec2 const &World) const { return {m_config.east - World.x, m_config.north + World.y}; }
glm::dvec2 to_world(glm::dvec2 const &Puwg) const { return {m_config.east - Puwg.x, Puwg.y - m_config.north}; }
// the area kept in the scenery, as the lines after its mark
static std::vector<std::string> area_lines(std::vector<glm::dvec2> const &Polygon);
static std::vector<glm::dvec2> read_area(std::vector<std::string> const &Lines);
// 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;
// the rectangles (x, z) of the tiles of the imagery asked for and not shown yet
std::vector<std::pair<glm::dvec2, glm::dvec2>> 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
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.
// ViewProjection must map camera-relative world positions (world - CameraPos) to clip space.
void draw(glm::mat4 const &ViewProjection, glm::dvec3 const &CameraPos, float ScreenWidth, float ScreenHeight) const;
// forgets pending (not yet started) downloads, e.g. when the editor is left
void cancel_pending();
// drops resident tiles and deletes the on-disk cache; enabled tiles are fetched again
void clear_cache();
// clears failure state so failed tiles are attempted again
void retry_failed();
statistics stats() const;
// false when the build has no http client; cached tiles are still shown
static bool can_download();
static std::string cache_directory();
private:
using tile_key = std::pair<int, int>; // (easting index, northing index) on the PUWG tile grid
struct source
{
int year{0};
bool hires{false};
};
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;
std::uint32_t generation{0};
};
struct result
{
tile_key key;
int map{0};
int level{0};
std::uint8_t alpha{255};
std::uint32_t generation{0};
bool ok{false};
bool empty{false}; // no imagery for this place/year: nothing to draw
int width{0};
int height{0};
std::vector<std::uint8_t> pixels; // RGBA8, first row = north edge
};
struct texture_slot
{
int index{-1}; // names the texture and its material
texture_handle texture{null_handle};
material_handle material{null_handle}; // created on first use by the scene geometry
};
struct tile
{
texture_slot slot;
int level{0}; // size of the imagery currently held (0 = nothing yet)
int requested{0}; // level of the job scheduled for this tile (0 = none)
std::uint8_t alpha{255}; // alpha of the imagery currently held
std::uint8_t requested_alpha{255}; // alpha of the scheduled job
bool empty{false};
int failures{0};
double retry_at{0.0}; // steady clock seconds
// ground fit
bool grounded{false}; // ground was sampled
std::vector<float> heights; // (ground_grid+1)^2 world Y, rows north->south, columns west->east; empty if no ground
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
gfx::geometry_handle geometry{0, 0};
std::size_t capacity{0}; // vertex count of the geometry chunk
bool scene_dirty{true}; // the shape needs to be rebuilt
};
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, 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);
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);
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();
// the tiles of the area queued for the download in the background again, nearest to the camera first
void restart_prefetch();
config m_config;
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;
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
std::uint8_t m_alpha{255}; // in effect
std::uint8_t m_alpha_pending{255}; // latest requested
double m_alpha_changed{0.0}; // when the requested value last changed
std::vector<glm::dvec2> m_area; // PUWG, empty: no area
std::set<tile_key> m_area_tiles; // the tiles it touches
int m_uploads{0};
int m_stale{0};
// shared with the workers, guarded by m_mutex
mutable std::mutex m_mutex;
std::condition_variable m_wakeup;
std::deque<job> m_queue;
std::deque<result> m_done;
tile_key m_focus{0, 0}; // workers pick the queued tile closest to it
std::set<std::pair<int, tile_key>> m_running; // (map, tile) a worker is on: another job of it waits till that one is done
// the download of the area in the background: the tiles nearest to the camera first, taken when no tile around the camera waits
std::vector<tile_key> m_prefetch;
std::size_t m_prefetch_next{0};
std::set<tile_key> m_prefetch_failed;
std::set<tile_key> m_prefetch_cached;
bool m_prefetch_paused{false};
source m_prefetch_source;
std::uint32_t m_prefetch_generation{0};
std::atomic<bool> m_stop{false};
std::atomic<std::uint32_t> m_generation{1};
std::vector<std::thread> m_workers;
};