mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 18:07:29 +01:00
The library was a heap of half-built services - fitting, parallelism, niwelety, an SVG writer - that never met each other. In their place is one authored Document and one solver that lays it whole: tracks as element chains, turnouts placed on them, and a Solution that is rebuilt from scratch every time and never fed back into the document. What cannot be laid comes back as a diagnostic with the number in it rather than as silence or a fitted-away error. Turnouts are dimensioned the way the drawings dimension them. The blade follows Koc's rys. 5.9 - the curve is theoretically tangent to the opornica at PR, the blade starts at a with a planed nose of d, and A is where the rail stands u thick - and a + d derived that way lands on his tablica 5.1. Bending follows 5.3.6: it rotates the triangle OBC about the centre of the turnout, so the tangent t and the crossing angle stand and both radii follow from them. That is Koc's (5.7) and (5.8) without coding either, and it puts a symmetrically bent Rz 49E1-190-1:9 on the catalogue's Rlus 49E1-380,292-1:9. A rozjazd standing on a curve is bent onto it; one on a track drawn straight can carry a bend of its own. The szyny laczace take up the difference, because the switch and the frog are only bent, not re-cut. The plan panel draws all of it: the catalogue drawing of a type with its construction points, the theoretical arc dashed where no rail runs, and the blade nose as the straight it is cut as. Dragging a straight now stops where it would pull the track out from under a turnout. File format goes to m0s 6. Versions 3 to 5 still open.
226 lines
8.4 KiB
C++
226 lines
8.4 KiB
C++
// 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;
|
|
|
|
namespace {
|
|
|
|
bool near(double a, double b, double tolerance = 1e-6) {
|
|
return std::abs(a - b) <= tolerance;
|
|
}
|
|
|
|
bool has(const Solution& solution, Code code) {
|
|
for (const auto& diagnostic : solution.diagnostics) {
|
|
if (diagnostic.code == code) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// A plain Rz 1:9 R190, the everyday Polish turnout.
|
|
void add_catalogue(Document& document) {
|
|
TurnoutType type;
|
|
type.name = "Rz 1:9 R190";
|
|
type.crossing_n = 9.0;
|
|
type.length = 27.138; // the catalogue's a + b
|
|
type.blade = BladeSpec{0.005, 0.070};
|
|
type.pieces = {
|
|
// przediglicowy, then blade and curve as one arc of the crossing angle, then
|
|
// the frog rail: a = 13,834 and b = 13,304 with R*tan(alfa/2) = 10,5232
|
|
TurnoutPieceSpec{0, 3.3108, 0.0, 0.0, 0.0},
|
|
TurnoutPieceSpec{1, 7.188, 190.0, 190.0, 0.0},
|
|
TurnoutPieceSpec{2, 190.0 * std::atan(1.0 / 9.0) - 7.188, 190.0, 190.0, 0.0},
|
|
TurnoutPieceSpec{3, 2.7808, 0.0, 0.0, 0.0},
|
|
};
|
|
document.turnout_types.push_back(type);
|
|
}
|
|
|
|
TurnoutId add_turnout(Document& document, TrackId on, double station, int hand) {
|
|
TurnoutPlacement placement;
|
|
placement.id = mint_turnout(document);
|
|
placement.type = "Rz 1:9 R190";
|
|
placement.on = on;
|
|
placement.station = station;
|
|
placement.hand = hand;
|
|
document.turnouts.push_back(placement);
|
|
return placement.id;
|
|
}
|
|
|
|
TrackId add_straight_track(Document& document, const char* name, double x, double y,
|
|
double azimuth, double length) {
|
|
Track track;
|
|
track.id = mint_track(document);
|
|
track.name = name;
|
|
track.anchor = AtPose{x, y, azimuth};
|
|
track.elements = {
|
|
Element{mint_element(document), Kind::Line, 0.0, 0, length, Free{}}};
|
|
document.tracks.push_back(track);
|
|
return track.id;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// A branch anchored to a frog is laid after the turnout, which is laid after
|
|
/// its through track — whatever order they appear in the document.
|
|
void a_branch_follows_its_frog() {
|
|
Document document;
|
|
add_catalogue(document);
|
|
|
|
// The branch is declared BEFORE the turnout and the through track it needs.
|
|
Track branch;
|
|
branch.id = mint_track(document);
|
|
branch.name = "odnoga";
|
|
branch.elements = {
|
|
Element{mint_element(document), Kind::Line, 0.0, 0, 100.0, Free{}}};
|
|
document.tracks.push_back(branch);
|
|
|
|
const TrackId through = add_straight_track(document, "zasadniczy", 0.0, 0.0, 0.0, 1000.0);
|
|
const TurnoutId turnout = add_turnout(document, through, 300.0, 1);
|
|
document.tracks[0].anchor = AtPort{turnout, Port::Frog};
|
|
|
|
const Solution solution = solve(document);
|
|
|
|
CHECK(solution.diagnostics.empty());
|
|
const SolvedTurnout* solved_turnout = find_turnout(solution, turnout);
|
|
CHECK(solved_turnout != nullptr && solved_turnout->valid);
|
|
|
|
const SolvedTrack* solved_branch = find_track(solution, branch.id);
|
|
CHECK(solved_branch != nullptr);
|
|
CHECK(solved_branch->elements.size() == 1);
|
|
// The branch starts exactly at the frog, on the frog's heading. It was never
|
|
// anywhere else, so there is nothing to pin back.
|
|
CHECK(near(solved_branch->start.x, solved_turnout->frog.x));
|
|
CHECK(near(solved_branch->start.y, solved_turnout->frog.y));
|
|
CHECK(near(solved_branch->start.hx, solved_turnout->frog.hx));
|
|
CHECK(near(solved_branch->start.hy, solved_turnout->frog.hy));
|
|
// It diverges: the frog is off the through track's straight line.
|
|
CHECK(std::abs(solved_turnout->frog.x) > 0.5);
|
|
}
|
|
|
|
/// A turnout on the branch of a turnout. The old solver ran a fixed two passes,
|
|
/// so this depth had no way to settle.
|
|
void a_turnout_on_a_branch_of_a_turnout() {
|
|
Document document;
|
|
add_catalogue(document);
|
|
|
|
const TrackId through = add_straight_track(document, "zasadniczy", 0.0, 0.0, 0.0, 2000.0);
|
|
const TurnoutId first = add_turnout(document, through, 300.0, 1);
|
|
|
|
Track branch;
|
|
branch.id = mint_track(document);
|
|
branch.name = "odnoga 1";
|
|
branch.anchor = AtPort{first, Port::Frog};
|
|
branch.elements = {
|
|
Element{mint_element(document), Kind::Line, 0.0, 0, 400.0, Free{}}};
|
|
document.tracks.push_back(branch);
|
|
|
|
const TurnoutId second = add_turnout(document, branch.id, 150.0, -1);
|
|
|
|
Track branch2;
|
|
branch2.id = mint_track(document);
|
|
branch2.name = "odnoga 2";
|
|
branch2.anchor = AtPort{second, Port::Frog};
|
|
branch2.elements = {
|
|
Element{mint_element(document), Kind::Arc, 300.0, -1, 120.0, Free{}}};
|
|
document.tracks.push_back(branch2);
|
|
|
|
const Solution solution = solve(document);
|
|
|
|
CHECK(solution.diagnostics.empty());
|
|
CHECK(find_turnout(solution, second) != nullptr);
|
|
CHECK(find_turnout(solution, second)->valid);
|
|
|
|
const SolvedTrack* deep = find_track(solution, branch2.id);
|
|
CHECK(deep != nullptr);
|
|
CHECK(deep->elements.size() == 1);
|
|
CHECK(near(deep->start.x, find_turnout(solution, second)->frog.x));
|
|
CHECK(near(deep->start.y, find_turnout(solution, second)->frog.y));
|
|
// Three levels deep, well away from the through track.
|
|
CHECK(std::abs(deep->start.x) > 1.0);
|
|
}
|
|
|
|
/// References that run in a circle are named, not relaxed at.
|
|
void a_cycle_is_reported() {
|
|
// Two tracks, each held parallel to the other's element.
|
|
Document document;
|
|
const TrackId first = add_straight_track(document, "tor 1", 0.0, 0.0, 0.0, 500.0);
|
|
const TrackId second = add_straight_track(document, "tor 2", 20.0, 0.0, 0.0, 500.0);
|
|
document.tracks[0].elements[0].hold =
|
|
Parallel{document.tracks[1].elements[0].id, 4.75};
|
|
document.tracks[1].elements[0].hold =
|
|
Parallel{document.tracks[0].elements[0].id, 4.75};
|
|
(void)first;
|
|
(void)second;
|
|
|
|
const Solution solution = solve(document);
|
|
CHECK(has(solution, Code::CyclicDependency));
|
|
}
|
|
|
|
/// A turnout whose catalogue type is missing says so, and the branch that hangs
|
|
/// off it says why it could not be laid — neither is silently dropped.
|
|
void a_missing_type_is_reported() {
|
|
Document document;
|
|
const TrackId through = add_straight_track(document, "zasadniczy", 0.0, 0.0, 0.0, 500.0);
|
|
const TurnoutId turnout = add_turnout(document, through, 100.0, 1); // no catalogue
|
|
|
|
Track branch;
|
|
branch.id = mint_track(document);
|
|
branch.name = "odnoga";
|
|
branch.anchor = AtPort{turnout, Port::Frog};
|
|
branch.elements = {
|
|
Element{mint_element(document), Kind::Line, 0.0, 0, 50.0, Free{}}};
|
|
document.tracks.push_back(branch);
|
|
|
|
const Solution solution = solve(document);
|
|
CHECK(has(solution, Code::UnknownReference));
|
|
CHECK(solution.diagnostics.size() >= 2);
|
|
// Every track still has an entry, so a host can index by id without holes.
|
|
CHECK(find_track(solution, branch.id) != nullptr);
|
|
CHECK(find_track(solution, through) != nullptr);
|
|
}
|
|
|
|
/// Solving twice gives the same answer, and solving does not touch the document.
|
|
void solving_leaves_the_document_alone() {
|
|
Document document;
|
|
add_catalogue(document);
|
|
const TrackId through = add_straight_track(document, "zasadniczy", 0.0, 0.0, 0.0, 1000.0);
|
|
const TurnoutId turnout = add_turnout(document, through, 300.0, 1);
|
|
Track branch;
|
|
branch.id = mint_track(document);
|
|
branch.anchor = AtPort{turnout, Port::Frog};
|
|
branch.elements = {
|
|
Element{mint_element(document), Kind::Arc, 300.0, 1, 200.0, Free{}}};
|
|
document.tracks.push_back(branch);
|
|
|
|
const Document before = document;
|
|
const Solution first = solve(document);
|
|
const Solution second = solve(document);
|
|
|
|
CHECK(document.tracks.size() == before.tracks.size());
|
|
CHECK(document.next_id == before.next_id);
|
|
CHECK(first.tracks.size() == second.tracks.size());
|
|
for (std::size_t i = 0; i < first.tracks.size(); ++i) {
|
|
CHECK(near(first.tracks[i].length, second.tracks[i].length));
|
|
CHECK(near(first.tracks[i].end.x, second.tracks[i].end.x));
|
|
CHECK(near(first.tracks[i].end.y, second.tracks[i].end.y));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
RUN(a_branch_follows_its_frog);
|
|
RUN(a_turnout_on_a_branch_of_a_turnout);
|
|
RUN(a_cycle_is_reported);
|
|
RUN(a_missing_type_is_reported);
|
|
RUN(solving_leaves_the_document_alone);
|
|
return REPORT();
|
|
}
|