17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 17:04:53 +01:00

feat(editor): the signalling mode sets the signals of the lines out of a station, the automatic block signals and the block sections to the next station on a double-track line, a warning disc before the entry signal on a single-track one

The station keeps its km and the direction of its numbering in its record. The block signals are named after their km, odd on track 1 and even on track 2, the sections after the signals at their ends, and each signal gets the control of its section.
This commit is contained in:
maj00r
2026-10-22 12:13:22 +02:00
parent e230544987
commit 46e665c468
8 changed files with 919 additions and 41 deletions

View File

@@ -1891,7 +1891,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
// takes them out, as one step of the undo
std::size_t remove_templates(std::vector<scene::instance_handle> const &Instances) override;
bool standing_signals(std::vector<standing_signal> &Signals, bool const Wait) override;
std::size_t signal_batch(std::vector<signal_order> const &Placed, std::vector<signal_rename> const &Renamed, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error) override;
std::size_t signal_batch(signal_changes const &Changes, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error) override;
struct signal_template
{
@@ -2383,7 +2383,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
hekto_tool m_hekto{*this, *this, *this, *this, *this, *this};
fouling_tool m_fouling{*this, *this, *this, *this, *this};
parallel_tool m_parallel{*this, *this, *this, *this};
srk_tool m_srk{*this, *this, *this, *this};
srk_tool m_srk{*this, *this, *this, *this, *this};
std::vector<editor_tool *> m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle, &m_hekto, &m_fouling, &m_parallel, &m_srk};
void infra_rebase();

View File

