17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 09:59:06 +01:00

feat(editor): a switch laid into a curve is checked by ST-T1-A6 as it's placed: the radii of its main and diverging tracks at its start and its end shown at it; warnings where the curvature of a track turns from one side to the other within it (KZW II, 9.7 pkt 12), where the diverging track is sharper or the main track bent more than table 11.9 allows the basic switch (the values allowed by way of exception named), where a switch of 1:9.403 or steeper is bent (11.4 pkt 4), where the cant changes within it (11.4 pkt 7), where a cant ramp starts or ends within it or under 6 m off it (9.6 pkt 16-17) and where the cant is over 100 mm (table 9.6); a note on a transition curve, which only an exception allows. The ends of the cant ramps are marked on the curve, and the start of the switch pressed within 3 m of where it starts or ends at an end of a ramp, or keeps the 6 m off it, is drawn there. The warnings stay in the status once it's laid. The checks stand on their own in editor/editorSwitchCurve, with unit tests

This commit is contained in:
maj00r
2026-10-25 00:29:05 +02:00
parent aeef2cebb3
commit ece61056ff
9 changed files with 633 additions and 12 deletions

View File

@@ -35,6 +35,7 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorAlignment.hpp"
#include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorSwitchCurve.hpp"
#include "editor/editorKilometrage.hpp"
#include "editor/editorSpeed.hpp"
#include "editor/editorRoad.hpp"
@@ -1240,6 +1241,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
int last{0}; // template armed the last time
bool placing{false};
double along{0.0};
double pressed{0.0}; // along a curve: where the press was, the start of the switch drawn from it to the ends of the cant ramps
glm::dvec3 point{0.0};
glm::dvec3 mouse{0.0};
editor_track::straight line;
@@ -1528,6 +1530,15 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void switch_placed(std::vector<TTrack *> const &Created);
static std::vector<curve_sample> sample_chain(editor_track::chain const &Chain);
static curve_sample sample_at(std::vector<curve_sample> const &Frame, double const Station);
// the curve a switch goes into as the norms look at it: its headings and its cant along it
std::vector<switch_curve::track_point> frame_points(std::vector<curve_sample> const &Frame) const;
// the way the switch being placed into a curve goes along it and the side its diverging track leaves to, from the drag
void curved_switch_pose(int &Direction, int &Side) const;
// the start of the switch being placed into a curve: where it was pressed, drawn to where the switch meets an end of a cant ramp or
// keeps 6 m off it; What receives which
double curved_switch_start(int const Direction, std::string *What = nullptr) const;
// the switch made of the paths, laid into the curve at the station, as ST-T1-A6 sees it
switch_curve::switch_report curved_switch_report(std::vector<segment_data> const &Paths, double const Station, int const Direction) const;
static double nearest_station(std::vector<curve_sample> const &Frame, glm::dvec3 const &Point);
static std::vector<segment_data> curved_switch_paths(editor_track::switch_template const &Shape, std::vector<curve_sample> const &Frame, double const Station, int const Direction, int const Side);
void insert_curved_switch(int const Direction, int const Side);

View File

