diff --git a/application/editormode.cpp b/application/editormode.cpp index 56dabcb8..eb4ca8e3 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -3516,7 +3516,7 @@ void editor_mode::render_view_menu() toggle_ortho(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("The view straight down without perspective, as on a map; again for the camera as it was")); - ImGui::MenuItem("Show lanes", nullptr, &m_roadtool.lanes); + ImGui::MenuItem(STR_C("Show lanes"), nullptr, &m_roadtool.lanes); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", "Draws the lanes of the roads with the ways the vehicles take through the junctions"); if (ImGui::MenuItem(STR_C("Structure gauge"), nullptr, m_gauge.open)) @@ -4705,6 +4705,8 @@ void editor_mode::render_shortcuts_window() {"Ctrl+wheel", "Laying: radius"}, {"Shift+wheel", "Laying: arc then straight, straight then arc, arc, straight; at a free end: another way to join"}, {"0-9 while laying", "Type the length (or the radius) of the next piece, Enter lays it"}, + {"Alt held while laying", "The direction in steps of 15 degrees"}, + {"Shift+LMB on a track in L", "Choose it for a parallel track"}, {"Enter / Backspace", "Laying track: lay up to the cursor / take the last piece back"}, {"Del", "Delete the selected tracks"}}}, {"Roads", @@ -4742,7 +4744,7 @@ void editor_mode::render_shortcuts_window() void editor_mode::render_gizmo_options() { - if (ImGui::Button(Global.EditorOrtho ? "3D view (O)" : "Top view, orthographic (O)")) + if (ImGui::Button(Global.EditorOrtho ? STR_C("3D view (O)") : STR_C("Top view, orthographic (O)"))) toggle_ortho(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", Global.EditorOrtho ? STR_C("Wheel zooms. Hold the wheel and drag to pan.") : STR_C("Top-down orthographic view")); @@ -4785,7 +4787,7 @@ void editor_mode::render_gizmo_options() { auto const located = editor_includes::parameter_with_role(m_include.info, "pos.x") != 0 || editor_includes::parameter_with_role(m_include.info, "pos.y") != 0 || editor_includes::parameter_with_role(m_include.info, "pos.z") != 0; - ImGui::TextDisabled("%s", false == located ? "Include selected, no position parameters described" : + ImGui::TextDisabled("%s", false == located ? STR_C("Include selected, no position parameters described") : false == m_include.placement ? STR_C("Include selected, no gizmo under rotate or scale") : STR_C("Include selected, axes as its template allows")); } @@ -5399,11 +5401,11 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in case GLFW_KEY_F4: choose_work_area(work_area::roads); break; case GLFW_KEY_F5: choose_work_area(work_area::terrain); break; // the edit modes in the order of the toolbar; in the track mode the digits typed as a value were taken already - case GLFW_KEY_1: choose_edit_mode(nodebank_panel::MODIFY); break; - case GLFW_KEY_2: choose_edit_mode(nodebank_panel::ADD); break; - case GLFW_KEY_3: choose_edit_mode(nodebank_panel::BRUSH); break; - case GLFW_KEY_4: choose_edit_mode(nodebank_panel::FILL); break; - case GLFW_KEY_5: choose_edit_mode(nodebank_panel::COPY); break; + case GLFW_KEY_1: handled = ui()->mode() != nodebank_panel::TRACK; if (handled) choose_edit_mode(nodebank_panel::MODIFY); break; + case GLFW_KEY_2: handled = ui()->mode() != nodebank_panel::TRACK; if (handled) choose_edit_mode(nodebank_panel::ADD); break; + case GLFW_KEY_3: handled = ui()->mode() != nodebank_panel::TRACK; if (handled) choose_edit_mode(nodebank_panel::BRUSH); break; + case GLFW_KEY_4: handled = ui()->mode() != nodebank_panel::TRACK; if (handled) choose_edit_mode(nodebank_panel::FILL); break; + case GLFW_KEY_5: handled = ui()->mode() != nodebank_panel::TRACK; if (handled) choose_edit_mode(nodebank_panel::COPY); break; default: handled = false; break; } if (handled) @@ -5552,7 +5554,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in } if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK && selected_track() != nullptr) { - auto const under{track_under_cursor(selected_track())}; + auto const under{split_spot()}; if (under.track != nullptr) split_track_at(*under.track, under.point); } diff --git a/application/editormode.h b/application/editormode.h index d33480e6..2bc552f8 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1092,7 +1092,7 @@ class editor_mode : public application_mode, private editor_track::observer std::string preview_error; double preview_length{0.0}; double radius{500.0}; - bool transitions{true}; + bool transitions{false}; double piece_length{100.0}; bool copy_selected{false}; editor_track::path_style style; @@ -1106,6 +1106,9 @@ class editor_mode : public application_mode, private editor_track::observer std::vector chain; }; lay_tool m_lay; + bool m_norms_free{true}; + alignment::limits laying_norms() const; + glm::dvec3 lay_angle_snap(glm::dvec3 const &Mouse) const; void lay_after_history(bool const Undo); void lay_back(); void render_lay_ui(); @@ -1250,7 +1253,8 @@ class editor_mode : public application_mode, private editor_track::observer double since{0.0}; }; track_hover m_hover; - track_hover track_under_cursor(TTrack const *Only = nullptr) const; + track_hover track_under_cursor(TTrack const *Only = nullptr, float const Pixels = 0.0f) const; + track_hover split_spot() const; void update_track_hover(); void draw_track_hover() const; struct track_intent diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 16e4956d..412b0516 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1111,9 +1111,9 @@ void editor_mode::render_path_parameters(TTrack &Track) editor_track::velocity(*track, velocity); item_tooltip("Speed limit of the path for the AI drivers and the timetable, -1: no limit of its own"); finish_edit(rebuild::none); - char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"}; + char const *environments[] = {STR_C("flat"), STR_C("mountains"), STR_C("canyon"), STR_C("tunnel"), STR_C("bridge"), STR_C("bank")}; int environment = std::clamp(static_cast(track->eEnvironment), 0, 5); - if (ImGui::Combo("Environment", &environment, environments, IM_ARRAYSIZE(environments))) + if (ImGui::Combo(STR_C("Environment"), &environment, environments, IM_ARRAYSIZE(environments))) { track->eEnvironment = static_cast(environment); apply_now(rebuild::none); @@ -1267,7 +1267,8 @@ void editor_mode::render_path_circuits(TTrack &Track) int overheadmode{track->fOverhead < 0.f ? 0 : track->fOverhead == 0.f ? 1 : 2}; ImGui::PushItemWidth(220.0f); - if (ImGui::Combo("Overhead line", &overheadmode, "Normal\0No current, coasting\0Pantographs lowered, speed limit\0")) + char const *overheadmodes[] = {STR_C("Normal"), STR_C("No current, coasting"), STR_C("Pantographs lowered, speed limit")}; + if (ImGui::Combo(STR_C("Overhead line"), &overheadmode, overheadmodes, IM_ARRAYSIZE(overheadmodes))) { editor_track::overhead(*track, overheadmode == 0 ? -1.f : overheadmode == 1 ? 0.f : std::max(1.f, track->fOverhead > 0.f ? track->fOverhead : 40.f)); changed(before, false); @@ -2373,8 +2374,8 @@ bool editor_mode::render_route_parameters() item_tooltip("Speed the cant, the transitions and the recommended radius are designed for"); design.speed = std::max(1.0, design.speed); int shape = static_cast(design.shape); - char const *shapes[] = {"Cubic parabola", "Clothoid"}; - if (ImGui::Combo("Transition curve", &shape, shapes, IM_ARRAYSIZE(shapes))) + char const *shapes[] = {STR_C("Cubic parabola"), STR_C("Clothoid")}; + if (ImGui::Combo(STR_C("Transition curve"), &shape, shapes, IM_ARRAYSIZE(shapes))) { design.shape = static_cast(shape); changed = true; @@ -3652,14 +3653,14 @@ std::vector editor_mode::track_key_hints(bool const All) if (m_lay.active) { if (m_lay.points.empty()) - hints = {{"LMB", "start of the new track, on the ground or at a free end"}, {"Drag from a free end", "the shape chosen towards the cursor, to a free end: joins it"}, {"Parallel", "first select a straight, then a second track beside it in the panel"}, {"Esc", "stop laying"}}; + hints = {{"LMB", "start of the new track, on the ground or at a free end"}, {"Drag from a free end", "the shape chosen towards the cursor, to a free end: joins it"}, {"Shift+click a straight", "a parallel track beside it, in the panel"}, {"Alt", "the direction in steps of 15 degrees"}, {"Esc", "stop laying"}}; else { bool const chained{m_lay.start.track != nullptr}; hints = {{"LMB", chained ? "lay the shape up to here and go on from its end" : "lay the straight up to here and go on from its end"}, {"LMB on a free end", "join and lay"}}; if (chained || m_lay.mouse_snap.track != nullptr) hints.insert(hints.end(), {{"Ctrl+wheel", "radius"}, {"Shift+wheel", "arc / straight, at a free end: another shape"}}); - hints.insert(hints.end(), {{"Enter", "lay up to the cursor"}, {"Backspace", m_lay.chain.empty() ? "take back the point" : "take back the last piece"}, {"Esc", "cancel"}}); + hints.insert(hints.end(), {{"Alt", "the direction in steps of 15 degrees"}, {"Enter", "lay up to the cursor"}, {"Backspace", m_lay.chain.empty() ? "take back the point" : "take back the last piece"}, {"Esc", "cancel"}}); } } else if (false == m_straights.detour.empty()) @@ -3844,6 +3845,8 @@ std::string editor_mode::track_readout() const text += "\n" + lay.preview_error; if (lay.points.size() == 1 && (lay.start.track != nullptr || lay.mouse_snap.track != nullptr)) text += "\n" + track_shaping(lay.mouse_snap.track != nullptr && lay.start.track != nullptr); + if (ImGui::GetIO().KeyAlt && false == lay.points.empty()) + text += format("\naz %.0f deg", azimuth(lay.points.back(), lay.mouse)); if (false == lay.status.empty() && ImGui::GetTime() - lay.status_time < 4.0) text += "\n" + lay.status; return text; @@ -4074,6 +4077,8 @@ void editor_mode::update_build_tools() double const height{false == lay.points.empty() ? lay.points.back().y : lay.start.track != nullptr ? lay.start.position.y : ground.y}; if (pointing) lay.mouse = typed_lay(cursor_terrain(height)); + if (pointing && ImGui::GetIO().KeyAlt && m_typed.empty() && false == lay.points.empty()) + lay.mouse = lay_angle_snap(lay.mouse); lay.mouse_snap = m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {}); if (lay.mouse_snap.track == nullptr) lay.mouse = lay_bed(lay.mouse); @@ -4463,7 +4468,7 @@ std::vector editor_mode::join_pieces(TTrack const *From, editor_tr request.speed = design_speed(From, End.track); request.kink = m_joints.angle * 0.5; request.shape = m_route.design.shape; - request.norms = m_route.design.norms; + request.norms = laying_norms(); auto result{alignment::join(request)}; if (m_lay.transitions && result.kind != alignment::join_kind::straight && result.kind != alignment::join_kind::arc && glm::length(request.end.direction) > 1e-9) { @@ -4784,7 +4789,7 @@ alignment::result editor_mode::lay_shape(std::vector const &Points, { auto const &tool{m_lay}; design = {}; - design.norms = m_route.design.norms; + design.norms = laying_norms(); design.shape = m_route.design.shape; design.speed = design_speed(tool.start.track, nullptr); design.start = Points.front(); @@ -5008,9 +5013,50 @@ void editor_mode::lay_back() tool.start = {}; } +glm::dvec3 editor_mode::lay_angle_snap(glm::dvec3 const &Mouse) const +{ + auto const &last{m_lay.points.back()}; + glm::dvec2 const offset{Mouse.x - last.x, Mouse.z - last.z}; + auto const length{glm::length(offset)}; + if (length < 1.0) + return Mouse; + auto const leaving{m_lay.start.track != nullptr ? -plan_of(m_lay.start.direction) : glm::dvec2{0.0}}; + glm::dvec2 const reference{glm::length(leaving) > 1e-6 ? glm::normalize(leaving) : glm::dvec2{0.0, 1.0}}; + auto const step{glm::radians(15.0)}; + auto const heading{turned(reference, std::round(signed_angle(reference, offset / length) / step) * step) * length}; + return {last.x + heading.x, Mouse.y, last.z + heading.y}; +} + +editor_mode::track_hover editor_mode::split_spot() const +{ + auto const *selected{selected_track()}; + if (selected == nullptr || ui()->mode() != nodebank_panel::TRACK || ImGui::GetIO().WantCaptureMouse) + return {}; + return track_under_cursor(selected, 20.0f); +} + +alignment::limits editor_mode::laying_norms() const +{ + auto norms{m_route.design.norms}; + norms.checked = false == m_norms_free; + return norms; +} + void editor_mode::render_lay_ui() { auto &tool{m_lay}; + { + int norms{m_norms_free ? 0 : 1}; + char const *names[]{STR_C("free"), STR_C("PKP PLK")}; + ImGui::PushItemWidth(120.0f); + if (ImGui::Combo(STR_C("Norms"), &norms, names, IM_ARRAYSIZE(names))) + { + m_norms_free = norms == 0; + tool.transitions = false == m_norms_free; + } + ImGui::PopItemWidth(); + item_tooltip("Free: plain arcs with no transition curves and no cant, no warnings about the norms, the track goes where it's clicked.\nPKP PLK: transition curves and cant for the design speed, warnings when the radius, the cant or the transitions don't meet the norms"); + } if (auto *track{selected_track()}; track != nullptr) { std::string reason; @@ -5424,6 +5470,16 @@ void editor_mode::draw_build_overlay() const projection.line(drawlist, origin + glm::dvec3{i * cell, 0.0, j * cell}, origin + glm::dvec3{(i + 1) * cell, 0.0, j * cell}, colour, 1.0f); } } + if (m_track_tab == track_tab::path && false == m_lay.active && false == m_track_gizmo_using) + if (auto const spot{split_spot()}; spot.track != nullptr) + { + ImVec2 at; + if (projection.project(spot.point, at)) + { + drawlist->AddCircle(at, 6.0f, overlay_color::highlight, 12, 1.5f); + drawlist->AddText({at.x + 9.0f, at.y - 18.0f}, overlay_color::highlight, STR_C("K: split here")); + } + } auto const &display{ImGui::GetIO().DisplaySize}; for (auto const *track : simulation::Paths.sequence()) { diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 11ae4ac3..ec508fc9 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -92,13 +92,13 @@ double segment_distance2(glm::vec2 const &Point, glm::vec2 const &A, glm::vec2 c } // namespace -editor_mode::track_hover editor_mode::track_under_cursor(TTrack const *Only) const +editor_mode::track_hover editor_mode::track_under_cursor(TTrack const *Only, float const Pixels) const { track_hover result; auto const &io{ImGui::GetIO()}; screen_projection const projection; glm::vec2 const mouse{io.MousePos.x, io.MousePos.y}; - double best{Only != nullptr ? std::numeric_limits::max() : kHoverPixels * kHoverPixels}; + double best{Pixels > 0.0f ? Pixels * Pixels : Only != nullptr ? std::numeric_limits::max() : kHoverPixels * kHoverPixels}; for (auto *track : simulation::Paths.sequence()) { if (track == nullptr || false == rail(*track) || track->m_editorremoved || (Only != nullptr && track != Only)) @@ -216,6 +216,15 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods) return make(kind::lay_end, lay.points.empty() ? "Click: start from this free end, drag: lead a track out of it" : "Click: join this end and lay"); } intent.position = lay.mouse; + if (lay.points.empty()) + if (auto const under{track_under_cursor()}; under.track != nullptr) + { + intent.track = under.track; + intent.path = under.path; + if (shift) + return make(kind::select, "Click: choose this track, the parallel track goes beside it"); + return make(kind::lay, "Click: start on the ground beside this track. Shift+click: a parallel track to it, T: a switch into it"); + } return make(kind::lay, lay.points.empty() ? "Click: start of the new track" : "Click: lay the track to here"); } if (m_track_tab == track_tab::turntable) diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index c702f17a..59ba13c5 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -656,9 +656,9 @@ result fit_between(design const &Design) report.unbalanced = unbalanced_acceleration(Design.speed, sharpest, std::max(0.0, vertex.cant), Design.norms); report.recommended = recommend(Design.speed, sharpest, Design.norms); r.curves.push_back(report); - if (sharpest < report.recommended.radius_min - 1e-6) + if (Design.norms.checked && sharpest < report.recommended.radius_min - 1e-6) r.warnings.emplace_back(format("Vertex %zu: R %.0f m is below the minimum %.0f m for %.0f km/h", k + 1, sharpest, report.recommended.radius_min, Design.speed)); - if (report.unbalanced > Design.norms.unbalanced + 1e-6) + if (Design.norms.checked && report.unbalanced > Design.norms.unbalanced + 1e-6) r.warnings.emplace_back(format("Vertex %zu: unbalanced acceleration %.2f m/s2 exceeds %.2f m/s2", k + 1, report.unbalanced, Design.norms.unbalanced)); continue; } @@ -756,6 +756,8 @@ result fit_between(design const &Design) r.curves.push_back(report); auto const &norms{Design.norms}; + if (false == norms.checked) + continue; if (radius < report.recommended.radius_min - 1e-6) r.warnings.emplace_back(format("Vertex %zu: R %.0f m is below the minimum %.0f m for %.0f km/h", k + 1, radius, report.recommended.radius_min, Design.speed)); if (curve.cant > norms.cant_max + 1e-6) @@ -782,7 +784,7 @@ result fit_between(design const &Design) push_straight(cursor, lastdirection, tail); r.length = chainage; - if (Design.norms.tangent_min_time > 0.0) + if (Design.norms.checked && Design.norms.tangent_min_time > 0.0) { auto const minimum{Design.norms.tangent_min_time * Design.speed / 3.6}; for (std::size_t i = 1; i + 1 < r.elements.size(); ++i) diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 08c03b22..474465c6 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -37,6 +37,7 @@ struct limits double tangent_min_time{0.0}; double arc_min{20.0}; double arc_min_factor{0.4}; + bool checked{true}; }; struct compound_arc diff --git a/lang/editor_pl.po b/lang/editor_pl.po index b9689cce..00b87ee8 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -15,7 +15,7 @@ msgid "Select Esc" msgstr "Wybór Esc" msgid "Lay track L" -msgstr "Układaj tor L" +msgstr "Układanie toru L" msgid "Switch T" msgstr "Rozjazd T" @@ -5559,3 +5559,78 @@ msgstr "0-9 w trakcie układania" msgid "Type the length (or the radius) of the next piece, Enter lays it" msgstr "Wpisz długość (lub promień) następnego odcinka, Enter go kładzie" + +msgid "canyon" +msgstr "jar" + +msgid "tunnel" +msgstr "tunel" + +msgid "bridge" +msgstr "most" + +msgid "bank" +msgstr "nasyp" + +msgid "Normal" +msgstr "Normalna" + +msgid "No current, coasting" +msgstr "Bez napięcia, jazda z rozpędu" + +msgid "Pantographs lowered, speed limit" +msgstr "Opuszczone pantografy, ograniczenie prędkości" + +msgid "Overhead line" +msgstr "Sieć trakcyjna" + +msgid "Transition curve" +msgstr "Krzywa przejściowa" + +msgid "Include selected, no position parameters described" +msgstr "Zaznaczony include, bez opisanych parametrów położenia" + +msgid "3D view (O)" +msgstr "Widok 3D (O)" + +msgid "free" +msgstr "swobodne" + +msgid "PKP PLK" +msgstr "PKP PLK" + +msgid "Norms" +msgstr "Normy" + +msgid "Free: plain arcs with no transition curves and no cant, no warnings about the norms, the track goes where it's clicked.\nPKP PLK: transition curves and cant for the design speed, warnings when the radius, the cant or the transitions don't meet the norms" +msgstr "Swobodne: zwykłe łuki bez krzywych przejściowych i przechyłki, bez ostrzeżeń o normach, tor idzie tam, gdzie klikniesz.\nPKP PLK: krzywe przejściowe i przechyłka dla prędkości projektowej, ostrzeżenia, gdy promień, przechyłka lub krzywe przejściowe nie spełniają norm" + +msgid "Click: choose this track, the parallel track goes beside it" +msgstr "Klik: wybierz ten tor, obok niego pójdzie tor równoległy" + +msgid "Click: start on the ground beside this track. Shift+click: a parallel track to it, T: a switch into it" +msgstr "Klik: początek na terenie obok tego toru. Shift+klik: tor równoległy do niego, T: rozjazd w nim" + +msgid "K: split here" +msgstr "K: podziel tutaj" + +msgid "Shift+click a straight" +msgstr "Shift+klik na prostej" + +msgid "a parallel track beside it, in the panel" +msgstr "tor równoległy obok niej, w panelu" + +msgid "the direction in steps of 15 degrees" +msgstr "kierunek co 15 stopni" + +msgid "Alt held while laying" +msgstr "Wciśnięty Alt w trakcie układania" + +msgid "The direction in steps of 15 degrees" +msgstr "Kierunek co 15 stopni" + +msgid "Shift+LMB on a track in L" +msgstr "Shift+LPM na torze w L" + +msgid "Choose it for a parallel track" +msgstr "Wybiera go pod tor równoległy"