mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 12:19:06 +01:00
feat(track): 3D sleepers and switch timbers, blade heel and throw from templates, one rail carving pass
- sleepers3d / sleepers3dplain / sleepers3dbed in eu07.ini: timbers put together from the begin, main and end parts of a model and stretched to their length, plain sleepers of their own model; tracks whose plain sleepers would touch are laid as one cluster, lines of timbers led by priority and turned to the bisectors of frogs and crossings at a limited rate, whole timbers under crossings, split ones between tracks with room to part them, gaps filled per rail and overlaps shortened or dropped; clusters worked out in a background thread while driving, on demand in the editor - the ballast can be left out under the beams (sleepers3dbed no) - blades get their heel and throw from the switch template or blades <heel> <throw> in the scenery; blades of double switches run on into the track linking them to the next switch - rail cutting at frogs, wing rails, movable noses and switch crossings done by one carving pass (RailCarve) - tint of the rail treads applied to the colour only, without the matte band at the frog - a switch trailed through with a movable nose set the other way derails the vehicle - self-tests: sleepercheck, sharedzones, beamsnear, throw, sleepersbg
This commit is contained in:
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorSwitchDesign.hpp"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "world/RailLayout.h"
|
||||
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
@@ -29,6 +30,11 @@ double constexpr kPi{3.14159265358979323846};
|
||||
double constexpr kMinRadius{10.0};
|
||||
double constexpr kTransitionStep{5.0};
|
||||
double constexpr kLink{1.0};
|
||||
double constexpr kTipStep{0.01};
|
||||
double constexpr kHalfGauge{0.7175};
|
||||
double constexpr kSecantAngle{25.0 / 60.0};
|
||||
double constexpr kSecantLead{0.6};
|
||||
double constexpr kCutTips[][2]{{190.0, 1.2483}, {300.0, 1.5}, {500.0, 2.165}, {760.0, 2.633}, {1200.0, 3.3347}};
|
||||
|
||||
glm::dvec2 heading_of(double const Angle)
|
||||
{
|
||||
@@ -215,24 +221,44 @@ bool read_parts(std::istream &Input, std::vector<part> &Parts)
|
||||
|
||||
bool double_switch(std::string const &Type)
|
||||
{
|
||||
return Type == "Rpd" || Type == "Rpj";
|
||||
return Type == "Rpd" || Type == "Rpj" || Type == "Rps";
|
||||
}
|
||||
|
||||
path diverging(std::vector<part> const &Parts, double const Lead, double const Entry)
|
||||
bool symmetric_switch(std::string const &Type)
|
||||
{
|
||||
return Type == "Rls";
|
||||
}
|
||||
|
||||
path diverging(std::vector<part> const &Parts, double const Lead, double const Entry, double const Tip)
|
||||
{
|
||||
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);
|
||||
auto const lay = [&](double const Length, double const Radius1, double const Radius2) {
|
||||
if (Radius1 == 0.0 && Radius2 == 0.0)
|
||||
walk.straight(Length);
|
||||
else if (Radius1 == Radius2)
|
||||
walk.arc(Length, Radius1);
|
||||
else
|
||||
walk.transition(Length, Radius1, Radius2);
|
||||
};
|
||||
auto split{Entry > 0.0 ? 0.0 : Tip - Lead};
|
||||
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);
|
||||
if (split > kTipStep && split < part.length - kTipStep)
|
||||
{
|
||||
auto const k1{curvature_of(part.radius1)};
|
||||
auto const k2{curvature_of(part.radius2)};
|
||||
auto const k{k1 + (k2 - k1) * split / part.length};
|
||||
auto const middle{part.radius1 == part.radius2 ? part.radius1 : (k != 0.0 ? 1.0 / k : 0.0)};
|
||||
lay(split, part.radius1, middle);
|
||||
lay(part.length - split, middle, part.radius2);
|
||||
}
|
||||
else
|
||||
walk.transition(part.length, part.radius1, part.radius2);
|
||||
lay(part.length, part.radius1, part.radius2);
|
||||
split -= part.length;
|
||||
}
|
||||
path result;
|
||||
result.angle = walk.heading;
|
||||
@@ -260,18 +286,80 @@ path diverging(std::vector<part> const &Parts, double const Lead, double const E
|
||||
return result;
|
||||
}
|
||||
|
||||
double blade_tip(std::vector<part> const &Parts, double const Lead, double const Entry, double const Tip, bool const Symmetric)
|
||||
{
|
||||
if (Entry > 0.0)
|
||||
return Lead;
|
||||
if (Tip > Lead)
|
||||
return Tip;
|
||||
auto const reach = [&](double const Gap) {
|
||||
glm::dvec2 at{0.0, Lead};
|
||||
auto heading{0.0};
|
||||
auto apart{0.0};
|
||||
for (auto const &part : Parts)
|
||||
{
|
||||
auto const k1{curvature_of(part.radius1)};
|
||||
auto const k2{curvature_of(part.radius2)};
|
||||
auto const steps{std::max(1, static_cast<int>(std::ceil(part.length / kTipStep)))};
|
||||
auto const step{part.length / steps};
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
auto const k{k1 + (k2 - k1) * (i + 0.5) / steps};
|
||||
auto const next{at + heading_of(heading + 0.5 * k * step) * step};
|
||||
heading += k * step;
|
||||
auto const nextapart{(Symmetric ? 2.0 : 1.0) * (next.x + kHalfGauge * (1.0 - std::cos(heading)))};
|
||||
if (nextapart >= Gap)
|
||||
return glm::mix(at.y, next.y, std::clamp((Gap - apart) / std::max(nextapart - apart, 1e-12), 0.0, 1.0));
|
||||
at = next;
|
||||
apart = nextapart;
|
||||
}
|
||||
}
|
||||
return Lead; };
|
||||
return std::max({Lead, reach(track_rails::blade_point), reach(track_rails::blade_nose) - track_rails::blade_planed});
|
||||
}
|
||||
|
||||
double cut_tip(std::string const &Rail, double const Radius)
|
||||
{
|
||||
if (Rail != "60E1" && Rail != "49E1")
|
||||
return 0.0;
|
||||
for (auto const &cut : kCutTips)
|
||||
if (std::abs(Radius - cut[0]) < 0.5)
|
||||
return cut[1];
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
std::pair<double, double> secant_blade(std::string const &Rail, double const Radius, double const Ratio)
|
||||
{
|
||||
if (Rail != "S42" || Ratio <= 0.0)
|
||||
return {0.0, 0.0};
|
||||
for (auto const &entry : catalogue())
|
||||
{
|
||||
if (entry.type != "Rz" || entry.rail != Rail || entry.entry <= 0.0 || entry.parts.empty() || std::abs(entry.parts.front().radius1 - Radius) > 0.5)
|
||||
continue;
|
||||
auto turn{glm::radians(entry.entry)};
|
||||
for (auto const &part : entry.parts)
|
||||
turn += 0.5 * (curvature_of(part.radius1) + curvature_of(part.radius2)) * part.length;
|
||||
if (std::abs(turn - std::atan(1.0 / Ratio)) < 1e-4)
|
||||
return {entry.lead, glm::radians(entry.entry)};
|
||||
}
|
||||
return {kSecantLead, glm::radians(kSecantAngle)};
|
||||
}
|
||||
|
||||
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)
|
||||
bool const symmetric{symmetric_switch(Design.type)};
|
||||
if (Design.type != "Rz" && false == twofold && false == symmetric)
|
||||
{
|
||||
Error = "Unknown type of switch";
|
||||
return result;
|
||||
}
|
||||
Error = checked(Design.parts);
|
||||
if (Error.empty() && twofold && false == Design.parts2.empty())
|
||||
// both switches of a symmetric double switch have the shape of the first one
|
||||
bool const mirrored{Design.type == "Rps"};
|
||||
if (Error.empty() && twofold && false == mirrored && 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";
|
||||
@@ -279,18 +367,29 @@ shape build(design const &Design, std::string &Error)
|
||||
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)};
|
||||
auto const tip{blade_tip(Design.parts, Design.lead, Design.entry, Design.tip, symmetric)};
|
||||
auto const first{diverging(Design.parts, Design.lead, Design.entry, tip)};
|
||||
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);
|
||||
// the tracks of a symmetric switch part by twice the angle of each
|
||||
result.ratio = 1.0 / std::tan(symmetric ? 2.0 * first.angle : first.angle);
|
||||
result.radius = first.radius;
|
||||
result.offset = end.x;
|
||||
result.tip = Design.lead;
|
||||
result.tip = tip;
|
||||
result.a = Design.a > 0.0 ? Design.a : tangent_point(end, first.angle);
|
||||
if (symmetric)
|
||||
{
|
||||
result.length = end.y;
|
||||
result.b = Design.b > 0.0 ? Design.b : result.length - result.a;
|
||||
auto other{first.whole};
|
||||
mirror(other);
|
||||
result.paths = {other, first.whole};
|
||||
return result;
|
||||
}
|
||||
if (false == twofold)
|
||||
{
|
||||
result.length = Design.main > 0.0 ? Design.main : end.y;
|
||||
@@ -309,20 +408,22 @@ shape build(design const &Design, std::string &Error)
|
||||
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};
|
||||
auto const lead2{Design.lead2 >= 0.0 && false == mirrored ? Design.lead2 : Design.lead};
|
||||
auto const entry2{Design.entry2 >= 0.0 && false == mirrored ? Design.entry2 : Design.entry};
|
||||
auto const &parts2{Design.parts2.empty() || mirrored ? Design.parts : Design.parts2};
|
||||
auto const tip2{blade_tip(parts2, lead2, entry2, Design.tip2 >= 0.0 && false == mirrored ? Design.tip2 : Design.tip, false)};
|
||||
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)};
|
||||
auto second{diverging(parts2, lead2, entry2, tip2)};
|
||||
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")
|
||||
if (Design.type == "Rpd" || mirrored)
|
||||
mirror(second.whole);
|
||||
shift(second.whole, Design.stagger);
|
||||
auto const secondend{second.whole.points[segment_data::point::end].z};
|
||||
@@ -334,7 +435,7 @@ shape build(design const &Design, std::string &Error)
|
||||
}
|
||||
result.b = Design.b > 0.0 ? Design.b : reach - result.a;
|
||||
result.stagger = Design.stagger;
|
||||
result.tip2 = Design.stagger + lead2;
|
||||
result.tip2 = Design.stagger + tip2;
|
||||
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)};
|
||||
@@ -344,75 +445,184 @@ shape build(design const &Design, std::string &Error)
|
||||
|
||||
std::vector<design> catalogue()
|
||||
{
|
||||
// the switches of the instruction Id-4 of PKP PLK, tables 8-1 (60E1), 8-2 (49E1) and 8-3 (S42): a from the start of the switch
|
||||
// to its centre, b on from there along the main track, and along the diverging track where the scheme gives it another length
|
||||
struct ordinary
|
||||
{
|
||||
char const *name;
|
||||
char const *rail;
|
||||
double radius;
|
||||
double ratio;
|
||||
double a;
|
||||
double b;
|
||||
double ratio; // at the end of the diverging track; of the two tracks parting, for a symmetric switch
|
||||
double a; // 0: from the geometry
|
||||
double b; // 0: the diverging track curved up to its end
|
||||
double diverging{0.0}; // 0: the same as b
|
||||
bool movable{false};
|
||||
char const *type{"Rz"};
|
||||
bool tangent{false}; // an S42 switch with blades lying tangent to the stock rails, its a too long for blades cutting into them at 25'
|
||||
};
|
||||
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},
|
||||
{"Rz 60E1-190-1:9", "60E1", 190.0, 9.0, 10.523, 16.615},
|
||||
{"Rz 60E1-300-1:9", "60E1", 300.0, 9.0, 16.615, 16.615},
|
||||
{"Rz 60E1-500-1:12", "60E1", 500.0, 12.0, 20.797, 20.797},
|
||||
{"Rz 60E1-500-1:12 movable nose", "60E1", 500.0, 12.0, 20.797, 20.797, 0.0, true},
|
||||
{"Rz 60E1-760-1:14", "60E1", 760.0, 14.0, 27.108, 27.108},
|
||||
{"Rz 60E1-1200-1:18,5", "60E1", 1200.0, 18.5, 32.409, 32.409},
|
||||
{"Rz 60E1-1200-1:18,5 movable nose", "60E1", 1200.0, 18.5, 32.409, 32.409, 0.0, true},
|
||||
{"Rz 60E1-2500-1:26,5 movable nose", "60E1", 2500.0, 26.5, 47.153, 47.153, 0.0, true},
|
||||
{"Rz 49E1-190-1:7,5", "49E1", 190.0, 7.5, 12.611, 12.611},
|
||||
{"Rz 49E1-190-1:7,5 long", "49E1", 190.0, 7.5, 12.611, 17.428},
|
||||
{"Rz 49E1-190-1:9", "49E1", 190.0, 9.0, 10.523, 16.615},
|
||||
{"Rz 49E1-190-1:9 gauge 1524", "49E1", 190.0, 9.0, 10.523, 17.417},
|
||||
{"Rz 49E1-190-1:7,5/1:6,6", "49E1", 190.0, 6.6, 14.312, 10.910, 14.312},
|
||||
{"Rz 49E1-300-1:9", "49E1", 300.0, 9.0, 16.615, 16.615},
|
||||
{"Rz 49E1-300-1:9,403", "49E1", 300.0, 9.403, 15.907, 17.343, 17.322},
|
||||
{"Rz 49E1-500-1:12", "49E1", 500.0, 12.0, 20.797, 20.797},
|
||||
{"Rz 49E1-500-1:14", "49E1", 500.0, 14.0, 17.834, 24.537},
|
||||
{"Rz 49E1-1200-1:18,5", "49E1", 1200.0, 18.5, 32.409, 32.409},
|
||||
// table 3-2, without a scheme
|
||||
{"Rls 49E1-215-1:4,8", "49E1", 215.0, 4.8, 0.0, 0.0, 0.0, false, "Rls"},
|
||||
{"Rz S42-190-1:6,6", "S42", 190.0, 6.6, 13.374, 12.560, 14.172},
|
||||
{"Rz S42-190-1:7", "S42", 190.0, 7.0, 12.573, 13.361},
|
||||
{"Rz S42-190-1:7,5", "S42", 190.0, 7.5, 11.691, 14.243, 14.235},
|
||||
{"Rz S42-205-1:9 I", "S42", 205.0, 9.0, 10.517, 16.500},
|
||||
{"Rz S42-205-1:9 II", "S42", 205.0, 9.0, 10.517, 17.591},
|
||||
{"Rz S42-230-1:9,51 I", "S42", 230.0, 9.51, 11.046, 16.008, 16.308},
|
||||
{"Rz S42-230-1:9,51 II", "S42", 230.0, 9.51, 11.046, 18.342, 18.642},
|
||||
{"Rz S42-230-1:9,51 III", "S42", 230.0, 9.51, 11.046, 16.008, 18.642},
|
||||
{"Rz S42-265-1:10 I", "S42", 265.0, 10.0, 11.9692, 18.1508},
|
||||
{"Rz S42-265-1:10 II", "S42", 265.0, 10.0, 11.9692, 19.1918},
|
||||
{"Rz S42-500-1:9", "S42", 500.0, 9.0, 25.055, 13.743, 27.513},
|
||||
{"Rz S42-500-1:12", "S42", 500.0, 12.0, 18.21526, 20.58234, 20.58202},
|
||||
{"Rz S42-500-1:14", "S42", 500.0, 14.0, 15.288, 26.323},
|
||||
{"Rz S42-70-1:5 industrial", "S42", 70.0, 5.0, 7.112, 8.708},
|
||||
{"Rz S42-140-1:7 industrial", "S42", 140.0, 7.0, 7.670, 12.932},
|
||||
{"Rls S42-140-1:5 industrial", "S42", 140.0, 5.0, 7.131, 8.715, 0.0, false, "Rls"},
|
||||
{"Rls S42-200-1:7 industrial", "S42", 200.0, 7.0, 6.493, 12.148, 0.0, false, "Rls"},
|
||||
{"Rz S42-205-1:6,6 industrial", "S42", 205.0, 6.6, 14.588, 13.602, 15.352},
|
||||
{"Rz S42-205-1:7 industrial", "S42", 205.0, 7.0, 13.710, 14.480},
|
||||
{"Rz S42-205-1:7,5 industrial", "S42", 205.0, 7.5, 12.754, 15.436, 15.427},
|
||||
{"Rz S42-300-1:9 industrial", "S42", 300.0, 9.0, 16.615, 16.615, 0.0, false, "Rz", true},
|
||||
{"Rz S42-205-1:9 gauge 1524", "S42", 205.0, 9.0, 10.513, 18.394},
|
||||
};
|
||||
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.rail = Ordinary.rail;
|
||||
entry.type = Ordinary.type;
|
||||
entry.movable = Ordinary.movable;
|
||||
auto const symmetric{symmetric_switch(entry.type)};
|
||||
// each track of a symmetric switch turns by a half of the angle between them
|
||||
auto const angle{std::atan(1.0 / Ordinary.ratio) * (symmetric ? 0.5 : 1.0)};
|
||||
auto const radius{Ordinary.radius};
|
||||
auto const tangent{radius * std::tan(angle * 0.5)};
|
||||
auto const a{Ordinary.a > 0.0 ? Ordinary.a : tangent};
|
||||
auto const across = [&](double const Entry) { return radius * (std::cos(Entry) - std::cos(angle)); };
|
||||
// the centre nearer than the tangent of the arc: the blade meets the stock rail at an angle and the arc turns the rest of the way;
|
||||
// further: the diverging track runs along the main one for a while
|
||||
// within a couple of centimetres of the tangent the catalogue only rounds it
|
||||
auto const centre = [&](double const Entry) { return radius * (std::sin(angle) - std::sin(Entry)) - across(Entry) / std::tan(angle); };
|
||||
auto const fit = [&](double const Centre) {
|
||||
auto low{0.0};
|
||||
auto high{angle};
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const middle{(low + high) * 0.5};
|
||||
(centre(middle) > Centre ? low : high) = middle;
|
||||
}
|
||||
return (low + high) * 0.5; };
|
||||
auto entryangle{0.0};
|
||||
auto lead{a > tangent + 0.02 ? a - tangent : 0.0};
|
||||
if (entry.rail == "S42" && false == Ordinary.tangent)
|
||||
{
|
||||
entry.parts.push_back({rest, 0.0, 0.0});
|
||||
entry.main = entry.a + entry.b;
|
||||
entryangle = glm::radians(kSecantAngle) * (symmetric ? 0.5 : 1.0);
|
||||
lead = a - centre(entryangle);
|
||||
if (lead <= 0.0)
|
||||
{
|
||||
lead = kSecantLead;
|
||||
entryangle = fit(a - lead);
|
||||
}
|
||||
}
|
||||
else
|
||||
entry.b = 0.0;
|
||||
if (estimated)
|
||||
entry.note = "(b est.)";
|
||||
else if (a < tangent - 0.02)
|
||||
entryangle = fit(a);
|
||||
else if (auto const cut{cut_tip(entry.rail, radius)}; cut > 0.0 && false == symmetric)
|
||||
entry.tip = lead + cut;
|
||||
entry.entry = glm::degrees(entryangle);
|
||||
entry.lead = lead;
|
||||
entry.a = a;
|
||||
auto const arcend{across(entryangle) / std::sin(angle)};
|
||||
entry.b = Ordinary.b > 0.0 ? Ordinary.b : arcend;
|
||||
entry.parts.push_back({radius * (angle - entryangle), radius, radius});
|
||||
auto const rest{(Ordinary.diverging > 0.0 ? Ordinary.diverging : entry.b) - arcend};
|
||||
if (rest > 0.05)
|
||||
entry.parts.push_back({rest, 0.0, 0.0});
|
||||
if (false == symmetric)
|
||||
entry.main = a + entry.b;
|
||||
if (Ordinary.a <= 0.0 || Ordinary.b <= 0.0)
|
||||
entry.note = "(from the geometry)";
|
||||
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);
|
||||
}
|
||||
// double switches: the second one starts on the main track of the first, as far on as the schemes give it, a one sided double
|
||||
// switch of Id-4 has its second switch on the diverging track of the first, here on the main one at the same place
|
||||
ordinary const r190_75{"", "", 190.0, 7.5, 12.611, 12.611};
|
||||
ordinary const r190_75long{"", "", 190.0, 7.5, 12.611, 12.611, 16.615};
|
||||
ordinary const r190_9{"", "", 190.0, 9.0, 10.523, 16.615};
|
||||
ordinary const r190_9tangent{"", "", 190.0, 9.0, 10.523, 16.615, 0.0, false, "Rz", true};
|
||||
ordinary const r190_66{"", "", 190.0, 6.6, 14.312, 10.910, 14.312};
|
||||
ordinary const r300_9{"", "", 300.0, 9.0, 16.615, 16.615};
|
||||
ordinary const r500_12{"", "", 500.0, 12.0, 20.797, 20.797};
|
||||
struct twofold
|
||||
{
|
||||
char const *name;
|
||||
char const *type;
|
||||
std::size_t ordinary;
|
||||
char const *rail;
|
||||
ordinary first;
|
||||
ordinary second;
|
||||
double stagger;
|
||||
char const *note;
|
||||
};
|
||||
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},
|
||||
{"Rpd 49E1-190-1:7,5/1:7,5", "Rpd", "49E1", r190_75long, r190_75, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:9/1:7,5", "Rpd", "49E1", r190_9, r190_75, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:9/1:9", "Rpd", "49E1", r190_9, r190_9, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:7,5/1:7,5/1:6,6", "Rpd", "49E1", r190_75, r190_66, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:7,5/1:6,6/1:7,5", "Rpd", "49E1", r190_66, r190_75, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:7,5/1:6,6/1:7,5/1:6,6", "Rpd", "49E1", r190_66, r190_66, 10.523, ""},
|
||||
{"Rpd 49E1-190-1:9/1:7,5/1:6,6", "Rpd", "49E1", r190_9, r190_66, 10.523, ""},
|
||||
{"Rpj 49E1-190-1:9", "Rpj", "49E1", r190_9, r190_9, 5.258, "(approx.)"},
|
||||
{"Rpd S42-190-1:9", "Rpd", "S42", r190_9tangent, r190_9tangent, 10.523, ""},
|
||||
{"Rpd S42-190-1:9 II", "Rpd", "S42", {"", "", 190.0, 9.0, 9.626, 15.500}, {"", "", 190.0, 9.0, 9.626, 17.591}, 9.5, ""},
|
||||
{"Rpj S42-190/180-1:10", "Rpj", "S42", {"", "", 190.0, 10.0, 0.0, 19.192, 0.0, false, "Rz", true}, {"", "", 180.0, 10.0, 0.0, 19.192, 0.0, false, "Rz", true}, 5.504, "(approx.)"},
|
||||
{"Rpj S42-140-1:9 industrial", "Rpj", "S42", {"", "", 140.0, 9.0, 9.274, 17.590, 0.0, false, "Rz", true}, {"", "", 140.0, 9.0, 9.274, 17.590, 0.0, false, "Rz", true}, 9.263, "(approx.)"},
|
||||
{"Rpd S42-140-1:7 industrial", "Rpd", "S42", {"", "", 140.0, 7.0, 7.670, 12.932}, {"", "", 140.0, 7.0, 7.670, 12.932}, 6.9, ""},
|
||||
// symmetric double switches, not in Id-4 but for its scheme: the same switch to both sides, the second one starting as close behind
|
||||
// the first one as their blades let them, past the heel of the blades of the first one and the short track between the two
|
||||
{"Rps 60E1-300-1:9", "Rps", "60E1", r300_9, r300_9, 14.2, "(designed)"},
|
||||
{"Rps 60E1-500-1:12", "Rps", "60E1", r500_12, r500_12, 18.0, "(designed)"},
|
||||
};
|
||||
for (auto const &entry : twofolds)
|
||||
{
|
||||
auto made{make(ordinaries[entry.ordinary])};
|
||||
auto first{entry.first};
|
||||
auto other{entry.second};
|
||||
first.rail = other.rail = entry.rail;
|
||||
auto made{make(first)};
|
||||
auto const second{make(other)};
|
||||
made.name = entry.name;
|
||||
made.rail = entry.rail;
|
||||
made.type = entry.type;
|
||||
made.stagger = entry.stagger;
|
||||
if (made.type != "Rps")
|
||||
{
|
||||
made.parts2 = second.parts;
|
||||
made.lead2 = second.lead;
|
||||
made.entry2 = second.entry;
|
||||
made.tip2 = second.tip;
|
||||
}
|
||||
made.main = 0.0;
|
||||
made.b = 0.0;
|
||||
made.note = format("(second switch %.1f m on, assumed)", entry.stagger);
|
||||
made.note = entry.note;
|
||||
result.push_back(made);
|
||||
}
|
||||
return result;
|
||||
@@ -461,6 +671,14 @@ std::vector<design> own()
|
||||
line >> entry.lead2;
|
||||
else if (keyword == "entry2")
|
||||
line >> entry.entry2;
|
||||
else if (keyword == "tip")
|
||||
line >> entry.tip;
|
||||
else if (keyword == "tip2")
|
||||
line >> entry.tip2;
|
||||
else if (keyword == "heel")
|
||||
line >> entry.heel;
|
||||
else if (keyword == "throw")
|
||||
line >> entry.bladethrow;
|
||||
else if (keyword == "a")
|
||||
line >> entry.a;
|
||||
else if (keyword == "b")
|
||||
@@ -492,13 +710,15 @@ bool save_own(std::vector<design> const &Designs, std::string &Error)
|
||||
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 << "# switch <name> type <Rz|Rpd|Rpj> rail <rail> main <m> stagger <m> a <m> b <m> lead <m> entry <deg> tip <m> [lead2 <m> entry2 <deg> tip2 <m>] [heel <m> throw <m>] 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 << "switch " << word(entry.name) << " type " << entry.type << " rail " << word(entry.rail) << ' ' << format("main %.4f stagger %.4f a %.4f b %.4f lead %.4f entry %.4f tip %.4f", entry.main, entry.stagger, entry.a, entry.b, entry.lead, entry.entry, entry.tip);
|
||||
if (entry.lead2 >= 0.0 || entry.entry2 >= 0.0 || entry.tip2 >= 0.0)
|
||||
file << format(" lead2 %.4f entry2 %.4f tip2 %.4f", entry.lead2, entry.entry2, entry.tip2);
|
||||
if (entry.heel > 0.0 || entry.bladethrow > 0.0)
|
||||
file << format(" heel %.4f throw %.4f", entry.heel, entry.bladethrow);
|
||||
file << " parts";
|
||||
write_parts(file, entry.parts);
|
||||
if (false == entry.parts2.empty())
|
||||
|
||||
@@ -15,8 +15,9 @@ http://mozilla.org/MPL/2.0/.
|
||||
#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
|
||||
// straight, the diverging track a chain of parts of linearly changing curvature from the start of the switch; a symmetric
|
||||
// switch (Rls) has both tracks turning by the same parts to both sides; 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
|
||||
{
|
||||
|
||||
@@ -30,23 +31,29 @@ struct part
|
||||
struct design
|
||||
{
|
||||
std::string name;
|
||||
std::string type{"Rz"}; // Rz, Rpd, Rpj
|
||||
std::string type{"Rz"}; // Rz, Rls, 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 tip{0.0};
|
||||
double heel{0.0}; // where the moving blades end, from the start of the switch, 0: as long as the flangeway behind them needs
|
||||
double bladethrow{0.0}; // how far the tips of the blades move over, 0: the usual 160 mm
|
||||
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 tip2{-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 movable{false}; // the frog with a movable nose
|
||||
};
|
||||
|
||||
bool double_switch(std::string const &Type);
|
||||
bool symmetric_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
|
||||
@@ -55,7 +62,13 @@ struct path
|
||||
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);
|
||||
path diverging(std::vector<part> const &Parts, double Lead = 0.0, double Entry = 0.0, double Tip = 0.0);
|
||||
double blade_tip(std::vector<part> const &Parts, double Lead, double Entry, double Tip, bool Symmetric);
|
||||
// the tip of a blade with a cut tip from the start of its curve, as the tables of the rail give it, 0: none for the radius
|
||||
double cut_tip(std::string const &Rail, double Radius);
|
||||
// the pre-blade stretch and the angle in radians of blades cutting into the stock rails, as the ordinary switch of the catalogue
|
||||
// with the same rail, radius and ratio has them; 0 and 0 for blades tangent to them
|
||||
std::pair<double, double> secant_blade(std::string const &Rail, double Radius, double Ratio);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -41,7 +41,7 @@ bool g_loaded{false};
|
||||
// format of the lines, coordinates in the space of the file:
|
||||
// <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>
|
||||
// km <km|-> number <label|-> source <designed|measured> [middle <R in the middle of the slip curves of a double slip>]
|
||||
std::string word(std::string const &Text)
|
||||
{
|
||||
if (Text.empty())
|
||||
@@ -122,6 +122,8 @@ void mark(scene::layer_handle const Layer)
|
||||
text += format(" start %.4f %.4f %.4f dir %.9f %.9f km ", start.x, start.y, start.z, direction.x, direction.y);
|
||||
text += entry.km.has_value() ? format("%.3f", *entry.km / 1000.0) : std::string{"-"};
|
||||
text += " number " + word(entry.number) + " source " + word(entry.source);
|
||||
if (entry.middle > 0.0)
|
||||
text += format(" middle %.3f", entry.middle);
|
||||
lines.push_back(text);
|
||||
}
|
||||
scene::Layers.mark(Layer, kMark, std::move(lines));
|
||||
@@ -177,6 +179,8 @@ void load()
|
||||
line >> start.x >> start.y >> start.z >> keyword >> direction.x >> direction.y >> keyword >> km >> keyword >> entry.number >> keyword >> entry.source;
|
||||
if (line.fail())
|
||||
continue;
|
||||
if (line >> keyword && keyword == "middle")
|
||||
line >> entry.middle;
|
||||
entry.start = context.to_world(start);
|
||||
ahead = context.to_world(start + glm::dvec3{direction.x, 0.0, direction.y});
|
||||
entry.direction = plan_of(ahead - entry.start);
|
||||
@@ -341,7 +345,7 @@ std::string label(record const &Record)
|
||||
else
|
||||
text += ' ';
|
||||
if (Record.radius > 0.0)
|
||||
text += format("%.0f-", Record.radius);
|
||||
text += Record.middle > 0.0 ? format("%.0f/%.0f-", Record.radius, Record.middle) : format("%.0f-", Record.radius);
|
||||
if (Record.ratio > 0.0)
|
||||
text += format(std::abs(Record.ratio - std::round(Record.ratio)) < 1e-3 ? "1:%.0f" : "1:%.1f", Record.ratio);
|
||||
if (Record.hand == 'L' || Record.hand == 'R')
|
||||
|
||||
@@ -46,6 +46,7 @@ struct record
|
||||
std::optional<double> km;
|
||||
std::string number{"-"};
|
||||
std::string source{"designed"}; // designed: from the catalogue, measured: copied from a switch of the scenery
|
||||
double middle{0.0}; // the radius in the middle of the slip curves of a double slip with the blades outside of the diamond
|
||||
};
|
||||
|
||||
void reset();
|
||||
|
||||
@@ -138,6 +138,9 @@ editor_track::state editor_track::capture(TTrack const &Track)
|
||||
result.material2 = Track.m_material2;
|
||||
result.material3 = Track.SwitchExtension ? Track.SwitchExtension->m_material3 : null_handle;
|
||||
result.frogs = Track.SwitchExtension && Track.SwitchExtension->m_frogs;
|
||||
result.movablenose = Track.SwitchExtension && Track.SwitchExtension->m_movablenose;
|
||||
result.bladeheel = Track.SwitchExtension ? Track.SwitchExtension->m_bladeheel : 0.0;
|
||||
result.bladethrow = Track.SwitchExtension ? Track.SwitchExtension->m_bladethrow : 0.0;
|
||||
result.texlength = Track.fTexLength;
|
||||
result.texheight = texture_height(Track);
|
||||
result.texwidth = Track.fTexWidth;
|
||||
@@ -151,6 +154,8 @@ editor_track::state editor_track::capture(TTrack const &Track)
|
||||
result.sleeper_skin = Track.m_sleeper_skin_name;
|
||||
result.sleeper_offset = Track.m_sleeper_offset;
|
||||
result.sleeper_ballast = Track.m_sleeper_ballast_z;
|
||||
result.timber_model = Track.m_timber_model_name;
|
||||
result.timber_skin = Track.m_timber_skin_name;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
result.events[i] = events(const_cast<TTrack &>(Track), i);
|
||||
result.name = Track.name();
|
||||
@@ -228,6 +233,9 @@ void editor_track::apply(TTrack &Track, state const &State)
|
||||
Track.SwitchExtension->fVelocity = State.switchvelocity;
|
||||
Track.SwitchExtension->m_material3 = State.material3;
|
||||
Track.SwitchExtension->m_frogs = State.frogs;
|
||||
Track.SwitchExtension->m_movablenose = State.movablenose;
|
||||
Track.SwitchExtension->m_bladeheel = State.bladeheel;
|
||||
Track.SwitchExtension->m_bladethrow = State.bladethrow;
|
||||
}
|
||||
Track.fTrackWidth = State.width;
|
||||
Track.fTrackWidth2 = State.width;
|
||||
@@ -250,6 +258,8 @@ void editor_track::apply(TTrack &Track, state const &State)
|
||||
Track.m_sleeper_skin_name = State.sleeper_skin;
|
||||
Track.m_sleeper_offset = State.sleeper_offset;
|
||||
Track.m_sleeper_ballast_z = State.sleeper_ballast;
|
||||
Track.m_timber_model_name = State.timber_model;
|
||||
Track.m_timber_skin_name = State.timber_skin;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
events(Track, i) = State.events[i];
|
||||
Track.m_events = std::any_of(State.events.begin(), State.events.end(), [](auto const &Events) {
|
||||
@@ -2224,33 +2234,45 @@ std::vector<editor_track::route_sample> editor_track::sample_route(route &Route,
|
||||
{
|
||||
auto &span{Route.spans[i]};
|
||||
auto const &path{span.track->m_paths[span.path]};
|
||||
bezier const curve{path};
|
||||
auto const count{std::max(2, static_cast<int>(std::ceil(curve.plan_length(16) / std::max(0.1, Step))))};
|
||||
// a path made of several curves is sampled along them, its own curve only stands in for them in older versions
|
||||
std::vector<segment_data const *> parts;
|
||||
if (path.pieces.empty())
|
||||
parts.push_back(&path);
|
||||
else
|
||||
for (auto const &piece : path.pieces)
|
||||
parts.push_back(&piece);
|
||||
if (false == span.forward)
|
||||
std::reverse(parts.begin(), parts.end());
|
||||
span.from = chainage;
|
||||
auto previous{plan_of(curve.point(span.forward ? 0.0 : 1.0))};
|
||||
for (int j = (samples.empty() ? 0 : 1); j <= count; ++j)
|
||||
for (auto const *part : parts)
|
||||
{
|
||||
auto const t{span.forward ? static_cast<double>(j) / count : 1.0 - static_cast<double>(j) / count};
|
||||
auto const position{curve.point(t)};
|
||||
chainage += glm::distance(previous, plan_of(position));
|
||||
previous = plan_of(position);
|
||||
auto derivative{curve.first(t)};
|
||||
if (false == span.forward)
|
||||
derivative = -derivative;
|
||||
auto const second{curve.second(t)};
|
||||
auto const planar{std::hypot(derivative.x, derivative.z)};
|
||||
route_sample sample;
|
||||
sample.chainage = chainage;
|
||||
sample.position = position;
|
||||
sample.span = i;
|
||||
sample.cant = 1.5 * std::sin(std::abs(glm::radians(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * t)));
|
||||
if (planar > 1e-9)
|
||||
bezier const curve{*part};
|
||||
auto const count{std::max(2, static_cast<int>(std::ceil(curve.plan_length(16) / std::max(0.1, Step))))};
|
||||
auto previous{plan_of(curve.point(span.forward ? 0.0 : 1.0))};
|
||||
for (int j = (samples.empty() ? 0 : 1); j <= count; ++j)
|
||||
{
|
||||
sample.direction = plan_of(derivative) / planar;
|
||||
sample.grade = derivative.y / planar;
|
||||
sample.curvature = (derivative.x * second.z - derivative.z * second.x) / (planar * planar * planar);
|
||||
auto const t{span.forward ? static_cast<double>(j) / count : 1.0 - static_cast<double>(j) / count};
|
||||
auto const position{curve.point(t)};
|
||||
chainage += glm::distance(previous, plan_of(position));
|
||||
previous = plan_of(position);
|
||||
auto derivative{curve.first(t)};
|
||||
if (false == span.forward)
|
||||
derivative = -derivative;
|
||||
auto const second{curve.second(t)};
|
||||
auto const planar{std::hypot(derivative.x, derivative.z)};
|
||||
route_sample sample;
|
||||
sample.chainage = chainage;
|
||||
sample.position = position;
|
||||
sample.span = i;
|
||||
sample.cant = 1.5 * std::sin(std::abs(glm::radians(part->rolls[0] + (part->rolls[1] - part->rolls[0]) * t)));
|
||||
if (planar > 1e-9)
|
||||
{
|
||||
sample.direction = plan_of(derivative) / planar;
|
||||
sample.grade = derivative.y / planar;
|
||||
sample.curvature = (derivative.x * second.z - derivative.z * second.x) / (planar * planar * planar);
|
||||
}
|
||||
samples.push_back(sample);
|
||||
}
|
||||
samples.push_back(sample);
|
||||
}
|
||||
span.to = chainage;
|
||||
}
|
||||
@@ -2443,6 +2465,26 @@ std::vector<editor_track::height_gap> editor_track::apply_profile(route &Route,
|
||||
auto const sign{span.forward ? 1.0 : -1.0};
|
||||
auto &path{span.track->m_paths[span.path]};
|
||||
set_heights(path, Elevation(startchainage), Elevation(endchainage), Grade(startchainage) * sign, Grade(endchainage) * sign);
|
||||
// the pieces of a path made of several curves follow the grade line along them, else the path would part from them and lose them
|
||||
std::vector<double> lengths;
|
||||
double total{0.0};
|
||||
for (auto const &piece : path.pieces)
|
||||
{
|
||||
lengths.push_back(bezier(piece).plan_length());
|
||||
total += lengths.back();
|
||||
}
|
||||
if (total > 1e-6)
|
||||
{
|
||||
auto const at = [&](double const Distance) { return span.forward ? span.from + Distance * (span.to - span.from) / total : span.to - Distance * (span.to - span.from) / total; };
|
||||
double along{0.0};
|
||||
for (std::size_t k = 0; k < path.pieces.size(); ++k)
|
||||
{
|
||||
auto const first{at(along)};
|
||||
auto const last{at(along + lengths[k])};
|
||||
set_heights(path.pieces[k], Elevation(first), Elevation(last), Grade(first) * sign, Grade(last) * sign);
|
||||
along += lengths[k];
|
||||
}
|
||||
}
|
||||
add_unique(changed, span.track);
|
||||
if (span.track->eType == tt_Normal)
|
||||
continue;
|
||||
@@ -2455,8 +2497,11 @@ std::vector<editor_track::height_gap> editor_track::apply_profile(route &Route,
|
||||
if (neighbour != nullptr && false == on_route(neighbour) && std::none_of(joints.begin(), joints.end(), [&](joint const &Joint) { return Joint.track == span.track && Joint.neighbour == neighbour; }))
|
||||
joints.push_back({span.track, i, atend, neighbour});
|
||||
}
|
||||
if (i != span.path)
|
||||
plane_heights(span.track->m_paths[i], path.points[segment_data::point::start], start_direction(path), grade);
|
||||
if (i == span.path)
|
||||
continue;
|
||||
plane_heights(span.track->m_paths[i], path.points[segment_data::point::start], start_direction(path), grade);
|
||||
for (auto &piece : span.track->m_paths[i].pieces)
|
||||
plane_heights(piece, path.points[segment_data::point::start], start_direction(path), grade);
|
||||
}
|
||||
}
|
||||
commit(changed);
|
||||
@@ -2632,6 +2677,9 @@ std::optional<editor_track::switch_template> editor_track::template_of(switch_de
|
||||
entry.a = shape.a;
|
||||
entry.b = shape.b;
|
||||
entry.own = Design.own;
|
||||
entry.movable = Design.movable;
|
||||
entry.heel = Design.heel;
|
||||
entry.bladethrow = Design.bladethrow;
|
||||
entry.local = {shape.paths[0], shape.paths[1]};
|
||||
entry.local[1].radius = static_cast<float>(shape.radius);
|
||||
if (shape.paths.size() >= 5)
|
||||
@@ -2645,6 +2693,81 @@ std::optional<editor_track::switch_template> editor_track::template_of(switch_de
|
||||
return entry;
|
||||
}
|
||||
|
||||
glm::dvec2 editor_track::slip_curve::point(double const Along) const
|
||||
{
|
||||
auto const arc = [](glm::dvec2 const &From, double const Heading, double const Radius, double const Length) {
|
||||
if (Radius <= 0.0)
|
||||
return From + glm::dvec2{std::cos(Heading), std::sin(Heading)} * Length;
|
||||
auto const to{Heading + Length / Radius};
|
||||
return From + Radius * glm::dvec2{std::sin(to) - std::sin(Heading), std::cos(Heading) - std::cos(to)};
|
||||
};
|
||||
auto const along{Along - lead};
|
||||
auto result{arc({0.0, 0.0}, 0.0, 0.0, std::min(Along, lead))};
|
||||
if (along > 0.0)
|
||||
result = arc(result, entry, radius, std::min(along, ends));
|
||||
if (along > ends)
|
||||
result = arc(result, heading(lead + ends), middle, std::min(along - ends, between));
|
||||
if (along > ends + between)
|
||||
result = arc(result, heading(lead + ends + between), radius, std::min(along - ends - between, ends));
|
||||
if (along > 2.0 * ends + between)
|
||||
result = arc(result, angle, 0.0, along - 2.0 * ends - between);
|
||||
return result;
|
||||
}
|
||||
|
||||
double editor_track::slip_curve::heading(double const Along) const
|
||||
{
|
||||
if (Along < lead)
|
||||
return 0.0;
|
||||
if (Along > length() - lead)
|
||||
return angle;
|
||||
auto const along{Along - lead};
|
||||
auto const inside{std::clamp(along - ends, 0.0, between)};
|
||||
return entry + (std::min(along, ends) + std::max(0.0, along - ends - between)) / radius + (middle > 0.0 ? inside / middle : 0.0);
|
||||
}
|
||||
|
||||
double editor_track::slip_curve::tangent() const
|
||||
{
|
||||
auto const end{point(length())};
|
||||
return end.x - end.y / std::tan(angle);
|
||||
}
|
||||
|
||||
double editor_track::slip_curve::offset() const
|
||||
{
|
||||
return glm::distance(point(length() * 0.5), glm::dvec2{tangent(), 0.0});
|
||||
}
|
||||
|
||||
editor_track::slip_curve editor_track::slip_of(double const Radius, double const Middle, double const Angle, double const Pass, double const Lead, double const Entry)
|
||||
{
|
||||
if (Entry > 0.0 && Angle * 0.5 <= Entry + 1e-6)
|
||||
return slip_of(Radius, Middle, Angle, Pass);
|
||||
auto const make = [&](double const Turn) {
|
||||
return slip_curve{Radius, Radius * Turn, Middle, Middle * (Angle - 2.0 * (Turn + Entry)), Angle, 0.0, Lead, Entry};
|
||||
};
|
||||
if (Middle < 0.0 && Angle >= 1e-6)
|
||||
{
|
||||
// a straight in the middle, as long as the arcs of Radius turning by a half of Angle pass the crossing as near
|
||||
auto low{0.0};
|
||||
auto high{4.0 * Pass / std::tan(Angle * 0.5)};
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const between{(low + high) * 0.5};
|
||||
(slip_curve{Radius, Radius * (Angle * 0.5 - Entry), 0.0, between, Angle, 0.0, Lead, Entry}.offset() > Pass ? high : low) = between;
|
||||
}
|
||||
return {Radius, Radius * (Angle * 0.5 - Entry), 0.0, (low + high) * 0.5, Angle, 0.0, Lead, Entry};
|
||||
}
|
||||
if (Middle <= 0.0 || Angle < 1e-6)
|
||||
return make(Angle * 0.5 - Entry);
|
||||
// the curve passes the crossing nearer as its end arcs turn further, the middle one being flatter
|
||||
auto low{0.0};
|
||||
auto high{Angle * 0.5 - Entry};
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const turn{(low + high) * 0.5};
|
||||
((make(turn).offset() > Pass) == (Middle > Radius) ? low : high) = turn;
|
||||
}
|
||||
return make((low + high) * 0.5);
|
||||
}
|
||||
|
||||
std::vector<editor_track::switch_template> editor_track::standard_switch_templates()
|
||||
{
|
||||
std::vector<switch_template> result;
|
||||
@@ -2663,36 +2786,95 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
|
||||
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"}})
|
||||
// the slips and crossings of Id-4: the angle comes from the straights clicked, the half length from the crossing to the joint
|
||||
// only to the label; the slips with the blades outside of the diamond have a straight in the middle of their curves, along which
|
||||
// the near rails touch. The asymmetric slips of Id-4 (1:9/1:7,5, 1:9/1:6,6) are left out, both of their curves being the same here
|
||||
struct slipping
|
||||
{
|
||||
char const *type;
|
||||
char const *name;
|
||||
double radius;
|
||||
double middle;
|
||||
double half;
|
||||
};
|
||||
slipping const slips[] = {
|
||||
{"Rkp", "60E1-190-1:9", 190.0, 0.0, 16.615},
|
||||
{"Rkp", "49E1-190-1:9", 190.0, 0.0, 16.615},
|
||||
{"Rkp", "49E1-190-1:6,6", 190.0, -1.0, 17.396},
|
||||
{"Rkp", "49E1-300-1:9", 300.0, -1.0, 22.639},
|
||||
{"Rkp", "49E1-500-1:9", 500.0, -1.0, 27.693},
|
||||
{"Rkp", "S42-230-1:9,51", 230.0, 0.0, 18.642},
|
||||
{"Rkp", "S42-265-1:10", 265.0, 0.0, 19.192},
|
||||
{"Rkpd", "60E1-190-1:9", 190.0, 0.0, 16.615},
|
||||
{"Rkpd", "49E1-190-1:9", 190.0, 0.0, 16.615},
|
||||
{"Rkpd", "49E1-190-1:6,6", 190.0, -1.0, 17.396},
|
||||
{"Rkpd", "49E1-300-1:9", 300.0, -1.0, 22.639},
|
||||
{"Rkpd", "49E1-500-1:9", 500.0, -1.0, 27.693},
|
||||
{"Rkpd", "49E1-190-1:9 gauge 1524", 190.0, 0.0, 17.420},
|
||||
{"Rkpd", "S42-205-1:9", 205.0, 0.0, 17.591},
|
||||
{"Rkpd", "S42-205-1:9 II", 205.0, 0.0, 18.394},
|
||||
{"Rkpd", "S42-230-1:9,51", 230.0, 0.0, 18.642},
|
||||
{"Rkpd", "S42-230-1:9,51 Ia", 230.0, 0.0, 18.342},
|
||||
{"Rkpd", "S42-265-1:10", 265.0, 0.0, 19.192},
|
||||
};
|
||||
auto const ratio_of = [](std::string const &Name) {
|
||||
auto const at{Name.find("1:")};
|
||||
auto number{Name.substr(at + 2)};
|
||||
std::replace(number.begin(), number.end(), ',', '.');
|
||||
return std::stod(number);
|
||||
};
|
||||
for (auto const &entry : slips)
|
||||
{
|
||||
switch_template slip;
|
||||
slip.double_slip = true;
|
||||
slip.type = "Rkp";
|
||||
slip.radius = slipping.first;
|
||||
slip.ratio = std::stod(std::string{slipping.second}.substr(2));
|
||||
slip.label = std::string{"Rkp "} + slipping.second + " R" + std::to_string(static_cast<int>(slipping.first)) + " single slip (click at a crossing of two straights, on the side of the joined tracks)";
|
||||
slip.type = entry.type;
|
||||
slip.radius = entry.radius;
|
||||
slip.middle = entry.middle;
|
||||
slip.ratio = ratio_of(entry.name);
|
||||
slip.rail = std::string{entry.name}.substr(0, std::string{entry.name}.find('-'));
|
||||
slip.label = std::string{entry.type} + ' ' + entry.name + format(" a %.3f", entry.half) + (slip.type == "Rkp" ? " single slip" : " double slip") +
|
||||
(entry.middle < 0.0 ? ", the blades outside of the diamond" : "") +
|
||||
(slip.type == "Rkp" ? " (click at a crossing of two straights, on the side of the joined tracks)" : " (click at a crossing of two straights)");
|
||||
result.push_back(slip);
|
||||
}
|
||||
for (auto const &crossing : {std::pair<double, char const *>{16.615, "1:9"}, {20.797, "1:12"}})
|
||||
struct crossing
|
||||
{
|
||||
char const *name;
|
||||
double half;
|
||||
};
|
||||
crossing const crossings[] = {
|
||||
{"60E1-1:9", 16.615},
|
||||
{"60E1-1:4,444", 10.907},
|
||||
{"49E1-1:2,9008", 6.904},
|
||||
{"49E1-1:3,224", 7.920},
|
||||
{"49E1-1:3,683", 9.881},
|
||||
{"49E1-1:4,44", 10.907},
|
||||
{"49E1-1:6,6", 17.396},
|
||||
{"49E1-1:7,5", 18.512},
|
||||
{"49E1-1:9", 16.615},
|
||||
{"49E1-1:9 long", 17.016},
|
||||
{"49E1-1:18,5", 32.409},
|
||||
{"S42-1:2", 5.743},
|
||||
{"S42-1:2,9008", 8.250},
|
||||
{"S42-1:3,429", 9.681},
|
||||
{"S42-1:3,43", 8.421},
|
||||
{"S42-1:4,44", 11.788},
|
||||
{"S42-1:4,444 gauge 1524", 12.189},
|
||||
{"S42-1:7", 12.932},
|
||||
{"S42-1:9", 17.591},
|
||||
{"S42-1:10", 19.192},
|
||||
};
|
||||
for (auto const &entry : crossings)
|
||||
{
|
||||
switch_template plain;
|
||||
plain.double_slip = true;
|
||||
plain.type = "Skrz";
|
||||
plain.a = crossing.first;
|
||||
plain.ratio = std::stod(std::string{crossing.second}.substr(2));
|
||||
plain.label = std::string{"Skrz "} + crossing.second + " crossing (click at a crossing of two straights)";
|
||||
plain.a = entry.half;
|
||||
plain.ratio = ratio_of(entry.name);
|
||||
plain.rail = std::string{entry.name}.substr(0, std::string{entry.name}.find('-'));
|
||||
plain.label = std::string{"Skrz "} + entry.name + format(" a %.3f", entry.half) + " crossing (click at a crossing of two straights)";
|
||||
result.push_back(plain);
|
||||
}
|
||||
for (auto const &slipping : {std::pair<double, char const *>{190.0, "1:9"}, {300.0, "1:9"}, {500.0, "1:12"}})
|
||||
{
|
||||
switch_template slip;
|
||||
slip.double_slip = true;
|
||||
slip.type = "Rkpd";
|
||||
slip.radius = slipping.first;
|
||||
slip.ratio = std::stod(std::string{slipping.second}.substr(2));
|
||||
slip.label = std::string{"Rkpd "} + slipping.second + " R" + std::to_string(static_cast<int>(slipping.first)) + " double slip (click at a crossing of two straights)";
|
||||
result.push_back(slip);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2731,6 +2913,8 @@ void write_ending(std::ostream &Text, TTrack const &Style)
|
||||
Text << "overhead " << Style.fOverhead << ' ';
|
||||
if (Style.m_sleeper_enabled && false == Style.m_sleeper_model_name.empty())
|
||||
Text << "sleepermodel " << Style.m_sleeper_frequency << ' ' << Style.m_sleeper_model_name << ' ' << (Style.m_sleeper_skin_name.empty() ? std::string{"none"} : Style.m_sleeper_skin_name) << ' ' << Style.m_sleeper_offset.x << ' ' << Style.m_sleeper_offset.y << ' ' << Style.m_sleeper_offset.z << ' ' << Style.m_sleeper_ballast_z << ' ';
|
||||
if (false == Style.m_timber_model_name.empty())
|
||||
Text << "timbermodel " << Style.m_timber_model_name << ' ' << (Style.m_timber_skin_name.empty() ? std::string{"none"} : Style.m_timber_skin_name) << ' ';
|
||||
Text << "endtrack\n";
|
||||
}
|
||||
|
||||
@@ -2779,6 +2963,9 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector
|
||||
track = load_path(text.str(), Style, Name);
|
||||
}
|
||||
track->SwitchExtension->m_frogs = true;
|
||||
track->SwitchExtension->m_movablenose = Template.movable;
|
||||
track->SwitchExtension->m_bladeheel = Template.heel;
|
||||
track->SwitchExtension->m_bladethrow = Template.bladethrow;
|
||||
track->m_paths.assign(Paths.begin(), Paths.begin() + 2);
|
||||
return track;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ class editor_track
|
||||
material_handle material2{0};
|
||||
material_handle material3{0};
|
||||
bool frogs{false};
|
||||
bool movablenose{false};
|
||||
double bladeheel{0.0};
|
||||
double bladethrow{0.0};
|
||||
float texlength{4.f};
|
||||
float texheight{0.6f};
|
||||
float texwidth{0.9f};
|
||||
@@ -65,6 +68,8 @@ class editor_track
|
||||
std::string sleeper_skin;
|
||||
glm::vec3 sleeper_offset{0.f};
|
||||
float sleeper_ballast{0.f};
|
||||
std::string timber_model;
|
||||
std::string timber_skin;
|
||||
std::array<std::vector<std::pair<std::string, basic_event *>>, 6> events;
|
||||
std::string name;
|
||||
};
|
||||
@@ -413,9 +418,40 @@ class editor_track
|
||||
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};
|
||||
bool movable{false}; // the frog with a movable nose
|
||||
double heel{0.0}; // where the moving blades end, 0: as long as the flangeway behind them needs
|
||||
double bladethrow{0.0}; // how far the tips of the blades move over, 0: the usual throw
|
||||
int design{-1}; // in the list of the designs the template was built from
|
||||
double middle{0.0}; // a double slip with the blades outside of the diamond: the radius in the middle of its slip curves, below 0 a straight there
|
||||
bool twofold() const { return stagger > 0.0; }
|
||||
};
|
||||
// the diverging curve of a slip turning by Angle: arcs of Radius at both ends, each Ends long, and the arc of Middle,
|
||||
// Between long, between them; a straight when Middle is 0. With blades cutting into the stock rails the curve starts and ends
|
||||
// with a straight Lead long along them, the arcs of Radius meeting it at Entry
|
||||
struct slip_curve
|
||||
{
|
||||
double radius{0.0};
|
||||
double ends{0.0};
|
||||
double middle{0.0};
|
||||
double between{0.0};
|
||||
double angle{0.0};
|
||||
double tip{0.0}; // of the blades from each end of the curve, 0: where the blades get as thin as they may
|
||||
double lead{0.0};
|
||||
double entry{0.0}; // radians
|
||||
double length() const { return 2.0 * (lead + ends) + between; }
|
||||
// the point and the heading at Along of the curve starting at the origin along x and turning towards y; the heading at the ends
|
||||
// of the leads is the one of the arcs
|
||||
glm::dvec2 point(double const Along) const;
|
||||
double heading(double const Along) const;
|
||||
// from the start of the curve to the crossing of its tangents
|
||||
double tangent() const;
|
||||
// from the middle of the curve to the crossing of its tangents
|
||||
double offset() const;
|
||||
slip_curve scaled(double const Factor) const { return {radius * Factor, ends * Factor, middle * Factor, between * Factor, angle, Factor == 1.0 ? tip : 0.0, lead * Factor, entry}; }
|
||||
};
|
||||
// one arc of Radius, or with Middle the slip curves of a double slip with the blades outside of the diamond, their near rails
|
||||
// touching in its middle: Pass from the crossing, half of the gauge and the width of the head; a straight there when Middle < 0
|
||||
static slip_curve slip_of(double const Radius, double const Middle, double const Angle, double const Pass, double const Lead = 0.0, double const Entry = 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();
|
||||
|
||||
Reference in New Issue
Block a user