17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 06:58:35 +01:00

feat(editor): the SRK tool checks the lights of the signals against the routes of their trains: the speed of each route through the switches by the radius of their curves and the speed set on them, 40, 60 or 100 km/h, a lower orange light for these with an orange strip for 60 and a green one for 100 as Ie-1 gives them, an upper orange one where the next signal can show stop, the white one of the substitute signal; a signal telling of the next one nearer than the braking distance slows the trains with it at stop, Ie-4 § 8 ust. 5, a W21 for 140. The templates read for the speeds their events show; the signals set get the template of the kind with these lights and the least beyond them, the standing ones fitted so in one step of undo, the template swapped with the name, the plate and the read event kept, a variant leaning the other way only with the same speeds. The braking distance shown with the speed of the main tracks it follows, set by hand in the record of the station and used for the block sections too; the warning discs and the block sections nearer than it marked, the signals that don't fit marked in the schematic and the scenery

This commit is contained in:
maj00r
2026-10-22 16:07:04 +02:00
parent ad0a7e7d62
commit 92552b49cd
10 changed files with 1300 additions and 13 deletions

View File

@@ -1904,6 +1904,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
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(signal_changes const &Changes, std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created, std::string &Error) override;
bool signal_heads(std::vector<signal_head> &Heads, bool const Wait) override;
struct signal_template
{
@@ -1918,6 +1919,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri
int parameters{0};
bool plate{false};
bool linked{false};
std::vector<int> speeds;
bool informs{false};
};
struct signal_spot
{

View File

@@ -264,6 +264,7 @@ bool editor_mode::signal_scan(bool const Wait)
if (parameter.id == 7)
item.linked = true;
}
editor_includes::signal_speeds(entry.file, item.speeds, item.informs);
tool.templates.push_back(std::move(item));
}
std::sort(tool.templates.begin(), tool.templates.end(), [](signal_template const &A, signal_template const &B) {
@@ -698,7 +699,7 @@ std::string editor_mode::signal_variant(signal_template const &Template, signal_
auto best{std::numeric_limits<std::size_t>::max()};
for (auto const &other : m_signal.templates)
{
if (other.kind != Template.kind || group_of(other.mount, other.lamps) != group || lean_of(other.lean) != wanted)
if (other.kind != Template.kind || group_of(other.mount, other.lamps) != group || lean_of(other.lean) != wanted || other.speeds != Template.speeds)
continue;
auto const distance{edit_distance(other.name, Template.name)};
if (distance < best)
@@ -891,6 +892,27 @@ bool editor_mode::standing_signals(std::vector<standing_signal> &Signals, bool c
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{}});
auto &signal{Signals.back()};
signal.file = standing.include.file;
signal.track = standing.track;
signal.facing = standing.track == nullptr ? 0 : standing.list % 3 == 2 ? 1 : standing.list % 3 == 1 ? -1 : 0;
}
return true;
}
bool editor_mode::signal_heads(std::vector<signal_head> &Heads, bool const Wait)
{
Heads.clear();
signal_scan(Wait);
if (m_signal.templates.empty())
return false;
for (int index = 0; index < static_cast<int>(m_signal.templates.size()); ++index)
{
auto const &item{m_signal.templates[index]};
if (item.lamps.empty())
continue;
auto const kind{kind_index(item.kind)};
Heads.push_back({item.file, item.name, item.kind, item.mount, item.lamps, item.speeds, item.informs, kind >= 0 && m_signal.chosen[kind] == index});
}
return true;
}
@@ -906,6 +928,8 @@ std::size_t editor_mode::signal_batch(signal_changes const &Changes, std::vector
{
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};
if (auto const *wanted{order.file.empty() ? nullptr : signal_template_of(order.file)}; wanted != nullptr && wanted->kind == order.kind)
item = wanted;
if (item == nullptr)
{
Error = format(STR_C("no template of the kind %s"), STR(kKindLabels[std::max(0, kind)]).c_str());
@@ -980,6 +1004,50 @@ std::size_t editor_mode::signal_batch(signal_changes const &Changes, std::vector
set_include_directive(instance, directive);
}
}
for (auto const &[instance, wanted] : Changes.files)
{
auto const *standing{signal_standing_of(instance)};
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr};
auto const *chosen{signal_template_of(wanted)};
auto const before{scene::Layers.directive(instance)};
std::string file;
std::vector<std::string> values;
if (item == nullptr || chosen == nullptr || chosen->kind != item->kind || false == editor_includes::parse_directive(before, file, values) || values.empty())
continue;
signal_spot spot;
auto const *variant{signal_standing_spot(*standing, spot) ? signal_template_of(signal_variant(*chosen, spot)) : chosen};
if (variant == nullptr)
variant = chosen;
if (variant->file == item->file)
continue;
include_info had;
include_info info;
std::string missing;
editor_includes::load(item->file, had, missing);
editor_includes::load(variant->file, info, missing);
auto const role_of = [](include_info &Info, int const Id) { return Info.parameter(Id).role; };
std::vector<std::string> after(std::max(variant->parameters, 1));
for (int id = 1; id <= static_cast<int>(after.size()); ++id)
{
auto &value{after[id - 1]};
auto const role{role_of(info, id)};
for (int old = 1; old <= static_cast<int>(values.size()) && value.empty(); ++old)
if (role != "free" && role != "text" && role_of(had, old) == role)
value = values[old - 1];
if (id == 7 && variant->linked)
value = item->linked && values.size() > 6 ? values[6] : std::string{"none"};
if (value.empty() && id == 6 && variant->plate)
value = plate_for(std::string{}, values[0].substr(values[0].rfind('_') + 1));
if (value.empty())
value = info.parameter(id).value.empty() ? std::string{"none"} : info.parameter(id).value;
}
auto const read_before{item->read.empty() ? std::string{} : editor_includes::substitute(item->read, values)};
auto const read_after{variant->read.empty() ? std::string{} : editor_includes::substitute(variant->read, after)};
record(instance, before);
set_include_directive(instance, editor_includes::compose_directive(variant->file, after));
if (false == read_before.empty() && false == read_after.empty() && ToLower(read_before) != ToLower(read_after))
signal_renamed(read_before, read_after, step);
}
std::vector<scene::instance_handle> instances;
if (Error.empty() && false == items.empty())
{

View File

@@ -52,6 +52,7 @@ 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 kBlockShare{1.1}; // of the braking distance, the block sections towards the end of the tracks
double constexpr kPredicted{305.0}; // m, out from the next station to its entry signal, while it has none
// the events of a block section, switching the block signal guarding it
char const *const kControl{"sbl_odstep.inc"};
// m, of a train at the speed, as Ie-4 § 8 takes it: the least block section of the automatic block, a warning disc that far before
@@ -59,7 +60,7 @@ char const *const kControl{"sbl_odstep.inc"};
double braking_distance(double const Speed)
{
return Speed <= 60.0 ? 400.0 : Speed <= 100.0 ? 700.0 : Speed <= 140.0 ? 1000.0 : 1300.0;
} // 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)};
@@ -70,6 +71,9 @@ ImU32 constexpr kJoint{IM_COL32(255, 255, 255, 235)};
ImU32 constexpr kUseful{IM_COL32(120, 230, 255, 255)};
double constexpr kTrainMargin{25.0}; // m, of a useful length the train leaves: 10 to stop, 10 to see the signal, 5 to the joint, ST-T1-A6
double constexpr kUnequal{20.0}; // m, the useful lengths of the two ways differing more are shown: ST-T1-A6 wants them the same
ImU32 constexpr kUnfit{IM_COL32(255, 190, 40, 255)};
double constexpr kFastest{250.0}; // km/h, a track set faster is taken for one without a limit
ImVec4 constexpr kWrong{1.0f, 0.42f, 0.36f, 1.0f};
bool is_switch(TTrack const &Track)
{
@@ -237,6 +241,224 @@ glm::dvec3 axis_point(station::layout const &Layout, double const Chainage)
return glm::mix(Layout.axis[i - 1], Layout.axis[i], step > 1e-9 ? (Chainage - Layout.chainage[i - 1]) / step : 0.0);
}
// km/h, a signal shows for the route at the speed: 40, 60 or 100 with a light strip, Ie-1; 0 for the highest the line allows
int shown_speed(double const Speed, double const Line)
{
if (Speed <= 0.0 || Speed >= Line - 1.0)
return 0;
auto const shown{Speed >= 98.0 ? 100 : Speed >= 58.0 ? 60 : 40};
return shown >= Line ? 0 : shown;
}
// km/h, a signal slows the trains to with the next one at stop standing that far on, Ie-4 § 8 ust. 5; 140 with a W21 indicator
int shortened_speed(double const Length)
{
return Length < 400.0 ? 40 : Length < 700.0 ? 60 : Length < 1000.0 ? 100 : 140;
}
// what the lights of a signal must show for the routes of its trains
struct head_need
{
std::vector<int> speeds; // km/h, the lower ones
bool informs{false};
bool white{false};
int shortened{0};
bool indicator{false};
};
head_need need_of(std::vector<station::route> const &Routes, bool const Main, double const Line, double const Braking)
{
head_need need;
need.white = Main;
auto const add = [&](int const Speed) {
if (Speed > 0 && std::find(need.speeds.begin(), need.speeds.end(), Speed) == need.speeds.end())
need.speeds.push_back(Speed);
};
for (auto const &route : Routes)
{
auto const shown{Main ? shown_speed(route.speed, Line) : 0};
add(shown);
auto const told{route.next >= 0 && route.warning < 0};
need.informs = need.informs || told;
if (false == Main || false == told || route.length >= Braking)
continue;
auto const slower{shortened_speed(route.length)};
if (slower >= (shown > 0 ? shown : Line))
continue;
if (slower > 100)
need.indicator = true;
else
{
add(slower);
need.shortened = need.shortened == 0 ? slower : std::min(need.shortened, slower);
}
}
std::sort(need.speeds.begin(), need.speeds.end());
return need;
}
bool has_speed(std::vector<int> const &Speeds, int const Speed)
{
return std::find(Speeds.begin(), Speeds.end(), Speed) != Speeds.end();
}
// the template has the lights the need takes: a lower orange one for a lower speed, a strip for 60 and 100, Ie-1 § 7, two orange
// ones to show a lower speed with the next signal at stop
bool shows(signal_head const &Head, head_need const &Need)
{
auto const &lamps{Head.lamps};
auto const count = [&](char const Lamp) { return std::count(lamps.begin(), lamps.end(), Lamp); };
if (count('z') == 0 || count('c') == 0 || (Need.white && count('b') == 0))
return false;
if (Need.informs && (false == Head.informs || count('p') == 0))
return false;
if (Need.speeds.empty())
return true;
if (lamps.find('p', lamps.find('c')) == std::string::npos || (Need.informs && count('p') < 2))
return false;
return std::all_of(Need.speeds.begin(), Need.speeds.end(), [&](int const Speed) { return has_speed(Head.speeds, Speed); });
}
// the more lights and strips beyond the need, the more
int surplus(signal_head const &Head, head_need const &Need)
{
auto result{static_cast<int>(Head.lamps.size()) * 4};
for (auto const speed : {60, 100})
if (has_speed(Head.speeds, speed) && false == has_speed(Need.speeds, speed))
++result;
return result;
}
// of the templates of the kind and the mount that fit the need, the one with the least beyond it, the most like the one named;
// -1 for none
int best_head(std::vector<signal_head> const &Heads, std::string const &Kind, std::string const &Mount, std::string const &Like, head_need const &Need)
{
auto best{-1};
std::tuple<int, int, std::size_t> score;
for (int index = 0; index < static_cast<int>(Heads.size()); ++index)
{
auto const &head{Heads[index]};
if (head.kind != Kind || head.mount != Mount || false == shows(head, Need))
continue;
std::size_t common{0};
while (common < head.name.size() && common < Like.size() && head.name[common] == Like[common])
++common;
std::tuple<int, int, std::size_t> const candidate{surplus(head, Need), -static_cast<int>(common), head.name.size()};
if (best < 0 || candidate < score)
{
best = index;
score = candidate;
}
}
return best;
}
// the lights of the need in words: green, two orange, red, white; 40, 60 km/h with an orange strip
std::string need_text(head_need const &Need)
{
std::string text{STR("green")};
if (Need.informs && false == Need.speeds.empty())
text += ", " + STR("two orange");
else if (Need.informs || false == Need.speeds.empty())
text += ", " + STR("orange");
text += ", " + STR("red");
if (Need.white)
text += ", " + STR("white");
if (Need.speeds.empty())
return text;
std::string speeds;
for (auto const speed : Need.speeds)
speeds += (speeds.empty() ? "" : ", ") + std::to_string(speed);
text += "; " + format(STR_C("%s km/h"), speeds.c_str());
if (has_speed(Need.speeds, 60))
text += ", " + STR("an orange strip");
if (has_speed(Need.speeds, 100))
text += ", " + STR("a green strip");
return text;
}
ImU32 lamp_colour(char const Lamp)
{
switch (Lamp)
{
case 'z': return IM_COL32(40, 210, 80, 255);
case 'c': return IM_COL32(230, 40, 40, 255);
case 'p': return IM_COL32(255, 160, 30, 255);
case 'b': return IM_COL32(235, 235, 235, 255);
case 'n': return IM_COL32(60, 110, 255, 255);
default: return IM_COL32(70, 70, 70, 255);
}
}
// the lights of a template in a row, from its top on the left, its strips after them
void lamp_row(std::string const &Lamps, std::vector<int> const &Speeds)
{
auto const height{ImGui::GetTextLineHeight()};
auto const radius{height * 0.3f};
auto const origin{ImGui::GetCursorScreenPos()};
auto *draw{ImGui::GetWindowDrawList()};
auto x{origin.x + radius};
for (auto const lamp : Lamps)
{
draw->AddCircleFilled(ImVec2(x, origin.y + height * 0.5f), radius, lamp_colour(lamp));
x += radius * 2.6f;
}
for (auto const &[speed, colour] : {std::make_pair(60, lamp_colour('p')), std::make_pair(100, lamp_colour('z'))})
if (has_speed(Speeds, speed))
{
draw->AddRectFilled(ImVec2(x, origin.y + height * 0.38f), ImVec2(x + radius * 3.0f, origin.y + height * 0.62f), colour, 1.0f);
x += radius * 3.6f;
}
ImGui::Dummy(ImVec2(std::max(1.0f, x - origin.x), height));
}
// m, the routes of the trains are followed from a signal at most, beyond it the next one needs no telling of
double route_reach(double const Braking)
{
return std::max(3000.0, 3.0 * Braking);
}
// a standing signal by the track its trains read it on, no track for a signal of another kind or by none
station::post post_of(standing_signal const &Signal)
{
using role = station::signal::role;
station::post post;
if (Signal.kind == "main")
post.kind = role::entry;
else if (Signal.kind == "block")
post.kind = role::block;
else if (Signal.kind == "warning")
post.kind = role::warning;
else if (Signal.kind == "shunt")
post.kind = role::shunting;
else
return post;
post.point = Signal.position;
if (Signal.track != nullptr && Signal.facing != 0 && false == Signal.track->m_editorremoved)
{
post.track = Signal.track;
post.facing = Signal.facing;
return post;
}
int path{0};
post.track = kilometrage::track_near(Signal.position, 4.5, path);
if (post.track == nullptr)
return post;
geometry::bezier const curve{post.track->m_paths[path]};
auto parameter{0.0};
auto best{std::numeric_limits<double>::max()};
for (int k = 0; k <= 64; ++k)
if (auto const distance{geometry::plan_distance(curve.point(k / 64.0), Signal.position)}; distance < best)
{
best = distance;
parameter = k / 64.0;
}
auto const tangent{curve.first(parameter)};
auto const travel{travel_of(Signal.yaw)};
post.facing = travel.x * tangent.x + travel.y * tangent.z >= 0.0 ? 1 : -1;
return post;
}
} // namespace
void srk_tool::load()
@@ -265,6 +487,8 @@ void srk_tool::load()
words >> entry.km;
else if (word == "from")
entry.reversed = (words >> word) && word == "right";
else if (word == "braking")
words >> entry.braking;
else if (word == "anchor")
words >> entry.anchor;
else if (word == "name")
@@ -301,7 +525,8 @@ 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 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()));
auto const braking{entry.braking > 0 ? format(" braking %d", entry.braking) : std::string{}};
lines.push_back(format("station %s line %s km %s from %s anchor %s%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(), braking.c_str(), entry.name.c_str()));
}
scene::Layers.mark(layer, kMark, std::move(lines));
}
@@ -539,9 +764,20 @@ void srk_tool::draw_overlay() const
ImVec2 screen;
if (false == near_enough(mark.head, kLabelRange) || false == projection.project(mark.head, screen))
continue;
auto const size{ImGui::CalcTextSize(mark.text.c_str())};
drawlist->AddRectFilled(ImVec2(screen.x - size.x * 0.5f - 4.0f, screen.y - size.y - 4.0f), ImVec2(screen.x + size.x * 0.5f + 4.0f, screen.y), kSignal, 3.0f);
drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), IM_COL32(255, 255, 255, 255), mark.text.c_str());
auto const text{mark.unfit ? mark.text + " !" : mark.text};
auto const size{ImGui::CalcTextSize(text.c_str())};
drawlist->AddRectFilled(ImVec2(screen.x - size.x * 0.5f - 4.0f, screen.y - size.y - 4.0f), ImVec2(screen.x + size.x * 0.5f + 4.0f, screen.y), mark.unfit ? kUnfit : kSignal, 3.0f);
drawlist->AddText(ImVec2(screen.x - size.x * 0.5f, screen.y - size.y - 2.0f), mark.unfit ? IM_COL32(0, 0, 0, 255) : IM_COL32(255, 255, 255, 255), text.c_str());
}
if (m_head_hover >= 0 && m_head_hover < static_cast<int>(m_heads.size()))
{
auto const &head{m_heads[m_head_hover]};
ImVec2 foot, top;
if (projection.project(head.position, foot) && projection.project(head.position + glm::dvec3{0.0, 6.0, 0.0}, top))
{
drawlist->AddLine(foot, top, kHovered, 3.0f);
drawlist->AddCircle(top, 9.0f, kHovered, 0, 3.0f);
}
}
}
@@ -695,6 +931,10 @@ void srk_tool::render_options()
if (ImGui::CollapsingHeader(format("%s (%zu)###srklengths", STR_C("Track lengths"), m_lengths.size()).c_str(), ImGuiTreeNodeFlags_DefaultOpen))
render_lengths();
ImGui::Spacing();
m_head_hover = -1;
if (ImGui::CollapsingHeader(format("%s (%zu)###srkheads", STR_C("Signal heads"), static_cast<std::size_t>(std::count_if(m_heads.begin(), m_heads.end(), [](head_check const &Head) { return Head.station; }))).c_str(), ImGuiTreeNodeFlags_DefaultOpen))
render_heads();
ImGui::Spacing();
if (ImGui::CollapsingHeader(format("%s (%zu)###srkswitches", STR_C("Switches"), layout.switches.size()).c_str(), ImGuiTreeNodeFlags_DefaultOpen))
{
if (ImGui::BeginTable("##srkswitchlist", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY, ImVec2(0.0f, std::min(320.0f, ImGui::GetTextLineHeightWithSpacing() * (layout.switches.size() + 1.5f)))))
@@ -847,6 +1087,186 @@ void srk_tool::render_lengths()
dimmed(STR_C("The useful length: where a train stands, from the signal it stops by back to the joint, the fouling point or the level crossing behind it, between those where no signal stands (dimmed). The longest train leaves 25 m of it: 10 m to stop, 10 m to see the signal, 5 m to the joint"));
}
void srk_tool::render_heads()
{
auto const dimmed = [](char const *Text) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
ImGui::TextWrapped("%s", Text);
ImGui::PopStyleColor();
};
if (begin_fields("##srkbraking"))
{
field_row("Line speed");
auto const set{set_speed_of_open()};
if (set > 0.0)
ImGui::TextWrapped("%s", format(STR_C("%.0f km/h, the highest set on its main tracks"), set).c_str());
else
ImGui::TextColored(kWrong, "%s", format(STR_C("%.0f km/h taken: no track of the station has a speed set"), speed_of_open()).c_str());
item_tooltip("The speed of the trains through the station, the highest one its main tracks and the lines out of it are set to in the Track tool, up to 250 km/h: the braking distance and the lights of the signals go by it. Without one 160 km/h is taken, or the speed of the braking distance set by hand");
field_row("Braking distance");
auto const by_hand{m_record >= 0 ? m_records[m_record].braking : 0};
auto const after{format(STR_C("%.0f m, after the speed"), braking_distance(set > 0.0 ? set : 160.0))};
auto changed{false};
ImGui::SetNextItemWidth(-1.0f);
if (ImGui::BeginCombo("##srkbrakingset", by_hand > 0 ? format(STR_C("%d m, set by hand"), by_hand).c_str() : after.c_str()))
{
if (ImGui::Selectable(after.c_str(), by_hand == 0))
{
record_of_open().braking = 0;
changed = true;
}
for (auto const distance : {400, 700, 1000, 1300})
if (ImGui::Selectable(format(STR_C("%d m, set by hand"), distance).c_str(), by_hand == distance))
{
record_of_open().braking = distance;
changed = true;
}
ImGui::EndCombo();
}
item_tooltip("The braking distance of the trains on the line, Ie-4 § 8: 400 m up to 60 km/h, 700 m up to 100, 1000 m up to 140, 1300 m above. The block sections and the warning discs stand no nearer, a signal telling of the next one standing nearer slows the trains. Set it by hand where the line has another one");
ImGui::EndTable();
if (changed)
{
m_changed = true;
survey_signals();
}
}
if (false == m_heads_known)
{
dimmed(STR_C("Looking for the signal templates..."));
return;
}
if (std::none_of(m_heads.begin(), m_heads.end(), [](head_check const &Head) { return Head.station; }))
{
dimmed(STR_C("No main signal stands by the station yet: its lights are checked once one does"));
if (ImGui::Button((std::string{STR_C("Set the signals")} + "##srkheads").c_str(), ImVec2(-1.0f, 0.0f)))
set_signals();
return;
}
auto const line{speed_of_open()};
std::vector<std::size_t> rows;
std::size_t fine{0};
for (std::size_t index = 0; index < m_heads.size(); ++index)
{
auto const &head{m_heads[index]};
if (head.station || false == head.fits || false == head.problems.empty())
rows.push_back(index);
else
++fine;
}
if (ImGui::BeginTable("##srkheadlist", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY, ImVec2(0.0f, std::min(320.0f, ImGui::GetTextLineHeightWithSpacing() * (rows.size() + 1.5f)))))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(STR_C("Signal"), ImGuiTableColumnFlags_WidthStretch, 0.25f);
ImGui::TableSetupColumn(STR_C("Speeds"), ImGuiTableColumnFlags_WidthStretch, 0.23f);
ImGui::TableSetupColumn(STR_C("Lights"), ImGuiTableColumnFlags_WidthStretch, 0.25f);
ImGui::TableSetupColumn(STR_C("State"), ImGuiTableColumnFlags_WidthStretch, 0.27f);
ImGui::TableHeadersRow();
for (auto const index : rows)
{
auto const &head{m_heads[index]};
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable((head.name + "##srkhead" + std::to_string(index)).c_str(), static_cast<int>(index) == m_head_hover, ImGuiSelectableFlags_SpanAllColumns))
m_view.fly_to(head.position + glm::dvec3{0.0, 4.0, 0.0}, glm::normalize(glm::dvec3{head.travel.x, -0.3, head.travel.y}), 30.0);
if (ImGui::IsItemHovered())
{
m_head_hover = static_cast<int>(index);
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 32.0f);
auto const *kind{head.kind == "main" ? STR_C("Main signal %s") : head.kind == "block" ? STR_C("Block signal %s") : STR_C("Warning disc %s")};
ImGui::TextUnformatted(format(kind, head.name.c_str()).c_str());
ImGui::TextDisabled("%s", head.file.c_str());
for (std::size_t k = 0; k < head.routes.size(); ++k)
{
auto const &route{head.routes[k]};
std::string text;
if (route.next >= 0)
text = format(STR_C("To %s: %.0f m"), head.nexts[k].c_str(), route.length);
else if (route.ended)
text = format(STR_C("To the end of the tracks: %.0f m"), route.length);
else
text = format(STR_C("No signal within %.0f m"), route.length);
if (head.kind == "main")
text += ", " + (route.speed > 0.0 && shown_speed(route.speed, line) > 0 ? format(STR_C("%d km/h through the switches"), shown_speed(route.speed, line)) : format(STR_C("%.0f km/h"), line));
std::string turnouts;
for (auto const *turnout : route.turnouts)
turnouts += (turnouts.empty() ? "" : ", ") + without_stem(turnout->name(), stem_of_open());
if (false == turnouts.empty())
text += ", " + format(STR_C("turning at %s"), turnouts.c_str());
if (route.warning >= 0)
text += ", " + STR("past a warning disc");
ImGui::BulletText("%s", text.c_str());
}
if (head.kind != "warning")
{
head_need need;
need.speeds = head.speeds;
need.informs = head.informs;
need.white = head.white;
ImGui::TextUnformatted(format(STR_C("Needs: %s"), need_text(need).c_str()).c_str());
}
if (head.shortened > 0)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", format(STR_C("The next signal stands nearer than the braking distance: with it at stop this one slows the trains to %d km/h, Ie-4 § 8 ust. 5"), head.shortened).c_str());
if (head.indicator)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", STR_C("Give it a W21 indicator \"14\": 140 km/h with the next signal at stop, Ie-4 § 8 ust. 5"));
if (head.lamps.empty() && head.kind != "warning")
ImGui::TextDisabled("%s", STR_C("A semaphore with arms: its lights aren't checked"));
else if (false == head.fits && head.fit.empty())
ImGui::TextColored(kWrong, "%s", STR_C("No template of its kind and mount has these lights"));
else if (false == head.fits)
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", format(STR_C("Its lights don't show all of it: %s does"), head.fit.c_str()).c_str());
else if (head.more)
ImGui::TextDisabled("%s", format(STR_C("It has more lights than it needs: %s will do"), head.fit.c_str()).c_str());
for (auto const &problem : head.problems)
ImGui::TextColored(kWrong, "%s", problem.c_str());
ImGui::TextDisabled("%s", STR_C("LMB: fly to it"));
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::TableNextColumn();
if (head.kind == "main")
{
auto text{format("%.0f", line)};
for (auto speed{head.speeds.rbegin()}; speed != head.speeds.rend(); ++speed)
text += ", " + std::to_string(*speed);
ImGui::TextUnformatted(text.c_str());
}
else
ImGui::TextDisabled("-");
ImGui::TableNextColumn();
if (head.lamps.empty())
ImGui::TextDisabled("%s", head.kind == "warning" ? STR_C("disc") : STR_C("arms"));
else
lamp_row(head.lamps, head.own);
ImGui::TableNextColumn();
if (head.lamps.empty())
ImGui::TextDisabled("-");
else if (false == head.fits)
ImGui::TextColored(head.fit.empty() ? kWrong : ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", head.fit.empty() ? STR_C("no template") : STR_C("lights missing"));
else if (head.more)
ImGui::TextDisabled("%s", STR_C("more than needed"));
else
ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.45f, 1.0f), "%s", STR_C("fits"));
if (false == head.problems.empty())
{
ImGui::SameLine();
ImGui::TextColored(kWrong, "%s", STR_C("too near"));
}
}
ImGui::EndTable();
}
if (fine > 0)
ImGui::TextDisabled("%s", format(STR_P("%zu signal of the lines fits", "%zu signals of the lines fit", fine), fine).c_str());
auto const fitting{static_cast<std::size_t>(std::count_if(m_heads.begin(), m_heads.end(), [](head_check const &Head) { return false == Head.fit.empty() && Head.fit != Head.file && (false == Head.fits || Head.more); }))};
ImGui::BeginDisabled(fitting == 0);
if (main_button(format("%s (%zu)###srkfitheads", STR_C("Fit the lights"), fitting).c_str(), ImVec2(-1.0f, 0.0f)))
fit_heads();
ImGui::EndDisabled();
item_tooltip("Gives each signal marked the template of its kind and mount with the lights its routes need and the least beyond them, the name, the plate and the place kept, the trains reading it as before. One step of undo");
dimmed(STR_C("The lights go by the routes of the trains: 40 km/h through the switches 1:9, 60 through 1:12 and 1:14, 100 through 1:18.5, by their radii, for these a lower orange light, with an orange strip for 60, a green one for 100, Ie-1; an upper orange one where the next signal can show stop; the white one of the substitute signal. A signal telling of the next one standing nearer than the braking distance slows the trains with it at stop, Ie-4 § 8 ust. 5"));
}
std::string srk_tool::lengths_table() const
{
auto const stem{stem_of_open()};
@@ -1408,12 +1828,53 @@ void srk_tool::set_signals()
m_status = STR_C("The signals are set already");
return;
}
// the lights of the main signals placed after the routes of their trains, to the signals standing and to these
std::vector<std::string> unfit;
std::vector<signal_head> heads;
m_signals.signal_heads(heads, true);
auto const chosen{std::find_if(heads.begin(), heads.end(), [](signal_head const &Head) { return Head.kind == "main" && Head.chosen; })};
if (chosen != heads.end())
{
std::vector<standing_signal> everywhere;
m_signals.standing_signals(everywhere, true);
std::vector<station::post> posts;
for (auto const &signal : everywhere)
if (auto const post{post_of(signal)}; post.track != nullptr)
posts.push_back(post);
std::vector<std::size_t> from;
for (auto const &order : placed)
{
if (order.kind == "main")
from.push_back(posts.size());
posts.push_back({order.track, order.facing, order.point, order.kind == "shunt" ? station::signal::role::shunting : station::signal::role::entry});
}
auto const found{station::routes(posts, from, route_reach(braking_of_open()))};
auto const line{speed_of_open()};
std::size_t next{0};
for (auto &order : placed)
{
if (order.kind != "main")
continue;
auto const need{need_of(found[next++], true, line, braking_of_open())};
if (auto const best{best_head(heads, "main", chosen->mount, chosen->name, need)}; best >= 0)
order.file = heads[best].file;
else
unfit.push_back(signal_shown(order.name, stem));
}
}
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());
if (false == unfit.empty())
{
std::string names;
for (auto const &name : unfit)
names += (names.empty() ? "" : ", ") + name;
m_status += "\n" + format(STR_C("No template has the lights %s need, they got the one chosen in the Signals tool: see Signal heads"), names.c_str());
}
}
void srk_tool::set_lines()
@@ -1523,13 +1984,15 @@ void srk_tool::set_lines()
return (growth > 0) != lines[Index].inbound;
return (km_on(Index, 1000.0) > km_on(Index, 0.0)) != lines[Index].inbound;
};
// the braking distance at the highest speed out along the line, at 160 km/h while its tracks have none
// the braking distance at the highest speed out along the line, at 160 km/h while its tracks have none, unless one is set by hand
auto const braking = [&](std::size_t const Index) {
if (m_record >= 0 && m_records[m_record].braking > 0)
return static_cast<double>(m_records[m_record].braking);
auto speed{0.0};
auto const &line{lines[Index]};
for (std::size_t k = 0; k < line.points.size(); ++k)
if (line.distances[k] >= 0.0)
speed = std::max(speed, line.tracks[line.owners[k]]->VelocityGet());
if (auto const set{line.tracks[line.owners[k]]->VelocityGet()}; line.distances[k] >= 0.0 && set <= kFastest)
speed = std::max(speed, set);
return braking_distance(speed > 0.0 ? speed : 160.0);
};
for (std::size_t index = 0; index < lines.size(); ++index)
@@ -1914,10 +2377,13 @@ void srk_tool::survey_signals()
m_signals_known = m_layout.has_value() && m_signals.standing_signals(standing, false);
if (false == m_signals_known)
{
m_heads.clear();
m_heads_known = false;
if (false == m_measured)
measure({});
return;
}
check_heads(standing);
auto const stem{stem_of_open()};
std::vector<station::signal> stops;
for (auto const &signal : standing)
@@ -1927,7 +2393,8 @@ void srk_tool::survey_signals()
auto const travel{travel_of(signal.yaw)};
auto const at{station::project(*m_layout, signal.position)};
auto const ahead{station::project(*m_layout, signal.position + glm::dvec3{travel.x, 0.0, travel.y} * 5.0)};
m_signal_marks.push_back({at, ahead.x > at.x, signal_shown(signal.name, stem), signal.position + glm::dvec3{0.0, signal.kind == "shunt" ? 3.0 : 7.0, 0.0}});
auto const unfit{std::any_of(m_heads.begin(), m_heads.end(), [&](head_check const &Head) { return Head.instance == signal.instance && (false == Head.fits || false == Head.problems.empty()); })};
m_signal_marks.push_back({at, ahead.x > at.x, signal_shown(signal.name, stem), signal.position + glm::dvec3{0.0, signal.kind == "shunt" ? 3.0 : 7.0, 0.0}, unfit});
if (signal.kind != "main" && signal.kind != "shunt")
continue;
station::signal stop;
@@ -1954,6 +2421,211 @@ void srk_tool::measure(std::vector<station::signal> const &Signals)
});
}
double srk_tool::set_speed_of_open() const
{
if (false == m_layout.has_value())
return 0.0;
auto const highest = [](auto const &Tracks) {
auto speed{0.0};
for (auto *track : Tracks)
if (auto const set{track->VelocityGet()}; track->eType == tt_Normal && set <= kFastest)
speed = std::max(speed, set);
return speed;
};
std::vector<TTrack *> through{m_layout->main};
through.insert(through.end(), m_layout->second.begin(), m_layout->second.end());
for (auto const &path : m_layout->paths)
if (path.approach)
through.push_back(path.track);
if (auto const speed{highest(through)}; speed > 0.0)
return speed;
std::vector<TTrack *> all;
for (auto const &path : m_layout->paths)
all.push_back(path.track);
return highest(all);
}
double srk_tool::speed_of_open() const
{
if (auto const speed{set_speed_of_open()}; speed > 0.0)
return speed;
auto const set{m_record >= 0 ? m_records[m_record].braking : 0};
return set <= 0 ? 160.0 : set <= 400 ? 60.0 : set <= 700 ? 100.0 : set <= 1000 ? 140.0 : 160.0;
}
double srk_tool::braking_of_open() const
{
auto const set{m_record >= 0 ? m_records[m_record].braking : 0};
return set > 0 ? static_cast<double>(set) : braking_distance(speed_of_open());
}
void srk_tool::check_heads(std::vector<standing_signal> const &Standing)
{
using role = station::signal::role;
m_heads.clear();
std::vector<signal_head> heads;
m_heads_known = m_layout.has_value() && m_signals.signal_heads(heads, false);
if (false == m_heads_known)
return;
auto const &layout{*m_layout};
auto const stem{stem_of_open()};
auto const line{speed_of_open()};
auto const braking{braking_of_open()};
auto const middle{axis_point(layout, (layout.from + layout.to) * 0.5)};
std::vector<station::post> posts;
std::vector<std::size_t> owners;
for (std::size_t k = 0; k < Standing.size(); ++k)
if (geometry::plan_distance(Standing[k].position, middle) <= kLineLimit)
if (auto const post{post_of(Standing[k])}; post.track != nullptr)
{
posts.push_back(post);
owners.push_back(k);
}
// the main signals of the station, the block signals and the warning discs of the lines in and out of it
std::vector<std::size_t> from;
std::vector<char> own(posts.size(), 0);
for (std::size_t index = 0; index < posts.size(); ++index)
{
if (posts[index].kind == role::entry && gap_to(layout, posts[index].point) <= 8.0)
own[index] = 1;
if (own[index] || posts[index].kind == role::block || posts[index].kind == role::warning)
from.push_back(index);
}
auto const found{station::routes(posts, from, route_reach(braking))};
std::vector<int> routes_of(posts.size(), -1);
for (std::size_t k = 0; k < from.size(); ++k)
routes_of[from[k]] = static_cast<int>(k);
std::vector<char> checked(own);
std::vector<std::size_t> order;
for (std::size_t index = 0; index < posts.size(); ++index)
if (own[index])
order.push_back(index);
for (std::size_t at = 0; at < order.size(); ++at)
for (auto const &route : found[routes_of[order[at]]])
for (auto const index : {route.warning, route.next})
if (index >= 0 && 0 == checked[index] && posts[index].kind != role::entry && routes_of[index] >= 0)
{
checked[index] = 1;
order.push_back(static_cast<std::size_t>(index));
}
for (auto grown{true}; grown;)
{
grown = false;
for (std::size_t index = 0; index < posts.size(); ++index)
{
if (checked[index] || routes_of[index] < 0 || posts[index].kind == role::entry)
continue;
auto const &routes{found[routes_of[index]]};
if (std::none_of(routes.begin(), routes.end(), [&](station::route const &Route) { return Route.next >= 0 && checked[Route.next]; }))
continue;
checked[index] = 1;
order.push_back(index);
grown = true;
}
}
std::unordered_map<std::string, std::size_t> by_file;
for (std::size_t index = 0; index < heads.size(); ++index)
by_file.emplace(heads[index].file, index);
for (auto const index : order)
{
auto const &signal{Standing[owners[index]]};
auto const &post{posts[index]};
head_check check;
check.instance = signal.instance;
check.name = signal_shown(signal.name, stem);
check.kind = signal.kind;
check.position = signal.position;
check.travel = travel_of(signal.yaw);
check.file = signal.file;
check.station = own[index] != 0;
check.routes = found[routes_of[index]];
for (auto const &route : check.routes)
check.nexts.push_back(route.next >= 0 ? signal_shown(Standing[owners[route.next]].name, stem) : std::string{});
auto const known{by_file.find(signal.file)};
auto const *template_of{known != by_file.end() ? &heads[known->second] : nullptr};
if (template_of != nullptr)
{
check.lamps = template_of->lamps;
check.own = template_of->speeds;
}
if (post.kind == role::warning)
{
for (std::size_t k = 0; k < check.routes.size(); ++k)
{
auto const &route{check.routes[k]};
if (route.next < 0)
check.problems.push_back(format(STR_C("No main signal within %.0f m ahead of it to tell of"), route.length));
else if (route.length < braking - 1.0)
check.problems.push_back(format(STR_C("It stands %.0f m before %s, less than the braking distance of %.0f m: the trains don't stop by the signal in time"), route.length, check.nexts[k].c_str(), braking));
}
m_heads.push_back(std::move(check));
continue;
}
auto const main{post.kind == role::entry};
auto const need{need_of(check.routes, main, line, braking)};
check.speeds = need.speeds;
check.informs = need.informs;
check.white = need.white;
check.shortened = need.shortened;
check.indicator = need.indicator;
if (false == main)
for (std::size_t k = 0; k < check.routes.size(); ++k)
{
auto const &route{check.routes[k]};
if (route.next >= 0 && route.warning < 0 && route.length < braking - 1.0)
check.problems.push_back(format(STR_C("The block section to %s is %.0f m long, shorter than the braking distance of %.0f m, Ie-4 § 8 ust. 1"), check.nexts[k].c_str(), route.length, braking));
}
if (template_of != nullptr)
{
check.fits = shows(*template_of, need);
auto const best{best_head(heads, signal.kind, template_of->mount, template_of->name, need)};
if (best >= 0)
{
check.fit = heads[best].file;
check.more = check.fits && surplus(heads[best], need) < surplus(*template_of, need);
}
}
m_heads.push_back(std::move(check));
}
std::stable_sort(m_heads.begin(), m_heads.end(), [&](head_check const &A, head_check const &B) {
if (A.station != B.station)
return A.station;
if (A.station)
return station::project(layout, A.position).x < station::project(layout, B.position).x;
return geometry::plan_distance(A.position, middle) < geometry::plan_distance(B.position, middle);
});
}
void srk_tool::fit_heads()
{
m_status.clear();
signal_changes changes;
for (auto const &head : m_heads)
if (false == head.fit.empty() && head.fit != head.file && (false == head.fits || head.more))
changes.files.emplace_back(head.instance, head.fit);
if (changes.files.empty())
{
m_status = std::any_of(m_heads.begin(), m_heads.end(), [](head_check const &Head) { return false == Head.fits; }) ? STR_C("No template has the lights the signals marked need") : STR_C("The lights of the signals fit already");
return;
}
std::string error;
m_signals.signal_batch(changes, {}, {}, error);
if (false == error.empty())
{
m_status = format(STR_C("The signals couldn't be changed: %s"), error.c_str());
return;
}
std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true);
std::size_t changed{0};
for (auto const &head : m_heads)
changed += static_cast<std::size_t>(std::count_if(standing.begin(), standing.end(), [&](standing_signal const &Signal) { return Signal.instance == head.instance && Signal.file != head.file; }));
if (changed == 0)
m_status = STR_C("The lights of the signals fit already");
else
m_status = format(STR_P("%zu signal got the lights it needs, Ctrl+Z takes it back", "%zu signals got the lights they need, Ctrl+Z takes it back", changed), changed);
}
void srk_tool::survey()
{
m_circuit_of.assign(m_layout.has_value() ? m_layout->paths.size() : 0, -1);
@@ -2259,8 +2931,9 @@ void srk_tool::render_canvas(float const Height)
{
auto const at{screen_of(mark.at)};
auto const way{mark.ahead ? 1.0f : -1.0f};
draw->AddTriangleFilled(ImVec2(at.x - 4.0f * way, at.y - 4.0f), ImVec2(at.x - 4.0f * way, at.y + 4.0f), ImVec2(at.x + 4.0f * way, at.y), kSignal);
label(mark.text, kSignal, {ImVec2(at.x, at.y - height - 5.0f), ImVec2(at.x, at.y + 5.0f)});
auto const colour{mark.unfit ? kUnfit : kSignal};
draw->AddTriangleFilled(ImVec2(at.x - 4.0f * way, at.y - 4.0f), ImVec2(at.x - 4.0f * way, at.y + 4.0f), ImVec2(at.x + 4.0f * way, at.y), colour);
label(mark.unfit ? mark.text + " !" : mark.text, colour, {ImVec2(at.x, at.y - height - 5.0f), ImVec2(at.x, at.y + 5.0f)});
}
std::vector<std::size_t> order;
for (std::size_t index = 0; index < m_circuits.size(); ++index)
@@ -2545,6 +3218,58 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
}
return editor_selftest_result::handled;
}
if (Command == "srkheads")
{
// srkheads [braking=<m>] [fit=1]: the lights of the signals of the open station checked, fitted
if (false == m_layout.has_value())
{
WriteLog("SELFTEST srkheads: no station open");
return editor_selftest_result::handled;
}
auto const values{selftest_values(Words)};
if (auto const braking{values.find("braking")}; braking != values.end())
{
record_of_open().braking = std::atoi(braking->second.c_str());
m_changed = true;
}
std::vector<standing_signal> standing;
m_signals.standing_signals(standing, true);
std::vector<signal_head> heads;
m_signals.signal_heads(heads, true);
refresh();
if (auto const fit{values.find("fit")}; fit != values.end() && fit->second == "1" && m_layout.has_value())
{
fit_heads();
WriteLog("SELFTEST srkheads fit: " + m_status);
refresh();
}
if (false == m_layout.has_value())
{
WriteLog("SELFTEST srkheads: the station is gone");
return editor_selftest_result::handled;
}
WriteLog(format("SELFTEST srkheads: speed %.0f (set %.0f), braking %.0f, %zu checked, templates %zu", speed_of_open(), set_speed_of_open(), braking_of_open(), m_heads.size(), heads.size()));
auto const list = [](std::vector<int> const &Speeds) {
std::string text;
for (auto const speed : Speeds)
text += (text.empty() ? "" : "/") + std::to_string(speed);
return text.empty() ? std::string{"-"} : text;
};
for (auto const &head : m_heads)
{
std::string routes;
for (std::size_t k = 0; k < head.routes.size(); ++k)
{
auto const &route{head.routes[k]};
routes += format(" %s:%.0f:%.0f%s%s", route.next >= 0 ? head.nexts[k].c_str() : route.ended ? "end" : "none", route.length, route.speed, route.warning >= 0 ? ":to" : "", route.turnouts.empty() ? "" : format(":%zut", route.turnouts.size()).c_str());
}
WriteLog(format("SELFTEST srkheads %s %s%s %s lamps=%s own=%s need=%s%s%s short=%d%s fits=%d more=%d fit=%s routes%s problems=%zu", head.name.c_str(), head.kind.c_str(), head.station ? "" : "(line)", head.file.c_str(), head.lamps.c_str(), list(head.own).c_str(), list(head.speeds).c_str(), head.informs ? "+inform" : "",
head.white ? "+white" : "", head.shortened, head.indicator ? " w21" : "", head.fits ? 1 : 0, head.more ? 1 : 0, head.fit.empty() ? "-" : head.fit.c_str(), routes.c_str(), head.problems.size()));
for (auto const &problem : head.problems)
WriteLog("SELFTEST srkheads " + problem);
}
return editor_selftest_result::handled;
}
if (Command == "srkstrip")
{
// srkstrip shown=0: the schematic hidden, the 3d view whole for a screenshot

View File

@@ -56,6 +56,7 @@ class srk_tool final : public editor_tool
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
int braking{0}; // m, the braking distance of the line set by hand, 0: after its speed
};
// the schematic: the chainage across, the offsets upwards, exaggerated
struct view
@@ -104,6 +105,7 @@ class srk_tool final : public editor_tool
bool ahead{true}; // the trains it faces run along the chainage
std::string text;
glm::dvec3 head{0.0}; // above its lamps, in the scenery
bool unfit{false}; // its lights don't show what its routes need
};
std::vector<signal_mark> m_signal_marks;
bool m_signals_known{false}; // the signals standing surveyed, once their templates are found
@@ -113,6 +115,33 @@ class srk_tool final : public editor_tool
int m_length_hover{-1}; // of the lengths, the row under the mouse
int m_length_canvas{-1}; // of the lengths, of the track under the mouse in the schematic
int m_length_shown{-1}; // of the lengths, the one drawn: of the row or the track under the mouse, else of the track selected
// a signal of the station or of its lines, its lights as the routes of its trains need them: Ie-1, Ie-4 § 8
struct head_check
{
scene::instance_handle instance{0};
std::string name; // shown, without the code of the station
std::string kind;
glm::dvec3 position{0.0};
glm::dvec2 travel{0.0, 1.0};
std::string file; // of its template
std::string lamps;
std::vector<int> own; // the speeds of its template
bool station{false}; // stands by the station, not out on a line
std::vector<station::route> routes;
std::vector<std::string> nexts; // of the routes, the signals they end at, shown
std::vector<int> speeds; // km/h, the lower ones it must show: 40, 60, 100
bool informs{false}; // it must tell the next signal shows stop
bool white{false}; // it must give the substitute signal
int shortened{0}; // km/h, it slows the trains to, the next signal nearer than the braking distance: Ie-4 § 8 ust. 5
bool indicator{false}; // a W21 "14" beside it, for 140 km/h so
bool fits{true}; // its template shows all of it
bool more{false}; // its template has more than it needs
std::string fit; // the template that fits best, empty for none
std::vector<std::string> problems; // of its place
};
std::vector<head_check> m_heads;
bool m_heads_known{false}; // the templates found
int m_head_hover{-1}; // of the heads, the row under the mouse
std::string m_status;
void load();
@@ -137,6 +166,17 @@ class srk_tool final : public editor_tool
void survey_signals();
// the lengths of the station tracks, the signals given by them
void measure(std::vector<station::signal> const &Signals);
// km/h, the highest of the speeds the tracks of the open station are set to, 0 while none is
double set_speed_of_open() const;
// km/h, the trains run the station at: the highest set on its tracks, else after the braking distance set by hand, else 160
double speed_of_open() const;
// m, the braking distance of the line of the open station: set by hand, else after its speed, as Ie-4 § 8 gives it
double braking_of_open() const;
// the lights of the signals checked against the routes of their trains and the places of the signals they tell of
void check_heads(std::vector<standing_signal> const &Standing);
void render_heads();
// the signals given the templates whose lights fit their routes; one step of the history
void fit_heads();
// the lengths of the station tracks as the list of the tracks gives them, the useful ones each way
void render_lengths();
// the lengths as a table to paste into a spreadsheet, a line a track, the columns parted by tabs

