mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 19:19:03 +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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user