@@ -51,12 +51,10 @@ std::vector<segment_data> editor_mode::switch_preview() const
return {};
if (tool.curved)
{
auto const sample{sample_at(tool.frame, tool.along)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
auto const forward{glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1};
glm::dvec2 const direction{sample.tangent * static_cast<double>(forward)};
auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1};
return curved_switch_paths(tool.templates[tool.armed], tool.frame, tool.along, forward, side);
int forward;
int side;
curved_switch_pose(forward, side);
return curved_switch_paths(tool.templates[tool.armed], tool.frame, curved_switch_start(forward), forward, side);
}
if (switch_link plan; plan_switch_link(plan))
{
@@ -116,6 +114,7 @@ bool editor_mode::start_switch_placement()
return false;
tool.curved = true;
tool.along = nearest_station(tool.frame, ground);
tool.pressed = tool.along;
tool.point = sample_at(tool.frame, tool.along).position;
if (plan_distance(tool.point, ground) > kSwitchReach)
return false;
@@ -167,11 +166,10 @@ void editor_mode::finish_switch_placement()
}
if (tool.curved)
{
auto const sample{sample_at(tool.frame, tool.along)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
auto const forward{glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1};
glm::dvec2 const direction{sample.tangent * static_cast<double>(forward)};
auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1};
int forward;
int side;
curved_switch_pose(forward, side);
tool.along = curved_switch_start(forward);
insert_curved_switch(forward, side);
return;
}
@@ -1256,6 +1254,89 @@ double editor_mode::nearest_station(std::vector<curve_sample> const &Frame, glm:
return station;
}
std::vector<switch_curve::track_point> editor_mode::frame_points(std::vector<curve_sample> const &Frame) const
{
auto const gauge{m_route.design.norms.gauge};
std::vector<switch_curve::track_point> points;
for (auto const &sample : Frame)
{
switch_curve::track_point point;
point.station = sample.station;
point.heading = std::atan2(sample.tangent.y, sample.tangent.x);
// the headings go on through a half turn, so that their differences are the turns between them
if (false == points.empty())
point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi<double>());
point.cant = gauge * std::sin(glm::radians(static_cast<double>(sample.roll)));
points.push_back(point);
}
return points;
}
void editor_mode::curved_switch_pose(int &Direction, int &Side) const
{
auto const &tool{m_switch};
auto const sample{sample_at(tool.frame, tool.pressed)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
Direction = glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1;
glm::dvec2 const direction{sample.tangent * static_cast<double>(Direction)};
Side = direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1;
}
double editor_mode::curved_switch_start(int const Direction, std::string *What) const
{
auto const &tool{m_switch};
if (What != nullptr)
What->clear();
if (tool.frame.size() < 2 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()))
return tool.pressed;
auto const length{tool.templates[tool.armed].length};
std::string what;
auto const start{switch_curve::snapped_start(switch_curve::ramp_ends(frame_points(tool.frame)), tool.pressed, length, Direction, 3.0, &what)};
// a start which would put the switch off the curve isn't one
auto const total{tool.frame.back().station};
if (Direction > 0 ? start < 0.0 || start > total - length : start < length || start > total)
return tool.pressed;
if (What != nullptr)
*What = what;
return start;
}
switch_curve::switch_report editor_mode::curved_switch_report(std::vector<segment_data> const &Paths, double const Station, int const Direction) const
{
auto const &tool{m_switch};
if (Paths.size() < 2 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()))
return {};
// a path of the switch from its start, in the plan
auto const along = [](segment_data const &Path) {
std::vector<switch_curve::track_point> points;
auto const add = [&](segment_data const &Piece) {
bezier const curve{Piece};
auto const steps{std::max(4, static_cast<int>(std::ceil(curve.plan_length() / 0.25)))};
for (int k = points.empty() ? 0 : 1; k <= steps; ++k)
{
auto const t{static_cast<double>(k) / steps};
auto const position{plan_of(curve.point(t))};
auto const tangent{plan_of(curve.first(t))};
switch_curve::track_point point;
point.heading = std::atan2(tangent.y, tangent.x);
if (false == points.empty())
{
point.station = points.back().station + glm::distance(position, plan_of(curve.point(static_cast<double>(k - 1) / steps)));
point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi<double>());
}
points.push_back(point);
}
};
if (Path.pieces.empty())
add(Path);
for (auto const &piece : Path.pieces)
add(piece);
return points;
};
auto const &shape{tool.templates[tool.armed]};
return switch_curve::check(along(Paths[0]), along(Paths[1]), frame_points(tool.frame), Station, Station + Direction * shape.length, shape.radius, shape.ratio);
}
std::vector<segment_data> editor_mode::curved_switch_paths(editor_track::switch_template const &Shape, std::vector<curve_sample> const &Frame, double const Station, int const Direction, int const Side)
{
auto const map = [&](glm::dvec3 const &Local, glm::dvec2 &Tangent, glm::dvec2 const &Localtangent) {
@@ -1393,10 +1474,16 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side)
}
}
auto const paths{curved_switch_paths(shape, tool.frame, station, Direction, Side)};
auto const report{curved_switch_report(paths, station, Direction)};
auto const made{create_switch_set(shape, paths, *tool.frame_tracks.front(), switch_name_for_new())};
created.insert(created.end(), made.begin(), made.end());
if (made.size() == 1)
tool.status = format(STR_C("Switch %s inserted into the curve, Ctrl+Z takes it back"), made.front()->name().c_str());
// what the norms say of it stays in the status
if (report.transition)
tool.status += "\n" + report.notes.front();
for (auto const &warning : report.warnings)
tool.status += "\n" + warning;
auto const placed{created};
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
straight_refresh();

