mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-05 01:55:43 +01:00
Merge branch 'feat/track-join-ux' into feat/switch-blades
This commit is contained in:
@@ -178,6 +178,9 @@ set(SOURCES
|
|||||||
"editor/editorGroundFollow.cpp"
|
"editor/editorGroundFollow.cpp"
|
||||||
"editor/editorAlignmentStore.cpp"
|
"editor/editorAlignmentStore.cpp"
|
||||||
"editor/editorSwitchStore.cpp"
|
"editor/editorSwitchStore.cpp"
|
||||||
|
"editor/editorSwitchDesign.cpp"
|
||||||
|
"editor/editorConnections.cpp"
|
||||||
|
"application/editormodeconnection.cpp"
|
||||||
"editor/editorSpeed.cpp"
|
"editor/editorSpeed.cpp"
|
||||||
"editor/editorProfile.cpp"
|
"editor/editorProfile.cpp"
|
||||||
"editor/editorGauge.cpp"
|
"editor/editorGauge.cpp"
|
||||||
@@ -192,6 +195,7 @@ set(SOURCES
|
|||||||
"application/editormoderecords.cpp"
|
"application/editormoderecords.cpp"
|
||||||
"application/editormodeswitchdrive.cpp"
|
"application/editormodeswitchdrive.cpp"
|
||||||
"application/editormodeswitchslide.cpp"
|
"application/editormodeswitchslide.cpp"
|
||||||
|
"application/editormodeswitchdesign.cpp"
|
||||||
"application/editormodetrackbed.cpp"
|
"application/editormodetrackbed.cpp"
|
||||||
"application/editormodetrackux.cpp"
|
"application/editormodetrackux.cpp"
|
||||||
"application/editormoderoad.cpp"
|
"application/editormoderoad.cpp"
|
||||||
|
|||||||
@@ -1483,6 +1483,7 @@ bool editor_mode::update()
|
|||||||
render_shortcuts_window();
|
render_shortcuts_window();
|
||||||
render_first_steps();
|
render_first_steps();
|
||||||
render_orthophoto_window();
|
render_orthophoto_window();
|
||||||
|
render_switch_designer();
|
||||||
render_environment_window();
|
render_environment_window();
|
||||||
draw_selection_overlay();
|
draw_selection_overlay();
|
||||||
render_context_menu();
|
render_context_menu();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "editor/editorTerrainStreamer.hpp"
|
#include "editor/editorTerrainStreamer.hpp"
|
||||||
#include "editor/editorOrthophoto.hpp"
|
#include "editor/editorOrthophoto.hpp"
|
||||||
#include "editor/editorTrack.hpp"
|
#include "editor/editorTrack.hpp"
|
||||||
|
#include "editor/editorConnections.hpp"
|
||||||
#include "editor/editorAlignment.hpp"
|
#include "editor/editorAlignment.hpp"
|
||||||
#include "editor/editorAlignmentStore.hpp"
|
#include "editor/editorAlignmentStore.hpp"
|
||||||
#include "editor/editorSwitchStore.hpp"
|
#include "editor/editorSwitchStore.hpp"
|
||||||
@@ -725,6 +726,8 @@ class editor_mode : public application_mode, private editor_track::observer
|
|||||||
void track_gizmo_take_switch_joints(TTrack &Track, bool const Controlmode);
|
void track_gizmo_take_switch_joints(TTrack &Track, bool const Controlmode);
|
||||||
track_gizmo_drag::switch_joint track_gizmo_link(TTrack &Track, editor_track::point_ref const &Own, std::pair<TTrack *, editor_track::point_ref> const &Connection, std::function<bool(TTrack const *)> const &Movable);
|
track_gizmo_drag::switch_joint track_gizmo_link(TTrack &Track, editor_track::point_ref const &Own, std::pair<TTrack *, editor_track::point_ref> const &Connection, std::function<bool(TTrack const *)> const &Movable);
|
||||||
void track_gizmo_take_switches(TTrack &Track);
|
void track_gizmo_take_switches(TTrack &Track);
|
||||||
|
// the other switch of a double switch and the track between them go with the moved or turned switch as a whole
|
||||||
|
void track_gizmo_take_double(TTrack &Track);
|
||||||
void track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode);
|
void track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode);
|
||||||
void track_gizmo_measure(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
void track_gizmo_measure(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
||||||
void track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
void track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
||||||
@@ -832,6 +835,19 @@ class editor_mode : public application_mode, private editor_track::observer
|
|||||||
void route_read_stored(TTrack &Track);
|
void route_read_stored(TTrack &Track);
|
||||||
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_switch(TTrack &Track, editor_track::switch_template const &Shape);
|
||||||
|
void record_double(TTrack &First, TTrack &Second, editor_track::switch_template const &Shape);
|
||||||
|
// the two switches of a double switch and the plain track between them, found from any of the three
|
||||||
|
struct double_switch
|
||||||
|
{
|
||||||
|
TTrack *first{nullptr};
|
||||||
|
TTrack *link{nullptr};
|
||||||
|
TTrack *second{nullptr};
|
||||||
|
};
|
||||||
|
std::optional<double_switch> double_of(TTrack const &Track) const;
|
||||||
|
// the paths of the set in the order editor_track::place_switch gives them
|
||||||
|
static std::vector<segment_data> double_paths(double_switch const &Set);
|
||||||
|
// the switch of the template, or the two switches of a double one with the plain track between them, committed and recorded
|
||||||
|
std::vector<TTrack *> create_switch_set(editor_track::switch_template const &Shape, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name);
|
||||||
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 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 const &Track) const;
|
void render_switch_record(TTrack const &Track) const;
|
||||||
void route_store();
|
void route_store();
|
||||||
@@ -978,15 +994,33 @@ class editor_mode : public application_mode, private editor_track::observer
|
|||||||
double low{0.0};
|
double low{0.0};
|
||||||
double high{0.0};
|
double high{0.0};
|
||||||
double distance{0.0};
|
double distance{0.0};
|
||||||
|
double length{0.0};
|
||||||
std::vector<double> joints;
|
std::vector<double> joints;
|
||||||
};
|
};
|
||||||
slide_state slide;
|
slide_state slide;
|
||||||
double slide_typed{0.0};
|
double slide_typed{0.0};
|
||||||
|
int connection{-1};
|
||||||
|
int connection_second{-1};
|
||||||
|
double connection_radius{0.0};
|
||||||
|
double connection_gap{6.0};
|
||||||
|
bool connection_together{false};
|
||||||
|
char connection_spacings[64]{"4.75;4.75;5.0"};
|
||||||
|
double connection_stub{25.0};
|
||||||
};
|
};
|
||||||
switch_tool m_switch;
|
switch_tool m_switch;
|
||||||
|
bool plan_connection(connection::request &Request, connection::result &Result) const;
|
||||||
|
void insert_connection();
|
||||||
|
void render_connection_ui();
|
||||||
|
std::string connection_readout(connection::result const &Plan) const;
|
||||||
|
editor_track::straight straight_near(glm::dvec3 const &Point, std::vector<TTrack const *> const &Exclude, double const Reach) const;
|
||||||
|
editor_track::straight straight_through(glm::dvec3 const &Point, glm::dvec2 const &Direction) const;
|
||||||
|
void record_connection(connection::kind const Kind, std::vector<TTrack *> const &Switches, connection::result const &Plan, editor_track::switch_template const &Shape);
|
||||||
bool switch_room(TTrack &Track, double &Low, double &High, std::string &Reason);
|
bool switch_room(TTrack &Track, double &Low, double &High, std::string &Reason);
|
||||||
bool switch_vacate(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Shift, editor_track::straight const &Line, editor_track::edit &Edit, std::string &Status);
|
bool switch_vacate(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Shift, editor_track::straight const &Line, editor_track::edit &Edit, std::string &Status);
|
||||||
bool move_switch_paths(TTrack &Track, std::vector<segment_data> const &Paths, std::string &Status);
|
bool move_switch_paths(TTrack &Track, std::vector<segment_data> const &Paths, std::string &Status);
|
||||||
|
// room on the straight of the whole double switch along the axis of its first switch, and the length of the set
|
||||||
|
bool double_room(double_switch const &Set, double &Low, double &High, double &Length, std::string &Reason);
|
||||||
|
bool move_double_paths(double_switch const &Set, std::vector<segment_data> const &Paths, std::string &Status);
|
||||||
bool slide_switch(TTrack &Track, double const Distance);
|
bool slide_switch(TTrack &Track, double const Distance);
|
||||||
bool switch_slides(TTrack const &Track) const;
|
bool switch_slides(TTrack const &Track) const;
|
||||||
void switch_slide_start(TTrack &Track);
|
void switch_slide_start(TTrack &Track);
|
||||||
@@ -1213,6 +1247,29 @@ class editor_mode : public application_mode, private editor_track::observer
|
|||||||
int m_lineside_tab{0};
|
int m_lineside_tab{0};
|
||||||
void render_lineside_ui();
|
void render_lineside_ui();
|
||||||
void arm_switch();
|
void arm_switch();
|
||||||
|
// the window of the switch templates: the catalogue and the own ones, made of straights, arcs and transition curves
|
||||||
|
struct switch_designer
|
||||||
|
{
|
||||||
|
bool open{false};
|
||||||
|
bool loaded{false};
|
||||||
|
std::vector<switch_design::design> catalogue;
|
||||||
|
std::vector<switch_design::design> own;
|
||||||
|
bool from_catalogue{true};
|
||||||
|
int selected{-1};
|
||||||
|
bool dirty{false};
|
||||||
|
bool stretched{true};
|
||||||
|
double quick_radius{300.0};
|
||||||
|
double quick_ratio{9.0};
|
||||||
|
double quick_straight{0.0};
|
||||||
|
double blade_length{5.0};
|
||||||
|
double blade_gap{0.15};
|
||||||
|
std::string status;
|
||||||
|
};
|
||||||
|
switch_designer m_switch_designer;
|
||||||
|
void switch_designs_load();
|
||||||
|
// the list of the switch tool built again with the own templates, keeping the armed one or arming the one whose label starts with Arm
|
||||||
|
void switch_templates_refresh(std::string const &Arm = {});
|
||||||
|
void render_switch_designer();
|
||||||
bool m_track_window_open{false};
|
bool m_track_window_open{false};
|
||||||
// the inspector is pinned to the right edge of the screen, the profile strip to the bottom one, both resized from the inner edge
|
// the inspector is pinned to the right edge of the screen, the profile strip to the bottom one, both resized from the inner edge
|
||||||
void render_track_inspector();
|
void render_track_inspector();
|
||||||
|
|||||||
388
application/editormodeconnection.cpp
Normal file
388
application/editormodeconnection.cpp
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
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 "application/editormode.h"
|
||||||
|
#include "application/editoruilayer.h"
|
||||||
|
#include "application/editormodeshared.h"
|
||||||
|
#include "editor/editorFormat.hpp"
|
||||||
|
#include "editor/editorAlignmentStore.hpp"
|
||||||
|
#include "editor/editorSwitchStore.hpp"
|
||||||
|
#include "editor/editorGeometry.hpp"
|
||||||
|
|
||||||
|
#include "scene/scene.h"
|
||||||
|
#include "simulation/simulation.h"
|
||||||
|
#include "world/Track.h"
|
||||||
|
#include "utilities/translation.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using geometry::cross;
|
||||||
|
using geometry::plan_of;
|
||||||
|
|
||||||
|
char const *connection_name(int const Kind)
|
||||||
|
{
|
||||||
|
switch (static_cast<connection::kind>(Kind))
|
||||||
|
{
|
||||||
|
case connection::kind::double_crossover: return STR_C("Double crossover");
|
||||||
|
case connection::kind::scissors: return STR_C("Scissors crossover");
|
||||||
|
case connection::kind::ladder: return STR_C("Switch ladder");
|
||||||
|
default: return STR_C("Crossover");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> spacings_of(char const *Text)
|
||||||
|
{
|
||||||
|
std::string text{Text};
|
||||||
|
for (auto &letter : text)
|
||||||
|
if (letter == ';')
|
||||||
|
letter = ' ';
|
||||||
|
else if (letter == ',')
|
||||||
|
letter = '.';
|
||||||
|
std::istringstream words{text};
|
||||||
|
std::vector<double> result;
|
||||||
|
double value{0.0};
|
||||||
|
while (words >> value)
|
||||||
|
result.push_back(value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string switch_label(editor_track::switch_template const &Template)
|
||||||
|
{
|
||||||
|
if (Template.source != nullptr)
|
||||||
|
return STR("Scenery: ") + Template.label;
|
||||||
|
return (Template.own ? STR("Own: ") : std::string{"PLK: "}) + Template.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usable_in_connection(editor_track::switch_template const &Template)
|
||||||
|
{
|
||||||
|
return false == Template.double_slip && false == Template.twofold();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
editor_track::straight editor_mode::straight_near(glm::dvec3 const &Point, std::vector<TTrack const *> const &Exclude, double const Reach) const
|
||||||
|
{
|
||||||
|
TTrack *found{nullptr};
|
||||||
|
auto best{Reach};
|
||||||
|
for (auto *section : simulation::Region->sections(Point, static_cast<float>(std::max(40.0, Reach))))
|
||||||
|
for (auto const &cell : section->m_cells)
|
||||||
|
for (auto *track : cell.m_paths)
|
||||||
|
{
|
||||||
|
if (track->eType != tt_Normal || track->m_editorremoved || track->m_paths.empty() || std::find(Exclude.begin(), Exclude.end(), track) != Exclude.end() || false == editor_track::is_straight(*track, m_straights.tolerance))
|
||||||
|
continue;
|
||||||
|
auto const &path{track->m_paths.front()};
|
||||||
|
auto const a{plan_of(path.points[segment_data::point::start])};
|
||||||
|
auto const segment{plan_of(path.points[segment_data::point::end]) - a};
|
||||||
|
auto const t{std::clamp(glm::dot(plan_of(Point) - a, segment) / std::max(1e-9, glm::dot(segment, segment)), 0.0, 1.0)};
|
||||||
|
auto const distance{glm::distance(plan_of(Point), a + segment * t)};
|
||||||
|
if (distance < best)
|
||||||
|
{
|
||||||
|
best = distance;
|
||||||
|
found = track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found != nullptr ? editor_track::find_straight(*found, m_straights.tolerance) : editor_track::straight{};
|
||||||
|
}
|
||||||
|
|
||||||
|
editor_track::straight editor_mode::straight_through(glm::dvec3 const &Point, glm::dvec2 const &Direction) const
|
||||||
|
{
|
||||||
|
for (auto *section : simulation::Region->sections(Point, 40.f))
|
||||||
|
for (auto const &cell : section->m_cells)
|
||||||
|
for (auto *track : cell.m_paths)
|
||||||
|
{
|
||||||
|
if (track->eType != tt_Normal || track->m_editorremoved || track->m_paths.empty() || false == editor_track::is_straight(*track, m_straights.tolerance))
|
||||||
|
continue;
|
||||||
|
auto const &path{track->m_paths.front()};
|
||||||
|
auto const a{plan_of(path.points[segment_data::point::start])};
|
||||||
|
auto const segment{plan_of(path.points[segment_data::point::end]) - a};
|
||||||
|
auto const length{glm::length(segment)};
|
||||||
|
if (length < 1e-6 || std::abs(cross(segment / length, Direction)) > 1e-3 || std::abs(cross(segment / length, plan_of(Point) - a)) > 0.02)
|
||||||
|
continue;
|
||||||
|
auto const t{glm::dot(plan_of(Point) - a, segment) / (length * length)};
|
||||||
|
if (t >= -1e-6 && t <= 1.0 + 1e-6)
|
||||||
|
return editor_track::find_straight(*track, m_straights.tolerance);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool editor_mode::plan_connection(connection::request &Request, connection::result &Result) const
|
||||||
|
{
|
||||||
|
auto const &tool{m_switch};
|
||||||
|
Result = {};
|
||||||
|
if (false == tool.placing || tool.curved || tool.connection < 0 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()) || tool.line.tracks.empty())
|
||||||
|
return false;
|
||||||
|
auto const &line{tool.line};
|
||||||
|
Request = {};
|
||||||
|
Request.type = static_cast<connection::kind>(tool.connection);
|
||||||
|
Request.from = connection::of(line);
|
||||||
|
Request.first = tool.templates[tool.armed];
|
||||||
|
auto const second{tool.connection_second >= 0 && tool.connection_second < static_cast<int>(tool.templates.size()) ? tool.connection_second : tool.armed};
|
||||||
|
Request.second = tool.templates[second];
|
||||||
|
Request.radius = tool.connection_radius;
|
||||||
|
Request.gap = tool.connection_gap;
|
||||||
|
Request.together = tool.connection_together;
|
||||||
|
Request.stub = tool.connection_stub;
|
||||||
|
Request.spacings = spacings_of(tool.connection_spacings);
|
||||||
|
Request.along = tool.along;
|
||||||
|
glm::dvec2 const offset{plan_of(tool.mouse - tool.point)};
|
||||||
|
Request.forward = glm::dot(offset, line.direction) >= 0.0 ? 1 : -1;
|
||||||
|
glm::dvec2 const travel{line.direction * static_cast<double>(Request.forward)};
|
||||||
|
Request.side = cross(travel, offset) >= 0.0 ? 1 : -1;
|
||||||
|
std::vector<TTrack const *> const exclude(line.tracks.begin(), line.tracks.end());
|
||||||
|
auto const other{straight_near(tool.mouse, exclude, 8.0)};
|
||||||
|
if (false == other.tracks.empty())
|
||||||
|
{
|
||||||
|
auto const target{connection::of(other)};
|
||||||
|
if (Request.type != connection::kind::ladder)
|
||||||
|
{
|
||||||
|
Request.to.push_back(target);
|
||||||
|
Request.target = target.along(tool.mouse);
|
||||||
|
}
|
||||||
|
else if (std::abs(cross(target.direction, line.direction)) < 1e-3)
|
||||||
|
{
|
||||||
|
auto const side{cross(travel, plan_of(target.point(target.along(tool.point)) - tool.point)) > 0.0 ? 1.0 : -1.0};
|
||||||
|
auto const lateral = [&](glm::dvec3 const &Point) { return side * cross(travel, plan_of(Point - tool.point)); };
|
||||||
|
auto const outermost{lateral(target.point(target.along(tool.point)))};
|
||||||
|
std::map<long, std::pair<double, TTrack *>> fan;
|
||||||
|
auto const better = [](double const One, double const Two) { return (One < 0.0) != (Two < 0.0) ? One >= 0.0 : std::abs(One) < std::abs(Two); };
|
||||||
|
auto const centre{(tool.point + tool.mouse) * 0.5};
|
||||||
|
for (auto *section : simulation::Region->sections(centre, static_cast<float>(glm::length(offset) * 0.5 + 200.0)))
|
||||||
|
for (auto const &cell : section->m_cells)
|
||||||
|
for (auto *track : cell.m_paths)
|
||||||
|
{
|
||||||
|
if (track->eType != tt_Normal || track->m_editorremoved || track->m_paths.empty() || std::find(exclude.begin(), exclude.end(), track) != exclude.end() || false == editor_track::is_straight(*track, m_straights.tolerance))
|
||||||
|
continue;
|
||||||
|
auto const &path{track->m_paths.front()};
|
||||||
|
auto const a{path.points[segment_data::point::start]};
|
||||||
|
auto const b{path.points[segment_data::point::end]};
|
||||||
|
auto const direction{plan_of(b - a)};
|
||||||
|
if (glm::length(direction) < 1e-6 || std::abs(cross(glm::normalize(direction), line.direction)) > 1e-3)
|
||||||
|
continue;
|
||||||
|
auto const distance{lateral(a)};
|
||||||
|
auto const along1{glm::dot(travel, plan_of(a - tool.point))};
|
||||||
|
auto const along2{glm::dot(travel, plan_of(b - tool.point))};
|
||||||
|
if (distance < 1.0 || distance > outermost + 0.05 || std::max(along1, along2) <= 0.0)
|
||||||
|
continue;
|
||||||
|
auto const nearer{std::min(along1, along2)};
|
||||||
|
auto &entry{fan[std::lround(distance * 20.0)]};
|
||||||
|
if (entry.second == nullptr || better(nearer, entry.first))
|
||||||
|
entry = {nearer, track};
|
||||||
|
}
|
||||||
|
for (auto const &entry : fan)
|
||||||
|
Request.to.push_back(connection::of(editor_track::find_straight(*entry.second.second, m_straights.tolerance)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result = connection::plan(Request);
|
||||||
|
return Result.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string editor_mode::connection_readout(connection::result const &Plan) const
|
||||||
|
{
|
||||||
|
if (false == Plan.valid)
|
||||||
|
return Plan.error;
|
||||||
|
auto text{format(STR_C("%s: %d switches, spacing %.2f m"), connection_name(m_switch.connection), static_cast<int>(Plan.switches.size()), Plan.spacing)};
|
||||||
|
if (false == Plan.straights.empty())
|
||||||
|
{
|
||||||
|
text += STR_C(", straight inserts");
|
||||||
|
for (auto const length : Plan.straights)
|
||||||
|
text += format(" %.2f", length);
|
||||||
|
text += " m";
|
||||||
|
}
|
||||||
|
if (Plan.points > 0.0)
|
||||||
|
text += format(STR_C(", mathematical points %.2f m apart"), Plan.points);
|
||||||
|
if (Plan.radius > 0.0)
|
||||||
|
text += format(STR_C(", aligning curve R %.0f m"), Plan.radius);
|
||||||
|
if (Plan.crossing && Plan.angle > 1e-6)
|
||||||
|
text += format(STR_C(", crossing 1:%.2f"), 1.0 / std::tan(Plan.angle));
|
||||||
|
if (false == Plan.fan.empty())
|
||||||
|
{
|
||||||
|
text += STR_C(", tracks at");
|
||||||
|
for (auto const offset : Plan.fan)
|
||||||
|
text += format(" %.2f", offset);
|
||||||
|
text += " m";
|
||||||
|
}
|
||||||
|
if (false == Plan.warning.empty())
|
||||||
|
text += "\n" + Plan.warning;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::insert_connection()
|
||||||
|
{
|
||||||
|
auto &tool{m_switch};
|
||||||
|
connection::request request;
|
||||||
|
connection::result plan;
|
||||||
|
auto const planned{plan_connection(request, plan)};
|
||||||
|
tool.placing = false;
|
||||||
|
if (false == planned)
|
||||||
|
{
|
||||||
|
tool.status = plan.error.empty() ? std::string{STR_C("Drag onto the other track")} : plan.error;
|
||||||
|
ui()->set_status(tool.status, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||||
|
std::vector<TTrack *> created;
|
||||||
|
std::vector<TTrack *> removed;
|
||||||
|
auto *style{tool.line.tracks.front()};
|
||||||
|
auto const fail = [&](std::string const &Reason) {
|
||||||
|
for (auto *track : created)
|
||||||
|
editor_track::retire(*track);
|
||||||
|
for (auto entry{states.rbegin()}; entry != states.rend(); ++entry)
|
||||||
|
{
|
||||||
|
editor_track::apply(*entry->first, entry->second);
|
||||||
|
editor_track::commit({entry->first});
|
||||||
|
}
|
||||||
|
for (auto *track : removed)
|
||||||
|
{
|
||||||
|
track->m_editorremoved = false;
|
||||||
|
editor_track::commit({track});
|
||||||
|
}
|
||||||
|
if (auto before{switch_store::take_changes()})
|
||||||
|
switch_store::restore(std::move(*before));
|
||||||
|
if (auto before{alignment_store::take_changes()})
|
||||||
|
alignment_store::restore(std::move(*before));
|
||||||
|
straight_refresh();
|
||||||
|
tool.status = Reason;
|
||||||
|
ui()->set_status(Reason, true);
|
||||||
|
};
|
||||||
|
std::vector<TTrack *> switches;
|
||||||
|
for (auto const &entry : plan.switches)
|
||||||
|
{
|
||||||
|
auto const &shape{entry.second ? request.second : request.first};
|
||||||
|
auto const name{switch_name_for_new()};
|
||||||
|
auto const before{created.size()};
|
||||||
|
auto const start{entry.paths[0].points[segment_data::point::start]};
|
||||||
|
auto const end{entry.paths[0].points[segment_data::point::end]};
|
||||||
|
if (entry.line >= 0)
|
||||||
|
{
|
||||||
|
auto const &target{entry.line == 0 ? request.from : request.to[entry.line - 1]};
|
||||||
|
auto const found{straight_through((start + end) * 0.5, target.direction)};
|
||||||
|
if (found.tracks.empty())
|
||||||
|
{
|
||||||
|
fail(STR_C("A switch of the connection goes where its track isn't a plain straight"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const forward{glm::dot(found.direction, plan_of(end - start)) > 0.0 ? 1 : -1};
|
||||||
|
if (false == place_switch_on_straight(found, shape, nullptr, found.along(start), forward, entry.side, false, states, created, removed, name))
|
||||||
|
{
|
||||||
|
fail(tool.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const made{create_switch_set(shape, entry.paths, *style, name)};
|
||||||
|
if (made.empty())
|
||||||
|
{
|
||||||
|
fail(tool.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
created.insert(created.end(), made.begin(), made.end());
|
||||||
|
}
|
||||||
|
auto const made{std::find_if(created.begin() + static_cast<std::ptrdiff_t>(before), created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; })};
|
||||||
|
if (made == created.end() || glm::distance((*made)->m_paths.front().points[segment_data::point::start], start) > 0.01)
|
||||||
|
{
|
||||||
|
fail(STR_C("A switch of the connection doesn't fit where it was planned, its straight is too short"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switches.push_back(*made);
|
||||||
|
advance_switch_name();
|
||||||
|
}
|
||||||
|
std::vector<TTrack *> inserts;
|
||||||
|
for (auto const &insert : plan.inserts)
|
||||||
|
if (auto *track{editor_track::create_path(*style, insert)})
|
||||||
|
inserts.push_back(track);
|
||||||
|
editor_track::commit(inserts);
|
||||||
|
digitize_drawn(inserts);
|
||||||
|
created.insert(created.end(), inserts.begin(), inserts.end());
|
||||||
|
if (plan.crossing)
|
||||||
|
{
|
||||||
|
auto const first{glm::normalize(plan_of(plan.ends[1] - plan.ends[0]))};
|
||||||
|
auto second{glm::normalize(plan_of(plan.ends[3] - plan.ends[2]))};
|
||||||
|
if (glm::dot(first, second) < 0.0)
|
||||||
|
second = -second;
|
||||||
|
auto const parts{build_double_slip(plan.ends[0], plan.ends[1], plan.ends[2], plan.ends[3], plan_of(plan.centre), plan.centre.y, first, second, plan.angle, 0.0, *style, "Skrz")};
|
||||||
|
editor_track::commit(parts);
|
||||||
|
created.insert(created.end(), parts.begin(), parts.end());
|
||||||
|
}
|
||||||
|
record_connection(request.type, switches, plan, request.first);
|
||||||
|
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
|
||||||
|
straight_refresh();
|
||||||
|
first_step_done(first_step::turnout);
|
||||||
|
m_track_point = {};
|
||||||
|
m_node = switches.front();
|
||||||
|
ui()->set_node(m_node);
|
||||||
|
std::string names;
|
||||||
|
for (auto *track : switches)
|
||||||
|
{
|
||||||
|
if (place_switch_drive(*track))
|
||||||
|
join_last_steps();
|
||||||
|
names += (names.empty() ? "" : ", ") + track->name();
|
||||||
|
}
|
||||||
|
tool.status = format(STR_C("%s laid, switches %s"), connection_name(tool.connection), names.c_str()) + "\n" + connection_readout(plan);
|
||||||
|
ui()->set_status(tool.status, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::render_connection_ui()
|
||||||
|
{
|
||||||
|
auto &tool{m_switch};
|
||||||
|
ImGui::PushItemWidth(220.0f);
|
||||||
|
if (ImGui::BeginCombo(STR_C("Connection##switchconnection"), tool.connection < 0 ? STR_C("Single switch") : connection_name(tool.connection)))
|
||||||
|
{
|
||||||
|
if (ImGui::Selectable(STR_C("Single switch"), tool.connection < 0))
|
||||||
|
tool.connection = -1;
|
||||||
|
for (int i = 0; i < connection::kind_count; ++i)
|
||||||
|
if (ImGui::Selectable(connection_name(i), tool.connection == i))
|
||||||
|
tool.connection = i;
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
item_tooltip("A connection of tracks laid whole: press on a straight and drag onto the other track. Its switches are cut into the tracks with the inserts between them and get their drives; Shift+wheel while dragging: another kind, Ctrl+wheel: R of the aligning curve");
|
||||||
|
if (tool.connection < 0)
|
||||||
|
{
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const label = [&](int const Index) { return Index < 0 || Index >= static_cast<int>(tool.templates.size()) ? std::string{STR_C("The same as the first one")} : switch_label(tool.templates[Index]); };
|
||||||
|
if (ImGui::BeginCombo(STR_C("Other switches##connectionsecond"), label(tool.connection_second).c_str()))
|
||||||
|
{
|
||||||
|
if (ImGui::Selectable(STR_C("The same as the first one"), tool.connection_second < 0))
|
||||||
|
tool.connection_second = -1;
|
||||||
|
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||||
|
if (usable_in_connection(tool.templates[i]) && ImGui::Selectable((switch_label(tool.templates[i]) + "##connectionsecond" + std::to_string(i)).c_str(), tool.connection_second == i))
|
||||||
|
tool.connection_second = i;
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
item_tooltip("Template of the switches on the other track and along a ladder; the first switch is the one chosen on the list above");
|
||||||
|
ImGui::InputDouble(STR_C("Aligning curve R, m##connectionradius"), &tool.connection_radius, 10.0, 100.0, "%.0f");
|
||||||
|
tool.connection_radius = std::clamp(tool.connection_radius, 0.0, 10000.0);
|
||||||
|
item_tooltip("Of the curve between switches of different angles or on tracks that aren't parallel, and at the end of a ladder; 0: the radius of the sharper switch");
|
||||||
|
if (tool.connection == static_cast<int>(connection::kind::double_crossover))
|
||||||
|
{
|
||||||
|
ImGui::InputDouble(STR_C("Gap between the switches, m##connectiongap"), &tool.connection_gap, 0.5, 1.0, "%.2f");
|
||||||
|
tool.connection_gap = std::clamp(tool.connection_gap, 0.0, 100.0);
|
||||||
|
item_tooltip("Straight between the starts of the two switches meeting on one track");
|
||||||
|
ImGui::Checkbox(STR_C("The switches meet on this track##connectiontogether"), &tool.connection_together);
|
||||||
|
item_tooltip("Otherwise the two crossovers meet on the other track; Alt+wheel while dragging switches it");
|
||||||
|
}
|
||||||
|
if (tool.connection == static_cast<int>(connection::kind::ladder))
|
||||||
|
{
|
||||||
|
ImGui::InputText(STR_C("Spacings, m##connectionspacings"), tool.connection_spacings, sizeof(tool.connection_spacings));
|
||||||
|
item_tooltip("Distances between the tracks of the fan from this track on, such as 4.75;4.75;5.0; used when the drag doesn't end on a parallel track, otherwise the parallel tracks up to that one make the fan");
|
||||||
|
ImGui::InputDouble(STR_C("New tracks, m##connectionstub"), &tool.connection_stub, 5.0, 25.0, "%.0f");
|
||||||
|
tool.connection_stub = std::clamp(tool.connection_stub, 1.0, 1000.0);
|
||||||
|
item_tooltip("Length of the new tracks of the fan where there are none to lead onto");
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
}
|
||||||
@@ -264,6 +264,88 @@ void editor_mode::record_switch(TTrack &Track, editor_track::switch_template con
|
|||||||
switch_store::put(entry, alignment_store::layer_of(Track));
|
switch_store::put(entry, alignment_store::layer_of(Track));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a double switch is kept as one record of its two switches, with the type, the size and the side of the first one
|
||||||
|
void editor_mode::record_double(TTrack &First, TTrack &Second, editor_track::switch_template const &Shape)
|
||||||
|
{
|
||||||
|
if (First.eType != tt_Switch || Second.eType != tt_Switch || First.m_paths.size() < 2 || First.name().empty() || Second.name().empty() || scene::Layers.empty())
|
||||||
|
return;
|
||||||
|
auto entry{switch_entry(Shape)};
|
||||||
|
entry.kind = "double";
|
||||||
|
entry.name = First.name();
|
||||||
|
entry.parts = {First.name(), Second.name()};
|
||||||
|
switch_store::place(entry, First);
|
||||||
|
switch_store::classify(entry, First);
|
||||||
|
switch_store::put(entry, alignment_store::layer_of(First));
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::record_connection(connection::kind const Kind, std::vector<TTrack *> const &Switches, connection::result const &Plan, editor_track::switch_template const &Shape)
|
||||||
|
{
|
||||||
|
if (Switches.empty() || Switches.front()->eType != tt_Switch || Switches.front()->m_paths.size() < 2 || scene::Layers.empty())
|
||||||
|
return;
|
||||||
|
auto entry{switch_entry(Shape)};
|
||||||
|
entry.kind = "connection";
|
||||||
|
entry.name = Switches.front()->name();
|
||||||
|
for (auto const *track : Switches)
|
||||||
|
if (track->name().empty())
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
entry.parts.push_back(track->name());
|
||||||
|
switch_store::place(entry, *Switches.front());
|
||||||
|
switch_store::classify(entry, *Switches.front());
|
||||||
|
entry.type = connection::word(Kind);
|
||||||
|
entry.a = Plan.spacing;
|
||||||
|
entry.b = Plan.straights.empty() ? 0.0 : Plan.straights.front();
|
||||||
|
switch_store::put(entry, alignment_store::layer_of(*Switches.front()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<editor_mode::double_switch> editor_mode::double_of(TTrack const &Track) const
|
||||||
|
{
|
||||||
|
auto const of_switch = [](TTrack const &Switch) -> std::optional<double_switch> {
|
||||||
|
if (Switch.eType != tt_Switch || Switch.m_editorremoved)
|
||||||
|
return std::nullopt;
|
||||||
|
auto const known{switch_store::named(Switch)};
|
||||||
|
if (false == known.has_value() || known->kind != "double" || known->parts.size() != 2)
|
||||||
|
return std::nullopt;
|
||||||
|
auto *first{simulation::Paths.find(known->parts[0])};
|
||||||
|
auto *second{simulation::Paths.find(known->parts[1])};
|
||||||
|
for (auto const *part : {first, second})
|
||||||
|
if (part == nullptr || part->m_editorremoved || part->eType != tt_Switch || part->m_paths.size() != 2)
|
||||||
|
return std::nullopt;
|
||||||
|
if (first == second)
|
||||||
|
return std::nullopt;
|
||||||
|
auto const start{second->m_paths.front().points[segment_data::point::start]};
|
||||||
|
for (auto const &[other, point] : editor_track::connected_points(*first, first->m_paths.front().points[segment_data::point::end]))
|
||||||
|
{
|
||||||
|
if (other->m_editorremoved || other->eType != tt_Normal || other->m_paths.size() != 1)
|
||||||
|
continue;
|
||||||
|
auto const &path{other->m_paths.front()};
|
||||||
|
auto const opposite{path.points[point.kind == editor_track::point_kind::start ? segment_data::point::end : segment_data::point::start]};
|
||||||
|
if (glm::distance(opposite, start) < geometry::same_point)
|
||||||
|
return double_switch{first, other, second};
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
if (Track.eType == tt_Switch)
|
||||||
|
return of_switch(Track);
|
||||||
|
if (Track.eType != tt_Normal || Track.m_editorremoved || Track.m_paths.size() != 1)
|
||||||
|
return std::nullopt;
|
||||||
|
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||||
|
for (auto const &[other, point] : editor_track::connected_points(Track, Track.m_paths.front().points[index]))
|
||||||
|
if (other->eType == tt_Switch)
|
||||||
|
if (auto const found{of_switch(*other)}; found.has_value() && found->link == &Track)
|
||||||
|
return found;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<segment_data> editor_mode::double_paths(double_switch const &Set)
|
||||||
|
{
|
||||||
|
auto link{Set.link->m_paths.front()};
|
||||||
|
auto const joint{Set.first->m_paths.front().points[segment_data::point::end]};
|
||||||
|
if (glm::distance(link.points[segment_data::point::start], joint) > glm::distance(link.points[segment_data::point::end], joint))
|
||||||
|
link = geometry::reversed(link);
|
||||||
|
return {Set.first->m_paths[0], Set.first->m_paths[1], Set.second->m_paths[0], Set.second->m_paths[1], link};
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
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())
|
if (Parts.empty() || scene::Layers.empty())
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
|
|||||||
float extent{0.0f};
|
float extent{0.0f};
|
||||||
words >> extent;
|
words >> extent;
|
||||||
choose_work_area(work_area::tracks);
|
choose_work_area(work_area::tracks);
|
||||||
if (false == Global.EditorOrtho)
|
if (Global.EditorOrtho == extent < 0.0f)
|
||||||
toggle_ortho();
|
toggle_ortho();
|
||||||
if (extent > 0.0f)
|
if (extent > 0.0f)
|
||||||
Global.EditorOrthoExtent = std::clamp(extent, 5.0f, 50000.0f);
|
Global.EditorOrthoExtent = std::clamp(extent, 5.0f, 50000.0f);
|
||||||
@@ -1318,6 +1318,166 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
|
|||||||
WriteLog("SELFTEST curvedswitch " + (tool.armed >= 0 ? tool.templates[tool.armed].label : std::string{}) + ": " + tool.status);
|
WriteLog("SELFTEST curvedswitch " + (tool.armed >= 0 ? tool.templates[tool.armed].label : std::string{}) + ": " + tool.status);
|
||||||
return selftest_result::handled;
|
return selftest_result::handled;
|
||||||
}
|
}
|
||||||
|
if (command == "designer")
|
||||||
|
{
|
||||||
|
std::string list;
|
||||||
|
int index{0};
|
||||||
|
words >> list >> index;
|
||||||
|
auto &designer{m_switch_designer};
|
||||||
|
designer.open = list != "close";
|
||||||
|
designer.from_catalogue = list != "own";
|
||||||
|
designer.selected = index;
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
if (command == "release")
|
||||||
|
{
|
||||||
|
m_switch.placing = false;
|
||||||
|
lay_cancel();
|
||||||
|
m_cursor_override.reset();
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
if (command == "hover")
|
||||||
|
{
|
||||||
|
glm::dvec3 at{0.0};
|
||||||
|
if (words >> at.x >> at.z)
|
||||||
|
m_cursor_override = at;
|
||||||
|
else
|
||||||
|
m_cursor_override.reset();
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
if (command == "layhold")
|
||||||
|
{
|
||||||
|
int layout{0};
|
||||||
|
words >> layout >> m_lay.radius;
|
||||||
|
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||||
|
m_lay.active = true;
|
||||||
|
lay_cancel();
|
||||||
|
std::vector<glm::dvec3> points;
|
||||||
|
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||||
|
points.push_back(point);
|
||||||
|
for (std::size_t i = 0; i + 1 < points.size(); ++i)
|
||||||
|
{
|
||||||
|
auto point{points[i]};
|
||||||
|
point.y = 5000.0;
|
||||||
|
std::vector<char> found;
|
||||||
|
auto const ground{ground_heights({point}, true, &found)};
|
||||||
|
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||||
|
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
|
||||||
|
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
|
||||||
|
lay_click();
|
||||||
|
}
|
||||||
|
if (false == points.empty())
|
||||||
|
m_cursor_override = points.back();
|
||||||
|
WriteLog("SELFTEST layhold: " + m_lay.status);
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
if (command == "connection" || command == "connectionplan" || command == "connectionhold")
|
||||||
|
{
|
||||||
|
std::string kindword;
|
||||||
|
std::string label;
|
||||||
|
std::string second;
|
||||||
|
glm::dvec3 at{0.0};
|
||||||
|
glm::dvec3 toward{0.0};
|
||||||
|
words >> kindword >> at.x >> at.z >> toward.x >> toward.z >> label >> second;
|
||||||
|
connection::kind kind{connection::kind::crossover};
|
||||||
|
if (kindword != "single" && false == connection::parse(kindword, kind))
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST connection: unknown kind " + kindword);
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
auto &tool{m_switch};
|
||||||
|
for (std::string key; words >> key;)
|
||||||
|
{
|
||||||
|
if (key == "radius")
|
||||||
|
words >> tool.connection_radius;
|
||||||
|
else if (key == "gap")
|
||||||
|
words >> tool.connection_gap;
|
||||||
|
else if (key == "together")
|
||||||
|
words >> tool.connection_together;
|
||||||
|
else if (key == "stub")
|
||||||
|
words >> tool.connection_stub;
|
||||||
|
else if (key == "spacings")
|
||||||
|
{
|
||||||
|
std::string value;
|
||||||
|
words >> value;
|
||||||
|
std::snprintf(tool.connection_spacings, sizeof(tool.connection_spacings), "%s", value.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show_track_tab(track_tab::turnout);
|
||||||
|
arm_switch();
|
||||||
|
auto const find = [&](std::string Label) {
|
||||||
|
std::replace(Label.begin(), Label.end(), '_', ' ');
|
||||||
|
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||||
|
if (false == Label.empty() && Label != "-" && false == tool.templates[i].double_slip && false == tool.templates[i].twofold() && tool.templates[i].label.find(Label) != std::string::npos)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
auto const first{find(label)};
|
||||||
|
if (first < 0)
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST connection: no template " + label);
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
tool.armed = first;
|
||||||
|
tool.connection_second = find(second);
|
||||||
|
tool.connection = kindword == "single" ? -1 : static_cast<int>(kind);
|
||||||
|
auto const line{straight_near(at, {}, 3.0)};
|
||||||
|
if (line.tracks.empty())
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST connection: no straight at the start");
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
m_node = line.tracks.front();
|
||||||
|
ui()->set_node(m_node);
|
||||||
|
m_cursor_override = at;
|
||||||
|
auto const started{start_switch_placement()};
|
||||||
|
m_cursor_override.reset();
|
||||||
|
if (false == started)
|
||||||
|
{
|
||||||
|
WriteLog("SELFTEST connection: not started, " + tool.status);
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
tool.mouse = glm::dvec3{toward.x, tool.point.y, toward.z};
|
||||||
|
if (command == "connectionhold")
|
||||||
|
{
|
||||||
|
m_cursor_override = toward;
|
||||||
|
WriteLog("SELFTEST connectionhold: " + tool.status);
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
connection::request request;
|
||||||
|
connection::result plan;
|
||||||
|
auto const valid{plan_connection(request, plan)};
|
||||||
|
WriteLog("SELFTEST connection " + kindword + ": " + (valid ? connection_readout(plan) : "invalid, " + plan.error));
|
||||||
|
for (auto const &entry : plan.switches)
|
||||||
|
{
|
||||||
|
auto const &start{entry.paths[0].points[segment_data::point::start]};
|
||||||
|
auto const &end{entry.paths[1].points[segment_data::point::end]};
|
||||||
|
WriteLog(format("SELFTEST connection switch on %d at %.3f, forward %d side %d%s: %.3f %.3f %.3f -> branch end %.3f %.3f %.3f", entry.line, entry.along, entry.forward, entry.side, entry.second ? " (second)" : "", start.x, start.y, start.z, end.x, end.y,
|
||||||
|
end.z));
|
||||||
|
}
|
||||||
|
for (auto const &insert : plan.inserts)
|
||||||
|
{
|
||||||
|
auto const &start{insert.points[segment_data::point::start]};
|
||||||
|
auto const &end{insert.points[segment_data::point::end]};
|
||||||
|
WriteLog(format("SELFTEST connection insert %.3f %.3f %.3f -> %.3f %.3f %.3f, %.3f m, R %.1f", start.x, start.y, start.z, end.x, end.y, end.z, geometry::bezier{insert}.plan_length(), std::abs(insert.radius)));
|
||||||
|
}
|
||||||
|
if (command == "connectionplan" || false == valid)
|
||||||
|
{
|
||||||
|
tool.placing = false;
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
insert_connection();
|
||||||
|
WriteLog("SELFTEST connection built: " + tool.status);
|
||||||
|
for (auto const &entry : switch_store::records())
|
||||||
|
if (entry.kind == "connection")
|
||||||
|
{
|
||||||
|
std::string parts;
|
||||||
|
for (auto const &part : entry.parts)
|
||||||
|
parts += ' ' + part;
|
||||||
|
WriteLog(format("SELFTEST connection record %s %s: spacing %.3f, insert %.3f, parts%s", entry.type.c_str(), entry.name.c_str(), entry.a, entry.b, parts.c_str()));
|
||||||
|
}
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
if (command == "conecheck")
|
if (command == "conecheck")
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -1626,7 +1786,9 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
|
|||||||
double low{0.0};
|
double low{0.0};
|
||||||
double high{0.0};
|
double high{0.0};
|
||||||
std::string reason;
|
std::string reason;
|
||||||
auto const room{switch_room(*track, low, high, reason)};
|
auto const set{double_of(*track)};
|
||||||
|
double length{0.0};
|
||||||
|
auto const room{set.has_value() ? double_room(*set, low, high, length, reason) : switch_room(*track, low, high, reason)};
|
||||||
auto const done{slide_switch(*track, distance)};
|
auto const done{slide_switch(*track, distance)};
|
||||||
WriteLog(format("SELFTEST switchslide %s %.3f: room %d %.3f .. %.3f, done %d, %s", name.c_str(), distance, room ? 1 : 0, low, high, done ? 1 : 0, m_switch.status.c_str()));
|
WriteLog(format("SELFTEST switchslide %s %.3f: room %d %.3f .. %.3f, done %d, %s", name.c_str(), distance, room ? 1 : 0, low, high, done ? 1 : 0, m_switch.status.c_str()));
|
||||||
return selftest_result::handled;
|
return selftest_result::handled;
|
||||||
@@ -1642,14 +1804,24 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
|
|||||||
WriteLog("SELFTEST switchreplace: no template " + label);
|
WriteLog("SELFTEST switchreplace: no template " + label);
|
||||||
return selftest_result::handled;
|
return selftest_result::handled;
|
||||||
}
|
}
|
||||||
|
auto const set{double_of(*track)};
|
||||||
|
if (set.has_value())
|
||||||
|
track = set->first;
|
||||||
auto const &main{track->m_paths[0]};
|
auto const &main{track->m_paths[0]};
|
||||||
auto const origin{main.points[segment_data::point::start]};
|
auto const origin{main.points[segment_data::point::start]};
|
||||||
auto const chord{geometry::plan_of(main.points[segment_data::point::end] - origin)};
|
auto const chord{geometry::plan_of(main.points[segment_data::point::end] - origin)};
|
||||||
auto const length{glm::length(chord)};
|
auto const length{glm::length(chord)};
|
||||||
auto paths{editor_track::place_switch(*found, origin, chord / length, side, (main.points[segment_data::point::end].y - origin.y) / length)};
|
auto paths{editor_track::place_switch(*found, origin, chord / length, side, (main.points[segment_data::point::end].y - origin.y) / length)};
|
||||||
std::string status;
|
std::string status;
|
||||||
auto const done{paths.size() >= 2 && move_switch_paths(*track, paths, status)};
|
auto const done{set.has_value() ? found->twofold() && paths.size() >= 5 && move_double_paths(*set, paths, status) : paths.size() >= 2 && move_switch_paths(*track, paths, status)};
|
||||||
if (done)
|
if (done && set.has_value())
|
||||||
|
{
|
||||||
|
record_double(*set->first, *set->second, *found);
|
||||||
|
switch_store::follow();
|
||||||
|
records_to_history();
|
||||||
|
straight_refresh();
|
||||||
|
}
|
||||||
|
else if (done)
|
||||||
{
|
{
|
||||||
record_switch(*track, *found);
|
record_switch(*track, *found);
|
||||||
records_to_history();
|
records_to_history();
|
||||||
@@ -1720,6 +1892,61 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com
|
|||||||
}
|
}
|
||||||
return selftest_result::handled;
|
return selftest_result::handled;
|
||||||
}
|
}
|
||||||
|
if (command == "switchlinks")
|
||||||
|
{
|
||||||
|
auto const name_of = [](TTrack const *Track) { return Track == nullptr ? std::string{"-"} : Track->name().empty() ? std::string{"(noname)"} : Track->name(); };
|
||||||
|
for (auto *track : simulation::Paths.sequence())
|
||||||
|
{
|
||||||
|
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->SwitchExtension == nullptr)
|
||||||
|
continue;
|
||||||
|
auto const &extension{*track->SwitchExtension};
|
||||||
|
WriteLog(format("SELFTEST switchlinks %s: prev %s / %s, next %s / %s, lengths %.3f / %.3f m", track->name().c_str(), name_of(extension.pPrevs[0]).c_str(), name_of(extension.pPrevs[1]).c_str(), name_of(extension.pNexts[0]).c_str(), name_of(extension.pNexts[1]).c_str(),
|
||||||
|
extension.Segments[0]->GetLength(), extension.Segments[1]->GetLength()));
|
||||||
|
}
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
|
if (command == "switchtemplates")
|
||||||
|
{
|
||||||
|
for (auto const &entry : editor_track::standard_switch_templates())
|
||||||
|
{
|
||||||
|
if (entry.double_slip)
|
||||||
|
continue;
|
||||||
|
auto const paths{editor_track::place_switch(entry, glm::dvec3{0.0}, glm::dvec2{0.0, 1.0}, 1, 0.0)};
|
||||||
|
double gap{0.0};
|
||||||
|
double kink{0.0};
|
||||||
|
std::size_t pieces{0};
|
||||||
|
for (auto const &path : paths)
|
||||||
|
{
|
||||||
|
pieces += path.pieces.size();
|
||||||
|
for (std::size_t i = 0; i + 1 < path.pieces.size(); ++i)
|
||||||
|
{
|
||||||
|
auto const &one{path.pieces[i]};
|
||||||
|
auto const &two{path.pieces[i + 1]};
|
||||||
|
gap = std::max(gap, glm::distance(one.points[segment_data::point::end], two.points[segment_data::point::start]));
|
||||||
|
geometry::bezier const a{one};
|
||||||
|
geometry::bezier const b{two};
|
||||||
|
auto const ta{glm::normalize(geometry::plan_of(a.first(1.0)))};
|
||||||
|
auto const tb{glm::normalize(geometry::plan_of(b.first(0.0)))};
|
||||||
|
kink = std::max(kink, std::abs(geometry::signed_angle(ta, tb)));
|
||||||
|
}
|
||||||
|
if (false == path.pieces.empty())
|
||||||
|
gap = std::max({gap, glm::distance(path.pieces.front().points[segment_data::point::start], path.points[segment_data::point::start]), glm::distance(path.pieces.back().points[segment_data::point::end], path.points[segment_data::point::end])});
|
||||||
|
}
|
||||||
|
std::string ends;
|
||||||
|
for (auto const &path : paths)
|
||||||
|
ends += format(" (%.3f %.3f)", path.points[segment_data::point::end].x, path.points[segment_data::point::end].z);
|
||||||
|
std::string starts;
|
||||||
|
for (std::size_t i = 1; i < paths.size() && i <= 3; i += 2)
|
||||||
|
{
|
||||||
|
geometry::bezier const first{paths[i].pieces.empty() ? paths[i] : paths[i].pieces.front()};
|
||||||
|
auto const tangent{glm::normalize(geometry::plan_of(first.first(0.0)))};
|
||||||
|
starts += format(" %.4f", glm::degrees(std::abs(geometry::signed_angle(glm::dvec2{0.0, 1.0}, tangent))));
|
||||||
|
}
|
||||||
|
WriteLog(format("SELFTEST switchtemplate %s: %s%s, L %.3f 1:%.3f R %.1f a %.3f b %.3f stagger %.3f, %zu paths, %zu pieces, gap %.6f m, kink %.6f deg, start deg%s, ends%s", entry.label.c_str(), entry.type.c_str(), entry.own ? " own" : "", entry.length, entry.ratio, entry.radius, entry.a, entry.b, entry.stagger, paths.size(), pieces, gap,
|
||||||
|
glm::degrees(kink), starts.c_str(), ends.c_str()));
|
||||||
|
}
|
||||||
|
return selftest_result::handled;
|
||||||
|
}
|
||||||
if (command == "switches")
|
if (command == "switches")
|
||||||
{
|
{
|
||||||
for (auto const &entry : switch_store::records())
|
for (auto const &entry : switch_store::records())
|
||||||
|
|||||||
542
application/editormodeswitchdesign.cpp
Normal file
542
application/editormodeswitchdesign.cpp
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
/*
|
||||||
|
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 "application/editormode.h"
|
||||||
|
#include "application/editoruilayer.h"
|
||||||
|
#include "application/editormodeshared.h"
|
||||||
|
#include "editor/editorFormat.hpp"
|
||||||
|
#include "editor/editorGeometry.hpp"
|
||||||
|
#include "editor/editorSwitchDesign.hpp"
|
||||||
|
|
||||||
|
#include "utilities/translation.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class part_kind
|
||||||
|
{
|
||||||
|
straight,
|
||||||
|
arc,
|
||||||
|
transition
|
||||||
|
};
|
||||||
|
|
||||||
|
part_kind kind_of(switch_design::part const &Part)
|
||||||
|
{
|
||||||
|
if (Part.radius1 == 0.0 && Part.radius2 == 0.0)
|
||||||
|
return part_kind::straight;
|
||||||
|
return Part.radius1 == Part.radius2 ? part_kind::arc : part_kind::transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_kind(switch_design::part &Part, part_kind const Kind)
|
||||||
|
{
|
||||||
|
auto const radius{Part.radius2 != 0.0 ? Part.radius2 : Part.radius1 != 0.0 ? Part.radius1 : 300.0};
|
||||||
|
switch (Kind)
|
||||||
|
{
|
||||||
|
case part_kind::straight:
|
||||||
|
Part.radius1 = Part.radius2 = 0.0;
|
||||||
|
break;
|
||||||
|
case part_kind::arc:
|
||||||
|
Part.radius1 = Part.radius2 = radius;
|
||||||
|
break;
|
||||||
|
case part_kind::transition:
|
||||||
|
Part.radius1 = 0.0;
|
||||||
|
Part.radius2 = radius;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the parts of the diverging track as a table; true when something changed
|
||||||
|
bool edit_parts(std::vector<switch_design::part> &Parts, char const *Id, bool const Editable)
|
||||||
|
{
|
||||||
|
bool changed{false};
|
||||||
|
char const *kinds[] = {STR_C("Straight"), STR_C("Arc"), STR_C("Transition curve")};
|
||||||
|
ImGui::PushID(Id);
|
||||||
|
if (false == Editable)
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
int erase{-1};
|
||||||
|
int up{-1};
|
||||||
|
for (int i = 0; i < static_cast<int>(Parts.size()); ++i)
|
||||||
|
{
|
||||||
|
auto &part{Parts[i]};
|
||||||
|
ImGui::PushID(i);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%d.", i + 1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
auto kind{static_cast<int>(kind_of(part))};
|
||||||
|
ImGui::SetNextItemWidth(130.0f);
|
||||||
|
if (ImGui::Combo("##kind", &kind, kinds, 3))
|
||||||
|
{
|
||||||
|
set_kind(part, static_cast<part_kind>(kind));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("m##length"), &part.length, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("Length of the part, measured along it");
|
||||||
|
switch (static_cast<part_kind>(kind))
|
||||||
|
{
|
||||||
|
case part_kind::arc:
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
if (ImGui::InputDouble(STR_C("R##radius"), &part.radius1, 0.0, 0.0, "%.1f"))
|
||||||
|
{
|
||||||
|
part.radius2 = part.radius1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("Radius of the arc; negative turns back towards the main track");
|
||||||
|
if (part.radius1 != 0.0)
|
||||||
|
{
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextDisabled("%.3f deg", glm::degrees(part.length / part.radius1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case part_kind::transition:
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("R from##radius1"), &part.radius1, 0.0, 0.0, "%.1f");
|
||||||
|
item_tooltip("Radius at the start of the transition curve, 0: from a straight");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("to##radius2"), &part.radius2, 0.0, 0.0, "%.1f");
|
||||||
|
item_tooltip("Radius at the end of the transition curve, 0: into a straight; the curvature changes evenly along it");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("^") && i > 0)
|
||||||
|
up = i;
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("x"))
|
||||||
|
erase = i;
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
if (up > 0)
|
||||||
|
{
|
||||||
|
std::swap(Parts[up], Parts[up - 1]);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (erase >= 0)
|
||||||
|
{
|
||||||
|
Parts.erase(Parts.begin() + erase);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
auto const radius{Parts.empty() ? 300.0 : std::max({std::abs(Parts.back().radius1), std::abs(Parts.back().radius2), 0.0})};
|
||||||
|
if (ImGui::SmallButton(STR_C("+ Straight")))
|
||||||
|
{
|
||||||
|
Parts.push_back({5.0, 0.0, 0.0});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton(STR_C("+ Arc")))
|
||||||
|
{
|
||||||
|
auto const r{radius > 0.0 ? radius : 300.0};
|
||||||
|
Parts.push_back({20.0, r, r});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton(STR_C("+ Transition curve")))
|
||||||
|
{
|
||||||
|
Parts.push_back({10.0, 0.0, radius > 0.0 ? radius : 300.0});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (false == Editable)
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// plan of the switch in its own space, the main track along the width of the box
|
||||||
|
void draw_shape(switch_design::shape const &Shape, float const Height, bool const Stretched)
|
||||||
|
{
|
||||||
|
auto const width{ImGui::GetContentRegionAvail().x};
|
||||||
|
ImGui::BeginChild("##switchshape", ImVec2(0.0f, Height), true, ImGuiWindowFlags_NoScrollbar);
|
||||||
|
auto *drawlist{ImGui::GetWindowDrawList()};
|
||||||
|
auto const origin{ImGui::GetCursorScreenPos()};
|
||||||
|
if (Shape.paths.empty() || Shape.length <= 0.0)
|
||||||
|
{
|
||||||
|
ImGui::EndChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double reach{0.0};
|
||||||
|
double across{0.0};
|
||||||
|
for (auto const &path : Shape.paths)
|
||||||
|
{
|
||||||
|
reach = std::max(reach, path.points[segment_data::point::end].z);
|
||||||
|
across = std::max(across, std::abs(path.points[segment_data::point::end].x));
|
||||||
|
}
|
||||||
|
auto const margin{12.0f};
|
||||||
|
auto const scale{(width - margin * 2.0f) / static_cast<float>(std::max(1.0, reach))};
|
||||||
|
auto const stretch{Stretched ? std::min(8.0f, (Height * 0.5f - margin) / std::max(0.01f, static_cast<float>(across) * scale)) : 1.0f};
|
||||||
|
auto const middle{origin.y + Height * 0.5f};
|
||||||
|
auto const screen = [&](glm::dvec3 const &Point) { return ImVec2{origin.x + margin + static_cast<float>(Point.z) * scale, middle - static_cast<float>(Point.x) * scale * std::max(1.0f, stretch)}; };
|
||||||
|
ImU32 const colours[] = {IM_COL32(200, 200, 200, 255), IM_COL32(90, 200, 255, 255), IM_COL32(200, 200, 200, 255), IM_COL32(255, 170, 60, 255)};
|
||||||
|
for (std::size_t i = 0; i < Shape.paths.size(); ++i)
|
||||||
|
{
|
||||||
|
auto const &path{Shape.paths[i]};
|
||||||
|
std::vector<segment_data> pieces{path.pieces};
|
||||||
|
if (pieces.empty())
|
||||||
|
pieces.push_back(path);
|
||||||
|
for (auto const &piece : pieces)
|
||||||
|
{
|
||||||
|
geometry::bezier const curve{piece};
|
||||||
|
auto previous{screen(curve.point(0.0))};
|
||||||
|
for (int j = 1; j <= 24; ++j)
|
||||||
|
{
|
||||||
|
auto const next{screen(curve.point(j / 24.0))};
|
||||||
|
drawlist->AddLine(previous, next, colours[i % 4], 2.0f);
|
||||||
|
previous = next;
|
||||||
|
}
|
||||||
|
drawlist->AddCircleFilled(screen(piece.points[segment_data::point::end]), 3.0f, colours[i % 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawlist->AddCircleFilled(screen({0.0, 0.0, 0.0}), 4.0f, IM_COL32(255, 255, 255, 255));
|
||||||
|
if (Shape.stagger > 0.0)
|
||||||
|
drawlist->AddCircleFilled(screen({0.0, 0.0, Shape.stagger}), 4.0f, IM_COL32(255, 170, 60, 255));
|
||||||
|
for (auto const tip : {Shape.tip, Shape.stagger > 0.0 ? Shape.tip2 : 0.0})
|
||||||
|
if (tip > 0.0)
|
||||||
|
{
|
||||||
|
auto const at{screen({0.0, 0.0, tip})};
|
||||||
|
drawlist->AddTriangleFilled({at.x, at.y}, {at.x - 4.0f, at.y + 8.0f}, {at.x + 4.0f, at.y + 8.0f}, IM_COL32(120, 255, 120, 220));
|
||||||
|
}
|
||||||
|
if (Shape.a > 0.0)
|
||||||
|
{
|
||||||
|
auto const at{screen({0.0, 0.0, Shape.a})};
|
||||||
|
drawlist->AddLine({at.x, at.y - 6.0f}, {at.x, at.y + 6.0f}, IM_COL32(255, 255, 255, 160), 1.0f);
|
||||||
|
}
|
||||||
|
if (Stretched && stretch > 1.01f)
|
||||||
|
drawlist->AddText({origin.x + 4.0f, origin.y + 2.0f}, IM_COL32(255, 255, 255, 120), format(STR_C("across x%.1f"), stretch).c_str());
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void editor_mode::switch_designs_load()
|
||||||
|
{
|
||||||
|
auto &designer{m_switch_designer};
|
||||||
|
if (designer.loaded)
|
||||||
|
return;
|
||||||
|
designer.loaded = true;
|
||||||
|
designer.catalogue = switch_design::catalogue();
|
||||||
|
designer.own = switch_design::own();
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::switch_templates_refresh(std::string const &Arm)
|
||||||
|
{
|
||||||
|
auto &tool{m_switch};
|
||||||
|
std::vector<editor_track::switch_template> scenery;
|
||||||
|
for (auto const &entry : tool.templates)
|
||||||
|
if (entry.source != nullptr)
|
||||||
|
scenery.push_back(entry);
|
||||||
|
auto const armed{false == Arm.empty() ? Arm : tool.armed >= 0 && tool.armed < static_cast<int>(tool.templates.size()) ? tool.templates[tool.armed].label : std::string{}};
|
||||||
|
tool.templates = editor_track::standard_switch_templates();
|
||||||
|
tool.templates.insert(tool.templates.end(), scenery.begin(), scenery.end());
|
||||||
|
tool.collected = true;
|
||||||
|
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||||
|
if (false == armed.empty() && tool.templates[i].label.rfind(armed, 0) == 0)
|
||||||
|
{
|
||||||
|
tool.last = i;
|
||||||
|
if (tool.armed >= 0)
|
||||||
|
tool.armed = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_turnout_template = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void editor_mode::render_switch_designer()
|
||||||
|
{
|
||||||
|
auto &designer{m_switch_designer};
|
||||||
|
if (false == designer.open)
|
||||||
|
return;
|
||||||
|
switch_designs_load();
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(820.0f, 560.0f), ImGuiCond_FirstUseEver);
|
||||||
|
if (false == ImGui::Begin((std::string(STR_C("Switch templates")) + "###switchdesigner").c_str(), &designer.open, ImGuiWindowFlags_NoCollapse))
|
||||||
|
{
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::BeginChild("##designlist", ImVec2(230.0f, 0.0f), true);
|
||||||
|
ImGui::TextDisabled("%s", STR_C("Catalogue (read only)"));
|
||||||
|
item_tooltip("The catalogue switches start tangentially at the joint before the blades: without the pre-blade stretch and the angle of the blade, which a copy can be given");
|
||||||
|
for (int i = 0; i < static_cast<int>(designer.catalogue.size()); ++i)
|
||||||
|
if (ImGui::Selectable((designer.catalogue[i].name + "##catalogue" + std::to_string(i)).c_str(), designer.from_catalogue && designer.selected == i))
|
||||||
|
{
|
||||||
|
designer.from_catalogue = true;
|
||||||
|
designer.selected = i;
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextDisabled("%s", STR_C("Own templates"));
|
||||||
|
for (int i = 0; i < static_cast<int>(designer.own.size()); ++i)
|
||||||
|
if (ImGui::Selectable((designer.own[i].name + "##own" + std::to_string(i)).c_str(), false == designer.from_catalogue && designer.selected == i))
|
||||||
|
{
|
||||||
|
designer.from_catalogue = false;
|
||||||
|
designer.selected = i;
|
||||||
|
}
|
||||||
|
if (designer.own.empty())
|
||||||
|
ImGui::TextDisabled("%s", STR_C("(none yet)"));
|
||||||
|
if (ImGui::Button(STR_C("New")))
|
||||||
|
{
|
||||||
|
switch_design::design entry;
|
||||||
|
entry.own = true;
|
||||||
|
entry.name = format("%s %zu", STR_C("Switch"), designer.own.size() + 1);
|
||||||
|
auto const angle{std::atan(1.0 / 9.0)};
|
||||||
|
entry.parts = {{190.0 * angle, 190.0, 190.0}, {6.0, 0.0, 0.0}};
|
||||||
|
designer.own.push_back(entry);
|
||||||
|
designer.from_catalogue = false;
|
||||||
|
designer.selected = static_cast<int>(designer.own.size()) - 1;
|
||||||
|
designer.dirty = true;
|
||||||
|
}
|
||||||
|
item_tooltip("A new own template, an Rz 1:9 R190 to start from");
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginChild("##designedit", ImVec2(0.0f, 0.0f), false);
|
||||||
|
auto const &list{designer.from_catalogue ? designer.catalogue : designer.own};
|
||||||
|
if (designer.selected < 0 || designer.selected >= static_cast<int>(list.size()))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped("%s", STR_C("Choose a template on the left. The catalogue ones can be copied to own templates and changed there. A template is a straight main track and the diverging track from the start of the switch as a chain of straights, arcs and transition curves; a double switch is two switches, the second one starting on the main track of the first."));
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool const editable{false == designer.from_catalogue};
|
||||||
|
auto &entry{designer.from_catalogue ? designer.catalogue[designer.selected] : designer.own[designer.selected]};
|
||||||
|
bool changed{false};
|
||||||
|
if (false == editable)
|
||||||
|
{
|
||||||
|
if (ImGui::Button(STR_C("Copy to own templates")))
|
||||||
|
{
|
||||||
|
auto copy{entry};
|
||||||
|
copy.own = true;
|
||||||
|
copy.note.clear();
|
||||||
|
copy.name += STR(" (copy)");
|
||||||
|
designer.own.push_back(copy);
|
||||||
|
designer.from_catalogue = false;
|
||||||
|
designer.selected = static_cast<int>(designer.own.size()) - 1;
|
||||||
|
designer.dirty = true;
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextDisabled("%s", STR_C("A catalogue template can't be changed"));
|
||||||
|
}
|
||||||
|
if (false == editable)
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
char name[96];
|
||||||
|
std::snprintf(name, sizeof(name), "%s", entry.name.c_str());
|
||||||
|
ImGui::SetNextItemWidth(240.0f);
|
||||||
|
if (ImGui::InputText(STR_C("Name##designname"), name, sizeof(name)))
|
||||||
|
{
|
||||||
|
entry.name = name;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
char const *types[] = {STR_C("Rz ordinary"), STR_C("Rpd double, to both sides"), STR_C("Rpj double, to one side")};
|
||||||
|
char const *codes[] = {"Rz", "Rpd", "Rpj"};
|
||||||
|
int type{entry.type == "Rpd" ? 1 : entry.type == "Rpj" ? 2 : 0};
|
||||||
|
ImGui::SetNextItemWidth(240.0f);
|
||||||
|
if (ImGui::Combo(STR_C("Type##designtype"), &type, types, 3))
|
||||||
|
{
|
||||||
|
entry.type = codes[type];
|
||||||
|
if (type > 0 && entry.stagger <= 0.0)
|
||||||
|
entry.stagger = 5.0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("A double switch is laid as two switches: the first one with its diverging track and the main track up to the second one, which starts there with the other diverging track");
|
||||||
|
char rail[32];
|
||||||
|
std::snprintf(rail, sizeof(rail), "%s", entry.rail == "-" ? "" : entry.rail.c_str());
|
||||||
|
ImGui::SetNextItemWidth(120.0f);
|
||||||
|
if (ImGui::InputText(STR_C("Rail##designrail"), rail, sizeof(rail)))
|
||||||
|
{
|
||||||
|
entry.rail = rail[0] != '\0' ? std::string{rail} : std::string{"-"};
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("Rail of the switch kept in its record: 60E1, 49E1...");
|
||||||
|
ImGui::PushItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("Pre-blade stretch, m##designlead"), &entry.lead, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("From the start of the switch (the joint before the blades) to the tip of the blade; the diverging track runs along the main one there");
|
||||||
|
ImGui::SameLine();
|
||||||
|
changed |= ImGui::InputDouble(STR_C("Blade angle, deg##designentry"), &entry.entry, 0.0, 0.0, "%.4f");
|
||||||
|
item_tooltip("The angle at which the blade meets the stock rail at its tip: the diverging track starts there at this angle instead of tangentially. Curved blades a fraction of a degree, straight blades 1 to 2 degrees; 0: tangential");
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
if (editable)
|
||||||
|
{
|
||||||
|
ImGui::PushItemWidth(70.0f);
|
||||||
|
ImGui::TextDisabled("%s", STR_C("Straight blade:"));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputDouble(STR_C("l, m##bladelength"), &designer.blade_length, 0.0, 0.0, "%.2f");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputDouble(STR_C("z, m##bladegap"), &designer.blade_gap, 0.0, 0.0, "%.3f");
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton(STR_C("Angle from l and z")) && designer.blade_length > 0.1 && designer.blade_gap > 0.0 && designer.blade_gap < designer.blade_length)
|
||||||
|
{
|
||||||
|
entry.entry = glm::degrees(std::asin(designer.blade_gap / designer.blade_length));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("sin angle = z / l: l the length of a straight blade, z the gap between the running edges of the stock rail and the blade at its heel");
|
||||||
|
}
|
||||||
|
if (false == editable)
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextDisabled("%s", STR_C("Diverging track from the start of the switch"));
|
||||||
|
if (editable)
|
||||||
|
{
|
||||||
|
ImGui::PushItemWidth(80.0f);
|
||||||
|
ImGui::InputDouble(STR_C("R##quickradius"), &designer.quick_radius, 0.0, 0.0, "%.0f");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputDouble(STR_C("1:n##quickratio"), &designer.quick_ratio, 0.0, 0.0, "%.2f");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::InputDouble(STR_C("straight after, m##quickstraight"), &designer.quick_straight, 0.0, 0.0, "%.3f");
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton(STR_C("Fill in")) && designer.quick_radius >= 10.0 && designer.quick_ratio > 1.0)
|
||||||
|
{
|
||||||
|
auto const angle{std::atan(1.0 / designer.quick_ratio)};
|
||||||
|
entry.parts = {{designer.quick_radius * angle, designer.quick_radius, designer.quick_radius}};
|
||||||
|
if (designer.quick_straight > 0.01)
|
||||||
|
entry.parts.push_back({designer.quick_straight, 0.0, 0.0});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("Replaces the parts with an arc of R up to the angle 1:n and the straight after it, the shape of an ordinary switch");
|
||||||
|
}
|
||||||
|
changed |= edit_parts(entry.parts, "##parts", editable);
|
||||||
|
bool const twofold{switch_design::double_switch(entry.type)};
|
||||||
|
if (false == editable)
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
if (twofold)
|
||||||
|
{
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::SetNextItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("Second switch starts at, m##stagger"), &entry.stagger, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("Distance from the start of the first switch to the start of the second one, along the main track");
|
||||||
|
bool own{false == entry.parts2.empty()};
|
||||||
|
if (ImGui::Checkbox(STR_C("The second switch has a diverging track of its own"), &own))
|
||||||
|
{
|
||||||
|
entry.parts2 = own ? entry.parts : std::vector<switch_design::part>{};
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
item_tooltip("Otherwise the second switch diverges the same way as the first, to the other side for Rpd, to the same side for Rpj");
|
||||||
|
if (own)
|
||||||
|
{
|
||||||
|
ImGui::PushItemWidth(90.0f);
|
||||||
|
auto lead2{entry.lead2 >= 0.0 ? entry.lead2 : entry.lead};
|
||||||
|
if (ImGui::InputDouble(STR_C("Pre-blade stretch, m##designlead2"), &lead2, 0.0, 0.0, "%.3f"))
|
||||||
|
{
|
||||||
|
entry.lead2 = std::max(0.0, lead2);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
auto entry2{entry.entry2 >= 0.0 ? entry.entry2 : entry.entry};
|
||||||
|
if (ImGui::InputDouble(STR_C("Blade angle, deg##designentry2"), &entry2, 0.0, 0.0, "%.4f"))
|
||||||
|
{
|
||||||
|
entry.entry2 = std::max(0.0, entry2);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
changed |= edit_parts(entry.parts2, "##parts2", editable);
|
||||||
|
}
|
||||||
|
else if (entry.lead2 >= 0.0 || entry.entry2 >= 0.0)
|
||||||
|
{
|
||||||
|
entry.lead2 = entry.entry2 = -1.0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::PushItemWidth(90.0f);
|
||||||
|
changed |= ImGui::InputDouble(STR_C("Main track, m##designmain"), &entry.main, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("Length of the main track, 0: as far as the diverging tracks reach");
|
||||||
|
ImGui::SameLine();
|
||||||
|
changed |= ImGui::InputDouble(STR_C("a##designa"), &entry.a, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("From the start to the point where the tangents of both tracks meet, as the catalogue gives it; 0: from the geometry");
|
||||||
|
ImGui::SameLine();
|
||||||
|
changed |= ImGui::InputDouble(STR_C("b##designb"), &entry.b, 0.0, 0.0, "%.3f");
|
||||||
|
item_tooltip("From the point where the tangents meet to the end, as the catalogue gives it; 0: the main track less a");
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
if (false == editable)
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
std::string error;
|
||||||
|
auto const shape{switch_design::build(entry, error)};
|
||||||
|
ImGui::Separator();
|
||||||
|
if (false == error.empty())
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.35f, 1.0f), "%s", STR_C(error.c_str()));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::Text(STR_C("Angle 1:%.3f (%.4f deg), sharpest R %.1f m, a %.3f m, b %.3f m"), shape.ratio, glm::degrees(std::atan(1.0 / shape.ratio)), shape.radius, shape.a, shape.b);
|
||||||
|
ImGui::Text(STR_C("Length %.3f m, the diverging track ends %.3f m beside the main one, in %zu pieces"), shape.length, shape.offset, std::max<std::size_t>(1, shape.paths[1].pieces.size()));
|
||||||
|
if (entry.lead > 0.0 || entry.entry > 0.0)
|
||||||
|
ImGui::Text(STR_C("Tip of the blade %.3f m from the start, the diverging track leaves there at %.4f deg"), shape.tip, entry.entry);
|
||||||
|
}
|
||||||
|
ImGui::Checkbox(STR_C("Stretch across"), &designer.stretched);
|
||||||
|
item_tooltip("Draws the offsets from the main track larger, so the shape of the diverging track can be seen");
|
||||||
|
draw_shape(shape, 150.0f, designer.stretched);
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
designer.dirty = true;
|
||||||
|
if (editable)
|
||||||
|
{
|
||||||
|
if (ImGui::Button(designer.dirty ? STR_C("Save *") : STR_C("Save")))
|
||||||
|
{
|
||||||
|
std::string problem;
|
||||||
|
if (switch_design::save_own(designer.own, problem))
|
||||||
|
{
|
||||||
|
designer.dirty = false;
|
||||||
|
designer.status = STR("Saved, the templates are on the list of the switch tool");
|
||||||
|
switch_templates_refresh();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
designer.status = problem;
|
||||||
|
}
|
||||||
|
item_tooltip("Keeps the own templates in eu07_switch_templates.txt in the configuration directory of the simulator and puts them on the list of the switch tool");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button(STR_C("Delete")))
|
||||||
|
{
|
||||||
|
designer.own.erase(designer.own.begin() + designer.selected);
|
||||||
|
designer.selected = -1;
|
||||||
|
designer.dirty = true;
|
||||||
|
}
|
||||||
|
item_tooltip("Removes the template; Save keeps the change");
|
||||||
|
}
|
||||||
|
if (error.empty())
|
||||||
|
{
|
||||||
|
if (editable)
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button(STR_C("Place this switch")))
|
||||||
|
{
|
||||||
|
if (designer.dirty && editable)
|
||||||
|
{
|
||||||
|
std::string problem;
|
||||||
|
if (switch_design::save_own(designer.own, problem))
|
||||||
|
designer.dirty = false;
|
||||||
|
else
|
||||||
|
designer.status = problem;
|
||||||
|
}
|
||||||
|
switch_templates_refresh(entry.name + " ");
|
||||||
|
show_track_tab(track_tab::turnout);
|
||||||
|
arm_switch();
|
||||||
|
}
|
||||||
|
item_tooltip("Saves the own templates and chooses this one in the switch tool: then click a straight and drag to the side the switch goes");
|
||||||
|
}
|
||||||
|
if (false == designer.status.empty())
|
||||||
|
ImGui::TextWrapped("%s", designer.status.c_str());
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
@@ -218,15 +219,129 @@ bool editor_mode::move_switch_paths(TTrack &Track, std::vector<segment_data> con
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool editor_mode::double_room(double_switch const &Set, double &Low, double &High, double &Length, std::string &Reason)
|
||||||
|
{
|
||||||
|
double low{0.0};
|
||||||
|
double high{0.0};
|
||||||
|
double low2{0.0};
|
||||||
|
double high2{0.0};
|
||||||
|
auto const axis{editor_track::main_axis(*Set.first)};
|
||||||
|
if (false == axis.has_value() || false == switch_room(*Set.first, low, high, Reason) || false == switch_room(*Set.second, low2, high2, Reason))
|
||||||
|
return false;
|
||||||
|
auto const &main{Set.second->m_paths.front()};
|
||||||
|
Low = low;
|
||||||
|
High = axis->along(main.points[segment_data::point::start]) + high2;
|
||||||
|
Length = axis->along(main.points[segment_data::point::end]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool editor_mode::move_double_paths(double_switch const &Set, std::vector<segment_data> const &Paths, std::string &Status)
|
||||||
|
{
|
||||||
|
auto const axis{editor_track::main_axis(*Set.first)};
|
||||||
|
if (Paths.size() < 5 || false == axis.has_value())
|
||||||
|
return false;
|
||||||
|
double low{0.0};
|
||||||
|
double high{0.0};
|
||||||
|
double length{0.0};
|
||||||
|
std::string reason;
|
||||||
|
bool const online{double_room(Set, low, high, length, reason)};
|
||||||
|
if (false == online && false == reason.empty())
|
||||||
|
{
|
||||||
|
Status = reason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct outer_end
|
||||||
|
{
|
||||||
|
TTrack *track{nullptr};
|
||||||
|
switch_end_move move;
|
||||||
|
};
|
||||||
|
std::vector<outer_end> ends;
|
||||||
|
auto const outer = [&](TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, bool const Atstart) {
|
||||||
|
if (false == online || glm::distance(From, To) < 0.005 || axis->lateral(To) > m_straights.tolerance.offset)
|
||||||
|
return true;
|
||||||
|
auto const target{axis->along(To)};
|
||||||
|
if (target < low - 0.01 || target > high + 0.01)
|
||||||
|
{
|
||||||
|
Status = STR_C("The switch would run into another switch or past the end of the straight");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ends.push_back({&Track, {From, To, Atstart ? axis->along(From) - target : target - axis->along(From)}});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (false == outer(*Set.first, Set.first->m_paths.front().points[segment_data::point::start], Paths[0].points[segment_data::point::start], true) ||
|
||||||
|
false == outer(*Set.second, Set.second->m_paths.front().points[segment_data::point::end], Paths[2].points[segment_data::point::end], false))
|
||||||
|
return false;
|
||||||
|
std::stable_sort(ends.begin(), ends.end(), [](outer_end const &A, outer_end const &B) { return A.move.shift > B.move.shift; });
|
||||||
|
std::array<TTrack *, 2> const switches{Set.first, Set.second};
|
||||||
|
std::array<std::vector<editor_track::joint>, 2> joints;
|
||||||
|
std::array<std::vector<glm::dvec3>, 2> positions;
|
||||||
|
std::array<std::vector<bool>, 2> handled;
|
||||||
|
for (std::size_t k = 0; k < 2; ++k)
|
||||||
|
{
|
||||||
|
joints[k] = editor_track::joints(*switches[k]);
|
||||||
|
for (auto const &joint : joints[k])
|
||||||
|
{
|
||||||
|
positions[k].push_back(editor_track::point_position(*switches[k], joint.own));
|
||||||
|
handled[k].push_back(joint.other == Set.link || joint.other == Set.first || joint.other == Set.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto const joint{Set.first->m_paths.front().points[segment_data::point::end]};
|
||||||
|
auto const &link{Set.link->m_paths.front()};
|
||||||
|
bool const reversed{glm::distance(link.points[segment_data::point::start], joint) > glm::distance(link.points[segment_data::point::end], joint)};
|
||||||
|
editor_track::edit edit;
|
||||||
|
for (auto *track : {Set.first, Set.link, Set.second})
|
||||||
|
edit.capture(*track);
|
||||||
|
for (auto const &end : ends)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < 2; ++k)
|
||||||
|
for (std::size_t i = 0; i < joints[k].size(); ++i)
|
||||||
|
if (glm::distance(positions[k][i], end.move.from) < geometry::same_point)
|
||||||
|
handled[k][i] = true;
|
||||||
|
auto const line{editor_track::find_straight(*end.track, m_straights.tolerance)};
|
||||||
|
if (false == switch_vacate(*end.track, end.move.from, end.move.to, end.move.shift, line, edit, Status))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Set.first->m_paths.assign(Paths.begin(), Paths.begin() + 2);
|
||||||
|
Set.second->m_paths.assign(Paths.begin() + 2, Paths.begin() + 4);
|
||||||
|
Set.link->m_paths = {reversed ? geometry::reversed(Paths[4]) : Paths[4]};
|
||||||
|
std::string loose;
|
||||||
|
for (std::size_t k = 0; k < 2; ++k)
|
||||||
|
{
|
||||||
|
auto const left{switch_rejoin(*switches[k], joints[k], handled[k], edit)};
|
||||||
|
if (false == left.empty())
|
||||||
|
loose += (loose.empty() ? "" : ", ") + left;
|
||||||
|
}
|
||||||
|
editor_track::commit(edit.changed);
|
||||||
|
push_track_snapshot(std::move(edit.states), std::move(edit.created), std::move(edit.removed));
|
||||||
|
if (false == loose.empty())
|
||||||
|
Status = STR_C("Too far from the new ends of the switch, left unjoined: ") + loose;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool editor_mode::slide_switch(TTrack &Track, double const Distance)
|
bool editor_mode::slide_switch(TTrack &Track, double const Distance)
|
||||||
{
|
{
|
||||||
if (Track.m_paths.size() < 2 || std::abs(Distance) < 0.005)
|
if (std::abs(Distance) < 0.005)
|
||||||
return false;
|
return false;
|
||||||
auto const paths{editor_track::slid(Track, Distance)};
|
auto const set{double_of(Track)};
|
||||||
|
auto &first{set.has_value() ? *set->first : Track};
|
||||||
|
if (first.m_paths.size() < 2)
|
||||||
|
return false;
|
||||||
|
auto paths{editor_track::slid(first, Distance)};
|
||||||
if (paths.empty())
|
if (paths.empty())
|
||||||
return false;
|
return false;
|
||||||
std::string status;
|
std::string status;
|
||||||
if (false == move_switch_paths(Track, paths, status))
|
bool moved{false};
|
||||||
|
if (set.has_value())
|
||||||
|
{
|
||||||
|
auto const shift{paths.front().points[segment_data::point::start] - first.m_paths.front().points[segment_data::point::start]};
|
||||||
|
paths = double_paths(*set);
|
||||||
|
for (auto &path : paths)
|
||||||
|
path.translate(shift);
|
||||||
|
moved = move_double_paths(*set, paths, status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
moved = move_switch_paths(Track, paths, status);
|
||||||
|
if (false == moved)
|
||||||
{
|
{
|
||||||
m_switch.status = status;
|
m_switch.status = status;
|
||||||
ui()->set_status(status, true);
|
ui()->set_status(status, true);
|
||||||
@@ -235,13 +350,18 @@ bool editor_mode::slide_switch(TTrack &Track, double const Distance)
|
|||||||
switch_store::follow();
|
switch_store::follow();
|
||||||
records_to_history();
|
records_to_history();
|
||||||
straight_refresh();
|
straight_refresh();
|
||||||
m_switch.status = status.empty() ? format(STR_C("Switch %s moved %.2f m along the straight"), Track.name().c_str(), Distance) : status;
|
m_switch.status = status.empty() ? format(STR_C("Switch %s moved %.2f m along the straight"), first.name().c_str(), Distance) : status;
|
||||||
ui()->set_status(m_switch.status, false == status.empty());
|
ui()->set_status(m_switch.status, false == status.empty());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool editor_mode::switch_slides(TTrack const &Track) const
|
bool editor_mode::switch_slides(TTrack const &Track) const
|
||||||
{
|
{
|
||||||
|
if (Track.eType == tt_Normal)
|
||||||
|
{
|
||||||
|
auto const set{double_of(Track)};
|
||||||
|
return set.has_value() && switch_slides(*set->first) && switch_slides(*set->second);
|
||||||
|
}
|
||||||
return Track.eType == tt_Switch && Track.m_paths.size() == 2 && false == Track.m_editorremoved && false == has_controls(Track.m_paths.front());
|
return Track.eType == tt_Switch && Track.m_paths.size() == 2 && false == Track.m_editorremoved && false == has_controls(Track.m_paths.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,26 +370,39 @@ void editor_mode::switch_slide_start(TTrack &Track)
|
|||||||
auto &slide{m_switch.slide};
|
auto &slide{m_switch.slide};
|
||||||
slide = {};
|
slide = {};
|
||||||
std::string reason;
|
std::string reason;
|
||||||
if (false == switch_room(Track, slide.low, slide.high, reason))
|
auto const set{double_of(Track)};
|
||||||
|
auto &first{set.has_value() ? *set->first : Track};
|
||||||
|
auto const axis{editor_track::main_axis(first)};
|
||||||
|
if (false == axis.has_value())
|
||||||
|
return;
|
||||||
|
slide.length = axis->length;
|
||||||
|
if (false == (set.has_value() ? double_room(*set, slide.low, slide.high, slide.length, reason) : switch_room(first, slide.low, slide.high, reason)))
|
||||||
{
|
{
|
||||||
if (false == reason.empty())
|
if (false == reason.empty())
|
||||||
ui()->set_status(reason, true);
|
ui()->set_status(reason, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto const axis{editor_track::main_axis(Track)};
|
|
||||||
auto const &mouse{ImGui::GetIO().MousePos};
|
auto const &mouse{ImGui::GetIO().MousePos};
|
||||||
slide.active = true;
|
slide.active = true;
|
||||||
slide.track = &Track;
|
slide.track = &first;
|
||||||
slide.pressed = {mouse.x, mouse.y};
|
slide.pressed = {mouse.x, mouse.y};
|
||||||
slide.grip = axis->along(cursor_level(axis->origin.y));
|
slide.grip = axis->along(cursor_level(axis->origin.y));
|
||||||
auto const line{editor_track::find_straight(Track, m_straights.tolerance)};
|
std::vector<TTrack *> members{&first};
|
||||||
for (std::size_t i = 0; i < line.tracks.size(); ++i)
|
if (set.has_value())
|
||||||
|
members = {set->first, set->link, set->second};
|
||||||
|
for (auto *member : members)
|
||||||
{
|
{
|
||||||
if (line.tracks[i] == &Track)
|
if (member->eType != tt_Switch)
|
||||||
continue;
|
continue;
|
||||||
auto const &path{line.tracks[i]->m_paths[line.paths[i]]};
|
auto const line{editor_track::find_straight(*member, m_straights.tolerance)};
|
||||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
for (std::size_t i = 0; i < line.tracks.size(); ++i)
|
||||||
slide.joints.push_back(axis->along(path.points[index]));
|
{
|
||||||
|
if (std::find(members.begin(), members.end(), line.tracks[i]) != members.end())
|
||||||
|
continue;
|
||||||
|
auto const &path{line.tracks[i]->m_paths[line.paths[i]]};
|
||||||
|
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||||
|
slide.joints.push_back(axis->along(path.points[index]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +417,7 @@ void editor_mode::switch_slide_update()
|
|||||||
slide = {};
|
slide = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto const length{axis->length};
|
auto const length{slide.length};
|
||||||
auto const distance{std::clamp(axis->along(cursor_level(axis->origin.y)) - slide.grip, slide.low, slide.high - length)};
|
auto const distance{std::clamp(axis->along(cursor_level(axis->origin.y)) - slide.grip, slide.low, slide.high - length)};
|
||||||
double best{1.0};
|
double best{1.0};
|
||||||
double snapped{distance};
|
double snapped{distance};
|
||||||
@@ -320,9 +453,16 @@ void editor_mode::draw_switch_slide() const
|
|||||||
auto const &mouse{ImGui::GetIO().MousePos};
|
auto const &mouse{ImGui::GetIO().MousePos};
|
||||||
if (glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) <= kDragPixels)
|
if (glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) <= kDragPixels)
|
||||||
return;
|
return;
|
||||||
auto const paths{editor_track::slid(*slide.track, slide.distance)};
|
auto paths{editor_track::slid(*slide.track, slide.distance)};
|
||||||
if (paths.empty())
|
if (paths.empty())
|
||||||
return;
|
return;
|
||||||
|
if (auto const set{double_of(*slide.track)})
|
||||||
|
{
|
||||||
|
auto const shift{paths.front().points[segment_data::point::start] - slide.track->m_paths.front().points[segment_data::point::start]};
|
||||||
|
paths = double_paths(*set);
|
||||||
|
for (auto &path : paths)
|
||||||
|
path.translate(shift);
|
||||||
|
}
|
||||||
screen_projection const projection;
|
screen_projection const projection;
|
||||||
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||||
for (auto const &path : paths)
|
for (auto const &path : paths)
|
||||||
|
|||||||
@@ -57,6 +57,13 @@ using geometry::level;
|
|||||||
using geometry::reversed;
|
using geometry::reversed;
|
||||||
using geometry::straight_piece;
|
using geometry::straight_piece;
|
||||||
|
|
||||||
|
std::string template_label(editor_track::switch_template const &Template)
|
||||||
|
{
|
||||||
|
if (Template.source != nullptr)
|
||||||
|
return STR("Scenery: ") + Template.label + " (" + std::to_string(Template.count) + "x)";
|
||||||
|
return (Template.own ? STR("Own: ") : std::string{"PLK: "}) + Template.label;
|
||||||
|
}
|
||||||
|
|
||||||
double constexpr kParallelFindAngle{15.0}; // deg, neighbours counted as beside the straight
|
double constexpr kParallelFindAngle{15.0}; // deg, neighbours counted as beside the straight
|
||||||
double constexpr kParallelSnapAngle{5.0}; // deg, close enough to snap parallel
|
double constexpr kParallelSnapAngle{5.0}; // deg, close enough to snap parallel
|
||||||
double constexpr kParallelSnapWindow{0.45}; // m from 4.00 / 4.50 / 4.75 m
|
double constexpr kParallelSnapWindow{0.45}; // m from 4.00 / 4.50 / 4.75 m
|
||||||
@@ -910,6 +917,9 @@ void editor_mode::render_turnout_ui()
|
|||||||
auto *track{selected_track()};
|
auto *track{selected_track()};
|
||||||
if (track == nullptr || track->eType != tt_Switch || track->m_paths.size() < 2)
|
if (track == nullptr || track->eType != tt_Switch || track->m_paths.size() < 2)
|
||||||
return;
|
return;
|
||||||
|
auto const set{double_of(*track)};
|
||||||
|
if (set.has_value())
|
||||||
|
track = set->first;
|
||||||
auto const &main{track->m_paths[0]};
|
auto const &main{track->m_paths[0]};
|
||||||
auto const &diverging{track->m_paths[1]};
|
auto const &diverging{track->m_paths[1]};
|
||||||
auto const origin{main.points[segment_data::point::start]};
|
auto const origin{main.points[segment_data::point::start]};
|
||||||
@@ -928,43 +938,59 @@ 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("right") : STR_C("left"), std::abs(offset));
|
if (set.has_value())
|
||||||
|
{
|
||||||
|
auto const &second{set->second->m_paths.front()};
|
||||||
|
ImGui::Text(STR_C("Double switch %s + %s: length %.3f m, the second switch starts %.3f m on"), set->first->name().c_str(), set->second->name().c_str(), glm::dot(plan_of(second.points[segment_data::point::end] - origin), axis), glm::dot(plan_of(second.points[segment_data::point::start] - origin), axis));
|
||||||
|
ImGui::TextDisabled(STR_C("First switch: diverging %s, end offset %.3f m"), offset > 0.0 ? STR_C("right") : STR_C("left"), std::abs(offset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
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);
|
render_switch_record(*track);
|
||||||
if (auto const drive{find_switch_drive(*track)})
|
auto const drive_ui = [&](TTrack &Switch) {
|
||||||
{
|
ImGui::PushID(&Switch);
|
||||||
auto const family{drive->family};
|
if (set.has_value())
|
||||||
auto const variant{drive->index};
|
ImGui::TextUnformatted(Switch.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());
|
if (auto const drive{find_switch_drive(Switch)})
|
||||||
ImGui::PushItemWidth(200.0f);
|
|
||||||
if (ImGui::BeginCombo(STR_C("Drive##standing"), m_switch.drives[family].name.c_str()))
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < static_cast<int>(m_switch.drives.size()); ++i)
|
auto const family{drive->family};
|
||||||
if (ImGui::Selectable(m_switch.drives[i].name.c_str(), family == i) && i != family)
|
auto const variant{drive->index};
|
||||||
replace_switch_drive(*track, i, false);
|
ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), Switch.name().c_str(), Switch.name().c_str());
|
||||||
ImGui::EndCombo();
|
ImGui::PushItemWidth(200.0f);
|
||||||
|
if (ImGui::BeginCombo(STR_C("Drive##standing"), m_switch.drives[family].name.c_str()))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < static_cast<int>(m_switch.drives.size()); ++i)
|
||||||
|
if (ImGui::Selectable(m_switch.drives[i].name.c_str(), family == i) && i != family)
|
||||||
|
replace_switch_drive(Switch, i, false);
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
item_tooltip("Puts another drive in place of the drive of this switch, on the same side");
|
||||||
|
int side{((variant >> 1) == (variant & 1)) ? 0 : 1};
|
||||||
|
ImGui::SameLine();
|
||||||
|
auto const before{side};
|
||||||
|
ImGui::RadioButton(STR_C("diverging side##standing"), &side, 0);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::RadioButton(STR_C("other side##standing"), &side, 1);
|
||||||
|
if (side != before)
|
||||||
|
flip_switch_drive(Switch);
|
||||||
|
item_tooltip("Puts the drive of the switch on the other side of the track: the same drive, its variant for the other side");
|
||||||
}
|
}
|
||||||
ImGui::PopItemWidth();
|
else if ((simulation::Events.FindEvent(Switch.name() + "+") != nullptr && simulation::Events.FindEvent(Switch.name() + "-") != nullptr) || (m_track_name.track == &Switch && m_track_name.uses.includes > 0))
|
||||||
item_tooltip("Puts another drive in place of the drive of this switch, on the same side");
|
ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), Switch.name().c_str(), Switch.name().c_str());
|
||||||
int side{((variant >> 1) == (variant & 1)) ? 0 : 1};
|
else
|
||||||
ImGui::SameLine();
|
{
|
||||||
auto const before{side};
|
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", STR_C("No drive: the switch can't be switched during the game"));
|
||||||
ImGui::RadioButton(STR_C("diverging side##standing"), &side, 0);
|
render_switch_drive_choice();
|
||||||
ImGui::SameLine();
|
if (m_switch.drive >= 0 && ImGui::Button(STR_C("Put the drive by the switch")))
|
||||||
ImGui::RadioButton(STR_C("other side##standing"), &side, 1);
|
place_switch_drive(Switch);
|
||||||
if (side != before)
|
}
|
||||||
flip_switch_drive(*track);
|
ImGui::PopID();
|
||||||
item_tooltip("Puts the drive of the switch on the other side of the track: the same drive, its variant for the other side");
|
};
|
||||||
}
|
drive_ui(*track);
|
||||||
else 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 (set.has_value())
|
||||||
ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), track->name().c_str(), track->name().c_str());
|
drive_ui(*set->second);
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", STR_C("No drive: the switch can't be switched during the game"));
|
|
||||||
render_switch_drive_choice();
|
|
||||||
if (m_switch.drive >= 0 && ImGui::Button(STR_C("Put the drive by the switch")))
|
|
||||||
place_switch_drive(*track);
|
|
||||||
}
|
|
||||||
bool frogs{track->SwitchExtension->m_frogs};
|
bool frogs{track->SwitchExtension->m_frogs};
|
||||||
if (ImGui::Checkbox(STR_C("Frog and check rails made from the rails"), &frogs))
|
if (ImGui::Checkbox(STR_C("Frog and check rails made from the rails"), &frogs))
|
||||||
{
|
{
|
||||||
@@ -989,9 +1015,10 @@ void editor_mode::render_turnout_ui()
|
|||||||
if (tool.templates.empty())
|
if (tool.templates.empty())
|
||||||
return;
|
return;
|
||||||
m_turnout_template = std::clamp(m_turnout_template, 0, static_cast<int>(tool.templates.size()) - 1);
|
m_turnout_template = std::clamp(m_turnout_template, 0, static_cast<int>(tool.templates.size()) - 1);
|
||||||
if (tool.templates[m_turnout_template].double_slip)
|
auto const fits = [&](editor_track::switch_template const &Template) { return false == Template.double_slip && Template.twofold() == set.has_value(); };
|
||||||
|
if (false == fits(tool.templates[m_turnout_template]))
|
||||||
{
|
{
|
||||||
auto const ordinary{std::find_if(tool.templates.begin(), tool.templates.end(), [](auto const &Template) { return false == Template.double_slip; })};
|
auto const ordinary{std::find_if(tool.templates.begin(), tool.templates.end(), fits)};
|
||||||
if (ordinary == tool.templates.end())
|
if (ordinary == tool.templates.end())
|
||||||
return;
|
return;
|
||||||
m_turnout_template = static_cast<int>(ordinary - tool.templates.begin());
|
m_turnout_template = static_cast<int>(ordinary - tool.templates.begin());
|
||||||
@@ -1002,18 +1029,43 @@ void editor_mode::render_turnout_ui()
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||||
{
|
{
|
||||||
if (tool.templates[i].double_slip)
|
if (false == fits(tool.templates[i]))
|
||||||
continue;
|
continue;
|
||||||
auto const label{(tool.templates[i].source != nullptr ? "Scenery: " : "PLK: ") + tool.templates[i].label};
|
auto const label{template_label(tool.templates[i])};
|
||||||
if (ImGui::Selectable(label.c_str(), i == m_turnout_template))
|
if (ImGui::Selectable(label.c_str(), i == m_turnout_template))
|
||||||
m_turnout_template = i;
|
m_turnout_template = i;
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
|
auto const finish_set = [&](std::string const &Status) {
|
||||||
|
switch_store::follow();
|
||||||
|
records_to_history();
|
||||||
|
straight_refresh();
|
||||||
|
if (false == Status.empty())
|
||||||
|
ui()->set_status(Status, true);
|
||||||
|
};
|
||||||
auto const replace = [&](editor_track::switch_template const &Template, int const Side) {
|
auto const replace = [&](editor_track::switch_template const &Template, int const Side) {
|
||||||
auto const grade{length > 0.0 ? (main.points[segment_data::point::end].y - origin.y) / length : 0.0};
|
auto const grade{length > 0.0 ? (main.points[segment_data::point::end].y - origin.y) / length : 0.0};
|
||||||
auto paths{editor_track::place_switch(Template, origin, axis, Side, grade)};
|
auto paths{editor_track::place_switch(Template, origin, axis, Side, grade)};
|
||||||
|
if (set.has_value())
|
||||||
|
{
|
||||||
|
if (paths.size() < 5)
|
||||||
|
return;
|
||||||
|
auto const before{double_paths(*set)};
|
||||||
|
for (std::size_t i = 0; i < paths.size(); ++i)
|
||||||
|
if (paths[i].rolls[0] == 0.f && paths[i].rolls[1] == 0.f)
|
||||||
|
paths[i].rolls = before[i < 4 ? i & ~std::size_t{1} : 4].rolls;
|
||||||
|
std::string status;
|
||||||
|
if (false == move_double_paths(*set, paths, status))
|
||||||
|
{
|
||||||
|
ui()->set_status(status, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
record_double(*set->first, *set->second, Template);
|
||||||
|
finish_set(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (paths.size() < 2)
|
if (paths.size() < 2)
|
||||||
return;
|
return;
|
||||||
for (int i = 0; i < 2; ++i)
|
for (int i = 0; i < 2; ++i)
|
||||||
@@ -1036,7 +1088,30 @@ void editor_mode::render_turnout_ui()
|
|||||||
replace(tool.templates[m_turnout_template], side);
|
replace(tool.templates[m_turnout_template], side);
|
||||||
item_tooltip("Keeps the start and the direction of the switch, takes the shape of the chosen template");
|
item_tooltip("Keeps the start and the direction of the switch, takes the shape of the chosen template");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(STR_C("Flip the side")))
|
if (set.has_value() && ImGui::Button(STR_C("Flip the side")))
|
||||||
|
{
|
||||||
|
auto const mirror = [&](glm::dvec3 const &Vector) {
|
||||||
|
auto const across{glm::dot(plan_of(Vector), normal)};
|
||||||
|
return glm::dvec3{Vector.x - 2.0 * across * normal.x, Vector.y, Vector.z - 2.0 * across * normal.y};
|
||||||
|
};
|
||||||
|
auto paths{double_paths(*set)};
|
||||||
|
for (auto &path : paths)
|
||||||
|
path.transform([&](glm::dvec3 const &Point) { return origin + mirror(Point - origin); }, mirror);
|
||||||
|
std::string status;
|
||||||
|
if (false == move_double_paths(*set, paths, status))
|
||||||
|
ui()->set_status(status, true);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (auto entry{switch_store::named(*set->first)})
|
||||||
|
{
|
||||||
|
switch_store::place(*entry, *set->first);
|
||||||
|
switch_store::classify(*entry, *set->first);
|
||||||
|
switch_store::put(*entry, alignment_store::layer_of(*set->first));
|
||||||
|
}
|
||||||
|
finish_set(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (false == set.has_value() && ImGui::Button(STR_C("Flip the side")))
|
||||||
{
|
{
|
||||||
editor_track::switch_template own;
|
editor_track::switch_template own;
|
||||||
own.source = track;
|
own.source = track;
|
||||||
@@ -1056,16 +1131,16 @@ void editor_mode::render_turnout_ui()
|
|||||||
replace(own, -side);
|
replace(own, -side);
|
||||||
}
|
}
|
||||||
item_tooltip("Mirrors the switch: the diverging track goes to the other side");
|
item_tooltip("Mirrors the switch: the diverging track goes to the other side");
|
||||||
if (double low, high; switch_slides(*track) && switch_room(*track, low, high, m_switch.status))
|
if (double low, high, reach{length}; switch_slides(*track) && (set.has_value() ? switch_slides(*set->second) && double_room(*set, low, high, reach, m_switch.status) : switch_room(*track, low, high, m_switch.status)))
|
||||||
{
|
{
|
||||||
ImGui::PushItemWidth(100.0f);
|
ImGui::PushItemWidth(100.0f);
|
||||||
ImGui::InputDouble("##switchslide", &tool.slide_typed, 0.0, 0.0, "%.2f m");
|
ImGui::InputDouble("##switchslide", &tool.slide_typed, 0.0, 0.0, "%.2f m");
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(STR_C("Slide along the straight")))
|
if (ImGui::Button(STR_C("Slide along the straight")))
|
||||||
slide_switch(*track, std::clamp(tool.slide_typed, low, high - length));
|
slide_switch(*track, std::clamp(tool.slide_typed, low, high - reach));
|
||||||
item_tooltip("Moves the switch along its straight by the distance typed, + towards the frog; the straight paths on both sides get shorter or longer. Dragging the switch with the mouse does the same");
|
item_tooltip("Moves the switch along its straight by the distance typed, + towards the frog; the straight paths on both sides get shorter or longer. Dragging the switch with the mouse does the same");
|
||||||
ImGui::TextDisabled(STR_C("Room on the straight: %.2f m back, %.2f m ahead"), -low, high - length);
|
ImGui::TextDisabled(STR_C("Room on the straight: %.2f m back, %.2f m ahead"), -low, high - reach);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
render_path_parameters(*track);
|
render_path_parameters(*track);
|
||||||
@@ -3708,6 +3783,39 @@ void editor_mode::delete_selected_track()
|
|||||||
ui()->set_status(STR_C("The track can't be deleted while vehicles stand on it"), true);
|
ui()->set_status(STR_C("The track can't be deleted while vehicles stand on it"), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (auto const set{double_of(*track)})
|
||||||
|
{
|
||||||
|
for (auto *part : {set->first, set->link, set->second})
|
||||||
|
if (false == part->Dynamics.empty())
|
||||||
|
{
|
||||||
|
ui()->set_status(STR_C("The track can't be deleted while vehicles stand on it"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto const known{switch_store::named(*set->first)})
|
||||||
|
switch_store::forget(known->name);
|
||||||
|
auto main{set->first->m_paths.front()};
|
||||||
|
main.points[segment_data::point::end] = set->second->m_paths.front().points[segment_data::point::end];
|
||||||
|
auto const velocity{speed_check::path_velocity(*set->first, 0)};
|
||||||
|
for (auto *part : {set->first, set->link, set->second})
|
||||||
|
editor_track::retire(*part);
|
||||||
|
std::vector<TTrack *> created;
|
||||||
|
if (false == Global.shiftState)
|
||||||
|
{
|
||||||
|
auto *straight{editor_track::create_path(*set->first, main)};
|
||||||
|
editor_track::velocity(*straight, velocity);
|
||||||
|
editor_track::commit({straight});
|
||||||
|
created.push_back(straight);
|
||||||
|
ui()->set_status(STR_C("The double switch is replaced by its main track, Shift+Del removes all of it"), false);
|
||||||
|
}
|
||||||
|
push_track_snapshot({}, created, {set->first, set->link, set->second});
|
||||||
|
if (std::any_of(m_route.chain.tracks.begin(), m_route.chain.tracks.end(), [&](TTrack const *Each) { return Each == set->first || Each == set->link || Each == set->second; }))
|
||||||
|
m_route = {};
|
||||||
|
m_node = nullptr;
|
||||||
|
ui()->set_node(nullptr);
|
||||||
|
m_track_point = {};
|
||||||
|
straight_refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// retired first, so the infrastructure it carried and moves elsewhere is in the same undo step
|
// retired first, so the infrastructure it carried and moves elsewhere is in the same undo step
|
||||||
if (auto const known{switch_store::of(*track)})
|
if (auto const known{switch_store::of(*track)})
|
||||||
switch_store::forget(known->name);
|
switch_store::forget(known->name);
|
||||||
@@ -3765,7 +3873,9 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
|
|||||||
else if (m_extend.active)
|
else if (m_extend.active)
|
||||||
hints = {{"Drag", "lays the shape to the cursor, to a free end: joins it"}, {"Ctrl+wheel", "radius"}, {"Shift+wheel", "another shape"}, {"Release", "build"}, {"Esc", "cancel"}};
|
hints = {{"Drag", "lays the shape to the cursor, to a free end: joins it"}, {"Ctrl+wheel", "radius"}, {"Shift+wheel", "another shape"}, {"Release", "build"}, {"Esc", "cancel"}};
|
||||||
else if (m_switch.placing)
|
else if (m_switch.placing)
|
||||||
hints = {{"Drag along", "direction of the switch"}, {"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}};
|
hints = m_switch.connection >= 0 && false == m_switch.curved ?
|
||||||
|
std::vector<key_hint>{{"Drag onto the other track", "the connection there"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}, {"Alt+wheel", "where a double crossover meets"}, {"Esc", "cancel"}} :
|
||||||
|
std::vector<key_hint>{{"Drag along", "direction of the switch"}, {"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}};
|
||||||
else if (m_track_tab == track_tab::lay)
|
else if (m_track_tab == track_tab::lay)
|
||||||
hints = {{"LMB", "start, on the ground or at a free end"}};
|
hints = {{"LMB", "start, on the ground or at a free end"}};
|
||||||
else if (m_track_tab == track_tab::turnout && (selected_track() == nullptr || selected_track()->eType == tt_Switch))
|
else if (m_track_tab == track_tab::turnout && (selected_track() == nullptr || selected_track()->eType == tt_Switch))
|
||||||
@@ -4725,6 +4835,25 @@ void editor_mode::render_layout_combo()
|
|||||||
bool editor_mode::track_wheel(double const Offset)
|
bool editor_mode::track_wheel(double const Offset)
|
||||||
{
|
{
|
||||||
auto const &io{ImGui::GetIO()};
|
auto const &io{ImGui::GetIO()};
|
||||||
|
if (m_switch.placing && m_switch.connection >= 0 && Offset != 0.0 && (io.KeyCtrl || io.KeyShift || io.KeyAlt))
|
||||||
|
{
|
||||||
|
auto &tool{m_switch};
|
||||||
|
int const step{Offset > 0.0 ? 1 : -1};
|
||||||
|
if (io.KeyShift)
|
||||||
|
tool.connection = (tool.connection + step + connection::kind_count) % connection::kind_count;
|
||||||
|
else if (io.KeyCtrl)
|
||||||
|
{
|
||||||
|
static double const radii[]{0.0, 150.0, 190.0, 250.0, 300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1200.0, 1500.0, 2000.0, 3000.0};
|
||||||
|
int index{0};
|
||||||
|
for (int i = 0; i < static_cast<int>(std::size(radii)); ++i)
|
||||||
|
if (radii[i] <= tool.connection_radius + 1e-6)
|
||||||
|
index = i;
|
||||||
|
tool.connection_radius = radii[std::clamp(index + step, 0, static_cast<int>(std::size(radii)) - 1)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tool.connection_together = false == tool.connection_together;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if ((false == io.KeyCtrl && false == io.KeyShift) || Offset == 0.0)
|
if ((false == io.KeyCtrl && false == io.KeyShift) || Offset == 0.0)
|
||||||
return false;
|
return false;
|
||||||
bool const laying{m_lay.active && false == m_lay.points.empty()};
|
bool const laying{m_lay.active && false == m_lay.points.empty()};
|
||||||
@@ -5318,6 +5447,18 @@ bool editor_mode::start_switch_placement()
|
|||||||
void editor_mode::finish_switch_placement()
|
void editor_mode::finish_switch_placement()
|
||||||
{
|
{
|
||||||
auto &tool{m_switch};
|
auto &tool{m_switch};
|
||||||
|
if (tool.connection >= 0)
|
||||||
|
{
|
||||||
|
if (false == tool.curved)
|
||||||
|
{
|
||||||
|
insert_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tool.placing = false;
|
||||||
|
tool.status = STR_C("A connection is laid between straights");
|
||||||
|
ui()->set_status(tool.status, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch_link plan;
|
switch_link plan;
|
||||||
auto const linked{plan_switch_link(plan)};
|
auto const linked{plan_switch_link(plan)};
|
||||||
tool.placing = false;
|
tool.placing = false;
|
||||||
@@ -5373,7 +5514,25 @@ void editor_mode::switch_placed(std::vector<TTrack *> const &Created)
|
|||||||
m_node = *found;
|
m_node = *found;
|
||||||
ui()->set_node(m_node);
|
ui()->set_node(m_node);
|
||||||
if (slip)
|
if (slip)
|
||||||
|
{
|
||||||
|
auto const known{switch_store::named(**found)};
|
||||||
|
if (false == known.has_value() || known->kind != "double")
|
||||||
|
return;
|
||||||
|
std::vector<std::string> driven;
|
||||||
|
for (auto *track : Created)
|
||||||
|
if (track != nullptr && track->eType == tt_Switch && place_switch_drive(*track))
|
||||||
|
{
|
||||||
|
join_last_steps();
|
||||||
|
driven.push_back(track->name());
|
||||||
|
}
|
||||||
|
if (driven.size() == 2)
|
||||||
|
{
|
||||||
|
m_switch.status = format(STR_C("Double switch %s + %s with drives: events %s+, %s- and %s+, %s- switch them, also in the map"), driven[0].c_str(), driven[1].c_str(), driven[0].c_str(), driven[0].c_str(), driven[1].c_str(), driven[1].c_str());
|
||||||
|
ui()->set_status(m_switch.status, false);
|
||||||
|
}
|
||||||
|
advance_switch_name();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if (place_switch_drive(**found))
|
if (place_switch_drive(**found))
|
||||||
join_last_steps();
|
join_last_steps();
|
||||||
advance_switch_name();
|
advance_switch_name();
|
||||||
@@ -5524,30 +5683,59 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e
|
|||||||
auto const point = [&](double const Distance) { return Line.start + glm::dvec3{Line.direction.x, Line.grade, Line.direction.y} * Distance; };
|
auto const point = [&](double const Distance) { return Line.start + glm::dvec3{Line.direction.x, Line.grade, Line.direction.y} * Distance; };
|
||||||
glm::dvec2 const direction{Line.direction * static_cast<double>(Direction)};
|
glm::dvec2 const direction{Line.direction * static_cast<double>(Direction)};
|
||||||
auto const paths{editor_track::place_switch(shape, point(origin), direction, Side, Line.grade * Direction)};
|
auto const paths{editor_track::place_switch(shape, point(origin), direction, Side, Line.grade * Direction)};
|
||||||
auto *track{editor_track::create_switch(shape, paths, Style != nullptr ? *Style : *first, Name)};
|
auto const made{create_switch_set(shape, paths, Style != nullptr ? *Style : *first, Name)};
|
||||||
if (track != nullptr)
|
Created.insert(Created.end(), made.begin(), made.end());
|
||||||
{
|
|
||||||
editor_track::commit({track});
|
|
||||||
record_switch(*track, shape);
|
|
||||||
Created.push_back(track);
|
|
||||||
m_switch.status = STR_C("Switch ") + track->name() + STR_C(" inserted");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
m_switch.status = STR_C("The switch couldn't be created from this template");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<TTrack *> editor_mode::create_switch_set(editor_track::switch_template const &Shape, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name)
|
||||||
|
{
|
||||||
|
if (Shape.twofold() && Paths.size() >= 5)
|
||||||
|
{
|
||||||
|
auto firstshape{Shape};
|
||||||
|
firstshape.length = glm::distance(Paths[0].points[segment_data::point::start], Paths[0].points[segment_data::point::end]);
|
||||||
|
auto secondshape{Shape};
|
||||||
|
secondshape.length = Shape.length - Shape.stagger;
|
||||||
|
auto *one{editor_track::create_switch(firstshape, Paths, Style, Name)};
|
||||||
|
auto *two{one != nullptr ? editor_track::create_switch(secondshape, {Paths[2], Paths[3]}, Style, Name + "b") : nullptr};
|
||||||
|
auto *link{two != nullptr ? editor_track::create_path(Style, Paths[4]) : nullptr};
|
||||||
|
if (link == nullptr)
|
||||||
|
{
|
||||||
|
m_switch.status = STR_C("The switch couldn't be created from this template");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
editor_track::commit({one, two, link});
|
||||||
|
record_double(*one, *two, Shape);
|
||||||
|
m_switch.status = format(STR_C("Double switch %s with its second switch %s inserted"), one->name().c_str(), two->name().c_str());
|
||||||
|
return {one, two, link};
|
||||||
|
}
|
||||||
|
auto *track{editor_track::create_switch(Shape, Paths, Style, Name)};
|
||||||
|
if (track == nullptr)
|
||||||
|
{
|
||||||
|
m_switch.status = STR_C("The switch couldn't be created from this template");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
editor_track::commit({track});
|
||||||
|
record_switch(*track, Shape);
|
||||||
|
m_switch.status = STR_C("Switch ") + track->name() + STR_C(" inserted");
|
||||||
|
return {track};
|
||||||
|
}
|
||||||
|
|
||||||
void editor_mode::draw_build_overlay() const
|
void editor_mode::draw_build_overlay() const
|
||||||
{
|
{
|
||||||
screen_projection const projection;
|
screen_projection const projection;
|
||||||
ImDrawList *drawlist = ImGui::GetBackgroundDrawList(ImGui::GetMainViewport());
|
ImDrawList *drawlist = ImGui::GetBackgroundDrawList(ImGui::GetMainViewport());
|
||||||
auto const drawpath = [&](segment_data const &Path, ImU32 const Color) {
|
auto const drawpath = [&](segment_data const &Path, ImU32 const Color) {
|
||||||
auto previous{bezier{Path}.point(0.0)};
|
for (auto const &piece : Path.pieces.empty() ? std::vector<segment_data>{Path} : Path.pieces)
|
||||||
for (int i = 1; i <= 24; ++i)
|
|
||||||
{
|
{
|
||||||
auto const next{bezier{Path}.point(i / 24.0)};
|
bezier const curve{piece};
|
||||||
projection.line(drawlist, previous, next, Color, 3.0f);
|
auto previous{curve.point(0.0)};
|
||||||
previous = next;
|
for (int i = 1; i <= 24; ++i)
|
||||||
|
{
|
||||||
|
auto const next{curve.point(i / 24.0)};
|
||||||
|
projection.line(drawlist, previous, next, Color, 3.0f);
|
||||||
|
previous = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (false == GfxRenderer->Mouse_Hit() && false == ImGui::GetIO().WantCaptureMouse)
|
if (false == GfxRenderer->Mouse_Hit() && false == ImGui::GetIO().WantCaptureMouse)
|
||||||
@@ -5620,7 +5808,40 @@ void editor_mode::draw_build_overlay() const
|
|||||||
drawpath(piece, overlay_color::marked);
|
drawpath(piece, overlay_color::marked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_switch.placing)
|
if (m_switch.placing && m_switch.connection >= 0 && false == m_switch.curved)
|
||||||
|
{
|
||||||
|
connection::request request;
|
||||||
|
connection::result plan;
|
||||||
|
auto const valid{plan_connection(request, plan)};
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
for (auto const &entry : plan.switches)
|
||||||
|
for (auto const &path : entry.paths)
|
||||||
|
drawpath(path, overlay_color::marked);
|
||||||
|
for (auto const &insert : plan.inserts)
|
||||||
|
drawpath(insert, overlay_color::grip);
|
||||||
|
if (plan.crossing)
|
||||||
|
for (int i = 0; i < 4; i += 2)
|
||||||
|
{
|
||||||
|
segment_data path;
|
||||||
|
path.points[segment_data::point::start] = plan.ends[i];
|
||||||
|
path.points[segment_data::point::end] = plan.ends[i + 1];
|
||||||
|
drawpath(path, overlay_color::grip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for (auto const &piece : switch_preview())
|
||||||
|
drawpath(piece, overlay_color::invalid);
|
||||||
|
ImVec2 screen;
|
||||||
|
auto const text{valid ? connection_readout(plan) : plan.error.empty() ? std::string{STR_C("Drag onto the other track")} : plan.error};
|
||||||
|
if (projection.project(m_switch.mouse, screen))
|
||||||
|
{
|
||||||
|
auto const size{ImGui::CalcTextSize(text.c_str())};
|
||||||
|
drawlist->AddRectFilled(ImVec2(screen.x + 14.0f, screen.y + 10.0f), ImVec2(screen.x + 22.0f + size.x, screen.y + 14.0f + size.y), IM_COL32(0, 0, 0, 180), 3.0f);
|
||||||
|
drawlist->AddText(ImVec2(screen.x + 18.0f, screen.y + 12.0f), valid ? (plan.warning.empty() ? overlay_color::highlight : overlay_color::warning) : overlay_color::invalid, text.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_switch.placing)
|
||||||
{
|
{
|
||||||
for (auto const &piece : switch_preview())
|
for (auto const &piece : switch_preview())
|
||||||
drawpath(piece, overlay_color::marked);
|
drawpath(piece, overlay_color::marked);
|
||||||
@@ -5724,7 +5945,7 @@ void editor_mode::render_switch_ui()
|
|||||||
auto const &entry{tool.templates[i]};
|
auto const &entry{tool.templates[i]};
|
||||||
if (entry.double_slip != Slips)
|
if (entry.double_slip != Slips)
|
||||||
continue;
|
continue;
|
||||||
auto label{entry.source != nullptr ? STR("Scenery: ") + entry.label + " (" + std::to_string(entry.count) + "x)" : "PLK: " + entry.label};
|
auto label{template_label(entry)};
|
||||||
label += "##switch" + std::to_string(i);
|
label += "##switch" + std::to_string(i);
|
||||||
if (ImGui::Selectable(label.c_str(), tool.armed == i))
|
if (ImGui::Selectable(label.c_str(), tool.armed == i))
|
||||||
{
|
{
|
||||||
@@ -5747,6 +5968,7 @@ void editor_mode::render_switch_ui()
|
|||||||
list(true);
|
list(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
render_connection_ui();
|
||||||
ImGui::PushItemWidth(120.0f);
|
ImGui::PushItemWidth(120.0f);
|
||||||
ImGui::InputTextWithHint(STR_C("Name##switchname"), switch_name_for_new().c_str(), tool.name, sizeof(tool.name));
|
ImGui::InputTextWithHint(STR_C("Name##switchname"), switch_name_for_new().c_str(), tool.name, sizeof(tool.name));
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
@@ -5765,6 +5987,10 @@ void editor_mode::render_switch_ui()
|
|||||||
arm_switch();
|
arm_switch();
|
||||||
}
|
}
|
||||||
item_tooltip("Looks for the switches of the scenery, to use their shapes as the templates");
|
item_tooltip("Looks for the switches of the scenery, to use their shapes as the templates");
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton(STR_C("Template editor...")))
|
||||||
|
m_switch_designer.open = true;
|
||||||
|
item_tooltip("Own switch templates: diverging tracks of straights, arcs and transition curves, double switches");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<glm::dvec3> editor_mode::detour_outline() const
|
std::vector<glm::dvec3> editor_mode::detour_outline() const
|
||||||
@@ -6015,9 +6241,7 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
|||||||
if (std::abs(lateral) < 0.01)
|
if (std::abs(lateral) < 0.01)
|
||||||
return false;
|
return false;
|
||||||
auto const side{lateral > 0.0 ? 1.0 : -1.0};
|
auto const side{lateral > 0.0 ? 1.0 : -1.0};
|
||||||
auto const normal{perpendicular * side};
|
auto const &control{(diverging.pieces.empty() ? diverging : diverging.pieces.back()).points[segment_data::point::control2]};
|
||||||
auto const offset{std::abs(lateral)};
|
|
||||||
auto const &control{diverging.points[segment_data::point::control2]};
|
|
||||||
auto const tangent{glm::normalize(control != glm::dvec3{} ? -plan_of(control) : plan_of(frog - origin))};
|
auto const tangent{glm::normalize(control != glm::dvec3{} ? -plan_of(control) : plan_of(frog - origin))};
|
||||||
auto const angle{std::atan2(std::abs(cross(axis, tangent)), glm::dot(axis, tangent))};
|
auto const angle{std::atan2(std::abs(cross(axis, tangent)), glm::dot(axis, tangent))};
|
||||||
if (angle < 1e-4)
|
if (angle < 1e-4)
|
||||||
@@ -6055,22 +6279,6 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
|||||||
auto const &line{Plan.target};
|
auto const &line{Plan.target};
|
||||||
if (line.tracks.empty() || std::abs(cross(line.direction, axis)) > std::sin(glm::radians(1.0)))
|
if (line.tracks.empty() || std::abs(cross(line.direction, axis)) > std::sin(glm::radians(1.0)))
|
||||||
return false;
|
return false;
|
||||||
auto const distance{glm::dot(plan_of(line.start) - plan_of(origin), normal)};
|
|
||||||
auto const remaining{distance - 2.0 * offset};
|
|
||||||
if (remaining < 0.01)
|
|
||||||
return false;
|
|
||||||
auto const length{remaining / std::sin(angle)};
|
|
||||||
glm::dvec2 const crossing{axis * std::cos(angle) + normal * std::sin(angle)};
|
|
||||||
auto const alongfrog{glm::dot(plan_of(frog - origin), axis)};
|
|
||||||
auto const otherfrog{plan_of(frog) + crossing * length};
|
|
||||||
auto const otherorigin{otherfrog + axis * alongfrog + normal * offset};
|
|
||||||
Plan.along = glm::dot(otherorigin - plan_of(line.start), line.direction);
|
|
||||||
if (Plan.along < 0.0 || Plan.along > line.length)
|
|
||||||
return false;
|
|
||||||
Plan.direction = glm::dot(-axis, line.direction) > 0.0 ? 1 : -1;
|
|
||||||
glm::dvec2 const direction{line.direction * static_cast<double>(Plan.direction)};
|
|
||||||
Plan.side = glm::dot(glm::dvec2{-direction.y, direction.x}, -normal) > 0.0 ? 1 : -1;
|
|
||||||
|
|
||||||
editor_track::switch_template shape;
|
editor_track::switch_template shape;
|
||||||
shape.source = tool.track;
|
shape.source = tool.track;
|
||||||
shape.length = glm::dot(plan_of(main.points[segment_data::point::end] - origin), axis);
|
shape.length = glm::dot(plan_of(main.points[segment_data::point::end] - origin), axis);
|
||||||
@@ -6089,11 +6297,14 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Plan.shape = shape;
|
Plan.shape = shape;
|
||||||
auto const station{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * Plan.along};
|
auto const faced{connection::face(frog, tangent, connection::of(line), 1, shape)};
|
||||||
Plan.paths = editor_track::place_switch(shape, station, direction, Plan.side, line.grade * Plan.direction);
|
if (false == faced.valid)
|
||||||
Plan.insert = segment_data{};
|
return false;
|
||||||
Plan.insert.points[segment_data::point::start] = frog;
|
Plan.along = faced.partner.along;
|
||||||
Plan.insert.points[segment_data::point::end] = Plan.paths[1].points[segment_data::point::end];
|
Plan.direction = faced.partner.forward;
|
||||||
|
Plan.side = faced.partner.side;
|
||||||
|
Plan.paths = faced.partner.paths;
|
||||||
|
Plan.insert = faced.insert;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6116,7 +6327,7 @@ bool editor_mode::stretchable(editor_track::snap_target const &Target) const
|
|||||||
bool editor_mode::plan_switch_link(switch_link &Plan) const
|
bool editor_mode::plan_switch_link(switch_link &Plan) const
|
||||||
{
|
{
|
||||||
auto const &tool{m_switch};
|
auto const &tool{m_switch};
|
||||||
if (false == tool.placing || tool.curved || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()) || tool.line.tracks.empty() || tool.templates[tool.armed].double_slip)
|
if (false == tool.placing || tool.curved || tool.connection >= 0 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()) || tool.line.tracks.empty() || tool.templates[tool.armed].double_slip)
|
||||||
return false;
|
return false;
|
||||||
auto const &shape{tool.templates[tool.armed]};
|
auto const &shape{tool.templates[tool.armed]};
|
||||||
auto const &line{tool.line};
|
auto const &line{tool.line};
|
||||||
@@ -6697,13 +6908,11 @@ std::vector<segment_data> editor_mode::curved_switch_paths(editor_track::switch_
|
|||||||
path.points[segment_data::point::control2] = {-Enddirection.x * handle, -(End.y - Start.y) / 3.0, -Enddirection.y * handle};
|
path.points[segment_data::point::control2] = {-Enddirection.x * handle, -(End.y - Start.y) / 3.0, -Enddirection.y * handle};
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
std::vector<segment_data> result;
|
auto const bend = [&](segment_data const &Local) {
|
||||||
for (auto const &local : Shape.local)
|
auto const &start{Local.points[segment_data::point::start]};
|
||||||
{
|
auto const &end{Local.points[segment_data::point::end]};
|
||||||
auto const &start{local.points[segment_data::point::start]};
|
auto const &control1{Local.points[segment_data::point::control1]};
|
||||||
auto const &end{local.points[segment_data::point::end]};
|
auto const &control2{Local.points[segment_data::point::control2]};
|
||||||
auto const &control1{local.points[segment_data::point::control1]};
|
|
||||||
auto const &control2{local.points[segment_data::point::control2]};
|
|
||||||
glm::dvec2 const chord{end.x - start.x, end.z - start.z};
|
glm::dvec2 const chord{end.x - start.x, end.z - start.z};
|
||||||
auto const localstart{glm::normalize(control1 != glm::dvec3{} ? plan_of(control1) : chord)};
|
auto const localstart{glm::normalize(control1 != glm::dvec3{} ? plan_of(control1) : chord)};
|
||||||
auto const localend{glm::normalize(control2 != glm::dvec3{} ? -plan_of(control2) : chord)};
|
auto const localend{glm::normalize(control2 != glm::dvec3{} ? -plan_of(control2) : chord)};
|
||||||
@@ -6729,19 +6938,35 @@ std::vector<segment_data> editor_mode::curved_switch_paths(editor_track::switch_
|
|||||||
auto const rollstart{sample_at(Frame, Station + Direction * start.z).roll * Direction};
|
auto const rollstart{sample_at(Frame, Station + Direction * start.z).roll * Direction};
|
||||||
auto const rollend{sample_at(Frame, Station + Direction * end.z).roll * Direction};
|
auto const rollend{sample_at(Frame, Station + Direction * end.z).roll * Direction};
|
||||||
path.rolls = {static_cast<float>(rollstart), static_cast<float>(rollend)};
|
path.rolls = {static_cast<float>(rollstart), static_cast<float>(rollend)};
|
||||||
path.radius = local.radius;
|
path.radius = Local.radius;
|
||||||
{
|
{
|
||||||
bezier const curve{path};
|
bezier const curve{path};
|
||||||
auto const begin{plan_of(curve.first(0.0))};
|
auto const begin{plan_of(curve.first(0.0))};
|
||||||
auto const finish{plan_of(curve.first(1.0))};
|
auto const finish{plan_of(curve.first(1.0))};
|
||||||
auto const turn{glm::length(begin) > 1e-9 && glm::length(finish) > 1e-9 ? geometry::signed_angle(begin, finish) : 0.0};
|
auto const turn{glm::length(begin) > 1e-9 && glm::length(finish) > 1e-9 ? geometry::signed_angle(begin, finish) : 0.0};
|
||||||
if (local.radius != 0.f && std::abs(turn) > 1e-6)
|
if (Local.radius != 0.f && std::abs(turn) > 1e-6)
|
||||||
path.radius = static_cast<float>(std::copysign(curve.plan_length() / std::abs(turn), static_cast<double>(local.radius)));
|
path.radius = static_cast<float>(std::copysign(curve.plan_length() / std::abs(turn), static_cast<double>(Local.radius)));
|
||||||
}
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
std::vector<segment_data> locals{Shape.local.begin(), Shape.local.end()};
|
||||||
|
if (Shape.twofold())
|
||||||
|
{
|
||||||
|
locals.insert(locals.end(), Shape.second.begin(), Shape.second.end());
|
||||||
|
locals.push_back(Shape.link);
|
||||||
|
}
|
||||||
|
std::vector<segment_data> result;
|
||||||
|
for (std::size_t i = 0; i < locals.size(); ++i)
|
||||||
|
{
|
||||||
|
auto const &local{locals[i]};
|
||||||
|
auto path{bend(local)};
|
||||||
|
for (auto const &piece : local.pieces)
|
||||||
|
path.pieces.push_back(bend(piece));
|
||||||
|
// the main tracks follow the curve they are laid into
|
||||||
|
if (i % 2 == 0)
|
||||||
|
path.radius = sample_at(Frame, Station + Direction * 0.5 * (local.points[segment_data::point::start].z + local.points[segment_data::point::end].z)).radius;
|
||||||
result.push_back(path);
|
result.push_back(path);
|
||||||
}
|
}
|
||||||
if (false == result.empty())
|
|
||||||
result.front().radius = sample_at(Frame, Station).radius;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6799,16 +7024,10 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto const paths{curved_switch_paths(shape, tool.frame, station, Direction, Side)};
|
auto const paths{curved_switch_paths(shape, tool.frame, station, Direction, Side)};
|
||||||
auto *track{editor_track::create_switch(shape, paths, *tool.frame_tracks.front(), switch_name_for_new())};
|
auto const made{create_switch_set(shape, paths, *tool.frame_tracks.front(), switch_name_for_new())};
|
||||||
if (track != nullptr)
|
created.insert(created.end(), made.begin(), made.end());
|
||||||
{
|
if (made.size() == 1)
|
||||||
editor_track::commit({track});
|
tool.status = STR_C("Switch ") + made.front()->name() + STR_C(" inserted into the curve");
|
||||||
record_switch(*track, shape);
|
|
||||||
created.push_back(track);
|
|
||||||
tool.status = STR_C("Switch ") + track->name() + STR_C(" inserted into the curve");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
tool.status = STR_C("The switch couldn't be created from this template");
|
|
||||||
auto const placed{created};
|
auto const placed{created};
|
||||||
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
|
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
|
||||||
straight_refresh();
|
straight_refresh();
|
||||||
|
|||||||
@@ -405,6 +405,27 @@ void editor_mode::track_gizmo_take_switches(TTrack &Track)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void editor_mode::track_gizmo_take_double(TTrack &Track)
|
||||||
|
{
|
||||||
|
auto const set{double_of(Track)};
|
||||||
|
if (false == set.has_value() || Track.eType != tt_Switch)
|
||||||
|
return;
|
||||||
|
auto *other{&Track == set->first ? set->second : set->first};
|
||||||
|
if (false == editable(*set->link) || false == editable(*other) || track_gizmo_clustered(set->link))
|
||||||
|
return;
|
||||||
|
editor_track::point_ref const own{0, &Track == set->first ? editor_track::point_kind::end : editor_track::point_kind::start};
|
||||||
|
auto const position{editor_track::point_position(Track, own)};
|
||||||
|
for (auto const &connection : editor_track::connected_points(Track, position))
|
||||||
|
{
|
||||||
|
if (connection.first != set->link)
|
||||||
|
continue;
|
||||||
|
std::erase_if(m_track_joints, [&](editor_track::joint const &Joint) { return Joint.other == set->link; });
|
||||||
|
auto const movable = [&](TTrack const *Each) { return Each == set->link || Each == other; };
|
||||||
|
m_track_gizmo_drag.switches.push_back(track_gizmo_link(Track, own, connection, movable));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void editor_mode::track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode)
|
void editor_mode::track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode)
|
||||||
{
|
{
|
||||||
auto &drag{m_track_gizmo_drag};
|
auto &drag{m_track_gizmo_drag};
|
||||||
@@ -482,6 +503,8 @@ void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_o
|
|||||||
track_gizmo_take_partners(Track);
|
track_gizmo_take_partners(Track);
|
||||||
if (false == whole && Track.eType == tt_Switch && m_track_drag_connected)
|
if (false == whole && Track.eType == tt_Switch && m_track_drag_connected)
|
||||||
track_gizmo_take_switch_joints(Track, controlmode);
|
track_gizmo_take_switch_joints(Track, controlmode);
|
||||||
|
if (whole)
|
||||||
|
track_gizmo_take_double(Track);
|
||||||
if (m_track_drag_connected)
|
if (m_track_drag_connected)
|
||||||
track_gizmo_take_switches(Track);
|
track_gizmo_take_switches(Track);
|
||||||
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||||
|
|||||||
626
editor/editorConnections.cpp
Normal file
626
editor/editorConnections.cpp
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
/*
|
||||||
|
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/editorConnections.hpp"
|
||||||
|
#include "editor/editorFormat.hpp"
|
||||||
|
#include "editor/editorGeometry.hpp"
|
||||||
|
#include "utilities/translation.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace connection
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using geometry::bezier;
|
||||||
|
using geometry::cross;
|
||||||
|
using geometry::grade_of;
|
||||||
|
using geometry::has_controls;
|
||||||
|
using geometry::plan_of;
|
||||||
|
using geometry::signed_angle;
|
||||||
|
|
||||||
|
double constexpr kStraightTolerance{2e-3};
|
||||||
|
double constexpr kCrossingHalf{2.0};
|
||||||
|
|
||||||
|
struct branch
|
||||||
|
{
|
||||||
|
glm::dvec3 end{0.0};
|
||||||
|
glm::dvec2 heading{0.0, 1.0};
|
||||||
|
double grade{0.0};
|
||||||
|
double tail{0.0}; // straight at the end of the diverging track
|
||||||
|
};
|
||||||
|
|
||||||
|
bool plain(segment_data const &Piece)
|
||||||
|
{
|
||||||
|
if (false == has_controls(Piece))
|
||||||
|
return true;
|
||||||
|
bezier const curve{Piece};
|
||||||
|
auto const first{plan_of(curve.first(0.0))};
|
||||||
|
auto const last{plan_of(curve.first(1.0))};
|
||||||
|
if (glm::length(first) < 1e-9 || glm::length(last) < 1e-9)
|
||||||
|
return true;
|
||||||
|
return std::abs(signed_angle(first, last)) < 1e-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
branch branch_of(std::vector<segment_data> const &Paths)
|
||||||
|
{
|
||||||
|
branch result;
|
||||||
|
if (Paths.size() < 2)
|
||||||
|
return result;
|
||||||
|
auto const &path{Paths[1]};
|
||||||
|
result.end = path.points[segment_data::point::end];
|
||||||
|
auto const &last{path.pieces.empty() ? path : path.pieces.back()};
|
||||||
|
auto tangent{bezier{last}.first(1.0)};
|
||||||
|
if (glm::length(plan_of(tangent)) < 1e-9)
|
||||||
|
tangent = last.points[segment_data::point::end] - last.points[segment_data::point::start];
|
||||||
|
result.heading = glm::normalize(plan_of(tangent));
|
||||||
|
result.grade = grade_of(tangent);
|
||||||
|
for (auto piece{path.pieces.rbegin()}; piece != path.pieces.rend() && plain(*piece); ++piece)
|
||||||
|
result.tail += bezier{*piece}.plan_length();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec2 middle_point(placed const &Switch)
|
||||||
|
{
|
||||||
|
auto const &main{Switch.paths[0]};
|
||||||
|
auto const origin{plan_of(main.points[segment_data::point::start])};
|
||||||
|
auto const axis{glm::normalize(plan_of(main.points[segment_data::point::end] - main.points[segment_data::point::start]))};
|
||||||
|
auto const diverging{branch_of(Switch.paths)};
|
||||||
|
auto const determinant{cross(diverging.heading, axis)};
|
||||||
|
if (std::abs(determinant) < 1e-9)
|
||||||
|
return origin;
|
||||||
|
return origin + axis * (cross(diverging.heading, plan_of(diverging.end) - origin) / determinant);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec3 start_of(placed const &Switch)
|
||||||
|
{
|
||||||
|
return Switch.paths[0].points[segment_data::point::start];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fits(line const &Line, double const Along, int const Forward, double const Length)
|
||||||
|
{
|
||||||
|
auto const from{Forward > 0 ? Along : Along - Length};
|
||||||
|
return from >= -1e-3 && from + Length <= Line.length + 1e-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
placed put(line const &Line, int const Index, double const Along, int const Forward, int const Side, editor_track::switch_template const &Shape, bool const Second)
|
||||||
|
{
|
||||||
|
placed result;
|
||||||
|
result.line = Index;
|
||||||
|
result.along = Along;
|
||||||
|
result.forward = Forward;
|
||||||
|
result.side = Side;
|
||||||
|
result.second = Second;
|
||||||
|
result.paths = editor_track::place_switch(Shape, Line.point(Along), Line.direction * static_cast<double>(Forward), Side, Line.grade * Forward);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_data straight(glm::dvec3 const &From, glm::dvec3 const &To)
|
||||||
|
{
|
||||||
|
segment_data path;
|
||||||
|
path.points[segment_data::point::start] = From;
|
||||||
|
path.points[segment_data::point::end] = To;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sharper(editor_track::switch_template const &One, editor_track::switch_template const &Two)
|
||||||
|
{
|
||||||
|
double result{0.0};
|
||||||
|
for (auto const radius : {One.radius, Two.radius})
|
||||||
|
if (radius > 1.0 && (result == 0.0 || radius < result))
|
||||||
|
result = radius;
|
||||||
|
return result > 0.0 ? result : 190.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crossing_over
|
||||||
|
{
|
||||||
|
bool valid{false};
|
||||||
|
bool straight{false};
|
||||||
|
std::string error;
|
||||||
|
std::string warning;
|
||||||
|
placed one;
|
||||||
|
placed two;
|
||||||
|
std::vector<segment_data> inserts;
|
||||||
|
std::vector<double> straights;
|
||||||
|
double radius{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// the first switch on From diverging towards To and the switch on To facing it, joined by a straight insert when their angles
|
||||||
|
// agree, by an aligning curve between straight inserts when they don't, the switch on To then starting as near Target as it can
|
||||||
|
crossing_over cross_over(line const &From, int const Fromindex, double const Along, int const Forward, editor_track::switch_template const &Fromshape, bool const Fromsecond, line const &To, int const Toindex,
|
||||||
|
editor_track::switch_template const &Toshape, bool const Tosecond, double const Target, request const &Request)
|
||||||
|
{
|
||||||
|
crossing_over result;
|
||||||
|
if (false == fits(From, Along, Forward, Fromshape.length))
|
||||||
|
{
|
||||||
|
result.error = STR_C("A switch of the connection runs off the end of its straight");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
glm::dvec2 const direction{From.direction * static_cast<double>(Forward)};
|
||||||
|
auto const origin{From.point(Along)};
|
||||||
|
auto const nearest{To.point(To.along(origin))};
|
||||||
|
auto const side{cross(direction, plan_of(nearest) - plan_of(origin)) > 0.0 ? 1 : -1};
|
||||||
|
result.one = put(From, Fromindex, Along, Forward, side, Fromshape, Fromsecond);
|
||||||
|
if (result.one.paths.size() < 2)
|
||||||
|
{
|
||||||
|
result.error = STR_C("The switch template has no diverging track");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const diverging{branch_of(result.one.paths)};
|
||||||
|
auto faced{face(diverging.end, diverging.heading, To, Toindex, Toshape)};
|
||||||
|
if (faced.valid)
|
||||||
|
{
|
||||||
|
faced.partner.second = Tosecond;
|
||||||
|
result.two = faced.partner;
|
||||||
|
result.straight = true;
|
||||||
|
if (faced.length >= 0.01)
|
||||||
|
result.inserts.push_back(faced.insert);
|
||||||
|
result.straights.push_back(faced.length);
|
||||||
|
auto const curves{faced.length + diverging.tail + branch_of(result.two.paths).tail};
|
||||||
|
if (curves < Request.insert - 1e-3)
|
||||||
|
result.warning = format(STR_C("The straight between the curves of the switches is %.2f m, under %.0f m"), curves, Request.insert);
|
||||||
|
result.valid = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (faced.partner.paths.empty())
|
||||||
|
{
|
||||||
|
result.error = faced.error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const forward{faced.partner.forward};
|
||||||
|
auto const otherside{faced.partner.side};
|
||||||
|
auto const radius{Request.radius > 0.0 ? Request.radius : sharper(Fromshape, Toshape)};
|
||||||
|
auto const trial{branch_of(put(To, Toindex, 0.0, forward, otherside, Toshape, Tosecond).paths)};
|
||||||
|
auto const into{-trial.heading};
|
||||||
|
auto const turn{signed_angle(diverging.heading, into)};
|
||||||
|
auto const determinant{cross(diverging.heading, into)};
|
||||||
|
if (std::abs(determinant) < 1e-9 || std::abs(turn) > glm::radians(60.0))
|
||||||
|
{
|
||||||
|
result.error = STR_C("The switches can't face each other across these tracks");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const tangent{radius * std::tan(std::abs(turn) * 0.5)};
|
||||||
|
auto const offset{plan_of(trial.end) - plan_of(diverging.end)};
|
||||||
|
auto const reach{cross(offset, into) / determinant};
|
||||||
|
auto const after{cross(diverging.heading, offset) / determinant};
|
||||||
|
auto const reachrate{cross(To.direction, into) / determinant};
|
||||||
|
auto const afterrate{cross(diverging.heading, To.direction) / determinant};
|
||||||
|
auto low{forward > 0 ? 0.0 : Toshape.length};
|
||||||
|
auto high{forward > 0 ? To.length - Toshape.length : To.length};
|
||||||
|
auto const bound = [&](double const Value, double const Rate, double const Need) {
|
||||||
|
if (std::abs(Rate) < 1e-12)
|
||||||
|
{
|
||||||
|
if (Value - tangent < Need - 1e-6)
|
||||||
|
{
|
||||||
|
low = std::numeric_limits<double>::infinity();
|
||||||
|
high = -low;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const limit{(Need + tangent - Value) / Rate};
|
||||||
|
if (Rate > 0.0)
|
||||||
|
low = std::max(low, limit);
|
||||||
|
else
|
||||||
|
high = std::min(high, limit);
|
||||||
|
};
|
||||||
|
bound(reach, reachrate, std::max(0.0, Request.insert - diverging.tail));
|
||||||
|
bound(after, afterrate, std::max(0.0, Request.insert - trial.tail));
|
||||||
|
if (low > high + 1e-6)
|
||||||
|
{
|
||||||
|
result.error = format(STR_C("The aligning curve R %.0f m with %.0f m straights doesn't fit between the switches"), radius, Request.insert);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.two = put(To, Toindex, std::clamp(Target, low, std::max(low, high)), forward, otherside, Toshape, Tosecond);
|
||||||
|
auto const arrived{branch_of(result.two.paths)};
|
||||||
|
auto const link{editor_track::link_branch({diverging.end, diverging.heading, diverging.grade, 0.0}, radius, {arrived.end, -arrived.heading, -arrived.grade, 0.0}, 1e-4, Request.insert, false)};
|
||||||
|
if (false == link.valid || link.pieces.empty() || link.trim != 0.0 || link.reverse)
|
||||||
|
{
|
||||||
|
result.error = format(STR_C("The aligning curve R %.0f m with %.0f m straights doesn't fit between the switches"), radius, Request.insert);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.inserts = link.pieces;
|
||||||
|
result.straights = {link.before, link.after};
|
||||||
|
result.radius = radius;
|
||||||
|
result.valid = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void take(result &Result, crossing_over const &Part, bool const Inserts)
|
||||||
|
{
|
||||||
|
Result.switches.push_back(Part.one);
|
||||||
|
Result.switches.push_back(Part.two);
|
||||||
|
if (Inserts)
|
||||||
|
Result.inserts.insert(Result.inserts.end(), Part.inserts.begin(), Part.inserts.end());
|
||||||
|
Result.straights.insert(Result.straights.end(), Part.straights.begin(), Part.straights.end());
|
||||||
|
if (Part.radius > 0.0)
|
||||||
|
Result.radius = Part.radius;
|
||||||
|
if (Result.warning.empty())
|
||||||
|
Result.warning = Part.warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overlapping(std::vector<placed> const &Switches, std::vector<line> const &Lines)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < Switches.size(); ++i)
|
||||||
|
for (std::size_t j = i + 1; j < Switches.size(); ++j)
|
||||||
|
{
|
||||||
|
auto const &one{Switches[i]};
|
||||||
|
auto const &two{Switches[j]};
|
||||||
|
if (one.line != two.line || one.line < 0 || one.line >= static_cast<int>(Lines.size()))
|
||||||
|
continue;
|
||||||
|
auto const &line{Lines[one.line]};
|
||||||
|
auto const span = [&](placed const &Switch) {
|
||||||
|
auto const a{line.along(Switch.paths[0].points[segment_data::point::start])};
|
||||||
|
auto const b{line.along(Switch.paths[0].points[segment_data::point::end])};
|
||||||
|
return std::pair{std::min(a, b), std::max(a, b)};
|
||||||
|
};
|
||||||
|
auto const [from1, to1]{span(one)};
|
||||||
|
auto const [from2, to2]{span(two)};
|
||||||
|
if (from2 < to1 - 0.01 && from1 < to2 - 0.01)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
char const *word(kind const Kind)
|
||||||
|
{
|
||||||
|
switch (Kind)
|
||||||
|
{
|
||||||
|
case kind::double_crossover: return "double_crossover";
|
||||||
|
case kind::scissors: return "scissors";
|
||||||
|
case kind::ladder: return "ladder";
|
||||||
|
default: return "crossover";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse(std::string const &Word, kind &Kind)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < kind_count; ++i)
|
||||||
|
if (Word == word(static_cast<kind>(i)))
|
||||||
|
{
|
||||||
|
Kind = static_cast<kind>(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec3 line::point(double const Along) const
|
||||||
|
{
|
||||||
|
return start + glm::dvec3{direction.x, grade, direction.y} * Along;
|
||||||
|
}
|
||||||
|
|
||||||
|
double line::along(glm::dvec3 const &Point) const
|
||||||
|
{
|
||||||
|
return glm::dot(plan_of(Point) - plan_of(start), direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
double line::lateral(glm::dvec3 const &Point) const
|
||||||
|
{
|
||||||
|
return cross(direction, plan_of(Point) - plan_of(start));
|
||||||
|
}
|
||||||
|
|
||||||
|
line of(editor_track::straight const &Straight)
|
||||||
|
{
|
||||||
|
line result;
|
||||||
|
result.start = Straight.start;
|
||||||
|
result.direction = Straight.direction;
|
||||||
|
result.grade = Straight.grade;
|
||||||
|
result.length = Straight.length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
facing face(glm::dvec3 const &End, glm::dvec2 const &Heading, line const &To, int const Index, editor_track::switch_template const &Shape)
|
||||||
|
{
|
||||||
|
facing result;
|
||||||
|
auto const forward{glm::dot(To.direction, Heading) > 0.0 ? -1 : 1};
|
||||||
|
glm::dvec2 const direction{To.direction * static_cast<double>(forward)};
|
||||||
|
auto const side{cross(direction, plan_of(End) - plan_of(To.start)) > 0.0 ? 1 : -1};
|
||||||
|
auto const trial{put(To, Index, 0.0, forward, side, Shape, true)};
|
||||||
|
if (trial.paths.size() < 2)
|
||||||
|
{
|
||||||
|
result.error = STR_C("The switch template has no diverging track");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const diverging{branch_of(trial.paths)};
|
||||||
|
auto const mismatch{signed_angle(Heading, -diverging.heading)};
|
||||||
|
if (std::abs(mismatch) > kStraightTolerance)
|
||||||
|
{
|
||||||
|
result.partner = trial;
|
||||||
|
result.error = format(STR_C("The diverging tracks meet at %.2f deg, a straight insert can't join them"), glm::degrees(std::abs(mismatch)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const across{cross(Heading, To.direction)};
|
||||||
|
if (std::abs(across) < 1e-9)
|
||||||
|
{
|
||||||
|
result.error = STR_C("The diverging track runs along the other track");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const shift{-cross(Heading, plan_of(diverging.end) - plan_of(End)) / across};
|
||||||
|
auto const meeting{plan_of(diverging.end) + To.direction * shift};
|
||||||
|
result.length = glm::dot(Heading, meeting - plan_of(End));
|
||||||
|
if (result.length < -0.005)
|
||||||
|
{
|
||||||
|
result.error = format(STR_C("The tracks are %.2f m too close for these switches"), -result.length * std::abs(across));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.length = std::max(0.0, result.length);
|
||||||
|
if (false == fits(To, shift, forward, Shape.length))
|
||||||
|
{
|
||||||
|
result.error = STR_C("The switch on the other track runs off the end of its straight");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.partner = put(To, Index, shift, forward, side, Shape, true);
|
||||||
|
result.insert = straight(End, result.partner.paths[1].points[segment_data::point::end]);
|
||||||
|
result.valid = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result plan(request const &Request)
|
||||||
|
{
|
||||||
|
result out;
|
||||||
|
auto const usable = [](editor_track::switch_template const &Shape) { return false == Shape.double_slip && false == Shape.twofold() && Shape.length > 0.5; };
|
||||||
|
if (false == usable(Request.first) || false == usable(Request.second))
|
||||||
|
{
|
||||||
|
out.error = STR_C("A connection is made of ordinary switches, not of slips, crossings or double switches");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const &from{Request.from};
|
||||||
|
glm::dvec2 const travel{from.direction * static_cast<double>(Request.forward)};
|
||||||
|
auto const origin{from.point(Request.along)};
|
||||||
|
std::vector<line> lines{from};
|
||||||
|
lines.insert(lines.end(), Request.to.begin(), Request.to.end());
|
||||||
|
|
||||||
|
if (Request.type == kind::ladder)
|
||||||
|
{
|
||||||
|
auto side{Request.side >= 0 ? 1 : -1};
|
||||||
|
std::vector<double> offsets;
|
||||||
|
if (false == Request.to.empty())
|
||||||
|
{
|
||||||
|
auto const &first{Request.to.front()};
|
||||||
|
side = cross(travel, plan_of(first.point(first.along(origin))) - plan_of(origin)) > 0.0 ? 1 : -1;
|
||||||
|
for (auto const &fan : Request.to)
|
||||||
|
{
|
||||||
|
auto const offset{side * cross(travel, plan_of(fan.point(fan.along(origin))) - plan_of(origin))};
|
||||||
|
if (offset < 1.0 || (false == offsets.empty() && offset < offsets.back() + 1.0) || std::abs(cross(fan.direction, from.direction)) > 1e-3)
|
||||||
|
{
|
||||||
|
out.error = STR_C("The tracks of a fan have to be parallel, on one side and at least 1 m apart");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
offsets.push_back(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double sum{0.0};
|
||||||
|
for (auto const spacing : Request.spacings)
|
||||||
|
{
|
||||||
|
if (spacing < 1.0)
|
||||||
|
{
|
||||||
|
out.error = STR_C("The spacings of the tracks of a ladder have to be at least 1 m");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
offsets.push_back(sum += spacing);
|
||||||
|
}
|
||||||
|
if (offsets.empty())
|
||||||
|
{
|
||||||
|
out.error = STR_C("Give the spacings of the tracks of the ladder");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (false == fits(from, Request.along, Request.forward, Request.first.length))
|
||||||
|
{
|
||||||
|
out.error = STR_C("A switch of the connection runs off the end of its straight");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const start{put(from, 0, Request.along, Request.forward, side, Request.first, false)};
|
||||||
|
auto const ladder{branch_of(start.paths)};
|
||||||
|
auto const climb{side * cross(travel, ladder.heading)};
|
||||||
|
if (climb < 1e-4)
|
||||||
|
{
|
||||||
|
out.error = STR_C("The switch template has no diverging track");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
out.switches.push_back(start);
|
||||||
|
auto const lateral = [&](glm::dvec3 const &Point) { return side * cross(travel, plan_of(Point) - plan_of(origin)); };
|
||||||
|
glm::dvec3 const step{ladder.heading.x, ladder.grade, ladder.heading.y};
|
||||||
|
auto const rung = [&](double const Along) { return ladder.end + step * Along; };
|
||||||
|
auto const back{cross(ladder.heading, travel) > 0.0 ? 1 : -1};
|
||||||
|
auto const radius{Request.radius > 0.0 ? Request.radius : sharper(Request.second, Request.second)};
|
||||||
|
auto const settle = [&](glm::dvec3 const &Point, glm::dvec2 const &Heading, double const Grade) {
|
||||||
|
auto const turn{signed_angle(Heading, travel)};
|
||||||
|
return std::abs(turn) > kStraightTolerance ? geometry::arc_pieces(Point, Heading, Grade, radius, std::abs(turn), turn > 0.0 ? 1 : -1) : std::vector<segment_data>{};
|
||||||
|
};
|
||||||
|
double reached{0.0};
|
||||||
|
for (std::size_t i = 0; i < offsets.size(); ++i)
|
||||||
|
{
|
||||||
|
bool const last{i + 1 == offsets.size()};
|
||||||
|
std::vector<segment_data> paths;
|
||||||
|
std::vector<segment_data> bend;
|
||||||
|
glm::dvec3 landing{0.0};
|
||||||
|
if (false == last)
|
||||||
|
{
|
||||||
|
paths = editor_track::place_switch(Request.second, rung(0.0), ladder.heading, back, ladder.grade);
|
||||||
|
auto const diverging{branch_of(paths)};
|
||||||
|
bend = settle(diverging.end, diverging.heading, diverging.grade);
|
||||||
|
landing = bend.empty() ? diverging.end : bend.back().points[segment_data::point::end];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bend = settle(rung(0.0), ladder.heading, ladder.grade);
|
||||||
|
landing = bend.empty() ? rung(0.0) : bend.back().points[segment_data::point::end];
|
||||||
|
}
|
||||||
|
auto const along{(offsets[i] - lateral(landing)) / climb};
|
||||||
|
if (along < reached - 1e-3)
|
||||||
|
{
|
||||||
|
out.error = format(STR_C("Track %d of the fan is %.2f m too close to the previous one for these switches"), static_cast<int>(i + 1), (reached - along) * climb);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const shift{step * along};
|
||||||
|
if (along - reached >= 0.01)
|
||||||
|
{
|
||||||
|
out.inserts.push_back(straight(rung(reached), rung(along)));
|
||||||
|
out.straights.push_back(along - reached);
|
||||||
|
}
|
||||||
|
for (auto &path : paths)
|
||||||
|
path.translate(shift);
|
||||||
|
for (auto &piece : bend)
|
||||||
|
piece.translate(shift);
|
||||||
|
if (false == last)
|
||||||
|
{
|
||||||
|
placed rungswitch;
|
||||||
|
rungswitch.line = -1;
|
||||||
|
rungswitch.along = along;
|
||||||
|
rungswitch.side = back;
|
||||||
|
rungswitch.second = true;
|
||||||
|
rungswitch.paths = paths;
|
||||||
|
out.switches.push_back(rungswitch);
|
||||||
|
reached = along + Request.second.length;
|
||||||
|
}
|
||||||
|
if (false == bend.empty())
|
||||||
|
out.radius = radius;
|
||||||
|
out.inserts.insert(out.inserts.end(), bend.begin(), bend.end());
|
||||||
|
auto const fanstart{landing + shift};
|
||||||
|
if (i < Request.to.size())
|
||||||
|
{
|
||||||
|
auto const &fan{Request.to[i]};
|
||||||
|
auto end{fan.start};
|
||||||
|
auto ahead{glm::dot(travel, plan_of(fan.start) - plan_of(fanstart))};
|
||||||
|
auto const other{fan.point(fan.length)};
|
||||||
|
if (auto const further{glm::dot(travel, plan_of(other) - plan_of(fanstart))}; further < ahead)
|
||||||
|
{
|
||||||
|
end = other;
|
||||||
|
ahead = further;
|
||||||
|
}
|
||||||
|
if (ahead < -0.01)
|
||||||
|
{
|
||||||
|
out.error = format(STR_C("Track %d of the fan reaches into the ladder, it has to start %.2f m further on"), static_cast<int>(i + 1), -ahead);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (ahead >= 0.01)
|
||||||
|
out.inserts.push_back(straight(fanstart, end));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
out.inserts.push_back(straight(fanstart, fanstart + glm::dvec3{travel.x, from.grade * Request.forward, travel.y} * std::max(1.0, Request.stub)));
|
||||||
|
}
|
||||||
|
out.fan = offsets;
|
||||||
|
out.spacing = offsets.front();
|
||||||
|
out.valid = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request.to.empty())
|
||||||
|
{
|
||||||
|
out.error = STR_C("Drag onto the other track");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const &to{Request.to.front()};
|
||||||
|
out.spacing = std::abs(to.lateral(origin));
|
||||||
|
auto const one{cross_over(from, 0, Request.along, Request.forward, Request.first, false, to, 1, Request.second, true, Request.target, Request)};
|
||||||
|
if (false == one.valid)
|
||||||
|
{
|
||||||
|
out.error = one.error;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
out.points = glm::distance(middle_point(one.one), middle_point(one.two));
|
||||||
|
if (Request.type == kind::crossover)
|
||||||
|
{
|
||||||
|
take(out, one, true);
|
||||||
|
out.valid = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const towards{glm::dot(to.direction, travel) > 0.0 ? 1 : -1};
|
||||||
|
auto const span{glm::dot(travel, plan_of(start_of(one.two)) - plan_of(start_of(one.one)))};
|
||||||
|
if (Request.type == kind::double_crossover)
|
||||||
|
{
|
||||||
|
crossing_over two;
|
||||||
|
if (false == Request.together)
|
||||||
|
{
|
||||||
|
auto const along{one.two.along + towards * std::max(0.0, Request.gap)};
|
||||||
|
two = cross_over(to, 1, along, towards, Request.second, true, from, 0, Request.first, false, from.along(to.point(along)) + Request.forward * span, Request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const along{Request.along - Request.forward * std::max(0.0, Request.gap)};
|
||||||
|
two = cross_over(from, 0, along, -Request.forward, Request.first, false, to, 1, Request.second, true, to.along(from.point(along)) - towards * span, Request);
|
||||||
|
}
|
||||||
|
if (false == two.valid)
|
||||||
|
{
|
||||||
|
out.error = two.error;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
take(out, one, true);
|
||||||
|
take(out, two, true);
|
||||||
|
if (overlapping(out.switches, lines))
|
||||||
|
{
|
||||||
|
out.error = STR_C("The switches of the two crossovers overlap, the gap between them has to be longer");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
out.valid = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (false == one.straight || std::abs(cross(from.direction, to.direction)) > 1e-4)
|
||||||
|
{
|
||||||
|
out.error = STR_C("A scissors crossover needs parallel tracks and switches of the same angle");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const p1{branch_of(one.one.paths).end};
|
||||||
|
auto const p2{branch_of(one.two.paths).end};
|
||||||
|
auto const middle{(p1 + p2) * 0.5};
|
||||||
|
auto const two{cross_over(from, 0, 2.0 * from.along(middle) - Request.along, -Request.forward, Request.first, false, to, 1, Request.second, true, 0.0, Request)};
|
||||||
|
if (false == two.valid || false == two.straight)
|
||||||
|
{
|
||||||
|
out.error = two.valid ? std::string{STR_C("A scissors crossover needs parallel tracks and switches of the same angle")} : two.error;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
take(out, one, false);
|
||||||
|
take(out, two, false);
|
||||||
|
if (overlapping(out.switches, lines))
|
||||||
|
{
|
||||||
|
out.error = STR_C("The tracks are too close for a scissors crossover with these switches, its switches overlap");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const q1{branch_of(two.one.paths).end};
|
||||||
|
auto const q2{branch_of(two.two.paths).end};
|
||||||
|
auto const first{plan_of(p2 - p1)};
|
||||||
|
auto const second{plan_of(q2 - q1)};
|
||||||
|
auto const determinant{cross(first, second)};
|
||||||
|
auto const sine{std::abs(cross(glm::normalize(travel), glm::length(first) > 1e-9 ? glm::normalize(first) : travel))};
|
||||||
|
auto const shortfall{(2.0 * kCrossingHalf - one.straights.front()) * sine};
|
||||||
|
if (std::abs(determinant) < 1e-9 || glm::length(first) < 2.0 * kCrossingHalf || glm::length(second) < 2.0 * kCrossingHalf)
|
||||||
|
{
|
||||||
|
out.error = format(STR_C("The tracks are too close for the crossing of a scissors crossover: %.2f m more spacing is needed"), std::max(0.01, shortfall));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
auto const fraction{cross(plan_of(q1) - plan_of(p1), second) / determinant};
|
||||||
|
auto const meeting{plan_of(p1) + first * fraction};
|
||||||
|
auto const half{std::min({glm::distance(meeting, plan_of(p1)), glm::distance(meeting, plan_of(p2)), glm::distance(meeting, plan_of(q1)), glm::distance(meeting, plan_of(q2))})};
|
||||||
|
if (half < kCrossingHalf)
|
||||||
|
{
|
||||||
|
out.error = format(STR_C("The tracks are too close for the crossing of a scissors crossover: %.2f m more spacing is needed"), std::max(0.01, shortfall));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
out.crossing = true;
|
||||||
|
out.ends = {p1, p2, q1, q2};
|
||||||
|
out.centre = {meeting.x, p1.y + (p2.y - p1.y) * fraction, meeting.y};
|
||||||
|
auto const unitone{glm::normalize(first)};
|
||||||
|
auto unittwo{glm::normalize(second)};
|
||||||
|
if (glm::dot(unitone, unittwo) < 0.0)
|
||||||
|
unittwo = -unittwo;
|
||||||
|
out.angle = std::atan2(std::abs(cross(unitone, unittwo)), glm::dot(unitone, unittwo));
|
||||||
|
out.valid = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace connection
|
||||||
107
editor/editorConnections.hpp
Normal file
107
editor/editorConnections.hpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
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 <array>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "editor/editorTrack.hpp"
|
||||||
|
|
||||||
|
// connections of the straight tracks of a station made of switches: a crossover of two switches joined by a straight insert or
|
||||||
|
// by an aligning curve between straight inserts, a double crossover of two crossovers one after the other, a scissors crossover
|
||||||
|
// of two crossovers around one centre with a crossing between their inserts, a ladder of switches leading onto a fan of tracks
|
||||||
|
namespace connection
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class kind
|
||||||
|
{
|
||||||
|
crossover,
|
||||||
|
double_crossover,
|
||||||
|
scissors,
|
||||||
|
ladder
|
||||||
|
};
|
||||||
|
int constexpr kind_count{4};
|
||||||
|
char const *word(kind const Kind);
|
||||||
|
bool parse(std::string const &Word, kind &Kind);
|
||||||
|
|
||||||
|
struct line
|
||||||
|
{
|
||||||
|
glm::dvec3 start{0.0};
|
||||||
|
glm::dvec2 direction{0.0, 1.0};
|
||||||
|
double grade{0.0};
|
||||||
|
double length{0.0};
|
||||||
|
glm::dvec3 point(double const Along) const;
|
||||||
|
double along(glm::dvec3 const &Point) const;
|
||||||
|
// to the left of the direction
|
||||||
|
double lateral(glm::dvec3 const &Point) const;
|
||||||
|
};
|
||||||
|
line of(editor_track::straight const &Straight);
|
||||||
|
|
||||||
|
struct placed
|
||||||
|
{
|
||||||
|
int line{0}; // 0 the starting track, 1 the other one, or the tracks of a fan in their order from 1, -1 the ladder track
|
||||||
|
double along{0.0}; // of the start of the switch along its line
|
||||||
|
int forward{1};
|
||||||
|
int side{1};
|
||||||
|
bool second{false}; // made of the template of the other tracks
|
||||||
|
std::vector<segment_data> paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
// the switch on a track facing the end of a diverging track and the straight insert joining the two
|
||||||
|
struct facing
|
||||||
|
{
|
||||||
|
bool valid{false};
|
||||||
|
placed partner;
|
||||||
|
segment_data insert;
|
||||||
|
double length{0.0};
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
facing face(glm::dvec3 const &End, glm::dvec2 const &Heading, line const &To, int const Index, editor_track::switch_template const &Shape);
|
||||||
|
|
||||||
|
struct request
|
||||||
|
{
|
||||||
|
kind type{kind::crossover};
|
||||||
|
line from; // the track the connection starts on
|
||||||
|
std::vector<line> to; // the other track; for a ladder the tracks of the fan, the nearest first, or none
|
||||||
|
double along{0.0}; // of the start of the first switch on the starting track
|
||||||
|
int forward{1};
|
||||||
|
int side{0}; // of a ladder with no tracks of the fan to lead onto
|
||||||
|
double target{0.0}; // where the switch on the other track starts along it when no straight insert fixes the place
|
||||||
|
editor_track::switch_template first;
|
||||||
|
editor_track::switch_template second; // on the other tracks and along the ladder
|
||||||
|
double radius{0.0}; // of the aligning curves and of the end of a ladder, 0: of the sharper switch
|
||||||
|
double insert{6.0}; // the shortest straight between curves turning the opposite ways
|
||||||
|
double gap{6.0}; // of a double crossover: between the starts of the switches meeting on one track
|
||||||
|
bool together{false}; // of a double crossover: the switches meet on the starting track, not on the other one
|
||||||
|
std::vector<double> spacings; // of a ladder with no tracks of the fan: from the starting track to the first one and on
|
||||||
|
double stub{25.0}; // of a ladder: length of the new tracks of the fan
|
||||||
|
};
|
||||||
|
|
||||||
|
struct result
|
||||||
|
{
|
||||||
|
bool valid{false};
|
||||||
|
std::string error;
|
||||||
|
std::string warning;
|
||||||
|
std::vector<placed> switches;
|
||||||
|
std::vector<segment_data> inserts;
|
||||||
|
std::vector<double> straights; // lengths of the straight inserts between the switches
|
||||||
|
double spacing{0.0}; // of the tracks at the start of the first switch
|
||||||
|
double points{0.0}; // between the mathematical points of the switches of the first crossover
|
||||||
|
double radius{0.0}; // of the aligning curve, 0 when a straight insert joins the switches
|
||||||
|
std::vector<double> fan; // offsets of the tracks of a ladder from the starting track
|
||||||
|
bool crossing{false}; // of a scissors crossover, at the meeting of its two inserts
|
||||||
|
std::array<glm::dvec3, 4> ends{}; // of the two inserts crossing: along the first one, then along the second one
|
||||||
|
glm::dvec3 centre{0.0};
|
||||||
|
double angle{0.0};
|
||||||
|
};
|
||||||
|
result plan(request const &Request);
|
||||||
|
|
||||||
|
} // namespace connection
|
||||||
519
editor/editorSwitchDesign.cpp
Normal file
519
editor/editorSwitchDesign.cpp
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
/*
|
||||||
|
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/editorSwitchDesign.hpp"
|
||||||
|
#include "editor/editorFormat.hpp"
|
||||||
|
|
||||||
|
#include "utilities/utilities.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace switch_design
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
double constexpr kPi{3.14159265358979323846};
|
||||||
|
double constexpr kMinRadius{10.0};
|
||||||
|
double constexpr kTransitionStep{5.0};
|
||||||
|
double constexpr kLink{1.0};
|
||||||
|
|
||||||
|
glm::dvec2 heading_of(double const Angle)
|
||||||
|
{
|
||||||
|
return {std::sin(Angle), std::cos(Angle)};
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec3 spatial(glm::dvec2 const &Plan)
|
||||||
|
{
|
||||||
|
return {Plan.x, 0.0, Plan.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
double curvature_of(double const Radius)
|
||||||
|
{
|
||||||
|
return Radius != 0.0 ? 1.0 / Radius : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_data piece(glm::dvec2 const &From, glm::dvec2 const &To, double const Heading0, double const Heading1, double const Handle0, double const Handle1, double const Radius)
|
||||||
|
{
|
||||||
|
segment_data result;
|
||||||
|
result.points[segment_data::point::start] = spatial(From);
|
||||||
|
result.points[segment_data::point::end] = spatial(To);
|
||||||
|
if (Handle0 > 0.0 || Handle1 > 0.0)
|
||||||
|
{
|
||||||
|
result.points[segment_data::point::control1] = spatial(heading_of(Heading0) * Handle0);
|
||||||
|
result.points[segment_data::point::control2] = spatial(-heading_of(Heading1) * Handle1);
|
||||||
|
}
|
||||||
|
result.radius = static_cast<float>(std::abs(Radius));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct walker
|
||||||
|
{
|
||||||
|
glm::dvec2 at{0.0};
|
||||||
|
double heading{0.0};
|
||||||
|
double sharpest{0.0};
|
||||||
|
std::vector<segment_data> pieces;
|
||||||
|
|
||||||
|
void note(double const Radius)
|
||||||
|
{
|
||||||
|
if (Radius != 0.0 && (sharpest == 0.0 || std::abs(Radius) < sharpest))
|
||||||
|
sharpest = std::abs(Radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
void straight(double const Length)
|
||||||
|
{
|
||||||
|
auto const to{at + heading_of(heading) * Length};
|
||||||
|
pieces.push_back(piece(at, to, heading, heading, 0.0, 0.0, 0.0));
|
||||||
|
at = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arc(double const Length, double const Radius)
|
||||||
|
{
|
||||||
|
note(Radius);
|
||||||
|
auto const angle{Length / Radius};
|
||||||
|
auto const count{std::max(1, static_cast<int>(std::ceil(std::abs(angle) / (kPi * 0.5) - 1e-9)))};
|
||||||
|
auto const step{angle / count};
|
||||||
|
auto const handle{4.0 / 3.0 * std::abs(Radius * std::tan(step * 0.25))};
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
auto const chord{2.0 * std::abs(Radius * std::sin(step * 0.5))};
|
||||||
|
auto const to{at + heading_of(heading + step * 0.5) * chord};
|
||||||
|
pieces.push_back(piece(at, to, heading, heading + step, handle, handle, Radius));
|
||||||
|
at = to;
|
||||||
|
heading += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void transition(double const Length, double const Radius1, double const Radius2)
|
||||||
|
{
|
||||||
|
note(Radius1);
|
||||||
|
note(Radius2);
|
||||||
|
auto const k1{curvature_of(Radius1)};
|
||||||
|
auto const k2{curvature_of(Radius2)};
|
||||||
|
auto const start{heading};
|
||||||
|
auto const heading_at = [&](double const S) { return start + k1 * S + (k2 - k1) * S * S / (2.0 * Length); };
|
||||||
|
auto const count{std::max(2, static_cast<int>(std::ceil(Length / kTransitionStep)))};
|
||||||
|
auto const step{Length / count};
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
auto const s0{step * i};
|
||||||
|
int const samples{32};
|
||||||
|
glm::dvec2 offset{0.0};
|
||||||
|
for (int j = 0; j <= samples; ++j)
|
||||||
|
{
|
||||||
|
auto const weight{j == 0 || j == samples ? 1.0 : (j % 2 == 1 ? 4.0 : 2.0)};
|
||||||
|
offset += heading_of(heading_at(s0 + step * j / samples)) * weight;
|
||||||
|
}
|
||||||
|
offset *= step / (3.0 * samples);
|
||||||
|
auto const h0{heading_at(s0)};
|
||||||
|
auto const h1{heading_at(s0 + step)};
|
||||||
|
auto const middle{curvature_of(Radius1) + (k2 - k1) * (s0 + step * 0.5) / Length};
|
||||||
|
auto const handle{glm::length(offset) / 3.0};
|
||||||
|
pieces.push_back(piece(at, at + offset, h0, h1, handle, handle, middle != 0.0 ? 1.0 / middle : 0.0));
|
||||||
|
at += offset;
|
||||||
|
}
|
||||||
|
heading = heading_at(Length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void mirror(segment_data &Path)
|
||||||
|
{
|
||||||
|
auto const flip = [](glm::dvec3 const &Point) { return glm::dvec3{-Point.x, Point.y, Point.z}; };
|
||||||
|
Path.transform(flip, flip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shift(segment_data &Path, double const Along)
|
||||||
|
{
|
||||||
|
Path.transform([&](glm::dvec3 const &Point) { return Point + glm::dvec3{0.0, 0.0, Along}; }, [](glm::dvec3 const &Vector) { return Vector; });
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_data straight_path(double const From, double const To)
|
||||||
|
{
|
||||||
|
segment_data result;
|
||||||
|
result.points[segment_data::point::start] = {0.0, 0.0, From};
|
||||||
|
result.points[segment_data::point::end] = {0.0, 0.0, To};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string checked(std::vector<part> const &Parts)
|
||||||
|
{
|
||||||
|
if (Parts.empty())
|
||||||
|
return "The diverging track has no parts";
|
||||||
|
for (auto const &part : Parts)
|
||||||
|
{
|
||||||
|
if (part.length < 0.05)
|
||||||
|
return "A part of the diverging track is shorter than 5 cm";
|
||||||
|
for (auto const radius : {part.radius1, part.radius2})
|
||||||
|
if (radius != 0.0 && std::abs(radius) < kMinRadius)
|
||||||
|
return "A radius of the diverging track is under 10 m";
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// the point where the tangent at the end of the diverging track meets the main track, as the distance along it
|
||||||
|
double tangent_point(glm::dvec2 const &End, double const Angle)
|
||||||
|
{
|
||||||
|
return End.y - End.x / std::tan(Angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string word(std::string const &Text)
|
||||||
|
{
|
||||||
|
if (Text.empty())
|
||||||
|
return "-";
|
||||||
|
auto result{Text};
|
||||||
|
std::replace(result.begin(), result.end(), ' ', '_');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string unword(std::string const &Text)
|
||||||
|
{
|
||||||
|
if (Text == "-")
|
||||||
|
return {};
|
||||||
|
auto result{Text};
|
||||||
|
std::replace(result.begin(), result.end(), '_', ' ');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path own_path()
|
||||||
|
{
|
||||||
|
auto path{user_config_path("eu07_switch_templates.txt")};
|
||||||
|
return path.empty() ? std::filesystem::path{"eu07_switch_templates.txt"} : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_parts(std::ostream &Output, std::vector<part> const &Parts)
|
||||||
|
{
|
||||||
|
Output << ' ' << Parts.size();
|
||||||
|
for (auto const &part : Parts)
|
||||||
|
Output << ' ' << format("%.4f %.4f %.4f", part.length, part.radius1, part.radius2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_parts(std::istream &Input, std::vector<part> &Parts)
|
||||||
|
{
|
||||||
|
std::size_t count{0};
|
||||||
|
Input >> count;
|
||||||
|
if (Input.fail() || count > 64)
|
||||||
|
return false;
|
||||||
|
Parts.resize(count);
|
||||||
|
for (auto &part : Parts)
|
||||||
|
Input >> part.length >> part.radius1 >> part.radius2;
|
||||||
|
return false == Input.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool double_switch(std::string const &Type)
|
||||||
|
{
|
||||||
|
return Type == "Rpd" || Type == "Rpj";
|
||||||
|
}
|
||||||
|
|
||||||
|
path diverging(std::vector<part> const &Parts, double const Lead, double const Entry)
|
||||||
|
{
|
||||||
|
walker walk;
|
||||||
|
if (Lead > 0.0)
|
||||||
|
walk.straight(Lead);
|
||||||
|
auto const start{Lead > 0.0 ? 0.0 : glm::radians(Entry)};
|
||||||
|
walk.heading += glm::radians(Entry);
|
||||||
|
for (auto const &part : Parts)
|
||||||
|
{
|
||||||
|
if (part.radius1 == 0.0 && part.radius2 == 0.0)
|
||||||
|
walk.straight(part.length);
|
||||||
|
else if (part.radius1 == part.radius2)
|
||||||
|
walk.arc(part.length, part.radius1);
|
||||||
|
else
|
||||||
|
walk.transition(part.length, part.radius1, part.radius2);
|
||||||
|
}
|
||||||
|
path result;
|
||||||
|
result.angle = walk.heading;
|
||||||
|
result.radius = walk.sharpest;
|
||||||
|
if (walk.pieces.size() == 1)
|
||||||
|
{
|
||||||
|
result.whole = walk.pieces.front();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const end{walk.at};
|
||||||
|
auto handle0{glm::length(end) / 3.0};
|
||||||
|
auto handle1{handle0};
|
||||||
|
if (std::abs(walk.heading) > 1e-6)
|
||||||
|
{
|
||||||
|
auto const along{tangent_point(end, walk.heading)};
|
||||||
|
auto const back{end.x / std::sin(walk.heading)};
|
||||||
|
if (along > 0.0 && back > 0.0)
|
||||||
|
{
|
||||||
|
handle0 = along * 2.0 / 3.0;
|
||||||
|
handle1 = back * 2.0 / 3.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.whole = piece({0.0, 0.0}, end, start, walk.heading, handle0, handle1, walk.sharpest);
|
||||||
|
result.whole.pieces = std::move(walk.pieces);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
shape build(design const &Design, std::string &Error)
|
||||||
|
{
|
||||||
|
shape result;
|
||||||
|
Error.clear();
|
||||||
|
bool const twofold{double_switch(Design.type)};
|
||||||
|
if (Design.type != "Rz" && false == twofold)
|
||||||
|
{
|
||||||
|
Error = "Unknown type of switch";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Error = checked(Design.parts);
|
||||||
|
if (Error.empty() && twofold && false == Design.parts2.empty())
|
||||||
|
Error = checked(Design.parts2);
|
||||||
|
if (Error.empty() && (Design.lead < 0.0 || Design.lead > 30.0))
|
||||||
|
Error = "The pre-blade stretch has to be between 0 and 30 m";
|
||||||
|
if (Error.empty() && (Design.entry < 0.0 || Design.entry > 5.0))
|
||||||
|
Error = "The angle of the blade has to be between 0 and 5 degrees";
|
||||||
|
if (false == Error.empty())
|
||||||
|
return result;
|
||||||
|
auto const first{diverging(Design.parts, Design.lead, Design.entry)};
|
||||||
|
if (first.angle <= 1e-4 || first.angle >= kPi / 4.0)
|
||||||
|
{
|
||||||
|
Error = "The diverging track has to leave to the side at an angle between 0 and 45 degrees";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const end{glm::dvec2{first.whole.points[segment_data::point::end].x, first.whole.points[segment_data::point::end].z}};
|
||||||
|
result.ratio = 1.0 / std::tan(first.angle);
|
||||||
|
result.radius = first.radius;
|
||||||
|
result.offset = end.x;
|
||||||
|
result.tip = Design.lead;
|
||||||
|
result.a = Design.a > 0.0 ? Design.a : tangent_point(end, first.angle);
|
||||||
|
if (false == twofold)
|
||||||
|
{
|
||||||
|
result.length = Design.main > 0.0 ? Design.main : end.y;
|
||||||
|
if (result.length < 1.0)
|
||||||
|
{
|
||||||
|
Error = "The main track is shorter than 1 m";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.b = Design.b > 0.0 ? Design.b : result.length - result.a;
|
||||||
|
result.paths = {straight_path(0.0, result.length), first.whole};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const reach{end.y};
|
||||||
|
if (Design.stagger < 1.0 || Design.stagger > reach - 1.0)
|
||||||
|
{
|
||||||
|
Error = "The second switch has to start at least 1 m along the main track and 1 m before the end of the diverging track";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto const lead2{Design.lead2 >= 0.0 ? Design.lead2 : Design.lead};
|
||||||
|
auto const entry2{Design.entry2 >= 0.0 ? Design.entry2 : Design.entry};
|
||||||
|
if (lead2 > 30.0 || entry2 > 5.0)
|
||||||
|
{
|
||||||
|
Error = "The pre-blade stretch of the second switch has to be at most 30 m and the angle of its blade at most 5 degrees";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
auto second{diverging(Design.parts2.empty() ? Design.parts : Design.parts2, lead2, entry2)};
|
||||||
|
if (second.angle <= 1e-4 || second.angle >= kPi / 4.0)
|
||||||
|
{
|
||||||
|
Error = "The diverging track of the second switch has to leave to the side at an angle between 0 and 45 degrees";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (Design.type == "Rpd")
|
||||||
|
mirror(second.whole);
|
||||||
|
shift(second.whole, Design.stagger);
|
||||||
|
auto const secondend{second.whole.points[segment_data::point::end].z};
|
||||||
|
result.length = Design.main > 0.0 ? Design.main : std::max(reach, secondend);
|
||||||
|
if (result.length < Design.stagger + 1.0)
|
||||||
|
{
|
||||||
|
Error = "The main track ends less than 1 m after the start of the second switch";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.b = Design.b > 0.0 ? Design.b : reach - result.a;
|
||||||
|
result.stagger = Design.stagger;
|
||||||
|
result.tip2 = Design.stagger + lead2;
|
||||||
|
result.radius = std::min(first.radius, second.radius > 0.0 ? second.radius : first.radius);
|
||||||
|
// the simulation joins switches only to plain tracks, so a short one lies between the two
|
||||||
|
auto const link{std::min(kLink, Design.stagger * 0.5)};
|
||||||
|
result.paths = {straight_path(0.0, Design.stagger - link), first.whole, straight_path(Design.stagger, result.length), second.whole, straight_path(Design.stagger - link, Design.stagger)};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<design> catalogue()
|
||||||
|
{
|
||||||
|
struct ordinary
|
||||||
|
{
|
||||||
|
char const *name;
|
||||||
|
double radius;
|
||||||
|
double ratio;
|
||||||
|
double a;
|
||||||
|
double b;
|
||||||
|
};
|
||||||
|
ordinary const ordinaries[] = {
|
||||||
|
{"Rz 1:9 R190", 190.0, 9.0, 10.523, 16.615},
|
||||||
|
{"Rz 1:9 R300", 300.0, 9.0, 16.615, 16.615},
|
||||||
|
{"Rz 1:12 R500", 500.0, 12.0, 20.797, 0.0},
|
||||||
|
{"Rz 1:14 R760", 760.0, 14.0, 0.0, 0.0},
|
||||||
|
{"Rz 1:18,5 R1200", 1200.0, 18.5, 32.409, 34.943},
|
||||||
|
{"Rz 1:26,5 R2500", 2500.0, 26.5, 0.0, 0.0},
|
||||||
|
};
|
||||||
|
auto const make = [](ordinary const &Ordinary) {
|
||||||
|
design entry;
|
||||||
|
entry.name = Ordinary.name;
|
||||||
|
auto const angle{std::atan(1.0 / Ordinary.ratio)};
|
||||||
|
entry.a = Ordinary.a > 0.0 ? Ordinary.a : Ordinary.radius * std::tan(angle * 0.5);
|
||||||
|
auto const estimated{Ordinary.b <= 0.0};
|
||||||
|
entry.b = estimated ? 1.87 * Ordinary.ratio : Ordinary.b;
|
||||||
|
entry.parts.push_back({Ordinary.radius * angle, Ordinary.radius, Ordinary.radius});
|
||||||
|
auto const rest{(entry.a + entry.b - Ordinary.radius * std::sin(angle)) / std::cos(angle)};
|
||||||
|
if (rest > 0.01)
|
||||||
|
{
|
||||||
|
entry.parts.push_back({rest, 0.0, 0.0});
|
||||||
|
entry.main = entry.a + entry.b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
entry.b = 0.0;
|
||||||
|
if (estimated)
|
||||||
|
entry.note = "(b est.)";
|
||||||
|
return entry;
|
||||||
|
};
|
||||||
|
std::vector<design> result;
|
||||||
|
for (auto const &ordinary : ordinaries)
|
||||||
|
result.push_back(make(ordinary));
|
||||||
|
{
|
||||||
|
design curved;
|
||||||
|
curved.name = "Rz 1:7,5 R190";
|
||||||
|
auto const angle{std::atan(1.0 / 7.5)};
|
||||||
|
curved.parts.push_back({190.0 * angle, 190.0, 190.0});
|
||||||
|
curved.note = "(curved to the end)";
|
||||||
|
result.push_back(curved);
|
||||||
|
}
|
||||||
|
struct twofold
|
||||||
|
{
|
||||||
|
char const *name;
|
||||||
|
char const *type;
|
||||||
|
std::size_t ordinary;
|
||||||
|
double stagger;
|
||||||
|
};
|
||||||
|
twofold const twofolds[] = {
|
||||||
|
{"Rpd 1:9 R190", "Rpd", 0, 5.0},
|
||||||
|
{"Rpj 1:9 R190", "Rpj", 0, 5.0},
|
||||||
|
{"Rpd 1:9 R300", "Rpd", 1, 6.0},
|
||||||
|
{"Rpj 1:9 R300", "Rpj", 1, 6.0},
|
||||||
|
};
|
||||||
|
for (auto const &entry : twofolds)
|
||||||
|
{
|
||||||
|
auto made{make(ordinaries[entry.ordinary])};
|
||||||
|
made.name = entry.name;
|
||||||
|
made.type = entry.type;
|
||||||
|
made.stagger = entry.stagger;
|
||||||
|
made.main = 0.0;
|
||||||
|
made.b = 0.0;
|
||||||
|
made.note = format("(second switch %.1f m on, assumed)", entry.stagger);
|
||||||
|
result.push_back(made);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<design> own()
|
||||||
|
{
|
||||||
|
std::vector<design> result;
|
||||||
|
std::ifstream file{own_path()};
|
||||||
|
if (false == file.is_open())
|
||||||
|
return result;
|
||||||
|
std::string text;
|
||||||
|
while (std::getline(file, text))
|
||||||
|
{
|
||||||
|
if (text.empty() || text.front() == '#')
|
||||||
|
continue;
|
||||||
|
std::istringstream line{text};
|
||||||
|
design entry;
|
||||||
|
entry.own = true;
|
||||||
|
std::string keyword;
|
||||||
|
std::string name;
|
||||||
|
line >> keyword >> name;
|
||||||
|
if (keyword != "switch")
|
||||||
|
continue;
|
||||||
|
entry.name = unword(name);
|
||||||
|
bool good{true};
|
||||||
|
while (good && line >> keyword)
|
||||||
|
{
|
||||||
|
if (keyword == "type")
|
||||||
|
line >> entry.type;
|
||||||
|
else if (keyword == "rail")
|
||||||
|
{
|
||||||
|
line >> entry.rail;
|
||||||
|
if (entry.rail.empty())
|
||||||
|
entry.rail = "-";
|
||||||
|
}
|
||||||
|
else if (keyword == "main")
|
||||||
|
line >> entry.main;
|
||||||
|
else if (keyword == "stagger")
|
||||||
|
line >> entry.stagger;
|
||||||
|
else if (keyword == "lead")
|
||||||
|
line >> entry.lead;
|
||||||
|
else if (keyword == "entry")
|
||||||
|
line >> entry.entry;
|
||||||
|
else if (keyword == "lead2")
|
||||||
|
line >> entry.lead2;
|
||||||
|
else if (keyword == "entry2")
|
||||||
|
line >> entry.entry2;
|
||||||
|
else if (keyword == "a")
|
||||||
|
line >> entry.a;
|
||||||
|
else if (keyword == "b")
|
||||||
|
line >> entry.b;
|
||||||
|
else if (keyword == "parts")
|
||||||
|
good = read_parts(line, entry.parts);
|
||||||
|
else if (keyword == "parts2")
|
||||||
|
good = read_parts(line, entry.parts2);
|
||||||
|
else
|
||||||
|
good = false;
|
||||||
|
good = good && false == line.fail();
|
||||||
|
}
|
||||||
|
if (good && false == entry.name.empty())
|
||||||
|
result.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_own(std::vector<design> const &Designs, std::string &Error)
|
||||||
|
{
|
||||||
|
auto const path{own_path()};
|
||||||
|
std::error_code error;
|
||||||
|
if (path.has_parent_path())
|
||||||
|
std::filesystem::create_directories(path.parent_path(), error);
|
||||||
|
std::ofstream file{path, std::ios::trunc};
|
||||||
|
if (false == file.is_open())
|
||||||
|
{
|
||||||
|
Error = "Can't write " + path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file << "# switch templates made in the track editor of MaSzyna\n";
|
||||||
|
file << "# switch <name> type <Rz|Rpd|Rpj> rail <rail> main <m> stagger <m> a <m> b <m> lead <m> entry <deg> [lead2 <m> entry2 <deg>] parts <count> (<length> <R at the start> <R at the end>)... parts2 <count> ...\n";
|
||||||
|
file << "# R 0 is a straight, the same R at both ends an arc, different ones a transition curve; 0 for main, a, b: from the geometry\n";
|
||||||
|
for (auto const &entry : Designs)
|
||||||
|
{
|
||||||
|
file << "switch " << word(entry.name) << " type " << entry.type << " rail " << word(entry.rail) << ' ' << format("main %.4f stagger %.4f a %.4f b %.4f lead %.4f entry %.4f", entry.main, entry.stagger, entry.a, entry.b, entry.lead, entry.entry);
|
||||||
|
if (entry.lead2 >= 0.0 || entry.entry2 >= 0.0)
|
||||||
|
file << format(" lead2 %.4f entry2 %.4f", entry.lead2, entry.entry2);
|
||||||
|
file << " parts";
|
||||||
|
write_parts(file, entry.parts);
|
||||||
|
if (false == entry.parts2.empty())
|
||||||
|
{
|
||||||
|
file << " parts2";
|
||||||
|
write_parts(file, entry.parts2);
|
||||||
|
}
|
||||||
|
file << '\n';
|
||||||
|
}
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
Error = "Can't write " + path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace switch_design
|
||||||
82
editor/editorSwitchDesign.hpp
Normal file
82
editor/editorSwitchDesign.hpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
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 <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "world/Segment.h"
|
||||||
|
|
||||||
|
// switches described by their geometry, of the catalogue and made by the user in the template editor: the main track is a
|
||||||
|
// straight, the diverging track a chain of parts of linearly changing curvature from the start of the switch; a double switch
|
||||||
|
// (Rpd to both sides, Rpj to one side) is two switches, the second starting on the main track of the first
|
||||||
|
namespace switch_design
|
||||||
|
{
|
||||||
|
|
||||||
|
struct part
|
||||||
|
{
|
||||||
|
double length{0.0};
|
||||||
|
double radius1{0.0}; // at the start of the part, 0 straight
|
||||||
|
double radius2{0.0}; // at its end; the same as radius1 for an arc, different for a transition curve
|
||||||
|
};
|
||||||
|
|
||||||
|
struct design
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string type{"Rz"}; // Rz, Rpd, Rpj
|
||||||
|
std::string rail{"-"};
|
||||||
|
double main{0.0}; // length of the main track, 0: as far as the diverging tracks reach
|
||||||
|
std::vector<part> parts;
|
||||||
|
double lead{0.0}; // the pre-blade stretch: the diverging track runs along the main one up to the tip of the blade
|
||||||
|
double entry{0.0}; // degrees, the angle of the blade to the stock rail at its tip, 0: the diverging track leaves tangentially
|
||||||
|
double stagger{0.0}; // of a double switch: where its second switch starts on the main track
|
||||||
|
std::vector<part> parts2; // of the second switch, empty: the same as the first
|
||||||
|
double lead2{-1.0}; // of the second switch, negative: the same as the first
|
||||||
|
double entry2{-1.0};
|
||||||
|
double a{0.0}; // as the catalogue gives them, 0: from the geometry
|
||||||
|
double b{0.0};
|
||||||
|
std::string note; // shown after the name on the list
|
||||||
|
bool own{false}; // made in the template editor, kept in the file of the user
|
||||||
|
};
|
||||||
|
|
||||||
|
bool double_switch(std::string const &Type);
|
||||||
|
|
||||||
|
// diverging track of the chain of parts from the start, with the first direction along +z and turning to +x
|
||||||
|
struct path
|
||||||
|
{
|
||||||
|
segment_data whole; // the classic bezier, with the parts as its pieces when one bezier doesn't make them
|
||||||
|
double angle{0.0}; // at the end, radians
|
||||||
|
double radius{0.0}; // the sharpest
|
||||||
|
};
|
||||||
|
path diverging(std::vector<part> const &Parts, double Lead = 0.0, double Entry = 0.0);
|
||||||
|
|
||||||
|
// the paths of the switch in its own space: the main and the diverging track of the first switch, then of the second one of a
|
||||||
|
// double switch and the plain track joining the two; empty with the reason in Error when the design can't make a switch
|
||||||
|
struct shape
|
||||||
|
{
|
||||||
|
std::vector<segment_data> paths;
|
||||||
|
double length{0.0};
|
||||||
|
double ratio{0.0};
|
||||||
|
double radius{0.0};
|
||||||
|
double a{0.0};
|
||||||
|
double b{0.0};
|
||||||
|
double offset{0.0}; // of the end of the diverging track from the main one
|
||||||
|
double stagger{0.0};
|
||||||
|
double tip{0.0}; // of the blade, along the main track
|
||||||
|
double tip2{0.0};
|
||||||
|
};
|
||||||
|
shape build(design const &Design, std::string &Error);
|
||||||
|
|
||||||
|
std::vector<design> catalogue();
|
||||||
|
// templates of the user, kept in eu07_switch_templates.txt in the configuration directory
|
||||||
|
std::vector<design> own();
|
||||||
|
bool save_own(std::vector<design> const &Designs, std::string &Error);
|
||||||
|
|
||||||
|
} // namespace switch_design
|
||||||
@@ -39,7 +39,7 @@ bool g_changed{false};
|
|||||||
bool g_loaded{false};
|
bool g_loaded{false};
|
||||||
|
|
||||||
// format of the lines, coordinates in the space of the file:
|
// 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>
|
// <switch|slip|crossing|double> <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>... shape <main end along, across> <diverging end along, across> start <x y z> dir <x z>
|
// parts <count> <names>... shape <main end along, across> <diverging end along, across> start <x y z> dir <x z>
|
||||||
// km <km|-> number <label|-> source <designed|measured>
|
// km <km|-> number <label|-> source <designed|measured>
|
||||||
std::string word(std::string const &Text)
|
std::string word(std::string const &Text)
|
||||||
@@ -156,7 +156,7 @@ void load()
|
|||||||
std::string km;
|
std::string km;
|
||||||
std::size_t count{0};
|
std::size_t count{0};
|
||||||
line >> entry.kind;
|
line >> entry.kind;
|
||||||
if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing")
|
if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing" && entry.kind != "double" && entry.kind != "connection")
|
||||||
continue;
|
continue;
|
||||||
line >> entry.name >> entry.type >> entry.rail >> entry.radius >> entry.ratio >> hand >> entry.a >> entry.b >> entry.main_radius >> keyword >> count;
|
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)
|
if (line.fail() || keyword != "parts" || count > 64)
|
||||||
@@ -205,11 +205,24 @@ std::optional<record> named(TTrack const &Track)
|
|||||||
if (name.empty())
|
if (name.empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
for (auto const &entry : g_records)
|
for (auto const &entry : g_records)
|
||||||
if (std::find(entry.parts.begin(), entry.parts.end(), name) != entry.parts.end())
|
if (entry.kind != "connection" && std::find(entry.parts.begin(), entry.parts.end(), name) != entry.parts.end())
|
||||||
return entry;
|
return entry;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<record> connections(TTrack const &Track)
|
||||||
|
{
|
||||||
|
load();
|
||||||
|
std::vector<record> result;
|
||||||
|
auto const &name{Track.name()};
|
||||||
|
if (name.empty())
|
||||||
|
return result;
|
||||||
|
for (auto const &entry : g_records)
|
||||||
|
if (entry.kind == "connection" && std::find(entry.parts.begin(), entry.parts.end(), name) != entry.parts.end())
|
||||||
|
result.push_back(entry);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<record> of(TTrack const &Track)
|
std::optional<record> of(TTrack const &Track)
|
||||||
{
|
{
|
||||||
auto const found{named(Track)};
|
auto const found{named(Track)};
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ namespace switch_store
|
|||||||
struct record
|
struct record
|
||||||
{
|
{
|
||||||
scene::layer_handle layer{null_handle};
|
scene::layer_handle layer{null_handle};
|
||||||
std::string kind{"switch"}; // switch, slip, crossing
|
std::string kind{"switch"}; // switch, slip, crossing, double, connection
|
||||||
std::string name; // the switch, or the base name of a slip or a 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 type{"Rz"}; // Rz, Rlj, Rld, Rkp, Rkpd, Skrz, Rpd, Rpj, - when unknown
|
||||||
std::string rail{"-"};
|
std::string rail{"-"};
|
||||||
double radius{0.0}; // of the diverging track of the straight switch the type is made of
|
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
|
double ratio{0.0}; // n of 1:n
|
||||||
@@ -36,7 +36,7 @@ struct record
|
|||||||
double a{0.0};
|
double a{0.0};
|
||||||
double b{0.0};
|
double b{0.0};
|
||||||
double main_radius{0.0}; // of the main track, signed: + to the right, - to the left, 0 straight
|
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
|
std::vector<std::string> parts; // the switch, the switches of a slip or of a double switch, the paths of a crossing
|
||||||
glm::dvec3 start{0.0}; // of the first part
|
glm::dvec3 start{0.0}; // of the first part
|
||||||
glm::dvec2 direction{0.0, 1.0};
|
glm::dvec2 direction{0.0, 1.0};
|
||||||
// ends of the main and the diverging track of the first part seen from its start: along, across, along, across. it stays the
|
// ends of the main and the diverging track of the first part seen from its start: along, across, along, across. it stays the
|
||||||
@@ -55,6 +55,8 @@ std::vector<record> const &records();
|
|||||||
std::optional<record> of(TTrack const &Track);
|
std::optional<record> of(TTrack const &Track);
|
||||||
// the record of the track by its name alone, also when the switch lost its shape
|
// the record of the track by its name alone, also when the switch lost its shape
|
||||||
std::optional<record> named(TTrack const &Track);
|
std::optional<record> named(TTrack const &Track);
|
||||||
|
// the connections of switches the switch is a part of
|
||||||
|
std::vector<record> connections(TTrack const &Track);
|
||||||
// records whose switches were moved or turned as a whole take their new place and km; returns their names
|
// records whose switches were moved or turned as a whole take their new place and km; returns their names
|
||||||
std::vector<std::string> follow();
|
std::vector<std::string> follow();
|
||||||
// radius of the diverging track: of the type, bent with the main track for an arc switch
|
// radius of the diverging track: of the type, bent with the main track for an arc switch
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ http:
|
|||||||
#include "utilities/Globals.h"
|
#include "utilities/Globals.h"
|
||||||
#include "utilities/utilities.h"
|
#include "utilities/utilities.h"
|
||||||
#include "utilities/parser.h"
|
#include "utilities/parser.h"
|
||||||
|
#include "utilities/Logs.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -2595,77 +2596,72 @@ std::vector<segment_data> editor_track::place_switch(switch_template const &Temp
|
|||||||
auto const planar{Direction * Local.z + normal * (Local.x * side)};
|
auto const planar{Direction * Local.z + normal * (Local.x * side)};
|
||||||
return glm::dvec3{planar.x, Local.y + Grade * Local.z, planar.y};
|
return glm::dvec3{planar.x, Local.y + Grade * Local.z, planar.y};
|
||||||
};
|
};
|
||||||
for (auto const &local : Template.local)
|
auto const put = [&](segment_data const &Local) {
|
||||||
{
|
segment_data path{Local};
|
||||||
segment_data path{local};
|
|
||||||
path.transform([&](glm::dvec3 const &Point) { return Origin + vector(Point); }, vector);
|
path.transform([&](glm::dvec3 const &Point) { return Origin + vector(Point); }, vector);
|
||||||
if (Side < 0)
|
if (Side < 0)
|
||||||
{
|
{
|
||||||
path.rolls = {-local.rolls[0], -local.rolls[1]};
|
path.rolls = {-Local.rolls[0], -Local.rolls[1]};
|
||||||
for (auto &piece : path.pieces)
|
for (auto &piece : path.pieces)
|
||||||
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
|
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
|
||||||
}
|
}
|
||||||
result.push_back(path);
|
result.push_back(path);
|
||||||
|
};
|
||||||
|
for (auto const &local : Template.local)
|
||||||
|
put(local);
|
||||||
|
if (Template.twofold())
|
||||||
|
{
|
||||||
|
for (auto const &local : Template.second)
|
||||||
|
put(local);
|
||||||
|
put(Template.link);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<editor_track::switch_template> editor_track::template_of(switch_design::design const &Design, std::string &Error)
|
||||||
|
{
|
||||||
|
auto const shape{switch_design::build(Design, Error)};
|
||||||
|
if (shape.paths.size() < 2)
|
||||||
|
return std::nullopt;
|
||||||
|
switch_template entry;
|
||||||
|
entry.type = Design.type;
|
||||||
|
entry.rail = Design.rail.empty() ? std::string{"-"} : Design.rail;
|
||||||
|
entry.length = shape.length;
|
||||||
|
entry.ratio = shape.ratio;
|
||||||
|
entry.radius = shape.radius;
|
||||||
|
entry.a = shape.a;
|
||||||
|
entry.b = shape.b;
|
||||||
|
entry.own = Design.own;
|
||||||
|
entry.local = {shape.paths[0], shape.paths[1]};
|
||||||
|
entry.local[1].radius = static_cast<float>(shape.radius);
|
||||||
|
if (shape.paths.size() >= 5)
|
||||||
|
{
|
||||||
|
entry.second = {shape.paths[2], shape.paths[3]};
|
||||||
|
entry.link = shape.paths[4];
|
||||||
|
entry.stagger = shape.stagger;
|
||||||
|
}
|
||||||
|
auto const decimals = [](double const Value) { return format("%.3f", Value); };
|
||||||
|
entry.label = Design.name + " a " + decimals(entry.a) + " b " + decimals(entry.b) + (Design.note.empty() ? std::string{} : " " + Design.note);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<editor_track::switch_template> editor_track::standard_switch_templates()
|
std::vector<editor_track::switch_template> editor_track::standard_switch_templates()
|
||||||
{
|
{
|
||||||
struct definition
|
|
||||||
{
|
|
||||||
char const *name;
|
|
||||||
double radius;
|
|
||||||
double ratio;
|
|
||||||
double a;
|
|
||||||
double b;
|
|
||||||
};
|
|
||||||
definition const definitions[] = {
|
|
||||||
{"Rz 1:9 R190", 190.0, 9.0, 10.523, 16.615},
|
|
||||||
{"Rz 1:9 R300", 300.0, 9.0, 16.615, 16.615},
|
|
||||||
{"Rz 1:12 R500", 500.0, 12.0, 20.797, 0.0},
|
|
||||||
{"Rz 1:14 R760", 760.0, 14.0, 0.0, 0.0},
|
|
||||||
{"Rz 1:18,5 R1200", 1200.0, 18.5, 32.409, 34.943},
|
|
||||||
{"Rz 1:26,5 R2500", 2500.0, 26.5, 0.0, 0.0},
|
|
||||||
};
|
|
||||||
std::vector<switch_template> result;
|
std::vector<switch_template> result;
|
||||||
for (auto const &definition : definitions)
|
auto designs{switch_design::catalogue()};
|
||||||
|
auto const own{switch_design::own()};
|
||||||
|
designs.insert(designs.end(), own.begin(), own.end());
|
||||||
|
for (std::size_t i = 0; i < designs.size(); ++i)
|
||||||
{
|
{
|
||||||
auto const angle{std::atan(1.0 / definition.ratio)};
|
std::string error;
|
||||||
auto const a{definition.a > 0.0 ? definition.a : definition.radius * std::tan(angle * 0.5)};
|
auto entry{template_of(designs[i], error)};
|
||||||
auto const estimated{definition.b <= 0.0};
|
if (false == entry.has_value())
|
||||||
auto const b{estimated ? 1.87 * definition.ratio : definition.b};
|
|
||||||
auto const offset{b * std::tan(angle)};
|
|
||||||
switch_template entry;
|
|
||||||
entry.a = a;
|
|
||||||
entry.b = b;
|
|
||||||
entry.length = a + b;
|
|
||||||
entry.ratio = definition.ratio;
|
|
||||||
entry.radius = definition.radius;
|
|
||||||
auto &main{entry.local[0]};
|
|
||||||
main.points[segment_data::point::end] = {0.0, 0.0, a + b};
|
|
||||||
auto &diverging{entry.local[1]};
|
|
||||||
diverging.points[segment_data::point::end] = {offset, 0.0, a + b};
|
|
||||||
diverging.points[segment_data::point::control1] = {0.0, 0.0, a * 2.0 / 3.0};
|
|
||||||
diverging.points[segment_data::point::control2] = {-offset * 2.0 / 3.0, 0.0, -b * 2.0 / 3.0};
|
|
||||||
diverging.radius = static_cast<float>(definition.radius);
|
|
||||||
auto const tangent{definition.radius * std::tan(angle * 0.5)};
|
|
||||||
glm::dvec3 const arcend{tangent * std::sin(angle), 0.0, tangent + tangent * std::cos(angle)};
|
|
||||||
if (glm::distance(arcend, diverging.points[segment_data::point::end]) > 0.5)
|
|
||||||
{
|
{
|
||||||
auto const handle{4.0 / 3.0 * definition.radius * std::tan(angle * 0.25)};
|
WriteLog("Editor: switch template \"" + designs[i].name + "\" left out: " + error, logtype::generic);
|
||||||
segment_data arc;
|
continue;
|
||||||
arc.points[segment_data::point::end] = arcend;
|
|
||||||
arc.points[segment_data::point::control1] = {0.0, 0.0, handle};
|
|
||||||
arc.points[segment_data::point::control2] = {-handle * std::sin(angle), 0.0, -handle * std::cos(angle)};
|
|
||||||
arc.radius = static_cast<float>(definition.radius);
|
|
||||||
segment_data straight;
|
|
||||||
straight.points[segment_data::point::start] = arcend;
|
|
||||||
straight.points[segment_data::point::end] = diverging.points[segment_data::point::end];
|
|
||||||
diverging.pieces = {arc, straight};
|
|
||||||
}
|
}
|
||||||
entry.label = std::string{definition.name} + " a " + std::to_string(a).substr(0, std::to_string(a).find('.') + 4) + " b " + std::to_string(b).substr(0, std::to_string(b).find('.') + 4) + (estimated ? " (b est.)" : "");
|
entry->design = static_cast<int>(i);
|
||||||
result.push_back(entry);
|
result.push_back(std::move(*entry));
|
||||||
}
|
}
|
||||||
for (auto const &slipping : {std::pair<double, char const *>{190.0, "1:9"}, {300.0, "1:9"}})
|
for (auto const &slipping : {std::pair<double, char const *>{190.0, "1:9"}, {300.0, "1:9"}})
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ http:
|
|||||||
|
|
||||||
#include "world/Segment.h"
|
#include "world/Segment.h"
|
||||||
#include "editor/editorAlignment.hpp"
|
#include "editor/editorAlignment.hpp"
|
||||||
|
#include "editor/editorSwitchDesign.hpp"
|
||||||
#include "utilities/Classes.h"
|
#include "utilities/Classes.h"
|
||||||
|
|
||||||
class TTrack;
|
class TTrack;
|
||||||
@@ -407,8 +408,16 @@ class editor_track
|
|||||||
bool double_slip{false};
|
bool double_slip{false};
|
||||||
std::string type{"Rz"};
|
std::string type{"Rz"};
|
||||||
std::string rail{"-"};
|
std::string rail{"-"};
|
||||||
|
// the second switch of a double one, starting on the main track of the first at the stagger
|
||||||
|
std::array<segment_data, 2> second;
|
||||||
|
segment_data link; // plain track from the main track of the first switch to the start of the second one
|
||||||
|
double stagger{0.0};
|
||||||
|
bool own{false};
|
||||||
|
int design{-1}; // in the list of the designs the template was built from
|
||||||
|
bool twofold() const { return stagger > 0.0; }
|
||||||
};
|
};
|
||||||
static std::vector<switch_template> standard_switch_templates();
|
static std::vector<switch_template> standard_switch_templates();
|
||||||
|
static std::optional<switch_template> template_of(switch_design::design const &Design, std::string &Error);
|
||||||
static std::vector<switch_template> find_switch_templates();
|
static std::vector<switch_template> find_switch_templates();
|
||||||
static TTrack *load_path(std::string const &Text, TTrack const &Template, std::string const &Name = {});
|
static TTrack *load_path(std::string const &Text, TTrack const &Template, std::string const &Name = {});
|
||||||
static TTrack *create_path(TTrack const &Style, segment_data const &Path);
|
static TTrack *create_path(TTrack const &Style, segment_data const &Path);
|
||||||
|
|||||||
@@ -5747,3 +5747,436 @@ msgstr "Krzyżownica i kierownice z szyn"
|
|||||||
|
|
||||||
msgid "Cuts the rails of the switch at its frog and lays wing rails and check rails, for rail profiles S60, UIC60 and S39.\nOff for a switch whose include has its own model of them"
|
msgid "Cuts the rails of the switch at its frog and lays wing rails and check rails, for rail profiles S60, UIC60 and S39.\nOff for a switch whose include has its own model of them"
|
||||||
msgstr "Tnie szyny rozjazdu na krzyżownicy i układa szyny skrzydłowe i kierownice, dla profili szyn S60, UIC60 i S39.\nWyłącz dla rozjazdu, którego include ma ich własny model"
|
msgstr "Tnie szyny rozjazdu na krzyżownicy i układa szyny skrzydłowe i kierownice, dla profili szyn S60, UIC60 i S39.\nWyłącz dla rozjazdu, którego include ma ich własny model"
|
||||||
|
|
||||||
|
msgid "1:n##quickratio"
|
||||||
|
msgstr "1:n##quickratio"
|
||||||
|
|
||||||
|
msgid "across x%.1f"
|
||||||
|
msgstr "w poprzek x%.1f"
|
||||||
|
|
||||||
|
msgid "a##designa"
|
||||||
|
msgstr "a##designa"
|
||||||
|
|
||||||
|
msgid "A double switch is laid as two switches: the first one with its diverging track and the main track up to the second one, which starts there with the other diverging track"
|
||||||
|
msgstr "Rozjazd podwójny kładzie się jako dwie zwrotnice: pierwszą z jej torem zwrotnym i torem zasadniczym aż do drugiej, która zaczyna się tam z drugim torem zwrotnym"
|
||||||
|
|
||||||
|
msgid "A new own template, an Rz 1:9 R190 to start from"
|
||||||
|
msgstr "Nowy własny szablon, na początek Rz 1:9 R190"
|
||||||
|
|
||||||
|
msgid "Angle 1:%.3f (%.4f deg), sharpest R %.1f m, a %.3f m, b %.3f m"
|
||||||
|
msgstr "Skos 1:%.3f (%.4f°), najmniejszy R %.1f m, a %.3f m, b %.3f m"
|
||||||
|
|
||||||
|
msgid "Angle from l and z"
|
||||||
|
msgstr "Kąt z l i z"
|
||||||
|
|
||||||
|
msgid "+ Arc"
|
||||||
|
msgstr "+ Łuk"
|
||||||
|
|
||||||
|
msgid "Arc"
|
||||||
|
msgstr "Łuk"
|
||||||
|
|
||||||
|
msgid "b##designb"
|
||||||
|
msgstr "b##designb"
|
||||||
|
|
||||||
|
msgid "Blade angle, deg##designentry"
|
||||||
|
msgstr "Kąt iglicy, °##designentry"
|
||||||
|
|
||||||
|
msgid "Blade angle, deg##designentry2"
|
||||||
|
msgstr "Kąt iglicy, °##designentry2"
|
||||||
|
|
||||||
|
msgid "Choose a template on the left. The catalogue ones can be copied to own templates and changed there. A template is a straight main track and the diverging track from the start of the switch as a chain of straights, arcs and transition curves; a double switch is two switches, the second one starting on the main track of the first."
|
||||||
|
msgstr "Wybierz szablon po lewej. Katalogowe można skopiować do własnych i tam zmieniać. Szablon to prosty tor zasadniczy i tor zwrotny od początku rozjazdu jako ciąg prostych, łuków i krzywych przejściowych; rozjazd podwójny to dwie zwrotnice, druga zaczyna się na torze zasadniczym pierwszej."
|
||||||
|
|
||||||
|
msgid " (copy)"
|
||||||
|
msgstr " (kopia)"
|
||||||
|
|
||||||
|
msgid "Copy to own templates"
|
||||||
|
msgstr "Kopiuj do własnych szablonów"
|
||||||
|
|
||||||
|
msgid "Distance from the start of the first switch to the start of the second one, along the main track"
|
||||||
|
msgstr "Odległość od początku pierwszej zwrotnicy do początku drugiej, wzdłuż toru zasadniczego"
|
||||||
|
|
||||||
|
msgid "Double switch %s + %s: length %.3f m, the second switch starts %.3f m on"
|
||||||
|
msgstr "Rozjazd podwójny %s + %s: długość %.3f m, druga zwrotnica zaczyna się po %.3f m"
|
||||||
|
|
||||||
|
msgid "Double switch %s + %s with drives: events %s+, %s- and %s+, %s- switch them, also in the map"
|
||||||
|
msgstr "Rozjazd podwójny %s + %s z napędami: zdarzenia %s+, %s- i %s+, %s- je przestawiają, także w mapie"
|
||||||
|
|
||||||
|
msgid "Double switch %s with its second switch %s inserted"
|
||||||
|
msgstr "Wstawiono rozjazd podwójny %s z drugą zwrotnicą %s"
|
||||||
|
|
||||||
|
msgid "Draws the offsets from the main track larger, so the shape of the diverging track can be seen"
|
||||||
|
msgstr "Rysuje odsunięcia od toru zasadniczego większe, żeby było widać kształt toru zwrotnego"
|
||||||
|
|
||||||
|
msgid "Fill in"
|
||||||
|
msgstr "Wypełnij"
|
||||||
|
|
||||||
|
msgid "First switch: diverging %s, end offset %.3f m"
|
||||||
|
msgstr "Pierwsza zwrotnica: zwrotny w %s, odsunięcie końca %.3f m"
|
||||||
|
|
||||||
|
msgid "From the point where the tangents meet to the end, as the catalogue gives it; 0: the main track less a"
|
||||||
|
msgstr "Od punktu przecięcia stycznych do końca, jak podaje katalog; 0: tor zasadniczy minus a"
|
||||||
|
|
||||||
|
msgid "From the start of the switch (the joint before the blades) to the tip of the blade; the diverging track runs along the main one there"
|
||||||
|
msgstr "Od początku rozjazdu (styku przediglicowego) do ostrza iglicy; tor zwrotny biegnie tam wzdłuż zasadniczego"
|
||||||
|
|
||||||
|
msgid "From the start to the point where the tangents of both tracks meet, as the catalogue gives it; 0: from the geometry"
|
||||||
|
msgstr "Od początku do punktu przecięcia stycznych obu torów, jak podaje katalog; 0: z geometrii"
|
||||||
|
|
||||||
|
msgid "Keeps the own templates in eu07_switch_templates.txt in the configuration directory of the simulator and puts them on the list of the switch tool"
|
||||||
|
msgstr "Zapisuje własne szablony w eu07_switch_templates.txt w katalogu konfiguracji symulatora i dodaje je do listy narzędzia rozjazdów"
|
||||||
|
|
||||||
|
msgid "Length %.3f m, the diverging track ends %.3f m beside the main one, in %zu pieces"
|
||||||
|
msgstr "Długość %.3f m, tor zwrotny kończy się %.3f m obok zasadniczego, w %zu kawałkach"
|
||||||
|
|
||||||
|
msgid "Length of the main track, 0: as far as the diverging tracks reach"
|
||||||
|
msgstr "Długość toru zasadniczego, 0: tak daleko, jak sięgają tory zwrotne"
|
||||||
|
|
||||||
|
msgid "Length of the part, measured along it"
|
||||||
|
msgstr "Długość części, mierzona wzdłuż niej"
|
||||||
|
|
||||||
|
msgid "l, m##bladelength"
|
||||||
|
msgstr "l, m##bladelength"
|
||||||
|
|
||||||
|
msgid "Main track, m##designmain"
|
||||||
|
msgstr "Tor zasadniczy, m##designmain"
|
||||||
|
|
||||||
|
msgid "m##length"
|
||||||
|
msgstr "m##length"
|
||||||
|
|
||||||
|
msgid "Name##designname"
|
||||||
|
msgstr "Nazwa##designname"
|
||||||
|
|
||||||
|
msgid "Otherwise the second switch diverges the same way as the first, to the other side for Rpd, to the same side for Rpj"
|
||||||
|
msgstr "W przeciwnym razie druga zwrotnica odgałęzia się tak samo jak pierwsza, w drugą stronę dla Rpd, w tę samą dla Rpj"
|
||||||
|
|
||||||
|
msgid "Own: "
|
||||||
|
msgstr "Własny: "
|
||||||
|
|
||||||
|
msgid "Own switch templates: diverging tracks of straights, arcs and transition curves, double switches"
|
||||||
|
msgstr "Własne szablony rozjazdów: tory zwrotne z prostych, łuków i krzywych przejściowych, rozjazdy podwójne"
|
||||||
|
|
||||||
|
msgid "Place this switch"
|
||||||
|
msgstr "Postaw ten rozjazd"
|
||||||
|
|
||||||
|
msgid "Pre-blade stretch, m##designlead"
|
||||||
|
msgstr "Odcinek przediglicowy, m##designlead"
|
||||||
|
|
||||||
|
msgid "Pre-blade stretch, m##designlead2"
|
||||||
|
msgstr "Odcinek przediglicowy, m##designlead2"
|
||||||
|
|
||||||
|
msgid "Radius at the end of the transition curve, 0: into a straight; the curvature changes evenly along it"
|
||||||
|
msgstr "Promień na końcu krzywej przejściowej, 0: w prostą; krzywizna zmienia się wzdłuż niej równomiernie"
|
||||||
|
|
||||||
|
msgid "Radius at the start of the transition curve, 0: from a straight"
|
||||||
|
msgstr "Promień na początku krzywej przejściowej, 0: z prostej"
|
||||||
|
|
||||||
|
msgid "Radius of the arc; negative turns back towards the main track"
|
||||||
|
msgstr "Promień łuku; ujemny skręca z powrotem ku torowi zasadniczemu"
|
||||||
|
|
||||||
|
msgid "Rail##designrail"
|
||||||
|
msgstr "Szyna##designrail"
|
||||||
|
|
||||||
|
msgid "Rail of the switch kept in its record: 60E1, 49E1..."
|
||||||
|
msgstr "Szyna rozjazdu zapisywana w jego rekordzie: 60E1, 49E1..."
|
||||||
|
|
||||||
|
msgid "Removes the template; Save keeps the change"
|
||||||
|
msgstr "Usuwa szablon; Zapisz utrwala zmianę"
|
||||||
|
|
||||||
|
msgid "Replaces the parts with an arc of R up to the angle 1:n and the straight after it, the shape of an ordinary switch"
|
||||||
|
msgstr "Zastępuje części łukiem R do skosu 1:n i prostą za nim, kształtem rozjazdu zwyczajnego"
|
||||||
|
|
||||||
|
msgid "R from##radius1"
|
||||||
|
msgstr "R od##radius1"
|
||||||
|
|
||||||
|
msgid "Rpd double, to both sides"
|
||||||
|
msgstr "Rpd podwójny dwustronny"
|
||||||
|
|
||||||
|
msgid "Rpj double, to one side"
|
||||||
|
msgstr "Rpj podwójny jednostronny"
|
||||||
|
|
||||||
|
msgid "R##quickradius"
|
||||||
|
msgstr "R##quickradius"
|
||||||
|
|
||||||
|
msgid "R##radius"
|
||||||
|
msgstr "R##radius"
|
||||||
|
|
||||||
|
msgid "Rz ordinary"
|
||||||
|
msgstr "Rz zwyczajny"
|
||||||
|
|
||||||
|
msgid "Save *"
|
||||||
|
msgstr "Zapisz *"
|
||||||
|
|
||||||
|
msgid "Saved, the templates are on the list of the switch tool"
|
||||||
|
msgstr "Zapisano, szablony są na liście narzędzia rozjazdów"
|
||||||
|
|
||||||
|
msgid "Saves the own templates and chooses this one in the switch tool: then click a straight and drag to the side the switch goes"
|
||||||
|
msgstr "Zapisuje własne szablony i wybiera ten w narzędziu rozjazdów: potem kliknij prostą i przeciągnij w stronę, w którą idzie rozjazd"
|
||||||
|
|
||||||
|
msgid "Second switch starts at, m##stagger"
|
||||||
|
msgstr "Druga zwrotnica zaczyna się po, m##stagger"
|
||||||
|
|
||||||
|
msgid "sin angle = z / l: l the length of a straight blade, z the gap between the running edges of the stock rail and the blade at its heel"
|
||||||
|
msgstr "sin kąta = z / l: l długość iglicy prostej, z odstęp krawędzi tocznych opornicy i iglicy w osadzie"
|
||||||
|
|
||||||
|
msgid "A catalogue template can't be changed"
|
||||||
|
msgstr "Szablonu katalogowego nie można zmieniać"
|
||||||
|
|
||||||
|
msgid "Catalogue (read only)"
|
||||||
|
msgstr "Katalog (tylko do odczytu)"
|
||||||
|
|
||||||
|
msgid "Diverging track from the start of the switch"
|
||||||
|
msgstr "Tor zwrotny od początku rozjazdu"
|
||||||
|
|
||||||
|
msgid "(none yet)"
|
||||||
|
msgstr "(jeszcze brak)"
|
||||||
|
|
||||||
|
msgid "Own templates"
|
||||||
|
msgstr "Własne szablony"
|
||||||
|
|
||||||
|
msgid "Straight blade:"
|
||||||
|
msgstr "Iglica prosta:"
|
||||||
|
|
||||||
|
msgid "+ Straight"
|
||||||
|
msgstr "+ Prosta"
|
||||||
|
|
||||||
|
msgid "straight after, m##quickstraight"
|
||||||
|
msgstr "prosta za łukiem, m##quickstraight"
|
||||||
|
|
||||||
|
msgid "Stretch across"
|
||||||
|
msgstr "Rozciągnij w poprzek"
|
||||||
|
|
||||||
|
msgid "Switch templates"
|
||||||
|
msgstr "Szablony rozjazdów"
|
||||||
|
|
||||||
|
msgid "Template editor..."
|
||||||
|
msgstr "Edytor szablonów..."
|
||||||
|
|
||||||
|
msgid "The angle at which the blade meets the stock rail at its tip: the diverging track starts there at this angle instead of tangentially. Curved blades a fraction of a degree, straight blades 1 to 2 degrees; 0: tangential"
|
||||||
|
msgstr "Kąt oparcia iglicy o opornicę przy ostrzu: tor zwrotny zaczyna się tam pod tym kątem zamiast stycznie. Iglice krzywe ułamek stopnia, proste 1 do 2 stopni; 0: stycznie"
|
||||||
|
|
||||||
|
msgid "The catalogue switches start tangentially at the joint before the blades: without the pre-blade stretch and the angle of the blade, which a copy can be given"
|
||||||
|
msgstr "Rozjazdy katalogowe zaczynają się stycznie na styku przediglicowym: bez odcinka przediglicowego i kąta iglicy, które można nadać kopii"
|
||||||
|
|
||||||
|
msgid "The double switch is replaced by its main track, Shift+Del removes all of it"
|
||||||
|
msgstr "Rozjazd podwójny zastąpiono jego torem zasadniczym, Shift+Del usuwa całość"
|
||||||
|
|
||||||
|
msgid "The second switch has a diverging track of its own"
|
||||||
|
msgstr "Druga zwrotnica ma własny tor zwrotny"
|
||||||
|
|
||||||
|
msgid "Tip of the blade %.3f m from the start, the diverging track leaves there at %.4f deg"
|
||||||
|
msgstr "Ostrze iglicy %.3f m od początku, tor zwrotny odchodzi tam pod kątem %.4f°"
|
||||||
|
|
||||||
|
msgid "to##radius2"
|
||||||
|
msgstr "do##radius2"
|
||||||
|
|
||||||
|
msgid "+ Transition curve"
|
||||||
|
msgstr "+ Krzywa przejściowa"
|
||||||
|
|
||||||
|
msgid "Type##designtype"
|
||||||
|
msgstr "Typ##designtype"
|
||||||
|
|
||||||
|
msgid "z, m##bladegap"
|
||||||
|
msgstr "z, m##bladegap"
|
||||||
|
|
||||||
|
msgid "Unknown type of switch"
|
||||||
|
msgstr "Nieznany typ rozjazdu"
|
||||||
|
|
||||||
|
msgid "The pre-blade stretch has to be between 0 and 30 m"
|
||||||
|
msgstr "Odcinek przediglicowy musi mieć od 0 do 30 m"
|
||||||
|
|
||||||
|
msgid "The angle of the blade has to be between 0 and 5 degrees"
|
||||||
|
msgstr "Kąt iglicy musi wynosić od 0 do 5 stopni"
|
||||||
|
|
||||||
|
msgid "The diverging track has to leave to the side at an angle between 0 and 45 degrees"
|
||||||
|
msgstr "Tor zwrotny musi odchodzić w bok pod kątem od 0 do 45 stopni"
|
||||||
|
|
||||||
|
msgid "The main track is shorter than 1 m"
|
||||||
|
msgstr "Tor zasadniczy jest krótszy niż 1 m"
|
||||||
|
|
||||||
|
msgid "The second switch has to start at least 1 m along the main track and 1 m before the end of the diverging track"
|
||||||
|
msgstr "Druga zwrotnica musi zaczynać się co najmniej 1 m od początku toru zasadniczego i 1 m przed końcem toru zwrotnego"
|
||||||
|
|
||||||
|
msgid "The pre-blade stretch of the second switch has to be at most 30 m and the angle of its blade at most 5 degrees"
|
||||||
|
msgstr "Odcinek przediglicowy drugiej zwrotnicy może mieć najwyżej 30 m, a kąt jej iglicy najwyżej 5 stopni"
|
||||||
|
|
||||||
|
msgid "The diverging track of the second switch has to leave to the side at an angle between 0 and 45 degrees"
|
||||||
|
msgstr "Tor zwrotny drugiej zwrotnicy musi odchodzić w bok pod kątem od 0 do 45 stopni"
|
||||||
|
|
||||||
|
msgid "The main track ends less than 1 m after the start of the second switch"
|
||||||
|
msgstr "Tor zasadniczy kończy się mniej niż 1 m za początkiem drugiej zwrotnicy"
|
||||||
|
|
||||||
|
msgid "The diverging track has no parts"
|
||||||
|
msgstr "Tor zwrotny nie ma części"
|
||||||
|
|
||||||
|
msgid "A part of the diverging track is shorter than 5 cm"
|
||||||
|
msgstr "Część toru zwrotnego jest krótsza niż 5 cm"
|
||||||
|
|
||||||
|
msgid "A radius of the diverging track is under 10 m"
|
||||||
|
msgstr "Promień toru zwrotnego jest mniejszy niż 10 m"
|
||||||
|
|
||||||
|
msgid "%s laid, switches %s"
|
||||||
|
msgstr "Ułożono: %s, zwrotnice %s"
|
||||||
|
|
||||||
|
msgid "%s: %d switches, spacing %.2f m"
|
||||||
|
msgstr "%s: zwrotnic %d, rozstaw %.2f m"
|
||||||
|
|
||||||
|
msgid ", aligning curve R %.0f m"
|
||||||
|
msgstr ", łuk wyrównujący R %.0f m"
|
||||||
|
|
||||||
|
msgid ", crossing 1:%.2f"
|
||||||
|
msgstr ", skrzyżowanie 1:%.2f"
|
||||||
|
|
||||||
|
msgid ", mathematical points %.2f m apart"
|
||||||
|
msgstr ", punkty matematyczne co %.2f m"
|
||||||
|
|
||||||
|
msgid ", straight inserts"
|
||||||
|
msgstr ", wstawki proste"
|
||||||
|
|
||||||
|
msgid ", tracks at"
|
||||||
|
msgstr ", tory w odległości"
|
||||||
|
|
||||||
|
msgid "A connection is laid between straights"
|
||||||
|
msgstr "Połączenie układa się między prostymi"
|
||||||
|
|
||||||
|
msgid "A connection is made of ordinary switches, not of slips, crossings or double switches"
|
||||||
|
msgstr "Połączenie składa się z rozjazdów zwyczajnych, nie z krzyżowych, skrzyżowań ani podwójnych"
|
||||||
|
|
||||||
|
msgid "A connection of tracks laid whole: press on a straight and drag onto the other track. Its switches are cut into the tracks with the inserts between them and get their drives; Shift+wheel while dragging: another kind, Ctrl+wheel: R of the aligning curve"
|
||||||
|
msgstr "Połączenie torów układane w całości: wciśnij na prostej i przeciągnij na drugi tor. Zwrotnice są wcinane w tory razem ze wstawkami między nimi i dostają napędy; Shift+kółko podczas przeciągania: inny rodzaj, Ctrl+kółko: R łuku wyrównującego"
|
||||||
|
|
||||||
|
msgid "A scissors crossover needs parallel tracks and switches of the same angle"
|
||||||
|
msgstr "Nożyce wymagają torów równoległych i rozjazdów o tym samym skosie"
|
||||||
|
|
||||||
|
msgid "A switch of the connection doesn't fit where it was planned, its straight is too short"
|
||||||
|
msgstr "Zwrotnica połączenia nie mieści się tam, gdzie ją zaplanowano, jej prosta jest za krótka"
|
||||||
|
|
||||||
|
msgid "A switch of the connection goes where its track isn't a plain straight"
|
||||||
|
msgstr "Zwrotnica połączenia wypada tam, gdzie tor nie jest zwykłą prostą"
|
||||||
|
|
||||||
|
msgid "A switch of the connection runs off the end of its straight"
|
||||||
|
msgstr "Zwrotnica połączenia wychodzi poza koniec swojej prostej"
|
||||||
|
|
||||||
|
msgid "Aligning curve R, m##connectionradius"
|
||||||
|
msgstr "R łuku wyrównującego, m##connectionradius"
|
||||||
|
|
||||||
|
msgid "Alt+wheel"
|
||||||
|
msgstr "Alt+kółko"
|
||||||
|
|
||||||
|
msgid "Connection##switchconnection"
|
||||||
|
msgstr "Połączenie##switchconnection"
|
||||||
|
|
||||||
|
msgid "Crossover"
|
||||||
|
msgstr "Przejście rozjazdowe"
|
||||||
|
|
||||||
|
msgid "Distances between the tracks of the fan from this track on, such as 4.75;4.75;5.0; used when the drag doesn't end on a parallel track, otherwise the parallel tracks up to that one make the fan"
|
||||||
|
msgstr "Odległości między torami wachlarza od tego toru, np. 4.75;4.75;5.0; używane, gdy przeciąganie nie kończy się na torze równoległym, w przeciwnym razie wachlarz tworzą tory równoległe aż do tamtego"
|
||||||
|
|
||||||
|
msgid "Double crossover"
|
||||||
|
msgstr "Podwójne przejście rozjazdowe"
|
||||||
|
|
||||||
|
msgid "Drag onto the other track"
|
||||||
|
msgstr "Przeciągnij na drugi tor"
|
||||||
|
|
||||||
|
msgid "Gap between the switches, m##connectiongap"
|
||||||
|
msgstr "Odstęp między zwrotnicami, m##connectiongap"
|
||||||
|
|
||||||
|
msgid "Give the spacings of the tracks of the ladder"
|
||||||
|
msgstr "Podaj rozstawy torów ulicy zwrotnicowej"
|
||||||
|
|
||||||
|
msgid "Length of the new tracks of the fan where there are none to lead onto"
|
||||||
|
msgstr "Długość nowych torów wachlarza tam, gdzie nie ma torów do połączenia"
|
||||||
|
|
||||||
|
msgid "New tracks, m##connectionstub"
|
||||||
|
msgstr "Nowe tory, m##connectionstub"
|
||||||
|
|
||||||
|
msgid "Of the curve between switches of different angles or on tracks that aren't parallel, and at the end of a ladder; 0: the radius of the sharper switch"
|
||||||
|
msgstr "Łuku między rozjazdami o różnych skosach lub na torach nierównoległych oraz na końcu ulicy zwrotnicowej; 0: promień ostrzejszego rozjazdu"
|
||||||
|
|
||||||
|
msgid "Other switches##connectionsecond"
|
||||||
|
msgstr "Pozostałe zwrotnice##connectionsecond"
|
||||||
|
|
||||||
|
msgid "Otherwise the two crossovers meet on the other track; Alt+wheel while dragging switches it"
|
||||||
|
msgstr "W przeciwnym razie oba przejścia spotykają się na drugim torze; Alt+kółko podczas przeciągania to przełącza"
|
||||||
|
|
||||||
|
msgid "R of the aligning curve"
|
||||||
|
msgstr "R łuku wyrównującego"
|
||||||
|
|
||||||
|
msgid "Scissors crossover"
|
||||||
|
msgstr "Nożyce"
|
||||||
|
|
||||||
|
msgid "Single switch"
|
||||||
|
msgstr "Pojedynczy rozjazd"
|
||||||
|
|
||||||
|
msgid "Spacings, m##connectionspacings"
|
||||||
|
msgstr "Rozstawy, m##connectionspacings"
|
||||||
|
|
||||||
|
msgid "Straight between the starts of the two switches meeting on one track"
|
||||||
|
msgstr "Prosta między początkami dwóch zwrotnic spotykających się na jednym torze"
|
||||||
|
|
||||||
|
msgid "Switch ladder"
|
||||||
|
msgstr "Ulica zwrotnicowa"
|
||||||
|
|
||||||
|
msgid "Template of the switches on the other track and along a ladder; the first switch is the one chosen on the list above"
|
||||||
|
msgstr "Szablon zwrotnic na drugim torze i wzdłuż ulicy zwrotnicowej; pierwsza zwrotnica to ta wybrana na liście powyżej"
|
||||||
|
|
||||||
|
msgid "The aligning curve R %.0f m with %.0f m straights doesn't fit between the switches"
|
||||||
|
msgstr "Łuk wyrównujący R %.0f m z prostymi %.0f m nie mieści się między zwrotnicami"
|
||||||
|
|
||||||
|
msgid "The diverging track runs along the other track"
|
||||||
|
msgstr "Tor zwrotny biegnie wzdłuż drugiego toru"
|
||||||
|
|
||||||
|
msgid "The diverging tracks meet at %.2f deg, a straight insert can't join them"
|
||||||
|
msgstr "Tory zwrotne spotykają się pod kątem %.2f°, wstawka prosta ich nie połączy"
|
||||||
|
|
||||||
|
msgid "The same as the first one"
|
||||||
|
msgstr "Taka sama jak pierwsza"
|
||||||
|
|
||||||
|
msgid "The spacings of the tracks of a ladder have to be at least 1 m"
|
||||||
|
msgstr "Rozstawy torów ulicy zwrotnicowej muszą mieć co najmniej 1 m"
|
||||||
|
|
||||||
|
msgid "The straight between the curves of the switches is %.2f m, under %.0f m"
|
||||||
|
msgstr "Prosta między łukami zwrotnic ma %.2f m, mniej niż %.0f m"
|
||||||
|
|
||||||
|
msgid "The switch on the other track runs off the end of its straight"
|
||||||
|
msgstr "Zwrotnica na drugim torze wychodzi poza koniec swojej prostej"
|
||||||
|
|
||||||
|
msgid "The switch template has no diverging track"
|
||||||
|
msgstr "Szablon rozjazdu nie ma toru zwrotnego"
|
||||||
|
|
||||||
|
msgid "The switches can't face each other across these tracks"
|
||||||
|
msgstr "Zwrotnice nie mogą stanąć naprzeciw siebie na tych torach"
|
||||||
|
|
||||||
|
msgid "The switches meet on this track##connectiontogether"
|
||||||
|
msgstr "Zwrotnice spotykają się na tym torze##connectiontogether"
|
||||||
|
|
||||||
|
msgid "The switches of the two crossovers overlap, the gap between them has to be longer"
|
||||||
|
msgstr "Zwrotnice obu przejść nachodzą na siebie, odstęp między nimi musi być dłuższy"
|
||||||
|
|
||||||
|
msgid "The tracks are %.2f m too close for these switches"
|
||||||
|
msgstr "Tory są o %.2f m za blisko dla tych rozjazdów"
|
||||||
|
|
||||||
|
msgid "The tracks are too close for a scissors crossover with these switches, its switches overlap"
|
||||||
|
msgstr "Tory są za blisko dla nożyc z tymi rozjazdami, ich zwrotnice nachodzą na siebie"
|
||||||
|
|
||||||
|
msgid "The tracks are too close for the crossing of a scissors crossover: %.2f m more spacing is needed"
|
||||||
|
msgstr "Tory są za blisko dla skrzyżowania nożyc: potrzeba o %.2f m większego rozstawu"
|
||||||
|
|
||||||
|
msgid "The tracks of a fan have to be parallel, on one side and at least 1 m apart"
|
||||||
|
msgstr "Tory wachlarza muszą być równoległe, po jednej stronie i co najmniej 1 m od siebie"
|
||||||
|
|
||||||
|
msgid "Track %d of the fan is %.2f m too close to the previous one for these switches"
|
||||||
|
msgstr "Tor %d wachlarza jest o %.2f m za blisko poprzedniego dla tych rozjazdów"
|
||||||
|
|
||||||
|
msgid "Track %d of the fan reaches into the ladder, it has to start %.2f m further on"
|
||||||
|
msgstr "Tor %d wachlarza wchodzi w ulicę zwrotnicową, musi zaczynać się %.2f m dalej"
|
||||||
|
|
||||||
|
msgid "another kind"
|
||||||
|
msgstr "inny rodzaj"
|
||||||
|
|
||||||
|
msgid "the connection there"
|
||||||
|
msgstr "połączenie tam"
|
||||||
|
|
||||||
|
msgid "where a double crossover meets"
|
||||||
|
msgstr "gdzie spotyka się podwójne przejście"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user