17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 02:49:19 +02:00

feat(editor): track editor driven by what the cursor is over: the outline and a label show what a click or a drag does, snapping to free ends on the screen, handles and ends dragged directly, no rotation gizmo; laying from a free end the same in every mode, straight with Shift, the curve of a switch going on past its diverging end; height 0 where there's no terrain; placed switch leaves the switch mode, double slips listed apart, a drive of the switch placed with it by the //$e switch description of its template; compound curves of any number of radii; dragged end of a straight fits the curve beside it; name of a path, followed by the memory cells and the includes taking it; turntable placed from a template described as one, tracks led out of it and the tracks around fitted to it, its positions written to the include; infrastructure bound to the path whose events read its memory cell; terrain made where the route has none and shaped to the formation under the ballast; models along the track over the selected path and on along the joined ones while dragged, rarely used settings folded; vehicles and trainsets placed on any path, switches included, facing the way the arrow shows; joined paths spread into the selection; New scenery in the File menu starts the editor again with the wizard; ortho zoom towards the cursor; scripted self tests; Polish translation

This commit is contained in:
maj00r
2026-10-06 22:19:54 +02:00
parent 0a3cd55e59
commit 24923dd472
26 changed files with 9991 additions and 468 deletions

View File

@@ -97,18 +97,34 @@ struct compound_curve
double shortened{1.0};
};
compound_curve build_compound(double const Deflection, double const Radius1, double const Radius2, double const Lengthin, double const Lengthmiddle, double const Lengthout, double const Split)
compound_curve build_compound(double const Deflection, std::vector<compound_arc> const &Arcs, double const Lengthin, double const Lengthout)
{
compound_curve result;
auto const k1{1.0 / Radius1};
auto const k2{1.0 / Radius2};
auto const transitions{(k1 * Lengthin + (k1 + k2) * Lengthmiddle + k2 * Lengthout) * 0.5};
if (Arcs.empty())
return result;
std::vector<double> curvatures;
for (auto const &arc : Arcs)
curvatures.push_back(1.0 / std::max(1.0, arc.radius));
auto transitions{curvatures.front() * Lengthin + curvatures.back() * Lengthout};
for (std::size_t i = 1; i < Arcs.size(); ++i)
transitions += (curvatures[i - 1] + curvatures[i]) * std::max(0.0, Arcs[i].transition);
transitions *= 0.5;
if (transitions > Deflection)
result.shortened = Deflection / transitions * (1.0 - 1e-9);
auto const f{result.shortened};
auto const arcs{std::max(0.0, Deflection - transitions * f)};
auto const split{std::clamp(Split, 0.0, 1.0)};
std::array<double, 3> const segments[] = {{0.0, k1, Lengthin * f}, {k1, k1, split * arcs / k1}, {k1, k2, Lengthmiddle * f}, {k2, k2, (1.0 - split) * arcs / k2}, {k2, 0.0, Lengthout * f}};
double total{0.0};
for (auto const &arc : Arcs)
total += std::max(0.0, arc.share);
std::vector<std::array<double, 3>> segments{{0.0, curvatures.front(), Lengthin * f}};
for (std::size_t i = 0; i < Arcs.size(); ++i)
{
if (i > 0)
segments.push_back({curvatures[i - 1], curvatures[i], std::max(0.0, Arcs[i].transition) * f});
auto const share{total > 0.0 ? std::max(0.0, Arcs[i].share) / total : 1.0 / static_cast<double>(Arcs.size())};
segments.push_back({curvatures[i], curvatures[i], share * arcs / curvatures[i]});
}
segments.push_back({curvatures.back(), 0.0, Lengthout * f});
double x{0.0}, y{0.0}, heading{0.0};
for (auto const &segment : segments)
{
@@ -209,8 +225,11 @@ std::size_t element_pieces(element const &Element, transition_shape const Shape,
case element_kind::spiral:
{
auto const turn{(Element.curvature_start + Element.curvature_end) * 0.5 * Element.length};
auto const count{static_cast<std::size_t>(std::max({1.0, std::ceil(turn / (Arcangle * kPi / 180.0) - 1e-9), std::ceil(Element.length / 20.0 - 1e-9)}))};
return Element.curvature_start != Element.curvature_end ? std::max<std::size_t>(count, std::max(1, Transitions)) : count;
auto const arcs{std::max(1.0, std::ceil(turn / (Arcangle * kPi / 180.0) - 1e-9))};
if (Element.curvature_start == Element.curvature_end)
return static_cast<std::size_t>(arcs);
auto const count{static_cast<std::size_t>(std::max(arcs, std::ceil(Element.length / 20.0 - 1e-9)))};
return std::max<std::size_t>(count, std::max(1, Transitions));
}
default:
return 1;
@@ -360,10 +379,17 @@ result fit_between(design const &Design)
double tangent_out{0.0};
double deflection{0.0};
bool compound{false};
double radius2{0.0};
double transition_middle{0.0};
std::vector<compound_arc> arcs;
std::function<void(double, double &, double &)> tangents;
};
auto const scaled = [](std::vector<compound_arc> Arcs, double const Scale) {
for (auto &arc : Arcs)
{
arc.radius *= Scale;
arc.transition *= Scale;
}
return Arcs;
};
std::vector<fitted> fits(count);
for (std::size_t k = 0; k < count; ++k)
{
@@ -386,16 +412,17 @@ result fit_between(design const &Design)
r.errors.emplace_back(format("Vertex %zu: a turn of about 180 or 360 degrees needs one more vertex", k + 1));
return r;
}
if (vertex.compound)
if (vertex.compound && false == vertex.arcs.empty())
{
fit.compound = true;
fit.radius2 = std::max(1.0, vertex.radius2);
fit.transition_middle = std::max(0.0, vertex.transition_middle);
fit.arcs = {{fit.radius, 0.0, vertex.share}};
for (auto const &arc : vertex.arcs)
fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share});
fit.deflection = deflection;
fit.tangents = [&, k, directionin, directionout, side](double const Radius, double &Tangentin, double &Tangentout) {
fit.tangents = [&, k, directionin, directionout, side, scaled](double const Radius, double &Tangentin, double &Tangentout) {
auto const &own{fits[k]};
auto const scale{Radius / own.radius};
auto const shape{build_compound(own.deflection, Radius, own.radius2 * scale, own.transition_in * scale, own.transition_middle * scale, own.transition_out * scale, Design.vertices[k].split)};
auto const shape{build_compound(own.deflection, scaled(own.arcs, scale), own.transition_in * scale, own.transition_out * scale)};
auto const normalin{perpendicular(directionin) * static_cast<double>(side)};
auto const end{directionin * shape.end.x + normalin * shape.end.y};
auto const along{cross(directionout, end) / cross(directionout, directionin)};
@@ -485,9 +512,8 @@ result fit_between(design const &Design)
else
{
fit.radius *= scales[k];
fit.radius2 *= scales[k];
fit.arcs = scaled(fit.arcs, scales[k]);
fit.transition_in *= scales[k];
fit.transition_middle *= scales[k];
fit.transition_out *= scales[k];
}
r.warnings.emplace_back(format("Vertex %zu: R reduced from %.1f to %.1f m, transitions to %.1f / %.1f m, to fit between the ends", k + 1, Design.vertices[k].radius, fit.radius, fit.transition_in, fit.transition_out));
@@ -525,7 +551,7 @@ result fit_between(design const &Design)
}
if (fit.compound)
{
auto const shape{build_compound(deflection, fit.radius, fit.radius2, fit.transition_in, fit.transition_middle, fit.transition_out, vertex.split)};
auto const shape{build_compound(deflection, fit.arcs, fit.transition_in, fit.transition_out)};
if (shape.shortened < 1.0)
r.warnings.emplace_back(format("Vertex %zu: transition curves shortened to fit the deflection angle", k + 1));
auto const normalin{perpendicular(directionin) * static_cast<double>(side)};
@@ -541,7 +567,10 @@ result fit_between(design const &Design)
auto const startchainage{chainage};
glm::dvec2 origin{curvestart};
glm::dvec2 heading{directionin};
auto const peak{std::max(1.0 / fit.radius, 1.0 / fit.radius2)};
auto sharpest{fit.radius};
for (auto const &arc : fit.arcs)
sharpest = std::min(sharpest, arc.radius);
auto const peak{1.0 / sharpest};
for (auto const &segment : shape.segments)
{
element piece;
@@ -568,14 +597,14 @@ result fit_between(design const &Design)
curve_report report;
report.vertex = static_cast<int>(k);
report.radius = fit.radius;
report.radius2 = fit.radius2;
for (auto const &arc : fit.arcs)
report.radii.push_back(arc.radius);
report.transition_in = fit.transition_in * shape.shortened;
report.transition_out = fit.transition_out * shape.shortened;
report.deflection = deflection;
report.arc_length = chainage - startchainage;
report.tangent_in = glm::distance(position, curvestart);
report.tangent_out = glm::distance(position, cursor);
auto const sharpest{std::min(fit.radius, fit.radius2)};
report.unbalanced = unbalanced_acceleration(Design.speed, sharpest, std::max(0.0, vertex.cant), Design.norms);
report.recommended = recommend(Design.speed, sharpest, Design.norms);
r.curves.push_back(report);

View File

@@ -37,6 +37,13 @@ struct limits
double tangent_min_time{0.0};
};
struct compound_arc
{
double radius{600.0};
double transition{0.0};
double share{1.0};
};
struct vertex
{
glm::dvec2 position{0.0};
@@ -47,9 +54,8 @@ struct vertex
double cant{0.0};
bool reverse_turn{false};
bool compound{false};
double radius2{600.0};
double transition_middle{0.0};
double split{0.5};
double share{1.0};
std::vector<compound_arc> arcs;
};
struct design
@@ -116,7 +122,7 @@ struct curve_report
{
int vertex{-1};
double radius{0.0};
double radius2{0.0};
std::vector<double> radii;
double transition_in{0.0};
double transition_out{0.0};
double deflection{0.0};

View File

@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
#include <algorithm>
#include <cmath>
#include <vector>
#include <glm/glm.hpp>
#include "world/Segment.h"
@@ -56,6 +57,36 @@ inline double grade_of(glm::dvec3 const &Direction)
return Direction.y / std::max(1e-9, std::hypot(Direction.x, Direction.z));
}
inline std::vector<segment_data> arc_pieces(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Radius, double const Angle, int const Side)
{
std::vector<segment_data> result;
double const side{static_cast<double>(Side)};
glm::dvec2 const centre{plan_of(Point) + glm::dvec2{-Direction.y, Direction.x} * (side * Radius)};
auto const count{std::max(1, static_cast<int>(std::ceil(Angle / glm::radians(90.0) - 1e-9)))};
auto const step{Angle / count};
auto const handle{4.0 / 3.0 * std::tan(step / 4.0) * Radius};
glm::dvec2 const radial{plan_of(Point) - centre};
for (int i = 0; i < count; ++i)
{
auto const a0{side * step * i};
auto const a1{side * step * (i + 1)};
auto const p0{centre + turned(radial, a0)};
auto const p3{centre + turned(radial, a1)};
auto const d0{turned(Direction, a0)};
auto const d3{turned(Direction, a1)};
auto const y0{Point.y + Grade * Radius * step * i};
auto const y3{Point.y + Grade * Radius * step * (i + 1)};
segment_data path;
path.points[segment_data::point::start] = {p0.x, y0, p0.y};
path.points[segment_data::point::end] = {p3.x, y3, p3.y};
path.points[segment_data::point::control1] = {d0.x * handle, Grade * handle, d0.y * handle};
path.points[segment_data::point::control2] = {-d3.x * handle, -Grade * handle, -d3.y * handle};
path.radius = static_cast<float>(Radius);
result.push_back(path);
}
return result;
}
// path as a cubic Bezier curve; a path without control points is a straight with evenly spread parameter
struct bezier
{

View File

@@ -246,6 +246,17 @@ bool parse(template_text const &Text, include_info &Info, std::string &Error)
Info.name = document["name"].as<std::string>("");
Info.category = document["category"].as<std::string>("");
Info.description = document["description"].as<std::string>("");
if (auto const drive{document["switch"]}; drive.IsDefined() && drive.IsMap())
{
Info.switch_hand = drive["hand"].as<std::string>("");
Info.switch_drive = drive["drive"].as<std::string>("");
}
if (auto const table{document["turntable"]}; table.IsDefined() && table.IsMap())
{
Info.turntable_length = table["length"].as<double>(0.0);
Info.turntable_angles = table["angles"].as<int>(0);
Info.turntable_positions = table["positions"].as<int>(0);
}
if (auto const parameters{document["params"]}; parameters.IsDefined() && parameters.IsSequence())
{
for (auto const &entry : parameters)
@@ -633,7 +644,7 @@ std::string compose_directive(std::string const &File, std::vector<std::string>
return text + " end";
}
std::string directive(std::string const &File, include_info const &Info, int const Parameters, glm::dvec3 const &Location, std::optional<float> Yaw)
std::string directive(std::string const &File, include_info const &Info, int const Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track)
{
std::vector<std::string> values;
for (auto id = 1; id <= Parameters; ++id)
@@ -661,6 +672,10 @@ std::string directive(std::string const &File, include_info const &Info, int con
{
value = "0";
}
else if (role == "track" && false == Track.empty())
{
value = Track;
}
else if (role == "name")
{
// names have to be unique in the scenery. the default value, or the name of the template, makes them recognizable

View File

@@ -34,6 +34,11 @@ struct include_info
std::string name;
std::string category;
std::string description;
std::string switch_hand; // a switch drive: side of the diverging track, left or right, looking from the start of the switch
std::string switch_drive; // and the side the drive stands on
double turntable_length{0.0};
int turntable_angles{0};
int turntable_positions{0};
std::vector<include_parameter> parameters;
// description of specified parameter, created if there's none
@@ -66,7 +71,7 @@ bool complete(include_info const &Info, int Parameters, std::string *Issue = nul
// builds include directive which places specified template. Location and Yaw are the placement to pass through
// the parameters with matching roles, in the coordinates of the place the directive goes to; without Yaw the
// rotation parameters receive their default values
std::string directive(std::string const &File, include_info const &Info, int Parameters, glm::dvec3 const &Location, std::optional<float> Yaw);
std::string directive(std::string const &File, include_info const &Info, int Parameters, glm::dvec3 const &Location, std::optional<float> Yaw, std::string const &Track = {});
// splits include directive into the name of the included file and the values of the parameters, both as they're
// written. returns: false if the text isn't a complete include directive

View File

@@ -17,6 +17,9 @@ http:
#include "scene/scene.h"
#include "scene/scenelayers.h"
#include "simulation/simulation.h"
#include "editor/editorIncludeInfo.hpp"
#include "world/Event.h"
#include "world/MemCell.h"
#include "rendering/renderer.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
@@ -157,6 +160,7 @@ editor_track::state editor_track::capture(TTrack const &Track)
result.sleeper_ballast = Track.m_sleeper_ballast_z;
for (int i = 0; i < 6; ++i)
result.events[i] = events(const_cast<TTrack &>(Track), i);
result.name = Track.name();
return result;
}
@@ -257,6 +261,131 @@ void editor_track::apply(TTrack &Track, state const &State)
Track.m_events = std::any_of(State.events.begin(), State.events.end(), [](auto const &Events) {
return std::any_of(Events.begin(), Events.end(), [](auto const &Event) { return Event.second != nullptr; });
});
if (State.name != Track.name())
rename(Track, State.name);
}
namespace
{
std::string lower_name(std::string Name)
{
for (auto &character : Name)
if (character >= 'A' && character <= 'Z')
character = static_cast<char>(character - 'A' + 'a');
return Name;
}
int track_parameter(scene::instance_handle const Instance, std::string &File, std::vector<std::string> &Values)
{
if (false == scene::Layers.tracked(Instance))
return -1;
auto const &included{scene::Layers.instance(Instance)};
if (included.removed || included.dead || included.file == nullptr)
return -1;
static std::map<std::string, include_info> descriptions;
auto found{descriptions.find(*included.file)};
if (found == descriptions.end())
{
include_info info;
std::string error;
editor_includes::load(*included.file, info, error);
found = descriptions.emplace(*included.file, std::move(info)).first;
}
auto const index{editor_includes::parameter_with_role(found->second, "track") - 1};
if (index < 0 || false == editor_includes::parse_directive(scene::Layers.directive(Instance), File, Values) || index >= static_cast<int>(Values.size()))
return -1;
return index;
}
} // namespace
bool editor_track::name_valid(TTrack const &Track, std::string &Name, std::string &Reason, TTrack **Owner)
{
Name = lower_name(Name);
if (Name.empty() || Name == "none")
{
Reason = "The path needs a name";
return false;
}
if (Name.find_first_of(" \t\r\n\"';") != std::string::npos || Name.find("//") != std::string::npos)
{
Reason = "The name can't hold blanks, quotes, semicolons or //";
return false;
}
auto *owner{simulation::Paths.find(Name)};
if (owner != nullptr && owner != &Track && false == owner->m_editorremoved)
{
if (Owner != nullptr)
*Owner = owner;
Reason = "Another path has this name";
return false;
}
Reason.clear();
return true;
}
editor_track::name_uses editor_track::uses_of_name(TTrack const &Track)
{
name_uses result;
auto const &name{Track.name()};
if (name.empty() || name == "none")
return result;
for (auto const *cell : simulation::Memory.sequence())
result.cells += (cell != nullptr && cell->Track == &Track) ? 1 : 0;
for (scene::instance_handle handle = 1; handle <= scene::Layers.instance_count(); ++handle)
{
std::string file;
std::vector<std::string> values;
auto const index{track_parameter(handle, file, values)};
result.includes += (index >= 0 && lower_name(values[index]) == name) ? 1 : 0;
}
static std::array<char const *, 6> const kinds{"event0", "event1", "event2", "eventall0", "eventall1", "eventall2"};
for (auto const *event : simulation::Events.sequence())
{
if (event == nullptr)
continue;
auto const &eventname{event->name()};
if (std::any_of(kinds.begin(), kinds.end(), [&](char const *Kind) { return eventname == name + ':' + Kind; }))
{
result.loose.push_back(eventname);
continue;
}
if (result.includes > 0 && eventname.compare(0, name.size(), name) == 0)
continue;
auto const targets{event->target_nodes()};
if (std::find(targets.begin(), targets.end(), &Track) != targets.end())
result.events.push_back(eventname);
}
return result;
}
void editor_track::rename(TTrack &Track, std::string const &Name)
{
auto const old{Track.name()};
if (old == Name)
return;
simulation::Paths.rename(&Track, Name);
Track.m_name = Name;
Track.mark_dirty();
for (auto *cell : simulation::Memory.sequence())
if (cell != nullptr && cell->Track == &Track)
{
cell->asTrackName = Name;
cell->mark_dirty();
}
if (old.empty() || old == "none")
return;
for (scene::instance_handle handle = 1; handle <= scene::Layers.instance_count(); ++handle)
{
std::string file;
std::vector<std::string> values;
auto const index{track_parameter(handle, file, values)};
if (index < 0 || lower_name(values[index]) != old)
continue;
values[index] = Name;
scene::Layers.modify(handle, editor_includes::compose_directive(file, values));
}
}
bool editor_track::is_supported(TTrack const &Track)
@@ -550,9 +679,13 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl
editor_track::snap_target editor_track::find_free_end(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude)
{
snap_target result;
result.distance = std::numeric_limits<double>::max();
auto const found{free_ends(Self, Category, Position, Radius, Exclude)};
return found.empty() ? snap_target{} : found.front();
}
std::vector<editor_track::snap_target> editor_track::free_ends(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude)
{
std::vector<snap_target> result;
auto const excluded = [&](TTrack const *Other) { return std::find(Exclude.begin(), Exclude.end(), Other) != Exclude.end(); };
auto const sections{simulation::Region->sections(Position, static_cast<float>(Radius))};
for (auto *section : sections)
@@ -561,7 +694,7 @@ editor_track::snap_target editor_track::find_free_end(TTrack const *Self, int co
{
for (auto *other : cell.m_directories.paths)
{
if (other == Self || excluded(other) || (other->iCategoryFlag & 15) != Category || false == is_supported(*other))
if (other == Self || excluded(other) || other->m_editorremoved || (other->iCategoryFlag & 15) != Category || false == is_supported(*other))
continue;
for (int i = 0; i < static_cast<int>(other->m_paths.size()); ++i)
{
@@ -570,7 +703,9 @@ editor_track::snap_target editor_track::find_free_end(TTrack const *Self, int co
{
auto const &point{path.points[point_index(kind)]};
auto const distance{glm::distance(point, Position)};
if (distance > Radius || distance >= result.distance)
if (distance > Radius)
continue;
if (std::any_of(result.begin(), result.end(), [&](snap_target const &Found) { return Found.track == other && Found.point == point_ref{i, kind}; }))
continue;
auto const connections{connected_points(*other, point)};
if (std::any_of(connections.begin(), connections.end(), [&](auto const &Connection) { return false == excluded(Connection.first); }))
@@ -590,16 +725,19 @@ editor_track::snap_target editor_track::find_free_end(TTrack const *Self, int co
if (glm::length(direction) < 1e-6)
continue;
result.track = other;
result.point = point_ref{i, kind};
result.position = point;
result.direction = glm::normalize(direction);
result.distance = distance;
snap_target target;
target.track = other;
target.point = point_ref{i, kind};
target.position = point;
target.direction = glm::normalize(direction);
target.distance = distance;
result.push_back(target);
}
}
}
}
}
std::sort(result.begin(), result.end(), [](snap_target const &A, snap_target const &B) { return A.distance < B.distance; });
return result;
}
@@ -1341,42 +1479,31 @@ bool editor_track::analyse_curve(std::vector<std::pair<TTrack *, bool>> const &R
if (Curve.transition_in + Curve.transition_out >= std::accumulate(lengths.begin(), lengths.end(), 0.0))
Curve.transition_in = Curve.transition_out = 0.0;
Curve.radius = smallest;
std::vector<std::pair<double, double>> arcs;
std::vector<curve::arc_part> arcs;
double between{0.0};
for (std::size_t i = 0; i < run.size(); ++i)
{
if (varying[i] || curvatures[i] <= 0.0)
{
if (false == arcs.empty())
between += lengths[i];
continue;
}
auto const radius{1.0 / curvatures[i]};
if (false == arcs.empty() && std::abs(arcs.back().first - radius) < 0.05 * radius)
arcs.back().second += lengths[i];
if (false == arcs.empty() && std::abs(arcs.back().radius - radius) < 0.05 * radius)
arcs.back().turn += (lengths[i] + between) / radius;
else
arcs.emplace_back(radius, lengths[i]);
arcs.push_back({radius, lengths[i] / radius, arcs.empty() ? 0.0 : between});
between = 0.0;
}
if (arcs.size() >= 2 && Curve.reversals == 0)
{
auto const &first{arcs.front()};
auto const &last{arcs.back()};
if (std::abs(first.first - last.first) > 0.1 * std::min(first.first, last.first))
auto const [low, high]{std::minmax_element(arcs.begin(), arcs.end(), [](curve::arc_part const &A, curve::arc_part const &B) { return A.radius < B.radius; })};
if (high->radius - low->radius > 0.1 * low->radius)
{
Curve.compound = true;
Curve.radius = first.first;
Curve.radius2 = last.first;
auto const turnfirst{first.second / first.first};
auto const turnlast{last.second / last.first};
Curve.split = turnfirst / std::max(1e-9, turnfirst + turnlast);
double middle{0.0};
bool inside{false};
for (std::size_t i = 0; i < run.size(); ++i)
{
if (false == varying[i])
{
inside = true;
continue;
}
if (inside && i + 1 < run.size() && std::any_of(varying.begin() + i + 1, varying.end(), [](bool const Varying) { return false == Varying; }))
middle += lengths[i];
}
Curve.transition_middle = middle;
Curve.radius = arcs.front().radius;
Curve.arcs = arcs;
}
}
Curve.from = run.front().first;
@@ -2131,14 +2258,6 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
{"Rz 1:26,5 R2500", 2500.0, 26.5, 0.0, 0.0},
};
std::vector<switch_template> result;
for (auto const &definition : {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.radius = definition.first;
slip.label = std::string{"Rkpd "} + definition.second + " R" + std::to_string(static_cast<int>(definition.first)) + " double slip (click at a crossing of two straights)";
result.push_back(slip);
}
for (auto const &definition : definitions)
{
auto const angle{std::atan(1.0 / definition.ratio)};
@@ -2162,6 +2281,14 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
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);
}
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.radius = slipping.first;
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;
}
@@ -2210,6 +2337,22 @@ material_handle ballast_of(TTrack const &Style)
} // namespace
TTrack *editor_track::create_turntable(segment_data const &Path, TTrack const *Style, std::string const &Name)
{
std::ostringstream text;
text.precision(std::numeric_limits<double>::digits10);
auto const length{glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end])};
if (Style != nullptr && (Style->iCategoryFlag & 15) == 1 && Style->m_visible)
write_header(text, "turn", length, Style->fSoundDistance, *Style, null_handle);
else
text << "turn " << length << " 1.435 0.15 25 20 0 flat vis rail_screw_used1 4 none 0.2 0.5 1.3 ";
write_points(text, Path);
text << "endtrack\n";
auto *track{load_text(text.str(), Style, Name)};
track->m_paths = {Path};
return track;
}
TTrack *editor_track::create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name)
{
if (Paths.size() < 2)

View File

@@ -62,10 +62,22 @@ class editor_track
glm::vec3 sleeper_offset{0.f};
float sleeper_ballast{0.f};
std::array<std::vector<std::pair<std::string, basic_event *>>, 6> events;
std::string name;
};
static state capture(TTrack const &Track);
static void apply(TTrack &Track, state const &State);
static bool name_valid(TTrack const &Track, std::string &Name, std::string &Reason, TTrack **Owner = nullptr);
struct name_uses
{
int cells{0};
int includes{0};
std::vector<std::string> events;
std::vector<std::string> loose;
};
static name_uses uses_of_name(TTrack const &Track);
static void rename(TTrack &Track, std::string const &Name);
// lists of the events of the path, in the order of event_keyword(): event0, event1, event2, eventall0, eventall1, eventall2
static std::vector<std::pair<std::string, basic_event *>> &events(TTrack &Track, int const Index);
static char const *event_keyword(int const Index);
@@ -116,6 +128,8 @@ class editor_track
static snap_target find_snap_target(TTrack const &Track, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude);
// nearest end of a path of the category which nothing else joins; Self may be null
static snap_target find_free_end(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude);
// all of them within the radius, nearest first
static std::vector<snap_target> free_ends(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude);
static void snap_point(TTrack &Track, point_ref const &Point, snap_target const &Target, bool const Aligntangent);
struct chain
@@ -183,9 +197,13 @@ class editor_track
double transition_out{0.0};
double cant{0.0};
bool compound{false};
double radius2{0.0};
double transition_middle{0.0};
double split{0.5};
struct arc_part
{
double radius{0.0};
double turn{0.0};
double transition{0.0};
};
std::vector<arc_part> arcs;
};
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
// curve made of the given paths, each with the flag of being run from its start
@@ -292,6 +310,7 @@ class editor_track
static glm::dvec3 point_at(TTrack const &Track, double const T);
static std::vector<segment_data> place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade);
static TTrack *create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name = {});
static TTrack *create_turntable(segment_data const &Path, TTrack const *Style, std::string const &Name);
static void move_straights(std::vector<straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
static void retire(TTrack &Track);