17
0
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:
maj00r
2026-10-15 14:25:27 +02:00
19 changed files with 3655 additions and 185 deletions

View 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

View 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

View 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

View 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

View File

@@ -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)};

View File

@@ -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

View File

@@ -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"}})
{

View File

@@ -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);