@@ -883,19 +883,26 @@ bool editor_mode::standing_signals(std::vector<standing_signal> &Signals, bool c
return false;
signal_refresh(true);
for (auto const &standing : m_signal.standing)
if (standing.kind >= 0)
Signals.push_back({standing.include.instance, standing.name, kKinds[standing.kind], standing.include.location, standing.include.yaw});
{
if (standing.kind < 0)
continue;
std::string file;
std::vector<std::string> values;
auto const *item{signal_template_of(standing.include.file)};
auto const linked{item != nullptr && item->linked && editor_includes::parse_directive(scene::Layers.directive(standing.include.instance), file, values) && values.size() > 6};
Signals.push_back({standing.include.instance, standing.name, kKinds[standing.kind], standing.include.location, standing.include.yaw, linked ? values[6] : std::string{}});
}
return true;
}
std::size_t editor_mode::signal_batch(std::vector<signal_order> const &Placed, std::vector<signal_rename> const &Renamed, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error)
std::size_t editor_mode::signal_batch(signal_changes const &Changes, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error)
{
auto &tool{m_signal};
signal_scan();
signal_refresh(true);
std::vector<template_item> items;
std::vector<std::pair<signal_spot, std::string>> reads;
for (auto const &order : Placed)
for (auto const &order : Changes.placed)
{
auto const kind{kind_index(order.kind)};
auto const *item{kind >= 0 && tool.chosen[kind] >= 0 && tool.chosen[kind] < static_cast<int>(tool.templates.size()) ? &tool.templates[tool.chosen[kind]] : nullptr};
@@ -919,16 +926,22 @@ std::size_t editor_mode::signal_batch(std::vector<signal_order> const &Placed, s
if (variant->plate)
placing.values["#6"] = plate_for(std::string{}, order.plate);
if (variant->linked)
placing.values["#7"] = "none";
placing.values["#7"] = order.linked.empty() ? std::string{"none"} : order.linked;
items.push_back(std::move(placing));
reads.emplace_back(spot, variant->read.empty() ? std::string{} : editor_includes::substitute(variant->read, {order.name}));
}
// the names passed round among the signals go through ones of their own first, or a read event renamed would be renamed again
items.insert(items.end(), Changes.others.begin(), Changes.others.end());
EditorSnapshot step;
step.tracks = std::move(States);
std::vector<std::pair<scene::instance_handle, std::string>> directives;
std::unordered_set<scene::instance_handle> changed;
auto const record = [&](scene::instance_handle const Instance, std::string const &Before) {
if (changed.insert(Instance).second)
directives.emplace_back(Instance, Before);
};
for (auto const temporary : {true, false})
for (auto const &rename : Renamed)
for (auto const &rename : Changes.renamed)
{
auto const *standing{signal_standing_of(rename.instance)};
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr};
@@ -937,20 +950,36 @@ std::size_t editor_mode::signal_batch(std::vector<signal_order> const &Placed, s
std::vector<std::string> values;
if (item == nullptr || false == editor_includes::parse_directive(before, file, values) || values.empty())
continue;
if (temporary && ToLower(values[0]) == ToLower(rename.name))
continue;
auto const read_before{item->read.empty() ? std::string{} : editor_includes::substitute(item->read, values)};
values[0] = rename.name + (temporary ? "~" : "");
if (item->plate && values.size() > 5)
if (item->plate && values.size() > 5 && false == rename.plate.empty())
values[5] = plate_for(std::string{}, rename.plate);
if (item->linked && values.size() > 6 && false == rename.linked.empty())
values[6] = rename.linked;
auto const read_after{item->read.empty() ? std::string{} : editor_includes::substitute(item->read, values)};
auto const directive{editor_includes::compose_directive(file, values)};
if (directive == before)
continue;
if (temporary)
directives.emplace_back(rename.instance, before);
record(rename.instance, before);
set_include_directive(rename.instance, directive);
if (read_before != read_after)
signal_renamed(read_before, read_after, step);
}
for (auto const &[instance, values] : Changes.values)
{
auto const before{scene::Layers.directive(instance)};
std::string file;
std::vector<std::string> standing;
if (false == editor_includes::parse_directive(before, file, standing))
continue;
if (auto const directive{editor_includes::compose_directive(file, values)}; directive != before)
{
record(instance, before);
set_include_directive(instance, directive);
}
}
std::vector<scene::instance_handle> instances;
if (Error.empty() && false == items.empty())
{

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/editoruilayer.h"
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include "editor/editorIncludeInfo.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorTrack.hpp"
@@ -34,6 +35,7 @@ http://mozilla.org/MPL/2.0/.
#include <map>
#include <tuple>
#include <functional>
#include <unordered_map>
#include <unordered_set>
namespace
@@ -45,6 +47,13 @@ double constexpr kJointRange{1500.0}; // m, of the joints shown in the scenery
double constexpr kLabelRange{700.0}; // m, of the names shown there, farther ones crowd the horizon
double constexpr kRailHead{0.18}; // m, above the axis of a path
double constexpr kLineLimit{30000.0}; // m, of a line followed out to the next station
double constexpr kBlock{1400.0}; // m, of a block section of the automatic block
double constexpr kPredicted{305.0}; // m, out from the next station to its entry signal, while it has none
double constexpr kWarning{1000.0}; // m, from a warning disc to its entry signal, the braking distance
double constexpr kWarningFast{1300.0}; // m, the same on lines faster than 120 km/h
char const *const kControl{"sbl_odstep.inc"}; // the events of a block section, switching the block signal guarding it
ImU32 constexpr kPlain{IM_COL32(170, 176, 188, 255)};
ImU32 constexpr kSwitch{IM_COL32(240, 200, 90, 255)};
ImU32 constexpr kApproach{IM_COL32(95, 100, 112, 255)};
@@ -122,6 +131,16 @@ std::string signal_shown(std::string const &Name, std::string const &Stem)
return text;
}
// a signal in the names of the block sections: a block signal by its number, a station one by its letter, with the number of the
// track: 2411, A2
std::string section_tag(std::string const &Name, int const Track)
{
auto const cut{Name.rfind('_')};
auto tag{cut == std::string::npos ? Name : Name.substr(cut + 1)};
std::transform(tag.begin(), tag.end(), tag.begin(), [](char const Letter) { return static_cast<char>(std::toupper(static_cast<unsigned char>(Letter))); });
return tag + std::to_string(Track);
}
// the way the trains a signal faces run, in the plan: it stands turned to them
glm::dvec2 travel_of(double const Yaw)
{
@@ -174,6 +193,10 @@ void srk_tool::load()
{
if (word == "line")
words >> entry.line;
else if (word == "km")
words >> entry.km;
else if (word == "from")
entry.reversed = (words >> word) && word == "right";
else if (word == "anchor")
words >> entry.anchor;
else if (word == "name")
@@ -185,6 +208,7 @@ void srk_tool::load()
}
entry.code = shown(entry.code);
entry.line = shown(entry.line);
entry.km = shown(entry.km);
entry.track = simulation::Paths.find(entry.anchor);
if (false == entry.anchor.empty())
m_records.push_back(std::move(entry));
@@ -209,7 +233,7 @@ void srk_tool::store()
continue;
if (lines.empty())
lines.emplace_back("# stations described in the track editor, the simulation skips these lines");
lines.push_back(format("station %s line %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), anchor_of(entry).c_str(), entry.name.c_str()));
lines.push_back(format("station %s line %s km %s from %s anchor %s name %s", word_of(entry.code).c_str(), word_of(entry.line).c_str(), word_of(entry.km).c_str(), entry.reversed ? "right" : "left", anchor_of(entry).c_str(), entry.name.c_str()));
}
scene::Layers.mark(layer, kMark, std::move(lines));
}
@@ -239,6 +263,7 @@ void srk_tool::open(TTrack &Track)
m_record = index;
}
m_anchor = m_record >= 0 ? m_records[m_record].track : m_layout->switches.front();
m_reversed = m_record >= 0 && m_records[m_record].reversed;
survey();
}
@@ -494,6 +519,14 @@ void srk_tool::render_options()
item_tooltip("The abbreviation of the station: the names of its switches, track circuits and signals begin with it");
text("Line", "##srkline", entry != nullptr ? entry->line : std::string{}, "2", [&](std::string const &Value) { record_of_open().line = word_of(Value) == "-" ? std::string{} : word_of(Value); });
item_tooltip("The number of the railway line the station lies on, as the block sections between the stations are named after it");
double known{0.0};
auto const hint{layout.kilometrage && km_of_open((layout.from + layout.to) * 0.5, known) ? format("%.3f", known) : std::string{"189.563"}};
text("Km", "##srkkm", entry != nullptr ? entry->km : std::string{}, hint.c_str(), [&](std::string const &Value) {
auto km{word_of(Value)};
std::replace(km.begin(), km.end(), ',', '.');
record_of_open().km = km == "-" ? std::string{} : km;
});
item_tooltip("The km of the middle of the station along the line, rising the way the switches are numbered: the block signals of the lines are numbered after the km");
ImGui::EndTable();
}
step_heading(1, STR_C("Switch numbers"));
@@ -501,7 +534,11 @@ void srk_tool::render_options()
if (segmented("##srkfrom", from,
{{0, STR_C("From the left"), layout.kilometrage ? "From the lower km, as PKP PLK numbers them" : "From the west end of the station"},
{1, STR_C("From the right"), layout.kilometrage ? "From the higher km" : "From the east end of the station"}}))
{
m_reversed = from == 1;
record_of_open().reversed = m_reversed;
m_changed = true;
}
if (ImGui::Button(STR_C("Number the switches"), ImVec2(-1.0f, 0.0f)))
number_switches();
item_tooltip("Numbers the switches 1, 2, 3... along the station and names them after its code, as on the lines of PKP PLK: bpd_1, "
@@ -522,6 +559,14 @@ void srk_tool::render_options()
"signal at the end of each station track a train is sent out of, F1, G2 towards the start, R1, P2 towards the end; a "
"shunting signal Tm1, Tm2... at the end of a shorter track. A signal standing by the place of one gets its name, none is "
"doubled. The kinds of signals chosen in the Signals tool. One step of undo");
step_heading(4, STR_C("Lines"));
if (ImGui::Button(STR_C("Set the line signals"), ImVec2(-1.0f, 0.0f)))
set_lines();
item_tooltip("Places the signals of the lines out of the station, from its entry signals on. A double-track line gets the automatic "
"block: block signals evenly to the entry signals of the next station, about 1.4 km apart, numbered after the km, 241 on "
"track 1, 242 on track 2, and the block sections between them named as on the lines of PKP PLK, 2411-2571@LK2.1. A "
"single-track line gets a warning disc ToA 1000 m before the entry signal, 1300 m on a line faster than 120 km/h. Needs "
"the line and the km of the station. Run it at the next station too, it keeps what stands. One step of undo");
if (auto const *selected{m_selection.selected_track()}; selected != nullptr && false == selected->Isolated.empty())
if (auto const number{track_of(selected->Isolated.front()->asName, stem_of_open())}; false == number.empty() && begin_fields("##srktrack"))
{
@@ -591,6 +636,11 @@ void srk_tool::number_switches()
m_status = STR_C("Give the station its code first: the switches are named after it");
return;
}
if (m_records[m_record].reversed != m_reversed)
{
m_records[m_record].reversed = m_reversed;
m_changed = true;
}
auto const units{station::numbering(*m_layout, m_reversed)};
std::vector<std::pair<TTrack *, std::string>> names;
std::unordered_set<TTrack const *> own;
@@ -776,6 +826,57 @@ std::string srk_tool::stem_of_open() const
return m_record >= 0 && false == m_records[m_record].code.empty() ? station::prefix(m_records[m_record].code) : std::string{};
}
bool srk_tool::km_of_open(double const Chainage, double &Km) const
{
if (false == m_layout.has_value())
return false;
if (m_record >= 0 && false == m_records[m_record].km.empty())
{
char *end{nullptr};
auto const &text{m_records[m_record].km};
auto const middle{std::strtod(text.c_str(), &end)};
if (end != text.c_str())
{
Km = middle + (Chainage - (m_layout->from + m_layout->to) * 0.5) / 1000.0 * (m_reversed ? -1.0 : 1.0);
return true;
}
}
if (false == m_layout->kilometrage)
return false;
Km = station::km_of(*m_layout, Chainage);
return true;
}
bool srk_tool::station_tracks(std::vector<station::numbered> &Tracks, std::unordered_set<TTrack const *> &Inside) const
{
Tracks.clear();
Inside.clear();
auto const stem{stem_of_open()};
if (false == m_layout.has_value() || stem.empty())
return false;
std::map<std::string, std::map<std::string, std::vector<TTrack *>>> numbers;
for (auto const &path : m_layout->paths)
{
if (path.track->Isolated.empty() || Inside.count(path.track) > 0)
continue;
auto const &name{path.track->Isolated.front()->asName};
if (name.size() <= stem.size() || name.compare(0, stem.size(), stem) != 0 || entry_circuit(name, stem))
continue;
Inside.insert(path.track);
if (auto const number{track_of(name, stem)}; false == number.empty())
numbers[number][name].push_back(path.track);
}
for (auto &[number, circuits] : numbers)
{
station::numbered entry;
entry.number = number;
for (auto &[name, members] : circuits)
entry.circuits.push_back(std::move(members));
Tracks.push_back(std::move(entry));
}
return false == Tracks.empty();
}
void srk_tool::renumber_track(TTrack const &Track, std::string const &Number)
{
m_status.clear();
@@ -823,34 +924,13 @@ void srk_tool::set_signals()
return;
}
auto const &layout{*m_layout};
// the station tracks by their numbers, the circuits as the tracks have them
std::map<std::string, std::map<std::string, std::vector<TTrack *>>> numbers;
std::vector<station::numbered> tracks;
std::unordered_set<TTrack const *> inside;
for (auto const &path : layout.paths)
{
if (path.track->Isolated.empty() || inside.count(path.track) > 0)
continue;
auto const &name{path.track->Isolated.front()->asName};
if (name.size() <= stem.size() || name.compare(0, stem.size(), stem) != 0 || entry_circuit(name, stem))
continue;
inside.insert(path.track);
if (auto const number{track_of(name, stem)}; false == number.empty())
numbers[number][name].push_back(path.track);
}
if (numbers.empty())
if (false == station_tracks(tracks, inside))
{
m_status = STR_C("Set the track circuits first: the signals stand at their joints");
return;
}
std::vector<station::numbered> tracks;
for (auto &[number, circuits] : numbers)
{
station::numbered entry;
entry.number = number;
for (auto &[name, members] : circuits)
entry.circuits.push_back(std::move(members));
tracks.push_back(std::move(entry));
}
auto plan{station::signals(layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })};
std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true);
@@ -1013,13 +1093,460 @@ void srk_tool::set_signals()
return;
}
std::string error;
auto const count{m_signals.signal_batch(placed, renamed, std::move(before), std::move(created), error)};
auto const count{m_signals.signal_batch({placed, renamed, {}, {}}, std::move(before), std::move(created), error)};
if (false == error.empty())
m_status = format(STR_C("The signals couldn't be placed: %s"), error.c_str());
else
m_status = format(STR_P("%zu signal placed", "%zu signals placed", count), count) + ", " + format(STR_P("%zu renamed", "%zu renamed", renamed.size()), renamed.size());
}
void srk_tool::set_lines()
{
m_status.clear();
auto const stem{stem_of_open()};
if (stem.empty())
{
m_status = STR_C("Give the station its code first: the signals are named after it");
return;
}
auto const &layout{*m_layout};
std::vector<station::numbered> tracks;
std::unordered_set<TTrack const *> inside;
if (false == station_tracks(tracks, inside))
{
m_status = STR_C("Set the track circuits first: the signals stand at their joints");
return;
}
auto const plan{station::signals(layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })};
std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true);
// the lines begin at the entry signals standing
std::vector<station::signal> entries;
std::vector<std::size_t> entry_standing;
for (auto const &planned : plan)
{
if (planned.kind != station::signal::role::entry)
continue;
auto const found{std::find_if(standing.begin(), standing.end(), [&](standing_signal const &Signal) { return Signal.kind == "main" && ToLower(Signal.name) == stem + ToLower(planned.name); })};
if (found == standing.end())
continue;
entries.push_back(planned);
entry_standing.push_back(static_cast<std::size_t>(found - standing.begin()));
}
if (entries.empty())
{
m_status = STR_C("Set the signals first: the lines begin at the entry signals");
return;
}
auto const lines{station::lines_out(layout, entries, kLineLimit)};
if (lines.empty())
{
m_status = STR_C("No line leads out of the station beyond its entry signals");
return;
}
auto const &described{m_records[m_record]};
double known{0.0};
if (std::any_of(lines.begin(), lines.end(), [](station::line const &Line) { return Line.number > 0; }) && (false == km_of_open(0.0, known) || described.line.empty()))
{
m_status = STR_C("Give the station its line and its km first: the block signals and the block sections are named after them");
return;
}
auto const middle{(layout.from + layout.to) * 0.5};
struct planned_signal
{
station::signal::role kind{station::signal::role::block};
std::string name;
std::string plate;
std::size_t line{0};
double distance{0.0}; // out along the line
std::string linked{"none"};
int match{-1}; // of the standing signals
};
struct far_end
{
int signal{-1}; // the entry signal of the next station standing, of the standing signals
double distance{0.0}; // out to it, or to where the signals of the line end
};
std::vector<planned_signal> planned;
std::vector<far_end> ends(lines.size());
auto const facing_out = [&](station::line const &Line, standing_signal const &Signal, double const Distance) {
auto const way{station::point_on(Line, Distance).way};
return glm::dot(travel_of(Signal.yaw), glm::dvec2{way.x, way.z});
};
auto const km_on = [&](std::size_t const Index, double const Distance) {
auto const chainage{station::project(layout, lines[Index].points.front()).x};
double km{0.0};
km_of_open(chainage < middle ? chainage - Distance : chainage + Distance, km);
return km;
};
for (std::size_t index = 0; index < lines.size(); ++index)
{
auto const &line{lines[index]};
auto &end{ends[index]};
// the entry signal of the next station: the first main signal along the line facing out
for (std::size_t k = 0; k < standing.size(); ++k)
{
if (standing[k].kind != "main")
continue;
double gap{0.0};
auto const distance{station::distance_on(line, standing[k].position, gap)};
if (gap > 4.5 || distance < 100.0 || distance > line.length() - 1.0 || facing_out(line, standing[k], distance) < 0.87)
continue;
if (end.signal < 0 || distance < end.distance)
{
end.signal = static_cast<int>(k);
end.distance = distance;
}
}
if (end.signal < 0)
end.distance = line.station ? line.length() - kPredicted : line.length();
}
for (std::size_t index = 0; index < lines.size(); ++index)
{
auto const &line{lines[index]};
auto const &end{ends[index]};
auto const &entry{entries[line.entry]};
if (line.number > 0)
{
// the block signals: the line to the next station parted evenly, towards the end of the tracks every 1.4 km
std::vector<double> places;
if (line.station || end.signal >= 0)
{
// as many on both tracks of the line
auto const reach{line.partner >= 0 ? (end.distance + ends[line.partner].distance) * 0.5 : end.distance};
auto const sections{std::max(1L, std::lround(reach / kBlock))};
for (long k = 1; k < sections; ++k)
places.push_back(end.distance * static_cast<double>(k) / static_cast<double>(sections));
}
else
for (auto distance{kBlock}; distance <= end.distance - 100.0; distance += kBlock)
places.push_back(distance);
for (auto const distance : places)
{
planned_signal signal;
signal.kind = station::signal::role::block;
signal.name = station::block_number(km_on(index, distance), line.number);
signal.plate = signal.name;
signal.line = index;
signal.distance = distance;
planned.push_back(std::move(signal));
}
}
else
{
// the warning disc of the entry signal, the braking distance before it
auto const fast{entry.track != nullptr && entry.track->VelocityGet() > 120.0};
auto const distance{std::min(fast ? kWarningFast : kWarning, end.distance - 100.0)};
if (distance >= 400.0)
{
planned_signal signal;
signal.kind = station::signal::role::warning;
signal.name = stem + "to" + ToLower(entry.name);
signal.plate = "to" + ToLower(entry.name);
signal.line = index;
signal.distance = distance;
planned.push_back(std::move(signal));
}
}
}
// the standing ones by their places, then by their names
std::vector<char> used(standing.size(), 0);
for (auto const index : entry_standing)
used[index] = 1;
for (auto const &end : ends)
if (end.signal >= 0)
used[end.signal] = 1;
std::vector<std::tuple<double, std::size_t, std::size_t>> candidates;
std::vector<double> standing_at(standing.size(), 0.0);
for (std::size_t i = 0; i < planned.size(); ++i)
{
auto const &signal{planned[i]};
auto const &line{lines[signal.line]};
auto const kind{signal.kind == station::signal::role::block ? "block" : "warning"};
auto const reach{signal.kind == station::signal::role::block ? 300.0 : 400.0};
for (std::size_t k = 0; k < standing.size(); ++k)
{
if (standing[k].kind != kind)
continue;
double gap{0.0};
auto const distance{station::distance_on(line, standing[k].position, gap)};
auto const facing{facing_out(line, standing[k], distance)};
if (gap < 4.5 && std::abs(distance - signal.distance) < reach && (line.inbound ? -facing : facing) > 0.87)
candidates.emplace_back(std::abs(distance - signal.distance) + gap * 10.0, i, k);
}
}
std::sort(candidates.begin(), candidates.end());
for (auto const &[score, i, k] : candidates)
if (planned[i].match < 0 && 0 == used[k])
{
planned[i].match = static_cast<int>(k);
used[k] = 1;
double gap{0.0};
planned[i].distance = station::distance_on(lines[planned[i].line], standing[k].position, gap);
}
for (auto &signal : planned)
for (std::size_t k = 0; k < standing.size() && signal.match < 0; ++k)
if (0 == used[k] && ToLower(standing[k].name) == ToLower(signal.name))
{
signal.match = static_cast<int>(k);
used[k] = 1;
}
// a number another signal of the scenery has already gets a letter
std::unordered_set<std::string> taken;
for (std::size_t k = 0; k < standing.size(); ++k)
if (0 == used[k])
taken.insert(ToLower(standing[k].name));
for (auto &signal : planned)
{
if (signal.kind != station::signal::role::block)
continue;
auto const base{signal.name};
for (char letter = 'a'; taken.count(ToLower(signal.name)) > 0 && letter <= 'z'; ++letter)
signal.name = base + letter;
taken.insert(ToLower(signal.name));
signal.plate = signal.name;
}
// the block sections of each track of a double-track line, between the signals in the order the trains pass them
struct section
{
std::string name;
std::size_t line{0};
double from{0.0};
double to{0.0};
std::string guard; // the block signal at its start, empty for a station one
glm::dvec3 start{0.0};
};
std::vector<section> sections;
std::vector<std::pair<std::size_t, std::string>> relinked; // standing station signals, the block signal before each
for (std::size_t index = 0; index < lines.size(); ++index)
{
auto const &line{lines[index]};
if (line.number == 0)
continue;
struct stop
{
double distance{0.0};
std::string name;
int signal{-1}; // of the planned ones, -1 for a station signal
int standing{-1};
};
std::vector<stop> order{{0.0, standing[entry_standing[line.entry]].name, -1, static_cast<int>(entry_standing[line.entry])}};
for (std::size_t i = 0; i < planned.size(); ++i)
if (planned[i].line == index)
order.push_back({planned[i].distance, planned[i].name, static_cast<int>(i), planned[i].match});
if (ends[index].signal >= 0)
order.push_back({ends[index].distance, standing[ends[index].signal].name, -1, ends[index].signal});
std::sort(order.begin(), order.end(), [](stop const &A, stop const &B) { return A.distance < B.distance; });
if (line.inbound)
std::reverse(order.begin(), order.end());
for (std::size_t i = 0; i < order.size(); ++i)
{
auto const &here{order[i]};
auto const block_before{i > 0 && order[i - 1].signal >= 0};
auto const linked{block_before ? order[i - 1].name : std::string{"none"}};
if (here.signal >= 0)
planned[here.signal].linked = linked;
else if (block_before && here.standing >= 0 && false == standing[here.standing].linked.empty() && ToLower(standing[here.standing].linked) != ToLower(linked))
relinked.emplace_back(here.standing, linked);
if (i == 0)
continue;
auto const &before{order[i - 1]};
// the lower km first, as on the lines of PKP PLK: 2411-2571@LK2.1
auto const ascending{km_on(index, before.distance) <= km_on(index, here.distance)};
auto const &low{ascending ? before : here};
auto const &high{ascending ? here : before};
section found;
found.name = ToLower(section_tag(low.name, line.number) + "-" + section_tag(high.name, line.number) + "@lk" + described.line + "." + std::to_string(line.number));
found.line = index;
found.from = std::min(before.distance, here.distance);
found.to = std::max(before.distance, here.distance);
found.guard = before.signal >= 0 ? before.name : std::string{};
found.start = station::point_on(line, before.distance).point;
sections.push_back(std::move(found));
}
}
// the tracks cut where the sections meet, all of it checked before any is
struct cut
{
TTrack *track{nullptr};
glm::dvec3 point{0.0};
};
std::vector<cut> cuts;
auto const add_cut = [&](station::line const &Line, double const Distance) -> bool {
auto const at{station::point_on(Line, Distance)};
if (at.track == nullptr || std::any_of(cuts.begin(), cuts.end(), [&](cut const &Cut) { return glm::distance(Cut.point, at.point) < 0.5; }))
return true;
auto const &ends_of{at.track->m_paths.front().points};
if (std::min(glm::distance(at.point, ends_of[segment_data::point::start]), glm::distance(at.point, ends_of[segment_data::point::end])) <= 0.3)
return true;
std::string reason;
if (false == editor_track::can_edit_geometry(*at.track, reason))
{
m_status = format(STR_C("%s can't be cut: %s"), name_of(*at.track).c_str(), reason.c_str());
return false;
}
cuts.push_back({at.track, at.point});
return true;
};
for (auto const &found : sections)
if (false == add_cut(lines[found.line], found.from) || false == add_cut(lines[found.line], found.to))
return;
for (auto const &line : lines)
for (auto *part : line.tracks)
if (std::string reason; false == scene::Layers.editable(part, &reason) && std::any_of(sections.begin(), sections.end(), [&](section const &Found) { return &lines[Found.line] == &line; }))
{
m_status = format(STR_C("%s can't be changed: %s"), name_of(*part).c_str(), reason.c_str());
return;
}
std::vector<std::pair<TTrack *, editor_track::state>> before;
std::vector<TTrack *> created;
std::unordered_set<TTrack const *> captured;
std::unordered_map<TTrack const *, std::vector<TTrack *>> pieces; // of each track cut, itself and the pieces cut off it
auto const piece_at = [&](TTrack *Track, glm::dvec3 const &Point) {
auto found{pieces.find(Track)};
if (found == pieces.end())
return Track;
TTrack *best{Track};
auto nearest{std::numeric_limits<double>::max()};
for (auto *piece : found->second)
{
auto const point{editor_track::point_at(*piece, editor_track::nearest_parameter(*piece, Point))};
if (auto const distance{glm::distance(point, Point)}; distance < nearest)
{
nearest = distance;
best = piece;
}
}
return best;
};
for (auto const &entry : cuts)
{
auto *track{piece_at(entry.track, entry.point)};
auto const parameter{editor_track::nearest_parameter(*track, entry.point)};
auto const &ends_of{track->m_paths.front().points};
if (std::min(glm::distance(entry.point, ends_of[segment_data::point::start]), glm::distance(entry.point, ends_of[segment_data::point::end])) <= 0.3)
continue;
if (captured.insert(track).second)
before.emplace_back(track, editor_track::capture(*track));
if (auto *piece{editor_track::split_path(*track, parameter)})
{
created.push_back(piece);
captured.insert(piece);
auto &list{pieces[entry.track]};
if (list.empty())
list.push_back(entry.track);
list.push_back(piece);
}
}
// the sections named, each track by its middle
for (std::size_t index = 0; index < lines.size(); ++index)
{
std::vector<TTrack *> parts;
for (auto *track : lines[index].tracks)
{
auto const found{pieces.find(track)};
if (found == pieces.end())
parts.push_back(track);
else
parts.insert(parts.end(), found->second.begin(), found->second.end());
}
for (auto *part : parts)
{
double gap{0.0};
auto const distance{station::distance_on(lines[index], editor_track::point_at(*part, 0.5), gap)};
if (gap > 1.0)
continue;
auto const found{std::find_if(sections.begin(), sections.end(), [&](section const &Section) { return Section.line == index && distance > Section.from && distance < Section.to; })};
if (found == sections.end() || (part->Isolated.size() == 1 && ToLower(part->Isolated.front()->asName) == found->name))
continue;
if (captured.insert(part).second)
before.emplace_back(part, editor_track::capture(*part));
editor_track::isolated(*part, {found->name});
}
}
// the signals placed on the track the trains run on up to them
signal_changes changes;
std::size_t blocks{0};
std::size_t discs{0};
for (auto const &signal : planned)
{
auto const block{signal.kind == station::signal::role::block};
if (signal.match >= 0)
{
auto const &found{standing[signal.match]};
auto const relink{block && false == found.linked.empty() && ToLower(found.linked) != ToLower(signal.linked)};
if (ToLower(found.name) != ToLower(signal.name) || relink)
changes.renamed.push_back({found.instance, signal.name, signal.plate, relink ? signal.linked : std::string{}});
continue;
}
auto const &line{lines[signal.line]};
auto const inbound{line.inbound};
auto const at{station::point_on(line, signal.distance)};
auto const approach{station::point_on(line, signal.distance + (inbound ? 1.0 : -1.0))};
signal_order order;
order.track = piece_at(approach.track, approach.point);
order.point = at.point;
order.facing = inbound == approach.outward ? -1 : 1;
order.side = order.facing;
order.kind = block ? "block" : "warning";
order.name = signal.name;
order.plate = signal.plate;
order.linked = signal.linked;
changes.placed.push_back(std::move(order));
++(block ? blocks : discs);
}
for (auto const &[index, linked] : relinked)
changes.renamed.push_back({standing[index].instance, standing[index].name, std::string{}, linked});
// the block signals switched by the sections they guard
include_info control;
std::string missing;
auto const controlled{editor_includes::load(kControl, control, missing)};
if (controlled)
{
auto const standing_controls{m_templates.standing_templates([](std::string const &File) { return File == kControl; })};
for (auto const &found : sections)
{
if (found.guard.empty())
continue;
auto const already{std::find_if(standing_controls.begin(), standing_controls.end(), [&](standing_template const &Control) { return false == Control.values.empty() && ToLower(Control.values[0]) == found.name; })};
if (already != standing_controls.end())
{
if (already->values.size() > 5 && ToLower(already->values[5]) != ToLower(found.guard))
{
auto values{already->values};
values[5] = found.guard;
changes.values.emplace_back(already->instance, std::move(values));
}
continue;
}
template_item item;
item.file = kControl;
item.location = found.start;
item.described = true;
item.values["name"] = found.name;
item.values["#6"] = found.guard;
changes.others.push_back(std::move(item));
}
}
if (changes.placed.empty() && changes.renamed.empty() && changes.others.empty() && changes.values.empty() && before.empty() && created.empty())
{
m_status = STR_C("The lines are set already");
return;
}
std::string error;
auto const renamed{changes.renamed.size()};
m_signals.signal_batch(changes, std::move(before), std::move(created), error);
if (false == error.empty())
{
m_status = format(STR_C("The signals couldn't be placed: %s"), error.c_str());
return;
}
m_status = format(STR_P("%zu block signal placed", "%zu block signals placed", blocks), blocks) + ", " + format(STR_P("%zu warning disc", "%zu warning discs", discs), discs) + ", " +
format(STR_P("%zu renamed", "%zu renamed", renamed), renamed) + ", " + format(STR_P("%zu block section", "%zu block sections", sections.size()), sections.size());
if (false == controlled && std::any_of(sections.begin(), sections.end(), [](section const &Found) { return false == Found.guard.empty(); }))
m_status += "\n" + format(STR_C("%s isn't in the scenery folder: the block signals won't change their lights"), kControl);
}
void srk_tool::survey_signals()
{
m_signal_marks.clear();
@@ -1497,6 +2024,51 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
WriteLog(format("SELFTEST srksignals %zu:", m_signal_marks.size()) + list);
return editor_selftest_result::handled;
}
if (Command == "srklines")
{
// srklines: the signals of the lines out of the open station set
if (false == m_layout.has_value())
{
WriteLog("SELFTEST srklines: no station open");
return editor_selftest_result::handled;
}
set_lines();
WriteLog("SELFTEST srklines: " + m_status);
refresh();
std::vector<station::numbered> tracks;
std::unordered_set<TTrack const *> inside;
if (false == station_tracks(tracks, inside))
return editor_selftest_result::handled;
auto const plan{station::signals(*m_layout, tracks, [&](TTrack const &Track) { return inside.count(&Track) > 0; })};
std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true);
for (auto const &line : station::lines_out(*m_layout, plan, kLineLimit))
{
std::vector<std::pair<double, std::string>> along;
for (auto const &signal : standing)
{
double gap{0.0};
auto const distance{station::distance_on(line, signal.position, gap)};
if (gap < 4.5 && distance < line.length())
along.emplace_back(distance, format("%s(%s,%s)", signal.name.c_str(), signal.kind.c_str(), signal.linked.c_str()));
}
std::string last;
for (std::size_t i = 1; i < line.points.size(); i += 20)
{
auto const *track{line.tracks[line.owners[i]]};
auto const name{track->Isolated.empty() ? std::string{"-"} : track->Isolated.front()->asName};
if (name != last)
along.emplace_back(line.distances[i], "[" + name + "]");
last = name;
}
std::sort(along.begin(), along.end());
std::string list;
for (auto const &[distance, text] : along)
list += format(" %.0f:%s", distance, text.c_str());
WriteLog(format("SELFTEST srklines %s track %d %s %.0f m%s:", plan[line.entry].name.c_str(), line.number, line.inbound ? "in" : "out", line.length(), line.station ? " to a station" : "") + list);
}
return editor_selftest_result::handled;
}
if (Command == "srkstrip")
{
// srkstrip shown=0: the schematic hidden, the 3d view whole for a screenshot
@@ -1533,6 +2105,8 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
entry.name = name->second;
std::replace(entry.name.begin(), entry.name.end(), '_', ' ');
}
if (auto const km{values.find("km")}; km != values.end())
entry.km = km->second;
m_changed = true;
WriteLog("SELFTEST srkset: " + title());
return editor_selftest_result::handled;

