17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 17:24:53 +02:00

fix(editor): a slip goes to the crossing under the cursor or nowhere, shown before the click

A slip went to the crossing of the selected straight with another straight within 40 m of the click. When the track under the cursor wasn't taken as a straight (a slight curve, a switch), the slip silently went to another crossing up to 40 m away. A double slip within 25 m of the click was replaced, even when the click was on a free crossing.

- The crossing nearest the cursor, within 12 m, is found among all the rail tracks. A slip goes only there; when that track is a curve or a switch, the tool says so.
- A double slip is replaced only when it is nearer the cursor than any crossing.
- The cursor is taken at the height of the straight under it, so on a grade the 3D view doesn't put the click metres away from the track.
- Before the click, the view marks the crossing the slip goes to, with its two straights, or the reason it can't go there.
This commit is contained in:
maj00r
2026-10-19 18:20:41 +02:00
parent 59ee32da58
commit 93a45fb632
5 changed files with 204 additions and 53 deletions

View File

@@ -124,18 +124,19 @@ bool editor_mode::start_switch_placement()
return true;
}
auto const &line{current_straight()};
auto const along{std::clamp(line.along(ground), 0.0, line.length)};
auto const cursor{cursor_on_straight(line)};
auto const along{std::clamp(line.along(cursor), 0.0, line.length)};
auto const point{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * along};
if (plan_distance(point, ground) > kSwitchReach)
if (plan_distance(point, cursor) > kSwitchReach)
return false;
if (tool.templates[tool.armed].double_slip)
{
insert_double_slip(line, ground, tool.templates[tool.armed]);
insert_double_slip(line, cursor, tool.templates[tool.armed]);
return true;
}
tool.along = along;
tool.point = point;
tool.mouse = ground;
tool.mouse = cursor;
tool.line = line;
tool.placing = true;
return true;
@@ -817,61 +818,99 @@ std::vector<TTrack *> editor_mode::build_double_slip(glm::dvec3 const &PointA, g
return parts;
}
bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape)
glm::dvec3 editor_mode::cursor_on_straight(editor_track::straight const &Line) const
{
auto &state{m_straights};
state.status.clear();
{
auto const sections{simulation::Region->sections(Point, 40.f)};
for (auto *section : sections)
for (auto const &cell : section->m_cells)
for (auto *track : cell.m_paths)
if (Shape.type == "Rkpd" && track->eType == tt_Switch && false == track->m_editorremoved && track->DoubleSlip() && glm::distance(plan_of(track->location()), plan_of(Point)) < 25.0)
return replace_double_slip(*track, Shape);
}
TTrack *other{nullptr};
// the height of the straight under the cursor, worked out anew till it settles: on a grade the plane of the start of the
// straight would put the cursor metres away from where it points at the track
auto cursor{cursor_level(Line.start.y)};
for (int i = 0; i < 4; ++i)
cursor = cursor_level(Line.start.y + Line.grade * std::clamp(Line.along(cursor), 0.0, Line.length));
return cursor;
}
bool editor_mode::plan_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape, slip_plan &Plan) const
{
Plan = slip_plan{};
// the crossing of the straight with the rail track nearest the cursor, whatever that track is: the slip goes there or
// nowhere, never to another crossing around
TTrack *nearest{nullptr};
glm::dvec2 crossing{0.0};
double best{40.0};
auto const sections{simulation::Region->sections(Point, 60.f)};
for (auto *section : sections)
{
double best{kSlipReach};
auto const category{Line.tracks.empty() ? 1 : Line.tracks.front()->iCategoryFlag & 15};
for (auto *section : simulation::Region->sections(Point, 300.f))
for (auto const &cell : section->m_cells)
{
for (auto *track : cell.m_paths)
{
if (std::find(Line.tracks.begin(), Line.tracks.end(), track) != Line.tracks.end() || track->eType != tt_Normal || track->m_editorremoved || false == editor_track::is_straight(*track, m_straights.tolerance))
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 15) != category || std::find(Line.tracks.begin(), Line.tracks.end(), track) != Line.tracks.end())
continue;
auto const &path{track->m_paths.front()};
auto const a{plan_of(path.points[segment_data::point::start])};
auto const b{plan_of(path.points[segment_data::point::end])};
auto const segment{b - a};
auto const determinant{cross(Line.direction, segment)};
if (std::abs(determinant) < 1e-9)
continue;
auto const offset{a - plan_of(Line.start)};
auto const along{cross(offset, segment) / determinant};
auto const fraction{cross(offset, Line.direction) / determinant};
if (fraction < -0.01 || fraction > 1.01 || along < 0.0 || along > Line.length)
continue;
auto const position{plan_of(Line.start) + Line.direction * along};
auto const distance{glm::distance(position, plan_of(Point))};
if (distance < best)
for (auto const &path : track->m_paths)
{
best = distance;
other = track;
crossing = position;
bezier const curve{path};
auto const straight{path.points[segment_data::point::control1] == glm::dvec3{} && path.points[segment_data::point::control2] == glm::dvec3{}};
int const pieces{straight ? 1 : 16};
auto previous{plan_of(path.points[segment_data::point::start])};
for (int i = 1; i <= pieces; ++i)
{
auto const next{straight ? plan_of(path.points[segment_data::point::end]) : plan_of(curve.point(static_cast<double>(i) / pieces))};
auto const segment{next - previous};
auto const determinant{cross(Line.direction, segment)};
if (std::abs(determinant) > 1e-9)
{
auto const offset{previous - plan_of(Line.start)};
auto const along{cross(offset, segment) / determinant};
auto const fraction{cross(offset, Line.direction) / determinant};
auto const position{plan_of(Line.start) + Line.direction * along};
if (fraction >= -0.01 && fraction <= 1.01 && along >= 0.0 && along <= Line.length && glm::distance(position, plan_of(Point)) < best)
{
best = glm::distance(position, plan_of(Point));
nearest = track;
crossing = position;
}
}
previous = next;
}
}
}
// a double slip nearer the cursor than any crossing is replaced by the one chosen
if (Shape.type == "Rkpd")
{
TTrack *slip{nullptr};
double closest{25.0};
for (auto *section : simulation::Region->sections(Point, 40.f))
for (auto const &cell : section->m_cells)
for (auto *track : cell.m_paths)
if (track->eType == tt_Switch && false == track->m_editorremoved && track->DoubleSlip() && glm::distance(plan_of(track->location()), plan_of(Point)) < closest)
{
closest = glm::distance(plan_of(track->location()), plan_of(Point));
slip = track;
}
if (slip != nullptr && (nearest == nullptr || closest < best))
{
Plan.replaces = slip;
return true;
}
}
if (other == nullptr)
if (nearest == nullptr)
{
state.status = STR_C("No straight crossing the selected one near the click");
Plan.problem = STR_C("No track crosses the selected straight under the cursor");
return false;
}
auto const second{editor_track::find_straight(*other, m_straights.tolerance)};
if (second.tracks.empty())
if (nearest->eType != tt_Normal)
{
Plan.problem = STR_C("The track crossing here is a switch: a slip goes only where two straights cross");
return false;
}
if (false == editor_track::is_straight(*nearest, m_straights.tolerance))
{
Plan.problem = STR_C("The track crossing here isn't straight: a slip goes only where two straights cross");
return false;
}
auto const second{editor_track::find_straight(*nearest, m_straights.tolerance)};
if (second.tracks.empty())
{
Plan.problem = STR_C("The track crossing here isn't straight: a slip goes only where two straights cross");
return false;
}
auto const first{Line.direction};
auto direction{second.direction};
if (glm::dot(first, direction) < 0.0)
@@ -880,7 +919,7 @@ bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dv
bool const plain{Shape.type == "Skrz"};
if (angle < glm::radians(0.5) || angle > glm::radians(plain ? 89.0 : 30.0))
{
state.status = STR_C("The straights cross at an angle unsuitable for a double slip");
Plan.problem = STR_C("The straights cross at an angle unsuitable for a double slip");
return false;
}
auto const alongfirst{glm::dot(crossing - plan_of(Line.start), Line.direction)};
@@ -896,7 +935,7 @@ bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dv
half = available;
if (half < 2.0)
{
state.status = STR_C("Not enough straight track around the crossing");
Plan.problem = STR_C("Not enough straight track around the crossing");
return false;
}
}
@@ -906,20 +945,54 @@ bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dv
half = available;
if (curve.radius < 50.0)
{
state.status = STR_C("Not enough straight track around the crossing");
Plan.problem = STR_C("Not enough straight track around the crossing");
return false;
}
state.status = format(STR_C("R reduced to %d m, the straights end %d m from the crossing"), static_cast<int>(curve.radius), static_cast<int>(available));
Plan.note = format(STR_C("R reduced to %d m, the straights end %d m from the crossing"), static_cast<int>(curve.radius), static_cast<int>(available));
}
auto const heightfirst = [&](double const Along) { return Line.start.y + Line.grade * Along; };
auto const heightsecond = [&](double const Along) { return second.start.y + second.grade * Along; };
auto const sign{glm::dot(second.direction, direction) > 0.0 ? 1.0 : -1.0};
auto const at = [&](glm::dvec2 const &Planar, double const Height) { return glm::dvec3{Planar.x, Height, Planar.y}; };
auto const pointA{at(crossing - first * half, heightfirst(alongfirst - half))};
auto const pointB{at(crossing + first * half, heightfirst(alongfirst + half))};
auto const pointC{at(crossing - direction * half, heightsecond(alongsecond - sign * half))};
auto const pointD{at(crossing + direction * half, heightsecond(alongsecond + sign * half))};
Plan.other = nearest;
Plan.crossing = crossing;
Plan.first = first;
Plan.direction = direction;
Plan.angle = angle;
Plan.alongfirst = alongfirst;
Plan.half = half;
Plan.curve = curve;
Plan.points = {at(crossing - first * half, heightfirst(alongfirst - half)), at(crossing + first * half, heightfirst(alongfirst + half)), at(crossing - direction * half, heightsecond(alongsecond - sign * half)),
at(crossing + direction * half, heightsecond(alongsecond + sign * half))};
return true;
}
bool editor_mode::insert_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape)
{
auto &state{m_straights};
state.status.clear();
slip_plan plan;
if (false == plan_double_slip(Line, Point, Shape, plan))
{
state.status = plan.problem;
return false;
}
if (plan.replaces != nullptr)
return replace_double_slip(*plan.replaces, Shape);
state.status = plan.note;
auto *other{plan.other};
auto const crossing{plan.crossing};
auto const first{plan.first};
auto const direction{plan.direction};
auto const angle{plan.angle};
auto const alongfirst{plan.alongfirst};
auto const half{plan.half};
auto const curve{plan.curve};
auto const heightfirst = [&](double const Along) { return Line.start.y + Line.grade * Along; };
auto const &pointA{plan.points[0]};
auto const &pointB{plan.points[1]};
auto const &pointC{plan.points[2]};
auto const &pointD{plan.points[3]};
std::vector<std::pair<TTrack *, editor_track::state>> states;
std::vector<TTrack *> created;