mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-18 18:29:40 +02:00
Editor checkpoint: turnout domain service, web->editor rename, Id-1 templates
Checkpoint of the editor branch so work isn't carried loose across a branch switch. Builds. - maj0sted turnout geometry moved into the domain: domain::lay_turnout lays the diverging path (pre-blade lead, blade at beta, arc of alfa-beta, frog rail) and solve_junction is a thin adapter onto it. - the maj0sted web/ tree renamed to editor/ (namespace and paths) - there is no WASM here, it's plain native C++ the editor calls. - click places the switch at its start (początek rozjazdu); the branch's leaving straight is locked tangent to the frog; switches can be slid along the through track and flipped L/P. - Polish turnout templates carry catalogue length (Id-1-consistent lengths), plus blade-angle and pre-blade fields (left 0 until the catalogue figures are in). - TURNOUT_SPEC.md: the Bezier-based runtime/model contract. - also folds in other pending branch changes (scenery, tests) to leave a clean tree.
This commit is contained in:
@@ -136,11 +136,19 @@ bool http_get(std::string const &, std::vector<std::uint8_t> &, std::string &)
|
||||
|
||||
} // namespace
|
||||
|
||||
orthophoto_source::orthophoto_source()
|
||||
orthophoto_source::orthophoto_source(maj0sted::editor::WmsConfig Config, int const Gridzoom, std::string Cachesubdir)
|
||||
: m_service([&] {
|
||||
if (Gridzoom >= maj0sted::editor::TileGrid::kCellZoomMin)
|
||||
{
|
||||
Config.tile_pixels = 512; // coarse cells do not need orto's 1024 px
|
||||
}
|
||||
return std::move(Config);
|
||||
}()),
|
||||
m_gridzoom(Gridzoom), m_cachesubdir(std::move(Cachesubdir))
|
||||
{
|
||||
// the library asks for a URL and expects the answer whenever it arrives; the transfer is queued
|
||||
// here and handed back on the thread that drains it, so the service stays single threaded
|
||||
m_service.set_fetcher([this](std::string const &Url, maj0sted::web::TileService::DoneFn Done) {
|
||||
m_service.set_fetcher([this](std::string const &Url, maj0sted::editor::TileService::DoneFn Done) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_queued.push_back({Url, std::move(Done), false, {}, {}});
|
||||
@@ -210,7 +218,7 @@ void orthophoto_source::worker()
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::web::TileBBox const &View, int const Maxtiles)
|
||||
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::editor::TileBBox const &View, int const Maxtiles)
|
||||
{
|
||||
// hand finished transfers to the library here, on the caller's thread
|
||||
std::deque<transfer> arrived;
|
||||
@@ -227,8 +235,10 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
--m_inflight;
|
||||
}
|
||||
|
||||
std::vector<ready_tile> tiles;
|
||||
for (auto const &key : maj0sted::web::TileGrid::tiles_for_view(View, maj0sted::web::TileGrid::kLevel, Maxtiles))
|
||||
// fetch only what fits the budget for the current view. the window is recentred when the
|
||||
// view is larger than the cap, and zoom-to-cursor moves the camera, so the set of keys asked
|
||||
// for changes from frame to frame - that must not decide what is drawn
|
||||
for (auto const &key : maj0sted::editor::TileGrid::tiles_for_view(View, m_gridzoom, Maxtiles))
|
||||
{
|
||||
// a tile kept from an earlier session seeds the cache, so no request goes out for it at all
|
||||
if (false == m_service.has(key))
|
||||
@@ -237,33 +247,51 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
}
|
||||
|
||||
auto const status = m_service.request(key);
|
||||
if (status == maj0sted::web::TileStatus::Failed)
|
||||
if (status == maj0sted::editor::TileStatus::Failed)
|
||||
{
|
||||
++m_failed;
|
||||
// counted once per tile, not once per frame it stays unavailable
|
||||
m_failedkeys.insert(key);
|
||||
continue;
|
||||
}
|
||||
if (status != maj0sted::web::TileStatus::Ready)
|
||||
if (status != maj0sted::editor::TileStatus::Ready)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto found = m_textures.find(key);
|
||||
if (found == m_textures.end())
|
||||
if (m_textures.find(key) == m_textures.end())
|
||||
{
|
||||
// the first time a tile is drawn is also the moment its bytes are known to be good
|
||||
// the first time a tile is ready is also the moment its bytes are known to be good
|
||||
save_to_disk(key);
|
||||
found = m_textures.emplace(key, upload(key)).first;
|
||||
m_textures.emplace(key, upload(key));
|
||||
}
|
||||
if (found->second != 0)
|
||||
}
|
||||
|
||||
// draw every texture we already hold that still covers the view. returning only the fetch
|
||||
// window made tiles blink out during zoom as soon as they fell outside the cap, then blink
|
||||
// back in once the window moved over them again
|
||||
auto const overlaps = [](maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox const &Right) {
|
||||
return Left.min_x < Right.max_x && Left.max_x > Right.min_x && Left.min_y < Right.max_y && Left.max_y > Right.min_y;
|
||||
};
|
||||
|
||||
std::vector<ready_tile> tiles;
|
||||
tiles.reserve(m_textures.size());
|
||||
for (auto const &[key, texture] : m_textures)
|
||||
{
|
||||
if (texture == 0)
|
||||
{
|
||||
tiles.push_back({maj0sted::web::TileGrid::bbox(key), found->second});
|
||||
continue;
|
||||
}
|
||||
auto const box = maj0sted::editor::TileGrid::bbox(key);
|
||||
if (overlaps(box, View))
|
||||
{
|
||||
tiles.push_back({box, texture});
|
||||
}
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
unsigned int orthophoto_source::upload(maj0sted::web::TileKey const &Key)
|
||||
unsigned int orthophoto_source::upload(maj0sted::editor::TileKey const &Key)
|
||||
{
|
||||
auto const *image = m_service.peek(Key);
|
||||
if (image == nullptr || image->bytes.empty())
|
||||
@@ -296,12 +324,12 @@ unsigned int orthophoto_source::upload(maj0sted::web::TileKey const &Key)
|
||||
return texture;
|
||||
}
|
||||
|
||||
std::string orthophoto_source::tile_path(maj0sted::web::TileKey const &Key)
|
||||
std::string orthophoto_source::tile_path(maj0sted::editor::TileKey const &Key) const
|
||||
{
|
||||
return "editor/cache/orto/" + std::to_string(Key.zoom) + "_" + std::to_string(Key.x) + "_" + std::to_string(Key.y) + ".img";
|
||||
return "editor/cache/" + m_cachesubdir + "/" + std::to_string(Key.zoom) + "_" + std::to_string(Key.x) + "_" + std::to_string(Key.y) + ".img";
|
||||
}
|
||||
|
||||
bool orthophoto_source::load_from_disk(maj0sted::web::TileKey const &Key)
|
||||
bool orthophoto_source::load_from_disk(maj0sted::editor::TileKey const &Key)
|
||||
{
|
||||
std::ifstream file(tile_path(Key), std::ios::binary);
|
||||
if (false == file.is_open())
|
||||
@@ -320,7 +348,7 @@ bool orthophoto_source::load_from_disk(maj0sted::web::TileKey const &Key)
|
||||
return true;
|
||||
}
|
||||
|
||||
void orthophoto_source::save_to_disk(maj0sted::web::TileKey const &Key)
|
||||
void orthophoto_source::save_to_disk(maj0sted::editor::TileKey const &Key)
|
||||
{
|
||||
auto const *image = m_service.peek(Key);
|
||||
if (image == nullptr || image->bytes.empty())
|
||||
@@ -350,7 +378,7 @@ std::size_t orthophoto_source::pending() const
|
||||
|
||||
std::size_t orthophoto_source::failed() const
|
||||
{
|
||||
return m_failed;
|
||||
return m_failedkeys.size();
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -383,7 +411,7 @@ unsigned int upload_image(std::vector<std::uint8_t> const &Bytes)
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool same_box(maj0sted::web::TileBBox const &Left, maj0sted::web::TileBBox const &Right)
|
||||
bool same_box(maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox const &Right)
|
||||
{
|
||||
auto const close = [](double const A, double const B) { return std::abs(A - B) < 1.0; };
|
||||
return close(Left.min_x, Right.min_x) && close(Left.min_y, Right.min_y) && close(Left.max_x, Right.max_x) && close(Left.max_y, Right.max_y);
|
||||
@@ -391,7 +419,7 @@ bool same_box(maj0sted::web::TileBBox const &Left, maj0sted::web::TileBBox const
|
||||
|
||||
} // namespace
|
||||
|
||||
wms_image::wms_image(maj0sted::web::WmsConfig Config) : m_config(std::move(Config))
|
||||
wms_image::wms_image(maj0sted::editor::WmsConfig Config) : m_config(std::move(Config))
|
||||
{
|
||||
m_worker = std::thread([this]() { worker(); });
|
||||
}
|
||||
@@ -446,7 +474,7 @@ void wms_image::worker()
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int wms_image::texture_for(maj0sted::web::TileBBox const &Box, int const Pixels)
|
||||
unsigned int wms_image::texture_for(maj0sted::editor::TileBBox const &Box, int const Pixels)
|
||||
{
|
||||
// a finished download becomes the texture on this thread, where the graphics API is safe to touch
|
||||
{
|
||||
@@ -482,7 +510,7 @@ unsigned int wms_image::texture_for(maj0sted::web::TileBBox const &Box, int cons
|
||||
config.tile_pixels = Pixels;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_url = maj0sted::web::wms_getmap_url(config, Box);
|
||||
m_url = maj0sted::editor::wms_getmap_url(config, Box);
|
||||
m_haswork = true;
|
||||
}
|
||||
m_loading = true;
|
||||
|
||||
@@ -17,9 +17,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/web/tile_cache.hpp"
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
|
||||
namespace editor
|
||||
{
|
||||
@@ -37,18 +38,22 @@ class orthophoto_source
|
||||
public:
|
||||
struct ready_tile
|
||||
{
|
||||
maj0sted::web::TileBBox box;
|
||||
maj0sted::editor::TileBBox box;
|
||||
unsigned int texture{0};
|
||||
};
|
||||
|
||||
orthophoto_source();
|
||||
// default: Geoportal ortophoto on the fine 100 m grid. pass topo config and a coarse
|
||||
// grid zoom (e.g. 1000) for the zoomed-out topographic underlay
|
||||
explicit orthophoto_source(maj0sted::editor::WmsConfig Config = maj0sted::editor::WmsConfig::geoportal_ortho(), int const Gridzoom = maj0sted::editor::TileGrid::kLevel,
|
||||
std::string Cachesubdir = "orto");
|
||||
~orthophoto_source();
|
||||
orthophoto_source(orthophoto_source const &) = delete;
|
||||
orthophoto_source &operator=(orthophoto_source const &) = delete;
|
||||
|
||||
// asks for every tile covering the view and returns those that are already drawable. tiles still
|
||||
// on their way simply aren't in the answer yet; call again next frame
|
||||
std::vector<ready_tile> collect(maj0sted::web::TileBBox const &View, int const Maxtiles = 48);
|
||||
// kicks off fetches for the view (capped) and returns every already-uploaded tile that still
|
||||
// intersects it - including ones outside the fetch window, so a zoom cannot blink them away.
|
||||
// tiles still on their way simply aren't in the answer yet; call again next frame
|
||||
std::vector<ready_tile> collect(maj0sted::editor::TileBBox const &View, int const Maxtiles = 400);
|
||||
// how many tiles are still being fetched, for the panel to report
|
||||
std::size_t pending() const;
|
||||
// how many tiles gave up after exhausting their retries
|
||||
@@ -59,7 +64,7 @@ class orthophoto_source
|
||||
struct transfer
|
||||
{
|
||||
std::string url;
|
||||
maj0sted::web::TileService::DoneFn done;
|
||||
maj0sted::editor::TileService::DoneFn done;
|
||||
bool ok{false};
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::string content_type;
|
||||
@@ -67,15 +72,17 @@ class orthophoto_source
|
||||
// methods
|
||||
void worker();
|
||||
// decodes the cached bytes of a tile and uploads them; returns 0 when the image is unusable
|
||||
unsigned int upload(maj0sted::web::TileKey const &Key);
|
||||
unsigned int upload(maj0sted::editor::TileKey const &Key);
|
||||
// imagery of a given piece of ground never changes, so a fetched tile is kept on disk and the
|
||||
// next session starts with it instead of going back to a service that is slow and flaky
|
||||
static std::string tile_path(maj0sted::web::TileKey const &Key);
|
||||
bool load_from_disk(maj0sted::web::TileKey const &Key);
|
||||
void save_to_disk(maj0sted::web::TileKey const &Key);
|
||||
std::string tile_path(maj0sted::editor::TileKey const &Key) const;
|
||||
bool load_from_disk(maj0sted::editor::TileKey const &Key);
|
||||
void save_to_disk(maj0sted::editor::TileKey const &Key);
|
||||
// members
|
||||
maj0sted::web::TileService m_service;
|
||||
std::unordered_map<maj0sted::web::TileKey, unsigned int, maj0sted::web::TileKeyHash> m_textures;
|
||||
maj0sted::editor::TileService m_service;
|
||||
int m_gridzoom{maj0sted::editor::TileGrid::kLevel};
|
||||
std::string m_cachesubdir{"orto"};
|
||||
std::unordered_map<maj0sted::editor::TileKey, unsigned int, maj0sted::editor::TileKeyHash> m_textures;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_wakeup;
|
||||
@@ -84,7 +91,7 @@ class orthophoto_source
|
||||
std::vector<std::thread> m_workers;
|
||||
std::atomic<bool> m_quitting{false};
|
||||
std::atomic<std::size_t> m_inflight{0};
|
||||
std::size_t m_failed{0};
|
||||
std::unordered_set<maj0sted::editor::TileKey, maj0sted::editor::TileKeyHash> m_failedkeys;
|
||||
};
|
||||
|
||||
// a single WMS image covering an arbitrary box, for views too coarse for the tile grid - the whole
|
||||
@@ -94,25 +101,25 @@ class wms_image
|
||||
{
|
||||
|
||||
public:
|
||||
explicit wms_image(maj0sted::web::WmsConfig Config);
|
||||
explicit wms_image(maj0sted::editor::WmsConfig Config);
|
||||
~wms_image();
|
||||
wms_image(wms_image const &) = delete;
|
||||
wms_image &operator=(wms_image const &) = delete;
|
||||
|
||||
// asks for the given box at the given size and returns the texture currently available, which
|
||||
// may still be the previous box's until the new one arrives; 0 when there is nothing to show yet
|
||||
unsigned int texture_for(maj0sted::web::TileBBox const &Box, int const Pixels);
|
||||
unsigned int texture_for(maj0sted::editor::TileBBox const &Box, int const Pixels);
|
||||
// the box the returned texture actually covers
|
||||
maj0sted::web::TileBBox const &covered() const { return m_covered; }
|
||||
maj0sted::editor::TileBBox const &covered() const { return m_covered; }
|
||||
bool loading() const { return m_loading.load(); }
|
||||
|
||||
private:
|
||||
void worker();
|
||||
|
||||
maj0sted::web::WmsConfig m_config;
|
||||
maj0sted::web::TileBBox m_requested{};
|
||||
maj0sted::web::TileBBox m_inflightbox{};
|
||||
maj0sted::web::TileBBox m_covered{};
|
||||
maj0sted::editor::WmsConfig m_config;
|
||||
maj0sted::editor::TileBBox m_requested{};
|
||||
maj0sted::editor::TileBBox m_inflightbox{};
|
||||
maj0sted::editor::TileBBox m_covered{};
|
||||
unsigned int m_texture{0};
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
@@ -15,15 +15,21 @@ namespace editor
|
||||
|
||||
std::vector<turnout_preset> const &turnout_presets()
|
||||
{
|
||||
// the everyday PKP rozjazdy zwyczajne. the crossing mark and radius are the defining pair;
|
||||
// the length the switch spans follows from them and is shown in the editor
|
||||
// the standard PKP rozjazdy zwyczajne (49E1 / 60E1). crossing mark, radius and catalogue length
|
||||
// (PR→KR along the through track). the crossing angles check out as atan(1/n); the lengths follow
|
||||
// the type tables (metres). the diverging leaves not tangent but at the blade angle (beta) after a
|
||||
// straight pre-blade lead, then arcs by (alfa - beta) to the frog.
|
||||
//
|
||||
// blade_angle and pre_blade are NOT yet from Id-1 - left at 0 (a tangent arc from PR) until the
|
||||
// catalogue figures are in. Fill them per type when we have them; the geometry already supports it.
|
||||
static std::vector<turnout_preset> const presets{
|
||||
{"Rz 1:9 R190", 9.0, 190.0},
|
||||
{"Rz 1:9 R300", 9.0, 300.0},
|
||||
{"Rz 1:12 R300", 12.0, 300.0},
|
||||
{"Rz 1:14 R500", 14.0, 500.0},
|
||||
{"Rz 1:18,5 R1200", 18.5, 1200.0},
|
||||
{"Rz 1:26,5 R2500", 26.5, 2500.0}};
|
||||
{"Rz 1:7,5 R190", 7.5, 190.0, 25.222, 0.0, 0.0},
|
||||
{"Rz 1:9 R190", 9.0, 190.0, 27.138, 0.0, 0.0},
|
||||
{"Rz 1:9 R300", 9.0, 300.0, 33.230, 0.0, 0.0},
|
||||
{"Rz 1:12 R500", 12.0, 500.0, 41.594, 0.0, 0.0},
|
||||
{"Rz 1:14 R500", 14.0, 500.0, 42.371, 0.0, 0.0},
|
||||
{"Rz 1:14 R760", 14.0, 760.0, 54.216, 0.0, 0.0},
|
||||
{"Rz 1:18,5 R1200", 18.5, 1200.0, 64.818, 0.0, 0.0}};
|
||||
return presets;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,11 @@ namespace editor
|
||||
struct turnout_preset
|
||||
{
|
||||
std::string name;
|
||||
double crossing_n;
|
||||
double radius;
|
||||
double crossing_n; // skos 1:n
|
||||
double radius; // metres
|
||||
double length; // catalogue length PR->KR along the through track, metres
|
||||
double blade_angle; // kąt nagięcia iglicy (beta), radians; the diverging leaves the straight here
|
||||
double pre_blade; // odcinek przediglicowy: straight lead PR -> blade tip, metres
|
||||
};
|
||||
|
||||
// typical Polish turnouts (rozjazdy zwyczajne). the (skos, R) pairs are the recognised standard;
|
||||
|
||||
Reference in New Issue
Block a user