mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
feat(editor): curves over 180°, click to load curve
This commit is contained in:
@@ -311,6 +311,7 @@ class editor_mode : public application_mode
|
|||||||
};
|
};
|
||||||
void render_route_ui();
|
void render_route_ui();
|
||||||
void route_reset();
|
void route_reset();
|
||||||
|
bool route_from_curve(TTrack &Track);
|
||||||
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
||||||
void route_apply();
|
void route_apply();
|
||||||
void route_recommend(alignment::vertex &Vertex) const;
|
void route_recommend(alignment::vertex &Vertex) const;
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ void editor_mode::select_track(scene::basic_node *Node)
|
|||||||
m_track_point = {};
|
m_track_point = {};
|
||||||
m_node = track;
|
m_node = track;
|
||||||
ui()->set_node(m_node);
|
ui()->set_node(m_node);
|
||||||
|
if (ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::route && std::find(m_route.chain.tracks.begin(), m_route.chain.tracks.end(), track) == m_route.chain.tracks.end())
|
||||||
|
route_from_curve(*track);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool editor_mode::pick_track_handle()
|
bool editor_mode::pick_track_handle()
|
||||||
@@ -703,6 +705,42 @@ void editor_mode::route_reset()
|
|||||||
route_update();
|
route_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool editor_mode::route_from_curve(TTrack &Track)
|
||||||
|
{
|
||||||
|
editor_track::curve curve;
|
||||||
|
if (false == editor_track::find_curve(Track, m_straights.tolerance, m_route.design.norms.gauge, curve))
|
||||||
|
return false;
|
||||||
|
m_route.from = curve.from;
|
||||||
|
m_route.to = curve.to;
|
||||||
|
route_reset();
|
||||||
|
if (m_route.chain.tracks.empty())
|
||||||
|
return false;
|
||||||
|
auto &design{m_route.design};
|
||||||
|
alignment::vertex vertex;
|
||||||
|
vertex.radius = curve.radius > 0.0 ? std::max(10.0, std::round(curve.radius)) : 1000.0;
|
||||||
|
route_recommend(vertex);
|
||||||
|
if (curve.transition_in > 0.0)
|
||||||
|
vertex.transition_in = std::round(curve.transition_in);
|
||||||
|
if (curve.transition_out > 0.0)
|
||||||
|
vertex.transition_out = std::round(curve.transition_out);
|
||||||
|
if (curve.cant > 0.0)
|
||||||
|
vertex.cant = std::round(curve.cant);
|
||||||
|
if (curve.reversals == 0)
|
||||||
|
{
|
||||||
|
vertex.reverse_turn = std::abs(curve.turn) > glm::pi<double>();
|
||||||
|
design.vertices = {vertex};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const distance{glm::distance(glm::dvec2{design.start.x, design.start.z}, glm::dvec2{design.end.x, design.end.z})};
|
||||||
|
vertex.offset = distance / 3.0;
|
||||||
|
design.vertices = {vertex, vertex};
|
||||||
|
}
|
||||||
|
route_update();
|
||||||
|
m_route.status = "Curve of " + std::to_string(m_route.chain.tracks.size()) + " paths, turning " + std::to_string(static_cast<int>(std::round(glm::degrees(curve.turn)))) + " deg";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void editor_mode::route_apply()
|
void editor_mode::route_apply()
|
||||||
{
|
{
|
||||||
auto &route{m_route};
|
auto &route{m_route};
|
||||||
@@ -913,9 +951,9 @@ void editor_mode::render_route_ui()
|
|||||||
{
|
{
|
||||||
auto &route{m_route};
|
auto &route{m_route};
|
||||||
auto &design{route.design};
|
auto &design{route.design};
|
||||||
ImGui::TextDisabled("Re-lays a chain of regular paths along an alignment designed with intersection points.\n"
|
ImGui::TextDisabled("LMB on a curve: loads the whole curve between the adjoining straights.\n"
|
||||||
"The outer ends of the chain and their directions stay fixed.\n"
|
"The ends of the curve and their directions stay fixed.\n"
|
||||||
"LMB: select a path or a vertex (W1, W2...) Esc: release the vertex");
|
"LMB on a vertex (W1, W2...): moves it with the gizmo Esc: release the vertex");
|
||||||
|
|
||||||
auto *track = selected_track();
|
auto *track = selected_track();
|
||||||
auto const name = [](TTrack const *Track) { return Track == nullptr ? std::string{"-"} : (Track->name().empty() ? std::string{"(noname)"} : Track->name()); };
|
auto const name = [](TTrack const *Track) { return Track == nullptr ? std::string{"-"} : (Track->name().empty() ? std::string{"(noname)"} : Track->name()); };
|
||||||
@@ -1051,6 +1089,7 @@ void editor_mode::render_route_ui()
|
|||||||
changed |= ImGui::InputDouble("Transition in (m)", &vertex.transition_in, 0.0, 0.0, "%.1f");
|
changed |= ImGui::InputDouble("Transition in (m)", &vertex.transition_in, 0.0, 0.0, "%.1f");
|
||||||
changed |= ImGui::InputDouble("Transition out (m)", &vertex.transition_out, 0.0, 0.0, "%.1f");
|
changed |= ImGui::InputDouble("Transition out (m)", &vertex.transition_out, 0.0, 0.0, "%.1f");
|
||||||
changed |= ImGui::InputDouble("Cant (mm)", &vertex.cant, 0.0, 0.0, "%.0f");
|
changed |= ImGui::InputDouble("Cant (mm)", &vertex.cant, 0.0, 0.0, "%.0f");
|
||||||
|
changed |= ImGui::Checkbox("Turn the longer way (over 180 deg)", &vertex.reverse_turn);
|
||||||
changed |= ImGui::Checkbox("Elevation from the ends", &vertex.auto_elevation);
|
changed |= ImGui::Checkbox("Elevation from the ends", &vertex.auto_elevation);
|
||||||
if (false == vertex.auto_elevation)
|
if (false == vertex.auto_elevation)
|
||||||
changed |= ImGui::InputDouble("Elevation (m)", &vertex.elevation, 0.0, 0.0, "%.3f");
|
changed |= ImGui::InputDouble("Elevation (m)", &vertex.elevation, 0.0, 0.0, "%.3f");
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ result compute(design const &Design)
|
|||||||
{
|
{
|
||||||
auto const t{cross(offset, enddirection) / determinant};
|
auto const t{cross(offset, enddirection) / determinant};
|
||||||
auto const u{cross(startdirection, offset) / determinant};
|
auto const u{cross(startdirection, offset) / determinant};
|
||||||
if (t <= 0.0 || u <= 0.0)
|
if ((t <= 0.0 || u <= 0.0) && false == Design.vertices.front().reverse_turn)
|
||||||
r.errors.emplace_back("Tangents of the ends intersect outside of the fragment, use two vertices");
|
r.errors.emplace_back("Tangents of the ends intersect outside of the fragment, turn the longer way or use two vertices");
|
||||||
r.vertices[0] = start + startdirection * t;
|
r.vertices[0] = start + startdirection * t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,12 +303,15 @@ result compute(design const &Design)
|
|||||||
auto const directionin{glm::normalize(polygon[k + 1] - polygon[k])};
|
auto const directionin{glm::normalize(polygon[k + 1] - polygon[k])};
|
||||||
auto const directionout{glm::normalize(polygon[k + 2] - polygon[k + 1])};
|
auto const directionout{glm::normalize(polygon[k + 2] - polygon[k + 1])};
|
||||||
auto const turn{cross(directionin, directionout)};
|
auto const turn{cross(directionin, directionout)};
|
||||||
auto const deflection{std::atan2(std::abs(turn), glm::dot(directionin, directionout))};
|
auto const geometric{std::atan2(std::abs(turn), glm::dot(directionin, directionout))};
|
||||||
|
auto const reverse{Design.vertices[k].reverse_turn};
|
||||||
|
auto const deflection{reverse ? 2.0 * kPi - geometric : geometric};
|
||||||
|
int const side{(turn > 0.0) != reverse ? 1 : -1};
|
||||||
if (deflection < 1e-7)
|
if (deflection < 1e-7)
|
||||||
continue;
|
continue;
|
||||||
if (deflection > kPi - 1e-3)
|
if (geometric > kPi - 1e-3 || (reverse && geometric < 1e-3))
|
||||||
{
|
{
|
||||||
r.errors.emplace_back(format("Vertex %zu: the route turns back on itself", k + 1));
|
r.errors.emplace_back(format("Vertex %zu: a turn of about 180 or 360 degrees needs one more vertex", k + 1));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
auto const angles = [&](double const Factor) {
|
auto const angles = [&](double const Factor) {
|
||||||
@@ -327,8 +330,7 @@ result compute(design const &Design)
|
|||||||
fit.transition_out *= low;
|
fit.transition_out *= low;
|
||||||
}
|
}
|
||||||
fit.deflection = deflection;
|
fit.deflection = deflection;
|
||||||
fit.tangents = [&, k, directionin, directionout, turn](double const Radius, double &Tangentin, double &Tangentout) {
|
fit.tangents = [&, k, directionin, directionout, side](double const Radius, double &Tangentin, double &Tangentout) {
|
||||||
int const side{turn > 0.0 ? 1 : -1};
|
|
||||||
auto const endin{transition_point(Design.shape, fits[k].transition_in, Radius, 1.0)};
|
auto const endin{transition_point(Design.shape, fits[k].transition_in, Radius, 1.0)};
|
||||||
auto const endout{transition_point(Design.shape, fits[k].transition_out, Radius, 1.0)};
|
auto const endout{transition_point(Design.shape, fits[k].transition_out, Radius, 1.0)};
|
||||||
auto const a{Radius + endin.y - Radius * (1.0 - std::cos(endin.angle))};
|
auto const a{Radius + endin.y - Radius * (1.0 - std::cos(endin.angle))};
|
||||||
@@ -408,7 +410,10 @@ result compute(design const &Design)
|
|||||||
auto const directionin{glm::normalize(polygon[k + 1] - polygon[k])};
|
auto const directionin{glm::normalize(polygon[k + 1] - polygon[k])};
|
||||||
auto const directionout{glm::normalize(polygon[k + 2] - polygon[k + 1])};
|
auto const directionout{glm::normalize(polygon[k + 2] - polygon[k + 1])};
|
||||||
auto const turn{cross(directionin, directionout)};
|
auto const turn{cross(directionin, directionout)};
|
||||||
auto const deflection{std::atan2(std::abs(turn), glm::dot(directionin, directionout))};
|
auto const geometric{std::atan2(std::abs(turn), glm::dot(directionin, directionout))};
|
||||||
|
auto const reverse{Design.vertices[k].reverse_turn};
|
||||||
|
auto const deflection{reverse ? 2.0 * kPi - geometric : geometric};
|
||||||
|
int const side{(turn > 0.0) != reverse ? 1 : -1};
|
||||||
|
|
||||||
auto const lead{glm::dot(position - cursor, directionin)};
|
auto const lead{glm::dot(position - cursor, directionin)};
|
||||||
if (deflection < 1e-7)
|
if (deflection < 1e-7)
|
||||||
@@ -420,12 +425,11 @@ result compute(design const &Design)
|
|||||||
cursor = position;
|
cursor = position;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (deflection > kPi - 1e-3)
|
if (geometric > kPi - 1e-3 || (reverse && geometric < 1e-3))
|
||||||
{
|
{
|
||||||
r.errors.emplace_back(format("Vertex %zu: the route turns back on itself", k + 1));
|
r.errors.emplace_back(format("Vertex %zu: a turn of about 180 or 360 degrees needs one more vertex", k + 1));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
int const side{turn > 0.0 ? 1 : -1};
|
|
||||||
auto const radius{fit.radius};
|
auto const radius{fit.radius};
|
||||||
auto const lengthin{fit.transition_in};
|
auto const lengthin{fit.transition_in};
|
||||||
auto const lengthout{fit.transition_out};
|
auto const lengthout{fit.transition_out};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ struct vertex
|
|||||||
bool auto_elevation{true};
|
bool auto_elevation{true};
|
||||||
double elevation{0.0};
|
double elevation{0.0};
|
||||||
double vertical_radius{0.0};
|
double vertical_radius{0.0};
|
||||||
|
bool reverse_turn{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct design
|
struct design
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ http:
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <numeric>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@@ -971,6 +972,77 @@ std::vector<editor_track::straight> editor_track::find_straights(double const Mi
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve)
|
||||||
|
{
|
||||||
|
Curve = {};
|
||||||
|
auto const curved = [&](TTrack const *Other) { return Other != nullptr && Other->eType == tt_Normal && is_supported(*Other) && false == Other->m_editorremoved && false == is_straight(*Other, Tolerance); };
|
||||||
|
if (false == curved(&Track))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::deque<std::pair<TTrack *, bool>> run{{&Track, true}};
|
||||||
|
for (auto const throughend : {true, false})
|
||||||
|
{
|
||||||
|
TTrack *previous{&Track};
|
||||||
|
TTrack *current{throughend ? Track.trNext : Track.trPrev};
|
||||||
|
while (curved(current) && std::none_of(run.begin(), run.end(), [&](auto const &Member) { return Member.first == current; }))
|
||||||
|
{
|
||||||
|
auto const entersatstart{current->trPrev == previous};
|
||||||
|
if (throughend)
|
||||||
|
run.emplace_back(current, entersatstart);
|
||||||
|
else
|
||||||
|
run.emplace_front(current, false == entersatstart);
|
||||||
|
previous = current;
|
||||||
|
current = entersatstart ? current->trNext : current->trPrev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> turns;
|
||||||
|
std::vector<double> lengths;
|
||||||
|
std::vector<bool> varying;
|
||||||
|
double smallest{0.0};
|
||||||
|
for (auto const &member : run)
|
||||||
|
{
|
||||||
|
auto const &path{member.first->m_paths.front()};
|
||||||
|
auto const tangentstart{glm::normalize(plan_of(path_tangent(path, false)))};
|
||||||
|
auto const tangentend{glm::normalize(plan_of(path_tangent(path, true)))};
|
||||||
|
auto turn{std::atan2(lateral_of(tangentstart, tangentend), glm::dot(tangentstart, tangentend))};
|
||||||
|
if (false == member.second)
|
||||||
|
turn = -turn;
|
||||||
|
turns.push_back(turn);
|
||||||
|
lengths.push_back(member.first->Length());
|
||||||
|
auto const radiusstart{path_radius(path, 0.0)};
|
||||||
|
auto const radiusend{path_radius(path, 1.0)};
|
||||||
|
auto const curvaturestart{radiusstart > 0.0 ? 1.0 / radiusstart : 0.0};
|
||||||
|
auto const curvatureend{radiusend > 0.0 ? 1.0 / radiusend : 0.0};
|
||||||
|
varying.push_back(std::abs(curvaturestart - curvatureend) > 0.1 * std::max(curvaturestart, curvatureend));
|
||||||
|
auto const radius{path_radius(path, 0.5)};
|
||||||
|
if (radius > 0.0 && (smallest == 0.0 || radius < smallest))
|
||||||
|
smallest = radius;
|
||||||
|
for (auto const roll : path.rolls)
|
||||||
|
Curve.cant = std::max(Curve.cant, Gauge * std::sin(std::abs(glm::radians(static_cast<double>(roll)))));
|
||||||
|
}
|
||||||
|
int sign{0};
|
||||||
|
for (auto const turn : turns)
|
||||||
|
{
|
||||||
|
Curve.turn += turn;
|
||||||
|
int const current{turn > 1e-9 ? 1 : turn < -1e-9 ? -1 : 0};
|
||||||
|
if (current != 0 && sign != 0 && current != sign)
|
||||||
|
++Curve.reversals;
|
||||||
|
if (current != 0)
|
||||||
|
sign = current;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < run.size() && varying[i]; ++i)
|
||||||
|
Curve.transition_in += lengths[i];
|
||||||
|
for (std::size_t i = run.size(); i > 0 && varying[i - 1]; --i)
|
||||||
|
Curve.transition_out += lengths[i - 1];
|
||||||
|
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;
|
||||||
|
Curve.from = run.front().first;
|
||||||
|
Curve.to = run.back().first;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TTrack *> editor_track::straight_affected(std::vector<straight> const &Lines)
|
std::vector<TTrack *> editor_track::straight_affected(std::vector<straight> const &Lines)
|
||||||
{
|
{
|
||||||
std::vector<TTrack *> result;
|
std::vector<TTrack *> result;
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ class editor_track
|
|||||||
static straight find_straight(TTrack &Track, straight_tolerance const &Tolerance);
|
static straight find_straight(TTrack &Track, straight_tolerance const &Tolerance);
|
||||||
static std::vector<straight> find_straights(double const Minimumlength, straight_tolerance const &Tolerance);
|
static std::vector<straight> find_straights(double const Minimumlength, straight_tolerance const &Tolerance);
|
||||||
static std::vector<TTrack *> straight_affected(std::vector<straight> const &Lines);
|
static std::vector<TTrack *> straight_affected(std::vector<straight> const &Lines);
|
||||||
|
struct curve
|
||||||
|
{
|
||||||
|
TTrack *from{nullptr};
|
||||||
|
TTrack *to{nullptr};
|
||||||
|
double turn{0.0};
|
||||||
|
int reversals{0};
|
||||||
|
double radius{0.0};
|
||||||
|
double transition_in{0.0};
|
||||||
|
double transition_out{0.0};
|
||||||
|
double cant{0.0};
|
||||||
|
};
|
||||||
|
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
|
||||||
static void move_straights(std::vector<straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);
|
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 std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
|
||||||
static void retire(TTrack &Track);
|
static void retire(TTrack &Track);
|
||||||
|
|||||||
Reference in New Issue
Block a user