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:
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user