17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 23:59:48 +01:00

fix(editor): the band of the curvature edits each arc of a compound curve: its arcs, elements of an even curvature, were taken for nothing to drag, and any change went to the first radius; now the plateau of an arc changes its own radius, a ramp between two arcs the transition between them, the ends of a plateau the transitions either side of it, by a drag or the wheel, the tooltip and the value shown while dragging naming the arc. The tutorial shows the correcting of a compound curve in a clip recorded in the editor in place of the drawing: the plan of the curve opened, the plateau of R2 dragged from 138 to 174 m, the line applied to the scenery

This commit is contained in:
maj00r
2026-10-25 12:01:12 +01:00
parent cb766dabb4
commit 91900a0ead
5 changed files with 93 additions and 108 deletions

View File

@@ -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<float>(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<ImVec2> 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<int>(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<float>(i) / steps};
auto const b{static_cast<float>(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"},

View File

@@ -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

View File

@@ -1694,7 +1694,46 @@ bool editor_mode::render_route_band()
auto const mouse{ImGui::GetIO().MousePos};
auto const count{static_cast<int>(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<role> roles(static_cast<std::size_t>(count));
std::vector<int> 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<int>(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<int>(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<int>(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<int>(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<int>(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();
}

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB