diff --git a/application/editormode.h b/application/editormode.h index d9ff9643..717725a5 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1072,6 +1072,10 @@ 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; // lays new track through the clicked points, with no path needed to start from @@ -1124,6 +1128,22 @@ class editor_mode : public application_mode, private editor_track::observer std::vector paths; }; bool plan_crossover(crossover_plan &Plan) const; + struct switch_link + { + editor_track::snap_target target; + editor_track::branch_link link; + double along{0.0}; + int forward{1}; + int side{1}; + std::vector paths; + std::string reason; + }; + bool plan_switch_link(switch_link &Plan) const; + void insert_linked_switch(switch_link const &Plan); + 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); 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 3f2783ae..ad5a84ce 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -1460,6 +1460,104 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com WriteLog("SELFTEST " + command + ": " + tool.status + " " + m_straights.status); return selftest_result::handled; } + if (command == "switchlink" || command == "branchlink") + { + std::string label; + glm::dvec3 point{0.0}; + glm::dvec3 target{0.0}; + if (command == "switchlink") + words >> label; + 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, + Link.shortfall, Link.pieces.size(), Reason.empty() ? "" : (", " + Reason).c_str())); + for (auto const &piece : Link.pieces) + { + auto const &a{piece.points[segment_data::point::start]}; + auto const &b{piece.points[segment_data::point::end]}; + WriteLog(format("SELFTEST %s piece: %.3f %.3f %.3f -> %.3f %.3f %.3f, L %.3f m, R %.1f m", command.c_str(), a.x, a.y, a.z, b.x, b.y, b.z, geometry::bezier{piece}.plan_length(), static_cast(piece.radius))); + } + }; + if (command == "switchlink") + { + show_track_tab(track_tab::turnout); + auto &tool{m_switch}; + tool.templates = editor_track::standard_switch_templates(); + tool.collected = true; + tool.armed = -1; + for (int i = 0; i < static_cast(tool.templates.size()); ++i) + if (tool.templates[i].label.rfind(label, 0) == 0) + { + tool.armed = i; + break; + } + 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; + } + } + if (tool.armed < 0 || nearest == nullptr) + { + WriteLog("SELFTEST switchlink: no template " + label + " or no path"); + return selftest_result::handled; + } + m_node = nearest; + ui()->set_node(m_node); + m_cursor_override = point; + if (false == start_switch_placement()) + { + m_cursor_override.reset(); + WriteLog("SELFTEST switchlink: not started, " + tool.status); + return selftest_result::handled; + } + tool.mouse = glm::dvec3{target.x, tool.point.y, target.z}; + switch_link plan; + plan_switch_link(plan); + log_link(plan.link, plan.reason); + finish_switch_placement(); + m_cursor_override.reset(); + WriteLog("SELFTEST switchlink: " + tool.status); + return selftest_result::handled; + } + TTrack *own{nullptr}; + for (auto *track : simulation::Paths.sequence()) + if (track != nullptr && false == track->m_editorremoved && track->eType == tt_Switch && track->m_paths.size() == 2 && geometry::plan_distance(track->m_paths[1].points[segment_data::point::end], point) < 3.0) + own = track; + if (own == nullptr) + { + WriteLog("SELFTEST branchlink: no switch branch end there"); + return selftest_result::handled; + } + editor_track::snap_target end; + end.track = own; + end.point = {1, editor_track::point_kind::end}; + end.position = own->m_paths[1].points[segment_data::point::end]; + if (false == start_extend(end)) + { + WriteLog("SELFTEST branchlink: the branch end isn't free"); + return selftest_result::handled; + } + m_extend.pressed = {-10000.f, -10000.f}; + m_extend.mouse = glm::dvec3{target.x, m_extend.point.y, target.z}; + m_extend.snap = editor_track::find_free_end(own, own->iCategoryFlag & 15, m_extend.mouse, 3.0, {own}); + editor_track::branch_link link; + std::string reason; + plan_branch_link(link, reason); + log_link(link, reason); + finish_extend(); + WriteLog(format("SELFTEST branchlink: switch now %.3f %.3f -> %.3f %.3f", own->m_paths[0].points[segment_data::point::start].x, own->m_paths[0].points[segment_data::point::start].z, own->m_paths[0].points[segment_data::point::end].x, + own->m_paths[0].points[segment_data::point::end].z)); + return selftest_result::handled; + } if (command == "switchslide" || command == "switchreplace") { std::string name; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 941180fd..7a208895 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -105,6 +105,28 @@ double const kJointRange{400.0}; // the ends of the paths are drawn for the trac ImU32 const kJointColor{IM_COL32(255, 255, 255, 200)}; int const kRailCategory{1}; +editor_track::branch_end branch_start(segment_data const &Diverging) +{ + auto const tangent{bezier{Diverging}.first(1.0)}; + return {Diverging.points[segment_data::point::end], plan_of(tangent), grade_of(tangent), static_cast(Diverging.rolls[1])}; +} + +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) +{ + 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); + if (Link.trim > 0.0) + text += format(STR_C(", %s shortened %.2f m"), name.c_str(), Link.trim); + return text; +} + std::optional eased_join(alignment::join_request const &Request, editor_track::easing const &In, editor_track::easing const &Out) { if (In.pieces.empty() && Out.pieces.empty()) @@ -3724,11 +3746,17 @@ std::string editor_mode::track_readout() const } editor_track::snap_target target; auto const snapped{extend_snap(target) && target.track != nullptr}; + editor_track::branch_link link; + std::string reason; + if (plan_branch_link(link, reason)) + return link_summary(target, link); 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)}; if (snapped) text += "\njoins " + name(target.track); + if (false == reason.empty()) + text += "\n" + reason; return text + "\n" + track_shaping(snapped); } if (m_lay.active) @@ -4063,6 +4091,20 @@ bool editor_mode::start_extend(editor_track::snap_target const &End) m_extend.origin = 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; } @@ -4301,6 +4343,13 @@ std::vector editor_mode::extend_pieces() const editor_track::snap_target target; if (extend_snap(target)) { + editor_track::branch_link link; + std::string reason; + if (plan_branch_link(link, reason)) + { + m_join_last = {}; + return link.pieces; + } double length; std::string error; return join_pieces(tool.track, {tool.path, tool.atend ? editor_track::point_kind::end : editor_track::point_kind::start}, tool.point, tool.direction, target, length, error); @@ -4313,10 +4362,9 @@ std::vector editor_mode::extend_pieces() const auto const begin{plan_of(curve.first(0.0))}; auto const finish{plan_of(curve.first(1.0))}; auto const turn{glm::length(begin) > 1e-9 && glm::length(finish) > 1e-9 ? geometry::signed_angle(begin, finish) : 0.0}; - if (std::abs(turn) > 1e-4) + if (auto const radius{editor_track::branch_radius(diverging)}; radius > 0.0) { int const side{turn > 0.0 ? 1 : -1}; - auto const radius{curve.plan_length() / std::abs(turn)}; auto const &direction{tool.direction}; auto const centre{plan_of(tool.point) + glm::dvec2{-direction.y, direction.x} * (side * radius)}; auto const angle{geometry::signed_angle(plan_of(tool.point) - centre, plan_of(tool.mouse) - centre) * side}; @@ -4401,6 +4449,14 @@ void editor_mode::finish_extend() m_track_point = m_extend.origin.point; return; } + std::string reason; + if (editor_track::branch_link link; plan_branch_link(link, reason)) + { + finish_branch_link(link); + return; + } + if (false == reason.empty()) + ui()->set_status(reason, true); crossover_plan plan; editor_track::snap_target target; if (false == extend_snap(target) && plan_crossover(plan)) @@ -4924,6 +4980,11 @@ std::vector editor_mode::switch_preview() const auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1}; return curved_switch_paths(tool.templates[tool.armed], tool.frame, tool.along, forward, side); } + if (switch_link plan; plan_switch_link(plan)) + { + plan.paths.insert(plan.paths.end(), plan.link.pieces.begin(), plan.link.pieces.end()); + return plan.paths; + } auto const &line{tool.line}; glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z}; auto const forward{glm::dot(offset, line.direction) >= 0.0 ? 1 : -1}; @@ -5005,9 +5066,16 @@ bool editor_mode::start_switch_placement() void editor_mode::finish_switch_placement() { auto &tool{m_switch}; + switch_link plan; + auto const linked{plan_switch_link(plan)}; tool.placing = false; if (tool.armed < 0 || tool.armed >= static_cast(tool.templates.size())) return; + if (linked) + { + insert_linked_switch(plan); + return; + } if (tool.curved) { auto const sample{sample_at(tool.frame, tool.along)}; @@ -5023,6 +5091,8 @@ void editor_mode::finish_switch_placement() glm::dvec2 const direction{tool.line.direction * static_cast(forward)}; auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1}; insert_switch(tool.line, tool.along, forward, side); + if (false == plan.reason.empty()) + ui()->set_status(plan.reason, true); } void editor_mode::insert_switch(editor_track::straight const &Line, double const Along, int const Direction, int const Side) @@ -5276,6 +5346,11 @@ 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); } @@ -5292,8 +5367,19 @@ void editor_mode::draw_build_overlay() const } } if (m_switch.placing) + { for (auto const &piece : switch_preview()) drawpath(piece, overlay_color::marked); + switch_link plan; + auto const linked{plan_switch_link(plan)}; + ImVec2 screen; + if (plan.target.track != nullptr && projection.project(plan.target.position, screen)) + { + drawlist->AddCircle(screen, 14.0f, linked ? IM_COL32(255, 60, 255, 255) : overlay_color::invalid, 20, 3.0f); + if (false == linked && false == plan.reason.empty()) + drawlist->AddText({screen.x + 18.0f, screen.y - 8.0f}, overlay_color::invalid, plan.reason.c_str()); + } + } if (m_lay.active) { auto const &lay{m_lay}; @@ -5756,6 +5842,162 @@ bool editor_mode::plan_crossover(crossover_plan &Plan) const return true; } +std::string editor_mode::link_problem(editor_track::snap_target const &Target, editor_track::branch_link const &Link) const +{ + 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) : + format(STR_C("The branch can't lead into %s"), name.c_str()); + if (Link.trim > 0.0) + { + auto const &path{Target.track->m_paths[Target.point.path]}; + if (has_controls(path) || geometry::chord_length(path) < Link.trim + 1.0) + return format(STR_C("%s would have to be shortened by %.2f m, it's too short or not straight"), name.c_str(), Link.trim); + } + return {}; +} + +bool editor_mode::plan_switch_link(switch_link &Plan) const +{ + auto const &tool{m_switch}; + if (false == tool.placing || tool.curved || tool.armed < 0 || tool.armed >= static_cast(tool.templates.size()) || tool.line.tracks.empty() || tool.templates[tool.armed].double_slip) + return false; + auto const &shape{tool.templates[tool.armed]}; + auto const &line{tool.line}; + std::vector const exclude(line.tracks.begin(), line.tracks.end()); + auto const category{line.tracks.front()->iCategoryFlag & 15}; + auto const orient = [&](glm::dvec3 const &Toward) { + glm::dvec2 const offset{plan_of(Toward - tool.point)}; + Plan.forward = glm::dot(offset, line.direction) >= 0.0 ? 1 : -1; + Plan.side = cross(line.direction * static_cast(Plan.forward), offset) >= 0.0 ? 1 : -1; + }; + auto const place = [&](double const Along) { + auto const point{line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * Along}; + return editor_track::place_switch(shape, point, line.direction * static_cast(Plan.forward), Plan.side, line.grade * Plan.forward); + }; + auto const &io{ImGui::GetIO()}; + if (false == m_cursor_override.has_value()) + Plan.target = snap_free_end(tool.mouse, {io.MousePos.x, io.MousePos.y}, nullptr, category, exclude); + if (Plan.target.track == nullptr) + Plan.target = editor_track::find_free_end(nullptr, category, tool.mouse, kLaySnapRadius, exclude); + if (Plan.target.track != nullptr) + orient(Plan.target.position); + else + { + orient(tool.mouse); + auto const paths{place(tool.along)}; + if (paths.size() < 2) + return false; + Plan.target = editor_track::find_free_end(nullptr, category, paths[1].points[segment_data::point::end], kLaySnapRadius, exclude); + if (Plan.target.track == nullptr) + return false; + } + auto const paths{place(tool.along)}; + if (paths.size() < 2) + 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; + } + Plan.paths = place(Plan.along); + return true; +} + +void editor_mode::insert_linked_switch(switch_link const &Plan) +{ + std::vector> states; + std::vector created; + std::vector removed; + 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); + 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); + ui()->set_status(m_switch.status, false); +} + +bool editor_mode::plan_branch_link(editor_track::branch_link &Link, std::string &Reason) const +{ + auto const &tool{m_extend}; + editor_track::snap_target target; + Reason.clear(); + if (false == extend_snap(target) || tool.track == nullptr || tool.track->eType != tt_Switch || tool.track->m_paths.size() != 2 || tool.path != 1 || false == tool.atend) + 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)); + 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; +} + +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()}; + 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); +} + +std::vector editor_mode::link_tracks(editor_track::snap_target const &Target, editor_track::branch_link const &Link, std::vector> &States, std::vector &Created) +{ + auto *target{Target.track}; + if (Link.trim > 0.0) + { + States.emplace_back(target, editor_track::capture(*target)); + auto const run{glm::length(plan_of(Target.direction))}; + editor_track::move_point(*target, Target.point, Target.position + Target.direction * (Link.trim / std::max(run, 1e-6))); + editor_track::commit({target}); + } + std::vector pieces; + for (auto const &piece : Link.pieces) + pieces.push_back(editor_track::create_path(*target, piece)); + editor_track::commit(pieces); + digitize_drawn(pieces); + Created.insert(Created.end(), pieces.begin(), pieces.end()); + return pieces; +} + std::vector editor_mode::build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, double const Radius, TTrack &Style, std::string const &Type) { auto const at = [](glm::dvec2 const &Planar, double const Y) { return glm::dvec3{Planar.x, Y, Planar.y}; }; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index d0fda4eb..b8a4e710 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -204,6 +204,27 @@ class editor_track static std::optional main_axis(TTrack const &Switch); static std::vector slid(TTrack const &Switch, double const Distance); + struct branch_end + { + glm::dvec3 position{0.0}; + glm::dvec2 direction{0.0, 1.0}; + double grade{0.0}; + double roll{0.0}; + }; + 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}; + 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); + struct snap_target { TTrack *track{nullptr}; diff --git a/editor/editorTrackShape.cpp b/editor/editorTrackShape.cpp index b3f2c3b4..8b50984f 100644 --- a/editor/editorTrackShape.cpp +++ b/editor/editorTrackShape.cpp @@ -10,12 +10,14 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "editor/editorTrack.hpp" #include "editor/editorGeometry.hpp" +#include "editor/editorGroundFollow.hpp" #include "world/Track.h" #include "utilities/Globals.h" #include #include +#include namespace { @@ -363,6 +365,116 @@ std::vector editor_track::slid(TTrack const &Switch, double const return paths; } +double editor_track::branch_radius(segment_data const &Diverging) +{ + if (std::abs(Diverging.radius) > 0.5f) + return std::abs(Diverging.radius); + bezier const curve{Diverging}; + 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(signed_angle(begin, finish))}; + 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) +{ + branch_link result; + if (glm::length(From.direction) < 1e-9 || glm::length(To.direction) < 1e-9 || glm::length(plan_of(Slide)) < 1e-9) + 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)}; + 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) + { + result.shortfall = -result.before * std::abs(std::sin(turn)); + return result; + } + result.before = std::max(0.0, result.before); + } + 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) + { + result.trim = -result.after; + result.after = 0.0; + } + } + auto const origin{glm::dvec3{start.x, From.position.y, start.y} + glm::dvec3{slide.x, rise, slide.y} * result.shift}; + 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)}; + if (result.before >= 0.01) + { + result.pieces.push_back(line(cursor, cursor + tangent * result.before)); + cursor += tangent * result.before; + } + for (auto piece : arc) + { + 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); + } + cursor += chord; + if (result.after >= 0.01) + result.pieces.push_back(line(cursor, target)); + else if (result.trim == 0.0 && false == result.pieces.empty()) + result.pieces.back().points[segment_data::point::end] = {target.x, 0.0, target.y}; + 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()); }); + std::vector lengths; + double total{0.0}; + for (auto const &piece : result.pieces) + total += lengths.emplace_back(bezier{piece}.plan_length()); + double chainage{0.0}; + for (std::size_t i = 0; i < result.pieces.size(); ++i) + { + auto const from{From.roll + (To.roll - From.roll) * chainage / std::max(total, 1e-6)}; + chainage += lengths[i]; + auto const to{From.roll + (To.roll - From.roll) * chainage / std::max(total, 1e-6)}; + result.pieces[i].rolls = {static_cast(from), static_cast(to)}; + } + } + result.valid = true; + return result; +} + void editor_track::edit::capture(TTrack &Track) { if (std::find(changed.begin(), changed.end(), &Track) != changed.end()) diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 48a303ad..f90351e8 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5455,3 +5455,30 @@ 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" +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 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"