From d4d2a02018980490a571fbd9b0c692f7200963da Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 12 Oct 2026 00:09:49 +0200 Subject: [PATCH] feat(editor): the switch no longer slides to meet the track, only the straight and arc fill in: arc of the switch radius between two straights, and where the angles match but the tracks are apart, or the arc doesn't fit, reverse curves with no transitions and the largest radius that fits; the straight between them (6 m to V 60, 0.15 V to 100, 0.25 V above) only when 1/R1 + 1/R2 > 1/106.5 m (PLK ST-T1 9.3, table 9.4); new pieces take the style of a plain track, not of a switch --- application/editormode.h | 5 +- application/editormodeselftest.cpp | 2 +- application/editormodetrack.cpp | 148 +++++++++--------------- editor/editorTrack.hpp | 6 +- editor/editorTrackShape.cpp | 177 ++++++++++++++++++++++------- lang/editor_pl.po | 19 ++-- 6 files changed, 206 insertions(+), 151 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index 717725a5..5df3f4c2 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1072,9 +1072,6 @@ class editor_mode : public application_mode, private editor_track::observer editor_track::snap_target origin; editor_track::snap_target snap; glm::vec2 pressed{0.f}; - bool slides{false}; - double slide_low{0.0}; - double slide_high{0.0}; std::vector line; }; extend_tool m_extend; @@ -1143,7 +1140,7 @@ class editor_mode : public application_mode, private editor_track::observer bool plan_branch_link(editor_track::branch_link &Link, std::string &Reason) const; void finish_branch_link(editor_track::branch_link const &Link); std::string link_problem(editor_track::snap_target const &Target, editor_track::branch_link const &Link) const; - std::vector link_tracks(editor_track::snap_target const &Target, editor_track::branch_link const &Link, std::vector> &States, std::vector &Created); + std::vector link_tracks(editor_track::snap_target const &Target, editor_track::branch_link const &Link, TTrack const &Style, std::vector> &States, std::vector &Created); bool cut_straight(editor_track::straight const &Line, double const From, double const To, std::vector> &States, std::vector &Created, std::vector &Removed, TTrack **Style); bool insert_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape); bool replace_double_slip(TTrack &Part, editor_track::switch_template const &Shape); diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index ad5a84ce..999d74b8 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -1470,7 +1470,7 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com words >> point.x >> point.z >> target.x >> target.z; std::replace(label.begin(), label.end(), '_', ' '); auto const log_link = [&](editor_track::branch_link const &Link, std::string const &Reason) { - WriteLog(format("SELFTEST %s plan: valid %d, shift %.3f m, R %.1f m, before %.3f m, after %.3f m, trim %.3f m, shortfall %.3f m, %zu pieces%s", command.c_str(), Link.valid ? 1 : 0, Link.shift, Link.radius, Link.before, Link.after, Link.trim, + WriteLog(format("SELFTEST %s plan: valid %d, R %.1f m, before %.3f m, after %.3f m, trim %.3f m, shortfall %.3f m, %zu pieces%s", command.c_str(), Link.valid ? 1 : 0, Link.radius, Link.before, Link.after, Link.trim, Link.shortfall, Link.pieces.size(), Reason.empty() ? "" : (", " + Reason).c_str())); for (auto const &piece : Link.pieces) { diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 7a208895..f84d4643 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -116,12 +116,43 @@ editor_track::branch_end branch_target(editor_track::snap_target const &Target) return {Target.position, plan_of(Target.direction), grade_of(Target.direction), Target.track != nullptr ? editor_track::joint_roll(*Target.track, Target.point) : 0.0}; } -std::string link_summary(editor_track::snap_target const &Target, editor_track::branch_link const &Link) +double switch_from(editor_track::straight const &Line, double const Length, double const Along, int const Direction, bool const Snap) +{ + auto from{std::clamp(Direction > 0 ? Along : Along - Length, 0.0, Line.length - Length)}; + if (false == Snap) + return from; + std::vector joints{0.0, Line.length}; + for (std::size_t i = 0; i < Line.tracks.size(); ++i) + { + auto const &path{Line.tracks[i]->m_paths[Line.paths[i]]}; + for (auto const index : {segment_data::point::start, segment_data::point::end}) + joints.push_back(Line.along(path.points[index])); + } + auto const nearest = [&](double const Value) { + double best{Value}; + for (auto const joint : joints) + if (std::abs(joint - Value) < std::abs(best - Value) || best == Value) + best = std::abs(joint - Value) < 1.0 ? joint : best; + return best; + }; + auto const snappedstart{nearest(from)}; + auto const snappedend{nearest(from + Length)}; + if (snappedstart != from) + from = snappedstart; + else if (snappedend != from + Length) + from = snappedend - Length; + return std::clamp(from, 0.0, Line.length - Length); +} + +std::string link_summary(editor_track::snap_target const &Target, editor_track::branch_link const &Link, double const Switchradius) { auto const name{Target.track != nullptr && false == Target.track->name().empty() ? Target.track->name() : std::string{"(noname)"}}; - auto text{Link.radius > 0.0 ? format(STR_C("The branch leads into %s with an R %.0f m curve"), name.c_str(), Link.radius) : format(STR_C("The branch leads straight into %s"), name.c_str())}; - if (std::abs(Link.shift) >= 0.005) - text += format(STR_C(", the switch moved %.2f m"), Link.shift); + auto text{Link.reverse ? format(STR_C("The branch leads into %s with reverse curves R %.0f m"), name.c_str(), Link.radius) : + Link.radius > 0.0 ? format(STR_C("The branch leads into %s with an R %.0f m curve"), name.c_str(), Link.radius) : format(STR_C("The branch leads straight into %s"), name.c_str())}; + if (Link.insert > 0.0) + text += format(STR_C(", a straight of %.1f m between them"), Link.insert); + if (Link.reverse && Link.radius < Switchradius - 0.5) + text += format(STR_C(", sharper than the switch (R %.0f m)"), Switchradius); if (Link.trim > 0.0) text += format(STR_C(", %s shortened %.2f m"), name.c_str(), Link.trim); return text; @@ -3749,7 +3780,7 @@ std::string editor_mode::track_readout() const editor_track::branch_link link; std::string reason; if (plan_branch_link(link, reason)) - return link_summary(target, link); + return link_summary(target, link, editor_track::branch_radius(m_extend.track->m_paths[1])); if (snapped && pieces.empty()) return "no track fits to the end of " + name(target.track); auto text{radius > 0.0 ? format("L %.2f m R %.0f m", length, radius) : format("L %.2f m straight", length)}; @@ -4092,19 +4123,7 @@ bool editor_mode::start_extend(editor_track::snap_target const &End) m_extend.pressed = {io.MousePos.x, io.MousePos.y}; m_extend.active = true; if (track->eType == tt_Switch && End.point.path == 1 && End.point.kind == editor_track::point_kind::end) - { - double low{0.0}; - double high{0.0}; - std::string reason; - auto const axis{editor_track::main_axis(*track)}; - if (axis.has_value() && switch_slides(*track) && switch_room(*track, low, high, reason)) - { - m_extend.slides = true; - m_extend.slide_low = low; - m_extend.slide_high = high - axis->length; - } m_extend.line = editor_track::find_straight(*track, m_straights.tolerance).tracks; - } return true; } @@ -5260,31 +5279,7 @@ bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, e m_switch.status = format(STR_C("The straight is %.1f m long, too short for the %.1f m switch"), Line.length, shape.length); return false; } - auto from{std::clamp(Direction > 0 ? Along : Along - shape.length, 0.0, Line.length - shape.length)}; - if (Snap) - { - std::vector joints{0.0, Line.length}; - for (std::size_t i = 0; i < Line.tracks.size(); ++i) - { - auto const &path{Line.tracks[i]->m_paths[Line.paths[i]]}; - for (auto const index : {segment_data::point::start, segment_data::point::end}) - joints.push_back(Line.along(path.points[index])); - } - auto const nearest = [&](double const Value) { - double best{Value}; - for (auto const joint : joints) - if (std::abs(joint - Value) < std::abs(best - Value) || best == Value) - best = std::abs(joint - Value) < 1.0 ? joint : best; - return best; - }; - auto const snappedstart{nearest(from)}; - auto const snappedend{nearest(from + shape.length)}; - if (snappedstart != from) - from = snappedstart; - else if (snappedend != from + shape.length) - from = snappedend - shape.length; - from = std::clamp(from, 0.0, Line.length - shape.length); - } + auto const from{switch_from(Line, shape.length, Along, Direction, Snap)}; auto const to{from + shape.length}; auto const origin{Direction > 0 ? from : to}; TTrack *first{nullptr}; @@ -5346,11 +5341,6 @@ void editor_mode::draw_build_overlay() const ImVec2 screen; if (projection.project(target.position, screen)) drawlist->AddCircle(screen, 14.0f, IM_COL32(255, 60, 255, 255), 20, 3.0f); - editor_track::branch_link link; - std::string reason; - if (plan_branch_link(link, reason) && std::abs(link.shift) >= 0.005) - for (auto const &path : editor_track::slid(*m_extend.track, link.shift)) - drawpath(path, overlay_color::grip); for (auto const &piece : extend_pieces()) drawpath(piece, overlay_color::marked); } @@ -5846,7 +5836,7 @@ std::string editor_mode::link_problem(editor_track::snap_target const &Target, e { auto const name{Target.track != nullptr && false == Target.track->name().empty() ? Target.track->name() : std::string{"(noname)"}}; if (false == Link.valid) - return Link.shortfall > 0.0 ? format(STR_C("The branch can't lead into %s: the tracks are %.2f m too close for this switch with its curve"), name.c_str(), Link.shortfall) : + return Link.shortfall > 0.0 ? format(STR_C("The branch can't lead into %s: the R %.0f m curve needs %.2f m more room"), name.c_str(), Link.radius, Link.shortfall) : format(STR_C("The branch can't lead into %s"), name.c_str()); if (Link.trim > 0.0) { @@ -5892,23 +5882,17 @@ bool editor_mode::plan_switch_link(switch_link &Plan) const if (Plan.target.track == nullptr) return false; } - auto const paths{place(tool.along)}; - if (paths.size() < 2) + if (shape.length > line.length - 0.02) return false; - glm::dvec2 const direction{line.direction * static_cast(Plan.forward)}; - Plan.link = editor_track::link_branch(branch_start(paths[1]), {direction.x, line.grade * Plan.forward, direction.y}, editor_track::branch_radius(paths[1]), branch_target(Plan.target), glm::radians(m_joints.angle * 0.5)); - Plan.reason = link_problem(Plan.target, Plan.link); - if (false == Plan.reason.empty()) - return false; - Plan.along = tool.along + Plan.link.shift * Plan.forward; - auto const from{Plan.forward > 0 ? Plan.along : Plan.along - shape.length}; - if (from < -1e-6 || from + shape.length > line.length + 1e-6) - { - Plan.reason = format(STR_C("The switch would have to move %.1f m, off the straight it stands on"), Plan.link.shift); - return false; - } + auto const from{switch_from(line, shape.length, tool.along, Plan.forward, true)}; + Plan.along = Plan.forward > 0 ? from : from + shape.length; Plan.paths = place(Plan.along); - return true; + if (Plan.paths.size() < 2) + return false; + Plan.link = editor_track::link_branch(branch_start(Plan.paths[1]), editor_track::branch_radius(Plan.paths[1]), branch_target(Plan.target), glm::radians(m_joints.angle * 0.5), + editor_track::reverse_insert(design_speed(line.tracks.front(), Plan.target.track))); + Plan.reason = link_problem(Plan.target, Plan.link); + return Plan.reason.empty(); } void editor_mode::insert_linked_switch(switch_link const &Plan) @@ -5919,12 +5903,12 @@ void editor_mode::insert_linked_switch(switch_link const &Plan) if (false == place_switch_on_straight(m_switch.line, m_switch.templates[m_switch.armed], nullptr, Plan.along, Plan.forward, Plan.side, false, states, created, removed, switch_name_for_new())) return; auto const placed{created}; - link_tracks(Plan.target, Plan.link, states, created); + link_tracks(Plan.target, Plan.link, Plan.target.track->eType == tt_Normal ? *Plan.target.track : *m_switch.line.tracks.front(), states, created); push_track_snapshot(std::move(states), std::move(created), std::move(removed)); straight_refresh(); m_switch.status.clear(); switch_placed(placed); - m_switch.status = link_summary(Plan.target, Plan.link) + (m_switch.status.empty() ? std::string{} : "\n" + m_switch.status); + m_switch.status = link_summary(Plan.target, Plan.link, editor_track::branch_radius(Plan.paths[1])) + (m_switch.status.empty() ? std::string{} : "\n" + m_switch.status); ui()->set_status(m_switch.status, false); } @@ -5937,22 +5921,10 @@ bool editor_mode::plan_branch_link(editor_track::branch_link &Link, std::string return false; if (std::find(tool.line.begin(), tool.line.end(), target.track) != tool.line.end()) return false; - auto const axis{editor_track::main_axis(*tool.track)}; - if (false == axis.has_value()) - return false; - auto const &main{tool.track->m_paths.front()}; auto const &diverging{tool.track->m_paths[1]}; - auto const rise{grade_of(main.points[segment_data::point::end] - main.points[segment_data::point::start])}; - Link = editor_track::link_branch(branch_start(diverging), {axis->direction.x, rise, axis->direction.y}, editor_track::branch_radius(diverging), branch_target(target), glm::radians(m_joints.angle * 0.5)); + Link = editor_track::link_branch(branch_start(diverging), editor_track::branch_radius(diverging), branch_target(target), glm::radians(m_joints.angle * 0.5), editor_track::reverse_insert(design_speed(tool.track, target.track))); Reason = link_problem(target, Link); - if (false == Reason.empty()) - return false; - if (std::abs(Link.shift) >= 0.005 && (false == tool.slides || Link.shift < tool.slide_low - 1e-6 || Link.shift > tool.slide_high + 1e-6)) - { - Reason = format(STR_C("The switch would have to move %.1f m, there's no plain track for it there"), Link.shift); - return false; - } - return true; + return Reason.empty(); } void editor_mode::finish_branch_link(editor_track::branch_link const &Link) @@ -5960,26 +5932,18 @@ void editor_mode::finish_branch_link(editor_track::branch_link const &Link) auto *own{m_extend.track}; auto const target{m_extend.snap}; m_extend.active = false; - auto link{Link}; - bool const slides{std::abs(link.shift) >= 0.005}; - if (slides && false == slide_switch(*own, link.shift)) - return; - if (false == slides && false == link.pieces.empty()) - link.pieces.front().points[segment_data::point::start] = own->m_paths[1].points[segment_data::point::end]; std::vector> states; std::vector created; - auto const linked{link_tracks(target, link, states, created)}; - bool const changed{false == states.empty() || false == created.empty()}; + auto const &style{target.track->eType == tt_Normal || m_extend.line.empty() ? *target.track : *m_extend.line.front()}; + auto const linked{link_tracks(target, Link, style, states, created)}; push_track_snapshot(std::move(states), std::move(created)); - if (slides && changed) - m_history.back().joined = true; m_node = linked.empty() ? own : linked.back(); ui()->set_node(m_node); straight_refresh(); - ui()->set_status(link_summary(target, link), false); + ui()->set_status(link_summary(target, Link, editor_track::branch_radius(own->m_paths[1])), false); } -std::vector editor_mode::link_tracks(editor_track::snap_target const &Target, editor_track::branch_link const &Link, std::vector> &States, std::vector &Created) +std::vector editor_mode::link_tracks(editor_track::snap_target const &Target, editor_track::branch_link const &Link, TTrack const &Style, std::vector> &States, std::vector &Created) { auto *target{Target.track}; if (Link.trim > 0.0) @@ -5991,7 +5955,7 @@ std::vector editor_mode::link_tracks(editor_track::snap_target const & } std::vector pieces; for (auto const &piece : Link.pieces) - pieces.push_back(editor_track::create_path(*target, piece)); + pieces.push_back(editor_track::create_path(Style, piece)); editor_track::commit(pieces); digitize_drawn(pieces); Created.insert(Created.end(), pieces.begin(), pieces.end()); diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index b8a4e710..ad1ab085 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -214,16 +214,18 @@ class editor_track struct branch_link { bool valid{false}; - double shift{0.0}; double trim{0.0}; double radius{0.0}; double before{0.0}; double after{0.0}; double shortfall{0.0}; + bool reverse{false}; + double insert{0.0}; std::vector pieces; }; static double branch_radius(segment_data const &Diverging); - static branch_link link_branch(branch_end const &From, glm::dvec3 const &Slide, double const Radius, branch_end const &To, double const Kink); + static double reverse_insert(double const Speed); + static branch_link link_branch(branch_end const &From, double const Radius, branch_end const &To, double const Kink, double const Insert); struct snap_target { diff --git a/editor/editorTrackShape.cpp b/editor/editorTrackShape.cpp index 8b50984f..9cc4cc3d 100644 --- a/editor/editorTrackShape.cpp +++ b/editor/editorTrackShape.cpp @@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include namespace { @@ -378,78 +379,170 @@ double editor_track::branch_radius(segment_data const &Diverging) return turn > 1e-4 ? curve.plan_length() / turn : 0.0; } -editor_track::branch_link editor_track::link_branch(branch_end const &From, glm::dvec3 const &Slide, double const Radius, branch_end const &To, double const Kink) +double editor_track::reverse_insert(double const Speed) +{ + if (Speed <= 60.0) + return 6.0; + return Speed <= 100.0 ? 0.15 * Speed : 0.25 * Speed; +} + +namespace +{ + +double constexpr kPi{3.14159265358979323846}; +double constexpr kHalfPi{0.5 * kPi}; +double constexpr kReverseNoInsert{106.5}; + +glm::dvec2 unit_chord(glm::dvec2 const &Heading, double const Angle) +{ + glm::dvec2 const left{-Heading.y, Heading.x}; + return Heading * std::sin(std::abs(Angle)) + left * ((Angle > 0.0 ? 1.0 : -1.0) * (1.0 - std::cos(Angle))); +} + +struct reverse_fit +{ + double radius{0.0}; + double first{0.0}; + double second{0.0}; +}; + +std::optional fit_reverse(glm::dvec2 const &Offset, glm::dvec2 const &Tangent, double const Turn, double const Insert) +{ + auto const residual = [&](double const First, double &Radius) { + auto const heading{turned(Tangent, First)}; + auto const chord{unit_chord(Tangent, First) + unit_chord(heading, Turn - First)}; + auto const rest{Offset - heading * Insert}; + Radius = glm::dot(chord, rest) / std::max(glm::dot(chord, chord), 1e-12); + return cross(chord, rest); + }; + auto const usable = [&](double const First) { return std::abs(First) > 1e-4 && std::abs(Turn - First) > 1e-4 && (First > 0.0) != (Turn - First > 0.0) && std::abs(Turn - First) <= kHalfPi; }; + std::optional best; + int constexpr steps{2000}; + double radius{0.0}; + auto previous{-kHalfPi}; + auto before{residual(previous, radius)}; + for (int i = 1; i <= steps; ++i) + { + auto const first{-kHalfPi + kPi * i / steps}; + auto const now{residual(first, radius)}; + if (usable(previous) && usable(first) && (before > 0.0) != (now > 0.0)) + { + auto low{previous}; + auto high{first}; + auto lowvalue{before}; + for (int k = 0; k < 60; ++k) + { + auto const middle{0.5 * (low + high)}; + auto const value{residual(middle, radius)}; + if ((value > 0.0) == (lowvalue > 0.0)) + { + low = middle; + lowvalue = value; + } + else + high = middle; + } + auto const root{0.5 * (low + high)}; + residual(root, radius); + if (radius >= 1.0 && (false == best.has_value() || radius > best->radius)) + best = reverse_fit{radius, root, Turn - root}; + } + previous = first; + before = now; + } + return best; +} + +} // namespace + +editor_track::branch_link editor_track::link_branch(branch_end const &From, double const Radius, branch_end const &To, double const Kink, double const Insert) { branch_link result; - if (glm::length(From.direction) < 1e-9 || glm::length(To.direction) < 1e-9 || glm::length(plan_of(Slide)) < 1e-9) + if (glm::length(From.direction) < 1e-9 || glm::length(To.direction) < 1e-9 || Radius < 1.0) return result; - auto const slide{glm::normalize(plan_of(Slide))}; - auto const rise{Slide.y / glm::length(plan_of(Slide))}; auto const tangent{glm::normalize(From.direction)}; auto const into{glm::normalize(To.direction)}; auto const turn{signed_angle(tangent, into)}; auto const start{plan_of(From.position)}; auto const target{plan_of(To.position)}; - auto const across{cross(into, slide)}; + auto const offset{target - start}; if (std::abs(turn) > glm::radians(170.0)) return result; - std::vector arc; - glm::dvec2 chord{0.0}; - if (std::abs(turn) > Kink) - { - if (Radius < 1.0) - return result; - arc = arc_pieces(glm::dvec3{0.0}, tangent, 0.0, Radius, std::abs(turn), turn > 0.0 ? 1 : -1); - chord = plan_of(arc.back().points[segment_data::point::end]); - result.radius = Radius; - } - else if (std::abs(across) < 1e-6) - return result; - if (false == arc.empty() && std::abs(across) < std::sin(glm::radians(1.0))) - { - auto const gap{target - start - chord}; - auto const determinant{cross(tangent, slide)}; - if (std::abs(determinant) < 1e-6) - return result; - result.before = cross(gap, slide) / determinant; - result.shift = cross(tangent, gap) / determinant; - if (result.before < -0.01) + std::vector> arcs; + auto const reverse = [&]() { + auto fit{fit_reverse(offset, tangent, turn, 0.0)}; + if (false == fit.has_value() || 2.0 / fit->radius > 1.0 / kReverseNoInsert) { - result.shortfall = -result.before * std::abs(std::sin(turn)); - return result; + fit = fit_reverse(offset, tangent, turn, Insert); + result.insert = Insert; } - result.before = std::max(0.0, result.before); + if (false == fit.has_value()) + return false; + result.reverse = true; + result.radius = fit->radius; + result.before = 0.0; + result.after = 0.0; + result.shortfall = 0.0; + arcs = {{std::abs(fit->first), fit->first > 0.0 ? 1 : -1}, {std::abs(fit->second), fit->second > 0.0 ? 1 : -1}}; + return true; + }; + if (std::abs(turn) <= Kink) + { + auto const lateral{cross(tangent, offset)}; + result.after = glm::dot(tangent, offset); + if (std::abs(lateral) >= 0.005 && false == reverse()) + return result; } else { - result.shift = cross(into, target - start - chord) / across; - result.after = glm::dot(target - (start + slide * result.shift + chord), into); - if (result.after < -0.01) + auto const determinant{cross(tangent, into)}; + auto const reach{cross(offset, into) / determinant}; + auto const tangentlength{Radius * std::tan(std::abs(turn) * 0.5)}; + result.before = reach - tangentlength; + result.after = cross(tangent, offset) / determinant - tangentlength; + if (reach > 0.0 && result.before >= -0.01) { - result.trim = -result.after; - result.after = 0.0; + result.before = std::max(0.0, result.before); + arcs = {{std::abs(turn), turn > 0.0 ? 1 : -1}}; + result.radius = Radius; + } + else if (false == reverse()) + { + result.shortfall = std::max(0.01, -result.before); + return result; } } - auto const origin{glm::dvec3{start.x, From.position.y, start.y} + glm::dvec3{slide.x, rise, slide.y} * result.shift}; + if (result.after < -0.01) + { + result.trim = -result.after; + result.after = 0.0; + } auto const line = [](glm::dvec2 const &A, glm::dvec2 const &B) { segment_data path; path.points[segment_data::point::start] = {A.x, 0.0, A.y}; path.points[segment_data::point::end] = {B.x, 0.0, B.y}; return path; }; - auto cursor{plan_of(origin)}; + auto cursor{start}; + auto heading{tangent}; if (result.before >= 0.01) { result.pieces.push_back(line(cursor, cursor + tangent * result.before)); cursor += tangent * result.before; } - for (auto piece : arc) + for (std::size_t i = 0; i < arcs.size(); ++i) { - for (auto const index : {segment_data::point::start, segment_data::point::end}) - piece.points[index] += glm::dvec3{cursor.x, 0.0, cursor.y}; - result.pieces.push_back(piece); + auto const [angle, side]{arcs[i]}; + if (i > 0 && result.insert >= 0.01) + { + result.pieces.push_back(line(cursor, cursor + heading * result.insert)); + cursor += heading * result.insert; + } + auto const pieces{arc_pieces({cursor.x, 0.0, cursor.y}, heading, 0.0, result.radius, angle, side)}; + result.pieces.insert(result.pieces.end(), pieces.begin(), pieces.end()); + cursor = plan_of(pieces.back().points[segment_data::point::end]); + heading = turned(heading, side * angle); } - cursor += chord; if (result.after >= 0.01) result.pieces.push_back(line(cursor, target)); else if (result.trim == 0.0 && false == result.pieces.empty()) @@ -457,7 +550,7 @@ editor_track::branch_link editor_track::link_branch(branch_end const &From, glm: if (false == result.pieces.empty()) { auto const height1{result.trim > 0.0 ? To.position.y + To.grade * result.trim : To.position.y}; - ground_follow::follow(result.pieces, {origin.y, From.grade, height1, To.grade}, [](std::vector const &Points, std::vector const &) { return std::vector(Points.size(), std::numeric_limits::quiet_NaN()); }); + ground_follow::follow(result.pieces, {From.position.y, From.grade, height1, To.grade}, [](std::vector const &Points, std::vector const &) { return std::vector(Points.size(), std::numeric_limits::quiet_NaN()); }); std::vector lengths; double total{0.0}; for (auto const &piece : result.pieces) diff --git a/lang/editor_pl.po b/lang/editor_pl.po index f90351e8..dd3af8ca 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5455,21 +5455,17 @@ msgstr "Zwrotnica %s: napęd %s, teraz po lewej" msgid "Switch %s without a drive now" msgstr "Zwrotnica %s jest teraz bez napędu" - msgid "The branch leads into %s with an R %.0f m curve" msgstr "Odgałęzienie wchodzi w %s łukiem R %.0f m" msgid "The branch leads straight into %s" msgstr "Odgałęzienie wchodzi w %s prostą" -msgid ", the switch moved %.2f m" -msgstr ", rozjazd przesunięty o %.2f m" - msgid ", %s shortened %.2f m" msgstr ", %s skrócony o %.2f m" -msgid "The branch can't lead into %s: the tracks are %.2f m too close for this switch with its curve" -msgstr "Odgałęzienie nie wejdzie w %s: tory są o %.2f m za blisko dla tego rozjazdu z łukiem" +msgid "The branch can't lead into %s: the R %.0f m curve needs %.2f m more room" +msgstr "Odgałęzienie nie wejdzie w %s: łuk R %.0f m potrzebuje %.2f m więcej miejsca" msgid "The branch can't lead into %s" msgstr "Odgałęzienie nie wejdzie w %s" @@ -5477,8 +5473,11 @@ msgstr "Odgałęzienie nie wejdzie w %s" msgid "%s would have to be shortened by %.2f m, it's too short or not straight" msgstr "%s trzeba by skrócić o %.2f m, a jest za krótki albo nie jest prosty" -msgid "The switch would have to move %.1f m, off the straight it stands on" -msgstr "Rozjazd musiałby się przesunąć o %.1f m, poza prostą, na której stoi" +msgid "The branch leads into %s with reverse curves R %.0f m" +msgstr "Odgałęzienie wchodzi w %s łukami odwrotnymi R %.0f m" -msgid "The switch would have to move %.1f m, there's no plain track for it there" -msgstr "Rozjazd musiałby się przesunąć o %.1f m, a tam nie ma zwykłego toru" +msgid ", a straight of %.1f m between them" +msgstr ", między nimi prosta %.1f m" + +msgid ", sharper than the switch (R %.0f m)" +msgstr ", ostrzej niż rozjazd (R %.0f m)"