mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 00:50:29 +01:00
Merge branch 'feat/track-join-ux' into feat/switch-blades
This commit is contained in:
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};
|
||||
|
||||
// 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>
|
||||
// km <km|-> number <label|-> source <designed|measured>
|
||||
std::string word(std::string const &Text)
|
||||
@@ -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")
|
||||
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,9 +26,9 @@ namespace switch_store
|
||||
struct record
|
||||
{
|
||||
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 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{"-"};
|
||||
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
|
||||
@@ -36,7 +36,7 @@ struct record
|
||||
double a{0.0};
|
||||
double b{0.0};
|
||||
double main_radius{0.0}; // of the main track, signed: + to the right, - to the left, 0 straight
|
||||
std::vector<std::string> parts; // the switch, the switches of a slip, the paths of a crossing
|
||||
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::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
|
||||
@@ -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
|
||||
|
||||
@@ -25,6 +25,7 @@ http:
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "utilities/parser.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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)};
|
||||
return glm::dvec3{planar.x, Local.y + Grade * Local.z, planar.y};
|
||||
};
|
||||
for (auto const &local : Template.local)
|
||||
{
|
||||
segment_data path{local};
|
||||
auto const put = [&](segment_data const &Local) {
|
||||
segment_data path{Local};
|
||||
path.transform([&](glm::dvec3 const &Point) { return Origin + vector(Point); }, vector);
|
||||
if (Side < 0)
|
||||
{
|
||||
path.rolls = {-local.rolls[0], -local.rolls[1]};
|
||||
path.rolls = {-Local.rolls[0], -Local.rolls[1]};
|
||||
for (auto &piece : path.pieces)
|
||||
piece.rolls = {-piece.rolls[0], -piece.rolls[1]};
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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;
|
||||
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)};
|
||||
auto const a{definition.a > 0.0 ? definition.a : definition.radius * std::tan(angle * 0.5)};
|
||||
auto const estimated{definition.b <= 0.0};
|
||||
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)
|
||||
std::string error;
|
||||
auto entry{template_of(designs[i], error)};
|
||||
if (false == entry.has_value())
|
||||
{
|
||||
auto const handle{4.0 / 3.0 * definition.radius * std::tan(angle * 0.25)};
|
||||
segment_data arc;
|
||||
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};
|
||||
WriteLog("Editor: switch template \"" + designs[i].name + "\" left out: " + error, logtype::generic);
|
||||
continue;
|
||||
}
|
||||
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.)" : "");
|
||||
result.push_back(entry);
|
||||
entry->design = static_cast<int>(i);
|
||||
result.push_back(std::move(*entry));
|
||||
}
|
||||
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 "editor/editorAlignment.hpp"
|
||||
#include "editor/editorSwitchDesign.hpp"
|
||||
#include "utilities/Classes.h"
|
||||
|
||||
class TTrack;
|
||||
@@ -407,8 +408,16 @@ class editor_track
|
||||
bool double_slip{false};
|
||||
std::string type{"Rz"};
|
||||
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::optional<switch_template> template_of(switch_design::design const &Design, std::string &Error);
|
||||
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 *create_path(TTrack const &Style, segment_data const &Path);
|
||||
|
||||
Reference in New Issue
Block a user