17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 07:59:19 +02:00

Plan tool: cofanie, złącza, przejścia rozjazdowe, and a demo to open

What the plan tool was missing was not geometry but the ordinary business of
editing: taking a mistake back, dividing what is drawn, and seeing what the next
click will do before making it.

Cofanie. The document is plain data, so a step is a copy of it. What counts as one
step is decided by watching the document - a fingerprint over the tracks, turnouts
and catalogue - rather than by dressing every edit with a call: a frame that
changes anything opens a step, and the step closes when the drawing settles. That
is what makes a drag, or a number typed digit by digit, one step and not thirty,
and nothing can be forgotten on the way in. Ctrl+Z / Ctrl+Y; the scene editor
listens for the same keys, so it hands them over while the plan has the keyboard.

Złącze i wycięcie. `insert_joint` divides whichever element a station lands in and
leaves one chain: the ground does not move, the curvature at the new joint is the
one that was always there, and the fit reads the chain the same way afterwards. It
is refused on a straight, because two straights end to end are one straight and the
skeleton will not read that, and under a turnout, which is one piece of geometry on
one element. `cut_track` takes a piece out and does leave two tracks - a gap is the
ground between them, not a hole in a chain.

Przejście rozjazdowe. A turnout may now stand against another one: where it stands
is then worked out from that one's frog, not authored, so moving either carries the
other with it and a trapez stays shut. The frog has to land on the line leaving the
other frog - not on the track's axis, which is 1,469 m further on for a 1:9 - so
the station is settled by laying the turnout and walking it along until it does.

Drawing is a tool now, not what a click happens to do: until Rysuj is on, a click
picks what is under it. With it on, the cursor at the growing end shows the corner
and the straight the next click would lay, worked out by the same fit_corner the
click itself uses. A chain only grows at a loose end, and the first element of a
track is put down by two clicks instead of landing on the plan's zero.

Also here: the curvature strip under the panel, pikietaż in the rows and at the
cursor, how far the laid axis runs from the clicks it was drawn from, the file
format saying which frame and which units it is in (v9), plans chosen from the
directory instead of typed, and editor/samples/demo.m0s - two parallel tracks and a
trapez - with the generator that wrote it and a test that opens it and demands it
lays clean.
This commit is contained in:
maj00r
2026-09-14 16:35:36 +02:00
parent 07cd3b3769
commit 366645ef59
18 changed files with 2584 additions and 41 deletions

View File

@@ -7,6 +7,7 @@ obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include <cmath>
#include <numbers>
#include "check.hpp"
import eu07.editor.plan_layout;
@@ -222,6 +223,88 @@ void solving_leaves_the_document_alone() {
}
}
/// Przejście rozjazdowe: a turnout standing against another one is placed by that
/// one's frog, not by a station somebody typed. Move the near one and the far one
/// goes with it, which is what makes the two of them one piece of trackwork.
void a_paired_turnout_follows_the_one_it_stands_against() {
Document document;
add_catalogue(document);
const double spacing = 4.75;
const TrackId first = add_straight_track(document, "tor 1", 0.0, 0.0, std::numbers::pi / 2.0, 1000.0);
const TrackId second = add_straight_track(document, "tor 2", 0.0, spacing, std::numbers::pi / 2.0, 1000.0);
// the near one opens toward the other track; the far one is trailing and stands
// against it, so where it stands is not authored at all
const TurnoutId near_id = add_turnout(document, first, 400.0, 1);
TurnoutPlacement against;
against.id = mint_turnout(document);
against.type = "Rz 1:9 R190";
against.on = second;
against.station = 0.0; // never used while it is paired
against.hand = 1; // a trailing turnout is laid reversed: the same side, the other hand
against.facing = false;
against.opposite = near_id;
document.turnouts.push_back(against);
Solution solution = solve(document);
CHECK(solution.diagnostics.empty());
const SolvedTurnout* laid_near = find_turnout(solution, near_id);
const SolvedTurnout* laid_far = find_turnout(solution, against.id);
CHECK(laid_near != nullptr && laid_near->valid);
CHECK(laid_far != nullptr && laid_far->valid);
if (laid_near == nullptr || laid_far == nullptr || !laid_far->valid) {
return;
}
// the two frogs face each other across the międzytorze, with the wstawka between them:
// 1,811 m of it left over after the two turnouts, which at 1:9 is 16,405 m of straight
const double gap = std::hypot(laid_far->frog.x - laid_near->frog.x,
laid_far->frog.y - laid_near->frog.y);
CHECK(near(gap, 16.405, 0.01));
const double station_before = laid_far->station;
CHECK(station_before > 0.0);
// slide the near one a hundred metres along its track
find_turnout(document, near_id)->station = 500.0;
solution = solve(document);
CHECK(solution.diagnostics.empty());
laid_near = find_turnout(solution, near_id);
laid_far = find_turnout(solution, against.id);
CHECK(laid_far != nullptr && laid_far->valid);
if (laid_far == nullptr || !laid_far->valid) {
return;
}
CHECK(near(laid_far->station, station_before + 100.0, 1e-3));
CHECK(near(std::hypot(laid_far->frog.x - laid_near->frog.x,
laid_far->frog.y - laid_near->frog.y),
gap, 1e-6));
// unpaired, it stands where it was authored to stand and stays there
find_turnout(document, against.id)->opposite = TurnoutId::none;
find_turnout(document, against.id)->station = 700.0;
solution = solve(document);
CHECK(near(find_turnout(solution, against.id)->station, 700.0));
}
/// Standing against a turnout that is not there is said, not guessed at.
void a_turnout_against_nothing_is_reported() {
Document document;
add_catalogue(document);
const TrackId only = add_straight_track(document, "tor 1", 0.0, 0.0, std::numbers::pi / 2.0, 800.0);
TurnoutPlacement placement;
placement.id = mint_turnout(document);
placement.type = "Rz 1:9 R190";
placement.on = only;
placement.station = 200.0;
placement.hand = 1;
placement.opposite = static_cast<TurnoutId>(9999);
document.turnouts.push_back(placement);
const Solution solution = solve(document);
CHECK(has(solution, Code::UnknownReference));
CHECK(solution.turnouts.size() == 1);
CHECK(!solution.turnouts.front().valid);
}
} // namespace
int main() {
@@ -229,6 +312,8 @@ int main() {
RUN(a_turnout_on_a_branch_of_a_turnout);
RUN(a_cycle_is_reported);
RUN(a_missing_type_is_reported);
RUN(a_paired_turnout_follows_the_one_it_stands_against);
RUN(a_turnout_against_nothing_is_reported);
RUN(solving_leaves_the_document_alone);
return REPORT();
}