17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 16:14:52 +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:
maj00r
2026-10-19 15:27:50 +02:00
parent 5a00d88be9
commit ff1710d9ea
78 changed files with 15011 additions and 4386 deletions

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editormodeshared.h"
#include "application/editoruilayer.h"
#include "application/editorprojection.h"
#include "editor/editorFormat.hpp"
@@ -525,7 +526,7 @@ void editor_mode::hekto_remove_duplicates()
if (found.duplicate)
instances.push_back(found.post.instance);
auto const removed{remove_templates(instances)};
tool.status = format(STR_C("%zu duplicate hectometre posts removed, Ctrl+Z brings them back"), removed);
tool.status = format(STR_P("%zu duplicate hectometre post removed, Ctrl+Z brings it back", "%zu duplicate hectometre posts removed, Ctrl+Z brings them back", static_cast<long long>(removed)), removed);
WriteLog("Editor: " + tool.status);
tool.dirty = true;
}
@@ -548,7 +549,9 @@ void editor_mode::hekto_place()
}
std::string error;
auto const placed{place_templates(items, error)};
tool.status = placed > 0 ? format(STR_C("%zu hectometre posts placed, Ctrl+Z takes them back"), placed) : error;
tool.status = placed > 0 ? format(STR_P("%zu hectometre post placed, Ctrl+Z takes it back", "%zu hectometre posts placed, Ctrl+Z takes them back", static_cast<long long>(placed)), placed) : error;
if (placed > 0)
first_step_done(first_step::posts);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
@@ -568,7 +571,7 @@ void editor_mode::hekto_fix()
moves.emplace_back(tool.found[post.standing].post, item);
}
auto const moved{move_templates(moves)};
tool.status = format(STR_C("%zu hectometre posts moved to their places, Ctrl+Z takes it back"), moved);
tool.status = format(STR_P("%zu hectometre post moved to its place, Ctrl+Z takes it back", "%zu hectometre posts moved to their places, Ctrl+Z takes it back", static_cast<long long>(moved)), moved);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
@@ -590,44 +593,46 @@ void editor_mode::render_hekto_ui()
if (Changed)
tool.dirty = true;
};
ImGui::PushItemWidth(110.0f);
changed(ImGui::InputDouble(STR_C("Kilometre at the point (km)"), &tool.km, 0.0, 0.0, "%.3f"));
ImGui::SameLine();
if (ImGui::SmallButton(STR_C("Read from the posts")))
if (begin_fields("##hektokm", 0.45f))
{
tool.km_read = false;
tool.dirty = true;
field_row("Kilometre at the point (km)");
changed(ImGui::InputDouble("##hektokilometre", &tool.km, 0.0, 0.0, "%.3f"));
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
if (ImGui::Button(STR_C("Read from the posts"), ImVec2(-FLT_MIN, 0.0f)))
{
tool.km_read = false;
tool.dirty = true;
}
ImGui::EndTable();
}
ImGui::TextDisabled("%s", STR_C("The kilometrage grows"));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("along the line"), &tool.growth, 1));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("against it"), &tool.growth, -1));
changed(segmented("growth", tool.growth, {{1, STR_C("along the line")}, {-1, STR_C("against it")}}));
ImGui::TextDisabled("%s", STR_C("Side, looking where it grows"));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("right"), &tool.side, 1));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("left"), &tool.side, -1));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("alternately"), &tool.side, 0));
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("The even hectometres on the right, the odd ones on the left"));
changed(ImGui::InputDouble(STR_C("From the axis (m)"), &tool.offset, 0.0, 0.0, "%.2f"));
tool.offset = std::clamp(tool.offset, 1.5, 30.0);
char const *styles[] = {"nowy", "stary", "zgnity", "mixed"};
ImGui::Combo(STR_C("Posts"), &tool.style, styles, IM_ARRAYSIZE(styles));
char const *turns[] = {"0", "90", "180", "270"};
int turn{tool.turn / 90};
if (ImGui::Combo(STR_C("Turned by (deg)"), &turn, turns, IM_ARRAYSIZE(turns)))
changed(segmented("side", tool.side, {{1, STR_C("on the right")}, {-1, STR_C("on the left")}, {0, STR_C("alternately"), "The even hectometres on the right, the odd ones on the left"}}));
if (ImGui::TreeNode(STR_C("More settings")))
{
tool.turn = turn * 90;
tool.dirty = true;
ImGui::TextDisabled("%s", STR_C("Look"));
segmented("hektostyle", tool.style, {{0, STR_C("new")}, {1, STR_C("old")}, {2, STR_C("rotten")}, {3, STR_C("mixed"), "New, old and rotten ones at random, mostly new"}});
ImGui::TextDisabled("%s", STR_C("Turned by (deg)"));
if (segmented("hektoturn", tool.turn, {{0, "0"}, {90, "90"}, {180, "180"}, {270, "270"}}))
tool.dirty = true;
if (begin_fields("##hektofields", 0.6f))
{
field_row("From the axis (m)");
changed(ImGui::InputDouble("##hektooffset", &tool.offset, 0.0, 0.0, "%.2f"));
field_row("Along the line each way (m)");
changed(ImGui::InputDouble("##hektoreach", &tool.reach, 0.0, 0.0, "%.0f"));
field_row("Tolerance (m)");
changed(ImGui::InputDouble("##hektotolerance", &tool.tolerance, 0.0, 0.0, "%.1f"));
item_tooltip("A post standing farther than this from its place counts as wrong");
ImGui::EndTable();
}
ImGui::TreePop();
}
changed(ImGui::InputDouble(STR_C("Along the line each way (m)"), &tool.reach, 0.0, 0.0, "%.0f"));
tool.offset = std::clamp(tool.offset, 1.5, 30.0);
tool.reach = std::clamp(tool.reach, 100.0, 100000.0);
changed(ImGui::InputDouble(STR_C("A post standing off by more (m) is wrong"), &tool.tolerance, 0.0, 0.0, "%.1f"));
tool.tolerance = std::clamp(tool.tolerance, 0.5, 50.0);
ImGui::PopItemWidth();
hekto_update();
if (false == tool.error.empty())
{
@@ -637,15 +642,21 @@ void editor_mode::render_hekto_ui()
auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })};
auto const wrong{std::count_if(tool.found.begin(), tool.found.end(), [](hekto_found const &Post) { return Post.wrong; })};
auto const duplicates{std::count_if(tool.found.begin(), tool.found.end(), [](hekto_found const &Post) { return Post.duplicate; })};
ImGui::Text(STR_C("Line %.0f m, km %s - %s: %d posts to place, %zu standing, %d of them wrong"), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, tool.posts.empty() ? "-" : km_text(std::min(tool.posts.front().hectometres, tool.posts.back().hectometres)).c_str(),
tool.posts.empty() ? "-" : km_text(std::max(tool.posts.front().hectometres, tool.posts.back().hectometres)).c_str(), static_cast<int>(missing), tool.found.size(), static_cast<int>(wrong));
if (missing > 0 && ImGui::Button(format(STR_C("Place %d posts"), static_cast<int>(missing)).c_str()))
hekto_place();
ImGui::Text(STR_C("To place: %d"), static_cast<int>(missing));
if (ImGui::IsItemHovered())
ImGui::SetTooltip(STR_C("Line %.0f m, km %s - %s. Standing: %zu, %d of them wrong"), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, tool.posts.empty() ? "-" : km_text(std::min(tool.posts.front().hectometres, tool.posts.back().hectometres)).c_str(),
tool.posts.empty() ? "-" : km_text(std::max(tool.posts.front().hectometres, tool.posts.back().hectometres)).c_str(), tool.found.size(), static_cast<int>(wrong));
if (missing > 0)
{
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(format(STR_P("Place %d post", "Place %d posts", static_cast<long long>(missing)), static_cast<int>(missing)).c_str(), ImVec2(-1.0f, std::round(ImGui::GetFrameHeight() * 1.3f))))
hekto_place();
ImGui::PopStyleColor(2);
}
if (duplicates > 0)
{
if (missing > 0)
ImGui::SameLine();
if (ImGui::Button(format(STR_C("Remove %d duplicate posts"), static_cast<int>(duplicates)).c_str()))
if (ImGui::Button(format(STR_P("Remove %d duplicate post", "Remove %d duplicate posts", static_cast<long long>(duplicates)), static_cast<int>(duplicates)).c_str()))
hekto_remove_duplicates();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Posts with a number another post nearer to its place already has"));
@@ -654,9 +665,9 @@ void editor_mode::render_hekto_ui()
{
if (wrong > 0)
{
if (missing > 0 || duplicates > 0)
if (duplicates > 0)
ImGui::SameLine();
if (ImGui::Button(format(STR_C("Move %d wrong posts to their places"), static_cast<int>(wrong)).c_str()))
if (ImGui::Button(format(STR_P("Move %d wrong post to its place", "Move %d wrong posts to their places", static_cast<long long>(wrong)), static_cast<int>(wrong)).c_str()))
hekto_fix();
}
ImGui::BeginChild("##hektowrong", ImVec2(0.0f, std::min(6, static_cast<int>(wrong + duplicates)) * ImGui::GetTextLineHeightWithSpacing() + 6.0f), true);
@@ -846,7 +857,7 @@ void editor_mode::fouling_place(int const Only)
}
std::string error;
auto const placed{place_templates(items, error)};
tool.status = placed > 0 ? format(STR_C("%zu fouling point markers placed, Ctrl+Z takes them back"), placed) : error;
tool.status = placed > 0 ? format(STR_P("%zu fouling point marker placed, Ctrl+Z takes it back", "%zu fouling point markers placed, Ctrl+Z takes them back", static_cast<long long>(placed)), placed) : error;
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
@@ -877,7 +888,7 @@ void editor_mode::fouling_fix(int const Only)
moves.emplace_back(found.marker, item);
}
auto const moved{move_templates(moves)};
tool.status = format(STR_C("%zu fouling point markers moved to their places, Ctrl+Z takes it back"), moved);
tool.status = format(STR_P("%zu fouling point marker moved to its place, Ctrl+Z takes it back", "%zu fouling point markers moved to their places, Ctrl+Z takes it back", static_cast<long long>(moved)), moved);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
@@ -917,11 +928,7 @@ void editor_mode::render_fouling_ui()
if (Changed)
tool.dirty = true;
};
changed(ImGui::RadioButton(STR_C("The selected switch"), &tool.scope, 0));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("Around the camera"), &tool.scope, 1));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("Whole scenery"), &tool.scope, 2));
changed(segmented("foulingscope", tool.scope, {{0, STR_C("The selected switch")}, {1, STR_C("Around the camera")}, {2, STR_C("Whole scenery")}}));
fouling_update();
if (false == tool.error.empty())
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", tool.error.c_str());
@@ -930,14 +937,22 @@ void editor_mode::render_fouling_ui()
ImGui::TextWrapped("%s", tool.status.c_str());
auto const missing{std::count_if(tool.points.begin(), tool.points.end(), [](fouling_point const &Point) { return Point.standing < 0; })};
auto const wrong{std::count_if(tool.found.begin(), tool.found.end(), [](fouling_found const &Found) { return Found.wrong; })};
if (missing > 0 && ImGui::Button(format(STR_C("Place %d markers"), static_cast<int>(missing)).c_str()))
fouling_place();
auto const tall{std::round(ImGui::GetFrameHeight() * 1.3f)};
if (missing > 0)
{
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(format(STR_P("Place %d marker", "Place %d markers", static_cast<long long>(missing)), static_cast<int>(missing)).c_str(), ImVec2(-1.0f, tall)))
fouling_place();
ImGui::PopStyleColor(2);
}
if (wrong > 0)
{
if (missing > 0)
ImGui::SameLine();
if (ImGui::Button(format(STR_C("Move %d wrong markers to their places"), static_cast<int>(wrong)).c_str()))
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.4f, 0.1f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.48f, 0.15f, 1.0f));
if (ImGui::Button(format(STR_P("Move %d wrong marker to its place", "Move %d wrong markers to their places", static_cast<long long>(wrong)), static_cast<int>(wrong)).c_str(), ImVec2(-1.0f, tall)))
fouling_fix();
ImGui::PopStyleColor(2);
}
if (missing + wrong > 0)
{
@@ -974,36 +989,42 @@ void editor_mode::render_fouling_ui()
ImGui::EndChild();
}
}
if (ImGui::CollapsingHeader(STR_C("Settings##fouling")))
if (ImGui::TreeNode(STR_C("More settings")))
{
ImGui::TextDisabled("%s", STR_C("W17 where the axes of the tracks meeting at a switch are the spacing apart,\nhalf way between them"));
ImGui::PushItemWidth(110.0f);
if (tool.scope == 1)
changed(ImGui::InputDouble(STR_C("Around the camera (m)"), &tool.reach, 0.0, 0.0, "%.0f"));
changed(ImGui::InputDouble(STR_C("Spacing of the axes (m)"), &tool.spacing, 0.0, 0.0, "%.2f"));
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("3.5 m on the straight track; more on the curves, where the cars lean out"));
changed(ImGui::InputDouble(STR_C("A marker off by more (m) is wrong"), &tool.tolerance, 0.0, 0.0, "%.1f"));
changed(ImGui::RadioButton(STR_C("On the ground"), &tool.height_mode, 0));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("Over the rail heads"), &tool.height_mode, 1));
if (tool.height_mode == 1)
if (begin_fields("##foulingfields", 0.6f))
{
ImGui::SameLine();
changed(ImGui::InputDouble(STR_C("by (m)"), &tool.height, 0.0, 0.0, "%.3f"));
changed(ImGui::InputDouble(STR_C("A marker higher or lower by more (m) is wrong"), &tool.height_tolerance, 0.0, 0.0, "%.3f"));
if (tool.scope == 1)
{
field_row("Around the camera (m)");
changed(ImGui::InputDouble("##foulingreach", &tool.reach, 0.0, 0.0, "%.0f"));
}
field_row("Spacing of the axes (m)");
changed(ImGui::InputDouble("##foulingspacing", &tool.spacing, 0.0, 0.0, "%.2f"));
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s\n%s", STR_C("W17 where the axes of the tracks meeting at a switch are the spacing apart,\nhalf way between them"), STR_C("3.5 m on the straight track; more on the curves, where the cars lean out"));
field_row("Tolerance (m)");
changed(ImGui::InputDouble("##foulingtolerance", &tool.tolerance, 0.0, 0.0, "%.1f"));
item_tooltip("A marker standing farther than this from its place counts as wrong");
ImGui::EndTable();
}
changed(segmented("foulingheight", tool.height_mode, {{0, STR_C("On the ground")}, {1, STR_C("Over the rail heads")}}));
if (tool.height_mode == 1 && begin_fields("##foulingheights", 0.6f))
{
field_row("by (m)");
changed(ImGui::InputDouble("##foulingby", &tool.height, 0.0, 0.0, "%.3f"));
field_row("Height tolerance (m)");
changed(ImGui::InputDouble("##foulingheighttolerance", &tool.height_tolerance, 0.0, 0.0, "%.3f"));
item_tooltip("A marker higher or lower than its place by more than this counts as wrong");
tool.height_tolerance = std::clamp(tool.height_tolerance, 0.005, 1.0);
ImGui::EndTable();
}
char const *models[] = {"w17", "w17_new", "w17_old", "w17_dwuteo"};
ImGui::Combo(STR_C("Marker"), &tool.model, models, IM_ARRAYSIZE(models));
char const *turns[] = {"0", "90", "180", "270"};
int turn{tool.turn / 90};
if (ImGui::Combo(STR_C("Turned by (deg)"), &turn, turns, IM_ARRAYSIZE(turns)))
{
tool.turn = turn * 90;
ImGui::TextDisabled("%s", STR_C("Marker"));
segmented("foulingmodel", tool.model,
{{0, "w17", "Round post (ip/pkp/w17.inc)"}, {1, "w17_new", "Round post, taller than w17 (ip/pkp/w17_new.inc)"}, {2, "w17_old", "Square post (ip/pkp/w17_old.inc)"}, {3, STR_C("I-beam"), "Post of an HEB I-beam (ip/pkp/w17_dwuteo.inc)"}});
ImGui::TextDisabled("%s", STR_C("Turned by (deg)"));
if (segmented("foulingturn", tool.turn, {{0, "0"}, {90, "90"}, {180, "180"}, {270, "270"}}))
tool.dirty = true;
}
ImGui::PopItemWidth();
ImGui::TreePop();
}
tool.reach = std::clamp(tool.reach, 50.0, 50000.0);
tool.spacing = std::clamp(tool.spacing, 2.0, 10.0);