View File

@@ -85,6 +85,7 @@ struct signal_order
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
std::string file; // the template, of its kind; empty for the one chosen for the kind
};
struct signal_rename
{
@@ -101,6 +102,21 @@ struct standing_signal
glm::dvec3 position{0.0};
double yaw{0.0};
std::string linked; // of a template that takes one, else empty
std::string file; // of its template
TTrack *track{nullptr}; // the trains read it on, nullptr while none does
int facing{0}; // the trains it faces run 1: towards the end of the path of the track, -1: towards its start, 0 unknown
};
// a signal template as the signalling picks one: the lights it has and the speeds its events show
struct signal_head
{
std::string file;
std::string name;
std::string kind;
std::string mount;
std::string lamps; // its chambers from the top: z green, c red, p orange, b white, n blue, x blind
std::vector<int> speeds; // of the signal itself, km/h: -1 the highest allowed, 20 the substitute signal
bool informs{false}; // it tells the next signal shows stop
bool chosen{false}; // the Signals tool places it for its kind
};
// one step of the history of the signalling: signals placed, standing ones renamed, other templates placed with them, the values
// of standing ones replaced
@@ -110,6 +126,7 @@ struct signal_changes
std::vector<signal_rename> renamed;
std::vector<template_item> others;
std::vector<std::pair<scene::instance_handle, std::vector<std::string>>> values;
std::vector<std::pair<scene::instance_handle, std::string>> files; // standing signals given another template, of the same kind, their values kept
};
struct nmt_place
@@ -195,6 +212,8 @@ class editor_signals
// 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;
// the signal templates with lights, false while they are still being looked for
virtual bool signal_heads(std::vector<signal_head> &Heads, bool const Wait) = 0;
protected:
~editor_signals() = default;