mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-27 15:17:29 +01:00
400 lines
12 KiB
C++
400 lines
12 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "editor/editorStation.hpp"
|
|
#include "editor/editorAlignmentStore.hpp"
|
|
#include "editor/editorGeometry.hpp"
|
|
#include "editor/editorSwitchStore.hpp"
|
|
#include "editor/editorTrack.hpp"
|
|
#include "simulation/simulation.h"
|
|
#include "world/Track.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
namespace station
|
|
{
|
|
|
|
using geometry::bezier;
|
|
using geometry::plan_distance;
|
|
using geometry::same_point;
|
|
|
|
namespace
|
|
{
|
|
|
|
bool rail(TTrack const &Track)
|
|
{
|
|
return (Track.iCategoryFlag & 15) == 1 && false == Track.m_editorremoved && false == Track.m_paths.empty();
|
|
}
|
|
|
|
// switches, crossings and turntables: where the plain track between them ends
|
|
bool node(TTrack const &Track)
|
|
{
|
|
return Track.eType != tt_Normal;
|
|
}
|
|
|
|
glm::dvec3 end_of(TTrack const &Track, int const Path, bool const Atend)
|
|
{
|
|
return Track.m_paths[Path].points[Atend ? segment_data::point::end : segment_data::point::start];
|
|
}
|
|
|
|
TTrack *neighbour(TTrack const &Track, int const Path, bool const Atend)
|
|
{
|
|
if (Track.SwitchExtension != nullptr && (Track.eType == tt_Switch || Track.eType == tt_Cross))
|
|
return Atend ? Track.SwitchExtension->pNexts[Path & 1] : Track.SwitchExtension->pPrevs[Path & 1]; // NOSONAR
|
|
return Atend ? Track.trNext : Track.trPrev;
|
|
}
|
|
|
|
// the end of the track at the point
|
|
bool end_at(TTrack const &Track, glm::dvec3 const &Point, int &Path, bool &Atend)
|
|
{
|
|
for (int path = 0; path < static_cast<int>(Track.m_paths.size()); ++path)
|
|
for (auto const atend : {false, true})
|
|
if (glm::distance(end_of(Track, path, atend), Point) < same_point)
|
|
{
|
|
Path = path;
|
|
Atend = atend;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// the ends of the paths of a switch, each place once: its start, the ends of its paths
|
|
std::vector<std::pair<int, bool>> ends_of(TTrack const &Track)
|
|
{
|
|
std::vector<std::pair<int, bool>> result;
|
|
for (int path = 0; path < static_cast<int>(Track.m_paths.size()); ++path)
|
|
for (auto const atend : {false, true})
|
|
{
|
|
auto const point{end_of(Track, path, atend)};
|
|
if (std::none_of(result.begin(), result.end(), [&](auto const &End) { return glm::distance(end_of(Track, End.first, End.second), point) < same_point; }))
|
|
result.emplace_back(path, atend);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double plan_length(TTrack const &Track, int const Path)
|
|
{
|
|
return bezier{Track.m_paths[Path]}.plan_length(16);
|
|
}
|
|
|
|
// plain track from an end of a switch on, up to the next switch, a free end or past the limit
|
|
struct chain
|
|
{
|
|
std::vector<TTrack *> tracks;
|
|
double length{0.0};
|
|
TTrack *reached{nullptr}; // the switch met at its other end
|
|
bool ended{false}; // at a free end
|
|
};
|
|
|
|
chain follow(TTrack const &From, int const Path, bool const Atend, double const Limit)
|
|
{
|
|
chain result;
|
|
auto point{end_of(From, Path, Atend)};
|
|
auto *next{neighbour(From, Path, Atend)};
|
|
while (next != nullptr && result.length <= Limit)
|
|
{
|
|
if (false == rail(*next))
|
|
break;
|
|
if (node(*next))
|
|
{
|
|
result.reached = next;
|
|
return result;
|
|
}
|
|
int path{0};
|
|
bool atend{false};
|
|
if (false == end_at(*next, point, path, atend) || std::find(result.tracks.begin(), result.tracks.end(), next) != result.tracks.end())
|
|
break;
|
|
result.tracks.push_back(next);
|
|
result.length += plan_length(*next, 0);
|
|
point = end_of(*next, 0, false == atend);
|
|
next = neighbour(*next, 0, false == atend);
|
|
}
|
|
result.ended = next == nullptr || false == rail(*next);
|
|
return result;
|
|
}
|
|
|
|
// the switches and the plain tracks of the station, and the lines leading out of it
|
|
struct area
|
|
{
|
|
std::vector<TTrack *> switches;
|
|
std::vector<TTrack *> plain;
|
|
std::vector<chain> exits;
|
|
};
|
|
|
|
area collect(TTrack &From, options const &Options)
|
|
{
|
|
area result;
|
|
if (false == rail(From))
|
|
return result;
|
|
auto *first{node(From) ? &From : nullptr};
|
|
if (first == nullptr)
|
|
{
|
|
auto nearest{Options.link};
|
|
for (auto const atend : {false, true})
|
|
if (auto const line{follow(From, 0, atend, Options.link)}; line.reached != nullptr && line.length < nearest)
|
|
{
|
|
nearest = line.length;
|
|
first = line.reached;
|
|
}
|
|
}
|
|
if (first == nullptr)
|
|
return result;
|
|
std::unordered_set<TTrack const *> known{first};
|
|
result.switches.push_back(first);
|
|
for (std::size_t i = 0; i < result.switches.size(); ++i)
|
|
{
|
|
auto const &from{*result.switches[i]};
|
|
for (auto const &[path, atend] : ends_of(from))
|
|
{
|
|
auto line{follow(from, path, atend, Options.link)};
|
|
if (line.reached == nullptr && false == line.ended)
|
|
{
|
|
result.exits.push_back(std::move(line));
|
|
continue;
|
|
}
|
|
for (auto *track : line.tracks)
|
|
if (known.insert(track).second)
|
|
result.plain.push_back(track);
|
|
if (line.reached != nullptr && known.insert(line.reached).second)
|
|
result.switches.push_back(line.reached);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<glm::dvec3> points_of(TTrack const &Track, int const Path, double &Length)
|
|
{
|
|
bezier const curve{Track.m_paths[Path]};
|
|
Length = curve.plan_length(16);
|
|
auto const count{std::clamp(static_cast<int>(Length / 2.0), 2, 64)};
|
|
std::vector<glm::dvec3> result;
|
|
for (int i = 0; i <= count; ++i)
|
|
result.push_back(curve.point(static_cast<double>(i) / count));
|
|
return result;
|
|
}
|
|
|
|
// the map shows the east to the right and the north up: the x of the scenery runs to the west
|
|
glm::dvec2 map_of(glm::dvec3 const &Point)
|
|
{
|
|
return {-Point.x, Point.z};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::optional<layout> find(TTrack &From, options const &Options)
|
|
{
|
|
auto const found{collect(From, Options)};
|
|
if (found.switches.empty())
|
|
return std::nullopt;
|
|
std::unordered_set<TTrack const *> switches(found.switches.begin(), found.switches.end());
|
|
// the axis: the run through the station along the main paths of its switches which passes the most of them
|
|
std::vector<TTrack *> starts;
|
|
for (auto const &line : found.exits)
|
|
if (false == line.tracks.empty())
|
|
starts.push_back(line.tracks.front());
|
|
if (starts.empty())
|
|
starts.push_back(found.switches.front());
|
|
editor_track::route best;
|
|
std::size_t most{0};
|
|
double longest{0.0};
|
|
for (auto *start : starts)
|
|
{
|
|
auto route{editor_track::run_route(*start, 20000.0)};
|
|
std::size_t passed{0};
|
|
double length{0.0};
|
|
for (auto const &span : route.spans)
|
|
{
|
|
passed += switches.count(span.track);
|
|
length += plan_length(*span.track, span.path);
|
|
}
|
|
if (passed > most || (passed == most && length > longest))
|
|
{
|
|
most = passed;
|
|
longest = length;
|
|
best = std::move(route);
|
|
}
|
|
}
|
|
layout result;
|
|
for (auto const &span : best.spans)
|
|
{
|
|
double length{0.0};
|
|
auto points{points_of(*span.track, span.path, length)};
|
|
if (false == span.forward)
|
|
std::reverse(points.begin(), points.end());
|
|
for (auto const &point : points)
|
|
if (result.axis.empty() || plan_distance(result.axis.back(), point) > 0.01)
|
|
result.axis.push_back(point);
|
|
}
|
|
if (result.axis.size() < 2)
|
|
return std::nullopt;
|
|
auto const measure = [&]() {
|
|
result.chainage.assign(1, 0.0);
|
|
for (std::size_t i = 1; i < result.axis.size(); ++i)
|
|
result.chainage.push_back(result.chainage.back() + plan_distance(result.axis[i - 1], result.axis[i]));
|
|
};
|
|
measure();
|
|
// the chainage runs with the km of the switches, when they have one and most of them agree on it
|
|
double constexpr kAgree{50.0}; // m
|
|
std::vector<std::pair<double, double>> marks; // the chainage and the km, in m
|
|
for (auto *track : found.switches)
|
|
{
|
|
auto const start{end_of(*track, 0, false)};
|
|
std::optional<double> km;
|
|
if (auto const record{switch_store::named(*track)}; record && record->km)
|
|
km = record->km;
|
|
else
|
|
km = alignment_store::chainage_at(start);
|
|
if (km)
|
|
marks.emplace_back(project(result, start).x, *km);
|
|
}
|
|
auto const length{result.chainage.back()};
|
|
struct fit
|
|
{
|
|
std::size_t agree{0};
|
|
double origin{0.0};
|
|
};
|
|
auto const fit_of = [&](bool const Reversed) {
|
|
std::vector<double> origins;
|
|
for (auto const &[chainage, km] : marks)
|
|
origins.push_back(km - (Reversed ? length - chainage : chainage));
|
|
std::nth_element(origins.begin(), origins.begin() + origins.size() / 2, origins.end());
|
|
fit result{0, origins[origins.size() / 2]};
|
|
for (auto const origin : origins)
|
|
result.agree += std::abs(origin - result.origin) <= kAgree ? 1 : 0;
|
|
return result;
|
|
};
|
|
if (false == marks.empty())
|
|
{
|
|
auto const along{fit_of(false)};
|
|
auto const against{fit_of(true)};
|
|
auto const reversed{against.agree > along.agree};
|
|
auto const &best{reversed ? against : along};
|
|
if (best.agree * 2 >= marks.size())
|
|
{
|
|
if (reversed)
|
|
{
|
|
std::reverse(result.axis.begin(), result.axis.end());
|
|
measure();
|
|
}
|
|
result.kilometrage = true;
|
|
result.km = best.origin / 1000.0;
|
|
}
|
|
}
|
|
// the paths, laid out along the axis
|
|
auto const add = [&](TTrack &Track, bool const Approach) {
|
|
for (int index = 0; index < static_cast<int>(Track.m_paths.size()); ++index)
|
|
{
|
|
path entry;
|
|
entry.track = &Track;
|
|
entry.index = index;
|
|
entry.approach = Approach;
|
|
entry.points = points_of(Track, index, entry.length);
|
|
for (auto const &point : entry.points)
|
|
entry.schematic.push_back(project(result, point));
|
|
result.paths.push_back(std::move(entry));
|
|
}
|
|
};
|
|
for (auto *track : found.switches)
|
|
add(*track, false);
|
|
for (auto *track : found.plain)
|
|
add(*track, false);
|
|
for (auto const &line : found.exits)
|
|
{
|
|
double length{0.0};
|
|
for (auto *track : line.tracks)
|
|
{
|
|
if (length >= Options.approach)
|
|
break;
|
|
add(*track, true);
|
|
length += plan_length(*track, 0);
|
|
}
|
|
}
|
|
result.from = std::numeric_limits<double>::max();
|
|
result.to = std::numeric_limits<double>::lowest();
|
|
result.low = std::numeric_limits<double>::max();
|
|
result.high = std::numeric_limits<double>::lowest();
|
|
for (auto const &entry : result.paths)
|
|
{
|
|
if (entry.approach)
|
|
continue;
|
|
for (auto const &point : entry.schematic)
|
|
{
|
|
if (switches.count(entry.track) > 0)
|
|
{
|
|
result.from = std::min(result.from, point.x);
|
|
result.to = std::max(result.to, point.x);
|
|
}
|
|
result.low = std::min(result.low, point.y);
|
|
result.high = std::max(result.high, point.y);
|
|
}
|
|
}
|
|
result.switches = found.switches;
|
|
std::sort(result.switches.begin(), result.switches.end(), [&](TTrack const *A, TTrack const *B) { return project(result, end_of(*A, 0, false)).x < project(result, end_of(*B, 0, false)).x; });
|
|
return result;
|
|
}
|
|
|
|
glm::dvec2 project(layout const &Layout, glm::dvec3 const &Point)
|
|
{
|
|
auto const point{map_of(Point)};
|
|
auto best{std::numeric_limits<double>::max()};
|
|
glm::dvec2 result{0.0};
|
|
for (std::size_t i = 1; i < Layout.axis.size(); ++i)
|
|
{
|
|
auto const a{map_of(Layout.axis[i - 1])};
|
|
auto const b{map_of(Layout.axis[i])};
|
|
auto const along{b - a};
|
|
auto const length{glm::length(along)};
|
|
if (length < 1e-9)
|
|
continue;
|
|
auto const unit{along / length};
|
|
auto const t{std::clamp(glm::dot(point - a, unit), 0.0, length)};
|
|
auto const foot{a + unit * t};
|
|
auto const distance{glm::distance(point, foot)};
|
|
if (distance >= best)
|
|
continue;
|
|
best = distance;
|
|
auto const side{point - foot};
|
|
result = {Layout.chainage[i - 1] + t, unit.x * side.y - unit.y * side.x};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double km_of(layout const &Layout, double const Chainage)
|
|
{
|
|
return Layout.km + Chainage / 1000.0;
|
|
}
|
|
|
|
std::vector<TTrack *> stations(options const &Options)
|
|
{
|
|
std::vector<TTrack *> result;
|
|
std::unordered_set<TTrack const *> taken;
|
|
for (auto *track : simulation::Paths.sequence())
|
|
{
|
|
if (track == nullptr || false == rail(*track) || false == node(*track) || taken.count(track) > 0)
|
|
continue;
|
|
auto const found{collect(*track, Options)};
|
|
for (auto const *member : found.switches)
|
|
taken.insert(member);
|
|
result.push_back(track);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<TTrack *> switches_of(TTrack &Switch, options const &Options)
|
|
{
|
|
return collect(Switch, Options).switches;
|
|
}
|
|
|
|
} // namespace station
|