17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 13:57:28 +01:00

feat(editor): switches kept in the scenery as //$r lines: type (Rz, arc Rlj/Rld, slip Rkpd), rail, R, 1:n, side, a, b, R of the main track, the parts of a slip, km and number, catalogue or measured; recorded as they are placed on a straight, in a curve (one-sided when diverging to the side the curve turns to), as a double slip, and when their geometry is replaced or flipped; the switch panel shows the kept type and the R of the diverging track bent with the main one, with the number, rail and km to edit (km from the kept horizontal alignment); the speed check of a diverging track takes its R from the kept type; records follow renamed switches and go with removed ones on save; editor records stay out of the terrain files (.txtf), and those written there by hand go with them into .btf; the side of the diverging track shown as seen; self-test commands for placing switches, the kept switches and the .btf conversion; Polish translation

This commit is contained in:
maj00r
2026-10-11 16:33:25 +02:00
parent 572daeb274
commit 0bfb8a4e7c
11 changed files with 635 additions and 8 deletions

View File

@@ -179,6 +179,7 @@ set(SOURCES
"editor/editorRoad.cpp" "editor/editorRoad.cpp"
"editor/editorAlignment.cpp" "editor/editorAlignment.cpp"
"editor/editorAlignmentStore.cpp" "editor/editorAlignmentStore.cpp"
"editor/editorSwitchStore.cpp"
"editor/editorSpeed.cpp" "editor/editorSpeed.cpp"
"editor/editorProfile.cpp" "editor/editorProfile.cpp"
"editor/editorGauge.cpp" "editor/editorGauge.cpp"

View File

@@ -29,6 +29,7 @@ extern char **environ;
#include "editor/editorGroundMesh.hpp" #include "editor/editorGroundMesh.hpp"
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorAlignmentStore.hpp" #include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorGeometry.hpp" #include "editor/editorGeometry.hpp"
#include "editor/editorIcons.hpp" #include "editor/editorIcons.hpp"
#include "utilities/translation.h" #include "utilities/translation.h"
@@ -497,6 +498,7 @@ void editor_mode::editormode_input::poll()
editor_mode::editor_mode() { editor_mode::editor_mode() {
alignment_store::reset(); alignment_store::reset();
switch_store::reset();
m_userinterface = std::make_shared<editor_ui>(); m_userinterface = std::make_shared<editor_ui>();
// the area fill settings live in the node bank window, in the tab of the fill mode // the area fill settings live in the node bank window, in the tab of the fill mode
ui()->set_fill_options([this]() { render_area_fill(); }); ui()->set_fill_options([this]() { render_area_fill(); });
@@ -2904,6 +2906,7 @@ bool editor_mode::save()
if (m_profile.changed) if (m_profile.changed)
profile_store(); profile_store();
switch_store::tidy();
infra_store(); infra_store();
if (false == m_saved_camera.has_value() || glm::distance(m_saved_camera->first, glm::dvec3{Camera.Pos}) > 0.5 || glm::distance(m_saved_camera->second, glm::vec3{Camera.Angle}) > 0.01f) if (false == m_saved_camera.has_value() || glm::distance(m_saved_camera->first, glm::dvec3{Camera.Pos}) > 0.5 || glm::distance(m_saved_camera->second, glm::vec3{Camera.Angle}) > 0.01f)
{ {

View File

@@ -787,6 +787,9 @@ class editor_mode : public application_mode, private editor_track::observer
bool route_load(TTrack &Track); bool route_load(TTrack &Track);
void route_read_stored(); void route_read_stored();
void route_take_stored(int const Index); void route_take_stored(int const Index);
void record_switch(TTrack &Track, editor_track::switch_template const &Shape);
void record_slip(std::vector<TTrack *> const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base);
void render_switch_record(TTrack &Track);
void route_store(); void route_store();
void render_route_stored(); void render_route_stored();
void route_update() { m_route.result = alignment::compute(m_route.design); } void route_update() { m_route.result = alignment::compute(m_route.design); }

View File

@@ -34,6 +34,9 @@ http://mozilla.org/MPL/2.0/.
#include <algorithm> #include <algorithm>
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorAlignmentStore.hpp" #include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "scene/sceneterrain.h"
#include "editor/editorSpeed.hpp"
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
@@ -98,6 +101,10 @@ http://mozilla.org/MPL/2.0/.
// routeapply, routetake as the buttons of the route design do it // routeapply, routetake as the buttons of the route design do it
// alignments logs the alignments kept in the scenery and the element under each path // alignments logs the alignments kept in the scenery and the element under each path
// splitkind <kind>, nudgekind <kind> <dx> <dz> the first path over an element of the kind split in two, or moved // splitkind <kind>, nudgekind <kind> <dx> <dz> the first path over an element of the kind split in two, or moved
// switchplace <template> <x> <z> <direction> <side> the catalogue template whose label starts so put into the straight
// nearest the point, or into the curve (switchcurve); slipplace <template> <x> <z>
// switches logs the switches kept in the scenery and the speed of each diverging track
// btfcheck <terrain file> the .txtf converted to .btf, the editor lines the binary file keeps logged
// screenshot // screenshot
// save // save
// log <text> // log <text>
@@ -1127,6 +1134,111 @@ void editor_mode::selftest_step()
text += format(" [R %.3f turn %.4f L %.3f]", arc.radius, arc.turn, arc.transition); text += format(" [R %.3f turn %.4f L %.3f]", arc.radius, arc.turn, arc.transition);
WriteLog(text); WriteLog(text);
} }
else if (command == "switchplace" || command == "switchcurve" || command == "slipplace")
{
std::string label;
glm::dvec3 point{0.0};
int direction{1}, side{1};
words >> label >> point.x >> point.z >> direction >> side;
std::replace(label.begin(), label.end(), '_', ' ');
auto &tool{m_switch};
tool.templates = editor_track::standard_switch_templates();
tool.collected = true;
tool.armed = -1;
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
if (tool.templates[i].label.rfind(label, 0) == 0)
{
tool.armed = i;
break;
}
TTrack *nearest{nullptr};
double distance{1e9};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
continue;
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
if (d < distance)
{
distance = d;
nearest = track;
}
}
if (tool.armed < 0 || nearest == nullptr)
{
WriteLog("SELFTEST " + command + ": no template " + label + " or no path");
continue;
}
tool.status.clear();
if (command == "switchcurve")
{
editor_track::curve curve;
editor_track::chain chain;
std::string error;
if (false == editor_track::find_curve(*nearest, m_straights.tolerance, m_route.design.norms.gauge, curve) || false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
{
WriteLog("SELFTEST switchcurve: no curve");
continue;
}
tool.frame = sample_chain(chain);
tool.frame_tracks = chain.tracks;
tool.curved = true;
tool.along = nearest_station(tool.frame, point);
insert_curved_switch(direction, side);
}
else
{
auto const line{editor_track::find_straight(*nearest, m_straights.tolerance)};
if (command == "slipplace")
{
WriteLog(format("SELFTEST slipplace: straight from %s, %zu paths, start %.3f %.3f, %.3f m", nearest->name().c_str(), line.tracks.size(), line.start.x, line.start.z, line.length));
insert_double_slip(line, point, tool.templates[tool.armed]);
}
else
insert_switch(line, std::clamp(line.along(point), 0.0, line.length), direction, side);
}
WriteLog("SELFTEST " + command + ": " + tool.status + " " + m_straights.status);
}
else if (command == "btfcheck")
{
std::string name;
words >> name;
auto const textfile{Global.asCurrentSceneryPath + name + ".txtf"};
auto const binaryfile{Global.asCurrentSceneryPath + name + ".btf"};
auto const written{scene::terrain_file::prepare(textfile, binaryfile) == scene::terrain_file::state::binary};
auto const kept{scene::terrain_file::extra(binaryfile)};
WriteLog(format("SELFTEST btfcheck: binary %d, %zu bytes of the editor lines kept", written ? 1 : 0, kept.size()));
std::istringstream lines{kept};
std::string line;
while (std::getline(lines, line))
WriteLog("SELFTEST btf kept: " + line);
}
else if (command == "switches")
{
for (auto const &entry : switch_store::records())
{
std::string parts;
for (auto const &part : entry.parts)
parts += part + " ";
WriteLog(format("SELFTEST switch record %s %s: %s, R %.3f 1:%.3f hand %c a %.3f b %.3f main R %.3f, branch R %.3f, km %s, number %s, %s, parts %s", entry.kind.c_str(), entry.name.c_str(), switch_store::label(entry).c_str(), entry.radius, entry.ratio, entry.hand, entry.a,
entry.b, entry.main_radius, switch_store::branch_radius(entry), entry.km.has_value() ? format("%.3f", *entry.km / 1000.0).c_str() : "-", entry.number.c_str(), entry.source.c_str(), parts.c_str()));
}
speed_check::options options;
options.norms = m_route.design.norms;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2)
continue;
auto const known{switch_store::of(*track)};
geometry::bezier const branch{track->m_paths[1]};
auto const first{branch.first(0.3)};
auto const second{branch.second(0.3)};
auto const planar{std::abs(first.x * second.z - first.z * second.x) / std::pow(first.x * first.x + first.z * first.z, 1.5)};
auto const verdict{speed_check::check(*track, 1, options)};
WriteLog(format("SELFTEST switch %s: %s, branch R at 0.3 %.3f, path R %.3f, branch speed %.1f km/h at R %.1f", track->name().c_str(), known.has_value() ? switch_store::label(*known).c_str() : "no record", planar > 1e-9 ? 1.0 / planar : 0.0,
track->m_paths[1].radius, verdict.safe, verdict.radius));
}
}
else if (command == "splitkind" || command == "nudgekind") else if (command == "splitkind" || command == "nudgekind")
{ {
std::string kind; std::string kind;

View File

@@ -14,6 +14,7 @@ http:
#include "application/editorprojection.h" #include "application/editorprojection.h"
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorAlignmentStore.hpp" #include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorGeometry.hpp" #include "editor/editorGeometry.hpp"
#include "editor/editorIncludeInfo.hpp" #include "editor/editorIncludeInfo.hpp"
@@ -1751,8 +1752,9 @@ void editor_mode::render_turnout_ui()
auto const offset{glm::dot(end, normal)}; auto const offset{glm::dot(end, normal)};
glm::dvec2 const tangent{glm::normalize(glm::dvec2{-control.x, -control.z})}; glm::dvec2 const tangent{glm::normalize(glm::dvec2{-control.x, -control.z})};
auto const angle{std::atan2(std::abs(tangent.x * axis.y - tangent.y * axis.x), glm::dot(tangent, axis))}; auto const angle{std::atan2(std::abs(tangent.x * axis.y - tangent.y * axis.x), glm::dot(tangent, axis))};
ImGui::Text(STR_C("Length %.3f m, diverging %s, end offset %.3f m"), length, offset > 0.0 ? STR_C("left") : STR_C("right"), std::abs(offset)); ImGui::Text(STR_C("Length %.3f m, diverging %s, end offset %.3f m"), length, offset > 0.0 ? STR_C("right") : STR_C("left"), std::abs(offset));
render_track_name(*track); render_track_name(*track);
render_switch_record(*track);
if ((simulation::Events.FindEvent(track->name() + "+") != nullptr && simulation::Events.FindEvent(track->name() + "-") != nullptr) || (m_track_name.track == track && m_track_name.uses.includes > 0)) if ((simulation::Events.FindEvent(track->name() + "+") != nullptr && simulation::Events.FindEvent(track->name() + "-") != nullptr) || (m_track_name.track == track && m_track_name.uses.includes > 0))
ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), track->name().c_str(), track->name().c_str()); ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), track->name().c_str(), track->name().c_str());
else else
@@ -1832,6 +1834,7 @@ void editor_mode::render_turnout_ui()
push_track_snapshot(std::move(states)); push_track_snapshot(std::move(states));
editor_track::follow(*track, following); editor_track::follow(*track, following);
editor_track::commit(changed); editor_track::commit(changed);
record_switch(*track, Template);
if (false == loose.empty()) if (false == loose.empty())
ui()->set_status(STR_C("Too far from the new ends of the switch, left unjoined: ") + loose, true); ui()->set_status(STR_C("Too far from the new ends of the switch, left unjoined: ") + loose, true);
}; };
@@ -2448,6 +2451,24 @@ alignment::vertex editor_mode::route_vertex_of(editor_track::curve const &Curve)
return vertex; return vertex;
} }
namespace
{
// layer the editor keeps the records of the track in: its own, unless it's a terrain file (.txtf), whose binary version
// (.btf) the editor doesn't read the records from
scene::layer_handle record_layer(TTrack const &Track)
{
auto const usable = [](scene::layer_handle const Layer) { return scene::Layers.valid(Layer) && scene::Layers.accepts(Layer) && false == scene::Layers.layer(Layer).name.ends_with(".txtf"); };
auto layer{scene::Layers.resolve(Track.layer())};
if (false == usable(layer))
layer = scene::Layers.active();
if (false == usable(layer))
layer = 1;
return layer;
}
} // namespace
bool editor_mode::route_load(TTrack &Track) bool editor_mode::route_load(TTrack &Track)
{ {
m_route.designed = 0; m_route.designed = 0;
@@ -2542,12 +2563,7 @@ void editor_mode::route_store()
auto &route{m_route}; auto &route{m_route};
if (false == route.result.valid || route.chain.tracks.empty() || scene::Layers.empty()) if (false == route.result.valid || route.chain.tracks.empty() || scene::Layers.empty())
return; return;
auto layer{scene::Layers.resolve(route.chain.tracks.front()->layer())}; alignment_store::store(route.design, route.result, route.origin, record_layer(*route.chain.tracks.front()));
if (false == scene::Layers.valid(layer) || false == scene::Layers.accepts(layer))
layer = scene::Layers.active();
if (false == scene::Layers.valid(layer))
layer = 1;
alignment_store::store(route.design, route.result, route.origin, layer);
route.departed = -1; route.departed = -1;
route.departure = 0.0; route.departure = 0.0;
} }
@@ -6120,6 +6136,138 @@ void editor_mode::insert_switch(editor_track::straight const &Line, double const
switch_placed(placed); switch_placed(placed);
} }
namespace
{
void place_record(switch_store::record &Entry, TTrack const &First)
{
auto const &main{First.m_paths.front()};
bezier const curve{main};
Entry.start = main.points[segment_data::point::start];
auto const direction{plan_of(curve.first(0.0))};
Entry.direction = glm::length(direction) > 1e-12 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
Entry.km = alignment_store::chainage_at(Entry.start);
}
} // namespace
// the switch is kept in the scenery with its type: of the catalogue template, of the switch of the scenery it was copied
// from, or measured; on a curve it is an arc switch, one-sided when it diverges to the side the curve turns to
void editor_mode::record_switch(TTrack &Track, editor_track::switch_template const &Shape)
{
if (Track.eType != tt_Switch || Track.m_paths.size() < 2 || Track.name().empty() || scene::Layers.empty())
return;
switch_store::record entry;
if (Shape.source != nullptr)
{
if (auto const known{switch_store::of(*Shape.source)}; known.has_value() && known->kind == "switch")
{
entry = *known;
entry.number = "-";
}
else
{
entry.type = "-";
entry.radius = Shape.radius;
entry.ratio = Shape.ratio;
entry.source = "measured";
}
}
else
{
entry.type = Shape.type;
entry.rail = Shape.rail;
entry.radius = Shape.radius;
entry.ratio = Shape.ratio;
entry.a = Shape.a;
entry.b = Shape.b;
entry.source = "designed";
}
entry.kind = "switch";
entry.name = Track.name();
entry.parts = {entry.name};
place_record(entry, Track);
bezier const main{Track.m_paths[0]};
auto const &diverging{Track.m_paths[1]};
entry.hand = cross(entry.direction, plan_of(diverging.points[segment_data::point::end] - entry.start)) > 0.0 ? 'R' : 'L';
auto const first{plan_of(main.first(0.0))};
auto const last{plan_of(main.first(1.0))};
auto const turn{glm::length(first) > 1e-12 && glm::length(last) > 1e-12 ? signed_angle(glm::normalize(first), glm::normalize(last)) : 0.0};
entry.main_radius = std::abs(turn) > 1e-6 ? main.plan_length() / turn : 0.0;
if (std::abs(entry.main_radius) > 20000.0)
entry.main_radius = 0.0;
if (entry.type == "Rz" || entry.type == "Rlj" || entry.type == "Rld")
entry.type = entry.main_radius == 0.0 ? "Rz" : (entry.main_radius > 0.0) == (entry.hand == 'R') ? "Rlj" : "Rld";
switch_store::put(entry, record_layer(Track));
}
void editor_mode::record_slip(std::vector<TTrack *> const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base)
{
if (Parts.empty() || scene::Layers.empty())
return;
switch_store::record entry;
entry.kind = Kind;
entry.name = Base;
entry.type = Type;
entry.radius = Radius;
entry.ratio = Angle > 1e-9 ? 1.0 / std::tan(Angle) : 0.0;
for (auto const *part : Parts)
if (part != nullptr && false == part->name().empty() && (Kind == "crossing" || part->eType == tt_Switch))
entry.parts.push_back(part->name());
if (entry.parts.empty())
return;
auto *first{simulation::Paths.find(entry.parts.front())};
if (first == nullptr || first->m_paths.empty())
return;
place_record(entry, *first);
switch_store::put(entry, record_layer(*first));
}
void editor_mode::render_switch_record(TTrack &Track)
{
auto const known{switch_store::of(Track)};
if (false == known.has_value())
{
ImGui::TextDisabled("%s", STR_C("The scenery keeps no type for this switch, it's read from its shape"));
return;
}
auto entry{*known};
ImGui::Text(STR_C("Type kept in the scenery: %s"), switch_store::label(entry).c_str());
if (entry.kind == "switch" && entry.main_radius != 0.0)
ImGui::TextDisabled(STR_C("Diverging track R %.0f m: the R %.0f m of the type bent with the main track"), switch_store::branch_radius(entry), entry.radius);
if (entry.source == "measured")
ImGui::TextDisabled("%s", STR_C("Copied from a switch of the scenery: the size is measured from its shape"));
bool changed{false};
char number[32];
std::snprintf(number, sizeof(number), "%s", entry.number == "-" ? "" : entry.number.c_str());
ImGui::PushItemWidth(100.0f);
if (ImGui::InputText(STR_C("Number##switchnumber"), number, sizeof(number), ImGuiInputTextFlags_EnterReturnsTrue) || (ImGui::IsItemDeactivatedAfterEdit()))
{
entry.number = number[0] != '\0' ? number : "-";
changed = true;
}
item_tooltip("Number of the switch in the station, as the plans of the station give it");
ImGui::SameLine();
char rail[16];
std::snprintf(rail, sizeof(rail), "%s", entry.rail == "-" ? "" : entry.rail.c_str());
if (ImGui::InputText(STR_C("Rail##switchrail"), rail, sizeof(rail), ImGuiInputTextFlags_EnterReturnsTrue) || (ImGui::IsItemDeactivatedAfterEdit()))
{
entry.rail = rail[0] != '\0' ? rail : "-";
changed = true;
}
item_tooltip("Rail of the switch: 60E1, 49E1...");
auto km{entry.km.has_value() ? *entry.km / 1000.0 : 0.0};
if (ImGui::InputDouble(STR_C("Km of the start##switchkm"), &km, 0.0, 0.0, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue))
{
entry.km = km * 1000.0;
changed = true;
}
item_tooltip("Chainage of the start of the switch; taken from the horizontal alignment kept in the scenery when the switch lies on one");
ImGui::PopItemWidth();
if (changed)
switch_store::put(entry, entry.layer);
}
void editor_mode::switch_placed(std::vector<TTrack *> const &Created) void editor_mode::switch_placed(std::vector<TTrack *> const &Created)
{ {
auto const found{std::find_if(Created.begin(), Created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; })}; auto const found{std::find_if(Created.begin(), Created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; })};
@@ -6410,6 +6558,7 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e
if (track != nullptr) if (track != nullptr)
{ {
editor_track::commit({track}); editor_track::commit({track});
record_switch(*track, shape);
Created.push_back(track); Created.push_back(track);
m_switch.status = STR_C("Switch ") + track->name() + STR_C(" inserted"); m_switch.status = STR_C("Switch ") + track->name() + STR_C(" inserted");
} }
@@ -6985,6 +7134,7 @@ std::vector<TTrack *> editor_mode::build_double_slip(glm::dvec3 const &PointA, g
parts.push_back(editor_track::create_path(Style, arc(PointA, First, side, PointD, middle - split, middle + split))); parts.push_back(editor_track::create_path(Style, arc(PointA, First, side, PointD, middle - split, middle + split)));
parts.push_back(editor_track::create_path(Style, arc(PointC, Second, -side, PointB, middle - split, middle + split))); parts.push_back(editor_track::create_path(Style, arc(PointC, Second, -side, PointB, middle - split, middle + split)));
parts.erase(std::remove(parts.begin(), parts.end(), nullptr), parts.end()); parts.erase(std::remove(parts.begin(), parts.end(), nullptr), parts.end());
record_slip(parts, "slip", "Rkpd", Radius, Angle, base);
return parts; return parts;
} }
@@ -7449,6 +7599,7 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side)
if (track != nullptr) if (track != nullptr)
{ {
editor_track::commit({track}); editor_track::commit({track});
record_switch(*track, shape);
created.push_back(track); created.push_back(track);
tool.status = STR_C("Switch ") + track->name() + STR_C(" inserted into the curve"); tool.status = STR_C("Switch ") + track->name() + STR_C(" inserted into the curve");
} }

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp" #include "editor/editorGeometry.hpp"
#include "editor/editorTrack.hpp" #include "editor/editorTrack.hpp"
#include "editor/editorSwitchStore.hpp"
#include "world/Track.h" #include "world/Track.h"
@@ -232,6 +233,10 @@ verdict check(TTrack &Track, int const Path, options const &Options)
auto const gauge{Options.norms.gauge / 1000.0}; auto const gauge{Options.norms.gauge / 1000.0};
bool const branch{Track.eType == tt_Switch && Path == 1}; bool const branch{Track.eType == tt_Switch && Path == 1};
double kept{0.0};
if (branch)
if (auto const known{switch_store::of(Track)})
kept = switch_store::branch_radius(*known);
auto const &own{Track.m_paths[Path]}; auto const &own{Track.m_paths[Path]};
geometry::bezier const curve{own}; geometry::bezier const curve{own};
auto const before{continuations(Track, Path, false, curve.first(0.0), true)}; auto const before{continuations(Track, Path, false, curve.first(0.0), true)};
@@ -273,7 +278,7 @@ verdict check(TTrack &Track, int const Path, options const &Options)
if (sampled[i].span != own_span) if (sampled[i].span != own_span)
continue; continue;
auto const &here{samples[i]}; auto const &here{samples[i]};
auto const curvature{std::abs(here.curvature)}; auto const curvature{kept > 0.0 ? std::min(std::abs(here.curvature), 1.0 / kept) : std::abs(here.curvature)};
auto const cant{std::abs(here.cant)}; auto const cant{std::abs(here.cant)};
double safe{Options.top}; double safe{Options.top};
cause why{cause::none}; cause why{cause::none};