View File

@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -24,7 +25,10 @@ http://mozilla.org/MPL/2.0/.
class srk_tool final : public editor_tool
{
public:
srk_tool(editor_selection &Selection, editor_view &View, editor_history &History, editor_signals &Signals) : m_selection{Selection}, m_view{View}, m_history{History}, m_signals{Signals} {}
srk_tool(editor_selection &Selection, editor_view &View, editor_history &History, editor_templates &Templates, editor_signals &Signals)
: m_selection{Selection}, m_view{View}, m_history{History}, m_templates{Templates}, m_signals{Signals}
{
}
void update() override;
void draw_overlay() const override;
void render_options() override;
@@ -39,6 +43,7 @@ class srk_tool final : public editor_tool
editor_selection &m_selection;
editor_view &m_view;
editor_history &m_history;
editor_templates &m_templates;
editor_signals &m_signals;
// a station described: found again by a switch of it
struct record
@@ -49,6 +54,8 @@ class srk_tool final : public editor_tool
std::string code; // the abbreviation of the station, as MMz
std::string name;
std::string line; // the number of the railway line
std::string km; // of the axis of the station along the line, empty while unknown
bool reversed{false}; // the km rise to the left of the schematic, the switches numbered from there
};
// the schematic: the chainage across, the offsets upwards, exaggerated
struct view
@@ -122,8 +129,16 @@ class srk_tool final : public editor_tool
void set_signals();
// the signals standing by the station, for the schematic
void survey_signals();
// the signals of the lines out of the station: on a double-track line the block signals to the next station and the block
// sections between them, on a single-track one a warning disc before the entry signal; one step of the history
void set_lines();
// the names of the switches and the circuits of the open station begin with it, empty while it has no code
std::string stem_of_open() const;
// the km at the chainage of the open station: of its record, else of its switches; false while unknown
bool km_of_open(double const Chainage, double &Km) const;
// the station tracks by their numbers with their circuits as the tracks have them, Inside: the tracks of the circuits; false
// while it has none
bool station_tracks(std::vector<station::numbered> &Tracks, std::unordered_set<TTrack const *> &Inside) const;
// the circuits of the station track of the track renamed after another number; one step of the history
void renumber_track(TTrack const &Track, std::string const &Number);
};

