diff --git a/application/editormode.cpp b/application/editormode.cpp index 8ebfab7c..66c8581f 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -5046,85 +5046,6 @@ void tutorial_trainset_sketch(ImDrawList &List, ImVec2 const Low, ImVec2 const H List.AddText(ImVec2(cursor.x + height * 0.16f, cursor.y), IM_COL32(255, 220, 120, 255), "x2"); } -// the plan of a curve over the band of its curvature: the plateau of the arc goes up and down under the mouse, the curve tightens -// and opens with it over the plan kept faint as it was -void tutorial_curve_sketch(ImDrawList &List, ImVec2 const Low, ImVec2 const High, double const Time) -{ - auto const width{High.x - Low.x}; - auto const height{High.y - Low.y}; - auto const lift{static_cast(0.5 + 0.5 * std::sin(Time * 1.3))}; - auto const curvature = [](float const Share, float const Peak) { - if (Share < 0.15f || Share > 0.85f) - return 0.0f; - if (Share < 0.35f) - return Peak * (Share - 0.15f) / 0.2f; - if (Share > 0.65f) - return Peak * (0.85f - Share) / 0.2f; - return Peak; - }; - auto const colour = [](float const Share) { - if (Share < 0.15f || Share > 0.85f) - return IM_COL32(255, 255, 255, 230); - if (Share < 0.35f || Share > 0.65f) - return IM_COL32(255, 140, 30, 255); - return IM_COL32(60, 230, 90, 255); - }; - int constexpr steps{90}; - auto const plan_height{height * 0.5f}; - float scale{1.0f}; - ImVec2 start{0.0f, 0.0f}; - auto const plan = [&](float const Peak) { - std::vector points; - float heading{0.0f}, x{0.0f}, y{0.0f}; - for (int i = 0; i <= steps; ++i) - { - points.emplace_back(start.x + x * scale, start.y + y * scale); - auto const share{(i + 0.5f) / steps}; - heading += curvature(share, Peak) / steps; - x += std::cos(heading) / steps; - y -= std::sin(heading) / steps; - } - return points; - }; - // the scale fits the plan of the sharpest curve, the least sharp one is the widest - auto const sharpest{plan(2.8f)}; - auto const widest{plan(1.6f)}; - scale = std::min(width * 0.85f / std::max(1e-3f, widest.back().x), plan_height / std::max(1e-3f, -sharpest.back().y)); - start = ImVec2(Low.x + (width - widest.back().x * scale) * 0.5f, Low.y + height * 0.06f + plan_height); - auto const before{plan(1.6f)}; - List.AddPolyline(before.data(), static_cast(before.size()), IM_COL32(170, 170, 170, 90), 0, 1.5f); - auto const peak{1.6f + 1.2f * lift}; - auto const now{plan(peak)}; - for (int i = 0; i < steps; ++i) - List.AddLine(now[i], now[i + 1], colour((i + 0.5f) / steps), 3.0f); - // the band under the plan - auto const band_top{Low.y + height * 0.66f}; - auto const band_bottom{High.y - height * 0.06f}; - auto const left{Low.x + width * 0.06f}; - auto const band_width{width * 0.88f}; - List.AddRectFilled(ImVec2(left - 6.0f, band_top - 6.0f), ImVec2(left + band_width + 6.0f, band_bottom + 4.0f), ImGui::GetColorU32(ImGuiCol_FrameBg), 4.0f); - List.AddLine(ImVec2(left, band_bottom), ImVec2(left + band_width, band_bottom), ImGui::GetColorU32(ImGuiCol_TextDisabled)); - auto const k_scale{(band_bottom - band_top) * 0.9f / 2.8f}; - for (int i = 0; i < steps; ++i) - { - auto const a{static_cast(i) / steps}; - auto const b{static_cast(i + 1) / steps}; - ImVec2 const p{left + a * band_width, band_bottom - curvature(a, peak) * k_scale}; - ImVec2 const q{left + b * band_width, band_bottom - curvature(b, peak) * k_scale}; - auto const tint{colour((a + b) * 0.5f)}; - auto const flags{List.Flags}; - List.Flags &= ~ImDrawListFlags_AntiAliasedFill; - List.AddQuadFilled(ImVec2(p.x, band_bottom), p, q, ImVec2(q.x, band_bottom), (tint & ~IM_COL32_A_MASK) | IM_COL32(0, 0, 0, 50)); - List.Flags = flags; - List.AddLine(p, q, tint, 2.0f); - } - char text[32]; - std::snprintf(text, sizeof(text), "R %.0f", std::round(900.0f / peak / 5.0f) * 5.0f); - ImVec2 const plateau{left + band_width * 0.5f, band_bottom - peak * k_scale}; - List.AddText(ImVec2(plateau.x - ImGui::CalcTextSize(text).x * 0.5f, plateau.y - ImGui::GetTextLineHeight() - 2.0f), IM_COL32(60, 230, 90, 255), text); - gesture_mouse(List, ImVec2(plateau.x + band_width * 0.08f, plateau.y - height * 0.06f), height * 0.16f, "LMB"); -} - // the terrain model under the tracks: the track laid level sinks onto the ground along, the formation and the slopes under it void tutorial_terrain_sketch(ImDrawList &List, ImVec2 const Low, ImVec2 const High, double const Time) { @@ -5238,15 +5159,14 @@ void editor_mode::render_first_steps() {{first_step::bend, "Lay a track with a bend in it"}, {first_step::trace, "Switch to tracing a curve with Tab"}}, trial::lay}, {"Correcting a curve", - nullptr, + "plan", {{"G", "The plan tool: a click on a curve shows its design, the vertex and the radius to drag in the view"}, - {"LMB + move", "In the panel, the band of the curvature: drag the plateau of the arc up or down for its radius"}, + {"LMB + move", "In the panel, the band of the curvature: drag the plateau of an arc up or down for its radius, each arc of a compound curve its own"}, {"LMB + move", "Drag a ramp sideways: the transition curve gets longer or shorter"}, {"wheel", "Over the band: the radius by 5%, a transition by 5 m"}, {"LMB", "Apply to the scenery: the tracks are laid by the design"}}, {{first_step::plan, "Open the plan of a curve"}, {first_step::plan_band, "Change the curve in the band"}, {first_step::plan_apply, "Apply it to the scenery"}}, - trial::plan, - tutorial_curve_sketch}, + trial::plan}, {"Joining tracks", "join", {{"LMB + move", "Press on a free end (a magenta ring) and drag: a new track goes out of it"}, diff --git a/application/editormode.h b/application/editormode.h index 72541120..671da0e8 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -982,7 +982,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri int hot{-1}; double at{-1.0}; int vertex{-1}; - int part{0}; // 1 the radius, 2 the transition in, 3 the transition out + int part{0}; // 1 the radius, 2 the transition in, 3 the transition out, 4 the transition between two arcs of a compound curve + int arc{0}; // the arc of a compound curve the radius is of, 0 the first; of a transition between arcs the one after it double value{0.0}; ImVec2 press{0.0f, 0.0f}; float metres{1.0f}; // pixels of a metre and of a unit of the curvature, kept as the drag goes diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index da91e933..d4695b6f 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -1694,7 +1694,46 @@ bool editor_mode::render_route_band() auto const mouse{ImGui::GetIO().MousePos}; auto const count{static_cast(result.elements.size())}; - // the element under the mouse and what of it a drag changes: near the ends of a plateau its transitions, also the ones of no length yet + // what of its vertex each element is: an arc, the arcs of a compound curve counted from the first one, which are elements of an even + // curvature as an arc is, or a transition, in from the straight, out to it, or between two arcs, which goes on from the arc before it + struct role + { + int part{0}; + int arc{0}; + }; + std::vector roles(static_cast(count)); + std::vector arcs(design.vertices.size(), 0); + { + int vertex{-1}; + int arc{-1}; + double last{0.0}; + for (int i = 0; i < count; ++i) + { + auto const &element{result.elements[i]}; + if (element.vertex != vertex) + { + vertex = element.vertex; + arc = -1; + last = 0.0; + } + if (element.vertex < 0 || element.vertex >= static_cast(design.vertices.size()) || element.kind == alignment::element_kind::straight) + continue; + auto const [from, to]{ends(element)}; + if (element.kind == alignment::element_kind::arc || (std::abs(from - to) <= 1e-6 * std::abs(from) && std::abs(from) > 1e-9)) + { + // the next arc where the curvature isn't the one of the arc before, an arc cut in parts staying one + if (arc < 0 || std::abs(std::abs(from) - last) > 1e-6 * std::abs(from)) + ++arc; + last = std::abs(from); + roles[i] = {1, arc}; + arcs[element.vertex] = std::max(arcs[element.vertex], arc + 1); + } + else + roles[i] = std::abs(from) <= 1e-9 ? role{2, 0} : std::abs(to) <= 1e-9 ? role{3, 0} : role{4, arc + 1}; + } + } + // the element under the mouse and what of it a drag changes: near the ends of a plateau the transitions either side of it, also the + // ones of no length yet int under{-1}; int part{0}; if (hovered && false == dragging) @@ -1702,27 +1741,38 @@ bool editor_mode::render_route_band() for (int i = 0; i < count; ++i) { auto const &element{result.elements[i]}; + int arc{0}; if (mouse.x < x_of(element.chainage) || mouse.x >= x_of(element.chainage + element.length)) continue; under = i; - auto const from{x_of(element.chainage)}; - auto const to{x_of(element.chainage + element.length)}; - auto const edge{std::min(std::round(font * 0.5f), (to - from) * 0.25f)}; - switch (element.kind) + part = roles[i].part; + arc = roles[i].arc; + if (part == 1) { - case alignment::element_kind::arc: part = mouse.x < from + edge ? 2 : mouse.x >= to - edge ? 3 : 1; break; - case alignment::element_kind::transition_in: part = 2; break; - case alignment::element_kind::transition_out: part = 3; break; - default: break; + auto const from{x_of(element.chainage)}; + auto const to{x_of(element.chainage + element.length)}; + auto const edge{std::min(std::round(font * 0.5f), (to - from) * 0.25f)}; + if (mouse.x < from + edge) + part = arc == 0 ? 2 : 4; + else if (mouse.x >= to - edge) + { + part = arc + 1 >= arcs[element.vertex] ? 3 : 4; + arc += 1; + } } - if (element.vertex < 0 || element.vertex >= static_cast(design.vertices.size())) - part = 0; break; } } - auto const value_of = [&](int const Vertex, int const Part) -> double & { + auto const value_of = [&](int const Vertex, int const Part, int const Arc) -> double & { auto &vertex{design.vertices[Vertex]}; - return Part == 1 ? vertex.radius : Part == 2 ? vertex.transition_in : vertex.transition_out; + auto const later{Arc >= 1 && Arc <= static_cast(vertex.arcs.size())}; + switch (Part) + { + case 1: return later ? vertex.arcs[Arc - 1].radius : vertex.radius; + case 2: return vertex.transition_in; + case 3: return vertex.transition_out; + default: return later ? vertex.arcs[Arc - 1].transition : vertex.transition_in; + } }; auto const rounded_radius = [](double const Radius) { auto const step{Radius < 1000.0 ? 1.0 : 10.0}; @@ -1733,14 +1783,15 @@ bool editor_mode::render_route_band() { band.vertex = result.elements[under].vertex; band.part = part; - band.value = value_of(band.vertex, part); + band.arc = arc; + band.value = value_of(band.vertex, part, arc); band.press = mouse; band.metres = metres; band.curvature = curvature; } if (band.vertex >= 0 && band.vertex < static_cast(design.vertices.size()) && active) { - auto &value{value_of(band.vertex, band.part)}; + auto &value{value_of(band.vertex, band.part, band.arc)}; double wanted{value}; if (band.part == 1) { @@ -1759,6 +1810,7 @@ bool editor_mode::render_route_band() } if (wanted != value) { + // the transition in grows to the left, from where it starts; the others to the right value = wanted; changed = true; } @@ -1773,7 +1825,7 @@ bool editor_mode::render_route_band() ImGui::SetMouseCursor(part == 1 ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); if (auto const wheel{ImGui::GetIO().MouseWheel}; wheel != 0.0f) { - auto &value{value_of(result.elements[under].vertex, part)}; + auto &value{value_of(result.elements[under].vertex, part, arc)}; value = part == 1 ? rounded_radius(value * (wheel > 0.0f ? 0.95 : 1.0 / 0.95)) : std::max(0.0, std::round(value / 5.0) * 5.0 + (wheel > 0.0f ? 5.0 : -5.0)); changed = true; } @@ -1862,8 +1914,8 @@ bool editor_mode::render_route_band() } if (dragging && band.vertex >= 0 && band.vertex < static_cast(design.vertices.size())) { - auto const &vertex{design.vertices[band.vertex]}; - auto const text{band.part == 1 ? format("R %.0f m", vertex.radius) : format("%.0f m", band.part == 2 ? vertex.transition_in : vertex.transition_out)}; + auto const value{value_of(band.vertex, band.part, band.arc)}; + auto const text{band.part == 1 ? format("R %.0f m", value) : format("%.0f m", value)}; auto const size{ImGui::CalcTextSize(text.c_str())}; ImVec2 const at{std::clamp(mouse.x + pad, top.x, bottom.x - size.x - pad), std::clamp(mouse.y - size.y - pad, top.y, bottom.y - size.y)}; list->AddRectFilled(ImVec2(at.x - 3.0f, at.y - 1.0f), ImVec2(at.x + size.x + 3.0f, at.y + size.y + 1.0f), IM_COL32(0, 0, 0, 170), 3.0f); @@ -1875,12 +1927,18 @@ bool editor_mode::render_route_band() if (part != 0 && false == dragging && ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { - auto const &vertex{design.vertices[result.elements[under].vertex]}; + auto const index{result.elements[under].vertex}; + auto const &vertex{design.vertices[index]}; + auto const value{value_of(index, part, arc)}; ImGui::BeginTooltip(); - if (part == 1) - ImGui::Text(STR_C("Curve %d: R %.0f m, cant %.0f mm"), result.elements[under].vertex + 1, vertex.radius, vertex.cant); + if (part == 1 && arcs[index] > 1) + ImGui::Text(STR_C("Curve %d, arc R%d: %.0f m"), index + 1, arc + 1, value); + else if (part == 1) + ImGui::Text(STR_C("Curve %d: R %.0f m, cant %.0f mm"), index + 1, value, vertex.cant); + else if (part == 4) + ImGui::Text(STR_C("Curve %d, transition between R%d and R%d: %.0f m"), index + 1, arc, arc + 1, value); else - ImGui::Text(part == 2 ? STR_C("Transition into curve %d: %.0f m") : STR_C("Transition out of curve %d: %.0f m"), result.elements[under].vertex + 1, part == 2 ? vertex.transition_in : vertex.transition_out); + ImGui::Text(part == 2 ? STR_C("Transition into curve %d: %.0f m") : STR_C("Transition out of curve %d: %.0f m"), index + 1, value); gesture_rows({{"Drag", part == 1 ? "up or down: the radius" : "sideways: the length"}, {"Wheel", part == 1 ? "the radius by 5%" : "the length by 5 m"}}); ImGui::EndTooltip(); } diff --git a/lang/editor_pl.po b/lang/editor_pl.po index e7e5fe5e..a630aa5e 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -7094,6 +7094,12 @@ msgstr "Łuk %d: R %.0f m, przechyłka %.0f mm" msgid "Transition into curve %d: %.0f m" msgstr "Przejściowa na wejściu w łuk %d: %.0f m" +msgid "Curve %d, arc R%d: %.0f m" +msgstr "Łuk %d, promień R%d: %.0f m" + +msgid "Curve %d, transition between R%d and R%d: %.0f m" +msgstr "Łuk %d, przejściowa między R%d a R%d: %.0f m" + msgid "Transition out of curve %d: %.0f m" msgstr "Przejściowa na wyjściu z łuku %d: %.0f m" @@ -7151,8 +7157,8 @@ msgstr "Poprawianie łuku" msgid "The plan tool: a click on a curve shows its design, the vertex and the radius to drag in the view" msgstr "Narzędzie Plan: klik na łuku pokazuje jego projekt, wierzchołek i promień do przeciągania w widoku" -msgid "In the panel, the band of the curvature: drag the plateau of the arc up or down for its radius" -msgstr "W panelu pasek krzywizny: półka łuku przeciągnięta w górę albo w dół zmienia jego promień" +msgid "In the panel, the band of the curvature: drag the plateau of an arc up or down for its radius, each arc of a compound curve its own" +msgstr "W panelu pasek krzywizny: półka łuku przeciągnięta w górę albo w dół zmienia jego promień, każdy łuk koszowego osobno" msgid "Drag a ramp sideways: the transition curve gets longer or shorter" msgstr "Rampa przeciągnięta w bok: krzywa przejściowa wydłuża się albo skraca" diff --git a/lang/editor_tutorial/plan.gif b/lang/editor_tutorial/plan.gif new file mode 100644 index 00000000..03f5e7f7 Binary files /dev/null and b/lang/editor_tutorial/plan.gif differ