mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 02:49:19 +02:00
Editor: fold the plan library into the tree as modules, drop its own name
The track layout library came in as a subproject of its own -- its own CMakeLists, its own install and export rules, its own include tree -- which only made the build harder to follow now that everything else is a module. It is part of the editor, so it lives where the editor lives. - editor/plan_*.cppm / plan_*.cpp: the fifteen interface units and twelve implementation units, flat in editor/ beside editorTerrain and orthophoto, with the plan_ prefix keeping them clear of what was already there (editor/turnouts is the preset catalogue, so the geometry is plan_turnout_geometry). - modules are named eu07.editor.plan_*, like every other module in the tree. - namespaces move under the editor's own: editor::plan, editor::plan::geometry, ::io, ::render. The library's old name is gone from the sources and the docs. - every moved file carries the MPL header the rest of the tree carries; it had none. version.hpp went with the subproject: macros only, and unused. - the umbrella header is dropped -- nothing imported it. - the unit tests survive the fold: editor/tests, behind EU07_EDITOR_PLAN_TESTS (off by default), compiles the interfaces a second time into a small library of its own, because a test can only import modules from a target it links and linking the game would drag the whole simulator in. The code under test needs nothing but a compiler. 7/7 pass. - a switch may now be written with two dashes as well: --editor beside -editor. Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF); -editor and --editor both come up in the editor on empty ground.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ ci_shadervalidator/validateshaders
|
||||
|
||||
# CMake build output
|
||||
build/
|
||||
build-*/
|
||||
bin/
|
||||
|
||||
#Visual Studio [Code] folders
|
||||
|
||||
@@ -199,6 +199,18 @@ set(SOURCES
|
||||
"editor/polandmap.cpp"
|
||||
"editor/turnouts.cpp"
|
||||
"editor/orthophoto.cpp"
|
||||
"editor/plan_chain.cpp"
|
||||
"editor/plan_document_io.cpp"
|
||||
"editor/plan_join.cpp"
|
||||
"editor/plan_layout.cpp"
|
||||
"editor/plan_model.cpp"
|
||||
"editor/plan_rail_renderer.cpp"
|
||||
"editor/plan_render.cpp"
|
||||
"editor/plan_scn_export.cpp"
|
||||
"editor/plan_sketch.cpp"
|
||||
"editor/plan_solution.cpp"
|
||||
"editor/plan_tilecache.cpp"
|
||||
"editor/plan_turnout_geometry.cpp"
|
||||
"editor/editorTerrain.cpp"
|
||||
"editor/editorTerrainStreamer.cpp"
|
||||
"application/editormode.cpp"
|
||||
@@ -633,9 +645,6 @@ target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR})
|
||||
|
||||
# track layout domain model used by the editor; plain C++20, no dependencies of its own
|
||||
add_subdirectory(maj0sted)
|
||||
set_target_properties(maj0sted PROPERTIES FOLDER "libraries")
|
||||
target_link_libraries(${PROJECT_NAME} maj0sted::maj0sted)
|
||||
|
||||
|
||||
|
||||
@@ -716,6 +725,8 @@ endif()
|
||||
# copied off it rather than restated; this has to happen after all the
|
||||
# third-party wiring above and before betterRenderer is added, or the mirrored
|
||||
# link list would point back at betterRenderer and make the dependency circular.
|
||||
add_subdirectory(editor/tests)
|
||||
|
||||
add_library(eu07_modules STATIC)
|
||||
|
||||
get_target_property(EU07_INCLUDES ${PROJECT_NAME} INCLUDE_DIRECTORIES)
|
||||
|
||||
@@ -63,7 +63,7 @@ osobnym, jawnym polem.** Kernel do tego już mamy: `geometry::layout_segment`.
|
||||
|
||||
## 3. Model docelowy
|
||||
|
||||
Płasko, w `maj0sted::editor`, Id jako mocne typy.
|
||||
Płasko, w `editor::plan`, Id jako mocne typy.
|
||||
|
||||
### 3.1. Odcinek jest bytem
|
||||
|
||||
|
||||
@@ -1176,6 +1176,13 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
|
||||
std::string token{Argv[i]};
|
||||
|
||||
// a switch may be written with one dash or two: both spellings are common on the command
|
||||
// line and neither says anything different, so the second dash is simply dropped here
|
||||
if (token.size() > 2 && token.compare(0, 2, "--") == 0)
|
||||
{
|
||||
token.erase(0, 1);
|
||||
}
|
||||
|
||||
if (token == "-s")
|
||||
{
|
||||
if (i + 1 < Argc)
|
||||
@@ -1199,7 +1206,8 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
{
|
||||
std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]"
|
||||
<< " [-v vehiclename]"
|
||||
<< " [-editor]" << std::endl;
|
||||
<< " [-editor]"
|
||||
<< " (every switch also takes a double dash: --editor)" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,25 @@ module;
|
||||
#include <limits>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "imgui/imgui.h"
|
||||
#include "utilities/Globals_macros.h"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
#include "maj0sted/editor/join.hpp"
|
||||
#include "maj0sted/editor/sketch.hpp"
|
||||
#include "maj0sted/io/document_io.hpp"
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
module eu07.application.planpanel;
|
||||
import eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_model;
|
||||
import eu07.editor.plan_render;
|
||||
import eu07.editor.plan_solution;
|
||||
import eu07.editor.plan_tilecache;
|
||||
import eu07.editor.plan_scn_export;
|
||||
import eu07.editor.plan_join;
|
||||
import eu07.editor.plan_sketch;
|
||||
import eu07.editor.plan_document_io;
|
||||
import eu07.glm;
|
||||
import eu07.utilities.globals;
|
||||
import eu07.utilities.utilities;
|
||||
@@ -44,13 +45,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
namespace
|
||||
{
|
||||
|
||||
using maj0sted::editor::Element;
|
||||
using maj0sted::editor::ElementId;
|
||||
using maj0sted::editor::Free;
|
||||
using maj0sted::editor::Kind;
|
||||
using maj0sted::editor::Parallel;
|
||||
using maj0sted::editor::TrackId;
|
||||
using maj0sted::editor::TurnoutId;
|
||||
using editor::plan::Element;
|
||||
using editor::plan::ElementId;
|
||||
using editor::plan::Free;
|
||||
using editor::plan::Kind;
|
||||
using editor::plan::Parallel;
|
||||
using editor::plan::TrackId;
|
||||
using editor::plan::TurnoutId;
|
||||
|
||||
// the plan works in EPSG:2180, the scenery in the engine's world space around its own zero. the
|
||||
// georeference says which point of the projection that zero stands for; a fictional scenery leaves
|
||||
@@ -286,7 +287,7 @@ editor::turnout_preset const *find_preset(std::string const &Name_)
|
||||
// the catalogue figure written onto a document's own type. a document keeps its own copy of the
|
||||
// catalogue - that is what lets an instance name its type and nothing more - so this is the one
|
||||
// place the preset's numbers cross over, and it has to carry all of them
|
||||
void adopt_preset(editor::turnout_preset const &Preset_, maj0sted::editor::TurnoutType &Type_)
|
||||
void adopt_preset(editor::turnout_preset const &Preset_, editor::plan::TurnoutType &Type_)
|
||||
{
|
||||
Type_.name = Preset_.name;
|
||||
Type_.crossing_n = Preset_.crossing_n;
|
||||
@@ -298,7 +299,7 @@ void adopt_preset(editor::turnout_preset const &Preset_, maj0sted::editor::Turno
|
||||
for (auto const &piece : Preset_.pieces)
|
||||
{
|
||||
Type_.pieces.push_back(
|
||||
maj0sted::editor::TurnoutPieceSpec{piece.part, piece.length, piece.radius_start, piece.radius_end, piece.turn_in});
|
||||
editor::plan::TurnoutPieceSpec{piece.part, piece.length, piece.radius_start, piece.radius_end, piece.turn_in});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +437,7 @@ void plan_panel::seed_catalogue()
|
||||
[&](auto const &Type_) { return Type_.name == preset.name; })};
|
||||
if (existing == m_document.turnout_types.end())
|
||||
{
|
||||
maj0sted::editor::TurnoutType type;
|
||||
editor::plan::TurnoutType type;
|
||||
adopt_preset(preset, type);
|
||||
m_document.turnout_types.push_back(std::move(type));
|
||||
continue;
|
||||
@@ -460,24 +461,24 @@ void plan_panel::seed_catalogue()
|
||||
|
||||
void plan_panel::solve()
|
||||
{
|
||||
m_solution = maj0sted::editor::solve(m_document);
|
||||
m_rails = maj0sted::editor::render_rails(m_solution);
|
||||
m_solution = editor::plan::solve(m_document);
|
||||
m_rails = editor::plan::render_rails(m_solution);
|
||||
}
|
||||
|
||||
plan_panel::Track *plan_panel::current_track()
|
||||
{
|
||||
return maj0sted::editor::find_track(m_document, m_track);
|
||||
return editor::plan::find_track(m_document, m_track);
|
||||
}
|
||||
|
||||
plan_panel::Track const *plan_panel::current_track() const
|
||||
{
|
||||
return maj0sted::editor::find_track(m_document, m_track);
|
||||
return editor::plan::find_track(m_document, m_track);
|
||||
}
|
||||
|
||||
void plan_panel::new_track()
|
||||
{
|
||||
Track track;
|
||||
track.id = maj0sted::editor::mint_track(m_document);
|
||||
track.id = editor::plan::mint_track(m_document);
|
||||
track.name = "tor " + std::to_string(m_document.tracks.size() + 1);
|
||||
m_document.tracks.push_back(track);
|
||||
m_track = track.id;
|
||||
@@ -548,8 +549,8 @@ void plan_panel::append_point_to(double const X, double const Y)
|
||||
m_status = "za krotka prosta";
|
||||
return;
|
||||
}
|
||||
track->anchor = maj0sted::editor::AtPose{m_startx, m_starty, std::atan2(dx, dy)};
|
||||
track->elements.push_back(maj0sted::editor::make_line(m_document, length));
|
||||
track->anchor = editor::plan::AtPose{m_startx, m_starty, std::atan2(dx, dy)};
|
||||
track->elements.push_back(editor::plan::make_line(m_document, length));
|
||||
m_pending = false;
|
||||
solve();
|
||||
m_status.clear();
|
||||
@@ -557,7 +558,7 @@ void plan_panel::append_point_to(double const X, double const Y)
|
||||
}
|
||||
|
||||
// the track ends on the last click, so that end is the corner this one turns at
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, track->id)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, track->id)};
|
||||
if (solved == nullptr || solved->elements.empty())
|
||||
{
|
||||
m_status = "tor nie jest polozony - popraw najpierw to, co nie wyszlo";
|
||||
@@ -574,7 +575,7 @@ void plan_panel::append_point_to(double const X, double const Y)
|
||||
return;
|
||||
}
|
||||
|
||||
auto const corner{maj0sted::editor::fit_corner(corner_pose.hx, corner_pose.hy, dx / reach, dy / reach, m_corner_radius)};
|
||||
auto const corner{editor::plan::fit_corner(corner_pose.hx, corner_pose.hy, dx / reach, dy / reach, m_corner_radius)};
|
||||
if (false == corner.ok)
|
||||
{
|
||||
// straight on: nothing to round. two straights end to end are one straight, so the one
|
||||
@@ -601,8 +602,8 @@ void plan_panel::append_point_to(double const X, double const Y)
|
||||
}
|
||||
|
||||
previous.length -= corner.tangent;
|
||||
track->elements.push_back(maj0sted::editor::make_arc(m_document, m_corner_radius, corner.hand, corner.arc_length));
|
||||
track->elements.push_back(maj0sted::editor::make_line(m_document, reach - corner.tangent));
|
||||
track->elements.push_back(editor::plan::make_arc(m_document, m_corner_radius, corner.hand, corner.arc_length));
|
||||
track->elements.push_back(editor::plan::make_line(m_document, reach - corner.tangent));
|
||||
solve();
|
||||
m_status.clear();
|
||||
}
|
||||
@@ -659,7 +660,7 @@ void plan_panel::append_element(Kind const Kind_)
|
||||
}
|
||||
|
||||
Element element;
|
||||
element.id = maj0sted::editor::mint_element(m_document);
|
||||
element.id = editor::plan::mint_element(m_document);
|
||||
element.kind = Kind_;
|
||||
element.radius = Kind_ == Kind::Line ? 0.0 : m_new_radius;
|
||||
element.hand = Kind_ == Kind::Line ? 0 : m_new_hand;
|
||||
@@ -739,33 +740,33 @@ bool plan_panel::refit_current_track(std::string &Why)
|
||||
auto *track{current_track()};
|
||||
// the solution still holds the geometry from before the change, which is exactly what the
|
||||
// straights are supposed to go on standing on
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, m_track)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, m_track)};
|
||||
if (track == nullptr || solved == nullptr)
|
||||
{
|
||||
Why = "tor nie jest polozony";
|
||||
return false;
|
||||
}
|
||||
|
||||
maj0sted::editor::Skeleton skeleton;
|
||||
if (false == maj0sted::editor::skeleton_of(*track, *solved, skeleton, Why))
|
||||
editor::plan::Skeleton skeleton;
|
||||
if (false == editor::plan::skeleton_of(*track, *solved, skeleton, Why))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return maj0sted::editor::lay_along_skeleton(*track, skeleton, Why);
|
||||
return editor::plan::lay_along_skeleton(*track, skeleton, Why);
|
||||
}
|
||||
|
||||
void plan_panel::drag_straight_to(double const X, double const Y)
|
||||
{
|
||||
auto *track{current_track()};
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, m_track)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, m_track)};
|
||||
if (track == nullptr || solved == nullptr || m_drag_index >= track->elements.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
maj0sted::editor::Skeleton skeleton;
|
||||
editor::plan::Skeleton skeleton;
|
||||
std::string why;
|
||||
if (false == maj0sted::editor::skeleton_of(*track, *solved, skeleton, why))
|
||||
if (false == editor::plan::skeleton_of(*track, *solved, skeleton, why))
|
||||
{
|
||||
m_status = why;
|
||||
return;
|
||||
@@ -818,18 +819,18 @@ void plan_panel::drag_straight_to(double const X, double const Y)
|
||||
|
||||
// nothing is written until the whole chain works out, so a bad drag leaves the track alone
|
||||
Track candidate{*track};
|
||||
if (false == maj0sted::editor::lay_along_skeleton(candidate, skeleton, why))
|
||||
if (false == editor::plan::lay_along_skeleton(candidate, skeleton, why))
|
||||
{
|
||||
m_status = why;
|
||||
return;
|
||||
}
|
||||
// the chain laying out is not enough: shortening a straight can pull it out from under a rozjazd
|
||||
// standing on it, and a rozjazd half on an arc is not a rozjazd. the drag stops at that instead
|
||||
auto const off_straight{[](maj0sted::editor::Solution const &Solution_) {
|
||||
auto const off_straight{[](editor::plan::Solution const &Solution_) {
|
||||
std::size_t count{0};
|
||||
for (auto const &diagnostic : Solution_.diagnostics)
|
||||
{
|
||||
count += diagnostic.code == maj0sted::editor::Code::StationOffTrack ? 1 : 0;
|
||||
count += diagnostic.code == editor::plan::Code::StationOffTrack ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}};
|
||||
@@ -842,7 +843,7 @@ void plan_panel::drag_straight_to(double const X, double const Y)
|
||||
{
|
||||
for (auto const &diagnostic : m_solution.diagnostics)
|
||||
{
|
||||
if (diagnostic.code == maj0sted::editor::Code::StationOffTrack)
|
||||
if (diagnostic.code == editor::plan::Code::StationOffTrack)
|
||||
{
|
||||
m_status = "dalej nie: " + diagnostic.text;
|
||||
break;
|
||||
@@ -870,8 +871,8 @@ void plan_panel::place_turnout_at(TrackId const On, double const Wx, double cons
|
||||
}
|
||||
auto const type{static_cast<std::size_t>(std::clamp(m_turnout_type, 0, static_cast<int>(m_document.turnout_types.size()) - 1))};
|
||||
|
||||
maj0sted::editor::TurnoutPlacement placement;
|
||||
placement.id = maj0sted::editor::mint_turnout(m_document);
|
||||
editor::plan::TurnoutPlacement placement;
|
||||
placement.id = editor::plan::mint_turnout(m_document);
|
||||
placement.type = m_document.turnout_types[type].name;
|
||||
placement.on = On;
|
||||
placement.station = station;
|
||||
@@ -880,9 +881,9 @@ void plan_panel::place_turnout_at(TrackId const On, double const Wx, double cons
|
||||
|
||||
// a switch is worth nothing without somewhere for the branch to go, so one starts at its frog
|
||||
Track branch;
|
||||
branch.id = maj0sted::editor::mint_track(m_document);
|
||||
branch.id = editor::plan::mint_track(m_document);
|
||||
branch.name = "odnoga " + std::to_string(m_document.tracks.size() + 1);
|
||||
branch.anchor = maj0sted::editor::AtPort{placement.id, maj0sted::editor::Port::Frog};
|
||||
branch.anchor = editor::plan::AtPort{placement.id, editor::plan::Port::Frog};
|
||||
m_document.tracks.push_back(branch);
|
||||
|
||||
m_track = branch.id;
|
||||
@@ -899,7 +900,7 @@ void plan_panel::place_turnout_at(TrackId const On, double const Wx, double cons
|
||||
|
||||
bool plan_panel::hit_straight_end(ImVec2 const &Mouse, std::size_t &OutIndex, int &OutEnd) const
|
||||
{
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, m_track)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, m_track)};
|
||||
auto const *track{current_track()};
|
||||
if (solved == nullptr || track == nullptr)
|
||||
{
|
||||
@@ -916,7 +917,7 @@ bool plan_panel::hit_straight_end(ImVec2 const &Mouse, std::size_t &OutIndex, in
|
||||
continue;
|
||||
}
|
||||
// a branch starts on its turnout's frog and cannot be pulled off it
|
||||
auto const pinned{i == 0 && false == std::holds_alternative<maj0sted::editor::AtPose>(track->anchor)};
|
||||
auto const pinned{i == 0 && false == std::holds_alternative<editor::plan::AtPose>(track->anchor)};
|
||||
for (int end = 0; end < 2; ++end)
|
||||
{
|
||||
if (end == 0 && pinned)
|
||||
@@ -1007,7 +1008,7 @@ bool plan_panel::hit_turnout(ImVec2 const &Mouse, TurnoutId &OutTurnout) const
|
||||
|
||||
bool plan_panel::station_on(TrackId const Track_, double const Wx, double const Wy, double &OutStation) const
|
||||
{
|
||||
auto const *track{maj0sted::editor::find_track(m_solution, Track_)};
|
||||
auto const *track{editor::plan::find_track(m_solution, Track_)};
|
||||
if (track == nullptr || track->centreline.size() < 2)
|
||||
{
|
||||
return false;
|
||||
@@ -1171,7 +1172,7 @@ void plan_panel::handle_scene()
|
||||
int end{0};
|
||||
if (hit_straight_end(mouse, index, end))
|
||||
{
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, m_track)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, m_track)};
|
||||
auto const *track{current_track()};
|
||||
if (solved != nullptr && index < solved->elements.size())
|
||||
{
|
||||
@@ -1183,7 +1184,7 @@ void plan_panel::handle_scene()
|
||||
m_drag_end = end;
|
||||
m_dragging_straight = true;
|
||||
m_sel_element = track->elements[index].id;
|
||||
if (track != nullptr && std::holds_alternative<maj0sted::editor::Parallel>(track->elements[index].hold))
|
||||
if (track != nullptr && std::holds_alternative<editor::plan::Parallel>(track->elements[index].hold))
|
||||
{
|
||||
m_status = "ten odcinek trzyma rownoleglosc - po puszczeniu tor i tak zostanie przystawiony";
|
||||
}
|
||||
@@ -1223,7 +1224,7 @@ void plan_panel::handle_scene()
|
||||
{
|
||||
m_sel_element = element;
|
||||
m_track = track;
|
||||
auto const *chosen{maj0sted::editor::find_track(m_document, track)};
|
||||
auto const *chosen{editor::plan::find_track(m_document, track)};
|
||||
if (chosen != nullptr)
|
||||
{
|
||||
std::snprintf(m_namebuf, sizeof(m_namebuf), "%s", chosen->name.c_str());
|
||||
@@ -1243,7 +1244,7 @@ void plan_panel::handle_scene()
|
||||
|
||||
if (ImGui::IsMouseDown(0) && m_dragging_turnout && m_sel_turnout != TurnoutId::none)
|
||||
{
|
||||
auto *placement{maj0sted::editor::find_turnout(m_document, m_sel_turnout)};
|
||||
auto *placement{editor::plan::find_turnout(m_document, m_sel_turnout)};
|
||||
if (placement != nullptr)
|
||||
{
|
||||
double station{0.0};
|
||||
@@ -1267,7 +1268,7 @@ void plan_panel::handle_scene()
|
||||
if (track != TrackId::none)
|
||||
{
|
||||
m_track = track;
|
||||
auto const *chosen{maj0sted::editor::find_track(m_document, track)};
|
||||
auto const *chosen{editor::plan::find_track(m_document, track)};
|
||||
if (chosen != nullptr)
|
||||
{
|
||||
std::snprintf(m_namebuf, sizeof(m_namebuf), "%s", chosen->name.c_str());
|
||||
@@ -1295,9 +1296,9 @@ void plan_panel::draw_on_scene()
|
||||
auto const display{ImGui::GetIO().DisplaySize};
|
||||
auto const halfheight{static_cast<double>(Global.editor_ortho_extent)};
|
||||
auto const halfwidth{halfheight * std::max(1.0f, display.x) / std::max(1.0f, display.y)};
|
||||
maj0sted::editor::TileBBox const view{planx - halfwidth, plany - halfheight, planx + halfwidth, plany + halfheight};
|
||||
editor::plan::TileBBox const view{planx - halfwidth, plany - halfheight, planx + halfwidth, plany + halfheight};
|
||||
|
||||
auto const draw_box = [&](maj0sted::editor::TileBBox const &Box, unsigned int const Texture) {
|
||||
auto const draw_box = [&](editor::plan::TileBBox const &Box, unsigned int const Texture) {
|
||||
ImVec2 corners[4];
|
||||
auto const ok = world_to_screen(plan_to_world(Box.min_x, Box.max_y), corners[0]) && world_to_screen(plan_to_world(Box.max_x, Box.max_y), corners[1]) &&
|
||||
world_to_screen(plan_to_world(Box.max_x, Box.min_y), corners[2]) && world_to_screen(plan_to_world(Box.min_x, Box.min_y), corners[3]);
|
||||
@@ -1399,7 +1400,7 @@ void plan_panel::draw_on_scene()
|
||||
// the points that sit on the straight - PR, the blade tip and the koniec rozjazdu -
|
||||
// are drawn the way a catalogue drawing marks them: a tick across the track, not a
|
||||
// dot lost among the rails the turnout runs along
|
||||
auto const tick{[&](maj0sted::editor::TurnoutMark const &Mark_, ImU32 const Colour_, float const Half_) {
|
||||
auto const tick{[&](editor::plan::TurnoutMark const &Mark_, ImU32 const Colour_, float const Half_) {
|
||||
ImVec2 centre;
|
||||
if (false == world_to_screen(plan_to_world(Mark_.x, Mark_.y), centre))
|
||||
{
|
||||
@@ -1536,7 +1537,7 @@ void plan_panel::draw_on_scene()
|
||||
// the ends of the edited track's straights, while ctrl says they are what you are after
|
||||
if (ImGui::GetIO().KeyCtrl)
|
||||
{
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, m_track)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, m_track)};
|
||||
auto const *track{current_track()};
|
||||
if (solved != nullptr && track != nullptr)
|
||||
{
|
||||
@@ -1546,7 +1547,7 @@ void plan_panel::draw_on_scene()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const pinned{i == 0 && false == std::holds_alternative<maj0sted::editor::AtPose>(track->anchor)};
|
||||
auto const pinned{i == 0 && false == std::holds_alternative<editor::plan::AtPose>(track->anchor)};
|
||||
for (int end = 0; end < 2; ++end)
|
||||
{
|
||||
if (end == 0 && pinned)
|
||||
@@ -1643,25 +1644,25 @@ void plan_panel::draw_on_scene()
|
||||
// left the way it came, and the arriving end reads the other way about
|
||||
if (m_pick_join == 2 && over && hovered != m_join_first)
|
||||
{
|
||||
auto const *head{maj0sted::editor::find_track(m_solution, m_join_first)};
|
||||
auto const *tail{maj0sted::editor::find_track(m_solution, hovered)};
|
||||
auto const *head{editor::plan::find_track(m_solution, m_join_first)};
|
||||
auto const *tail{editor::plan::find_track(m_solution, hovered)};
|
||||
if (head != nullptr && tail != nullptr)
|
||||
{
|
||||
auto const leaving{join_pose(*head, m_join_first_end, true)};
|
||||
auto const arriving{join_pose(*tail, hovered_end, false)};
|
||||
maj0sted::editor::JoinSettings settings;
|
||||
editor::plan::JoinSettings settings;
|
||||
settings.radius = m_join_radius;
|
||||
auto const plan{maj0sted::editor::plan_join(leaving, arriving, settings)};
|
||||
auto const plan{editor::plan::plan_join(leaving, arriving, settings)};
|
||||
|
||||
std::string label;
|
||||
if (plan.ok)
|
||||
{
|
||||
std::vector<maj0sted::domain::geometry::XY> points;
|
||||
std::vector<editor::plan::geometry::XY> points;
|
||||
auto walker{leaving};
|
||||
for (auto const &element : plan.elements)
|
||||
{
|
||||
auto const k{element.kind == Kind::Line || element.radius <= 0.0 ? 0.0 : element.hand / element.radius};
|
||||
walker = maj0sted::domain::geometry::layout_segment(k, k, element.length, walker, &points);
|
||||
walker = editor::plan::geometry::layout_segment(k, k, element.length, walker, &points);
|
||||
}
|
||||
std::vector<ImVec2> line;
|
||||
line.reserve(points.size());
|
||||
@@ -1677,7 +1678,7 @@ void plan_panel::draw_on_scene()
|
||||
{
|
||||
dashed_polyline(drawlist, line, IM_COL32(120, 255, 160, 235), 2.5f);
|
||||
}
|
||||
label = maj0sted::editor::join_kind_name(plan.kind);
|
||||
label = editor::plan::join_kind_name(plan.kind);
|
||||
auto laid{0.0};
|
||||
for (auto const &element : plan.elements)
|
||||
{
|
||||
@@ -1872,10 +1873,10 @@ void plan_panel::render_join()
|
||||
|
||||
void plan_panel::join_ends(TrackId const A, int const Aend, TrackId const B, int const Bend)
|
||||
{
|
||||
maj0sted::editor::JoinSettings settings;
|
||||
editor::plan::JoinSettings settings;
|
||||
settings.radius = m_join_radius;
|
||||
|
||||
auto const report{maj0sted::editor::join_ends(m_document, m_solution, A, Aend, B, Bend, settings)};
|
||||
auto const report{editor::plan::join_ends(m_document, m_solution, A, Aend, B, Bend, settings)};
|
||||
if (false == report.ok)
|
||||
{
|
||||
m_status = report.why;
|
||||
@@ -1884,16 +1885,16 @@ void plan_panel::join_ends(TrackId const A, int const Aend, TrackId const B, int
|
||||
|
||||
// whichever track was left standing is the one to select afterwards, and it is not
|
||||
// always the first clicked - the other one may have been the only one that could turn
|
||||
auto const survivor{maj0sted::editor::find_track(m_document, A) != nullptr ? A : B};
|
||||
auto const survivor{editor::plan::find_track(m_document, A) != nullptr ? A : B};
|
||||
|
||||
char status[256];
|
||||
std::snprintf(status, sizeof(status), "polaczone: %s, %d odcinkow zlanych w jeden, %d rozjazdow przeniesionych%s", maj0sted::editor::join_kind_name(report.join.kind), report.fused,
|
||||
std::snprintf(status, sizeof(status), "polaczone: %s, %d odcinkow zlanych w jeden, %d rozjazdow przeniesionych%s", editor::plan::join_kind_name(report.join.kind), report.fused,
|
||||
report.turnouts, report.reversed ? ", jeden tor czytany od drugiego konca" : "");
|
||||
m_status = status;
|
||||
m_track = survivor;
|
||||
m_pick_join = 0;
|
||||
m_join_first = TrackId::none;
|
||||
auto const *joined{maj0sted::editor::find_track(m_document, survivor)};
|
||||
auto const *joined{editor::plan::find_track(m_document, survivor)};
|
||||
if (joined != nullptr)
|
||||
{
|
||||
std::snprintf(m_namebuf, sizeof(m_namebuf), "%s", joined->name.c_str());
|
||||
@@ -1902,7 +1903,7 @@ void plan_panel::join_ends(TrackId const A, int const Aend, TrackId const B, int
|
||||
solve();
|
||||
}
|
||||
|
||||
maj0sted::domain::geometry::Pose plan_panel::join_pose(maj0sted::editor::SolvedTrack const &Track_, int const End_, bool const Leaving_)
|
||||
editor::plan::geometry::Pose plan_panel::join_pose(editor::plan::SolvedTrack const &Track_, int const End_, bool const Leaving_)
|
||||
{
|
||||
auto pose{End_ == 1 ? Track_.end : Track_.start};
|
||||
// what is laid runs out of one end and into the other, so an end that points the
|
||||
@@ -1916,7 +1917,7 @@ maj0sted::domain::geometry::Pose plan_panel::join_pose(maj0sted::editor::SolvedT
|
||||
return pose;
|
||||
}
|
||||
|
||||
bool plan_panel::loose_end(maj0sted::editor::SolvedTrack const &Track_, int const End_) const
|
||||
bool plan_panel::loose_end(editor::plan::SolvedTrack const &Track_, int const End_) const
|
||||
{
|
||||
if (Track_.elements.empty())
|
||||
{
|
||||
@@ -1925,8 +1926,8 @@ bool plan_panel::loose_end(maj0sted::editor::SolvedTrack const &Track_, int cons
|
||||
if (End_ == 0)
|
||||
{
|
||||
// a track hanging on a turnout's port starts on the frog, and that is not a loose end
|
||||
auto const *authored{maj0sted::editor::find_track(m_document, Track_.id)};
|
||||
if (authored == nullptr || false == std::holds_alternative<maj0sted::editor::AtPose>(authored->anchor))
|
||||
auto const *authored{editor::plan::find_track(m_document, Track_.id)};
|
||||
if (authored == nullptr || false == std::holds_alternative<editor::plan::AtPose>(authored->anchor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2050,7 +2051,7 @@ void plan_panel::render_elements()
|
||||
// and to nothing else, so the track is re-laid along the lines its straights already stood on.
|
||||
// a change of length is what it says it is: the element gets longer, and what follows moves
|
||||
auto refit{false};
|
||||
auto const *solved{maj0sted::editor::find_track(m_solution, track->id)};
|
||||
auto const *solved{editor::plan::find_track(m_solution, track->id)};
|
||||
|
||||
for (std::size_t i = 0; i < track->elements.size(); ++i)
|
||||
{
|
||||
@@ -2111,7 +2112,7 @@ void plan_panel::render_elements()
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputDouble(element.kind == Kind::Clothoid ? "R na koncu [m]" : "promien [m]", &element.radius, 10.0, 100.0, "%.2f"))
|
||||
{
|
||||
maj0sted::editor::sync_joint(*track, i);
|
||||
editor::plan::sync_joint(*track, i);
|
||||
dirty = true;
|
||||
refit = true;
|
||||
}
|
||||
@@ -2124,7 +2125,7 @@ void plan_panel::render_elements()
|
||||
if (ImGui::Combo("skret", &hand, "w lewo\0w prawo\0"))
|
||||
{
|
||||
element.hand = hand == 0 ? 1 : -1;
|
||||
maj0sted::editor::sync_joint(*track, i);
|
||||
editor::plan::sync_joint(*track, i);
|
||||
dirty = true;
|
||||
refit = true;
|
||||
}
|
||||
@@ -2191,7 +2192,7 @@ void plan_panel::render_elements()
|
||||
// prosta krzyzownicowa, in the order they are laid from PR. every length is the catalogue's -
|
||||
// nothing here is fitted, so the skos and the catalogue length are checked against what the list
|
||||
// works out to and the difference is said out loud
|
||||
void plan_panel::render_turnout_type(maj0sted::editor::TurnoutType &Type_)
|
||||
void plan_panel::render_turnout_type(editor::plan::TurnoutType &Type_)
|
||||
{
|
||||
auto dirty{false};
|
||||
|
||||
@@ -2290,7 +2291,7 @@ void plan_panel::render_turnout_type(maj0sted::editor::TurnoutType &Type_)
|
||||
}
|
||||
if (ImGui::SmallButton("dodaj odcinek"))
|
||||
{
|
||||
Type_.pieces.push_back(maj0sted::editor::TurnoutPieceSpec{2, 1.0, 0.0, 0.0, 0.0});
|
||||
Type_.pieces.push_back(editor::plan::TurnoutPieceSpec{2, 1.0, 0.0, 0.0, 0.0});
|
||||
m_sel_piece = static_cast<int>(Type_.pieces.size()) - 1;
|
||||
dirty = true;
|
||||
}
|
||||
@@ -2319,7 +2320,7 @@ void plan_panel::render_turnout_type(maj0sted::editor::TurnoutType &Type_)
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
auto const laid{maj0sted::domain::lay_turnout(maj0sted::domain::geometry::Pose{0.0, 0.0, 1.0, 0.0}, maj0sted::editor::to_domain(Type_, 1))};
|
||||
auto const laid{editor::plan::lay_turnout(editor::plan::geometry::Pose{0.0, 0.0, 1.0, 0.0}, editor::plan::to_domain(Type_, 1))};
|
||||
if (false == laid.valid)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.55f, 0.55f, 1.0f), "lista nie da sie polozyc: dlugosci > 0, promienie >= 0");
|
||||
@@ -2370,9 +2371,9 @@ void plan_panel::render_template_window()
|
||||
|
||||
m_turnout_type = std::clamp(m_turnout_type, 0, static_cast<int>(m_document.turnout_types.size()) - 1);
|
||||
auto const &type{m_document.turnout_types[static_cast<std::size_t>(m_turnout_type)]};
|
||||
auto const start{maj0sted::domain::geometry::Pose{0.0, 0.0, 1.0, 0.0}};
|
||||
auto const start{editor::plan::geometry::Pose{0.0, 0.0, 1.0, 0.0}};
|
||||
auto const bend{m_tpl_bend_radius > 1.0 ? (m_tpl_bend_hand == 0 ? 1.0 : -1.0) / m_tpl_bend_radius : 0.0};
|
||||
auto const laid{maj0sted::domain::lay_turnout(start, maj0sted::editor::to_domain(type, 1), bend)};
|
||||
auto const laid{editor::plan::lay_turnout(start, editor::plan::to_domain(type, 1), bend)};
|
||||
if (false == laid.valid)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.55f, 0.55f, 1.0f), bend != 0.0 ? "typu \"%s\" nie da sie wygiac na taki luk" : "typ \"%s\" nie da sie polozyc",
|
||||
@@ -2469,11 +2470,11 @@ void plan_panel::render_template_window()
|
||||
drawlist->PushClipRect(origin, ImVec2(origin.x + w, origin.y + h), true);
|
||||
drawlist->AddRectFilled(origin, ImVec2(origin.x + w, origin.y + h), IM_COL32(16, 20, 26, 255));
|
||||
|
||||
auto const half{maj0sted::domain::kHalfGauge};
|
||||
auto const half{editor::plan::kHalfGauge};
|
||||
|
||||
// the tor zasadniczy, and the opornica the blade lies against. bent, it is an arc like any other,
|
||||
// so it is walked rather than ruled
|
||||
auto const through_at{[&](double const Station_) { return maj0sted::domain::geometry::layout_segment(bend, bend, Station_, start, nullptr); }};
|
||||
auto const through_at{[&](double const Station_) { return editor::plan::geometry::layout_segment(bend, bend, Station_, start, nullptr); }};
|
||||
auto const through_line{[&](double const From_, double const To_, double const Offset_) {
|
||||
auto const steps{bend == 0.0 ? 1 : 96};
|
||||
std::vector<ImVec2> line;
|
||||
@@ -2505,7 +2506,7 @@ void plan_panel::render_template_window()
|
||||
// dziob, not the arc. from A on the blade lies on the arc and the line goes solid
|
||||
static ImU32 const part_colour[]{IM_COL32(150, 175, 210, 255), IM_COL32(120, 235, 255, 255), IM_COL32(240, 240, 240, 255), IM_COL32(255, 200, 110, 255)};
|
||||
auto const hand{laid.frog.y >= 0.0 ? 1 : -1};
|
||||
auto const rail_line{[&](std::vector<maj0sted::domain::geometry::XY> const &Points_, int const Side_) {
|
||||
auto const rail_line{[&](std::vector<editor::plan::geometry::XY> const &Points_, int const Side_) {
|
||||
std::vector<ImVec2> rail;
|
||||
rail.reserve(Points_.size());
|
||||
for (std::size_t i = 0; i < Points_.size(); ++i)
|
||||
@@ -2526,8 +2527,8 @@ void plan_panel::render_template_window()
|
||||
|
||||
// the sampling step is metres and the dziob is centimetres, so the two stretches have to part on
|
||||
// the exact distance rather than on whichever sample happens to fall nearest it
|
||||
auto const cut_at{[](std::vector<maj0sted::domain::geometry::XY> const &Points_, double const Distance_,
|
||||
std::vector<maj0sted::domain::geometry::XY> &Head_, std::vector<maj0sted::domain::geometry::XY> &Tail_) {
|
||||
auto const cut_at{[](std::vector<editor::plan::geometry::XY> const &Points_, double const Distance_,
|
||||
std::vector<editor::plan::geometry::XY> &Head_, std::vector<editor::plan::geometry::XY> &Tail_) {
|
||||
auto travelled{0.0};
|
||||
for (std::size_t i = 0; i < Points_.size(); ++i)
|
||||
{
|
||||
@@ -2542,7 +2543,7 @@ void plan_panel::render_template_window()
|
||||
{
|
||||
auto const &back{Points_[i - 1]};
|
||||
auto const share{(Distance_ - (travelled - step)) / step};
|
||||
maj0sted::domain::geometry::XY const meeting{back.x + (Points_[i].x - back.x) * share, back.y + (Points_[i].y - back.y) * share};
|
||||
editor::plan::geometry::XY const meeting{back.x + (Points_[i].x - back.x) * share, back.y + (Points_[i].y - back.y) * share};
|
||||
Head_.push_back(meeting);
|
||||
Tail_.push_back(meeting);
|
||||
}
|
||||
@@ -2550,7 +2551,7 @@ void plan_panel::render_template_window()
|
||||
}
|
||||
}};
|
||||
|
||||
auto const draw_run{[&](std::vector<maj0sted::domain::geometry::XY> const &Points_, ImU32 const Colour_, bool const Theoretical_) {
|
||||
auto const draw_run{[&](std::vector<editor::plan::geometry::XY> const &Points_, ImU32 const Colour_, bool const Theoretical_) {
|
||||
if (Points_.size() < 2)
|
||||
{
|
||||
return;
|
||||
@@ -2603,11 +2604,11 @@ void plan_panel::render_template_window()
|
||||
auto const s{std::sin(segment.turn_in)};
|
||||
walker = {walker.x, walker.y, walker.hx * c - walker.hy * s, walker.hx * s + walker.hy * c};
|
||||
}
|
||||
std::vector<maj0sted::domain::geometry::XY> points;
|
||||
walker = maj0sted::domain::geometry::layout_segment(segment.k0, segment.k1, segment.length, walker, &points);
|
||||
std::vector<editor::plan::geometry::XY> points;
|
||||
walker = editor::plan::geometry::layout_segment(segment.k0, segment.k1, segment.length, walker, &points);
|
||||
auto const from{station};
|
||||
station += segment.length;
|
||||
if (segment.part == maj0sted::domain::TurnoutPart::Blade)
|
||||
if (segment.part == editor::plan::TurnoutPart::Blade)
|
||||
{
|
||||
heel = station;
|
||||
}
|
||||
@@ -2625,8 +2626,8 @@ void plan_panel::render_template_window()
|
||||
draw_run(points, colour, false);
|
||||
continue;
|
||||
}
|
||||
std::vector<maj0sted::domain::geometry::XY> ahead;
|
||||
std::vector<maj0sted::domain::geometry::XY> behind;
|
||||
std::vector<editor::plan::geometry::XY> ahead;
|
||||
std::vector<editor::plan::geometry::XY> behind;
|
||||
cut_at(points, upto, behind, ahead);
|
||||
draw_run(behind, colour, true);
|
||||
draw_run(ahead, colour, false);
|
||||
@@ -2680,7 +2681,7 @@ void plan_panel::render_template_window()
|
||||
// the construction points, from the very list the solver reports for a placement
|
||||
if (m_tpl_marks)
|
||||
{
|
||||
auto marks{maj0sted::editor::turnout_marks(laid, start)};
|
||||
auto marks{editor::plan::turnout_marks(laid, start)};
|
||||
std::sort(marks.begin(), marks.end(), [](auto const &Left_, auto const &Right_) { return Left_.station < Right_.station; });
|
||||
|
||||
auto level{0};
|
||||
@@ -2820,8 +2821,8 @@ void plan_panel::render_turnouts()
|
||||
auto &placement{m_document.turnouts[i]};
|
||||
ImGui::PushID(static_cast<int>(1000 + i));
|
||||
|
||||
auto const *solved{maj0sted::editor::find_turnout(m_solution, placement.id)};
|
||||
auto const *on{maj0sted::editor::find_track(m_document, placement.on)};
|
||||
auto const *solved{editor::plan::find_turnout(m_solution, placement.id)};
|
||||
auto const *on{editor::plan::find_track(m_document, placement.on)};
|
||||
char label[200];
|
||||
std::snprintf(label, sizeof(label), "%s na %s, km %.1f%s", placement.type.c_str(), on != nullptr ? on->name.c_str() : "?", placement.station,
|
||||
(solved != nullptr && solved->valid) ? "" : " (nie da sie zlozyc)");
|
||||
@@ -2891,11 +2892,11 @@ void plan_panel::render_turnouts()
|
||||
// mean guessing kilometres by hand: the nearest element long enough to hold it, whole
|
||||
if (solved != nullptr && solved->valid)
|
||||
{
|
||||
auto const *through{maj0sted::editor::find_track(m_solution, placement.on)};
|
||||
auto const *through{editor::plan::find_track(m_solution, placement.on)};
|
||||
double curvature{0.0};
|
||||
double behind{0.0};
|
||||
double ahead{0.0};
|
||||
auto const room{through != nullptr && maj0sted::editor::steady_room(*through, placement.station, curvature, behind, ahead)
|
||||
auto const room{through != nullptr && editor::plan::steady_room(*through, placement.station, curvature, behind, ahead)
|
||||
? (placement.facing ? ahead : behind)
|
||||
: -1.0};
|
||||
if (through != nullptr && room < solved->through_length - 1e-6)
|
||||
@@ -2975,7 +2976,7 @@ void plan_panel::render_diagnostics()
|
||||
auto const &diagnostic{m_solution.diagnostics[i]};
|
||||
ImGui::PushID(static_cast<int>(2000 + i));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.60f, 0.55f, 1.0f));
|
||||
auto const *track{maj0sted::editor::find_track(m_document, diagnostic.track)};
|
||||
auto const *track{editor::plan::find_track(m_document, diagnostic.track)};
|
||||
if (ImGui::Selectable(diagnostic.text.c_str()))
|
||||
{
|
||||
if (diagnostic.track != TrackId::none)
|
||||
@@ -3019,12 +3020,12 @@ void plan_panel::render_storage()
|
||||
m_document.georeferenced = Global.scenery_georeferenced;
|
||||
m_document.origin_x = Global.scenery_origin.x;
|
||||
m_document.origin_y = Global.scenery_origin.y;
|
||||
m_status = maj0sted::io::save(m_document, m_path) ? "saved to " + std::string(m_path) : "could not write " + std::string(m_path);
|
||||
m_status = editor::plan::io::save(m_document, m_path) ? "saved to " + std::string(m_path) : "could not write " + std::string(m_path);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load"))
|
||||
{
|
||||
auto const loaded{maj0sted::io::load(m_path)};
|
||||
auto const loaded{editor::plan::io::load(m_path)};
|
||||
if (loaded)
|
||||
{
|
||||
m_document = *loaded;
|
||||
@@ -3110,7 +3111,7 @@ void plan_panel::export_scn(bool const Run_)
|
||||
// where a plan exports to belongs to the plan: say it once and it is there the next time the
|
||||
// project is opened, whichever MaSzyna it was drawn for
|
||||
m_document.scn_path = m_scn_path;
|
||||
maj0sted::io::ScnExportOptions opt;
|
||||
editor::plan::io::ScnExportOptions opt;
|
||||
opt.origin_east = Global.scenery_origin.x;
|
||||
opt.origin_north = Global.scenery_origin.y;
|
||||
opt.trainset = m_scn_trainset;
|
||||
@@ -3120,7 +3121,7 @@ void plan_panel::export_scn(bool const Run_)
|
||||
m_status = "could not write " + std::string(m_scn_path);
|
||||
return;
|
||||
}
|
||||
auto const result{maj0sted::io::export_scn(m_document, m_solution, opt, out)};
|
||||
auto const result{editor::plan::io::export_scn(m_document, m_solution, opt, out)};
|
||||
out.flush();
|
||||
if (false == out.good())
|
||||
{
|
||||
|
||||
@@ -8,16 +8,17 @@ http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "imgui/imgui.h"
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
|
||||
export module eu07.application.planpanel;
|
||||
import eu07.editor.plan_tilecache;
|
||||
import eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_model;
|
||||
import eu07.editor.plan_render;
|
||||
import eu07.editor.plan_solution;
|
||||
import eu07.editor.plan_scn_export;
|
||||
import eu07.application.uilayer;
|
||||
import eu07.editor.orthophoto;
|
||||
|
||||
@@ -25,7 +26,7 @@ export {
|
||||
|
||||
// track layout tool. the drawing happens in the viewport, which the tool puts into a top-down
|
||||
// orthographic plan view; the panel holds only the controls. none of the geometry is computed here:
|
||||
// maj0sted solves the whole document and the panel paints what comes back.
|
||||
// the plan library solves the whole document and the panel paints what comes back.
|
||||
//
|
||||
// a track is a chain of elements, and every one of them says outright what it is: prosta of so many
|
||||
// metres, luk of radius R turning so far, krzywa przejsciowa easing to R over so many metres. there
|
||||
@@ -45,13 +46,13 @@ class plan_panel : public ui_panel
|
||||
void render_contents() override;
|
||||
|
||||
private:
|
||||
using Document = maj0sted::editor::Document;
|
||||
using Element = maj0sted::editor::Element;
|
||||
using ElementId = maj0sted::editor::ElementId;
|
||||
using Solution = maj0sted::editor::Solution;
|
||||
using Track = maj0sted::editor::Track;
|
||||
using TrackId = maj0sted::editor::TrackId;
|
||||
using TurnoutId = maj0sted::editor::TurnoutId;
|
||||
using Document = editor::plan::Document;
|
||||
using Element = editor::plan::Element;
|
||||
using ElementId = editor::plan::ElementId;
|
||||
using Solution = editor::plan::Solution;
|
||||
using Track = editor::plan::Track;
|
||||
using TrackId = editor::plan::TrackId;
|
||||
using TurnoutId = editor::plan::TurnoutId;
|
||||
|
||||
// panels
|
||||
void render_toolbar();
|
||||
@@ -60,7 +61,7 @@ class plan_panel : public ui_panel
|
||||
// joining two tracks into one: the radius a join may bend at, and the button that starts
|
||||
// pointing at the ends to join. everything else about it happens on the map
|
||||
void render_join();
|
||||
void render_turnout_type(maj0sted::editor::TurnoutType &Type_);
|
||||
void render_turnout_type(editor::plan::TurnoutType &Type_);
|
||||
// the catalogue drawing of the chosen type, in its own window: the diverging path piece by
|
||||
// piece, both tracks' rails, and the points a drawing is dimensioned from
|
||||
void render_template_window();
|
||||
@@ -94,7 +95,7 @@ class plan_panel : public ui_panel
|
||||
// taken off the straight before it - which leaves the track ending on the click before
|
||||
void drop_last_vertex();
|
||||
// appends one element to the end of the current track, exactly as asked for
|
||||
void append_element(maj0sted::editor::Kind const Kind);
|
||||
void append_element(editor::plan::Kind const Kind);
|
||||
void drop_last_element();
|
||||
void delete_selected_element();
|
||||
void place_turnout_at(TrackId const On, double const Wx, double const Wy);
|
||||
@@ -117,10 +118,10 @@ class plan_panel : public ui_panel
|
||||
bool hit_track_end(ImVec2 const &Mouse, float const Tolerance, TrackId &OutTrack, int &OutEnd) const;
|
||||
// whether that end is loose at all: one that already stands on another track's end is joined
|
||||
// to it, and there is nothing to join twice
|
||||
bool loose_end(maj0sted::editor::SolvedTrack const &Track_, int const End_) const;
|
||||
bool loose_end(editor::plan::SolvedTrack const &Track_, int const End_) const;
|
||||
// how an end reads for a join laid out of it or into it: one facing the wrong way for that
|
||||
// is taken backwards, which is what turning the track round would do to it anyway
|
||||
static maj0sted::domain::geometry::Pose join_pose(maj0sted::editor::SolvedTrack const &Track_, int const End_, bool const Leaving_);
|
||||
static editor::plan::geometry::Pose join_pose(editor::plan::SolvedTrack const &Track_, int const End_, bool const Leaving_);
|
||||
// lays the join and leaves one track, saying what it cost
|
||||
void join_ends(TrackId const A, int const Aend, TrackId const B, int const Bend);
|
||||
// nearest station along a solved track's axis, for placing and sliding turnouts
|
||||
@@ -130,7 +131,7 @@ class plan_panel : public ui_panel
|
||||
// members
|
||||
Document m_document;
|
||||
Solution m_solution;
|
||||
std::vector<maj0sted::editor::PlanPolyline> m_rails;
|
||||
std::vector<editor::plan::PlanPolyline> m_rails;
|
||||
|
||||
TrackId m_track{};
|
||||
ElementId m_sel_element{};
|
||||
@@ -210,10 +211,10 @@ class plan_panel : public ui_panel
|
||||
double m_mapviewy{0.0};
|
||||
double m_mapscale{0.0};
|
||||
|
||||
editor::wms_image m_topomap{maj0sted::editor::WmsConfig::geoportal_topo()};
|
||||
editor::wms_image m_topomap{editor::plan::WmsConfig::geoportal_topo()};
|
||||
editor::orthophoto_source m_ortho;
|
||||
// 1 km topo cells for views wider than a kilometre (orto's 100 m grid is too dense there)
|
||||
editor::orthophoto_source m_topo{maj0sted::editor::WmsConfig::geoportal_topo(), 1000, "topo"};
|
||||
editor::orthophoto_source m_topo{editor::plan::WmsConfig::geoportal_topo(), 1000, "topo"};
|
||||
bool m_showortho{true};
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ module;
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
#include <glad/glad.h>
|
||||
#include "stb/stb_image.h"
|
||||
#ifdef _WIN32
|
||||
@@ -26,6 +25,7 @@ module;
|
||||
#endif
|
||||
|
||||
module eu07.editor.orthophoto;
|
||||
import eu07.editor.plan_tilecache;
|
||||
import eu07.utilities.logs;
|
||||
|
||||
/*
|
||||
@@ -209,9 +209,9 @@ bool http_get(std::string const &Url, std::vector<std::uint8_t> &Bytes, std::str
|
||||
|
||||
} // namespace
|
||||
|
||||
orthophoto_source::orthophoto_source(maj0sted::editor::WmsConfig Config, int const Gridzoom, std::string Cachesubdir)
|
||||
orthophoto_source::orthophoto_source(editor::plan::WmsConfig Config, int const Gridzoom, std::string Cachesubdir)
|
||||
: m_service([&] {
|
||||
if (Gridzoom >= maj0sted::editor::TileGrid::kCellZoomMin)
|
||||
if (Gridzoom >= editor::plan::TileGrid::kCellZoomMin)
|
||||
{
|
||||
Config.tile_pixels = 512; // coarse cells do not need orto's 1024 px
|
||||
}
|
||||
@@ -221,7 +221,7 @@ orthophoto_source::orthophoto_source(maj0sted::editor::WmsConfig Config, int con
|
||||
{
|
||||
// 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::editor::TileService::DoneFn Done) {
|
||||
m_service.set_fetcher([this](std::string const &Url, editor::plan::TileService::DoneFn Done) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_queued.push_back({Url, std::move(Done), false, {}, {}});
|
||||
@@ -291,7 +291,7 @@ void orthophoto_source::worker()
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::editor::TileBBox const &View, int const Maxtiles)
|
||||
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(editor::plan::TileBBox const &View, int const Maxtiles)
|
||||
{
|
||||
// hand finished transfers to the library here, on the caller's thread
|
||||
std::deque<transfer> arrived;
|
||||
@@ -311,7 +311,7 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
// 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))
|
||||
for (auto const &key : editor::plan::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))
|
||||
@@ -320,13 +320,13 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
}
|
||||
|
||||
auto const status = m_service.request(key);
|
||||
if (status == maj0sted::editor::TileStatus::Failed)
|
||||
if (status == editor::plan::TileStatus::Failed)
|
||||
{
|
||||
// counted once per tile, not once per frame it stays unavailable
|
||||
m_failedkeys.insert(key);
|
||||
continue;
|
||||
}
|
||||
if (status != maj0sted::editor::TileStatus::Ready)
|
||||
if (status != editor::plan::TileStatus::Ready)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -342,7 +342,7 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
// 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) {
|
||||
auto const overlaps = [](editor::plan::TileBBox const &Left, editor::plan::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;
|
||||
};
|
||||
|
||||
@@ -354,7 +354,7 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const box = maj0sted::editor::TileGrid::bbox(key);
|
||||
auto const box = editor::plan::TileGrid::bbox(key);
|
||||
if (overlaps(box, View))
|
||||
{
|
||||
tiles.push_back({box, texture});
|
||||
@@ -364,7 +364,7 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
|
||||
return tiles;
|
||||
}
|
||||
|
||||
unsigned int orthophoto_source::upload(maj0sted::editor::TileKey const &Key)
|
||||
unsigned int orthophoto_source::upload(editor::plan::TileKey const &Key)
|
||||
{
|
||||
auto const *image = m_service.peek(Key);
|
||||
if (image == nullptr || image->bytes.empty())
|
||||
@@ -397,12 +397,12 @@ unsigned int orthophoto_source::upload(maj0sted::editor::TileKey const &Key)
|
||||
return texture;
|
||||
}
|
||||
|
||||
std::string orthophoto_source::tile_path(maj0sted::editor::TileKey const &Key) const
|
||||
std::string orthophoto_source::tile_path(editor::plan::TileKey const &Key) const
|
||||
{
|
||||
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::editor::TileKey const &Key)
|
||||
bool orthophoto_source::load_from_disk(editor::plan::TileKey const &Key)
|
||||
{
|
||||
std::ifstream file(tile_path(Key), std::ios::binary);
|
||||
if (false == file.is_open())
|
||||
@@ -421,7 +421,7 @@ bool orthophoto_source::load_from_disk(maj0sted::editor::TileKey const &Key)
|
||||
return true;
|
||||
}
|
||||
|
||||
void orthophoto_source::save_to_disk(maj0sted::editor::TileKey const &Key)
|
||||
void orthophoto_source::save_to_disk(editor::plan::TileKey const &Key)
|
||||
{
|
||||
auto const *image = m_service.peek(Key);
|
||||
if (image == nullptr || image->bytes.empty())
|
||||
@@ -484,7 +484,7 @@ unsigned int upload_image(std::vector<std::uint8_t> const &Bytes)
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool same_box(maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox const &Right)
|
||||
bool same_box(editor::plan::TileBBox const &Left, editor::plan::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);
|
||||
@@ -492,7 +492,7 @@ bool same_box(maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox
|
||||
|
||||
} // namespace
|
||||
|
||||
wms_image::wms_image(maj0sted::editor::WmsConfig Config) : m_config(std::move(Config))
|
||||
wms_image::wms_image(editor::plan::WmsConfig Config) : m_config(std::move(Config))
|
||||
{
|
||||
m_worker = std::thread([this]() { worker(); });
|
||||
}
|
||||
@@ -547,7 +547,7 @@ void wms_image::worker()
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int wms_image::texture_for(maj0sted::editor::TileBBox const &Box, int const Pixels)
|
||||
unsigned int wms_image::texture_for(editor::plan::TileBBox const &Box, int const Pixels)
|
||||
{
|
||||
// a finished download becomes the texture on this thread, where the graphics API is safe to touch
|
||||
{
|
||||
@@ -583,7 +583,7 @@ unsigned int wms_image::texture_for(maj0sted::editor::TileBBox const &Box, int c
|
||||
config.tile_pixels = Pixels;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_url = maj0sted::editor::wms_getmap_url(config, Box);
|
||||
m_url = editor::plan::wms_getmap_url(config, Box);
|
||||
m_haswork = true;
|
||||
}
|
||||
m_loading = true;
|
||||
|
||||
@@ -17,9 +17,9 @@ module;
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
|
||||
export module eu07.editor.orthophoto;
|
||||
import eu07.editor.plan_tilecache;
|
||||
|
||||
export {
|
||||
|
||||
@@ -39,13 +39,13 @@ class orthophoto_source
|
||||
public:
|
||||
struct ready_tile
|
||||
{
|
||||
maj0sted::editor::TileBBox box;
|
||||
editor::plan::TileBBox box;
|
||||
unsigned int texture{0};
|
||||
};
|
||||
|
||||
// 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,
|
||||
explicit orthophoto_source(editor::plan::WmsConfig Config = editor::plan::WmsConfig::geoportal_ortho(), int const Gridzoom = editor::plan::TileGrid::kLevel,
|
||||
std::string Cachesubdir = "orto");
|
||||
~orthophoto_source();
|
||||
orthophoto_source(orthophoto_source const &) = delete;
|
||||
@@ -54,7 +54,7 @@ class orthophoto_source
|
||||
// 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);
|
||||
std::vector<ready_tile> collect(editor::plan::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
|
||||
@@ -65,7 +65,7 @@ class orthophoto_source
|
||||
struct transfer
|
||||
{
|
||||
std::string url;
|
||||
maj0sted::editor::TileService::DoneFn done;
|
||||
editor::plan::TileService::DoneFn done;
|
||||
bool ok{false};
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::string content_type;
|
||||
@@ -73,17 +73,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::editor::TileKey const &Key);
|
||||
unsigned int upload(editor::plan::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
|
||||
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);
|
||||
std::string tile_path(editor::plan::TileKey const &Key) const;
|
||||
bool load_from_disk(editor::plan::TileKey const &Key);
|
||||
void save_to_disk(editor::plan::TileKey const &Key);
|
||||
// members
|
||||
maj0sted::editor::TileService m_service;
|
||||
int m_gridzoom{maj0sted::editor::TileGrid::kLevel};
|
||||
editor::plan::TileService m_service;
|
||||
int m_gridzoom{editor::plan::TileGrid::kLevel};
|
||||
std::string m_cachesubdir{"orto"};
|
||||
std::unordered_map<maj0sted::editor::TileKey, unsigned int, maj0sted::editor::TileKeyHash> m_textures;
|
||||
std::unordered_map<editor::plan::TileKey, unsigned int, editor::plan::TileKeyHash> m_textures;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_wakeup;
|
||||
@@ -92,7 +92,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::unordered_set<maj0sted::editor::TileKey, maj0sted::editor::TileKeyHash> m_failedkeys;
|
||||
std::unordered_set<editor::plan::TileKey, editor::plan::TileKeyHash> m_failedkeys;
|
||||
};
|
||||
|
||||
// a single WMS image covering an arbitrary box, for views too coarse for the tile grid - the whole
|
||||
@@ -102,25 +102,25 @@ class wms_image
|
||||
{
|
||||
|
||||
public:
|
||||
explicit wms_image(maj0sted::editor::WmsConfig Config);
|
||||
explicit wms_image(editor::plan::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::editor::TileBBox const &Box, int const Pixels);
|
||||
unsigned int texture_for(editor::plan::TileBBox const &Box, int const Pixels);
|
||||
// the box the returned texture actually covers
|
||||
maj0sted::editor::TileBBox const &covered() const { return m_covered; }
|
||||
editor::plan::TileBBox const &covered() const { return m_covered; }
|
||||
bool loading() const { return m_loading.load(); }
|
||||
|
||||
private:
|
||||
void worker();
|
||||
|
||||
maj0sted::editor::WmsConfig m_config;
|
||||
maj0sted::editor::TileBBox m_requested{};
|
||||
maj0sted::editor::TileBBox m_inflightbox{};
|
||||
maj0sted::editor::TileBBox m_covered{};
|
||||
editor::plan::WmsConfig m_config;
|
||||
editor::plan::TileBBox m_requested{};
|
||||
editor::plan::TileBBox m_inflightbox{};
|
||||
editor::plan::TileBBox m_covered{};
|
||||
unsigned int m_texture{0};
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
#include "maj0sted/editor/chain.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
module eu07.editor.plan_chain;
|
||||
|
||||
namespace editor::plan {
|
||||
namespace {
|
||||
|
||||
constexpr double kEps = 1e-9;
|
||||
|
||||
using domain::geometry::Pose;
|
||||
using geometry::Pose;
|
||||
|
||||
Diagnostic make(Code code, TrackId track, ElementId element, std::string text) {
|
||||
Diagnostic diagnostic;
|
||||
@@ -113,8 +122,8 @@ TrackOutcome lay_track(const Document& /*document*/, const Track& track, const P
|
||||
solved.k1 = k1;
|
||||
solved.length = element.length;
|
||||
|
||||
std::vector<domain::geometry::XY> points;
|
||||
walker = domain::geometry::layout_segment(k0, k1, element.length, walker, &points);
|
||||
std::vector<geometry::XY> points;
|
||||
walker = geometry::layout_segment(k0, k1, element.length, walker, &points);
|
||||
solved.end = walker;
|
||||
solved.points.reserve(points.size());
|
||||
for (const auto& point : points) {
|
||||
@@ -138,4 +147,4 @@ TrackOutcome lay_track(const Document& /*document*/, const Track& track, const P
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,12 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/segment_layout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
export module eu07.editor.plan_chain;
|
||||
export import eu07.editor.plan_segment;
|
||||
export import eu07.editor.plan_model;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// What laying one track produced, and everything that went wrong doing it.
|
||||
struct TrackOutcome {
|
||||
@@ -26,6 +36,8 @@ struct TrackOutcome {
|
||||
/// ends the chain there; everything before it is still returned and still
|
||||
/// drawable.
|
||||
[[nodiscard]] TrackOutcome lay_track(const Document& document, const Track& track,
|
||||
const domain::geometry::Pose& start);
|
||||
const geometry::Pose& start);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,5 +1,15 @@
|
||||
#include "maj0sted/io/document_io.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@@ -9,10 +19,12 @@
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::io {
|
||||
module eu07.editor.plan_document_io;
|
||||
|
||||
namespace editor::plan::io {
|
||||
namespace {
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
constexpr int kVersion = 7;
|
||||
constexpr int kNoExportPath = 6; // before a plan remembered where it exports to
|
||||
@@ -553,4 +565,4 @@ std::optional<Document> load(const std::string& path) {
|
||||
return deserialize(buffer.str());
|
||||
}
|
||||
|
||||
} // namespace maj0sted::io
|
||||
} // namespace editor::plan::io
|
||||
@@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
export module eu07.editor.plan_document_io;
|
||||
export import eu07.editor.plan_model;
|
||||
|
||||
namespace maj0sted::io {
|
||||
export {
|
||||
|
||||
namespace editor::plan::io {
|
||||
|
||||
/// The `.m0s` format, version 3.
|
||||
///
|
||||
@@ -18,14 +28,16 @@ namespace maj0sted::io {
|
||||
///
|
||||
/// Doubles go through std::to_chars/from_chars, so a round trip is exact and
|
||||
/// does not depend on the locale.
|
||||
[[nodiscard]] std::string serialize(const editor::Document& document);
|
||||
[[nodiscard]] std::string serialize(const plan::Document& document);
|
||||
|
||||
/// Returns nothing when @p text is not a version 3 document or is malformed.
|
||||
[[nodiscard]] std::optional<editor::Document> deserialize(const std::string& text);
|
||||
[[nodiscard]] std::optional<plan::Document> deserialize(const std::string& text);
|
||||
|
||||
[[nodiscard]] std::string default_project_path();
|
||||
bool save(const editor::Document& document, const std::string& path = default_project_path());
|
||||
[[nodiscard]] std::optional<editor::Document> load(
|
||||
bool save(const plan::Document& document, const std::string& path = default_project_path());
|
||||
[[nodiscard]] std::optional<plan::Document> load(
|
||||
const std::string& path = default_project_path());
|
||||
|
||||
} // namespace maj0sted::io
|
||||
} // namespace editor::plan::io
|
||||
|
||||
} // export
|
||||
@@ -1,8 +1,21 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
module eu07.editor.plan_join;
|
||||
|
||||
// TYMCZASOWA ZASLEPKA - patrz komentarz w join.hpp.
|
||||
|
||||
#include "maj0sted/editor/join.hpp"
|
||||
|
||||
namespace maj0sted::editor {
|
||||
namespace editor::plan {
|
||||
|
||||
namespace {
|
||||
constexpr const char* kMissing = "scalanie koncow niedostepne: brak modulu join";
|
||||
@@ -24,7 +37,7 @@ const char* join_kind_name(JoinKind kind) {
|
||||
return "nic";
|
||||
}
|
||||
|
||||
JoinPlan plan_join(const domain::geometry::Pose& leaving, const domain::geometry::Pose& arriving,
|
||||
JoinPlan plan_join(const geometry::Pose& leaving, const geometry::Pose& arriving,
|
||||
const JoinSettings& settings) {
|
||||
(void)leaving;
|
||||
(void)arriving;
|
||||
@@ -48,4 +61,4 @@ JoinReport join_ends(Document& document, const Solution& solution, TrackId a, in
|
||||
return report;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,4 +1,21 @@
|
||||
#pragma once
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
export module eu07.editor.plan_join;
|
||||
export import eu07.editor.plan_segment;
|
||||
export import eu07.editor.plan_model;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
export {
|
||||
|
||||
// TYMCZASOWA ZASLEPKA. Modul scalania koncow (plan_join / join_ends) nie zostal nigdy
|
||||
// zacommitowany: commit 4ddfeb02 dopisal `src/editor/join.cpp` do CMakeLists i `#include` w
|
||||
@@ -7,14 +24,9 @@
|
||||
// API, ktorego uzywa panel planu, i mowi "niedostepne" - zeby reszta edytora dala sie uruchomic.
|
||||
// Do zastapienia prawdziwym rozwiazaniem geometrii (luk-prosta-luk) i chirurgia dokumentu.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/segment_layout.hpp" // Pose
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
|
||||
namespace maj0sted::editor {
|
||||
namespace editor::plan {
|
||||
|
||||
/// Co stanelo miedzy dwoma koncami.
|
||||
enum class JoinKind {
|
||||
@@ -41,8 +53,8 @@ struct JoinPlan {
|
||||
double tightest{0.0};
|
||||
};
|
||||
|
||||
[[nodiscard]] JoinPlan plan_join(const domain::geometry::Pose& leaving,
|
||||
const domain::geometry::Pose& arriving,
|
||||
[[nodiscard]] JoinPlan plan_join(const geometry::Pose& leaving,
|
||||
const geometry::Pose& arriving,
|
||||
const JoinSettings& settings);
|
||||
|
||||
/// Co scalenie kosztowalo.
|
||||
@@ -58,4 +70,6 @@ struct JoinReport {
|
||||
[[nodiscard]] JoinReport join_ends(Document& document, const Solution& solution, TrackId a,
|
||||
int a_end, TrackId b, int b_end, const JoinSettings& settings);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,5 +1,13 @@
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <variant>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -8,16 +16,17 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
#include "maj0sted/editor/chain.hpp"
|
||||
#include "maj0sted/editor/sketch.hpp"
|
||||
module eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_turnout_geometry;
|
||||
import eu07.editor.plan_chain;
|
||||
import eu07.editor.plan_sketch;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
namespace editor::plan {
|
||||
namespace {
|
||||
|
||||
constexpr double kEps = 1e-9;
|
||||
|
||||
using domain::geometry::Pose;
|
||||
using geometry::Pose;
|
||||
|
||||
std::string num(double value, int decimals = 1) {
|
||||
char buffer[64];
|
||||
@@ -180,21 +189,21 @@ Pose rigid_move(const Pose& anchor, const Pose& from, const Pose& to) {
|
||||
// Turnouts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
domain::TurnoutPart part_of(int part) {
|
||||
plan::TurnoutPart part_of(int part) {
|
||||
switch (part) {
|
||||
case 0: return domain::TurnoutPart::PreBlade;
|
||||
case 1: return domain::TurnoutPart::Blade;
|
||||
case 3: return domain::TurnoutPart::FrogRail;
|
||||
default: return domain::TurnoutPart::Curve;
|
||||
case 0: return plan::TurnoutPart::PreBlade;
|
||||
case 1: return plan::TurnoutPart::Blade;
|
||||
case 3: return plan::TurnoutPart::FrogRail;
|
||||
default: return plan::TurnoutPart::Curve;
|
||||
}
|
||||
}
|
||||
|
||||
int mark_kind_for(domain::TurnoutPart part) {
|
||||
int mark_kind_for(plan::TurnoutPart part) {
|
||||
switch (part) {
|
||||
case domain::TurnoutPart::PreBlade: return 0; // PR
|
||||
case domain::TurnoutPart::Blade: return 1; // ostrze iglicy
|
||||
case domain::TurnoutPart::Curve: return 2; // pięta iglicy
|
||||
case domain::TurnoutPart::FrogRail: return 4; // koniec krzywej
|
||||
case plan::TurnoutPart::PreBlade: return 0; // PR
|
||||
case plan::TurnoutPart::Blade: return 1; // ostrze iglicy
|
||||
case plan::TurnoutPart::Curve: return 2; // pięta iglicy
|
||||
case plan::TurnoutPart::FrogRail: return 4; // koniec krzywej
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
@@ -203,25 +212,25 @@ int mark_kind_for(domain::TurnoutPart part) {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
domain::Turnout to_domain(const TurnoutType& type, int hand) {
|
||||
domain::Turnout turnout{
|
||||
domain::CrossingMark{type.crossing_n},
|
||||
hand >= 0 ? domain::DivergeSide::Left : domain::DivergeSide::Right,
|
||||
plan::Turnout to_domain(const TurnoutType& type, int hand) {
|
||||
plan::Turnout turnout{
|
||||
plan::CrossingMark{type.crossing_n},
|
||||
hand >= 0 ? plan::DivergeSide::Left : plan::DivergeSide::Right,
|
||||
{},
|
||||
type.length,
|
||||
domain::Blade{type.blade.tip_thickness, type.blade.nose, type.blade.railtop_width},
|
||||
plan::Blade{type.blade.tip_thickness, type.blade.nose, type.blade.railtop_width},
|
||||
};
|
||||
turnout.path.reserve(type.pieces.size());
|
||||
for (const auto& piece : type.pieces) {
|
||||
turnout.path.push_back(domain::TurnoutPiece{part_of(piece.part), piece.length,
|
||||
turnout.path.push_back(plan::TurnoutPiece{part_of(piece.part), piece.length,
|
||||
piece.radius_start, piece.radius_end,
|
||||
piece.turn_in});
|
||||
}
|
||||
return turnout;
|
||||
}
|
||||
|
||||
std::vector<TurnoutMark> turnout_marks(const domain::TurnoutGeometry& geometry,
|
||||
const domain::geometry::Pose& start) {
|
||||
std::vector<TurnoutMark> turnout_marks(const plan::TurnoutGeometry& geometry,
|
||||
const geometry::Pose& start) {
|
||||
std::vector<TurnoutMark> marks;
|
||||
if (!geometry.valid) {
|
||||
return marks;
|
||||
@@ -230,27 +239,27 @@ std::vector<TurnoutMark> turnout_marks(const domain::TurnoutGeometry& geometry,
|
||||
// PR stands whatever the path starts with, so it is said once here
|
||||
marks.push_back(TurnoutMark{start.x, start.y, 0, 0.0});
|
||||
|
||||
domain::geometry::Pose walker = start;
|
||||
geometry::Pose walker = start;
|
||||
double station = 0.0;
|
||||
domain::TurnoutPart previous = domain::TurnoutPart::PreBlade;
|
||||
plan::TurnoutPart previous = plan::TurnoutPart::PreBlade;
|
||||
bool first = true;
|
||||
for (const auto& segment : geometry.path) {
|
||||
if (std::abs(segment.turn_in) > kEps) {
|
||||
const double cos_t = std::cos(segment.turn_in);
|
||||
const double sin_t = std::sin(segment.turn_in);
|
||||
walker = domain::geometry::Pose{walker.x, walker.y,
|
||||
walker = geometry::Pose{walker.x, walker.y,
|
||||
walker.hx * cos_t - walker.hy * sin_t,
|
||||
walker.hx * sin_t + walker.hy * cos_t};
|
||||
}
|
||||
if ((first || segment.part != previous) &&
|
||||
!(first && segment.part == domain::TurnoutPart::PreBlade)) {
|
||||
!(first && segment.part == plan::TurnoutPart::PreBlade)) {
|
||||
marks.push_back(
|
||||
TurnoutMark{walker.x, walker.y, mark_kind_for(segment.part), station});
|
||||
}
|
||||
previous = segment.part;
|
||||
first = false;
|
||||
|
||||
walker = domain::geometry::layout_segment(segment.k0, segment.k1, segment.length,
|
||||
walker = geometry::layout_segment(segment.k0, segment.k1, segment.length,
|
||||
walker, nullptr);
|
||||
station += segment.length;
|
||||
}
|
||||
@@ -409,8 +418,8 @@ Solution solve(const Document& document) {
|
||||
bend = -bend;
|
||||
}
|
||||
|
||||
const domain::TurnoutGeometry geometry =
|
||||
domain::lay_turnout(at, to_domain(*type, placement.hand), bend);
|
||||
const plan::TurnoutGeometry geometry =
|
||||
plan::lay_turnout(at, to_domain(*type, placement.hand), bend);
|
||||
if (!geometry.valid) {
|
||||
Diagnostic diagnostic;
|
||||
diagnostic.code = Code::TurnoutInvalid;
|
||||
@@ -491,8 +500,8 @@ Solution solve(const Document& document) {
|
||||
out_segment.k0 = segment.k0;
|
||||
out_segment.k1 = segment.k1;
|
||||
out_segment.length = segment.length;
|
||||
std::vector<domain::geometry::XY> points;
|
||||
walker = domain::geometry::layout_segment(segment.k0, segment.k1,
|
||||
std::vector<geometry::XY> points;
|
||||
walker = geometry::layout_segment(segment.k0, segment.k1,
|
||||
segment.length, walker,
|
||||
&points);
|
||||
out_segment.part = segment.part;
|
||||
@@ -703,4 +712,4 @@ Solution solve(const Document& document) {
|
||||
return solution;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,10 +1,23 @@
|
||||
#pragma once
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
export module eu07.editor.plan_layout;
|
||||
export import eu07.editor.plan_turnout_geometry;
|
||||
export import eu07.editor.plan_model;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// Solves a whole document: lays every turnout on its through track, lays every
|
||||
/// track from its anchor, and moves the tracks that are held parallel to another
|
||||
@@ -24,14 +37,16 @@ namespace maj0sted::editor {
|
||||
/// curvatures signed for @p hand (+1 diverges left). Public because the editor
|
||||
/// dimensions a *type* with it — laying one from an identity pose is how the
|
||||
/// catalogue numbers are checked before any instance stands on a track.
|
||||
[[nodiscard]] domain::Turnout to_domain(const TurnoutType& type, int hand);
|
||||
[[nodiscard]] plan::Turnout to_domain(const TurnoutType& type, int hand);
|
||||
|
||||
/// The construction points of a turnout laid from @p start: where its parts meet,
|
||||
/// where its blade is dimensioned, and the points the drawing takes its lengths
|
||||
/// between. Public for the same reason as to_domain — so a bare type is drawn from
|
||||
/// the very same list the solver reports for a placement, rather than from a second
|
||||
/// idea of what a construction point is.
|
||||
[[nodiscard]] std::vector<TurnoutMark> turnout_marks(const domain::TurnoutGeometry& geometry,
|
||||
const domain::geometry::Pose& start);
|
||||
[[nodiscard]] std::vector<TurnoutMark> turnout_marks(const plan::TurnoutGeometry& geometry,
|
||||
const geometry::Pose& start);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,6 +1,20 @@
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
module eu07.editor.plan_model;
|
||||
|
||||
namespace editor::plan {
|
||||
namespace {
|
||||
|
||||
template <typename Container, typename Id>
|
||||
@@ -71,4 +85,4 @@ const Track* find_element_track(const Document& document, ElementId id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,11 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export module eu07.editor.plan_model;
|
||||
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Identity
|
||||
@@ -129,7 +140,7 @@ struct Track {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// One piece of a catalogue turnout's diverging path. Mirrors
|
||||
/// domain::TurnoutPiece; kept here so the document does not depend on the
|
||||
/// plan::TurnoutPiece; kept here so the document does not depend on the
|
||||
/// geometry header.
|
||||
struct TurnoutPieceSpec {
|
||||
int part{2}; ///< 0 przediglicowy, 1 iglica, 2 łuk, 3 krzyżownicowa
|
||||
@@ -139,7 +150,7 @@ struct TurnoutPieceSpec {
|
||||
double turn_in{0.0}; ///< kąt nagięcia iglicy, radians, unsigned
|
||||
};
|
||||
|
||||
/// The blade's own dimensions, which place its cut tip. Mirrors domain::Blade.
|
||||
/// The blade's own dimensions, which place its cut tip. Mirrors plan::Blade.
|
||||
struct BladeSpec {
|
||||
double tip_thickness{0.0}; ///< u: width where the planed nose ends
|
||||
double nose{0.0}; ///< d: the planed nose, from the tip to that point
|
||||
@@ -219,4 +230,6 @@ struct Document {
|
||||
[[nodiscard]] const Element* find_element(const Document& document, ElementId id);
|
||||
[[nodiscard]] const Track* find_element_track(const Document& document, ElementId id);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "maj0sted/render/rail_renderer.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace maj0sted::render {
|
||||
module eu07.editor.plan_rail_renderer;
|
||||
|
||||
namespace editor::plan::render {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -57,4 +68,4 @@ void RailRenderer::add(const CentrelineElement& element) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace maj0sted::render
|
||||
} // namespace editor::plan::render
|
||||
@@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/render/track_renderer.hpp"
|
||||
export module eu07.editor.plan_rail_renderer;
|
||||
export import eu07.editor.plan_track_renderer;
|
||||
|
||||
namespace maj0sted::render {
|
||||
export {
|
||||
|
||||
namespace editor::plan::render {
|
||||
|
||||
/// A drawable polyline produced by RailRenderer: one rail, carrying the logical
|
||||
/// metadata of the centreline element it came from so the host can colour and
|
||||
@@ -40,4 +50,6 @@ private:
|
||||
std::vector<RailPolyline> rails_;
|
||||
};
|
||||
|
||||
} // namespace maj0sted::render
|
||||
} // namespace editor::plan::render
|
||||
|
||||
} // export
|
||||
@@ -1,11 +1,21 @@
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "maj0sted/render/rail_renderer.hpp"
|
||||
module eu07.editor.plan_render;
|
||||
import eu07.editor.plan_rail_renderer;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
namespace editor::plan {
|
||||
namespace {
|
||||
|
||||
render::ElementKind kind_of(Kind kind) {
|
||||
@@ -119,4 +129,4 @@ std::vector<PlanPolyline> render_rails(const Solution& solution, double gauge_me
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,10 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
export module eu07.editor.plan_render;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// One drawable rail, tagged with what it came from so the host can colour it
|
||||
/// and hit-test it. Exactly one of @c element / @c turnout is set.
|
||||
@@ -32,4 +42,6 @@ struct PlanPolyline {
|
||||
[[nodiscard]] std::vector<PlanPolyline> render_rails(const Solution& solution,
|
||||
double gauge_metres = 1.5);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
26
editor/plan_scene.cppm
Normal file
26
editor/plan_scene.cppm
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
export module eu07.editor.plan_scene;
|
||||
|
||||
export {
|
||||
|
||||
namespace editor::plan::render {
|
||||
|
||||
/// A point in the project's CRS (metres for EPSG:2180).
|
||||
struct Point {
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
/// Which kind of plan element a polyline came from (lets a GUI colour it).
|
||||
enum class ElementKind { Straight, Arc, Transition };
|
||||
|
||||
} // namespace editor::plan::render
|
||||
|
||||
} // export
|
||||
@@ -1,5 +1,13 @@
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <ostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -10,16 +18,18 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::io {
|
||||
module eu07.editor.plan_scn_export;
|
||||
|
||||
namespace editor::plan::io {
|
||||
namespace {
|
||||
|
||||
using maj0sted::editor::Document;
|
||||
using maj0sted::editor::Kind;
|
||||
using maj0sted::editor::PlanPoint;
|
||||
using maj0sted::editor::SolvedElement;
|
||||
using maj0sted::editor::SolvedTrack;
|
||||
using maj0sted::editor::SolvedTurnout;
|
||||
using maj0sted::editor::TurnoutPlacement;
|
||||
using editor::plan::Document;
|
||||
using editor::plan::Kind;
|
||||
using editor::plan::PlanPoint;
|
||||
using editor::plan::SolvedElement;
|
||||
using editor::plan::SolvedTrack;
|
||||
using editor::plan::SolvedTurnout;
|
||||
using editor::plan::TurnoutPlacement;
|
||||
|
||||
/// What `TTrack::Equal` calls one point, and so what the simulator will wire
|
||||
/// together. Everything the exporter writes has to land well inside it.
|
||||
@@ -413,7 +423,7 @@ struct Span {
|
||||
|
||||
/// What the catalogue calls the turnout's radius: the one the longest curved
|
||||
/// piece runs at, which is the łuk itself and not an easement into it.
|
||||
double catalogue_radius(const editor::TurnoutType& type) {
|
||||
double catalogue_radius(const plan::TurnoutType& type) {
|
||||
double longest = 0.0;
|
||||
double radius = 0.0;
|
||||
for (const auto& piece : type.pieces) {
|
||||
@@ -448,7 +458,7 @@ std::string switch_include(double radius, int hand) {
|
||||
|
||||
/// Which way the switch faces, in the degrees an include's `rotate` takes: zero
|
||||
/// along the simulator's +z, turning towards +x.
|
||||
double heading_degrees(const domain::geometry::Pose& pose) {
|
||||
double heading_degrees(const geometry::Pose& pose) {
|
||||
return std::atan2(pose.hx, -pose.hy) * 180.0 / std::numbers::pi;
|
||||
}
|
||||
|
||||
@@ -470,7 +480,7 @@ double diverging_radius(const SolvedTurnout& turnout) {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ScnExportOptions resolve_scn_origin(const maj0sted::editor::Solution& solution,
|
||||
ScnExportOptions resolve_scn_origin(const editor::plan::Solution& solution,
|
||||
ScnExportOptions options) {
|
||||
if (std::abs(options.origin_east) >= 1.0 || std::abs(options.origin_north) >= 1.0) {
|
||||
return options;
|
||||
@@ -490,7 +500,7 @@ ScnExportOptions resolve_scn_origin(const maj0sted::editor::Solution& solution,
|
||||
}
|
||||
|
||||
ScnExportResult export_scn(const Document& document,
|
||||
const maj0sted::editor::Solution& solution,
|
||||
const editor::plan::Solution& solution,
|
||||
const ScnExportOptions& options, std::ostream& out) {
|
||||
const auto opt = resolve_scn_origin(solution, options);
|
||||
ScnExportResult result;
|
||||
@@ -637,7 +647,7 @@ ScnExportResult export_scn(const Document& document,
|
||||
turnout.start.hy};
|
||||
|
||||
const double exit_station = span.facing ? span.to : span.from;
|
||||
domain::geometry::Pose exit_pose{};
|
||||
geometry::Pose exit_pose{};
|
||||
if (!pose_at(track, exit_station, exit_pose)) {
|
||||
result.warnings.push_back("rozjazd „" + span.placement->type + "” na torze „" +
|
||||
base + "” nie ma końca na torze zasadniczym");
|
||||
@@ -753,4 +763,4 @@ ScnExportResult export_scn(const Document& document,
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::io
|
||||
} // namespace editor::plan::io
|
||||
@@ -1,13 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
export module eu07.editor.plan_scn_export;
|
||||
export import eu07.editor.plan_model;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
namespace maj0sted::io {
|
||||
export {
|
||||
|
||||
namespace editor::plan::io {
|
||||
|
||||
struct ScnExportOptions {
|
||||
double origin_east{0.0};
|
||||
@@ -64,12 +74,14 @@ struct ScnExportResult {
|
||||
/// Height is still a constant and both roll slots are still zero — the vertical
|
||||
/// alignment is a separate piece of work, and nothing in the editor produces one
|
||||
/// yet.
|
||||
[[nodiscard]] ScnExportResult export_scn(const editor::Document& document,
|
||||
const editor::Solution& solution,
|
||||
[[nodiscard]] ScnExportResult export_scn(const plan::Document& document,
|
||||
const plan::Solution& solution,
|
||||
const ScnExportOptions& options,
|
||||
std::ostream& out);
|
||||
|
||||
[[nodiscard]] ScnExportOptions resolve_scn_origin(const editor::Solution& solution,
|
||||
[[nodiscard]] ScnExportOptions resolve_scn_origin(const plan::Solution& solution,
|
||||
ScnExportOptions options);
|
||||
|
||||
} // namespace maj0sted::io
|
||||
} // namespace editor::plan::io
|
||||
|
||||
} // export
|
||||
@@ -1,12 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <numbers>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::domain::geometry {
|
||||
export module eu07.editor.plan_segment;
|
||||
|
||||
export {
|
||||
|
||||
namespace editor::plan::geometry {
|
||||
|
||||
struct XY {
|
||||
double x;
|
||||
@@ -87,4 +98,6 @@ inline Pose layout_segment(double k0, double k1, double length, Pose start,
|
||||
start.hx * sin_t + start.hy * cos_t};
|
||||
}
|
||||
|
||||
} // namespace maj0sted::domain::geometry
|
||||
} // namespace editor::plan::geometry
|
||||
|
||||
} // export
|
||||
@@ -1,17 +1,27 @@
|
||||
#include "maj0sted/editor/sketch.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cmath>
|
||||
#include <variant>
|
||||
#include <cstdio>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
module eu07.editor.plan_sketch;
|
||||
|
||||
namespace editor::plan {
|
||||
namespace {
|
||||
constexpr double kEps = 1e-9;
|
||||
} // namespace
|
||||
|
||||
using domain::geometry::Pose;
|
||||
using geometry::Pose;
|
||||
|
||||
|
||||
Corner fit_corner(double in_hx, double in_hy, double out_hx, double out_hy, double radius) {
|
||||
@@ -217,7 +227,7 @@ Pose lay_group(const Track& track, const Group& group, const std::vector<double>
|
||||
const double k0 = element.kind == Kind::Clothoid ? k_prev : k1;
|
||||
const double length =
|
||||
element.kind == Kind::Arc ? arc_length[arc_at++] : element.length;
|
||||
pose = domain::geometry::layout_segment(k0, k1, length, pose, nullptr);
|
||||
pose = geometry::layout_segment(k0, k1, length, pose, nullptr);
|
||||
k_prev = k1;
|
||||
}
|
||||
return pose;
|
||||
@@ -492,4 +502,4 @@ bool lay_along_skeleton(Track& track, const Skeleton& skeleton, std::string& why
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,12 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
export module eu07.editor.plan_sketch;
|
||||
export import eu07.editor.plan_model;
|
||||
export import eu07.editor.plan_solution;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// Laying track by clicking along the ground.
|
||||
///
|
||||
@@ -86,7 +96,7 @@ struct Skeleton {
|
||||
/// curve comes out, rather than the curve being slid along a line drawn
|
||||
/// before the edit.
|
||||
bool pinned{false};
|
||||
domain::geometry::Pose begins{};
|
||||
geometry::Pose begins{};
|
||||
};
|
||||
|
||||
/// Pulls @p track's skeleton out of @p solved.
|
||||
@@ -122,4 +132,6 @@ struct Skeleton {
|
||||
[[nodiscard]] bool lay_along_skeleton(Track& track, const Skeleton& skeleton,
|
||||
std::string& why);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,9 +1,20 @@
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
module eu07.editor.plan_solution;
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
const char* code_name(Code code) {
|
||||
switch (code) {
|
||||
@@ -58,13 +69,13 @@ const SolvedElement* find_element(const Solution& solution, ElementId id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool pose_at(const SolvedTrack& track, double station, domain::geometry::Pose& out) {
|
||||
bool pose_at(const SolvedTrack& track, double station, geometry::Pose& out) {
|
||||
const auto& line = track.centreline;
|
||||
if (line.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (line.size() == 1) {
|
||||
out = domain::geometry::Pose{line[0].x, line[0].y, track.start.hx, track.start.hy};
|
||||
out = geometry::Pose{line[0].x, line[0].y, track.start.hx, track.start.hy};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,7 +89,7 @@ bool pose_at(const SolvedTrack& track, double station, domain::geometry::Pose& o
|
||||
}
|
||||
if (station <= walked + step) {
|
||||
const double t = std::clamp((station - walked) / step, 0.0, 1.0);
|
||||
out = domain::geometry::Pose{line[i - 1].x + dx * t, line[i - 1].y + dy * t,
|
||||
out = geometry::Pose{line[i - 1].x + dx * t, line[i - 1].y + dy * t,
|
||||
dx / step, dy / step};
|
||||
return true;
|
||||
}
|
||||
@@ -115,4 +126,4 @@ bool steady_room(const SolvedTrack& track, double station, double& curvature, do
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,13 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/segment_layout.hpp" // Pose
|
||||
#include "maj0sted/domain/geometry/turnout.hpp" // TurnoutPart
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
export module eu07.editor.plan_solution;
|
||||
export import eu07.editor.plan_segment;
|
||||
export import eu07.editor.plan_turnout_geometry;
|
||||
export import eu07.editor.plan_model;
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// A point in the project's CRS (metres for EPSG:2180).
|
||||
struct PlanPoint {
|
||||
@@ -25,8 +35,8 @@ struct PlanPoint {
|
||||
struct SolvedElement {
|
||||
ElementId id{ElementId::none};
|
||||
Kind kind{Kind::Line};
|
||||
domain::geometry::Pose start{}; ///< pose the element is laid from
|
||||
domain::geometry::Pose end{}; ///< pose it ends on = the next element's start
|
||||
geometry::Pose start{}; ///< pose the element is laid from
|
||||
geometry::Pose end{}; ///< pose it ends on = the next element's start
|
||||
double k0{0.0}; ///< signed curvature at the start (1/m)
|
||||
double k1{0.0}; ///< signed curvature at the end
|
||||
double length{0.0};
|
||||
@@ -40,8 +50,8 @@ struct SolvedTrack {
|
||||
TrackId id{TrackId::none};
|
||||
std::vector<SolvedElement> elements;
|
||||
std::vector<PlanPoint> centreline;
|
||||
domain::geometry::Pose start{};
|
||||
domain::geometry::Pose end{};
|
||||
geometry::Pose start{};
|
||||
geometry::Pose end{};
|
||||
double length{0.0};
|
||||
/// True when the chain was laid whole. False means it was cut short by a
|
||||
/// diagnostic — the elements up to the cut are still here and still drawable.
|
||||
@@ -50,9 +60,9 @@ struct SolvedTrack {
|
||||
|
||||
/// One laid segment of a turnout's diverging path.
|
||||
struct SolvedTurnoutSegment {
|
||||
domain::geometry::Pose start{};
|
||||
geometry::Pose start{};
|
||||
double k0{0.0}, k1{0.0}, length{0.0};
|
||||
domain::TurnoutPart part{domain::TurnoutPart::Curve};
|
||||
plan::TurnoutPart part{plan::TurnoutPart::Curve};
|
||||
std::vector<PlanPoint> points;
|
||||
};
|
||||
|
||||
@@ -73,8 +83,8 @@ struct TurnoutMark {
|
||||
struct SolvedTurnout {
|
||||
TurnoutId id{TurnoutId::none};
|
||||
bool valid{false};
|
||||
domain::geometry::Pose start{}; ///< PR, on the through track
|
||||
domain::geometry::Pose frog{}; ///< KR, where a branch anchors
|
||||
geometry::Pose start{}; ///< PR, on the through track
|
||||
geometry::Pose frog{}; ///< KR, where a branch anchors
|
||||
double tangent_front{0.0}, tangent_back{0.0};
|
||||
double diverging_length{0.0};
|
||||
double through_length{0.0}; ///< PR to the frog's projection, along the through track
|
||||
@@ -154,7 +164,7 @@ struct Solution {
|
||||
/// Pose at @p station metres along a solved track's centreline. Clamped to the
|
||||
/// ends. Returns false for an empty track.
|
||||
[[nodiscard]] bool pose_at(const SolvedTrack& track, double station,
|
||||
domain::geometry::Pose& out);
|
||||
geometry::Pose& out);
|
||||
|
||||
/// How much track of one steady curvature there is around @p station: @p behind
|
||||
/// back to where the element begins, @p ahead on to where it ends, and
|
||||
@@ -166,4 +176,6 @@ struct Solution {
|
||||
[[nodiscard]] bool steady_room(const SolvedTrack& track, double station, double& curvature,
|
||||
double& behind, double& ahead);
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,5 +1,17 @@
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
@@ -7,12 +19,14 @@
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
module eu07.editor.plan_tilecache;
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
namespace {
|
||||
|
||||
// Shortest round-trippable, locale-independent representation of a double —
|
||||
// same convention as maj0sted::io so URLs are stable and precise.
|
||||
// same convention as editor::plan::io so URLs are stable and precise.
|
||||
std::string num(double value) {
|
||||
char buffer[64];
|
||||
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), value);
|
||||
@@ -258,4 +272,4 @@ void TileService::schedule_retry(const TileKey& key) {
|
||||
begin_fetch(key);
|
||||
}
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
@@ -1,12 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::editor {
|
||||
export module eu07.editor.plan_tilecache;
|
||||
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// A stable tile in the EPSG:2180 tiling pyramid. Tiles are addressed by their
|
||||
/// zoom level and integer column/row, so the same ground square always maps to
|
||||
@@ -179,4 +190,6 @@ private:
|
||||
std::function<void(TileKey)> on_ready_;
|
||||
};
|
||||
|
||||
} // namespace maj0sted::editor
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
@@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/render/scene.hpp" // Point, ElementKind
|
||||
export module eu07.editor.plan_track_renderer;
|
||||
export import eu07.editor.plan_scene;
|
||||
|
||||
namespace maj0sted::render {
|
||||
export {
|
||||
|
||||
namespace editor::plan::render {
|
||||
|
||||
/// One sampled centreline element of a solved niweleta, tagged with its logical
|
||||
/// origin so a UI can colour, label and hit-test it. Rendering-neutral: it holds
|
||||
@@ -36,4 +46,6 @@ public:
|
||||
virtual void add(const CentrelineElement& element) = 0;
|
||||
};
|
||||
|
||||
} // namespace maj0sted::render
|
||||
} // namespace editor::plan::render
|
||||
|
||||
} // export
|
||||
@@ -1,11 +1,20 @@
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <vector>
|
||||
|
||||
namespace maj0sted::domain {
|
||||
module eu07.editor.plan_turnout_geometry;
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
using geometry::layout_segment;
|
||||
using geometry::Pose;
|
||||
@@ -311,4 +320,4 @@ TurnoutGeometry lay_turnout(const Pose& start, const Turnout& turnout, double be
|
||||
return geometry;
|
||||
}
|
||||
|
||||
} // namespace maj0sted::domain
|
||||
} // namespace editor::plan
|
||||
@@ -1,10 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/geometry/segment_layout.hpp" // Pose
|
||||
export module eu07.editor.plan_turnout_geometry;
|
||||
export import eu07.editor.plan_segment;
|
||||
|
||||
namespace maj0sted::domain {
|
||||
export {
|
||||
|
||||
namespace editor::plan {
|
||||
|
||||
/// The crossing mark of a turnout — the skos 1:n. It is the tangent of the angle
|
||||
/// between the through and diverging tracks, so it fixes the total angle the
|
||||
@@ -176,4 +186,6 @@ struct TurnoutGeometry {
|
||||
[[nodiscard]] TurnoutGeometry lay_turnout(const geometry::Pose& start, const Turnout& turnout,
|
||||
double bend = 0.0);
|
||||
|
||||
} // namespace maj0sted::domain
|
||||
} // namespace editor::plan
|
||||
|
||||
} // export
|
||||
52
editor/tests/CMakeLists.txt
Normal file
52
editor/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
# Unit tests for the plan library (the geometry and the solver behind the track
|
||||
# layout tool). They are off by default: switched on, the interface units are
|
||||
# compiled a second time into a small library of their own, because a test
|
||||
# executable can only import modules from a target it links, and linking the game
|
||||
# would drag the whole simulator in. The code under test needs nothing but a
|
||||
# C++20 compiler - no glm, no engine, no imgui.
|
||||
option(EU07_EDITOR_PLAN_TESTS "Build the editor plan library unit tests" OFF)
|
||||
if (NOT EU07_EDITOR_PLAN_TESTS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(PLAN_INTERFACES
|
||||
../plan_segment.cppm
|
||||
../plan_turnout_geometry.cppm
|
||||
../plan_model.cppm
|
||||
../plan_solution.cppm
|
||||
../plan_chain.cppm
|
||||
../plan_join.cppm
|
||||
../plan_layout.cppm
|
||||
../plan_render.cppm
|
||||
../plan_sketch.cppm
|
||||
../plan_tilecache.cppm
|
||||
../plan_document_io.cppm
|
||||
../plan_scn_export.cppm
|
||||
../plan_scene.cppm
|
||||
../plan_track_renderer.cppm
|
||||
../plan_rail_renderer.cppm)
|
||||
|
||||
file(GLOB PLAN_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../plan_*.cpp")
|
||||
|
||||
add_library(editor_plan STATIC ${PLAN_SOURCES})
|
||||
target_sources(editor_plan
|
||||
PUBLIC FILE_SET CXX_MODULES
|
||||
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
FILES ${PLAN_INTERFACES})
|
||||
set_target_properties(editor_plan PROPERTIES
|
||||
CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF
|
||||
POSITION_INDEPENDENT_CODE ON FOLDER "editor")
|
||||
|
||||
foreach(name chain hold lines graph format turnout tile_cache)
|
||||
add_executable(editor_plan_test_${name} test_${name}.cpp)
|
||||
target_link_libraries(editor_plan_test_${name} PRIVATE editor_plan)
|
||||
target_include_directories(editor_plan_test_${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set_target_properties(editor_plan_test_${name} PROPERTIES
|
||||
CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF FOLDER "editor")
|
||||
add_test(NAME plan_${name} COMMAND editor_plan_test_${name})
|
||||
endforeach()
|
||||
|
||||
# test_scn.cpp and test_join.cpp are missing: they were never committed, along
|
||||
# with the join module those two exercised (see the stub in plan_join.cpp).
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// A tiny zero-dependency check framework so the test suite needs nothing but a
|
||||
@@ -6,18 +14,18 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace maj0test {
|
||||
namespace plantest {
|
||||
inline int checks = 0;
|
||||
inline int failures = 0;
|
||||
} // namespace maj0test
|
||||
} // namespace plantest
|
||||
|
||||
// Variadic so that brace-init lists with commas, e.g. CHECK(Point{1, 2} == ...),
|
||||
// are passed through as a single condition instead of several macro arguments.
|
||||
#define CHECK(...) \
|
||||
do { \
|
||||
++::maj0test::checks; \
|
||||
++::plantest::checks; \
|
||||
if (!(__VA_ARGS__)) { \
|
||||
++::maj0test::failures; \
|
||||
++::plantest::failures; \
|
||||
std::printf(" [FAIL] %s:%d CHECK(%s)\n", __FILE__, __LINE__, \
|
||||
#__VA_ARGS__); \
|
||||
} \
|
||||
@@ -25,7 +33,7 @@ inline int failures = 0;
|
||||
|
||||
#define CHECK_THROWS(...) \
|
||||
do { \
|
||||
++::maj0test::checks; \
|
||||
++::plantest::checks; \
|
||||
bool threw = false; \
|
||||
try { \
|
||||
(void)(__VA_ARGS__); \
|
||||
@@ -33,7 +41,7 @@ inline int failures = 0;
|
||||
threw = true; \
|
||||
} \
|
||||
if (!threw) { \
|
||||
++::maj0test::failures; \
|
||||
++::plantest::failures; \
|
||||
std::printf(" [FAIL] %s:%d CHECK_THROWS(%s)\n", __FILE__, \
|
||||
__LINE__, #__VA_ARGS__); \
|
||||
} \
|
||||
@@ -46,6 +54,6 @@ inline int failures = 0;
|
||||
} while (0)
|
||||
|
||||
#define REPORT() \
|
||||
(std::printf("\n%d checks, %d failures\n", ::maj0test::checks, \
|
||||
::maj0test::failures), \
|
||||
::maj0test::failures == 0 ? 0 : 1)
|
||||
(std::printf("\n%d checks, %d failures\n", ::plantest::checks, \
|
||||
::plantest::failures), \
|
||||
::plantest::failures == 0 ? 0 : 1)
|
||||
@@ -1,14 +1,23 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include "check.hpp"
|
||||
import eu07.editor.plan_chain;
|
||||
|
||||
// The element chain: a walk, one element at a time, with every length said
|
||||
// outright. Nothing here is solved for — what the document says is what is laid.
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/chain.hpp"
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using maj0sted::domain::geometry::Pose;
|
||||
using namespace editor::plan;
|
||||
using editor::plan::geometry::Pose;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <variant>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include "check.hpp"
|
||||
import eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_document_io;
|
||||
|
||||
// The .m0s format, version 5: everything by id, an explicit version, and a
|
||||
// round trip that is byte-identical. Older files still open: a version-3 one has
|
||||
// its arcs and blade fields converted to the piece list on the way in, and a
|
||||
// version-4 one simply had no nose on its blade yet.
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/io/document_io.hpp"
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -108,20 +119,20 @@ bool same(const Document& a, const Document& b) {
|
||||
|
||||
void round_trip_is_byte_identical() {
|
||||
const Document document = sample();
|
||||
const std::string text = maj0sted::io::serialize(document);
|
||||
const std::string text = editor::plan::io::serialize(document);
|
||||
|
||||
const auto reloaded = maj0sted::io::deserialize(text);
|
||||
const auto reloaded = editor::plan::io::deserialize(text);
|
||||
CHECK(reloaded.has_value());
|
||||
if (!reloaded) {
|
||||
return;
|
||||
}
|
||||
CHECK(same(document, *reloaded));
|
||||
CHECK(maj0sted::io::serialize(*reloaded) == text);
|
||||
CHECK(editor::plan::io::serialize(*reloaded) == text);
|
||||
}
|
||||
|
||||
void every_field_survives() {
|
||||
const Document document = sample();
|
||||
const auto reloaded = maj0sted::io::deserialize(maj0sted::io::serialize(document));
|
||||
const auto reloaded = editor::plan::io::deserialize(editor::plan::io::serialize(document));
|
||||
CHECK(reloaded.has_value());
|
||||
if (!reloaded) {
|
||||
return;
|
||||
@@ -180,14 +191,14 @@ void an_older_file_is_refused() {
|
||||
// outright rather than half-understood. Version 3 is not refused — it is
|
||||
// converted, which an_old_file_upgrades_to_the_piece_list checks.
|
||||
const std::string version_1 =
|
||||
"maj0sted 1\ncrs 2180\nniwelety 0\neditor 1\neniw 2 1\nename niweleta 1\n";
|
||||
CHECK(!maj0sted::io::deserialize(version_1).has_value());
|
||||
"the plan library 1\ncrs 2180\nniwelety 0\neditor 1\neniw 2 1\nename niweleta 1\n";
|
||||
CHECK(!editor::plan::io::deserialize(version_1).has_value());
|
||||
const std::string version_2 =
|
||||
"m0s 2\nnext 9\nview 0 0 0\norigin 0 0 0 0\nlines 0\ntypes 0\n"
|
||||
"tracks 0\n";
|
||||
CHECK(!maj0sted::io::deserialize(version_2).has_value());
|
||||
CHECK(!maj0sted::io::deserialize("").has_value());
|
||||
CHECK(!maj0sted::io::deserialize("m0s 8\n").has_value());
|
||||
CHECK(!editor::plan::io::deserialize(version_2).has_value());
|
||||
CHECK(!editor::plan::io::deserialize("").has_value());
|
||||
CHECK(!editor::plan::io::deserialize("m0s 8\n").has_value());
|
||||
}
|
||||
|
||||
void a_file_from_before_lukowanie_still_opens() {
|
||||
@@ -197,7 +208,7 @@ void a_file_from_before_lukowanie_still_opens() {
|
||||
const std::string version_5 =
|
||||
"m0s 5\nnext 5\nview 0 0 0\norigin 0 0 0 0\ntypes 0\n"
|
||||
"turnouts 1\nturnout 2 1 100 1 1\ntutype Rz 1:9 R190\ntracks 0\n";
|
||||
const auto document = maj0sted::io::deserialize(version_5);
|
||||
const auto document = editor::plan::io::deserialize(version_5);
|
||||
CHECK(document.has_value());
|
||||
if (!document) {
|
||||
return;
|
||||
@@ -206,7 +217,7 @@ void a_file_from_before_lukowanie_still_opens() {
|
||||
CHECK(document->turnouts[0].station == 100.0);
|
||||
CHECK(document->turnouts[0].bend_from_track);
|
||||
CHECK(document->turnouts[0].bend == 0.0);
|
||||
CHECK(maj0sted::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
CHECK(editor::plan::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
}
|
||||
|
||||
void a_file_from_before_the_export_path_still_opens() {
|
||||
@@ -216,14 +227,14 @@ void a_file_from_before_the_export_path_still_opens() {
|
||||
"m0s 6\nnext 3\nview 0 0 0\norigin 0 0 0 0\ntypes 0\nturnouts 0\n"
|
||||
"tracks 1\ntrack 1\ntkname tor\nanchor pose 0 0 0\nelements 1\n"
|
||||
"element 2 line 0 0 250\n";
|
||||
const auto document = maj0sted::io::deserialize(version_6);
|
||||
const auto document = editor::plan::io::deserialize(version_6);
|
||||
CHECK(document.has_value());
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
CHECK(document->scn_path.empty());
|
||||
CHECK(document->tracks.size() == 1);
|
||||
CHECK(maj0sted::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
CHECK(editor::plan::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
}
|
||||
|
||||
void a_file_from_before_the_nose_still_opens() {
|
||||
@@ -234,7 +245,7 @@ void a_file_from_before_the_nose_still_opens() {
|
||||
"m0s 4\nnext 1\nview 0 0 0\norigin 0 0 0 0\n"
|
||||
"types 1\ntype 9 27.138 0.0049 0.07 1\ntseg 2 21.02 190 190 0\n"
|
||||
"tname Rz 1:9 R190\nturnouts 0\ntracks 0\n";
|
||||
const auto document = maj0sted::io::deserialize(version_4);
|
||||
const auto document = editor::plan::io::deserialize(version_4);
|
||||
CHECK(document.has_value());
|
||||
if (!document) {
|
||||
return;
|
||||
@@ -246,7 +257,7 @@ void a_file_from_before_the_nose_still_opens() {
|
||||
CHECK(document->turnout_types[0].pieces.size() == 1);
|
||||
|
||||
// and it is written back in the new form, with a place for the nose
|
||||
CHECK(maj0sted::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
CHECK(editor::plan::io::serialize(*document).find("m0s 7") != std::string::npos);
|
||||
}
|
||||
|
||||
void an_old_file_upgrades_to_the_piece_list() {
|
||||
@@ -257,7 +268,7 @@ void an_old_file_upgrades_to_the_piece_list() {
|
||||
"m0s 3\nnext 1\nview 0 0 0\norigin 0 0 0 0\n"
|
||||
"types 1\ntype 9 0 0 27.138 0 0 0 1\ntarc 190 0 0\ntname Rz 1:9 R190\n"
|
||||
"turnouts 0\ntracks 0\n";
|
||||
const auto document = maj0sted::io::deserialize(version_3);
|
||||
const auto document = editor::plan::io::deserialize(version_3);
|
||||
CHECK(document.has_value());
|
||||
if (!document) {
|
||||
return;
|
||||
@@ -270,20 +281,20 @@ void an_old_file_upgrades_to_the_piece_list() {
|
||||
CHECK(std::abs(type.pieces[0].length - 190.0 * std::atan(1.0 / 9.0)) < 1e-9);
|
||||
CHECK(type.pieces[1].part == 3);
|
||||
|
||||
const auto laid = maj0sted::domain::lay_turnout(
|
||||
maj0sted::domain::geometry::Pose{0.0, 0.0, 1.0, 0.0}, to_domain(type, 1));
|
||||
const auto laid = editor::plan::lay_turnout(
|
||||
editor::plan::geometry::Pose{0.0, 0.0, 1.0, 0.0}, to_domain(type, 1));
|
||||
CHECK(laid.valid);
|
||||
CHECK(std::abs(laid.angle_residual) < 1e-9);
|
||||
CHECK(std::abs(laid.length_residual) < 1e-9);
|
||||
|
||||
// and it is written back in the new form
|
||||
CHECK(maj0sted::io::serialize(*document).find("tseg ") != std::string::npos);
|
||||
CHECK(editor::plan::io::serialize(*document).find("tseg ") != std::string::npos);
|
||||
}
|
||||
|
||||
void a_truncated_file_is_refused() {
|
||||
const std::string text = maj0sted::io::serialize(sample());
|
||||
const std::string text = editor::plan::io::serialize(sample());
|
||||
// Cut off half way: the counts no longer match what follows.
|
||||
CHECK(!maj0sted::io::deserialize(text.substr(0, text.size() / 2)).has_value());
|
||||
CHECK(!editor::plan::io::deserialize(text.substr(0, text.size() / 2)).has_value());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1,13 +1,22 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <cmath>
|
||||
#include "check.hpp"
|
||||
import eu07.editor.plan_layout;
|
||||
|
||||
// What refers to what decides the order things are laid in. A turnout on the
|
||||
// branch of a turnout is not a special case, and a reference that runs in a
|
||||
// circle is reported rather than relaxed at for a fixed number of passes.
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <numbers>
|
||||
#include "check.hpp"
|
||||
import eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_sketch;
|
||||
|
||||
// What one track keeps against another, and what clicking along the ground
|
||||
// works out at the click. Both are the same idea from two ends: geometry is
|
||||
// decided once, in numbers, and nothing is left pointing at a drawing.
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <numbers>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/editor/sketch.hpp"
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <variant>
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "check.hpp"
|
||||
import eu07.editor.plan_layout;
|
||||
import eu07.editor.plan_sketch;
|
||||
|
||||
// Pulling a straight about by one of its ends. The arcs beside it re-fit, every
|
||||
// other straight keeps its bearing, and an arc that turns past a half circle
|
||||
// stays as far round as it was drawn — that last one is the whole reason the
|
||||
// construction goes through the arc's centre and not through where the tangents
|
||||
// would have crossed.
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/editor/sketch.hpp"
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
#include <cstdint>
|
||||
import eu07.editor.plan_tilecache;
|
||||
|
||||
using namespace maj0sted::editor;
|
||||
using namespace editor::plan;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include "check.hpp"
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
import eu07.editor.plan_turnout_geometry;
|
||||
|
||||
// A turnout is laid from the list its catalogue entry gives, piece by piece, and
|
||||
// nothing in it is fitted: whether the list keeps the promises of the crossing
|
||||
// mark and the catalogue length is reported, not corrected. The blade is
|
||||
@@ -6,14 +21,10 @@
|
||||
// rail at PR, but the blade only starts at a, and its planed nose runs on to A
|
||||
// where the rail is u thick.
|
||||
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
|
||||
using namespace maj0sted::domain;
|
||||
using maj0sted::domain::geometry::Pose;
|
||||
using namespace editor::plan;
|
||||
using editor::plan::geometry::Pose;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -76,7 +87,7 @@ Pose pose_after(const TurnoutGeometry& geometry, std::size_t index) {
|
||||
pose = Pose{pose.x, pose.y, pose.hx * c - pose.hy * s,
|
||||
pose.hx * s + pose.hy * c};
|
||||
}
|
||||
pose = geometry::layout_segment(segment.k0, segment.k1, segment.length, pose,
|
||||
pose = editor::plan::geometry::layout_segment(segment.k0, segment.k1, segment.length, pose,
|
||||
nullptr);
|
||||
}
|
||||
return pose;
|
||||
@@ -2,9 +2,9 @@ module;
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
|
||||
module eu07.editor.turnouts;
|
||||
import eu07.editor.plan_turnout_geometry;
|
||||
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
@@ -47,8 +47,8 @@ turnout_preset curved_tangent(std::string name, double crossing_n, double radius
|
||||
auto const alfa{std::atan(1.0 / crossing_n)};
|
||||
auto const tangent{radius * std::tan(0.5 * alfa)};
|
||||
auto const b{back_tangent > 0.0 ? back_tangent : tangent};
|
||||
auto const lead{maj0sted::domain::blade_run_to_offset(radius, 0.0, tip_thickness) - nose};
|
||||
auto const blade{maj0sted::domain::blade_run_to_offset(radius, 0.0, heel_offset) - lead};
|
||||
auto const lead{editor::plan::blade_run_to_offset(radius, 0.0, tip_thickness) - nose};
|
||||
auto const blade{editor::plan::blade_run_to_offset(radius, 0.0, heel_offset) - lead};
|
||||
|
||||
turnout_preset preset{std::move(name), crossing_n, tangent + b, tip_thickness, nose, railtop_width, {}};
|
||||
preset.pieces.push_back({0, lead, radius, radius, 0.0});
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace editor
|
||||
{
|
||||
|
||||
// one piece of a preset's diverging path, in catalogue order from PR. mirrors
|
||||
// maj0sted::editor::TurnoutPieceSpec so this header stays free of the library
|
||||
// editor::plan::TurnoutPieceSpec so this header stays free of the library
|
||||
struct turnout_preset_piece
|
||||
{
|
||||
int part; // 0 przediglicowy, 1 iglica, 2 luk, 3 krzyzownicowa
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
Standard: c++20
|
||||
ColumnLimit: 90
|
||||
IndentWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
PointerAlignment: Left
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Preserve
|
||||
24
maj0sted/.gitignore
vendored
24
maj0sted/.gitignore
vendored
@@ -1,24 +0,0 @@
|
||||
# Build output
|
||||
/build/
|
||||
/out/
|
||||
/cmake-build-*/
|
||||
|
||||
# Compiled objects / archives
|
||||
*.o
|
||||
*.obj
|
||||
*.a
|
||||
*.lib
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Editors / IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
# WASM / web build artifacts (produced by web/build.sh)
|
||||
web/maj0sted.js
|
||||
web/maj0sted.wasm
|
||||
web/serve
|
||||
@@ -1,101 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(maj0sted
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Model and solver for a railway track-layout editor"
|
||||
LANGUAGES CXX)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Options
|
||||
# ----------------------------------------------------------------------------
|
||||
option(MAJ0STED_BUILD_TESTS "Build unit tests" ON)
|
||||
option(MAJ0STED_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
|
||||
# Only drive tests/examples/install defaults when built on its own, not when
|
||||
# pulled in via add_subdirectory() from a larger project.
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(MAJ0STED_STANDALONE ON)
|
||||
else()
|
||||
set(MAJ0STED_STANDALONE OFF)
|
||||
endif()
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# The static library
|
||||
# ----------------------------------------------------------------------------
|
||||
add_library(maj0sted STATIC
|
||||
src/domain/turnout.cpp
|
||||
src/editor/model.cpp
|
||||
src/editor/solution.cpp
|
||||
src/editor/chain.cpp
|
||||
src/editor/sketch.cpp
|
||||
src/editor/join.cpp
|
||||
src/editor/layout.cpp
|
||||
src/editor/render.cpp
|
||||
src/editor/tile_cache.cpp
|
||||
src/render/rail_renderer.cpp
|
||||
src/io/document_io.cpp
|
||||
src/io/scn_export.cpp
|
||||
)
|
||||
add_library(maj0sted::maj0sted ALIAS maj0sted)
|
||||
|
||||
target_compile_features(maj0sted PUBLIC cxx_std_20)
|
||||
|
||||
target_include_directories(maj0sted PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(maj0sted PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(maj0sted PRIVATE
|
||||
-Wall -Wextra -Wpedantic -Wconversion -Wshadow)
|
||||
endif()
|
||||
|
||||
if(MAJ0STED_WARNINGS_AS_ERRORS)
|
||||
if(MSVC)
|
||||
target_compile_options(maj0sted PRIVATE /WX)
|
||||
else()
|
||||
target_compile_options(maj0sted PRIVATE -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Tests
|
||||
# ----------------------------------------------------------------------------
|
||||
if(MAJ0STED_BUILD_TESTS AND MAJ0STED_STANDALONE)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Install / package
|
||||
# ----------------------------------------------------------------------------
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
install(TARGETS maj0sted
|
||||
EXPORT maj0stedTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
install(EXPORT maj0stedTargets
|
||||
FILE maj0stedTargets.cmake
|
||||
NAMESPACE maj0sted::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maj0sted)
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/maj0stedConfig.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/maj0stedConfig.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maj0sted)
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/maj0stedConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/maj0stedConfig.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/maj0stedConfigVersion.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maj0sted)
|
||||
@@ -1,184 +0,0 @@
|
||||
# HANDOFF — maj0sted
|
||||
|
||||
Stan prac i punkt zaczepienia dla kolejnej sesji (np. po przejściu na `claude` CLI).
|
||||
Kod jest samoopisowy; ten plik to skrót "gdzie jesteśmy i co dalej".
|
||||
|
||||
## Czym jest projekt
|
||||
Statyczna biblioteka **C++20** (DDD) do edytora torowiska. Czysty model domenowy,
|
||||
bez zależności zewnętrznych. Namespace: `maj0sted::domain`.
|
||||
|
||||
**Uwaga (sandbox):** na tej maszynie wyłączone są nieuprzywilejowane user
|
||||
namespaces, więc domyślny sandbox komend pada w preflight (komenda „nie zwraca
|
||||
statusu”). Uruchamiaj polecenia powłoki **z wyłączonym sandboxem** (w Cursorze:
|
||||
`required_permissions: ["all"]`). Wtedy `git`, `g++`, `node` działają normalnie.
|
||||
|
||||
**Uwaga:** `cmake` NIE jest zainstalowany. Weryfikacja odbywa się przez `g++ 14.2`.
|
||||
Toolchain WASM: `emsdk` jest w `~/emsdk` (`source ~/emsdk/emsdk_env.sh` dodaje `em++`).
|
||||
|
||||
## Jak weryfikować (bez cmake)
|
||||
```bash
|
||||
cd /home/maj00r/Projects/maj0sted
|
||||
FLAGS="-std=c++20 -Iinclude -Wall -Wextra -Wpedantic -Wconversion -Wshadow"
|
||||
SRC="src/domain/*.cpp src/io/*.cpp src/render/*.cpp src/editor/solve.cpp src/editor/tile_cache.cpp src/app/editor_document.cpp"
|
||||
for t in map_project niweleta plan_alignment parallelism fitting serialization render web tile_cache document; do
|
||||
g++ $FLAGS -Itests $SRC tests/test_$t.cpp -o /tmp/t_$t && /tmp/t_$t | tail -1
|
||||
done
|
||||
```
|
||||
Wszystkie testy przechodzą, kompilacja bez ostrzeżeń. Oszczędnie: kompiluj tylko
|
||||
zmieniony test z minimalnym zestawem źródeł (np. `tile_cache` wymaga tylko
|
||||
`src/editor/tile_cache.cpp`; `document` — `src/domain/*.cpp src/io/project_serializer.cpp
|
||||
src/app/editor_document.cpp`). Szybki syntax-check: `g++ $FLAGS -fsyntax-only ...`.
|
||||
|
||||
Budowa WASM: `source ~/emsdk/emsdk_env.sh && cd web && ./build.sh`
|
||||
(produkuje `web/maj0sted.js` + `.wasm`, oba w `.gitignore`).
|
||||
|
||||
## Model — co już jest (✓ = zaimplementowane + testy)
|
||||
|
||||
### Value Objects (`include/maj0sted/domain/value_objects/`)
|
||||
- `Crs` — układ EPSG, domyślnie **EPSG:2180** (metry) ✓
|
||||
- `CartesianPosition` — (x=easting, y=northing), metry, double ✓
|
||||
- `Length` (≥0), `Radius` (>0) ✓
|
||||
- `Azimuth` — radiany, **zgodnie z ruchem wskazówek zegara od północy (+y)**, znormalizowany [0,2π) ✓
|
||||
- `NiweletaId`, `StraightId` — silne tożsamości ✓
|
||||
- `TrackOffset` — dystans (>0) + `Side::Left/Right` ✓
|
||||
|
||||
### Agregaty
|
||||
- `MapProject` — **projekt**: trzyma `Crs` (domyślnie EPSG:2180) + kolekcję niwelet
|
||||
(`add_niweleta`, `niwelety()`) ✓
|
||||
- `Niweleta` (`domain/niweleta.hpp`) — id, nazwa, plan + profil. **Sama nadaje `StraightId`**:
|
||||
`add_straight(start,end,joint) -> StraightId`; `add_plan_element` mintuje id anonimowej prostej
|
||||
(i podbija licznik przy id wczytanym z pliku). `lengths_consistent()`.
|
||||
**`apply_fit(FitResult)`** — wpina wynik fitowania w plan: podmienia sąsiednie proste
|
||||
(entry, exit) na `entry → curve... → exit` stykami Tangent; proste zachowują id ✓
|
||||
|
||||
### Geometria planu (`domain/geometry/`)
|
||||
- `PlanElement = variant<Straight, CircularArc, TransitionCurve>`
|
||||
- `Straight` — **zakotwiczony w XY (start/end)**, azymut i długość liczone; opcjonalny `StraightId`.
|
||||
Zasada: prosta trzyma linię nośną (pozycja+azymut), zmienia się tylko długość; krzywe to wypadkowa.
|
||||
- `CircularArc` — promień + kierunek + **opcjonalna** długość
|
||||
- `TransitionCurve` (klotoida) — długość + promienie końcowe (`optional`=∞) + kierunek
|
||||
- `HorizontalAlignment` — lista kierunkowa + `JointContinuity{Tangent, AzimuthBreak}`. Reguły styku ✓:
|
||||
- sąsiednie proste Tangent: muszą się stykać i mieć równy azymut
|
||||
- różny azymut prostych → wymaga `AzimuthBreak` (tylko prosta-prosta, muszą się stykać, różne azymuty)
|
||||
- sąsiednie łuki o tym samym promieniu i kierunku → zabronione
|
||||
- styki z krzywą akceptowane "pending fit"
|
||||
|
||||
### Geometria profilu
|
||||
- `ProfileElement = variant<Grade, VerticalCurve>`; `VerticalAlignment`. (Profil na razie nietknięty.)
|
||||
|
||||
### Równoległość (`domain/parallelism/`)
|
||||
- `TrackParallelism { StraightId origin, StraightId parallel, TrackOffset }` — **referencje wprost po StraightId**;
|
||||
niezmienniki: oba id niepuste, różne ✓
|
||||
- `ParallelismService`: `mark_parallel(originStraight, parallelStraight, offset)`, `are_parallel`,
|
||||
`derive_parallel_line`, `perpendicular_distance` ✓
|
||||
|
||||
### Fitowanie (`domain/fitting/`)
|
||||
- `FitParameters { entry, exit, radius, optional transition_length }` — klotoida symetryczna lub sam łuk
|
||||
- `CompoundFitParameters { entry, exit, radius1, radius2, split_station }` — **łuk koszowy**
|
||||
(miejsce podziału = kilometraż pierwszego łuku; `δ1 = split_station/R1`)
|
||||
- `FitResult { entry(przycięta), curve[], exit(przycięta), tangent_in, tangent_out, direction }`
|
||||
- `FittingService`:
|
||||
- `fit_between_straights` — klotoida–łuk–klotoida (lub sam łuk), przycina obie proste ✓
|
||||
- `fit_compound` — dwa łuki różnych promieni; **obie linie prostych bez zmian**, obie proste
|
||||
doprzycięte na długości; podział ustala `δ1`. Metoda stycznej pośredniej ✓
|
||||
|
||||
### Serializacja (`maj0sted::io`, `include/maj0sted/io/project_serializer.hpp`)
|
||||
- `serialize(MapProject) -> std::string` / `deserialize(std::string) -> MapProject` ✓
|
||||
- Format tekstowy, liniowy, bez zależności; liczby przez `to_chars`/`from_chars` (dokładny round-trip,
|
||||
niezależny od locale). Round-trip jest bajtowo stabilny (test to sprawdza).
|
||||
- Zapisuje: CRS + niwelety (id, nazwa, plan z elementami i stykami, profil). **NIE** zapisuje relacji
|
||||
równoległości (patrz niżej — StraightId nie są globalnie unikalne).
|
||||
|
||||
### Rendering: dwie szyny
|
||||
`src/editor/editor.cpp` rysuje **dwie szyny w rozstawie 1500 mm** (`kHalfGauge=0.75`,
|
||||
±750 mm od osi) zamiast samej osi. `offset_points` przesuwa linię osi wzdłuż
|
||||
normalnej per-wierzchołek (różnica centralna, normalna lewa `(-dy,dx)`),
|
||||
`push_rails` emituje lewą i prawą szynę zachowując `kind` (kolor). Uchwyty edycji
|
||||
(węzły/końce prostych) w JS dalej rysują się z osi (model).
|
||||
|
||||
### Warstwa web (WASM) — biblioteka robi wszystko, JS tylko rysuje
|
||||
Zasada: **cała logika jest w libce**; `web/` to cienki renderer + przekazywanie
|
||||
wejścia. `web/maj0sted_bindings.cpp` (embind) to jedyny plik zależny od emscripten.
|
||||
|
||||
- **Kafle podkładu WMS** (`maj0sted::editor`, `web/tile_cache.hpp/.cpp`) ✓
|
||||
- `TileGrid` — **jeden stały grid kafli 100 m × 100 m** w **EPSG:2180**
|
||||
(`kCellMetres=100`, **1024 px/kafel** → 100/1024 m/px, origin (0,0), jeden
|
||||
poziom `kLevel=0`, **bez piramidy per-zoom**); `tile_at`, `bbox`,
|
||||
`tiles_for_view`, `zoom_for_resolution` (argument zoom ignorowany, sygnatury
|
||||
zachowane dla boundary). Czysta, testowana natywnie.
|
||||
- **Retry:** nieudany fetch jest ponawiany **maks. `kMaxRetries=10` razy na
|
||||
kafel** (licznik `TileImage.attempts`); po przekroczeniu limitu kafel dostaje
|
||||
status `Failed`. Pod Emscripten retry jest odroczony async
|
||||
(`emscripten_async_call`, `kRetryDelayMs=1500`), bez blokowania/rekurencji;
|
||||
z wstrzykniętym fetcherem (testy) powtarza od razu.
|
||||
- `WmsConfig` + `wms_getmap_url` — WMS **1.1.1** GetMap (BBOX = minx,miny,maxx,maxy),
|
||||
presety Geoportalu (ortofoto/topo). Liczby przez `to_chars`.
|
||||
- `TileService` — cache + **fetch**: na trafienie zwraca z cache, na pudło sam
|
||||
wykonuje GetMap. Pod `__EMSCRIPTEN__` używa **Emscripten Fetch** (`-sFETCH`);
|
||||
natywnie/w testach przez wstrzykiwany `Fetcher` (bez sieci). Callback
|
||||
`on_ready` budzi przerysowanie w JS.
|
||||
- **Proxy WMS (`web/serve.py`):** przeglądarka pobiera kafle **same-origin**
|
||||
(`/wms?...`), a lokalny serwer pobiera je serwer-serwer z Geoportalu. Powód:
|
||||
WAF Geoportalu **zwraca 404 dla User-Agentów zawierających „Mozilla"** (czyli
|
||||
dla prawdziwych przeglądarek), a przepuszcza proste UA (`curl`), więc bezpośredni
|
||||
`fetch`/`emscripten_fetch` z przeglądarki dostaje 404. Proxy używa UA `curl/8.5.0`.
|
||||
Frontend ustawia `Module.wmsSetConfig({base_url: origin+'/wms'})` w `finishBoot`.
|
||||
Libka nadal robi fetch (`emscripten_fetch`) — tylko na `localhost`.
|
||||
Uruchamianie: `python3 web/serve.py 8080` (zamiast `http.server`).
|
||||
- **Cache na dysku:** proxy odsyła udane kafle z `Cache-Control: public,
|
||||
max-age=31536000, immutable`, więc przeglądarka trzyma je w HTTP cache na dysku —
|
||||
po odświeżeniu strony kafle są brane z cache, bez ponownego pobierania. Błędy
|
||||
(`502`) idą z `no-store` (nie cache'ują się). Uwaga: cache `TileService` w C++ jest
|
||||
tylko w pamięci WASM (ginie przy refreshu) — trwałość zapewnia właśnie HTTP cache.
|
||||
- **Retry w proxy:** WAF Geoportalu bywa niestabilny (sporadyczne 404/reset nawet dla
|
||||
UA `curl`), więc proxy ponawia pobranie `WMS_PROXY_RETRIES=6` razy zanim zwróci 502;
|
||||
do tego dochodzi client-side retry libki (10×).
|
||||
- **Trwałość projektu** (`maj0sted::app`, `app/editor_document.hpp/.cpp`) ✓
|
||||
- `EditorDocument` = model GUI (niezależne proste + parametry fitów) — bezstratny.
|
||||
- `serialize_document`/`deserialize_document` — **reużywa `io::serialize`** (osadza
|
||||
prawdziwy projekt maj0sted) + sekcja `editor` z prostymi i parametrami fitów.
|
||||
- `to_map_project`/`from_map_project` — konwersja do/z `MapProject` (best-effort,
|
||||
nie rzuca).
|
||||
- `save_project`/`load_project` — **I/O plikowe w libce** (`std::fstream` +
|
||||
`std::filesystem`), domyślna ścieżka `/data/maj0sted/project.m0s`.
|
||||
- **Fit nieudany → „brak" (decyzja w libce):** `solve_project` (editor) zwraca per
|
||||
niweleta `polylines` **oraz `applied_fits`** — autorytatywną listę fitów, które
|
||||
faktycznie zaszły. Fit, którego nie da się policzyć (zły parametr/geometria, punkty
|
||||
styczne poza odcinkami prostych), jest **odrzucany w libce** i nie ma go w `applied_fits`.
|
||||
Frontend tylko lustrzanie przyjmuje tę listę (bez własnej reguły o porażce).
|
||||
- **Boundary (embind, cienki):** `solveProject`, `wmsTilesForView`,
|
||||
`wmsZoomForResolution`, `wmsTileBytes`, `wmsSetOnReady`, `wmsSetConfig`,
|
||||
`saveProject`, `loadDefaultProject`.
|
||||
- **Frontend (`web/index.html`):** rysuje podkład WMS pod geometrią (georeferencja
|
||||
EPSG:2180, trzyma się przy pan/zoom); `Ctrl+S` → `saveProject` + `FS.syncfs`;
|
||||
na starcie montuje **IDBFS** w `/data`, `syncfs(true)` i `loadDefaultProject`
|
||||
(fallback: demo). Flow budowy: prosta → krzywa (czeka jako „pending”, rysowana
|
||||
przerywaną) → kolejna prosta kotwiczy ją z obu stron i **dopiero wtedy** liczony
|
||||
jest fit.
|
||||
|
||||
## Konwencje geometryczne
|
||||
- EPSG:2180, jednostka = metr; kierunek jazdy `d = (sin az, cos az)`; normalna lewa `(-cos az, sin az)`.
|
||||
- Tolerancje: pozycja `1e-6 m`, azymut `1e-9 rad`.
|
||||
|
||||
## Znane ograniczenia
|
||||
- **`StraightId` są lokalne per-niweleta** (licznik startuje od 1 w każdej niwelecie), więc NIE są
|
||||
globalnie unikalne. `TrackParallelism` referuje proste po samym `StraightId` — działa dla luźnych
|
||||
obiektów, ale w projekcie z wieloma niweletami trzeba by referować `(NiweletaId, StraightId)`.
|
||||
Do przemyślenia, jeśli równoległość ma być trwała/serializowana.
|
||||
|
||||
## Następne kroki (do decyzji / do zrobienia)
|
||||
1. **Klotoidy w łuku koszowym** (między łukami i/lub na końcach). (NIE zrobione.)
|
||||
2. Asymetryczne klotoidy w `fit_between_straights` (teraz tylko symetryczne). (NIE zrobione.)
|
||||
3. **Rejestr relacji równoległości** + poprawka referencji na `(NiweletaId, StraightId)` i ich serializacja. (NIE.)
|
||||
4. **Solver** ustawiający prostą równoległą na `derive_parallel_line` i przeliczający resztę. (NIE.)
|
||||
5. Instalacja `cmake`/`ninja` i budowa przez CMake (na razie tylko `g++`).
|
||||
|
||||
## Preferencje pracy (ustalone z użytkownikiem)
|
||||
- **Nie edytować `README.md`** (utrzymuje go użytkownik).
|
||||
- Robić **tylko to, o co proszono** — bez dokładania funkcji z własnej inicjatywy.
|
||||
- **Oszczędzać CPU**: kompilować tylko zmieniony test, `-fsyntax-only` do szybkiej walidacji,
|
||||
pełne testy na koniec/na żądanie; bez procesów w tle, przeglądarki i podagentów.
|
||||
- Język domeny: **niweleta** = jednolity odcinek toru złożony z wielu krzywych w planie i profilu.
|
||||
|
||||
## Git
|
||||
Repozytorium zainicjalizowane; pliki dodane do stage'a, **brak commitów** (commit dopiero na wyraźną prośbę).
|
||||
@@ -1,86 +0,0 @@
|
||||
# maj0sted
|
||||
|
||||
Statyczna biblioteka **C++20** do obsługi **edytora torowiska**, zbudowana
|
||||
zgodnie z **Domain-Driven Design (DDD)**.
|
||||
|
||||
Biblioteka dostarcza czysty model domenowy — bez zależności od UI, bazy danych
|
||||
czy frameworka. Warstwy aplikacji i infrastruktury podpina się do zdefiniowanych
|
||||
tu typów.
|
||||
|
||||
## Model domenowy
|
||||
|
||||
```
|
||||
maj0sted::domain
|
||||
├── value_objects/ Value Objects (niezmienne, porównywane przez wartość)
|
||||
│ ├── Crs układ odniesienia (kod EPSG), domyślnie EPSG:2180
|
||||
│ └── CartesianPosition pozycja kartezjańska (x, y w jednostkach CRS)
|
||||
└── MapProject projekt mapy — definiuje CRS dla wszystkich pozycji
|
||||
```
|
||||
|
||||
Projekt mapy (`MapProject`) definiuje kartezjański układ odniesienia, w którym
|
||||
wyrażane są wszystkie pozycje. Domyślnie jest to **EPSG:2180** (PUWG 1992 /
|
||||
Poland CS92), którego jednostką jest metr.
|
||||
|
||||
## Szybki start
|
||||
|
||||
```cpp
|
||||
#include "maj0sted/maj0sted.hpp"
|
||||
using namespace maj0sted::domain;
|
||||
|
||||
MapProject project; // domyślnie EPSG:2180
|
||||
int epsg = project.crs().epsg(); // 2180
|
||||
|
||||
CartesianPosition p{500000.0, 300000.0}; // x, y w metrach
|
||||
```
|
||||
|
||||
Pełniejszy przykład: [`examples/basic_editor.cpp`](examples/basic_editor.cpp).
|
||||
|
||||
## Budowanie
|
||||
|
||||
Wymagania: kompilator C++20 (GCC 11+/Clang 14+/MSVC 19.3+) oraz CMake ≥ 3.20.
|
||||
|
||||
```bash
|
||||
cmake -S . -B build # dodaj -G Ninja, jeśli masz ninja
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
./build/examples/maj0sted_example
|
||||
```
|
||||
|
||||
### Opcje CMake
|
||||
|
||||
| Opcja | Domyślnie | Znaczenie |
|
||||
|------------------------------|-----------|-------------------------------|
|
||||
| `MAJ0STED_BUILD_TESTS` | `ON` | buduj testy jednostkowe |
|
||||
| `MAJ0STED_BUILD_EXAMPLES` | `ON` | buduj przykłady |
|
||||
| `MAJ0STED_WARNINGS_AS_ERRORS`| `OFF` | traktuj ostrzeżenia jak błędy |
|
||||
|
||||
### Użycie w innym projekcie
|
||||
|
||||
Po `cmake --install build` (albo przez `add_subdirectory`):
|
||||
|
||||
```cmake
|
||||
find_package(maj0sted REQUIRED)
|
||||
target_link_libraries(twoj_target PRIVATE maj0sted::maj0sted)
|
||||
```
|
||||
|
||||
## Struktura projektu
|
||||
|
||||
```
|
||||
maj0sted/
|
||||
├── CMakeLists.txt
|
||||
├── include/maj0sted/ nagłówki publiczne (API)
|
||||
├── src/domain/ implementacje (.cpp)
|
||||
├── tests/ testy jednostkowe (CTest, bez zależności)
|
||||
├── examples/ przykłady użycia
|
||||
└── cmake/ pliki pakietu find_package
|
||||
```
|
||||
|
||||
## Testy
|
||||
|
||||
Testy używają własnego, malutkiego frameworka (`tests/check.hpp`) — nie wymagają
|
||||
żadnych zewnętrznych bibliotek. Każdy plik testowy to osobny plik wykonywalny
|
||||
zwracający kod wyjścia dla CTest.
|
||||
|
||||
## Licencja
|
||||
|
||||
Do uzupełnienia (np. MIT) — dodaj plik `LICENSE`.
|
||||
@@ -1,5 +0,0 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/maj0stedTargets.cmake")
|
||||
|
||||
check_required_components(maj0sted)
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// maj0sted — the model behind a railway track-layout editor ("edytor
|
||||
// torowiska"). One header pulls in the whole public surface.
|
||||
//
|
||||
// The shape of it: a Document is authored (construction lines, catalogue
|
||||
// turnout types and their placements, tracks made of elements), `solve` turns it
|
||||
// into a Solution, and nothing ever travels back the other way.
|
||||
|
||||
#include "maj0sted/version.hpp"
|
||||
|
||||
// The authored model, and what solving it produces.
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
|
||||
// Solving: one track's chain, and a whole document in dependency order.
|
||||
#include "maj0sted/editor/chain.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
|
||||
// Geometry primitives shared by the solver and the renderer.
|
||||
#include "maj0sted/domain/geometry/segment_layout.hpp"
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
|
||||
// Presentation and IO.
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
#include "maj0sted/io/document_io.hpp"
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace maj0sted::render {
|
||||
|
||||
/// A point in the project's CRS (metres for EPSG:2180).
|
||||
struct Point {
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
/// Which kind of plan element a polyline came from (lets a GUI colour it).
|
||||
enum class ElementKind { Straight, Arc, Transition };
|
||||
|
||||
} // namespace maj0sted::render
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Version of the maj0sted library. Keep in sync with project() in CMakeLists.txt.
|
||||
#define MAJ0STED_VERSION_MAJOR 0
|
||||
#define MAJ0STED_VERSION_MINOR 1
|
||||
#define MAJ0STED_VERSION_PATCH 0
|
||||
#define MAJ0STED_VERSION_STRING "0.1.0"
|
||||
@@ -1,45 +0,0 @@
|
||||
add_executable(maj0sted_test_chain test_chain.cpp)
|
||||
target_link_libraries(maj0sted_test_chain PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_chain PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME chain COMMAND maj0sted_test_chain)
|
||||
|
||||
add_executable(maj0sted_test_hold test_hold.cpp)
|
||||
target_link_libraries(maj0sted_test_hold PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_hold PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME hold COMMAND maj0sted_test_hold)
|
||||
|
||||
add_executable(maj0sted_test_lines test_lines.cpp)
|
||||
target_link_libraries(maj0sted_test_lines PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_lines PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME lines COMMAND maj0sted_test_lines)
|
||||
|
||||
add_executable(maj0sted_test_graph test_graph.cpp)
|
||||
target_link_libraries(maj0sted_test_graph PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_graph PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME graph COMMAND maj0sted_test_graph)
|
||||
|
||||
add_executable(maj0sted_test_format test_format.cpp)
|
||||
target_link_libraries(maj0sted_test_format PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_format PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME format COMMAND maj0sted_test_format)
|
||||
|
||||
add_executable(maj0sted_test_turnout test_turnout.cpp)
|
||||
target_link_libraries(maj0sted_test_turnout PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_turnout PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME turnout COMMAND maj0sted_test_turnout)
|
||||
|
||||
add_executable(maj0sted_test_scn test_scn.cpp)
|
||||
target_link_libraries(maj0sted_test_scn PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_scn PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME scn COMMAND maj0sted_test_scn)
|
||||
|
||||
add_executable(maj0sted_test_join test_join.cpp)
|
||||
target_link_libraries(maj0sted_test_join PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_join PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME join COMMAND maj0sted_test_join)
|
||||
|
||||
add_executable(maj0sted_test_tile_cache test_tile_cache.cpp)
|
||||
target_link_libraries(maj0sted_test_tile_cache PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_tile_cache PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME tile_cache COMMAND maj0sted_test_tile_cache)
|
||||
|
||||
Reference in New Issue
Block a user