View File

@@ -84,12 +84,14 @@ struct signal_order
std::string kind;
std::string name;
std::string plate; // the name on the plate under the head, when the template has one
std::string linked{"none"}; // the signal before it, when the template takes one: a block signal shows its aspects by it
};
struct signal_rename
{
scene::instance_handle instance{0};
std::string name;
std::string plate;
std::string linked; // the same, empty to keep the one it has
};
struct standing_signal
{
@@ -98,6 +100,16 @@ struct standing_signal
std::string kind;
glm::dvec3 position{0.0};
double yaw{0.0};
std::string linked; // of a template that takes one, else empty
};
// one step of the history of the signalling: signals placed, standing ones renamed, other templates placed with them, the values
// of standing ones replaced
struct signal_changes
{
std::vector<signal_order> placed;
std::vector<signal_rename> renamed;
std::vector<template_item> others;
std::vector<std::pair<scene::instance_handle, std::vector<std::string>>> values;
};
struct nmt_place
@@ -177,9 +189,9 @@ class editor_signals
public:
// false while the signal templates are still being looked for
virtual bool standing_signals(std::vector<standing_signal> &Signals, bool const Wait) = 0;
// the signals placed by the templates chosen for their kinds and the standing ones renamed, with the tracks changed before:
// one step of the history. returns: how many were placed
virtual std::size_t signal_batch(std::vector<signal_order> const &Placed, std::vector<signal_rename> const &Renamed, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error) = 0;
// the signals placed by the templates chosen for their kinds and the rest of the changes, with the tracks changed before: one
// step of the history. returns: how many signals were placed
virtual std::size_t signal_batch(signal_changes const &Changes, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error) = 0;
protected:
~editor_signals() = default;

