17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 05:20:52 +01:00

feat(editor): the SRK tool measures the station tracks: the construction length and the useful one each way, as the rozporządzenie on the railway structures (Dz.U. 2026 poz. 1225) § 2 defines it, from the signal the trains stop by back to the joint, the fouling point or the level crossing behind them, between those where no signal stands; the switches within a track part it into stretches. A list in the panel with the longest train, the two ways marked where they differ, the track under the mouse or selected drawn in the schematic and the scenery, the list copied as a table

This commit is contained in:
maj00r
2026-10-22 15:24:02 +02:00
parent 1f8a477ec4
commit 43346b61e7
5 changed files with 978 additions and 7 deletions

View File

@@ -330,8 +330,9 @@ double plan_gap(glm::dvec3 const &Point, std::vector<glm::dvec3> const &Line)
return best;
}
// how far from the end of the switch its joint lies: 4.2 m beyond the fouling point at a branch, before the start elsewhere
double clearance(TTrack const &Switch, int const Path, bool const Atend)
// how far out from the end of the switch along the track leading on its fouling point lies, below 0 within the switch: where the
// axes of the path there and of the other one are 3.5 m apart; none at the start of a switch
std::optional<double> fouling(TTrack const &Switch, int const Path, bool const Atend)
{
auto const point{end_of(Switch, Path, Atend)};
std::optional<std::pair<int, bool>> other;
@@ -343,7 +344,7 @@ double clearance(TTrack const &Switch, int const Path, bool const Atend)
other = std::make_pair(path, atend);
}
if (false == other.has_value())
return kStart;
return std::nullopt;
auto const theirs{ahead(Switch, other->first, other->second, kReach + 50.0)};
if (plan_gap(point, theirs) >= kFouling)
{
@@ -356,7 +357,7 @@ double clearance(TTrack const &Switch, int const Path, bool const Atend)
double back{0.0};
for (std::size_t i = 1; i < mine.size() && plan_gap(mine[i], own) >= kFouling; ++i)
back += plan_distance(mine[i - 1], mine[i]);
return std::max(0.0, kBeyond - back);
return -back;
}
auto const mine{ahead(Switch, Path, Atend, kReach)};
double along{0.0};
@@ -366,11 +367,18 @@ double clearance(TTrack const &Switch, int const Path, bool const Atend)
auto const step{plan_distance(mine[i - 1], mine[i])};
auto const next{plan_gap(mine[i], theirs)};
if (next >= kFouling)
return along + (next > gap ? step * std::clamp((kFouling - gap) / (next - gap), 0.0, 1.0) : step) + kBeyond;
return along + (next > gap ? step * std::clamp((kFouling - gap) / (next - gap), 0.0, 1.0) : step);
along += step;
gap = next;
}
return along;
return along - kBeyond;
}
// how far from the end of the switch its joint lies: 4.2 m beyond the fouling point at a branch, before the start elsewhere
double clearance(TTrack const &Switch, int const Path, bool const Atend)
{
auto const point{fouling(Switch, Path, Atend)};
return point.has_value() ? std::max(0.0, *point + kBeyond) : kStart;
}
} // namespace
@@ -1539,4 +1547,500 @@ std::string block_number(double const Km, int const Track, bool const Rising)
return std::to_string(number);
}
namespace
{
double constexpr kBeside{4.5}; // m, from the axis of a station track to a signal of it, at most
double constexpr kFacing{0.5}; // of the cosine between the way of a signal and the track, at least
double constexpr kLevel{1.5}; // m, the most a road crossing a track lies above or below it at a level crossing
double constexpr kSkew{0.2}; // of the sine between a road and a track, the least a level crossing is taken at
int constexpr kSearch{4096}; // of the steps of the way through the switches within a station track, the most tried
// the points of the path from its end, Length: in the plan
std::vector<glm::dvec3> from_end(TTrack const &Track, int const Path, bool const Atend, double &Length)
{
auto points{dense_points(Track, Path, Length)};
if (Atend)
std::reverse(points.begin(), points.end());
return points;
}
// a path of a track along a station track, run along from its end
struct step
{
TTrack *track{nullptr};
int path{0};
bool atend{false};
};
// the shortest way out of the end of the track through the tracks of others, the switches within a station track, on to one of
// its own not taken yet, no longer than the limit; its last step on that one, empty for none
std::vector<step> way_on(TTrack const &Track, bool const Atend, std::unordered_set<TTrack const *> const &Own, std::unordered_set<TTrack const *> const &Taken, double const Limit)
{
std::vector<step> best;
auto shortest{Limit};
std::vector<step> current;
auto budget{kSearch};
std::function<void(TTrack const &, int, bool, double)> search = [&](TTrack const &From, int const Path, bool const End, double const Length) {
auto const point{end_of(From, Path, End)};
auto *next{neighbour(From, Path, End)};
if (--budget < 0 || next == nullptr || false == rail(*next) || Length >= shortest || std::any_of(current.begin(), current.end(), [&](step const &Step) { return Step.track == next; }))
return;
if (Own.count(next) > 0)
{
int path{0};
bool atend{false};
if (Taken.count(next) == 0 && end_at(*next, point, path, atend))
{
best = current;
best.push_back({next, 0, atend});
shortest = Length;
}
return;
}
for (int path = 0; path < static_cast<int>(next->m_paths.size()); ++path)
for (auto const atend : {false, true})
if (glm::distance(end_of(*next, path, atend), point) < same_point)
{
current.push_back({next, path, atend});
search(*next, path, false == atend, Length + plan_length(*next, path));
current.pop_back();
}
};
search(Track, 0, Atend, 0.0);
return best;
}
// out of an end of a station track to the switch beyond it or to the end of the tracks
struct beyond
{
TTrack const *node{nullptr}; // the switch, none at the end of the tracks
bool open{false}; // neither within reach: the circuit of the track ends by another of plain track
double out{0.0}; // from the end of the track to the end of the switch, or to the end of the tracks
std::optional<double> fouling; // from the end of the track out to the fouling point of the switch
std::vector<glm::dvec3> points; // out from the end of the track, into the switch
std::vector<double> distances;
};
beyond out_of(TTrack const &Track, bool const Atend)
{
beyond result;
auto point{end_of(Track, 0, Atend)};
result.points.push_back(point);
result.distances.push_back(0.0);
auto *next{neighbour(Track, 0, Atend)};
std::unordered_set<TTrack const *> seen{&Track};
while (next != nullptr && rail(*next) && seen.insert(next).second)
{
int path{0};
bool atend{false};
if (false == end_at(*next, point, path, atend) || result.out > kReach)
break;
double length{0.0};
auto const points{from_end(*next, path, atend, length)};
for (std::size_t i = 1; i < points.size(); ++i)
{
result.distances.push_back(result.distances.back() + plan_distance(result.points.back(), points[i]));
result.points.push_back(points[i]);
}
if (node(*next))
{
result.node = next;
if (auto const at{fouling(*next, path, atend)})
result.fouling = result.out - *at;
return result;
}
result.out = result.distances.back();
point = end_of(*next, 0, false == atend);
next = neighbour(*next, 0, false == atend);
}
result.open = next != nullptr && rail(*next);
return result;
}
glm::dvec3 point_along(std::vector<glm::dvec3> const &Points, std::vector<double> const &Distances, double const Along)
{
auto const after{std::min<std::size_t>(std::upper_bound(Distances.begin(), Distances.end(), Along) - Distances.begin(), Distances.size() - 1)};
auto const before{after > 0 ? after - 1 : 0};
auto const span{Distances[after] - Distances[before]};
return glm::mix(Points[before], Points[after], span > 1e-9 ? std::clamp((Along - Distances[before]) / span, 0.0, 1.0) : 0.0);
}
// an end of a stretch of a station track between the switches: at a switch within the track or at an end of it, the distances along
// the track
struct bound
{
TTrack const *node{nullptr}; // the switch, none at the end of the tracks
bool open{false}; // no switch: the circuit ends by another of plain track
double joint{0.0}; // the end of the circuit of the stretch, the edge of a level crossing
std::optional<double> fouling; // of the switch
double start{0.0}; // of the switch, where the stretch meets it at its start; else the end of the tracks
bool crossing{false}; // a level crossing
};
// the limit of a stretch at its end, Inward: the way into it along the track. Behind the trains stopped by a signal the joint, or the
// fouling point where it lies within; else the fouling point, the start of the switch or the end of the tracks
limit limit_at(bound const &Bound, double const Inward, bool const Signalled)
{
limit found;
found.along = Bound.joint;
if (Bound.crossing)
found.what = limit::kind::crossing;
if (Bound.open || Bound.crossing)
return found;
if (Bound.node == nullptr)
{
found.what = limit::kind::buffer;
found.along = Bound.start;
return found;
}
found.name = Bound.node->name();
if (Bound.fouling.has_value())
{
if (false == Signalled || (*Bound.fouling - Bound.joint) * Inward > 0.0)
{
found.what = limit::kind::fouling;
found.along = *Bound.fouling;
}
}
else if (false == Signalled)
{
found.what = limit::kind::start;
found.along = Bound.start;
}
return found;
}
// the roads by the station, each as points along its axis with its width
struct road
{
std::vector<glm::dvec3> points;
double width{0.0};
glm::dvec2 low{0.0}; // the corners of the plan around it
glm::dvec2 high{0.0};
};
std::vector<road> roads_by(layout const &Layout)
{
std::vector<road> result;
glm::dvec2 low{std::numeric_limits<double>::max()};
glm::dvec2 high{std::numeric_limits<double>::lowest()};
for (auto const &path : Layout.paths)
for (auto const &point : path.points)
{
low = glm::min(low, glm::dvec2{point.x, point.z});
high = glm::max(high, glm::dvec2{point.x, point.z});
}
low -= glm::dvec2{50.0};
high += glm::dvec2{50.0};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || (track->iCategoryFlag & 2) == 0 || track->m_editorremoved)
continue;
for (auto const &segment : track->m_paths)
{
bezier const curve{segment};
auto const length{curve.plan_length(16)};
if (false == std::isfinite(length))
continue;
auto const count{std::clamp(static_cast<int>(length / 2.0), 2, 64)};
road entry;
entry.width = std::max(track->fTrackWidth, track->fTrackWidth2);
entry.low = glm::dvec2{std::numeric_limits<double>::max()};
entry.high = glm::dvec2{std::numeric_limits<double>::lowest()};
for (int i = 0; i <= count; ++i)
{
entry.points.push_back(curve.point(static_cast<double>(i) / count));
entry.low = glm::min(entry.low, glm::dvec2{entry.points.back().x, entry.points.back().z});
entry.high = glm::max(entry.high, glm::dvec2{entry.points.back().x, entry.points.back().z});
}
if (entry.low.x <= high.x && entry.high.x >= low.x && entry.low.y <= high.y && entry.high.y >= low.y)
result.push_back(std::move(entry));
}
}
return result;
}
double cross(glm::dvec2 const &A, glm::dvec2 const &B)
{
return A.x * B.y - A.y * B.x;
}
// the stretches along the track the roads crossing it on its level take, from the edge to the edge of each, in order
std::vector<std::pair<double, double>> crossings_of(std::vector<glm::dvec3> const &Points, std::vector<double> const &Distances, std::vector<road> const &Roads)
{
std::vector<std::pair<double, double>> result;
glm::dvec2 low{std::numeric_limits<double>::max()};
glm::dvec2 high{std::numeric_limits<double>::lowest()};
for (auto const &point : Points)
{
low = glm::min(low, glm::dvec2{point.x, point.z});
high = glm::max(high, glm::dvec2{point.x, point.z});
}
for (auto const &road : Roads)
{
if (road.low.x > high.x || road.high.x < low.x || road.low.y > high.y || road.high.y < low.y)
continue;
for (std::size_t k = 1; k < road.points.size(); ++k)
{
glm::dvec2 const r0{road.points[k - 1].x, road.points[k - 1].z};
glm::dvec2 const across{glm::dvec2{road.points[k].x, road.points[k].z} - r0};
if (glm::dot(across, across) < 1e-9)
continue;
for (std::size_t i = 1; i < Points.size(); ++i)
{
glm::dvec2 const p0{Points[i - 1].x, Points[i - 1].z};
glm::dvec2 const along{glm::dvec2{Points[i].x, Points[i].z} - p0};
auto const determinant{cross(along, across)};
if (std::abs(determinant) < 1e-9)
continue;
auto const u{cross(r0 - p0, across) / determinant};
auto const t{cross(r0 - p0, along) / determinant};
if (u < 0.0 || u > 1.0 || t < 0.0 || t > 1.0)
continue;
if (std::abs(glm::mix(road.points[k - 1].y, road.points[k].y, t) - glm::mix(Points[i - 1].y, Points[i].y, u)) > kLevel)
continue;
auto const sine{std::abs(determinant) / (glm::length(along) * glm::length(across))};
auto const half{road.width * 0.5 / std::max(sine, kSkew)};
auto const at{Distances[i - 1] + (Distances[i] - Distances[i - 1]) * u};
result.emplace_back(at - half, at + half);
}
}
}
std::sort(result.begin(), result.end());
std::vector<std::pair<double, double>> merged;
for (auto const &part : result)
if (false == merged.empty() && part.first <= merged.back().second)
merged.back().second = std::max(merged.back().second, part.second);
else
merged.push_back(part);
return merged;
}
} // namespace
std::vector<lengths> track_lengths(layout const &Layout, std::vector<numbered> const &Tracks, std::vector<signal> const &Signals)
{
std::vector<lengths> result;
auto const roads{roads_by(Layout)};
for (auto const &track : Tracks)
{
std::vector<TTrack *> own;
std::unordered_map<TTrack const *, std::size_t> circuit_of;
for (std::size_t circuit = 0; circuit < track.circuits.size(); ++circuit)
for (auto *part : track.circuits[circuit])
if (false == node(*part) && circuit_of.emplace(part, circuit).second)
own.push_back(part);
auto const ends{ends_out(own)};
if (ends.empty())
continue;
// from the end at the lower chainage along its tracks, through the switches between them: the stretches between the switches
// and the joints, each between the end of one bound and the start of the next
auto const first{std::min_element(ends.begin(), ends.end(), [&](track_end const &A, track_end const &B) { return project(Layout, A.point).x < project(Layout, B.point).x; })};
std::unordered_set<TTrack const *> const members(own.begin(), own.end());
std::unordered_set<TTrack const *> taken;
std::vector<glm::dvec3> points;
std::vector<double> distances;
std::vector<std::pair<TTrack const *, int>> paths;
std::vector<std::pair<bound, bound>> bounds; // of the stretch before, of the one after
double within{0.0};
auto const add = [&](TTrack const &Track, int const Path, bool const Atend) {
double length{0.0};
auto const along{from_end(Track, Path, Atend, length)};
for (std::size_t i = points.empty() ? 0 : 1; i < along.size(); ++i)
{
distances.push_back(points.empty() ? 0.0 : distances.back() + plan_distance(points.back(), along[i]));
points.push_back(along[i]);
}
paths.emplace_back(&Track, Path);
};
auto const low{out_of(*first->track, first->atend)};
{
bound after;
after.node = low.node;
after.open = low.open;
if (low.fouling.has_value())
after.fouling = -*low.fouling;
after.start = -low.out;
bounds.emplace_back(bound{}, after);
}
auto *at{first->track};
auto atend{first->atend};
taken.insert(at);
add(*at, 0, atend);
while (true)
{
auto const way{way_on(*at, false == atend, members, taken, kReach)};
if (way.empty())
break;
if (way.size() == 1 && circuit_of[way.front().track] == circuit_of[at])
{
at = way.front().track;
atend = way.front().atend;
taken.insert(at);
add(*at, 0, atend);
continue;
}
bound before;
bound after;
before.joint = distances.back();
for (std::size_t i = 0; i < way.size(); ++i)
{
auto const &step{way[i]};
auto const from{distances.back()};
if (i + 1 == way.size())
after.joint = from;
add(*step.track, step.path, step.atend);
if (false == node(*step.track))
continue;
within += distances.back() - from;
if (before.node == nullptr)
{
before.node = step.track;
before.start = from;
if (auto const out{fouling(*step.track, step.path, step.atend)})
before.fouling = from - *out;
}
after.node = step.track;
after.start = distances.back();
after.fouling.reset();
if (auto const out{fouling(*step.track, step.path, false == step.atend)})
after.fouling = distances.back() + *out;
}
before.open = after.open = before.node == nullptr;
bounds.emplace_back(before, after);
at = way.back().track;
atend = way.back().atend;
taken.insert(at);
}
auto const length{distances.back()};
auto const high{out_of(*at, false == atend)};
{
bound before;
before.node = high.node;
before.open = high.open;
before.joint = length;
if (high.fouling.has_value())
before.fouling = length + *high.fouling;
before.start = length + high.out;
bounds.emplace_back(before, bound{});
}
// the level crossings within the stretches part them
for (auto const &[from, to] : crossings_of(points, distances, roads))
{
auto const middle{(from + to) * 0.5};
for (std::size_t index = 0; index + 1 < bounds.size(); ++index)
if (middle > bounds[index].second.joint && middle < bounds[index + 1].first.joint)
{
bound before;
before.crossing = true;
before.joint = from;
bound after;
after.crossing = true;
after.joint = to;
bounds.insert(bounds.begin() + index + 1, {before, after});
break;
}
}
lengths entry;
entry.number = track.number;
entry.construction = length + (low.open ? 0.0 : low.out) + (high.open ? 0.0 : high.out) - within;
for (std::size_t i = low.points.size(); i-- > 1;)
{
entry.points.push_back(low.points[i]);
entry.distances.push_back(-low.distances[i]);
}
entry.points.insert(entry.points.end(), points.begin(), points.end());
entry.distances.insert(entry.distances.end(), distances.begin(), distances.end());
for (std::size_t i = 1; i < high.points.size(); ++i)
{
entry.points.push_back(high.points[i]);
entry.distances.push_back(length + high.distances[i]);
}
// the signals by it: nearer to it than to any other track, facing along it
struct stop
{
double along{0.0};
int facing{1};
std::string name;
};
std::vector<stop> stops;
for (auto const &signal : Signals)
{
glm::dvec2 const point{signal.point.x, signal.point.z};
auto gap{std::numeric_limits<double>::max()};
double along{0.0};
glm::dvec2 tangent{0.0, 1.0};
for (std::size_t i = 1; i < points.size(); ++i)
{
glm::dvec2 const a{points[i - 1].x, points[i - 1].z};
glm::dvec2 const segment{glm::dvec2{points[i].x, points[i].z} - a};
auto const length2{glm::dot(segment, segment)};
if (length2 < 1e-12)
continue;
auto const t{std::clamp(glm::dot(point - a, segment) / length2, 0.0, 1.0)};
if (auto const distance{glm::distance(point, a + segment * t)}; distance < gap)
{
gap = distance;
along = distances[i - 1] + (distances[i] - distances[i - 1]) * t;
tangent = segment / std::sqrt(length2);
}
}
if (gap > kBeside || std::any_of(Layout.paths.begin(), Layout.paths.end(), [&](path const &Path) {
return std::none_of(paths.begin(), paths.end(), [&](auto const &Own) { return Own.first == Path.track && Own.second == Path.index; }) && plan_gap(signal.point, Path.points) < gap - 0.01;
}))
continue;
auto const cosine{glm::dot(glm::normalize(glm::dvec2{signal.travel.x, signal.travel.z} + glm::dvec2{1e-12, 0.0}), tangent)};
if (std::abs(cosine) >= kFacing)
stops.push_back({along, cosine > 0.0 ? 1 : -1, signal.name});
}
// each way the longest: from a signal facing the trains back to the end of its stretch behind them, else of the stretches
// between the fouling points
for (int way = 0; way < 2; ++way)
{
auto const forward{way == 0};
auto const inward{forward ? 1.0 : -1.0};
auto &part{entry.ways[way]};
part.length = -1.0;
for (std::size_t index = 0; index + 1 < bounds.size(); ++index)
{
auto const &before{bounds[index].second};
auto const &after{bounds[index + 1].first};
auto const rear{forward ? limit_at(before, inward, true) : limit_at(after, inward, true)};
for (auto const &stop : stops)
{
if (stop.facing != (forward ? 1 : -1) || stop.along < before.joint || stop.along > after.joint)
continue;
auto const length{(stop.along - rear.along) * inward};
if (length > 0.0 && length > part.length)
{
part.rear = rear;
part.head.what = limit::kind::signal;
part.head.name = stop.name;
part.head.along = stop.along;
part.length = length;
}
}
}
if (part.length < 0.0)
for (std::size_t index = 0; index + 1 < bounds.size(); ++index)
{
auto const lower{limit_at(bounds[index].second, 1.0, false)};
auto const upper{limit_at(bounds[index + 1].first, -1.0, false)};
if (auto const length{upper.along - lower.along}; length > part.length)
{
part.rear = forward ? lower : upper;
part.head = forward ? upper : lower;
part.length = length;
}
}
part.length = std::max(0.0, part.length);
part.head.point = point_along(entry.points, entry.distances, part.head.along);
part.rear.point = point_along(entry.points, entry.distances, part.rear.along);
}
result.push_back(std::move(entry));
}
return result;
}
} // namespace station

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <array>
#include <functional>
#include <optional>
#include <string>
@@ -186,4 +187,43 @@ double distance_on(line const &Line, glm::dvec3 const &Point, double &Gap);
// signal odd on track 1, even on track 2: 241, 242; Rising: its trains run towards the higher km
std::string block_number(double const Km, int const Track, bool const Rising);
// a limit of the part of a station track a train stands on
struct limit
{
enum class kind
{
signal,
joint, // of the track circuits
fouling, // the fouling point of a switch
start, // of a switch
buffer, // the end of the tracks, where the buffer stop stands
crossing // the edge of a road crossing the track on its level
};
kind what{kind::joint};
std::string name; // of the signal, of the switch for the others
double along{0.0}; // along the track, from its end at the lower chainage
glm::dvec3 point{0.0};
};
// the part of a station track the trains running one way stand on
struct stand
{
limit head; // the trains stop by it
limit rear; // behind them
double length{0.0};
};
// the lengths of a station track, as the list of the tracks of a station gives them: the rozporządzenie of the minister of
// infrastructure on the railway structures (Dz.U. 2026 poz. 1225) § 2
struct lengths
{
std::string number;
double construction{0.0}; // between the ends of the switches at its ends, or to the buffer stop, the switches within it left out
// the useful one, of the trains running along the chainage, then of those running against it: from the signal they stop by back
// to the joint, the fouling point or the level crossing behind them, between those where no signal stands
std::array<stand, 2> ways;
std::vector<glm::dvec3> points; // along the track from its end at the lower chainage, through the switches within it
std::vector<double> distances; // of the points, from that end
};
// the lengths of the station tracks, the signals standing by them among the signals given
std::vector<lengths> track_lengths(layout const &Layout, std::vector<numbered> const &Tracks, std::vector<signal> const &Signals);
} // namespace station