View File

@@ -0,0 +1,257 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include "world/Track.h"
#include "simulation/simulation.h"
#include "utilities/Logs.h"
#include <algorithm>
#include <cmath>
#include <set>
#include <sstream>
namespace switch_store
{
namespace
{
using geometry::plan_of;
std::string const kMark{"//$r"};
double const kTolerance{0.02};
std::vector<record> g_records;
bool g_loaded{false};
// format of the lines, coordinates in the space of the file:
// <switch|slip|crossing> <name> <type> <rail> <R> <n of 1:n> <hand L|R|-> <a> <b> <R of the main track, + right, - left, 0 straight>
// parts <count> <names>... start <x y z> dir <x z> km <km|-> number <label|-> source <designed|measured>
std::string word(std::string const &Text)
{
if (Text.empty())
return "-";
auto result{Text};
std::replace(result.begin(), result.end(), ' ', '_');
return result;
}
glm::dvec2 direction_of(TTrack const &Track)
{
geometry::bezier const curve{Track.m_paths.front()};
auto const direction{plan_of(curve.first(0.0))};
return glm::length(direction) > 1e-12 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
}
TTrack *usable(std::string const &Name)
{
if (Name.empty() || Name == "-")
return nullptr;
auto *track{simulation::Paths.find(Name)};
return track != nullptr && false == track->m_editorremoved && false == track->m_paths.empty() ? track : nullptr;
}
bool in_place(record const &Record, TTrack const &Track)
{
return geometry::plan_distance(Track.m_paths.front().points[segment_data::point::start], Record.start) <= kTolerance && glm::dot(direction_of(Track), Record.direction) > 0.9999;
}
void mark(scene::layer_handle const Layer)
{
if (false == scene::Layers.valid(Layer))
return;
auto const &context{scene::Layers.layer(Layer).context_insert()};
std::vector<std::string> lines;
for (auto const &entry : g_records)
{
if (entry.layer != Layer)
continue;
if (lines.empty())
lines.emplace_back("# switches placed in the track editor, the simulation skips these lines");
auto const start{context.to_local(entry.start)};
auto const ahead{context.to_local(entry.start + glm::dvec3{entry.direction.x, 0.0, entry.direction.y})};
auto direction{plan_of(ahead - start)};
direction = glm::length(direction) > 1e-12 ? glm::normalize(direction) : entry.direction;
auto text{format("%s %s %s %s %.3f %.3f %c %.3f %.3f %.3f parts %zu", entry.kind.c_str(), word(entry.name).c_str(), word(entry.type).c_str(), word(entry.rail).c_str(), entry.radius, entry.ratio, entry.hand, entry.a, entry.b, entry.main_radius,
entry.parts.size())};
for (auto const &part : entry.parts)
text += ' ' + word(part);
text += format(" start %.4f %.4f %.4f dir %.9f %.9f km ", start.x, start.y, start.z, direction.x, direction.y);
text += entry.km.has_value() ? format("%.3f", *entry.km / 1000.0) : std::string{"-"};
text += " number " + word(entry.number) + " source " + word(entry.source);
lines.push_back(text);
}
scene::Layers.mark(Layer, kMark, std::move(lines));
}
} // namespace
void reset()
{
g_records.clear();
g_loaded = false;
}
void load()
{
if (g_loaded || scene::Layers.empty())
return;
g_loaded = true;
g_records.clear();
for (auto const layer : scene::Layers.marked_layers(kMark))
{
auto const &context{scene::Layers.layer(layer).context_insert()};
for (auto const &text : scene::Layers.marked(layer, kMark))
{
std::istringstream line{text};
record entry;
entry.layer = layer;
std::string keyword, hand, km;
std::size_t count{0};
line >> entry.kind;
if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing")
continue;
line >> entry.name >> entry.type >> entry.rail >> entry.radius >> entry.ratio >> hand >> entry.a >> entry.b >> entry.main_radius >> keyword >> count;
if (line.fail() || keyword != "parts" || count > 64)
continue;
entry.hand = hand.empty() ? '-' : hand.front();
entry.parts.resize(count);
for (auto &part : entry.parts)
line >> part;
glm::dvec3 start{0.0}, ahead{0.0};
glm::dvec2 direction{0.0, 1.0};
line >> keyword >> start.x >> start.y >> start.z >> keyword >> direction.x >> direction.y >> keyword >> km >> keyword >> entry.number >> keyword >> entry.source;
if (line.fail())
continue;
entry.start = context.to_world(start);
ahead = context.to_world(start + glm::dvec3{direction.x, 0.0, direction.y});
entry.direction = plan_of(ahead - entry.start);
entry.direction = glm::length(entry.direction) > 1e-12 ? glm::normalize(entry.direction) : glm::dvec2{0.0, 1.0};
if (km != "-")
entry.km = std::stod(km) * 1000.0;
g_records.push_back(std::move(entry));
}
}
if (false == g_records.empty())
WriteLog("Editor: " + std::to_string(g_records.size()) + " switch record(s) read from the scenery", logtype::generic);
}
std::vector<record> const &records()
{
load();
return g_records;
}
std::optional<record> of(TTrack const &Track)
{
load();
auto const &name{Track.name()};
if (name.empty())
return std::nullopt;
for (auto const &entry : g_records)
{
if (std::find(entry.parts.begin(), entry.parts.end(), name) == entry.parts.end())
continue;
auto const *first{usable(entry.parts.front())};
if (first != nullptr && in_place(entry, *first))
return entry;
}
return std::nullopt;
}
double branch_radius(record const &Record)
{
if (Record.radius <= 0.0)
return 0.0;
auto const side{Record.hand == 'L' ? -1.0 : 1.0};
auto const curvature{side / Record.radius + (Record.main_radius != 0.0 ? 1.0 / Record.main_radius : 0.0)};
return std::abs(curvature) > 1e-9 ? 1.0 / std::abs(curvature) : 0.0;
}
void put(record Entry, scene::layer_handle const Layer)
{
load();
auto const existing{std::find_if(g_records.begin(), g_records.end(), [&](record const &Other) { return Other.name == Entry.name && Other.kind == Entry.kind; })};
if (existing != g_records.end())
{
Entry.layer = existing->layer;
if (Entry.number == "-")
Entry.number = existing->number;
*existing = std::move(Entry);
mark(existing->layer);
return;
}
Entry.layer = Layer;
g_records.push_back(std::move(Entry));
mark(g_records.back().layer);
}
void forget(std::string const &Name)
{
load();
std::set<scene::layer_handle> layers;
for (auto const &entry : g_records)
if (entry.name == Name)
layers.insert(entry.layer);
g_records.erase(std::remove_if(g_records.begin(), g_records.end(), [&](record const &Entry) { return Entry.name == Name; }), g_records.end());
for (auto const layer : layers)
mark(layer);
}
void tidy()
{
load();
std::set<scene::layer_handle> layers;
for (auto &entry : g_records)
{
if (entry.kind != "switch" || usable(entry.parts.front()) != nullptr)
continue;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.empty() || track->name().empty() || false == in_place(entry, *track))
continue;
entry.name = track->name();
entry.parts = {entry.name};
layers.insert(entry.layer);
break;
}
}
auto const gone = [](record const &Entry) { return std::any_of(Entry.parts.begin(), Entry.parts.end(), [](std::string const &Part) { return usable(Part) == nullptr; }); };
for (auto const &entry : g_records)
if (gone(entry))
layers.insert(entry.layer);
g_records.erase(std::remove_if(g_records.begin(), g_records.end(), gone), g_records.end());
for (auto const layer : layers)
mark(layer);
}
std::string label(record const &Record)
{
auto text{Record.type};
if (Record.rail != "-")
text += ' ' + Record.rail + '-';
else
text += ' ';
if (Record.radius > 0.0)
text += format("%.0f-", Record.radius);
if (Record.ratio > 0.0)
text += format(std::abs(Record.ratio - std::round(Record.ratio)) < 1e-3 ? "1:%.0f" : "1:%.1f", Record.ratio);
if (Record.hand == 'L' || Record.hand == 'R')
text += ' ' + std::string(1, Record.hand == 'L' ? 'L' : 'P');
if (Record.main_radius != 0.0)
text += format(", Rg %.0f", std::abs(Record.main_radius));
return text;
}
} // namespace switch_store

