From 11aa67378dc0ed576f8df356e4b0569548205d37 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 11 Oct 2026 17:31:23 +0200 Subject: [PATCH] feat(editor): the drawn tracks kept in the scenery as //$h lines as they are drawn: laying, pulling out of a free end, joining two ends and the parallel track record the whole line they belong to, its straights, arcs and transition curves, with the radii from the chord and the turn of the arcs and the transitions from the turn they make, in the same undo step; the curves read in the route window and the Curve tab are what the paths are, no transitions or cant made up for them; a curve ends at the point of reverse; a parallel arc is concentric with its source; the paths laid in pieces of up to 90 degrees match the kept design within their own approximation; 'Keep the line as it lies' for lines drawn before; self-test commands digitize and parallel; Polish translation --- application/editormode.h | 2 + application/editormodeparallel.cpp | 27 ++++++- application/editormodeselftest.cpp | 29 ++++++++ application/editormodetrack.cpp | 115 ++++++++++++++++++++++++++--- editor/editorAlignmentStore.cpp | 71 ++++++++++++++++-- editor/editorAlignmentStore.hpp | 4 +- editor/editorTrack.cpp | 47 +++++++++++- editor/editorTrack.hpp | 2 +- lang/editor_pl.po | 12 +++ 9 files changed, 287 insertions(+), 22 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index f679db2b..5b047662 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -809,6 +809,8 @@ class editor_mode : public application_mode, private editor_track::observer void record_slip(std::vector const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base); void render_switch_record(TTrack &Track); void route_store(); + bool digitize_drawn(std::vector const &Created, bool const Alone = true); + bool digitize_chain(editor_track::chain const &Chain); void render_route_stored(); void route_update() { m_route.result = alignment::compute(m_route.design); } void route_apply(); diff --git a/application/editormodeparallel.cpp b/application/editormodeparallel.cpp index 24090fd0..7d0f57d5 100644 --- a/application/editormodeparallel.cpp +++ b/application/editormodeparallel.cpp @@ -23,6 +23,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/translation.h" #include #include +#include namespace { @@ -30,6 +31,7 @@ namespace using geometry::bezier; using geometry::cross; using geometry::plan_of; +using geometry::signed_angle; double const kParallelReach{5000.0}; // m each way along the line double const kSpacings[] = {3.5, 4.0, 4.5, 4.75, 5.0, 6.0}; @@ -76,6 +78,23 @@ double curvature(segment_data const &Path, bool const Atend) return speed > 1e-9 ? cross(first, second) / (speed * speed * speed) : 0.0; } +std::optional arc_curvature(segment_data const &Path) +{ + auto const [control1, control2] = controls(Path); + auto const along{plan_of(control1)}; + auto const back{-plan_of(control2)}; + if (glm::length(along) < 1e-9 || glm::length(back) < 1e-9) + return std::nullopt; + auto const turn{signed_angle(glm::normalize(along), glm::normalize(back))}; + if (std::abs(turn) < 1e-6) + return std::nullopt; + auto const radius{glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])) / (2.0 * std::sin(std::abs(turn) * 0.5))}; + auto const control{4.0 / 3.0 * std::tan(std::abs(turn) * 0.25) * radius}; + if (std::abs(glm::length(along) - control) > 1e-4 * control || std::abs(glm::length(back) - control) > 1e-4 * control) + return std::nullopt; + return std::copysign(1.0 / radius, turn); +} + glm::dvec2 direction(segment_data const &Path, bool const Atend) { auto const [control1, control2] = controls(Path); @@ -188,8 +207,9 @@ void editor_mode::parallel_update() { auto const &source{sources[i].second}; auto const [control1, control2] = controls(source); - auto const startscale{1.0 - curvature(source, false) * offset}; - auto const endscale{1.0 - curvature(source, true) * offset}; + auto const arc{arc_curvature(source)}; + auto const startscale{1.0 - (arc.has_value() ? *arc : curvature(source, false)) * offset}; + auto const endscale{1.0 - (arc.has_value() ? *arc : curvature(source, true)) * offset}; if (startscale < 0.05 || endscale < 0.05) { tool.pieces.clear(); @@ -205,7 +225,7 @@ void editor_mode::parallel_update() piece.rolls = source.rolls; auto const middle{0.5 * (curvature(source, false) + curvature(source, true))}; if (source.radius != 0.0f && std::abs(middle) > 1e-7) - piece.radius = static_cast(std::copysign(std::abs((1.0 - middle * offset) / middle), static_cast(source.radius))); + piece.radius = static_cast(std::copysign(std::abs(std::abs(static_cast(source.radius)) - std::copysign(offset, middle)), static_cast(source.radius))); tool.length += bezier{piece}.plan_length(); tool.pieces.push_back(piece); tool.styles.push_back(sources[i].first); @@ -222,6 +242,7 @@ void editor_mode::parallel_build() for (std::size_t i = 0; i < tool.pieces.size(); ++i) created.push_back(editor_track::create_path(*tool.styles[i], tool.pieces[i])); editor_track::commit(created); + digitize_drawn(created); push_track_snapshot({}, created); tool.status = format(STR_C("Parallel track of %zu paths, %.1f m, %.2f m away; Ctrl+Z takes it back"), created.size(), tool.length, tool.spacing); if (scene::Layers.active() == null_handle) diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index c034e51b..25cd7ba2 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -1373,6 +1373,7 @@ void editor_mode::selftest_step() for (auto const &piece : pieces) created.push_back(editor_track::create_path(*tool.track, piece)); editor_track::commit(created); + digitize_drawn(created); if (false == created.empty()) push_track_snapshot({}, created); char const *const kinds[]{"none", "straight", "arc", "curve", "reverse curve", "free curve"}; @@ -1395,6 +1396,34 @@ void editor_mode::selftest_step() if (false == created.empty()) WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b))); } + else if (command == "digitize") + { + glm::dvec3 point{0.0}; + words >> point.x >> point.z; + TTrack *nearest{nullptr}; + double distance{1e9}; + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty()) + continue; + auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)}; + if (d < distance) + { + distance = d; + nearest = track; + } + } + auto const kept{nearest != nullptr && digitize_drawn({nearest}, false)}; + WriteLog(format("SELFTEST digitize %s: %d", nearest != nullptr ? nearest->name().c_str() : "-", kept ? 1 : 0)); + } + else if (command == "parallel") + { + words >> m_parallel.spacing >> m_parallel.side; + m_parallel.scope = 1; + m_parallel.built_for = nullptr; + parallel_build(); + WriteLog("SELFTEST parallel: " + m_parallel.status + " " + m_parallel.error); + } else if (command == "layclicks") { int layout{0}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 1057c809..070a6a59 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -37,6 +37,7 @@ http: #include #include #include +#include namespace { @@ -94,6 +95,7 @@ float const kSnapPixels{16.0f}; float const kDragPixels{5.0f}; double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch double const kLineReach{2000.0}; // how far each way the Curve tab follows the line +double const kDrawnReach{100000.0}; double const kJointRange{400.0}; // the ends of the paths are drawn for the tracks this close to the camera ImU32 const kJointColor{IM_COL32(255, 255, 255, 200)}; int const kRailCategory{1}; @@ -2608,14 +2610,10 @@ alignment::vertex editor_mode::route_vertex_of(editor_track::curve const &Curve) if (Curve.designed.has_value()) return *Curve.designed; 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); + vertex.radius = Curve.radius > 0.0 ? std::max(1.0, Curve.radius) : 1000.0; + vertex.transition_in = std::max(0.0, Curve.transition_in); + vertex.transition_out = std::max(0.0, Curve.transition_out); + vertex.cant = std::max(0.0, Curve.cant); vertex.reverse_turn = std::abs(Curve.turn) > glm::pi(); compound_from(Curve, vertex); return vertex; @@ -2707,6 +2705,19 @@ void editor_mode::render_route_stored() route.origin = km * 1000.0; ImGui::PopItemWidth(); item_tooltip("Chainage of the start of the fragment, kept with the design in the scenery; the vertical profile of these paths counts from it"); + if (route.designed == 0 && route.departed < 0 && false == route.chain.tracks.empty()) + { + if (ImGui::SmallButton(STR_C("Keep the line as it lies"))) + { + auto *track{route.chain.tracks[route.chain.tracks.size() / 2]}; + auto const kept{digitize_drawn({track}, false)}; + alignment_store::settle(); + route_load(*track); + route.status += std::string{"\n"} + (kept ? STR_C("The line is kept in the scenery with its straights, arcs and transition curves") : STR_C("The paths don't make a line of straights, arcs and transition curves, nothing kept")); + return; + } + item_tooltip("Keeps the line through the selected path in the scenery as its paths lie, the way the drawn tracks are kept"); + } auto const &records{alignment_store::records()}; if (route.departed < 0 || route.departed >= static_cast(records.size())) return; @@ -2739,6 +2750,83 @@ void editor_mode::route_store() route.departure = 0.0; } +bool editor_mode::digitize_drawn(std::vector const &Created, bool const Alone) +{ + std::vector drawn; + for (auto *track : Created) + if (track != nullptr && track->eType == tt_Normal && false == track->m_editorremoved && false == track->m_paths.empty()) + drawn.push_back(track); + if (drawn.empty() || scene::Layers.empty()) + return false; + editor_track::chain chain; + std::string error; + if (editor_track::find_run(*drawn.front(), kDrawnReach, chain, error) && digitize_chain(chain)) + return true; + if (Alone && editor_track::find_chain(drawn.front(), drawn.back(), chain, error) && chain.tracks.size() == drawn.size() && digitize_chain(chain)) + return true; + WriteLog("Editor: the drawn paths don't make a line of straights, arcs and transition curves, no design kept for them", logtype::generic); + return false; +} + +bool editor_mode::digitize_chain(editor_track::chain const &Chain) +{ + if (Chain.tracks.empty()) + return false; + alignment::design design; + design.norms = m_route.design.norms; + design.shape = m_route.design.shape; + design.transition_pieces = m_route.design.transition_pieces; + design.arc_piece_angle = m_route.design.arc_piece_angle; + design.speed = Chain.velocity > 0.0 ? Chain.velocity : 100.0; + design.start = Chain.start; + design.end = Chain.end; + if (glm::length(plan_of(Chain.start_direction)) < 1e-9 || glm::length(plan_of(Chain.end_direction)) < 1e-9) + return false; + design.start_direction = glm::normalize(plan_of(Chain.start_direction)); + design.end_direction = glm::normalize(plan_of(Chain.end_direction)); + design.start_grade = grade_of(Chain.start_direction); + design.end_grade = grade_of(Chain.end_direction); + design.start_radius = Chain.start_radius; + design.end_radius = Chain.end_radius; + for (auto const &entry : editor_track::recognize_line(Chain, m_straights.tolerance, design.norms.gauge, design.shape)) + { + if (entry.kink) + return false; + auto vertex{route_vertex_of(entry.shape)}; + vertex.position = entry.position; + design.vertices.push_back(vertex); + } + if (design.vertices.size() >= 2) + { + design.vertices.front().offset = std::max(0.0, glm::dot(design.vertices.front().position - plan_of(design.start), design.start_direction)); + design.vertices.back().offset = std::max(0.0, glm::dot(plan_of(design.end) - design.vertices.back().position, design.end_direction)); + } + auto const result{alignment::compute(design)}; + if (false == alignment_store::follows(result, Chain.tracks)) + return false; + auto const &records{alignment_store::records()}; + std::set covered; + for (auto *track : Chain.tracks) + if (auto const found{alignment_store::on_track(*track)}) + covered.insert(found->record); + double origin{0.0}; + for (auto const index : covered) + { + auto const &old{records[index]}; + auto const start{alignment_store::chainage_on(result, old.design.start)}; + auto const end{alignment_store::chainage_on(result, old.design.end)}; + if (false == start.has_value() || false == end.has_value()) + return false; + if (index == *covered.begin() && *end > *start) + origin = old.origin - *start; + } + auto const layer{covered.empty() ? record_layer(*Chain.tracks.front()) : records[*covered.begin()].layer}; + for (auto index{covered.rbegin()}; index != covered.rend(); ++index) + alignment_store::forget(*index); + alignment_store::store(design, result, origin, layer, "drawn"); + return true; +} + bool editor_mode::route_from_line(TTrack &Track) { auto &route{m_route}; @@ -2751,7 +2839,7 @@ bool editor_mode::route_from_line(TTrack &Track) if (route.chain.tracks.empty()) return false; auto &design{route.design}; - auto const found{editor_track::recognize_line(route.chain, m_straights.tolerance, design.norms.gauge)}; + auto const found{editor_track::recognize_line(route.chain, m_straights.tolerance, design.norms.gauge, design.shape)}; route.designed = 0; if (false == found.empty()) { @@ -4325,6 +4413,12 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co states.push_back(entry); } created.insert(created.begin(), splitcreated.begin(), splitcreated.end()); + { + auto changed{created}; + for (auto const &entry : states) + changed.push_back(entry.first); + digitize_drawn(changed); + } push_track_snapshot(std::move(states), std::move(created)); std::string error; editor_track::find_chain(m_route.from, m_route.to, m_route.chain, error, true); @@ -5701,6 +5795,7 @@ void editor_mode::finish_extend() for (auto const &piece : pieces) created.push_back(editor_track::create_path(*style, piece)); editor_track::commit(created); + digitize_drawn(created); push_track_snapshot({}, created); m_node = created.back(); ui()->set_node(m_node); @@ -5730,6 +5825,7 @@ void editor_mode::extend_add(int const Side) for (auto const &piece : pieces) created.push_back(editor_track::create_path(*tool.track, piece)); editor_track::commit(created); + digitize_drawn(created); push_track_snapshot({}, created); m_node = created.back(); ui()->set_node(m_node); @@ -6055,6 +6151,7 @@ void editor_mode::lay_finish(editor_track::snap_target const &End) for (auto const &piece : pieces) created.push_back(style != nullptr ? editor_track::create_path(*style, piece) : editor_track::create_path(tool.style, piece)); editor_track::commit(created); + digitize_drawn(created); push_track_snapshot({}, created); tool.status = format(STR_C("Laid %zu paths, %.1f m"), created.size(), length); if (false == error.empty()) diff --git a/editor/editorAlignmentStore.cpp b/editor/editorAlignmentStore.cpp index 18506313..0542ca98 100644 --- a/editor/editorAlignmentStore.cpp +++ b/editor/editorAlignmentStore.cpp @@ -382,10 +382,23 @@ std::vector const &records() namespace { -// the point on the line of the stored alignment, if it lies by it within the reach and not beyond its ends -std::optional project(std::size_t const Index, glm::dvec2 const &Point, double const Reach) +double approximation(geometry::bezier const &Curve) { - auto const &entry{g_records[Index]}; + auto const begin{plan_of(Curve.first(0.0))}; + auto const finish{plan_of(Curve.first(1.0))}; + if (glm::length(begin) < 1e-9 || glm::length(finish) < 1e-9) + return 0.0; + auto const turn{std::abs(geometry::signed_angle(glm::normalize(begin), glm::normalize(finish)))}; + if (turn < 1e-6) + return 0.0; + auto const radius{geometry::plan_distance(Curve.point(0.0), Curve.point(1.0)) / (2.0 * std::sin(turn * 0.5))}; + return 3.0e-4 * radius * std::pow(turn / (0.5 * 3.14159265358979323846), 6.0); +} + +// the point on the line of the stored alignment, if it lies by it within the reach and not beyond its ends +std::optional project(record const &Entry, std::size_t const Index, glm::dvec2 const &Point, double const Reach) +{ + auto const &entry{Entry}; auto const margin{Reach + kStep}; if (entry.samples.empty() || Point.x < entry.low.x - margin || Point.y < entry.low.y - margin || Point.x > entry.high.x + margin || Point.y > entry.high.y + margin) return std::nullopt; @@ -430,6 +443,31 @@ std::optional project(std::size_t const Index, glm::dvec2 const &Point, dou return hit{static_cast(Index), static_cast(std::max(0, std::distance(elements.begin(), found) - 1)), chainage, offset}; } +std::optional project(std::size_t const Index, glm::dvec2 const &Point, double const Reach) +{ + return project(g_records[Index], Index, Point, Reach); +} + +bool lies_along(record const &Entry, TTrack const &Track) +{ + if (Track.m_paths.empty()) + return false; + geometry::bezier const curve{Track.m_paths.front()}; + auto const reach{tolerance + approximation(curve)}; + for (auto const t : {0.0, 0.25, 0.5, 0.75, 1.0}) + if (false == project(Entry, 0, plan_of(curve.point(t)), t == 0.25 || t == 0.75 ? reach : tolerance).has_value()) + return false; + return true; +} + +record temporary(alignment::result const &Result) +{ + record entry; + entry.result = Result; + sample(entry); + return entry; +} + } // namespace std::optional at(glm::dvec3 const &Point, double const Reach) @@ -451,6 +489,7 @@ std::optional on_track(TTrack const &Track, double const Reach) return std::nullopt; load(); geometry::bezier const curve{Track.m_paths.front()}; + auto const reach{Reach + approximation(curve)}; std::optional best; for (std::size_t r = 0; r < g_records.size(); ++r) { @@ -459,7 +498,7 @@ std::optional on_track(TTrack const &Track, double const Reach) continue; bool along{true}; for (auto const t : {0.0, 0.25, 0.75, 1.0}) - along = along && project(r, plan_of(curve.point(t)), Reach).has_value(); + along = along && project(r, plan_of(curve.point(t)), reach).has_value(); if (along) best = middle; } @@ -484,9 +523,10 @@ std::optional vertex_of(std::vector> if (member.first == nullptr || member.first->m_paths.empty()) return std::nullopt; geometry::bezier const curve{member.first->m_paths.front()}; + auto const reach{tolerance + approximation(curve)}; std::vector hits; for (auto const t : {0.0, 0.25, 0.5, 0.75, 1.0}) - if (auto const found{project(r, plan_of(curve.point(t)), tolerance)}) + if (auto const found{project(r, plan_of(curve.point(t)), t == 0.25 || t == 0.75 ? reach : tolerance)}) hits.push_back(*found); if (hits.size() < 5) { @@ -559,10 +599,29 @@ int departed(std::vector const &Tracks, double &Departure) return result; } -int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer) +bool follows(alignment::result const &Result, std::vector const &Tracks) +{ + if (false == Result.valid || Result.elements.empty()) + return false; + auto const entry{temporary(Result)}; + return std::all_of(Tracks.begin(), Tracks.end(), [&](TTrack const *Track) { return Track != nullptr && lies_along(entry, *Track); }); +} + +std::optional chainage_on(alignment::result const &Result, glm::dvec3 const &Point) +{ + if (false == Result.valid) + return std::nullopt; + auto const found{project(temporary(Result), 0, plan_of(Point), tolerance)}; + if (false == found.has_value()) + return std::nullopt; + return found->chainage; +} + +int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source) { load(); record entry; + entry.source = Source; entry.design = Design; entry.design.start_reserve = entry.design.end_reserve = 0.0; entry.origin = Origin; diff --git a/editor/editorAlignmentStore.hpp b/editor/editorAlignmentStore.hpp index 7d275bcc..84e5368c 100644 --- a/editor/editorAlignmentStore.hpp +++ b/editor/editorAlignmentStore.hpp @@ -83,7 +83,9 @@ int find(glm::dvec3 const &Start, glm::dvec3 const &End, bool &Reversed); // stored alignment near the tracks, whose line they no longer follow; Departure receives the largest distance int departed(std::vector const &Tracks, double &Departure); // keeps the design in the file of the layer, in place of the one with the same ends -int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer); +int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source = "designed"); +bool follows(alignment::result const &Result, std::vector const &Tracks); +std::optional chainage_on(alignment::result const &Result, glm::dvec3 const &Point); void forget(int const Index); // the point on the line of the stored alignment, if it lies by it within the reach std::optional on_record(int const Index, glm::dvec3 const &Point, double const Reach); diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index bed8f1f6..075709e2 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -1590,6 +1590,17 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance std::deque> run{{&Track, true}}; auto const known = [&](TTrack const *Other) { return std::any_of(run.begin(), run.end(), [&](auto const &Member) { return Member.first == Other; }); }; + auto const own_turn = [](TTrack const &Other, bool const Forward) { + auto const &path{Other.m_paths.front()}; + auto const begin{plan_of(path_tangent(path, false))}; + auto const finish{plan_of(path_tangent(path, true))}; + if (glm::length(begin) < 1e-9 || glm::length(finish) < 1e-9) + return 0.0; + auto const turn{signed_angle(glm::normalize(begin), glm::normalize(finish))}; + return Forward ? turn : -turn; + }; + auto const side{own_turn(Track, true)}; + auto const reversing = [&](TTrack const &Other, bool const Forward) { return own_turn(Other, Forward) * side < 0.0 && std::abs(own_turn(Other, Forward)) > 1e-4; }; for (auto const throughend : {true, false}) { TTrack *previous{&Track}; @@ -1603,7 +1614,7 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance if (atstart == (extension.pNexts[0] == previous)) break; auto *beyond{atstart ? extension.pNexts[0] : extension.pPrevs[0]}; - if (false == curved(beyond) || known(beyond)) + if (false == curved(beyond) || known(beyond) || reversing(*current, throughend == atstart)) break; if (throughend) run.emplace_back(current, atstart); @@ -1616,6 +1627,8 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance if (false == curved(current)) break; auto const entersatstart{current->trPrev == previous}; + if (reversing(*current, throughend == entersatstart)) + break; if (throughend) run.emplace_back(current, entersatstart); else @@ -1813,7 +1826,7 @@ bool editor_track::find_run(TTrack &Track, double const Limit, chain &Chain, std return find_chain(ends[1], ends[0], Chain, Error); } -std::vector editor_track::recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge) +std::vector editor_track::recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge, alignment::transition_shape const Shape) { std::vector result; auto const count{Chain.tracks.size()}; @@ -1844,6 +1857,36 @@ std::vector editor_track::recognize_line(chain const for (auto i = First; i <= Last; ++i) run.emplace_back(Chain.tracks[i], Chain.forward[i]); analyse_curve(run, Gauge, vertex.shape); + auto &shape{vertex.shape}; + if (false == shape.designed.has_value() && false == shape.compound && shape.reversals == 0) + { + auto const pieces = [&](double const Length, bool const Fromstart) { + std::size_t count{0}; + double covered{0.0}; + while (covered < Length - 1e-6 && count <= Last - First) + { + covered += Chain.tracks[Fromstart ? First + count : Last - count]->Length(); + ++count; + } + return count; + }; + auto const in{shape.transition_in > 0.0 ? pieces(shape.transition_in, true) : 0}; + auto const out{shape.transition_out > 0.0 ? pieces(shape.transition_out, false) : 0}; + if (in + out <= Last - First) + { + auto const turn_of = [&](std::size_t const From, std::size_t const To) { return std::abs(std::accumulate(turns.begin() + From, turns.begin() + To + 1, 0.0)); }; + auto const arc{turn_of(First + in, Last - out)}; + if (arc > 1e-6 && arc < 2.0 * glm::pi() - 1e-3) + { + shape.radius = glm::distance(point_in(First + in), point_out(Last - out)) / (2.0 * std::sin(arc * 0.5)); + auto const length = [&](double const Turn) { return Shape == alignment::transition_shape::clothoid ? 2.0 * shape.radius * Turn : 2.0 * shape.radius * std::tan(Turn); }; + if (in > 0) + shape.transition_in = length(turn_of(First, First + in - 1)); + if (out > 0) + shape.transition_out = length(turn_of(Last - out + 1, Last)); + } + } + } result.push_back(vertex); }; std::size_t i{0}; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 2149e8a1..4e6fa0a9 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -229,7 +229,7 @@ class editor_track curve shape; bool kink{false}; // the straights meet with no curve between them }; - static std::vector recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge); + static std::vector recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge, alignment::transition_shape const Shape = alignment::transition_shape::cubic_parabola); // regular straight paths continuing a chain end in line, which the chain can take over struct straight_run { diff --git a/lang/editor_pl.po b/lang/editor_pl.po index dbed1df1..dd9bee46 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5347,6 +5347,18 @@ msgstr "Zapisany projekt zostanie usunięty z " msgid "The paths stay as they are, the scenery no longer keeps a design for them" msgstr "Odcinki zostają bez zmian, sceneria przestaje przechowywać dla nich projekt" +msgid "Keep the line as it lies" +msgstr "Zachowaj linię tak, jak leży" + +msgid "Keeps the line through the selected path in the scenery as its paths lie, the way the drawn tracks are kept" +msgstr "Zapisuje w scenerii linię przez wybrany odcinek tak, jak leżą jej odcinki, tak jak zapisywane są rysowane tory" + +msgid "The line is kept in the scenery with its straights, arcs and transition curves" +msgstr "Linia jest zapisana w scenerii z prostymi, łukami i krzywymi przejściowymi" + +msgid "The paths don't make a line of straights, arcs and transition curves, nothing kept" +msgstr "Odcinki nie tworzą linii z prostych, łuków i krzywych przejściowych, nic nie zapisano" + msgid "The scenery keeps no type for this switch, it's read from its shape" msgstr "Sceneria nie przechowuje typu tego rozjazdu, jest odczytywany z jego kształtu"