View File

@@ -2354,8 +2354,64 @@ void editor_mode::draw_build_overlay() const
}
else if (m_switch.placing)
{
for (auto const &piece : switch_preview())
auto const preview{switch_preview()};
for (auto const &piece : preview)
drawpath(piece, overlay_color::marked);
// into a curve: the ends of the cant ramps, what the start was drawn to, the radii at the ends of the switch and the norms
if (auto const &tool{m_switch}; tool.curved && tool.connection < 0 && preview.size() >= 2)
{
auto const points{frame_points(tool.frame)};
for (auto const end : switch_curve::ramp_ends(points))
{
auto const sample{sample_at(tool.frame, end)};
glm::dvec3 const across{-sample.tangent.y * 2.5, 0.0, sample.tangent.x * 2.5};
projection.line(drawlist, sample.position - across, sample.position + across, IM_COL32(255, 170, 40, 255), 3.0f);
ImVec2 screen;
if (projection.project(sample.position + across, screen))
drawlist->AddText({screen.x + 4.0f, screen.y - 7.0f}, IM_COL32(255, 170, 40, 255), STR_C("ramp"));
}
int forward;
int side;
curved_switch_pose(forward, side);
std::string snapped;
auto const station{curved_switch_start(forward, &snapped)};
auto const report{curved_switch_report(preview, station, forward)};
// a radius counted positive the way the line turns, the other way with a minus
auto const radius = [](double const Curvature) { return std::abs(Curvature) < 2e-5 ? std::string{STR_C("straight")} : format("%.0f m", 1.0 / Curvature); };
std::vector<std::pair<std::string, ImU32>> lines;
if (false == snapped.empty())
lines.emplace_back(snapped, IM_COL32(255, 170, 40, 255));
if (report.curved)
{
lines.emplace_back(format(STR_C("main track R %s at the start, %s at the end"), radius(report.main_start).c_str(), radius(report.main_end).c_str()), IM_COL32(255, 255, 255, 235));
lines.emplace_back(format(STR_C("diverging track R %s at the start, %s at the end"), radius(report.branch_start).c_str(), radius(report.branch_end).c_str()), IM_COL32(255, 255, 255, 235));
}
lines.emplace_back(format(STR_C("cant %.0f mm at the start, %.0f mm at the end"), report.cant_start, report.cant_end), IM_COL32(255, 255, 255, 235));
for (auto const &note : report.notes)
lines.emplace_back(note, IM_COL32(170, 200, 230, 235));
for (auto const &warning : report.warnings)
lines.emplace_back(warning, overlay_color::invalid);
ImVec2 screen;
if (projection.project(sample_at(tool.frame, station).position, screen))
{
auto const wrap{460.0f};
auto height{0.0f};
auto width{0.0f};
for (auto const &[text, colour] : lines)
{
auto const size{ImGui::CalcTextSize(text.c_str(), nullptr, false, wrap)};
height += size.y + 2.0f;
width = std::max(width, size.x);
}
ImVec2 corner{screen.x + 24.0f, screen.y + 24.0f};
drawlist->AddRectFilled({corner.x - 6.0f, corner.y - 4.0f}, {corner.x + width + 6.0f, corner.y + height + 4.0f}, IM_COL32(10, 20, 30, 215), 4.0f);
for (auto const &[text, colour] : lines)
{
drawlist->AddText(ImGui::GetFont(), ImGui::GetFontSize(), corner, colour, text.c_str(), nullptr, wrap);
corner.y += ImGui::CalcTextSize(text.c_str(), nullptr, false, wrap).y + 2.0f;
}
}
}
switch_link plan;
auto const linked{plan_switch_link(plan)};
ImVec2 screen;