View File

@@ -0,0 +1,61 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <optional>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "scene/scenelayers.h"
class TTrack;
// switches placed in the track editor, kept in the scenery files as "//$r" comment lines: their type, size and side, the
// paths a slip or a crossing is made of, their km and number, so a switch is known again without guessing from its shape
namespace switch_store
{
struct record
{
scene::layer_handle layer{null_handle};
std::string kind{"switch"}; // switch, slip, crossing
std::string name; // the switch, or the base name of a slip or a crossing
std::string type{"Rz"}; // Rz, Rlj, Rld, Rkp, Rkpd, Skrz, - when unknown
std::string rail{"-"};
double radius{0.0}; // of the diverging track of the straight switch the type is made of
double ratio{0.0}; // n of 1:n
char hand{'-'}; // L, R as seen along the switch from its start
double a{0.0}, b{0.0};
double main_radius{0.0}; // of the main track, signed: + to the right, - to the left, 0 straight
std::vector<std::string> parts; // the switch, the switches of a slip, the paths of a crossing
glm::dvec3 start{0.0}; // of the first part
glm::dvec2 direction{0.0, 1.0};
std::optional<double> km;
std::string number{"-"};
std::string source{"designed"}; // designed: from the catalogue, measured: copied from a switch of the scenery
};
void reset();
void load();
std::vector<record> const &records();
// the record of the switch, slip or crossing the track is a part of, while its first part starts where the record says
std::optional<record> of(TTrack const &Track);
// radius of the diverging track: of the type, bent with the main track for an arc switch
double branch_radius(record const &Record);
// keeps the record in the file of the layer, in place of the one with the same name
void put(record Entry, scene::layer_handle const Layer);
void forget(std::string const &Name);
// on save: the records follow the renamed switches, the ones whose paths are gone are dropped
void tidy();
// "Rz 60E1-500-1:12 L", as the type is written on the lists of the switches
std::string label(record const &Record);
} // namespace switch_store