View File

@@ -1254,4 +1254,168 @@ std::vector<signal> signals(layout const &Layout, std::vector<numbered> const &T
return result;
}
std::vector<line> lines_out(layout const &Layout, std::vector<signal> const &Signals, double const Limit)
{
std::vector<line> result;
for (std::size_t index = 0; index < Signals.size(); ++index)
{
auto const &entry{Signals[index]};
if (entry.kind != signal::role::entry || entry.track == nullptr || node(*entry.track))
continue;
line found;
found.entry = index;
// out where the trains it faces come from
auto *track{entry.track};
auto outward{entry.facing < 0};
auto first{true};
std::unordered_set<TTrack const *> seen;
while (true)
{
double length{0.0};
auto points{dense_points(*track, 0, length)};
if (false == outward)
std::reverse(points.begin(), points.end());
if (first)
{
auto const nearest{std::min_element(points.begin(), points.end(), [&](glm::dvec3 const &A, glm::dvec3 const &B) { return plan_distance(A, entry.point) < plan_distance(B, entry.point); })};
points.erase(points.begin(), nearest);
points.front() = entry.point;
}
else
points.erase(points.begin());
seen.insert(track);
found.tracks.push_back(track);
found.outward.push_back(outward);
for (auto const &point : points)
{
found.distances.push_back(found.points.empty() ? 0.0 : found.distances.back() + plan_distance(found.points.back(), point));
found.points.push_back(point);
found.owners.push_back(found.tracks.size() - 1);
}
first = false;
if (found.length() > Limit)
break;
auto const end{end_of(*track, 0, outward)};
auto *next{neighbour(*track, 0, outward)};
if (next == nullptr || false == rail(*next) || seen.count(next) > 0)
break;
if (node(*next))
{
found.station = true;
break;
}
int path{0};
bool atend{false};
if (false == end_at(*next, end, path, atend))
break;
track = next;
outward = false == atend;
}
if (found.length() > 1.0)
result.push_back(std::move(found));
}
// a double-track line: two lines out of the same end 3 to 6.5 m apart all along their first kilometre
auto const middle{(Layout.from + Layout.to) * 0.5};
auto const start = [&](line const &Line) { return project(Layout, Line.points.front()).x < middle; };
auto const gap_between = [&](line const &A, line const &B) {
auto const reach{std::min({A.length(), B.length(), 1000.0})};
auto worst{0.0};
for (auto const fraction : {0.2, 0.6, 1.0})
{
double gap{0.0};
distance_on(B, point_on(A, reach * fraction).point, gap);
worst = std::max(worst, std::abs(gap - 4.5));
}
return reach < 100.0 ? std::numeric_limits<double>::max() : worst;
};
// the pairs running side by side the longest first: a siding along a main track for a while is no line
std::vector<std::tuple<double, double, std::size_t, std::size_t>> pairs;
for (std::size_t i = 0; i < result.size(); ++i)
for (std::size_t j = i + 1; j < result.size(); ++j)
if (start(result[i]) == start(result[j]))
if (auto const gap{gap_between(result[i], result[j])}; gap <= 2.0)
pairs.emplace_back(-std::min(result[i].length(), result[j].length()), gap, i, j);
std::sort(pairs.begin(), pairs.end());
std::vector<char> paired(result.size(), 0);
for (auto const &[reach_order, gap, i, partner] : pairs)
{
if (paired[i] != 0 || paired[partner] != 0)
continue;
paired[i] = paired[partner] = 1;
result[i].partner = static_cast<int>(partner);
result[partner].partner = static_cast<int>(i);
// the trains towards the station on the track on their right; along the chainage the right one is track 1
auto const &travel{Signals[result[i].entry].travel};
glm::dvec2 const right{-travel.z, travel.x};
auto const reach{std::min({result[i].length(), result[partner].length(), 200.0})};
auto const here{point_on(result[i], reach).point};
auto const there{point_on(result[partner], reach).point};
auto const partner_inbound{glm::dot(glm::dvec2{there.x - here.x, there.z - here.z}, right) > 0.0};
result[i].inbound = false == partner_inbound;
result[partner].inbound = partner_inbound;
for (auto *found : {&result[i], &result[partner]})
found->number = found->inbound == start(*found) ? 1 : 2;
}
return result;
}
on_line point_on(line const &Line, double const Distance)
{
on_line result;
if (Line.points.empty())
return result;
auto const after{std::lower_bound(Line.distances.begin(), Line.distances.end(), Distance)};
auto const i{std::clamp<std::size_t>(static_cast<std::size_t>(after - Line.distances.begin()), 1, std::max<std::size_t>(1, Line.points.size() - 1))};
if (Line.points.size() < 2)
{
result.track = Line.tracks[Line.owners.front()];
result.outward = Line.outward[Line.owners.front()];
result.point = Line.points.front();
return result;
}
auto const step{Line.distances[i] - Line.distances[i - 1]};
auto const t{step > 1e-9 ? std::clamp((Distance - Line.distances[i - 1]) / step, 0.0, 1.0) : 0.0};
result.track = Line.tracks[Line.owners[i]];
result.outward = Line.outward[Line.owners[i]];
result.point = glm::mix(Line.points[i - 1], Line.points[i], t);
auto const way{Line.points[i] - Line.points[i - 1]};
if (glm::length(way) > 1e-9)
result.way = glm::normalize(way);
return result;
}
double distance_on(line const &Line, glm::dvec3 const &Point, double &Gap)
{
Gap = std::numeric_limits<double>::max();
auto result{0.0};
glm::dvec2 const point{Point.x, Point.z};
for (std::size_t i = 1; i < Line.points.size(); ++i)
{
glm::dvec2 const a{Line.points[i - 1].x, Line.points[i - 1].z};
glm::dvec2 const b{Line.points[i].x, Line.points[i].z};
auto const along{b - a};
auto const length{glm::length(along)};
if (length < 1e-9)
continue;
auto const t{std::clamp(glm::dot(point - a, along) / (length * length), 0.0, 1.0)};
if (auto const gap{glm::distance(point, a + along * t)}; gap < Gap)
{
Gap = gap;
result = Line.distances[i - 1] + length * t;
}
}
return result;
}
std::string block_number(double const Km, int const Track)
{
auto const hectometres{std::max(Km, 0.0) * 10.0};
auto number{static_cast<long long>(std::llround(hectometres))};
if ((number % 2 == 1) != (Track % 2 == 1))
number += hectometres > static_cast<double>(number) ? 1 : -1;
if (number < 1)
number = Track % 2 == 1 ? 1 : 2;
return std::to_string(number);
}
} // namespace station

