mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-22 14:23:20 +02:00
feat(editor): connections of tracks laid whole from the switch tool: a crossover with a straight insert or an aligning curve between switches of different angles or tracks that aren't parallel, a double crossover meeting on either track, a scissors crossover with the crossing of its inserts, and a switch ladder onto the parallel tracks of a fan or onto new ones at given spacings; press on a straight and drag onto the other track, Shift+wheel the kind, Ctrl+wheel the R of the aligning curve, Alt+wheel where a double crossover meets; the ghost shows the whole set with the spacing, the inserts and the distance of the mathematical points; switches get their drives and a //$r connection record; the crossover drawn from a diverging end uses the same solver; self-test connection
This commit is contained in:
@@ -183,6 +183,8 @@ set(SOURCES
|
||||
"editor/editorAlignmentStore.cpp"
|
||||
"editor/editorSwitchStore.cpp"
|
||||
"editor/editorSwitchDesign.cpp"
|
||||
"editor/editorConnections.cpp"
|
||||
"application/editormodeconnection.cpp"
|
||||
"editor/editorSpeed.cpp"
|
||||
"editor/editorProfile.cpp"
|
||||
"editor/editorGauge.cpp"
|
||||
|
||||
@@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "editor/editorTerrainStreamer.hpp"
|
||||
#include "editor/editorOrthophoto.hpp"
|
||||
#include "editor/editorTrack.hpp"
|
||||
#include "editor/editorConnections.hpp"
|
||||
#include "editor/editorAlignment.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
@@ -998,8 +999,22 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
};
|
||||
slide_state slide;
|
||||
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;
|
||||
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_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);
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -278,6 +278,26 @@ void editor_mode::record_double(TTrack &First, TTrack &Second, editor_track::swi
|
||||
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> {
|
||||
|
||||
@@ -1317,6 +1317,107 @@ 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);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "connection" || command == "connectionplan")
|
||||
{
|
||||
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 (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 = 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};
|
||||
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")
|
||||
{
|
||||
std::string name;
|
||||
|
||||
@@ -3863,7 +3863,9 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
|
||||
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"}};
|
||||
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)
|
||||
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))
|
||||
@@ -4823,6 +4825,25 @@ void editor_mode::render_layout_combo()
|
||||
bool editor_mode::track_wheel(double const Offset)
|
||||
{
|
||||
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)
|
||||
return false;
|
||||
bool const laying{m_lay.active && false == m_lay.points.empty()};
|
||||
@@ -5416,6 +5437,18 @@ bool editor_mode::start_switch_placement()
|
||||
void editor_mode::finish_switch_placement()
|
||||
{
|
||||
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;
|
||||
auto const linked{plan_switch_link(plan)};
|
||||
tool.placing = false;
|
||||
@@ -5765,7 +5798,40 @@ void editor_mode::draw_build_overlay() const
|
||||
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())
|
||||
drawpath(piece, overlay_color::marked);
|
||||
@@ -5892,6 +5958,7 @@ void editor_mode::render_switch_ui()
|
||||
list(true);
|
||||
}
|
||||
}
|
||||
render_connection_ui();
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
ImGui::InputTextWithHint(STR_C("Name##switchname"), switch_name_for_new().c_str(), tool.name, sizeof(tool.name));
|
||||
ImGui::PopItemWidth();
|
||||
@@ -6164,9 +6231,7 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
||||
if (std::abs(lateral) < 0.01)
|
||||
return false;
|
||||
auto const side{lateral > 0.0 ? 1.0 : -1.0};
|
||||
auto const normal{perpendicular * side};
|
||||
auto const offset{std::abs(lateral)};
|
||||
auto const &control{diverging.points[segment_data::point::control2]};
|
||||
auto const &control{(diverging.pieces.empty() ? diverging : diverging.pieces.back()).points[segment_data::point::control2]};
|
||||
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))};
|
||||
if (angle < 1e-4)
|
||||
@@ -6204,22 +6269,6 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
||||
auto const &line{Plan.target};
|
||||
if (line.tracks.empty() || std::abs(cross(line.direction, axis)) > std::sin(glm::radians(1.0)))
|
||||
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;
|
||||
shape.source = tool.track;
|
||||
shape.length = glm::dot(plan_of(main.points[segment_data::point::end] - origin), axis);
|
||||
@@ -6238,11 +6287,14 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const
|
||||
}
|
||||
}
|
||||
Plan.shape = shape;
|
||||
auto const station{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * Plan.along};
|
||||
Plan.paths = editor_track::place_switch(shape, station, direction, Plan.side, line.grade * Plan.direction);
|
||||
Plan.insert = segment_data{};
|
||||
Plan.insert.points[segment_data::point::start] = frog;
|
||||
Plan.insert.points[segment_data::point::end] = Plan.paths[1].points[segment_data::point::end];
|
||||
auto const faced{connection::face(frog, tangent, connection::of(line), 1, shape)};
|
||||
if (false == faced.valid)
|
||||
return false;
|
||||
Plan.along = faced.partner.along;
|
||||
Plan.direction = faced.partner.forward;
|
||||
Plan.side = faced.partner.side;
|
||||
Plan.paths = faced.partner.paths;
|
||||
Plan.insert = faced.insert;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6265,7 +6317,7 @@ bool editor_mode::stretchable(editor_track::snap_target const &Target) const
|
||||
bool editor_mode::plan_switch_link(switch_link &Plan) const
|
||||
{
|
||||
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;
|
||||
auto const &shape{tool.templates[tool.armed]};
|
||||
auto const &line{tool.line};
|
||||
|
||||
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
|
||||
@@ -156,7 +156,7 @@ void load()
|
||||
std::string km;
|
||||
std::size_t count{0};
|
||||
line >> entry.kind;
|
||||
if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing" && entry.kind != "double")
|
||||
if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing" && entry.kind != "double" && entry.kind != "connection")
|
||||
continue;
|
||||
line >> entry.name >> entry.type >> entry.rail >> entry.radius >> entry.ratio >> hand >> entry.a >> entry.b >> entry.main_radius >> keyword >> count;
|
||||
if (line.fail() || keyword != "parts" || count > 64)
|
||||
@@ -205,11 +205,24 @@ std::optional<record> named(TTrack const &Track)
|
||||
if (name.empty())
|
||||
return std::nullopt;
|
||||
for (auto const &entry : g_records)
|
||||
if (std::find(entry.parts.begin(), entry.parts.end(), name) != entry.parts.end())
|
||||
if (entry.kind != "connection" && std::find(entry.parts.begin(), entry.parts.end(), name) != entry.parts.end())
|
||||
return entry;
|
||||
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)
|
||||
{
|
||||
auto const found{named(Track)};
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace switch_store
|
||||
struct record
|
||||
{
|
||||
scene::layer_handle layer{null_handle};
|
||||
std::string kind{"switch"}; // switch, slip, crossing, double
|
||||
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 type{"Rz"}; // Rz, Rlj, Rld, Rkp, Rkpd, Skrz, Rpd, Rpj, - when unknown
|
||||
std::string rail{"-"};
|
||||
@@ -55,6 +55,8 @@ std::vector<record> const &records();
|
||||
std::optional<record> of(TTrack const &Track);
|
||||
// the record of the track by its name alone, also when the switch lost its shape
|
||||
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
|
||||
std::vector<std::string> follow();
|
||||
// radius of the diverging track: of the type, bent with the main track for an arc switch
|
||||
|
||||
@@ -6010,3 +6010,168 @@ 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