/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 &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 // a WMS of the user's in place of the orthophoto of geoportal.gov.pl: its address and the layers asked of it in EPSG:2180, PNG // clear where it draws nothing. Its tiles are kept in memory only and asked for only around the camera, none ahead std::string service; std::string layers; }; using world_triangle = std::array; // 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 &Out)>; // height of the heightmap terrain at a point, false where there's none using height_query = std::function; 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; } // the line of the scenery (//$o): whether the imagery is on and, with Origin, where the scenery lies; the rest of the settings // are the user's 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 Polygon); std::vector const &area() const { return m_area; } // 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_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 area_lines(std::vector const &Polygon); static std::vector read_area(std::vector 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 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 static bool owns(gfx::geometry_handle const &Geometry); // 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(); // what a WMS tells of itself in its capabilities: its title, its fees and access constraints, its layers with a name and whether it // draws in EPSG:2180, the one the tiles are laid out in. Blocking, for a thread of its own struct service_info { std::string title; std::string terms; std::vector> layers; // name, title bool puwg{false}; std::string error; }; static service_info describe(std::string const &Service); static std::string cache_directory(); private: using tile_key = std::pair; // (easting index, northing index) on the PUWG tile grid struct source { int year{0}; bool hires{false}; std::string service; std::string layers; }; struct job { tile_key key; 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 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 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 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 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); 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 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 m_tiles; // main thread only tile_key m_camera_tile{0, 0}; std::vector m_free_slots; // pool of textures, reused between tiles 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 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 m_area; // PUWG, empty: no area std::set 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 m_queue; std::deque m_done; tile_key m_focus{0, 0}; // workers pick the queued tile closest to it std::set m_running; // tiles a worker is on: another job of one waits till that one is done std::size_t m_running_hires{0}; // of them, the ones of a 4K image // 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 m_prefetch; std::size_t m_prefetch_next{0}; std::set m_prefetch_failed; std::set m_prefetch_cached; bool m_prefetch_paused{false}; source m_prefetch_source; std::uint32_t m_prefetch_generation{0}; std::atomic m_stop{false}; std::atomic m_generation{1}; std::vector m_workers; };