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"