mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 20:34:54 +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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -501,6 +501,49 @@ int parameter_count(std::string const &File)
|
||||
return read_template(File, text, error) ? count_parameters(text) : 0;
|
||||
}
|
||||
|
||||
bool signal_speeds(std::string const &File, std::vector<int> &Speeds, bool &Informs)
|
||||
{
|
||||
Speeds.clear();
|
||||
Informs = false;
|
||||
template_text text;
|
||||
std::string error;
|
||||
if (false == read_template(File, text, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const tokens{tokenize(text.content)};
|
||||
auto const speed = [](std::string const &Token, int &Speed) {
|
||||
char *end{nullptr};
|
||||
auto const value{std::strtod(Token.c_str(), &end)};
|
||||
if (end == Token.c_str() || *end != '\0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Speed = static_cast<int>(std::lround(value));
|
||||
return true;
|
||||
};
|
||||
for (std::size_t idx = 0; idx + 2 < tokens.size(); ++idx)
|
||||
{
|
||||
if (tokens[idx] != "setvelocity")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int own{0};
|
||||
int next{0};
|
||||
auto const shown{speed(tokens[idx + 1], own)};
|
||||
if (shown && own != 0 && std::find(Speeds.begin(), Speeds.end(), own) == Speeds.end())
|
||||
{
|
||||
Speeds.push_back(own);
|
||||
}
|
||||
if (speed(tokens[idx + 2], next) && next == 0 && (tokens[idx + 1] == "*" || (shown && own != 0 && own != 20)))
|
||||
{
|
||||
Informs = true;
|
||||
}
|
||||
}
|
||||
std::sort(Speeds.begin(), Speeds.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void suggest(std::string const &File, include_info &Info)
|
||||
{
|
||||
template_text text;
|
||||
|
||||
@@ -68,6 +68,9 @@ bool load(std::string const &File, include_info &Info, std::string &Error);
|
||||
bool save(std::string const &File, include_info const &Info, std::string &Error);
|
||||
// highest number of (pN) placeholder used by specified template, 0 if it has none
|
||||
int parameter_count(std::string const &File);
|
||||
// the speeds the events of a signal template set it to show, by their SetVelocity: of the signal itself, km/h, -1 the highest
|
||||
// allowed, 20 the substitute signal. Informs: one of them tells the next signal shows stop. returns: false if the file can't be read
|
||||
bool signal_speeds(std::string const &File, std::vector<int> &Speeds, bool &Informs);
|
||||
// sets roles of the parameters whose use in the template makes their meaning clear. roles already set are left alone
|
||||
void suggest(std::string const &File, include_info &Info);
|
||||
// true if value of a parameter with specified role is supplied by the editor when the template is placed
|
||||
|
||||
@@ -2043,4 +2043,190 @@ std::vector<lengths> track_lengths(layout const &Layout, std::vector<numbered> c
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double constexpr kStraight{5000.0}; // m, a path curved less goes for a straight one
|
||||
int constexpr kPieces{20000}; // of the tracks passed by the routes of a signal, at most
|
||||
|
||||
struct reach
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
int facing{1};
|
||||
double from{0.0}; // m, along the path from its start, where the route comes in
|
||||
double length{0.0}; // of the route up to there
|
||||
double speed{0.0};
|
||||
int warning{-1};
|
||||
std::vector<TTrack *> turnouts;
|
||||
std::vector<std::pair<TTrack const *, int>> passed;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
double path_speed(TTrack const &Track, int const Path)
|
||||
{
|
||||
bezier const curve{Track.m_paths[Path]};
|
||||
int constexpr count{32};
|
||||
int constexpr span{4};
|
||||
auto least{std::numeric_limits<double>::max()};
|
||||
for (int i = 0; i + 2 * span <= count; ++i)
|
||||
{
|
||||
auto const a{map_of(curve.point(static_cast<double>(i) / count))};
|
||||
auto const b{map_of(curve.point(static_cast<double>(i + span) / count))};
|
||||
auto const c{map_of(curve.point(static_cast<double>(i + 2 * span) / count))};
|
||||
auto const twice{std::abs((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x))};
|
||||
if (twice > 1e-9)
|
||||
least = std::min(least, glm::distance(a, b) * glm::distance(b, c) * glm::distance(c, a) / (2.0 * twice));
|
||||
}
|
||||
return least > kStraight ? 0.0 : std::sqrt(8.424 * least);
|
||||
}
|
||||
|
||||
std::vector<std::vector<route>> routes(std::vector<post> const &Posts, std::vector<std::size_t> const &From, double const Limit)
|
||||
{
|
||||
std::vector<std::vector<route>> all;
|
||||
struct along
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
std::vector<double> distances;
|
||||
};
|
||||
std::map<std::pair<TTrack const *, int>, along> paths;
|
||||
auto const along_of = [&](TTrack const &Track, int const Path) -> along const & {
|
||||
auto &found{paths[{&Track, Path}]};
|
||||
if (found.points.empty())
|
||||
{
|
||||
double length{0.0};
|
||||
found.points = points_of(Track, Path, length);
|
||||
found.distances.assign(1, 0.0);
|
||||
for (std::size_t k = 1; k < found.points.size(); ++k)
|
||||
found.distances.push_back(found.distances.back() + plan_distance(found.points[k - 1], found.points[k]));
|
||||
}
|
||||
return found;
|
||||
};
|
||||
auto const project_on = [&](along const &Path, glm::dvec3 const &Point, double &Gap) {
|
||||
auto best{std::numeric_limits<double>::max()};
|
||||
double at{0.0};
|
||||
for (std::size_t k = 0; k + 1 < Path.points.size(); ++k)
|
||||
{
|
||||
auto const a{map_of(Path.points[k])};
|
||||
auto const ab{map_of(Path.points[k + 1]) - a};
|
||||
auto const length2{glm::dot(ab, ab)};
|
||||
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(map_of(Point) - a, ab) / length2, 0.0, 1.0) : 0.0};
|
||||
if (auto const distance{glm::distance(a + ab * t, map_of(Point))}; distance < best)
|
||||
{
|
||||
best = distance;
|
||||
at = Path.distances[k] + (Path.distances[k + 1] - Path.distances[k]) * t;
|
||||
}
|
||||
}
|
||||
Gap = best;
|
||||
return at;
|
||||
};
|
||||
std::unordered_map<TTrack const *, std::vector<std::size_t>> by_track;
|
||||
for (std::size_t index = 0; index < Posts.size(); ++index)
|
||||
if (Posts[index].track != nullptr)
|
||||
by_track[Posts[index].track].push_back(index);
|
||||
auto const routes_from = [&](std::size_t const Start) {
|
||||
std::vector<route> result;
|
||||
auto const &start{Posts[Start]};
|
||||
auto path{0};
|
||||
auto nearest{std::numeric_limits<double>::max()};
|
||||
double from{0.0};
|
||||
for (int candidate = 0; candidate < static_cast<int>(start.track->m_paths.size()); ++candidate)
|
||||
{
|
||||
double gap{0.0};
|
||||
auto const at{project_on(along_of(*start.track, candidate), start.point, gap)};
|
||||
if (gap < nearest - 1e-3)
|
||||
{
|
||||
nearest = gap;
|
||||
path = candidate;
|
||||
from = at;
|
||||
}
|
||||
}
|
||||
std::vector<reach> stack{{start.track, path, start.facing, from, 0.0, 0.0, -1, {}, {}}};
|
||||
auto pieces{0};
|
||||
while (false == stack.empty() && pieces++ < kPieces)
|
||||
{
|
||||
auto here{std::move(stack.back())};
|
||||
stack.pop_back();
|
||||
auto const &track{*here.track};
|
||||
auto const &line{along_of(track, here.path)};
|
||||
auto const total{line.distances.back()};
|
||||
here.passed.emplace_back(&track, here.path * 2 + (here.facing > 0 ? 1 : 0));
|
||||
if (node(track))
|
||||
{
|
||||
auto const speed{path_speed(track, here.path)};
|
||||
auto limit{speed};
|
||||
auto const set{track.SwitchExtension != nullptr ? std::abs(static_cast<double>(track.SwitchExtension->fVelocity)) : here.track->VelocityGet()};
|
||||
if (speed > 0.0 && set >= 2.0)
|
||||
limit = std::min(speed, set);
|
||||
if (limit > 0.0 && (here.speed <= 0.0 || limit < here.speed))
|
||||
here.speed = limit;
|
||||
if (speed > 0.0 && (track.eType != tt_Switch || here.path != straighter(track)))
|
||||
here.turnouts.push_back(here.track);
|
||||
}
|
||||
// the signals ahead on the path, the nearest first
|
||||
std::vector<std::pair<double, std::size_t>> ahead;
|
||||
if (auto const found{by_track.find(&track)}; found != by_track.end())
|
||||
for (auto const index : found->second)
|
||||
{
|
||||
auto const &other{Posts[index]};
|
||||
if (index == Start || other.facing != here.facing || other.kind == signal::role::shunting)
|
||||
continue;
|
||||
double gap{0.0};
|
||||
auto const at{project_on(line, other.point, gap)};
|
||||
auto const distance{(at - here.from) * here.facing};
|
||||
if (gap < 4.5 && distance > 0.1)
|
||||
ahead.emplace_back(distance, index);
|
||||
}
|
||||
std::sort(ahead.begin(), ahead.end());
|
||||
auto stopped{false};
|
||||
for (auto const &[distance, index] : ahead)
|
||||
{
|
||||
if (here.length + distance > Limit)
|
||||
break;
|
||||
if (Posts[index].kind == signal::role::warning)
|
||||
{
|
||||
if (here.warning < 0)
|
||||
here.warning = static_cast<int>(index);
|
||||
continue;
|
||||
}
|
||||
result.push_back({static_cast<int>(index), here.warning, here.length + distance, here.speed, here.turnouts, false});
|
||||
stopped = true;
|
||||
break;
|
||||
}
|
||||
if (stopped)
|
||||
continue;
|
||||
auto const length{here.length + (here.facing > 0 ? total - here.from : here.from)};
|
||||
if (length > Limit)
|
||||
{
|
||||
result.push_back({-1, here.warning, Limit, here.speed, here.turnouts, false});
|
||||
continue;
|
||||
}
|
||||
auto const atend{here.facing > 0};
|
||||
auto const point{end_of(track, here.path, atend)};
|
||||
auto *next{neighbour(track, here.path, atend)};
|
||||
auto went{false};
|
||||
if (next != nullptr && rail(*next))
|
||||
for (int other = 0; other < static_cast<int>(next->m_paths.size()); ++other)
|
||||
for (auto const end : {false, true})
|
||||
{
|
||||
if (glm::distance(end_of(*next, other, end), point) >= same_point)
|
||||
continue;
|
||||
auto const facing{end ? -1 : 1};
|
||||
if (std::find(here.passed.begin(), here.passed.end(), std::make_pair(static_cast<TTrack const *>(next), other * 2 + (facing > 0 ? 1 : 0))) != here.passed.end())
|
||||
continue;
|
||||
auto const &entered{along_of(*next, other)};
|
||||
stack.push_back({next, other, facing, end ? entered.distances.back() : 0.0, length, here.speed, here.warning, here.turnouts, here.passed});
|
||||
went = true;
|
||||
}
|
||||
if (false == went)
|
||||
result.push_back({-1, here.warning, length, here.speed, here.turnouts, true});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (auto const from : From)
|
||||
all.push_back(from < Posts.size() && Posts[from].track != nullptr && false == Posts[from].track->m_paths.empty() ? routes_from(from) : std::vector<route>{});
|
||||
return all;
|
||||
}
|
||||
|
||||
} // namespace station
|
||||
|
||||
@@ -226,4 +226,27 @@ struct lengths
|
||||
// the lengths of the station tracks, the signals standing by them among the signals given
|
||||
std::vector<lengths> track_lengths(layout const &Layout, std::vector<numbered> const &Tracks, std::vector<signal> const &Signals);
|
||||
|
||||
// a signal by the track the trains read it on
|
||||
struct post
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int facing{1}; // the trains it faces run 1: towards the end of the path, -1: towards its start
|
||||
glm::dvec3 point{0.0};
|
||||
signal::role kind{signal::role::entry}; // entry for any main signal
|
||||
};
|
||||
// the way of the trains from a signal on to the next main or block signal facing them
|
||||
struct route
|
||||
{
|
||||
int next{-1}; // of the posts, -1 for none within the limit
|
||||
int warning{-1}; // the warning disc passed on the way, of the posts
|
||||
double length{0.0}; // m, in the plan
|
||||
double speed{0.0}; // km/h, the least the switches on it allow on their curved paths, by the radius and by the speed set; 0 for no limit
|
||||
std::vector<TTrack *> turnouts; // the switches it turns off the straight at
|
||||
bool ended{false}; // at the end of the tracks, before the limit
|
||||
};
|
||||
// km/h, a train runs the path at by its least radius, 0.65 m/s² of the acceleration left unbalanced without cant; 0 for a straight one
|
||||
double path_speed(TTrack const &Track, int const Path);
|
||||
// the routes of the trains from each of the posts given on through the switches, none longer than the limit
|
||||
std::vector<std::vector<route>> routes(std::vector<post> const &Posts, std::vector<std::size_t> const &From, double const Limit);
|
||||
|
||||
} // namespace station
|
||||
|
||||
@@ -10885,3 +10885,180 @@ msgstr "Tor\tDługość budowlana [m]\tUżyteczna w prawo [m]\tod\tdo\tUżyteczn
|
||||
msgid "Track %s, useful length %.0f m to the right, %.0f m to the left"
|
||||
msgstr "Tor %s, długość użyteczna %.0f m w prawo, %.0f m w lewo"
|
||||
|
||||
msgid "green"
|
||||
msgstr "zielone"
|
||||
|
||||
msgid "two orange"
|
||||
msgstr "dwa pomarańczowe"
|
||||
|
||||
msgid "red"
|
||||
msgstr "czerwone"
|
||||
|
||||
msgid "%s km/h"
|
||||
msgstr "%s km/h"
|
||||
|
||||
msgid "an orange strip"
|
||||
msgstr "pomarańczowy pas świetlny"
|
||||
|
||||
msgid "a green strip"
|
||||
msgstr "zielony pas świetlny"
|
||||
|
||||
msgid "Signal heads"
|
||||
msgstr "Komory semaforów"
|
||||
|
||||
msgid "Line speed"
|
||||
msgstr "Prędkość linii"
|
||||
|
||||
msgid "%.0f km/h, the highest set on its main tracks"
|
||||
msgstr "%.0f km/h, najwyższa ustawiona na torach głównych"
|
||||
|
||||
msgid "%.0f km/h taken: no track of the station has a speed set"
|
||||
msgstr "przyjęto %.0f km/h: żaden tor stacji nie ma ustawionej prędkości"
|
||||
|
||||
msgid "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"
|
||||
msgstr "Prędkość pociągów przez stację: najwyższa, jaką jej tory główne i wychodzące z niej linie mają ustawioną w narzędziu Tor, do 250 km/h. Od niej zależą droga hamowania i światła semaforów. Gdy żaden tor jej nie ma, przyjmuje się 160 km/h albo prędkość drogi hamowania ustawionej ręcznie"
|
||||
|
||||
msgid "Braking distance"
|
||||
msgstr "Droga hamowania"
|
||||
|
||||
msgid "%.0f m, after the speed"
|
||||
msgstr "%.0f m, według prędkości"
|
||||
|
||||
msgid "%d m, set by hand"
|
||||
msgstr "%d m, ustawiona ręcznie"
|
||||
|
||||
msgid "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"
|
||||
msgstr "Droga hamowania pociągów na linii, Ie-4 § 8: 400 m do 60 km/h, 700 m do 100, 1000 m do 140, 1300 m powyżej. Odstępy blokady i tarcze ostrzegawcze nie są krótsze, a semafor zapowiadający następny, który stoi bliżej, zmniejsza prędkość pociągów. Ustaw ją ręcznie, gdy linia ma inną"
|
||||
|
||||
msgid "Looking for the signal templates..."
|
||||
msgstr "Szukam szablonów semaforów..."
|
||||
|
||||
msgid "No main signal stands by the station yet: its lights are checked once one does"
|
||||
msgstr "Przy stacji nie stoi jeszcze żaden semafor: światła sprawdzę, gdy jakiś stanie"
|
||||
|
||||
msgid "Speeds"
|
||||
msgstr "Prędkości"
|
||||
|
||||
msgid "Lights"
|
||||
msgstr "Światła"
|
||||
|
||||
msgid "State"
|
||||
msgstr "Stan"
|
||||
|
||||
msgid "Main signal %s"
|
||||
msgstr "Semafor %s"
|
||||
|
||||
msgid "Block signal %s"
|
||||
msgstr "Semafor SBL %s"
|
||||
|
||||
msgid "Warning disc %s"
|
||||
msgstr "Tarcza ostrzegawcza %s"
|
||||
|
||||
msgid "To %s: %.0f m"
|
||||
msgstr "Do %s: %.0f m"
|
||||
|
||||
msgid "To the end of the tracks: %.0f m"
|
||||
msgstr "Do końca torów: %.0f m"
|
||||
|
||||
msgid "No signal within %.0f m"
|
||||
msgstr "Żadnego semafora przez %.0f m"
|
||||
|
||||
msgid "%d km/h through the switches"
|
||||
msgstr "%d km/h przez rozjazdy"
|
||||
|
||||
msgid "%.0f km/h"
|
||||
msgstr "%.0f km/h"
|
||||
|
||||
msgid "turning at %s"
|
||||
msgstr "zwrot na %s"
|
||||
|
||||
msgid "past a warning disc"
|
||||
msgstr "za tarczą ostrzegawczą"
|
||||
|
||||
msgid "Needs: %s"
|
||||
msgstr "Potrzebne: %s"
|
||||
|
||||
msgid "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"
|
||||
msgstr "Następny semafor stoi bliżej niż droga hamowania: gdy on pokazuje stój, ten zmniejsza prędkość pociągów do %d km/h, Ie-4 § 8 ust. 5"
|
||||
|
||||
msgid "Give it a W21 indicator \"14\": 140 km/h with the next signal at stop, Ie-4 § 8 ust. 5"
|
||||
msgstr "Dostaw mu wskaźnik W21 „14”: 140 km/h, gdy następny semafor pokazuje stój, Ie-4 § 8 ust. 5"
|
||||
|
||||
msgid "A semaphore with arms: its lights aren't checked"
|
||||
msgstr "Semafor kształtowy: jego świateł nie sprawdzam"
|
||||
|
||||
msgid "No template of its kind and mount has these lights"
|
||||
msgstr "Żaden szablon tego rodzaju i mocowania nie ma tych świateł"
|
||||
|
||||
msgid "Its lights don't show all of it: %s does"
|
||||
msgstr "Jego światła nie pokażą wszystkiego, %s pokaże"
|
||||
|
||||
msgid "It has more lights than it needs: %s will do"
|
||||
msgstr "Ma więcej świateł, niż trzeba: wystarczy %s"
|
||||
|
||||
msgid "LMB: fly to it"
|
||||
msgstr "LPM: leć do niego"
|
||||
|
||||
msgid "disc"
|
||||
msgstr "tarcza"
|
||||
|
||||
msgid "arms"
|
||||
msgstr "ramiona"
|
||||
|
||||
msgid "no template"
|
||||
msgstr "brak szablonu"
|
||||
|
||||
msgid "lights missing"
|
||||
msgstr "brak świateł"
|
||||
|
||||
msgid "more than needed"
|
||||
msgstr "za dużo"
|
||||
|
||||
msgid "fits"
|
||||
msgstr "pasuje"
|
||||
|
||||
msgid "too near"
|
||||
msgstr "za blisko"
|
||||
|
||||
msgid "Fit the lights"
|
||||
msgstr "Dopasuj światła"
|
||||
|
||||
msgid "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"
|
||||
msgstr "Daje każdemu zaznaczonemu semaforowi szablon tego samego rodzaju i mocowania ze światłami, jakich wymagają jego przebiegi, i jak najmniejszym nadmiarem. Nazwa, tablica i miejsce zostają, pociągi odczytują go jak dotąd. Jeden krok cofania"
|
||||
|
||||
msgid "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"
|
||||
msgstr "Światła wynikają z przebiegów pociągów: 40 km/h przez rozjazdy 1:9, 60 przez 1:12 i 1:14, 100 przez 1:18,5, liczone z ich promieni; dla nich dolne światło pomarańczowe, z pomarańczowym pasem dla 60 i zielonym dla 100, Ie-1; górne pomarańczowe, gdy następny semafor może pokazać stój; białe dla sygnału zastępczego. Semafor zapowiadający następny, który stoi bliżej niż droga hamowania, zmniejsza prędkość pociągów, gdy tamten pokazuje stój, Ie-4 § 8 ust. 5"
|
||||
|
||||
msgid "No template has the lights %s need, they got the one chosen in the Signals tool: see Signal heads"
|
||||
msgstr "Żaden szablon nie ma świateł potrzebnych dla %s, dostały ten wybrany w narzędziu Sygnalizatory: zobacz Komory semaforów"
|
||||
|
||||
msgid "No main signal within %.0f m ahead of it to tell of"
|
||||
msgstr "Przez %.0f m przed nią nie ma semafora, który by zapowiadała"
|
||||
|
||||
msgid "It stands %.0f m before %s, less than the braking distance of %.0f m: the trains don't stop by the signal in time"
|
||||
msgstr "Stoi %.0f m przed %s, mniej niż droga hamowania %.0f m: pociąg nie zdąży się zatrzymać przed semaforem"
|
||||
|
||||
msgid "The block section to %s is %.0f m long, shorter than the braking distance of %.0f m, Ie-4 § 8 ust. 1"
|
||||
msgstr "Odstęp do %s ma %.0f m, mniej niż droga hamowania %.0f m, Ie-4 § 8 ust. 1"
|
||||
|
||||
msgid "No template has the lights the signals marked need"
|
||||
msgstr "Żaden szablon nie ma świateł potrzebnych zaznaczonym semaforom"
|
||||
|
||||
msgid "The lights of the signals fit already"
|
||||
msgstr "Światła semaforów już pasują"
|
||||
|
||||
msgid "The signals couldn't be changed: %s"
|
||||
msgstr "Nie udało się zmienić semaforów: %s"
|
||||
|
||||
msgid "%zu signal of the lines fits"
|
||||
msgid_plural "%zu signals of the lines fit"
|
||||
msgstr[0] "%zu semafor linii pasuje"
|
||||
msgstr[1] "%zu semafory linii pasują"
|
||||
msgstr[2] "%zu semaforów linii pasuje"
|
||||
|
||||
msgid "%zu signal got the lights it needs, Ctrl+Z takes it back"
|
||||
msgid_plural "%zu signals got the lights they need, Ctrl+Z takes it back"
|
||||
msgstr[0] "%zu semafor dostał potrzebne światła, Ctrl+Z cofa"
|
||||
msgstr[1] "%zu semafory dostały potrzebne światła, Ctrl+Z cofa"
|
||||
msgstr[2] "%zu semaforów dostało potrzebne światła, Ctrl+Z cofa"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user