View File

@@ -122,7 +122,9 @@ struct signal
{
entry,
exit,
shunting
shunting,
block,
warning
};
role kind{role::entry};
std::string name; // without the code of the station: A, F1, Tm3
@@ -145,4 +147,38 @@ struct signal
// station
std::vector<signal> signals(layout const &Layout, std::vector<numbered> const &Tracks, std::function<bool(TTrack const &)> const &Inside);
// a track of a line out beyond an entry signal of the station, to the next station or the end of the tracks
struct line
{
std::size_t entry{0}; // the entry signal standing on it, of the signals given
int number{0}; // of the track of a double-track line: 1 the one on the right along the chainage, 2 the other; 0 on a single-track line
bool inbound{true}; // its trains run towards the station: on a double-track line the track on their right
bool station{false}; // ends at a switch, of the next station, rather than at the end of the tracks
int partner{-1}; // the other track of a double-track line, of the lines
std::vector<TTrack *> tracks; // from the entry signal out
std::vector<bool> outward; // of each track: the line runs out towards the end of its path
std::vector<glm::dvec3> points; // along the tracks from the entry signal on, a metre or so apart
std::vector<double> distances; // of the points, from the entry signal
std::vector<std::size_t> owners; // of the points, the track each lies on
double length() const { return distances.empty() ? 0.0 : distances.back(); }
};
// where the line runs at the distance out: on what track, the point on its axis, the way out along the line
struct on_line
{
TTrack *track{nullptr};
bool outward{true}; // the way out runs towards the end of the path of the track
glm::dvec3 point{0.0};
glm::dvec3 way{0.0, 0.0, 1.0};
};
// the lines beyond the entry signals among the signals given, no longer than the limit; two lines out of the same end side by side
// are the tracks of a double-track line
std::vector<line> lines_out(layout const &Layout, std::vector<signal> const &Signals, double const Limit);
on_line point_on(line const &Line, double const Distance);
// how far out along the line the point lies, Gap: how far from its axis in the plan
double distance_on(line const &Line, glm::dvec3 const &Point, double &Gap);
// the number of a block signal at the km, as PKP PLK gives them: the km and the hectometre, the last digit odd on track 1, even on
// track 2: 241, 242
std::string block_number(double const Km, int const Track);
} // namespace station

