mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-21 21:06:25 +02:00
feat(editor): labels for a layman, light at the start; whole sceneries fitted to the terrain model
Labels: - every button, field, tooltip and message of the editor reread for a beginner: short nouns for the fields with the sentence in the tooltip, the less used options under More settings, one glossary (field of work, template, track, grade), sides told from directions, degrees and per mille as signs, messages built from one format string - plural forms through STR_P; the last entry of a po file is read too, and the Polish plural rule is in the header - lang/editor_pl.po: the code pass entries merged, 391 entries no code uses and 3 duplicates dropped - roads: the notes and errors naming a road translated; the build options and the layout fold the texture length, kerb sizes, traffic share, roundabout part, height over the ground and junction speed limit under More settings, the lanes start folded Grade lines fitted to the terrain model (NMT), a line or the whole scenery at once: - fit_ground: a robust fit on nodes 5 m apart; the ground parting from the line (a bridge, a viaduct, a wall) is gone past rather than followed, the bends keep off the switches as far as their curves reach, a run of bends the same way is one point of intersection, a curve with no room is as sharp as the room lets it be - the ends of the line and the other paths of its switches leading into lines fitted already bound the line by what the tracks between can climb; ends meeting in the plan, laid apart in height by hand, are joined before the run - apply_profile: the paths are split where split_path takes the parameter (a straight was cut in the wrong place, into pieces of a few centimetres the game takes for zero length), no piece shorter than the margin; the paths off the ends of the route are brought to its height - Bytom: 1600 lines, grade breaks at the joints 89 -> 20; Tarnowskie Gory: 848 lines, 223 -> 3; no step over 2 cm
This commit is contained in:
@@ -13,6 +13,7 @@ http:
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "application/editorgestures.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
@@ -155,7 +156,7 @@ bool editor_mode::route_from_curve(TTrack &Track)
|
||||
design.vertices = {vertex, vertex};
|
||||
}
|
||||
route_update();
|
||||
m_route.status = STR_C("Curve of ") + std::to_string(m_route.chain.tracks.size()) + STR_C(" paths, turning ") + std::to_string(static_cast<int>(std::round(glm::degrees(curve.turn)))) + STR_C(" deg");
|
||||
m_route.status = format(STR_P("Curve of %zu path, turning %d deg", "Curve of %zu paths, turning %d deg", m_route.chain.tracks.size()), m_route.chain.tracks.size(), static_cast<int>(std::round(glm::degrees(curve.turn))));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -268,23 +269,22 @@ void editor_mode::route_take_stored(int const Index)
|
||||
void editor_mode::render_route_stored()
|
||||
{
|
||||
auto &route{m_route};
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
auto km{route.origin / 1000.0};
|
||||
if (ImGui::InputDouble(STR_C("Km of the start"), &km, 0.0, 0.0, "%.3f"))
|
||||
route.origin = km * 1000.0;
|
||||
ImGui::PopItemWidth();
|
||||
item_tooltip("Chainage of the start of the fragment, kept with the design in the scenery; the vertical profile of these paths counts from it");
|
||||
if (ImGui::SmallButton(STR_C("Digitize all the tracks")))
|
||||
if (begin_fields("##routeorigin"))
|
||||
{
|
||||
digitize_all();
|
||||
return;
|
||||
field_row("Km of the start");
|
||||
if (ImGui::InputDouble("##routekm", &km, 0.0, 0.0, "%.3f"))
|
||||
route.origin = km * 1000.0;
|
||||
item_tooltip("Chainage of the start of the fragment, kept with the design in the scenery; the vertical profile of these paths counts from it");
|
||||
ImGui::EndTable();
|
||||
}
|
||||
item_tooltip("Every line of the scenery not digitized yet is kept in it with its straights, arcs and transition curves, the way the drawn tracks are kept; the paths stay as they are");
|
||||
auto const &records{alignment_store::records()};
|
||||
if (route.departed < 0 || route.departed >= static_cast<int>(records.size()))
|
||||
return;
|
||||
auto const &layer{scene::Layers.layer(records[route.departed].layer).name};
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), STR_C("The design kept in %s for this fragment departs from its paths by up to %.2f m"), layer.c_str(), route.departure);
|
||||
ImGui::PopTextWrapPos();
|
||||
if (ImGui::SmallButton(STR_C("Take the kept design")))
|
||||
{
|
||||
route_take_stored(route.departed);
|
||||
@@ -297,7 +297,7 @@ void editor_mode::render_route_stored()
|
||||
alignment_store::forget(route.departed);
|
||||
alignment_store::settle();
|
||||
route.departed = -1;
|
||||
route.status = STR_C("The kept design will be removed from ") + layer + STR_C(" on save");
|
||||
route.status = format(STR_C("The kept design will be removed from %s on save"), layer.c_str());
|
||||
}
|
||||
item_tooltip("The paths stay as they are, the scenery no longer keeps a design for them");
|
||||
}
|
||||
@@ -330,7 +330,7 @@ bool editor_mode::digitize_drawn(std::vector<TTrack *> const &Created, bool cons
|
||||
return false;
|
||||
}
|
||||
|
||||
void editor_mode::digitize_all()
|
||||
void editor_mode::digitize_all(int *Kept, int *Failed)
|
||||
{
|
||||
std::set<TTrack const *> visited;
|
||||
int kept{0};
|
||||
@@ -356,6 +356,10 @@ void editor_mode::digitize_all()
|
||||
first = reason;
|
||||
}
|
||||
alignment_store::settle();
|
||||
if (Kept != nullptr)
|
||||
*Kept = kept;
|
||||
if (Failed != nullptr)
|
||||
*Failed = failed;
|
||||
auto text{format(STR_C("Lines digitized: %d, paths left out: %d"), kept, failed)};
|
||||
if (false == first.empty())
|
||||
text += STR_C(", the first: ") + first;
|
||||
@@ -377,7 +381,7 @@ int editor_mode::digitize_parts(editor_track::chain const &Chain, std::string *R
|
||||
if (count == 1)
|
||||
{
|
||||
++Skipped;
|
||||
reason = (Chain.tracks.front()->name().empty() ? std::string{"(noname)"} : Chain.tracks.front()->name()) + ": " + reason;
|
||||
reason = (Chain.tracks.front()->name().empty() ? std::string{STR_C("(no name)")} : Chain.tracks.front()->name()) + ": " + reason;
|
||||
WriteLog("Editor: digitizing - " + reason, logtype::generic);
|
||||
if (Reason != nullptr && Reason->empty())
|
||||
*Reason = reason;
|
||||
@@ -402,7 +406,7 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string *
|
||||
{
|
||||
if (Chain.tracks.empty())
|
||||
return false;
|
||||
auto const name = [](TTrack const *Track) { return Track == nullptr || Track->name().empty() ? std::string{"(noname)"} : Track->name(); };
|
||||
auto const name = [](TTrack const *Track) { return Track == nullptr || Track->name().empty() ? std::string{STR_C("(no name)")} : Track->name(); };
|
||||
auto const nearest = [&](glm::dvec2 const &Point) {
|
||||
TTrack *found{nullptr};
|
||||
double distance{std::numeric_limits<double>::max()};
|
||||
@@ -564,7 +568,7 @@ bool editor_mode::route_from_line(TTrack &Track)
|
||||
}
|
||||
route_update();
|
||||
}
|
||||
route.status = format(STR_C("Line of %zu paths, %.0f m, %zu vertices"), route.chain.tracks.size(), route.chain.length, design.vertices.size());
|
||||
route.status = format(STR_P("Line of %zu path, %.0f m, %zu vertices", "Line of %zu paths, %.0f m, %zu vertices", route.chain.tracks.size()), route.chain.tracks.size(), route.chain.length, design.vertices.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -587,6 +591,111 @@ void editor_mode::route_apply()
|
||||
involved.push_back(passage.track);
|
||||
if (false == tracks_editable(involved, route.error))
|
||||
return;
|
||||
// the crossovers of the line can't be moved as their switches are: they're taken out, the line laid again, and they go back
|
||||
// where they were, onto the new line
|
||||
if (false == route.relaying)
|
||||
{
|
||||
struct again
|
||||
{
|
||||
int shape;
|
||||
glm::dvec3 press, release;
|
||||
std::string name;
|
||||
std::vector<std::string> names; // of the switches at the press and at the release
|
||||
};
|
||||
std::vector<again> crossovers;
|
||||
std::vector<TTrack const *> seen;
|
||||
// a line which ends on a switch of a crossover would be laid again only up to it, the crossover left on a kink
|
||||
for (auto const &end : {chain.start, chain.end})
|
||||
for (auto *track : chain.tracks)
|
||||
for (auto const &[other, point] : editor_track::connected_points(*track, end))
|
||||
if (other != nullptr && other->eType == tt_Switch && crossover_of(*other).has_value())
|
||||
{
|
||||
route.error = format(STR_C("The line ends on the switch %s of a crossover: move the crossover off the curve or take it out, change both lines, then lay it again"), other->name().c_str());
|
||||
return;
|
||||
}
|
||||
std::vector<TTrack *> switches;
|
||||
for (auto const &passage : chain.switches)
|
||||
switches.push_back(passage.track);
|
||||
for (auto *switchtrack : switches)
|
||||
{
|
||||
auto const crossover{crossover_of(*switchtrack)};
|
||||
if (false == crossover.has_value() || std::find(seen.begin(), seen.end(), crossover->first) != seen.end())
|
||||
continue;
|
||||
seen.push_back(crossover->first);
|
||||
// the press on this line, the release on the other one
|
||||
auto *here{crossover->first == switchtrack ? crossover->first : crossover->second};
|
||||
auto *there{here == crossover->first ? crossover->second : crossover->first};
|
||||
crossovers.push_back({crossover_template(crossover->record), here->m_paths.front().points[segment_data::point::start], there->m_paths.front().points[segment_data::point::start], crossover->first->name(), {here->name(), there->name()}});
|
||||
}
|
||||
if (false == crossovers.empty())
|
||||
{
|
||||
auto const tab{m_track_tab};
|
||||
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||
std::vector<TTrack *> created;
|
||||
std::vector<TTrack *> removed;
|
||||
for (auto *switchtrack : switches)
|
||||
if (auto const crossover{crossover_of(*switchtrack)})
|
||||
{
|
||||
TTrack *first{nullptr};
|
||||
TTrack *second{nullptr};
|
||||
take_out_crossover(*crossover, states, created, removed, first, second);
|
||||
}
|
||||
switch_store::follow();
|
||||
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
|
||||
records_to_history();
|
||||
show_track_tab(tab);
|
||||
// the line between the same points again: taking the lift back may have cut its paths
|
||||
auto const end_at = [&](glm::dvec3 const &Point) -> TTrack * {
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
if (track != nullptr && false == track->m_editorremoved && track->eType == tt_Normal && false == track->m_paths.empty())
|
||||
for (auto const which : {segment_data::point::start, segment_data::point::end})
|
||||
if (plan_distance(track->m_paths.front().points[which], Point) < 0.01)
|
||||
return track;
|
||||
return nullptr;
|
||||
};
|
||||
auto *start{end_at(route.chain.start)};
|
||||
auto *finish{end_at(route.chain.end)};
|
||||
editor_track::chain refreshed;
|
||||
std::string error;
|
||||
if (start != nullptr && finish != nullptr && editor_track::find_chain(start, finish, refreshed, error, true))
|
||||
{
|
||||
route.from = start;
|
||||
route.to = finish;
|
||||
route.chain = refreshed;
|
||||
}
|
||||
route.relaying = true;
|
||||
auto const steps{m_history.size()};
|
||||
route_apply();
|
||||
route.relaying = false;
|
||||
if (m_history.size() == steps)
|
||||
{
|
||||
undo_last();
|
||||
return;
|
||||
}
|
||||
join_last_steps();
|
||||
std::string back, left;
|
||||
for (auto const &crossover : crossovers)
|
||||
{
|
||||
// the new line moved sideways: the press onto it, the release onto the other line
|
||||
if (lay_crossover(crossover.shape, crossover.press, crossover.release, 30.0, crossover.names))
|
||||
{
|
||||
join_last_steps();
|
||||
back += (back.empty() ? "" : ", ") + crossover.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
left += (left.empty() ? "" : ", ") + crossover.name;
|
||||
route.crossovers_out.push_back({crossover.shape, crossover.press, crossover.release, crossover.name, crossover.names});
|
||||
}
|
||||
}
|
||||
show_track_tab(tab);
|
||||
if (false == back.empty())
|
||||
route.status += "\n" + format(STR_C("Crossovers laid again on the new line: %s"), back.c_str());
|
||||
if (false == left.empty())
|
||||
route.status += "\n" + format(STR_C("Crossovers taken out which don't fit the new line, lay them again: %s"), left.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||
std::vector<TTrack *> created;
|
||||
@@ -667,9 +776,27 @@ void editor_mode::route_apply()
|
||||
}
|
||||
route_store();
|
||||
records_to_history();
|
||||
route.status = STR_C("Re-laid ") + std::to_string(chain.tracks.size()) + STR_C(" paths") + (added > 0 ? STR_C(", added ") + std::to_string(added) : std::string{});
|
||||
route.status = added > 0 ? format(STR_P("Re-laid %zu path, added %zu", "Re-laid %zu paths, added %zu", chain.tracks.size()), chain.tracks.size(), added) : format(STR_P("Re-laid %zu path", "Re-laid %zu paths", chain.tracks.size()), chain.tracks.size());
|
||||
if (startextension > 0.0 || endextension > 0.0)
|
||||
route.status += format(", the curve took over %.2f / %.2f m of the adjoining straights", startextension, endextension);
|
||||
route.status += format(STR_C(", the curve took over %.2f / %.2f m of the adjoining straights"), startextension, endextension);
|
||||
// the crossovers taken out before which fit the lines now
|
||||
if (false == route.relaying && false == route.crossovers_out.empty())
|
||||
{
|
||||
auto const tab{m_track_tab};
|
||||
std::string back;
|
||||
for (auto out{route.crossovers_out.begin()}; out != route.crossovers_out.end();)
|
||||
if (lay_crossover(out->shape, out->press, out->release, 30.0, out->names))
|
||||
{
|
||||
join_last_steps();
|
||||
back += (back.empty() ? "" : ", ") + out->name;
|
||||
out = route.crossovers_out.erase(out);
|
||||
}
|
||||
else
|
||||
++out;
|
||||
show_track_tab(tab);
|
||||
if (false == back.empty())
|
||||
route.status += "\n" + format(STR_C("Crossovers laid again on the new line: %s"), back.c_str());
|
||||
}
|
||||
WriteLog("Editor: route design - " + route.status, logtype::generic);
|
||||
}
|
||||
|
||||
@@ -678,7 +805,7 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
{
|
||||
auto &route{m_route};
|
||||
auto const &result{route.result};
|
||||
auto const name = [](TTrack const *Track) { return Track->name().empty() ? std::string{"(noname)"} : Track->name(); };
|
||||
auto const name = [](TTrack const *Track) { return Track->name().empty() ? std::string{STR_C("(no name)")} : Track->name(); };
|
||||
auto const plan = [](glm::dvec3 const &Vector) { return glm::normalize(plan_of(Vector)); };
|
||||
struct placement
|
||||
{
|
||||
@@ -712,7 +839,7 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
auto const from{starts[k] + length * 0.5 < oldlength * 0.5 ? starts[k] : result.length - (oldlength - starts[k])};
|
||||
if (from < 0.0 || from + length > result.length || (false == breaks.empty() && from < breaks.back() - 0.01))
|
||||
{
|
||||
route.error = STR_C("The switch ") + name(passage.track) + STR_C(" doesn't fit on the designed line");
|
||||
route.error = format(STR_C("The switch %s doesn't fit on the designed line"), name(passage.track).c_str());
|
||||
return false;
|
||||
}
|
||||
auto const entry{main.points[passage.forward ? segment_data::point::start : segment_data::point::end]};
|
||||
@@ -895,6 +1022,25 @@ void editor_mode::draw_route_overlay() const
|
||||
if (projection.project(alignment::evaluate(result, e.chainage).position, screen))
|
||||
drawlist->AddCircleFilled(screen, 3.5f, overlay_color::highlight);
|
||||
}
|
||||
// what the mouse points at in the band of the curvature in the panel
|
||||
if (route.band.hot >= 0 && route.band.hot < static_cast<int>(result.elements.size()))
|
||||
{
|
||||
auto const &e{result.elements[route.band.hot]};
|
||||
int const steps = std::clamp(static_cast<int>(e.length / 2.0), 1, 400);
|
||||
auto previous{alignment::evaluate(result, e.chainage).position};
|
||||
for (int j = 1; j <= steps; ++j)
|
||||
{
|
||||
auto const next{alignment::evaluate(result, e.chainage + e.length * j / steps).position};
|
||||
projection.line(drawlist, previous, next, IM_COL32(255, 255, 255, 110), 8.0f);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
ImVec2 pointed;
|
||||
if (route.band.at >= 0.0 && projection.project(alignment::evaluate(result, std::min(route.band.at, result.length)).position, pointed))
|
||||
{
|
||||
drawlist->AddCircleFilled(pointed, 5.0f, overlay_color::highlight);
|
||||
drawlist->AddCircle(pointed, 10.0f, overlay_color::highlight, 20, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const atend : {false, true})
|
||||
@@ -997,17 +1143,21 @@ void editor_mode::render_route_ui()
|
||||
if (route.chain.tracks.empty())
|
||||
return;
|
||||
|
||||
ImGui::TextDisabled(STR_C("%zu paths, %.2f m"), route.chain.tracks.size(), route.chain.length);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(STR_C("Reset design")))
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled(STR_P("%zu path, %.2f m", "%zu paths, %.2f m", route.chain.tracks.size()), route.chain.tracks.size(), route.chain.length);
|
||||
auto const reset{STR_C("Reset design")};
|
||||
auto const reset_width{ImGui::CalcTextSize(reset).x + ImGui::GetStyle().FramePadding.x * 2.0f};
|
||||
ImGui::SameLine(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowContentRegionMax().x - reset_width));
|
||||
if (ImGui::Button(reset))
|
||||
{
|
||||
auto *track{route.chain.tracks[route.chain.tracks.size() / 2]};
|
||||
route_load(*track);
|
||||
return;
|
||||
}
|
||||
item_tooltip("Reads the curves of the line from the scenery again, dropping the changes not applied");
|
||||
auto changed{render_route_band()};
|
||||
render_route_stored();
|
||||
auto changed{render_route_vertices()};
|
||||
changed |= render_route_vertices();
|
||||
if (ImGui::CollapsingHeader(STR_C("Design speed and limits")))
|
||||
changed |= render_route_parameters();
|
||||
if (changed)
|
||||
@@ -1015,38 +1165,329 @@ void editor_mode::render_route_ui()
|
||||
|
||||
ImGui::Separator();
|
||||
render_route_result();
|
||||
|
||||
if (ImGui::CollapsingHeader(STR_C("Digitize the whole scenery")))
|
||||
{
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", STR_C("Keeps the plan of the lines in the scenery as straights, arcs and transitions, so the tools can edit them; the paths stay as they are"));
|
||||
ImGui::PopTextWrapPos();
|
||||
if (ImGui::Button(STR_C("Digitize all the tracks")))
|
||||
{
|
||||
digitize_all();
|
||||
return;
|
||||
}
|
||||
item_tooltip("Every line of the scenery not digitized yet is kept in it with its straights, arcs and transition curves, the way the drawn tracks are kept; the paths stay as they are");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(STR_C("Digitize the scenery")))
|
||||
{
|
||||
digitize_scenery(true);
|
||||
return;
|
||||
}
|
||||
item_tooltip("All at once: the lines, the switches with their types out of the catalogue (or the size measured from their shape), the crossovers as connections of switches and the signals, signs, poles and catenary bound to the tracks. The paths and the objects stay as they are, the scenery keeps it on save");
|
||||
}
|
||||
}
|
||||
|
||||
// the curvature along the fragment, the way the designers draw it: straights on the axis, the transitions as ramps,
|
||||
// the arcs as plateaus at 1/R, in the colours of the overlay in the view. dragging a plateau changes its radius,
|
||||
// dragging a ramp sideways the length of the transition, the wheel does both in steps
|
||||
bool editor_mode::render_route_band()
|
||||
{
|
||||
auto &route{m_route};
|
||||
auto &design{route.design};
|
||||
auto const &result{route.result};
|
||||
auto &band{route.band};
|
||||
band.hot = -1;
|
||||
band.at = -1.0;
|
||||
if (result.elements.empty() || result.length <= 1e-3 || design.vertices.empty())
|
||||
{
|
||||
band.vertex = -1;
|
||||
return false;
|
||||
}
|
||||
auto const font{ImGui::GetFontSize()};
|
||||
auto const pad{std::round(font * 0.5f)};
|
||||
auto const width{ImGui::GetContentRegionAvail().x};
|
||||
auto const height{std::round(font * 6.0f)};
|
||||
auto const top{ImGui::GetCursorScreenPos()};
|
||||
ImGui::InvisibleButton("##curvature", ImVec2(width, height));
|
||||
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
|
||||
auto const hovered{ImGui::IsItemHovered()};
|
||||
auto const active{ImGui::IsItemActive()};
|
||||
auto *list{ImGui::GetWindowDrawList()};
|
||||
ImVec2 const bottom{top.x + width, top.y + height};
|
||||
list->AddRectFilled(top, bottom, ImGui::GetColorU32(ImGuiCol_FrameBg), std::round(font * 0.35f));
|
||||
|
||||
auto const ends = [](alignment::element const &Element) -> std::pair<double, double> {
|
||||
auto const sign{static_cast<double>(Element.turn)};
|
||||
auto const k{Element.radius > 0.0 ? sign / Element.radius : 0.0};
|
||||
switch (Element.kind)
|
||||
{
|
||||
case alignment::element_kind::transition_in: return {0.0, k};
|
||||
case alignment::element_kind::arc: return {k, k};
|
||||
case alignment::element_kind::transition_out: return {k, 0.0};
|
||||
case alignment::element_kind::spiral: return {sign * Element.curvature_start, sign * Element.curvature_end};
|
||||
default: return {0.0, 0.0};
|
||||
}
|
||||
};
|
||||
auto const colour = [](alignment::element const &Element) {
|
||||
return Element.kind == alignment::element_kind::straight ? IM_COL32(255, 255, 255, 230) : Element.kind == alignment::element_kind::arc ? overlay_color::grip : IM_COL32(255, 140, 30, 255);
|
||||
};
|
||||
double peak{0.0};
|
||||
bool left{false}, right{false};
|
||||
for (auto const &element : result.elements)
|
||||
{
|
||||
auto const [from, to]{ends(element)};
|
||||
peak = std::max({peak, std::abs(from), std::abs(to)});
|
||||
left |= from > 1e-9 || to > 1e-9;
|
||||
right |= from < -1e-9 || to < -1e-9;
|
||||
}
|
||||
auto const line{ImGui::GetTextLineHeight()};
|
||||
auto const inner_left{top.x + pad};
|
||||
auto const inner_width{width - 2.0f * pad};
|
||||
auto const plot_top{top.y + pad * 0.5f + line};
|
||||
auto const plot_bottom{bottom.y - pad * 0.5f - line};
|
||||
auto const both{left && right};
|
||||
auto const axis{both ? std::round((plot_top + plot_bottom) * 0.5f) : plot_bottom};
|
||||
// the curves to one side only grow upwards
|
||||
auto const flip{(false == both && right) ? -1.0f : 1.0f};
|
||||
auto const dragging{band.vertex >= 0};
|
||||
auto const metres{dragging ? band.metres : inner_width / static_cast<float>(result.length)};
|
||||
auto const curvature{dragging ? band.curvature : (peak > 0.0 ? (axis - plot_top) * 0.85f / static_cast<float>(peak) : 0.0f)};
|
||||
auto const x_of = [&](double const Chainage) { return inner_left + static_cast<float>(Chainage) * metres; };
|
||||
auto const y_of = [&](double const K) { return axis - flip * static_cast<float>(K) * curvature; };
|
||||
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
|
||||
int under{-1};
|
||||
int part{0};
|
||||
if (hovered && false == dragging)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
auto const &element{result.elements[i]};
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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 &vertex{design.vertices[Vertex]};
|
||||
return Part == 1 ? vertex.radius : Part == 2 ? vertex.transition_in : vertex.transition_out;
|
||||
};
|
||||
auto const rounded_radius = [](double const Radius) {
|
||||
auto const step{Radius < 1000.0 ? 1.0 : 10.0};
|
||||
return std::clamp(std::round(Radius / step) * step, 10.0, 100000.0);
|
||||
};
|
||||
bool changed{false};
|
||||
if (part != 0 && ImGui::IsItemActivated())
|
||||
{
|
||||
band.vertex = result.elements[under].vertex;
|
||||
band.part = part;
|
||||
band.value = value_of(band.vertex, part);
|
||||
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)};
|
||||
double wanted{value};
|
||||
if (band.part == 1)
|
||||
{
|
||||
int turn{1};
|
||||
for (auto const &element : result.elements)
|
||||
if (element.vertex == band.vertex && element.turn != 0)
|
||||
turn = element.turn;
|
||||
auto const start{y_of(turn / std::max(1.0, band.value)) + (mouse.y - band.press.y)};
|
||||
auto const k{(axis - start) * flip * turn / std::max(1e-6f, curvature)};
|
||||
wanted = rounded_radius(1.0 / std::max(1e-5, static_cast<double>(k)));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const shift{(mouse.x - band.press.x) / std::max(1e-6f, metres)};
|
||||
wanted = std::max(0.0, std::round(band.value + (band.part == 2 ? -shift : shift)));
|
||||
}
|
||||
if (wanted != value)
|
||||
{
|
||||
value = wanted;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SetMouseCursor(band.part == 1 ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
|
||||
}
|
||||
else if (dragging && false == active)
|
||||
{
|
||||
band.vertex = -1;
|
||||
}
|
||||
if (part != 0)
|
||||
{
|
||||
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)};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushClipRect(top, bottom, true);
|
||||
auto const disabled{ImGui::GetColorU32(ImGuiCol_TextDisabled)};
|
||||
list->AddLine(ImVec2(inner_left, axis), ImVec2(inner_left + inner_width, axis), ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f));
|
||||
auto const hot_vertex{dragging ? band.vertex : under >= 0 ? result.elements[under].vertex : -1};
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
auto const &element{result.elements[i]};
|
||||
auto const [from, to]{ends(element)};
|
||||
ImVec2 const a{x_of(element.chainage), y_of(from)};
|
||||
ImVec2 const b{x_of(element.chainage + element.length), y_of(to)};
|
||||
auto const lit{element.vertex >= 0 && element.vertex == hot_vertex};
|
||||
auto const tint{colour(element)};
|
||||
if (element.kind != alignment::element_kind::straight)
|
||||
list->AddQuadFilled(ImVec2(a.x, axis), a, b, ImVec2(b.x, axis), (tint & ~IM_COL32_A_MASK) | (lit ? IM_COL32(0, 0, 0, 90) : IM_COL32(0, 0, 0, 40)));
|
||||
list->AddLine(a, b, tint, lit ? 3.0f : 2.0f);
|
||||
if (i > 0)
|
||||
list->AddLine(ImVec2(a.x, axis - 3.0f), ImVec2(a.x, axis + 3.0f), disabled);
|
||||
}
|
||||
// the labels: the radius over its plateau, the transition lengths along the axis, the km of the ends at the bottom
|
||||
std::vector<std::pair<float, float>> taken;
|
||||
auto const place = [&](float const Left, float const Right) {
|
||||
for (auto const &range : taken)
|
||||
if (Left < range.second + 4.0f && Right > range.first - 4.0f)
|
||||
return false;
|
||||
taken.emplace_back(Left, Right);
|
||||
return true;
|
||||
};
|
||||
auto const start_km{format("km %s", profile::format_chainage(route.origin).c_str())};
|
||||
auto const end_km{format("km %s", profile::format_chainage(route.origin + result.length).c_str())};
|
||||
auto const label_y{bottom.y - pad * 0.25f - line};
|
||||
auto const start_size{ImGui::CalcTextSize(start_km.c_str())};
|
||||
auto const end_size{ImGui::CalcTextSize(end_km.c_str())};
|
||||
place(inner_left, inner_left + start_size.x);
|
||||
list->AddText(ImVec2(inner_left, label_y), disabled, start_km.c_str());
|
||||
if (place(inner_left + inner_width - end_size.x, inner_left + inner_width))
|
||||
list->AddText(ImVec2(inner_left + inner_width - end_size.x, label_y), disabled, end_km.c_str());
|
||||
for (auto const &element : result.elements)
|
||||
{
|
||||
auto const from{x_of(element.chainage)};
|
||||
auto const to{x_of(element.chainage + element.length)};
|
||||
auto const middle{(from + to) * 0.5f};
|
||||
if (dragging && element.vertex == band.vertex)
|
||||
continue;
|
||||
if (element.kind == alignment::element_kind::arc || (element.kind == alignment::element_kind::spiral && element.curvature_start == element.curvature_end && element.radius > 0.0))
|
||||
{
|
||||
auto const &vertex{design.vertices[std::clamp(element.vertex, 0, static_cast<int>(design.vertices.size()) - 1)]};
|
||||
auto text{format("R %.0f", element.radius)};
|
||||
if (vertex.cant > 0.0)
|
||||
{
|
||||
auto const longer{text + format(" \xc2\xb7 %.0f mm", vertex.cant)};
|
||||
if (ImGui::CalcTextSize(longer.c_str()).x <= to - from)
|
||||
text = longer;
|
||||
}
|
||||
auto const size{ImGui::CalcTextSize(text.c_str())};
|
||||
auto const y{ends(element).first * flip >= 0.0 ? y_of(ends(element).first) - size.y - 1.0f : y_of(ends(element).first) + 1.0f};
|
||||
if (size.x <= std::max(to - from, size.x * 0.8f))
|
||||
list->AddText(ImVec2(std::round(middle - size.x * 0.5f), std::round(std::max(top.y + 1.0f, y))), colour(element), text.c_str());
|
||||
}
|
||||
else if (element.kind == alignment::element_kind::transition_in || element.kind == alignment::element_kind::transition_out)
|
||||
{
|
||||
auto const text{format("%.0f m", element.length)};
|
||||
auto const size{ImGui::CalcTextSize(text.c_str())};
|
||||
if (both)
|
||||
{
|
||||
auto const y{ends(element).first + ends(element).second > 0.0 ? axis + 2.0f : axis - size.y - 2.0f};
|
||||
if (size.x <= to - from + 8.0f)
|
||||
list->AddText(ImVec2(std::round(middle - size.x * 0.5f), std::round(y)), colour(element), text.c_str());
|
||||
}
|
||||
else if (place(middle - size.x * 0.5f, middle + size.x * 0.5f))
|
||||
list->AddText(ImVec2(std::round(middle - size.x * 0.5f), label_y), colour(element), text.c_str());
|
||||
}
|
||||
}
|
||||
// the point under the mouse, shown in the view too
|
||||
if (hovered || dragging)
|
||||
{
|
||||
auto const at{std::clamp(static_cast<double>((mouse.x - inner_left) / std::max(1e-6f, metres)), 0.0, result.length)};
|
||||
band.at = at;
|
||||
band.hot = dragging ? -1 : under;
|
||||
if (false == dragging)
|
||||
list->AddLine(ImVec2(mouse.x, plot_top), ImVec2(mouse.x, plot_bottom), IM_COL32(255, 255, 255, 60));
|
||||
}
|
||||
if (dragging && 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 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);
|
||||
list->AddText(at, IM_COL32(255, 255, 255, 255), text.c_str());
|
||||
}
|
||||
ImGui::PopClipRect();
|
||||
if (changed)
|
||||
first_step_done(first_step::plan_band);
|
||||
|
||||
if (part != 0 && false == dragging && ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
|
||||
{
|
||||
auto const &vertex{design.vertices[result.elements[under].vertex]};
|
||||
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);
|
||||
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);
|
||||
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();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool editor_mode::render_route_parameters()
|
||||
{
|
||||
auto &design{m_route.design};
|
||||
bool changed{false};
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
changed |= ImGui::InputDouble(STR_C("Design speed (km/h)"), &design.speed, 0.0, 0.0, "%.0f");
|
||||
ImGui::TextDisabled("%s", STR_C("Design speed (km/h)"));
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
changed |= ImGui::InputDouble("##designspeed", &design.speed, 10.0, 20.0, "%.0f");
|
||||
item_tooltip("Speed the cant, the transitions and the recommended radius are designed for");
|
||||
changed |= value_chips("designspeeds", design.speed, {60.0, 80.0, 100.0, 120.0, 160.0, 200.0}, "%.0f");
|
||||
design.speed = std::max(1.0, design.speed);
|
||||
ImGui::TextDisabled("%s", STR_C("Transition curve"));
|
||||
int shape = static_cast<int>(design.shape);
|
||||
char const *shapes[] = {STR_C("Cubic parabola"), STR_C("Clothoid")};
|
||||
if (ImGui::Combo(STR_C("Transition curve"), &shape, shapes, IM_ARRAYSIZE(shapes)))
|
||||
if (segmented("transitionshape", shape,
|
||||
{{static_cast<int>(alignment::transition_shape::cubic_parabola), STR_C("Cubic parabola"), "The usual one on the Polish lines"},
|
||||
{static_cast<int>(alignment::transition_shape::clothoid), STR_C("Clothoid"), "Curvature growing linearly with the length"}}))
|
||||
{
|
||||
design.shape = static_cast<alignment::transition_shape>(shape);
|
||||
changed = true;
|
||||
}
|
||||
item_tooltip("Cubic parabola: the usual one on the Polish lines; clothoid: curvature growing linearly with the length");
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
if (ImGui::TreeNode(STR_C("Limits")))
|
||||
if (ImGui::TreeNode(STR_C("Norms")))
|
||||
{
|
||||
auto &norms{design.norms};
|
||||
ImGui::PushItemWidth(100.0f);
|
||||
changed |= ImGui::InputDouble(STR_C("Rail axes distance (mm)"), &norms.gauge, 0.0, 0.0, "%.0f");
|
||||
changed |= ImGui::InputDouble(STR_C("Unbalanced acceleration (m/s2)"), &norms.unbalanced, 0.0, 0.0, "%.2f");
|
||||
changed |= ImGui::InputDouble(STR_C("Maximum cant (mm)"), &norms.cant_max, 0.0, 0.0, "%.0f");
|
||||
changed |= ImGui::InputDouble(STR_C("Cant change rate (mm/s)"), &norms.cant_rate, 0.0, 0.0, "%.0f");
|
||||
changed |= ImGui::InputDouble(STR_C("Cant ramp 1:(k*V), k"), &norms.ramp_factor, 0.0, 0.0, "%.1f");
|
||||
changed |= ImGui::InputDouble(STR_C("Unbalanced acc. change rate (m/s3)"), &norms.jerk, 0.0, 0.0, "%.2f");
|
||||
changed |= ImGui::InputDouble(STR_C("Straight between curves (s of travel)"), &norms.tangent_min_time, 0.0, 0.0, "%.1f");
|
||||
ImGui::PopItemWidth();
|
||||
if (begin_fields("##routelimits", 0.6f))
|
||||
{
|
||||
auto const limit = [&](char const *Label, char const *Id, double &Value, char const *Format) {
|
||||
field_row(Label);
|
||||
changed |= ImGui::InputDouble(Id, &Value, 0.0, 0.0, Format);
|
||||
};
|
||||
limit("Rail axes distance (mm)", "##gauge", norms.gauge, "%.0f");
|
||||
limit("Unbalanced acceleration (m/s2)", "##unbalanced", norms.unbalanced, "%.2f");
|
||||
limit("Maximum cant (mm)", "##cantmax", norms.cant_max, "%.0f");
|
||||
limit("Cant change rate (mm/s)", "##cantrate", norms.cant_rate, "%.0f");
|
||||
limit("Cant ramp 1:(k*V), k", "##ramp", norms.ramp_factor, "%.1f");
|
||||
limit("Unbalanced acc. change rate (m/s3)", "##jerk", norms.jerk, "%.2f");
|
||||
limit("Straight between curves (s of travel)", "##tangent", norms.tangent_min_time, "%.1f");
|
||||
ImGui::EndTable();
|
||||
}
|
||||
norms.gauge = std::max(100.0, norms.gauge);
|
||||
norms.cant_rate = std::max(1.0, norms.cant_rate);
|
||||
norms.jerk = std::max(0.01, norms.jerk);
|
||||
@@ -1076,9 +1517,26 @@ bool editor_mode::render_route_vertex(int const Index)
|
||||
auto const count{static_cast<int>(design.vertices.size())};
|
||||
bool changed{false};
|
||||
if (count > 1)
|
||||
ImGui::TextDisabled(STR_C("Curve %d of the S-curve"), Index + 1);
|
||||
ImGui::SeparatorText(format(STR_C("Curve %d of the S-curve"), Index + 1).c_str());
|
||||
// two fields in a row, the name and the unit inside them, a bar in the colour of the element in the band and in the view
|
||||
auto const half{std::floor((ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f)};
|
||||
auto const field = [&](char const *Id, double &Value, float const Speed, char const *Format, ImU32 const Colour, char const *Tooltip) {
|
||||
ImGui::SetNextItemWidth(half);
|
||||
auto const edited{ImGui::DragScalar(Id, ImGuiDataType_Double, &Value, Speed, nullptr, nullptr, Format)};
|
||||
auto const low{ImGui::GetItemRectMin()};
|
||||
auto const high{ImGui::GetItemRectMax()};
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(low, ImVec2(low.x + 3.0f, high.y), Colour, 2.0f, ImDrawFlags_RoundCornersLeft);
|
||||
item_tooltip(Tooltip);
|
||||
return edited;
|
||||
};
|
||||
ImU32 const transition{IM_COL32(255, 140, 30, 255)};
|
||||
changed |= field("##radius", vertex.radius, 5.0f, vertex.compound ? STR_C("R1 %.0f m") : STR_C("R %.0f m"), overlay_color::grip, "Radius of the arc: drag sideways, or a double click to type it");
|
||||
ImGui::SameLine();
|
||||
changed |= field("##cant", vertex.cant, 1.0f, STR_C("cant %.0f mm"), IM_COL32(240, 170, 60, 255), "Height of the outer rail above the inner one");
|
||||
changed |= field("##entry", vertex.transition_in, 1.0f, STR_C("entry %.0f m"), transition, "Length of the transition curve leading into the arc");
|
||||
ImGui::SameLine();
|
||||
changed |= field("##exit", vertex.transition_out, 1.0f, STR_C("exit %.0f m"), transition, "Length of the transition curve leading out of the arc");
|
||||
ImGui::PushItemWidth(110.0f);
|
||||
changed |= ImGui::DragScalar(vertex.compound ? STR_C("Radius R1 (m)") : STR_C("Radius R (m)"), ImGuiDataType_Double, &vertex.radius, 5.0f, nullptr, nullptr, "%.0f");
|
||||
if (ImGui::Checkbox(STR_C("Compound curve (several radii)"), &vertex.compound))
|
||||
{
|
||||
changed = true;
|
||||
@@ -1132,10 +1590,6 @@ bool editor_mode::render_route_vertex(int const Index)
|
||||
item_tooltip("Another arc after the last one, the angle of the curve is shared out by the shares");
|
||||
ImGui::Separator();
|
||||
}
|
||||
changed |= ImGui::DragScalar(STR_C("Transition in (m)"), ImGuiDataType_Double, &vertex.transition_in, 1.0f, nullptr, nullptr, "%.0f");
|
||||
changed |= ImGui::DragScalar(STR_C("Transition out (m)"), ImGuiDataType_Double, &vertex.transition_out, 1.0f, nullptr, nullptr, "%.0f");
|
||||
changed |= ImGui::DragScalar(STR_C("Cant (mm)"), ImGuiDataType_Double, &vertex.cant, 1.0f, nullptr, nullptr, "%.0f");
|
||||
item_tooltip("Height of the outer rail above the inner one");
|
||||
ImGui::PopItemWidth();
|
||||
vertex.radius = std::max(1.0, vertex.radius);
|
||||
vertex.transition_in = std::max(0.0, vertex.transition_in);
|
||||
@@ -1148,9 +1602,12 @@ bool editor_mode::render_route_vertex(int const Index)
|
||||
route_recommend(vertex);
|
||||
changed = true;
|
||||
}
|
||||
item_tooltip("Cant and transitions recommended for the design speed and the radius");
|
||||
auto const recommended{alignment::recommend(design.speed, vertex.radius, design.norms)};
|
||||
ImGui::TextDisabled(STR_C("For %.0f km/h: R min %.0f m, cant %.0f mm (equilibrium %.0f, min %.0f), transition %.0f m"), design.speed, recommended.radius_min, recommended.cant, recommended.cant_equilibrium, recommended.cant_min, recommended.transition);
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
auto const recommended{alignment::recommend(design.speed, vertex.radius, design.norms)};
|
||||
ImGui::SetTooltip("%s\n%s", STR_C("Cant and transitions recommended for the design speed and the radius"),
|
||||
format(STR_C("For %.0f km/h: R min %.0f m, cant %.0f mm (equilibrium %.0f, min %.0f), transition %.0f m"), design.speed, recommended.radius_min, recommended.cant, recommended.cant_equilibrium, recommended.cant_min, recommended.transition).c_str());
|
||||
}
|
||||
}
|
||||
if (ImGui::TreeNode(STR_C("Details")))
|
||||
{
|
||||
@@ -1173,25 +1630,28 @@ void editor_mode::render_route_result()
|
||||
auto const &route{m_route};
|
||||
auto const &design{route.design};
|
||||
auto const &result{route.result};
|
||||
if (result.length > 0.0)
|
||||
{
|
||||
auto const pieces{alignment::minimum_pieces(result, design)};
|
||||
ImGui::Text(STR_C("Length %.2f m (now %.2f m), %zu elements"), result.length, route.chain.length, result.elements.size());
|
||||
ImGui::TextDisabled(STR_C("Paths: %zu in the fragment, %zu needed%s"), route.chain.tracks.size(), pieces, pieces > route.chain.tracks.size() ? STR_C(" (copies of the neighbours will be added)") : "");
|
||||
if (result.start_extension > 0.0 || result.end_extension > 0.0)
|
||||
ImGui::Text(STR_C("Takes over %.2f m of the straight at the start, %.2f m at the end"), result.start_extension, result.end_extension);
|
||||
}
|
||||
if (result.length > 0.0 && (result.start_extension > 0.0 || result.end_extension > 0.0))
|
||||
ImGui::Text(STR_C("Takes over %.2f m of the straight at the start, %.2f m at the end"), result.start_extension, result.end_extension);
|
||||
for (auto const &error : result.errors)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", error.c_str());
|
||||
for (auto const &warning : result.warnings)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", warning.c_str());
|
||||
|
||||
ImGui::PushID("routeresult");
|
||||
if (result.length > 0.0 && ImGui::TreeNode(STR_C("Details")))
|
||||
{
|
||||
auto const pieces{alignment::minimum_pieces(result, design)};
|
||||
ImGui::Text(STR_P("Length %.2f m (now %.2f m), %zu element", "Length %.2f m (now %.2f m), %zu elements", result.elements.size()), result.length, route.chain.length, result.elements.size());
|
||||
ImGui::TextDisabled(STR_C("Paths: %zu in the fragment, %zu needed%s"), route.chain.tracks.size(), pieces, pieces > route.chain.tracks.size() ? STR_C(" (copies of the neighbours will be added)") : "");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (ImGui::TreeNode(STR_C("Elements")))
|
||||
{
|
||||
ImGui::TextDisabled(STR_C("Straight available beyond the ends: %.2f / %.2f m"), design.start_reserve, design.end_reserve);
|
||||
char const *kinds[] = {"straight", "transition", "curve", "transition", "spiral"};
|
||||
static char const *const kinds[] = {"straight", "transition", "arc", "transition", "transition between arcs"};
|
||||
for (auto const &e : result.elements)
|
||||
ImGui::Text("km %s %-10s L %8.2f m%s", profile::format_chainage(m_route.origin + e.chainage).c_str(), kinds[static_cast<int>(e.kind)], e.length, e.kind == alignment::element_kind::straight ? "" : (" R " + std::to_string(static_cast<int>(std::round(e.radius))) + " m").c_str());
|
||||
ImGui::Text("km %s %-10s L %8.2f m%s", profile::format_chainage(m_route.origin + e.chainage).c_str(), STR_C(kinds[static_cast<int>(e.kind)]), e.length, e.kind == alignment::element_kind::straight ? "" : (" R " + std::to_string(static_cast<int>(std::round(e.radius))) + " m").c_str());
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
@@ -1200,7 +1660,10 @@ void editor_mode::render_route_result()
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.65f, 0.3f, 1.0f));
|
||||
if (ImGui::Button(STR_C("Apply to the scenery"), ImVec2(-1.0f, 28.0f)))
|
||||
{
|
||||
route_apply();
|
||||
first_step_done(first_step::plan_apply);
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user