View File

@@ -2556,7 +2556,9 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
{ {
switch_template slip; switch_template slip;
slip.double_slip = true; slip.double_slip = true;
slip.type = "Rkpd";
slip.radius = slipping.first; slip.radius = slipping.first;
slip.ratio = std::stod(std::string{slipping.second}.substr(2));
slip.label = std::string{"Rkpd "} + slipping.second + " R" + std::to_string(static_cast<int>(slipping.first)) + " double slip (click at a crossing of two straights)"; slip.label = std::string{"Rkpd "} + slipping.second + " R" + std::to_string(static_cast<int>(slipping.first)) + " double slip (click at a crossing of two straights)";
result.push_back(slip); result.push_back(slip);
} }

View File

@@ -301,6 +301,8 @@ class editor_track
double a{0.0}; double a{0.0};
double b{0.0}; double b{0.0};
bool double_slip{false}; bool double_slip{false};
std::string type{"Rz"};
std::string rail{"-"};
}; };
static std::vector<switch_template> standard_switch_templates(); static std::vector<switch_template> standard_switch_templates();
static std::vector<switch_template> find_switch_templates(); static std::vector<switch_template> find_switch_templates();

View File

@@ -5346,3 +5346,33 @@ msgstr "Zapisany projekt zostanie usunięty z "
msgid "The paths stay as they are, the scenery no longer keeps a design for them" msgid "The paths stay as they are, the scenery no longer keeps a design for them"
msgstr "Odcinki zostają bez zmian, sceneria przestaje przechowywać dla nich projekt" msgstr "Odcinki zostają bez zmian, sceneria przestaje przechowywać dla nich projekt"
msgid "The scenery keeps no type for this switch, it's read from its shape"
msgstr "Sceneria nie przechowuje typu tego rozjazdu, jest odczytywany z jego kształtu"
msgid "Type kept in the scenery: %s"
msgstr "Typ zapisany w scenerii: %s"
msgid "Diverging track R %.0f m: the R %.0f m of the type bent with the main track"
msgstr "Tor zwrotny R %.0f m: R %.0f m typu wygięte razem z torem zasadniczym"
msgid "Copied from a switch of the scenery: the size is measured from its shape"
msgstr "Skopiowany z rozjazdu scenerii: wymiary zmierzone z jego kształtu"
msgid "Number##switchnumber"
msgstr "Numer##switchnumber"
msgid "Number of the switch in the station, as the plans of the station give it"
msgstr "Numer rozjazdu na stacji, według planów stacji"
msgid "Rail##switchrail"
msgstr "Szyna##switchrail"
msgid "Rail of the switch: 60E1, 49E1..."
msgstr "Szyna rozjazdu: 60E1, 49E1..."
msgid "Km of the start##switchkm"
msgstr "Km początku##switchkm"
msgid "Chainage of the start of the switch; taken from the horizontal alignment kept in the scenery when the switch lies on one"
msgstr "Kilometraż początku rozjazdu; brany z planu sytuacyjnego zapisanego w scenerii, gdy rozjazd na nim leży"