View File

@@ -10488,3 +10488,51 @@ msgstr[0] "%zu przemianowany"
msgstr[1] "%zu przemianowane"
msgstr[2] "%zu przemianowanych"
msgid "Lines"
msgstr "Szlaki"
msgid "Set the line signals"
msgstr "Ustaw sygnalizatory szlakowe"
msgid "Places the signals of the lines out of the station, from its entry signals on. A double-track line gets the automatic block: block signals evenly to the entry signals of the next station, about 1.4 km apart, numbered after the km, 241 on track 1, 242 on track 2, and the block sections between them named as on the lines of PKP PLK, 2411-2571@LK2.1. A single-track line gets a warning disc ToA 1000 m before the entry signal, 1300 m on a line faster than 120 km/h. Needs the line and the km of the station. Run it at the next station too, it keeps what stands. One step of undo"
msgstr "Stawia sygnalizatory szlaków wychodzących ze stacji, od jej semaforów wjazdowych. Szlak dwutorowy dostaje samoczynną blokadę liniową: semafory SBL rozstawione równo do semaforów wjazdowych następnej stacji, co około 1,4 km, numerowane według kilometra, 241 na torze 1, 242 na torze 2, a odstępy między nimi nazwane jak na liniach PKP PLK, 2411-2571@LK2.1. Szlak jednotorowy dostaje tarczę ostrzegawczą ToA 1000 m przed semaforem wjazdowym, 1300 m na linii szybszej niż 120 km/h. Potrzebna linia i kilometr stacji. Uruchom to też na następnej stacji, zostawia to, co już stoi. Jeden krok cofania"
msgid "Km"
msgstr "Kilometr"
msgid "The km of the middle of the station along the line, rising the way the switches are numbered: the block signals of the lines are numbered after the km"
msgstr "Kilometr środka stacji na linii, rosnący w stronę numeracji rozjazdów: według kilometra numerowane są semafory SBL na szlakach"
msgid "Give the station its line and its km first: the block signals and the block sections are named after them"
msgstr "Najpierw podaj linię i kilometr stacji: według nich nazywają się semafory SBL i odstępy"
msgid "Set the signals first: the lines begin at the entry signals"
msgstr "Najpierw ustaw semafory: szlaki zaczynają się od semaforów wjazdowych"
msgid "No line leads out of the station beyond its entry signals"
msgstr "Za semaforami wjazdowymi nie wychodzi ze stacji żaden szlak"
msgid "The lines are set already"
msgstr "Szlaki są już ustawione"
msgid "%s isn't in the scenery folder: the block signals won't change their lights"
msgstr "Brak %s w katalogu scenery: semafory SBL nie będą zmieniać świateł"
msgid "%zu block signal placed"
msgid_plural "%zu block signals placed"
msgstr[0] "Postawiono %zu semafor SBL"
msgstr[1] "Postawiono %zu semafory SBL"
msgstr[2] "Postawiono %zu semaforów SBL"
msgid "%zu warning disc"
msgid_plural "%zu warning discs"
msgstr[0] "%zu tarczę ostrzegawczą"
msgstr[1] "%zu tarcze ostrzegawcze"
msgstr[2] "%zu tarcz ostrzegawczych"
msgid "%zu block section"
msgid_plural "%zu block sections"
msgstr[0] "%zu odstęp"
msgstr[1] "%zu odstępy"
msgstr[2] "%zu odstępów"