mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-22 23:31:10 +02:00
feat(editor): connections of tracks laid whole from the switch tool: a crossover with a straight insert or an aligning curve between switches of different angles or tracks that aren't parallel, a double crossover meeting on either track, a scissors crossover with the crossing of its inserts, and a switch ladder onto the parallel tracks of a fan or onto new ones at given spacings; press on a straight and drag onto the other track, Shift+wheel the kind, Ctrl+wheel the R of the aligning curve, Alt+wheel where a double crossover meets; the ghost shows the whole set with the spacing, the inserts and the distance of the mathematical points; switches get their drives and a //$r connection record; the crossover drawn from a diverging end uses the same solver; self-test connection
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user