diff --git a/application/editormode.cpp b/application/editormode.cpp index ac6e48a1..5248a03b 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1418,6 +1418,8 @@ bool editor_mode::update() finish_track_box(); update_track_hover(); point_drag_update(); + switch_slide_update(); + draw_switch_slide(); if (m_track_tab == track_tab::turntable) draw_turntable_overlay(); if (m_track_tab == track_tab::signals) @@ -5700,6 +5702,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const turntable_finish_placement(); if (m_signal.placing || m_signal.moving) signal_release(); + if (m_switch.slide.active) + switch_slide_finish(); } m_dragging = false; diff --git a/application/editormode.h b/application/editormode.h index b4abd0c3..a888e5ce 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -938,8 +938,29 @@ class editor_mode : public application_mode, private editor_track::observer bool drives_scanned{false}; int drive{-1}; int drive_side{0}; + struct slide_state + { + bool active{false}; + TTrack *track{nullptr}; + glm::vec2 pressed{0.f}; + double grip{0.0}; + double low{0.0}; + double high{0.0}; + double distance{0.0}; + std::vector joints; + }; + slide_state slide; + double slide_typed{0.0}; }; switch_tool m_switch; + bool switch_room(TTrack &Track, double &Low, double &High, std::string &Reason); + bool move_switch_paths(TTrack &Track, std::vector const &Paths, std::string &Status); + bool slide_switch(TTrack &Track, double const Distance); + bool switch_slides(TTrack const &Track) const; + void switch_slide_start(TTrack &Track); + void switch_slide_update(); + void switch_slide_finish(); + void draw_switch_slide() const; void scan_switch_drives(); std::string switch_name_for_new() const; bool place_switch_drive(TTrack &Switch); @@ -1182,7 +1203,7 @@ class editor_mode : public application_mode, private editor_track::observer void draw_track_hover() const; struct track_intent { - enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move }; + enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide }; kind what{kind::none}; TTrack *track{nullptr}; int path{0}; diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 25cd7ba2..58d888a3 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -1244,6 +1244,75 @@ void editor_mode::selftest_step() while (std::getline(lines, line)) WriteLog("SELFTEST btf kept: " + line); } + else if (command == "switchslide" || command == "switchreplace") + { + std::string name; + words >> name; + auto *track{simulation::Paths.find(name)}; + if (track == nullptr || track->eType != tt_Switch || track->m_paths.size() < 2) + { + WriteLog("SELFTEST " + command + ": no switch " + name); + continue; + } + if (command == "switchslide") + { + double distance{0.0}; + words >> distance; + double low{0.0}, high{0.0}; + std::string reason; + auto const room{switch_room(*track, low, high, reason)}; + auto const done{slide_switch(*track, distance)}; + WriteLog(format("SELFTEST switchslide %s %.3f: room %d %.3f .. %.3f, done %d, %s", name.c_str(), distance, room ? 1 : 0, low, high, done ? 1 : 0, m_switch.status.c_str())); + continue; + } + std::string label; + int side{1}; + words >> label >> side; + std::replace(label.begin(), label.end(), '_', ' '); + auto const templates{editor_track::standard_switch_templates()}; + auto const found{std::find_if(templates.begin(), templates.end(), [&](editor_track::switch_template const &Template) { return Template.label.rfind(label, 0) == 0; })}; + if (found == templates.end()) + { + WriteLog("SELFTEST switchreplace: no template " + label); + continue; + } + auto const &main{track->m_paths[0]}; + auto const origin{main.points[segment_data::point::start]}; + auto const chord{geometry::plan_of(main.points[segment_data::point::end] - origin)}; + auto const length{glm::length(chord)}; + auto paths{editor_track::place_switch(*found, origin, chord / length, side, (main.points[segment_data::point::end].y - origin.y) / length)}; + std::string status; + auto const done{paths.size() >= 2 && move_switch_paths(*track, paths, status)}; + if (done) + { + record_switch(*track, *found); + records_to_history(); + straight_refresh(); + } + WriteLog(format("SELFTEST switchreplace %s %s: done %d, %s", name.c_str(), found->label.c_str(), done ? 1 : 0, status.c_str())); + } + else if (command == "pathsnear") + { + glm::dvec3 point{0.0}; + double radius{100.0}; + words >> point.x >> point.z >> radius; + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || track->m_paths.empty()) + continue; + for (std::size_t i = 0; i < track->m_paths.size(); ++i) + { + auto const &path{track->m_paths[i]}; + auto const &a{path.points[segment_data::point::start]}; + auto const &b{path.points[segment_data::point::end]}; + if (geometry::plan_distance(a, point) > radius && geometry::plan_distance(b, point) > radius) + continue; + auto const &c1{path.points[segment_data::point::control1]}; + auto const &c2{path.points[segment_data::point::control2]}; + WriteLog(format("SELFTEST path %s/%zu %s: %.3f %.3f -> %.3f %.3f, c1 %.3f %.3f, c2 %.3f %.3f, %.3f m", track->name().c_str(), i, track->eType == tt_Switch ? "switch" : "normal", a.x, a.z, b.x, b.z, c1.x, c1.z, c2.x, c2.z, geometry::bezier{path}.plan_length())); + } + } + } else if (command == "switches") { for (auto const &entry : switch_store::records()) diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index b18fae1e..09137f45 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1979,36 +1979,17 @@ void editor_mode::render_turnout_ui() for (int i = 0; i < 2; ++i) if (paths[i].rolls[0] == 0.f && paths[i].rolls[1] == 0.f) paths[i].rolls = track->m_paths[i].rolls; - auto const joints{editor_track::joints(*track)}; - std::vector> states{{track, editor_track::capture(*track)}}; - track->m_paths.assign(paths.begin(), paths.begin() + 2); - // the neighbours follow the ends which moved a little, those which went far are left loose - std::vector following; - std::vector changed{track}; - std::string loose; - for (auto const &joint : joints) + std::string status; + if (false == move_switch_paths(*track, paths, status)) { - auto const &theirs{joint.other->m_paths[joint.theirs.path]}; - auto const shift{glm::distance(editor_track::point_position(*track, joint.own), editor_track::point_position(*joint.other, joint.theirs))}; - if (shift > 0.3 * bezier{theirs}.plan_length()) - { - loose += (loose.empty() ? "" : ", ") + (joint.other->name().empty() ? std::string{"(noname)"} : joint.other->name()); - continue; - } - following.push_back(joint); - if (std::find(changed.begin(), changed.end(), joint.other) == changed.end()) - { - changed.push_back(joint.other); - states.emplace_back(joint.other, editor_track::capture(*joint.other)); - } + ui()->set_status(status, true); + return; } - push_track_snapshot(std::move(states)); - editor_track::follow(*track, following); - editor_track::commit(changed); record_switch(*track, Template); records_to_history(); - if (false == loose.empty()) - ui()->set_status(STR_C("Too far from the new ends of the switch, left unjoined: ") + loose, true); + straight_refresh(); + if (false == status.empty()) + ui()->set_status(status, true); }; int const side{offset > 0.0 ? 1 : -1}; if (ImGui::Button(STR_C("Replace the geometry with the template"))) @@ -2034,6 +2015,17 @@ void editor_mode::render_turnout_ui() replace(own, -side); } item_tooltip("Mirrors the switch: the diverging track goes to the other side"); + if (double low, high; switch_slides(*track) && switch_room(*track, low, high, m_switch.status)) + { + ImGui::PushItemWidth(100.0f); + ImGui::InputDouble("##switchslide", &tool.slide_typed, 0.0, 0.0, "%.2f m"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (ImGui::Button(STR_C("Slide along the straight"))) + slide_switch(*track, std::clamp(tool.slide_typed, low, high - length)); + item_tooltip("Moves the switch along its straight by the distance typed, + towards the frog; the straight paths on both sides get shorter or longer. Dragging the switch with the mouse does the same"); + ImGui::TextDisabled(STR_C("Room on the straight: %.2f m back, %.2f m ahead"), -low, high - length); + } ImGui::Separator(); render_path_parameters(*track); } @@ -6789,6 +6781,334 @@ bool editor_mode::cut_straight(editor_track::straight const &Line, double const return true; } +bool editor_mode::switch_room(TTrack &Track, double &Low, double &High, std::string &Reason) +{ + Low = 0.0; + High = 0.0; + if (Track.eType != tt_Switch || Track.m_paths.size() < 2) + return false; + auto const &main{Track.m_paths.front()}; + if (main.points[segment_data::point::control1] != glm::dvec3{} || main.points[segment_data::point::control2] != glm::dvec3{}) + return false; + auto const origin{main.points[segment_data::point::start]}; + auto const axis{plan_of(main.points[segment_data::point::end] - origin)}; + auto const length{glm::length(axis)}; + if (length < 1e-3) + return false; + auto const direction{axis / length}; + auto const line{editor_track::find_straight(Track, m_straights.tolerance)}; + if (false == tracks_editable(line.tracks, Reason)) + return false; + std::vector> spans; + for (std::size_t i = 0; i < line.tracks.size(); ++i) + { + if (line.tracks[i] == &Track) + continue; + auto const &path{line.tracks[i]->m_paths[line.paths[i]]}; + auto const a{glm::dot(plan_of(path.points[segment_data::point::start] - origin), direction)}; + auto const b{glm::dot(plan_of(path.points[segment_data::point::end] - origin), direction)}; + spans.emplace_back(std::min(a, b), std::max(a, b), line.tracks[i]->eType == tt_Normal); + } + std::sort(spans.begin(), spans.end()); + double low{0.0}, high{length}; + for (auto const &[from, to, plain] : spans) + if (from > length - 0.01) + { + if (false == plain || from > high + 0.01) + break; + high = std::max(high, to); + } + for (auto span{spans.rbegin()}; span != spans.rend(); ++span) + { + auto const &[from, to, plain] = *span; + if (to < 0.01) + { + if (false == plain || to < low - 0.01) + break; + low = std::min(low, from); + } + } + Low = low; + High = high; + return true; +} + +bool editor_mode::move_switch_paths(TTrack &Track, std::vector const &Paths, std::string &Status) +{ + if (Paths.size() < 2 || Track.m_paths.size() < 2) + return false; + auto const old{Track.m_paths}; + auto const &main{old.front()}; + auto const origin{main.points[segment_data::point::start]}; + auto const axis{plan_of(main.points[segment_data::point::end] - origin)}; + auto const length{glm::length(axis)}; + if (length < 1e-3) + return false; + auto const direction{axis / length}; + auto const along = [&](glm::dvec3 const &Point) { return glm::dot(plan_of(Point - origin), direction); }; + auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(cross(direction, plan_of(Point - origin))); }; + double low{0.0}, high{0.0}; + std::string reason; + bool const online{switch_room(Track, low, high, reason)}; + if (false == online && false == reason.empty()) + { + Status = reason; + return false; + } + struct end_move + { + glm::dvec3 from; + glm::dvec3 to; + double shift; + }; + std::vector ends; + for (auto const index : {segment_data::point::start, segment_data::point::end}) + { + auto const from{main.points[index]}; + auto const to{Paths.front().points[index]}; + if (false == online || glm::distance(from, to) < 0.005 || lateral(to) > m_straights.tolerance.offset) + continue; + auto const target{along(to)}; + if (target < low - 0.01 || target > high + 0.01) + { + Status = STR_C("The switch would run into another switch or past the end of the straight"); + return false; + } + ends.push_back({from, to, index == segment_data::point::start ? along(from) - target : target - along(from)}); + } + std::stable_sort(ends.begin(), ends.end(), [](end_move const &A, end_move const &B) { return A.shift > B.shift; }); + auto const joints{editor_track::joints(Track)}; + std::vector positions; + for (auto const &joint : joints) + positions.push_back(editor_track::point_position(Track, joint.own)); + std::vector handled(joints.size(), false); + std::vector> states{{&Track, editor_track::capture(Track)}}; + std::vector created; + std::vector removed; + std::vector changed{&Track}; + auto const line{online ? editor_track::find_straight(Track, m_straights.tolerance) : editor_track::straight{}}; + for (auto const &end : ends) + { + for (std::size_t i = 0; i < joints.size(); ++i) + if (glm::distance(positions[i], end.from) < geometry::same_point) + handled[i] = true; + if (end.shift > 0.0) + { + auto const a{line.along(end.from)}; + auto const b{line.along(end.to)}; + if (false == cut_straight(line, std::min(a, b), std::max(a, b), states, created, removed, nullptr)) + { + Status = STR_C("The switch would overlap another switch, move it onto plain track"); + return false; + } + continue; + } + TTrack const *style{nullptr}; + bool filler{false}; + for (auto const &connection : editor_track::connected_points(Track, end.from)) + { + auto *other{connection.first}; + if (other->m_editorremoved) + continue; + if (other->eType == tt_Normal && std::find(line.tracks.begin(), line.tracks.end(), other) != line.tracks.end()) + { + if (std::find(changed.begin(), changed.end(), other) == changed.end()) + { + states.emplace_back(other, editor_track::capture(*other)); + changed.push_back(other); + } + editor_track::move_point(*other, connection.second, end.to); + continue; + } + filler = true; + if (style == nullptr && other->eType == tt_Normal) + style = other; + } + if (filler) + { + segment_data path; + path.points[segment_data::point::start] = end.to; + path.points[segment_data::point::end] = end.from; + auto *piece{editor_track::create_path(style != nullptr ? *style : Track, path)}; + created.push_back(piece); + changed.push_back(piece); + } + } + Track.m_paths.assign(Paths.begin(), Paths.begin() + 2); + std::vector following; + std::string loose; + for (std::size_t i = 0; i < joints.size(); ++i) + { + auto const &joint{joints[i]}; + if (handled[i] || joint.other->m_editorremoved) + continue; + auto const &theirs{joint.other->m_paths[joint.theirs.path]}; + auto const shift{glm::distance(editor_track::point_position(Track, joint.own), editor_track::point_position(*joint.other, joint.theirs))}; + if (shift > 0.3 * bezier{theirs}.plan_length()) + { + loose += (loose.empty() ? "" : ", ") + (joint.other->name().empty() ? std::string{"(noname)"} : joint.other->name()); + continue; + } + following.push_back(joint); + if (std::find(changed.begin(), changed.end(), joint.other) == changed.end()) + { + changed.push_back(joint.other); + states.emplace_back(joint.other, editor_track::capture(*joint.other)); + } + } + editor_track::follow(Track, following); + editor_track::commit(changed); + push_track_snapshot(std::move(states), std::move(created), std::move(removed)); + if (false == loose.empty()) + Status = STR_C("Too far from the new ends of the switch, left unjoined: ") + loose; + return true; +} + +bool editor_mode::slide_switch(TTrack &Track, double const Distance) +{ + if (Track.m_paths.size() < 2 || std::abs(Distance) < 0.005) + return false; + auto const &main{Track.m_paths.front()}; + auto const run{main.points[segment_data::point::end] - main.points[segment_data::point::start]}; + auto const planlength{glm::length(plan_of(run))}; + if (planlength < 1e-3) + return false; + auto const shift{run * (Distance / planlength)}; + auto paths{Track.m_paths}; + for (auto &path : paths) + { + path.points[segment_data::point::start] += shift; + path.points[segment_data::point::end] += shift; + } + std::string status; + if (false == move_switch_paths(Track, paths, status)) + { + m_switch.status = status; + ui()->set_status(status, true); + return false; + } + switch_store::follow(); + records_to_history(); + straight_refresh(); + m_switch.status = status.empty() ? format(STR_C("Switch %s moved %.2f m along the straight"), Track.name().c_str(), Distance) : status; + ui()->set_status(m_switch.status, false == status.empty()); + return true; +} + +bool editor_mode::switch_slides(TTrack const &Track) const +{ + return Track.eType == tt_Switch && Track.m_paths.size() == 2 && false == Track.m_editorremoved && Track.m_paths.front().points[segment_data::point::control1] == glm::dvec3{} && Track.m_paths.front().points[segment_data::point::control2] == glm::dvec3{}; +} + +void editor_mode::switch_slide_start(TTrack &Track) +{ + auto &slide{m_switch.slide}; + slide = {}; + std::string reason; + if (false == switch_room(Track, slide.low, slide.high, reason)) + { + if (false == reason.empty()) + ui()->set_status(reason, true); + return; + } + auto const &main{Track.m_paths.front()}; + auto const origin{main.points[segment_data::point::start]}; + auto const direction{glm::normalize(plan_of(main.points[segment_data::point::end] - origin))}; + auto const &mouse{ImGui::GetIO().MousePos}; + slide.active = true; + slide.track = &Track; + slide.pressed = {mouse.x, mouse.y}; + slide.grip = glm::dot(plan_of(cursor_level(origin.y) - origin), direction); + auto const line{editor_track::find_straight(Track, m_straights.tolerance)}; + for (std::size_t i = 0; i < line.tracks.size(); ++i) + { + if (line.tracks[i] == &Track) + continue; + auto const &path{line.tracks[i]->m_paths[line.paths[i]]}; + for (auto const index : {segment_data::point::start, segment_data::point::end}) + slide.joints.push_back(glm::dot(plan_of(path.points[index] - origin), direction)); + } +} + +void editor_mode::switch_slide_update() +{ + auto &slide{m_switch.slide}; + if (false == slide.active) + return; + if (slide.track == nullptr || slide.track->m_editorremoved || slide.track->m_paths.size() < 2) + { + slide = {}; + return; + } + auto const &main{slide.track->m_paths.front()}; + auto const origin{main.points[segment_data::point::start]}; + auto const axis{plan_of(main.points[segment_data::point::end] - origin)}; + auto const length{glm::length(axis)}; + auto const direction{axis / length}; + auto distance{std::clamp(glm::dot(plan_of(cursor_level(origin.y) - origin), direction) - slide.grip, slide.low, slide.high - length)}; + double best{1.0}; + double snapped{distance}; + for (auto const joint : slide.joints) + for (auto const end : {0.0, length}) + if (auto const gap{std::abs(joint - (end + distance))}; gap < best && std::abs(joint - end) > 0.01) + { + best = gap; + snapped = joint - end; + } + slide.distance = std::clamp(snapped, slide.low, slide.high - length); +} + +void editor_mode::switch_slide_finish() +{ + auto &slide{m_switch.slide}; + if (false == slide.active) + return; + auto *track{slide.track}; + auto const distance{slide.distance}; + auto const &mouse{ImGui::GetIO().MousePos}; + auto const dragged{glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) > kDragPixels}; + slide = {}; + if (track != nullptr && false == track->m_editorremoved && dragged) + slide_switch(*track, distance); +} + +void editor_mode::draw_switch_slide() const +{ + auto const &slide{m_switch.slide}; + if (false == slide.active || slide.track == nullptr || slide.track->m_paths.size() < 2) + return; + auto const &mouse{ImGui::GetIO().MousePos}; + if (glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) <= kDragPixels) + return; + auto const &main{slide.track->m_paths.front()}; + auto const run{main.points[segment_data::point::end] - main.points[segment_data::point::start]}; + auto const shift{run * (slide.distance / std::max(1e-6, glm::length(plan_of(run))))}; + screen_projection const projection; + auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; + for (auto path : slide.track->m_paths) + { + path.points[segment_data::point::start] += shift; + path.points[segment_data::point::end] += shift; + bezier const curve{path}; + auto previous{curve.point(0.0)}; + for (int i = 1; i <= 24; ++i) + { + auto const next{curve.point(i / 24.0)}; + projection.line(drawlist, previous, next, overlay_color::marked, 3.0f); + previous = next; + } + } + ImVec2 at; + auto const start{main.points[segment_data::point::start] + shift}; + if (false == projection.project(start, at)) + return; + auto const km{alignment_store::chainage_at(start)}; + auto const label{km.has_value() ? format("%+.2f m km %.3f", slide.distance, *km / 1000.0) : format("%+.2f m", slide.distance)}; + auto const size{ImGui::CalcTextSize(label.c_str())}; + drawlist->AddRectFilled(ImVec2(at.x + 10.0f, at.y - 8.0f), ImVec2(at.x + 18.0f + size.x, at.y + 8.0f), IM_COL32(0, 0, 0, 180), 3.0f); + drawlist->AddText(ImVec2(at.x + 14.0f, at.y - 8.0f), overlay_color::highlight, label.c_str()); +} + bool editor_mode::place_switch_on_straight(editor_track::straight const &Line, editor_track::switch_template const &Shape, TTrack const *Style, double const Along, int const Direction, int const Side, bool const Snap, std::vector> &States, std::vector &Created, std::vector &Removed, std::string const &Name) { auto const &shape{Shape}; diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index c43ad29d..67b171ff 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -181,7 +181,7 @@ void editor_mode::draw_track_hover() const bool editor_mode::track_gesture_on() const { return mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_gizmo_using || m_extend.active || m_switch.placing || m_straights.dragging || m_straights.tool_mouse || - m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || ImGuizmo::IsUsing(); + m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || m_switch.slide.active || ImGuizmo::IsUsing(); } editor_mode::track_intent editor_mode::track_intent_at(int const Mods) @@ -343,6 +343,13 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods) return make(kind::straight_tool, m_straights.tool == 1 ? "Click: break the straight here" : "Click: start of the shift"); } } + if ((m_track_tab == track_tab::turnout || m_track_tab == track_tab::path) && m_hover.track != nullptr && switch_slides(*m_hover.track)) + { + intent.track = m_hover.track; + intent.path = m_hover.path; + intent.position = m_hover.point; + return make(kind::switch_slide, "Click: edit this switch, drag: slide it along the straight"); + } if (selected != nullptr && false == selected->m_paths.empty()) { glm::dvec3 point; @@ -453,6 +460,15 @@ void editor_mode::track_press(track_intent const &Intent) case kind::turntable_fit: turntable_press(Intent); return; case kind::signal_place: case kind::signal_move: signal_press(Intent); return; + case kind::switch_slide: + if (Intent.track != nullptr) + { + if (m_track_tab == track_tab::path) + m_track_set.clear(); + select_track(Intent.track); + switch_slide_start(*Intent.track); + } + return; case kind::select: case kind::none: { diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 4a29c3ba..b2069eaf 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -659,10 +659,23 @@ void editor_track::follow(TTrack const &Track, std::vector const &Joints) { if (joint.own.path >= static_cast(Track.m_paths.size()) || joint.theirs.path >= static_cast(joint.other->m_paths.size())) continue; - move_point(*joint.other, joint.theirs, point_position(Track, joint.own)); + auto &path{joint.other->m_paths[joint.theirs.path]}; + auto &control{path.points[joint.theirs.kind == point_kind::start ? segment_data::point::control1 : segment_data::point::control2]}; + bool const straight{path.points[segment_data::point::control1] == glm::dvec3{} && path.points[segment_data::point::control2] == glm::dvec3{}}; + auto const fixed{path.points[joint.theirs.kind == point_kind::start ? segment_data::point::end : segment_data::point::start]}; + auto const before{plan_of(path.points[point_index(joint.theirs.kind)] - fixed)}; + auto const target{point_position(Track, joint.own)}; + move_point(*joint.other, joint.theirs, target); auto const angle{signed_angle(joint.direction, end_direction(Track.m_paths[joint.own.path], joint.own.kind))}; + if (straight && std::abs(angle) < 1e-6 && glm::length(before) > 1e-6) + { + auto const after{plan_of(target - fixed)}; + auto const axis{glm::normalize(before)}; + if (std::abs(axis.x * after.y - axis.y * after.x) < 1e-3 && glm::dot(axis, after) > 0.0) + continue; + } auto const moved{turned(plan_of(joint.control), angle)}; - joint.other->m_paths[joint.theirs.path].points[joint.theirs.kind == point_kind::start ? segment_data::point::control1 : segment_data::point::control2] = {moved.x, joint.control.y, moved.y}; + control = {moved.x, joint.control.y, moved.y}; } } diff --git a/lang/editor_pl.po b/lang/editor_pl.po index dd9bee46..d98cdcb1 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5403,3 +5403,21 @@ msgstr "Przechyłka różni się od zapisanego projektu maks. o %.0f mm" msgid "Switch %s lost the shape of its type %s: Ctrl+Z takes it back" msgstr "Rozjazd %s stracił kształt swojego typu %s: Ctrl+Z cofa" + +msgid "The switch would run into another switch or past the end of the straight" +msgstr "Rozjazd wjechałby na inny rozjazd albo za koniec prostej" + +msgid "Switch %s moved %.2f m along the straight" +msgstr "Rozjazd %s przesunięty o %.2f m wzdłuż prostej" + +msgid "Slide along the straight" +msgstr "Przesuń wzdłuż prostej" + +msgid "Moves the switch along its straight by the distance typed, + towards the frog; the straight paths on both sides get shorter or longer. Dragging the switch with the mouse does the same" +msgstr "Przesuwa rozjazd wzdłuż jego prostej o wpisaną odległość, + w stronę krzyżownicy; proste po obu stronach skracają się albo wydłużają. To samo robi przeciągnięcie rozjazdu myszą" + +msgid "Room on the straight: %.2f m back, %.2f m ahead" +msgstr "Miejsce na prostej: %.2f m do tyłu, %.2f m do przodu" + +msgid "Click: edit this switch, drag: slide it along the straight" +msgstr "Klik: edytuj ten rozjazd, przeciągnij: przesuń go wzdłuż prostej"