17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-20 18:34:53 +02:00

feat(editor): W17 placed and checked one by one: a list of the switches without one or with one standing wrong, a click on its point in the view puts or moves that one

- the panel shows the scope and what's found first, then Place and Move for all, then each missing or wrong marker with its
  own button and a click flying the camera there; the spacing, tolerances, height, model and turn are under Settings
- a click on a green point (no marker) or a red one (marker standing wrong) in the view puts or moves that marker alone
- the check starts around the camera
- one camera flight, fly_to, instead of four copies of it
- self-test fouling show=1 and place=one
This commit is contained in:
maj00r
2026-10-18 02:00:54 +02:00
parent d222c833fc
commit 52a762879c
7 changed files with 190 additions and 88 deletions

View File

@@ -38,6 +38,7 @@ using geometry::plan_of;
double const kSampleStep{2.0}; // m between the samples of the line
double const kLineCorridor{15.0}; // m from the axis, posts farther away belong to another line
double const kOverlayRange{3000.0};
float const kFoulingPick{12.0f}; // px from a point of the fouling overlay a click takes it
double const kFoulingTrace{250.0}; // m from the start of a switch the tracks are followed for
double const kRailHeight{0.18}; // m from the points of a track to the heads of its rails
int const kRailCategory{1};
@@ -666,14 +667,7 @@ void editor_mode::render_hekto_ui()
auto const label{post.duplicate ? format(STR_C("km %s duplicate##%u"), km_text(post.hectometres).c_str(), post.post.instance) : format(STR_C("km %s stands %.1f m from its place##%u"), km_text(post.hectometres).c_str(), post.distance, post.post.instance)};
if (ImGui::Selectable(label.c_str()))
{
glm::dvec3 const look{glm::normalize(glm::dvec3{-1.0, -0.8, -1.0})};
m_focus_start_pos = Camera.Pos;
m_focus_start_angle = Camera.Angle;
m_focus_target_pos = post.post.location - look * 20.0;
m_focus_target_angle = glm::vec3(static_cast<float>(std::asin(look.y)), static_cast<float>(std::atan2(-look.x, -look.z)), 0.0f);
m_focus_active = true;
m_focus_time = 0.0;
m_focus_duration = 0.6;
fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0);
}
}
ImGui::EndChild();
@@ -832,14 +826,15 @@ void editor_mode::fouling_update()
tool.status = format(STR_C("%zu switches, %zu fouling points: %d without a marker, %d markers standing wrong"), switches.size(), tool.points.size(), static_cast<int>(missing), static_cast<int>(wrong));
}
void editor_mode::fouling_place()
void editor_mode::fouling_place(int const Only)
{
auto &tool{m_fouling};
fouling_update();
std::vector<template_item> items;
for (auto const &point : tool.points)
for (int i = 0; i < static_cast<int>(tool.points.size()); ++i)
{
if (point.standing >= 0)
auto const &point{tool.points[i]};
if (point.standing >= 0 || (Only >= 0 && i != Only))
continue;
template_item item;
item.file = kFoulingModels[std::clamp(tool.model, 0, 3)];
@@ -856,14 +851,15 @@ void editor_mode::fouling_place()
tool.dirty = true;
}
void editor_mode::fouling_fix()
void editor_mode::fouling_fix(int const Only)
{
auto &tool{m_fouling};
fouling_update();
std::vector<std::pair<standing_template, template_item>> moves;
for (auto const &found : tool.found)
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
{
if (false == found.wrong)
auto const &found{tool.found[i]};
if (false == found.wrong || (Only >= 0 && i != Only))
continue;
template_item item;
auto const &point{tool.points[found.point]};
@@ -886,10 +882,37 @@ void editor_mode::fouling_fix()
tool.dirty = true;
}
std::optional<std::pair<int, bool>> editor_mode::fouling_hit() const
{
auto const &tool{m_fouling};
screen_projection const projection;
auto const &mouse{ImGui::GetIO().MousePos};
glm::dvec3 const camera{Global.pCamera.Pos};
std::optional<std::pair<int, bool>> result;
auto best{kFoulingPick * kFoulingPick};
auto const consider = [&](glm::dvec3 const &Position, int const Index, bool const Marker) {
ImVec2 screen;
if (glm::distance(Position, camera) > kOverlayRange || false == projection.project(Position, screen))
return;
auto const distance2{(screen.x - mouse.x) * (screen.x - mouse.x) + (screen.y - mouse.y) * (screen.y - mouse.y)};
if (distance2 < best)
{
best = distance2;
result = std::make_pair(Index, Marker);
}
};
for (int i = 0; i < static_cast<int>(tool.points.size()); ++i)
if (tool.points[i].standing < 0)
consider(tool.points[i].position, i, false);
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
if (tool.found[i].wrong)
consider(tool.found[i].marker.location, i, true);
return result;
}
void editor_mode::render_fouling_ui()
{
auto &tool{m_fouling};
ImGui::TextDisabled("%s", STR_C("W17 where the axes of the tracks meeting at a switch are the spacing apart,\nhalf way between them"));
auto const changed = [&](bool const Changed) {
if (Changed)
tool.dirty = true;
@@ -899,54 +922,92 @@ void editor_mode::render_fouling_ui()
changed(ImGui::RadioButton(STR_C("Around the camera"), &tool.scope, 1));
ImGui::SameLine();
changed(ImGui::RadioButton(STR_C("Whole scenery"), &tool.scope, 2));
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)
fouling_update();
if (false == tool.error.empty())
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", tool.error.c_str());
else
{
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();
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()))
fouling_fix();
}
if (missing + wrong > 0)
{
// each one: a click flies there, the button fixes it alone; in the view a click on its point does the same
ImGui::TextDisabled("%s", STR_C("Click the green or red point in the view: that one only"));
ImGui::BeginChild("##foulinglist", ImVec2(0.0f, std::min(8, static_cast<int>(missing + wrong)) * ImGui::GetFrameHeightWithSpacing() + 6.0f), true);
auto const look{glm::normalize(glm::dvec3{-1.0, -0.8, -1.0})};
for (int i = 0; i < static_cast<int>(tool.points.size()); ++i)
{
auto const &point{tool.points[i]};
if (point.standing >= 0)
continue;
ImGui::PushID(i);
if (ImGui::SmallButton(STR_C("Place")))
fouling_place(i);
ImGui::SameLine();
if (ImGui::Selectable(format(STR_C("%s: no W17"), point.track->name().c_str()).c_str()))
fly_to(point.position, look, 25.0);
ImGui::PopID();
}
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
{
auto const &found{tool.found[i]};
if (false == found.wrong)
continue;
ImGui::PushID(1000000 + i);
if (ImGui::SmallButton(STR_C("Fix")))
fouling_fix(i);
ImGui::SameLine();
if (ImGui::Selectable(format(STR_C("%s: W17 %.1f m off, %+.2f m high"), tool.points[found.point].track->name().c_str(), found.error, found.height_error).c_str()))
fly_to(found.marker.location, look, 25.0);
ImGui::PopID();
}
ImGui::EndChild();
}
}
if (ImGui::CollapsingHeader(STR_C("Settings##fouling")))
{
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::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"));
tool.height_tolerance = std::clamp(tool.height_tolerance, 0.005, 1.0);
changed(ImGui::RadioButton(STR_C("Over the rail heads"), &tool.height_mode, 1));
if (tool.height_mode == 1)
{
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"));
tool.height_tolerance = std::clamp(tool.height_tolerance, 0.005, 1.0);
}
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;
tool.dirty = true;
}
ImGui::PopItemWidth();
}
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;
tool.dirty = true;
}
ImGui::PopItemWidth();
tool.reach = std::clamp(tool.reach, 50.0, 50000.0);
tool.spacing = std::clamp(tool.spacing, 2.0, 10.0);
tool.tolerance = std::clamp(tool.tolerance, 0.2, 20.0);
fouling_update();
if (false == tool.error.empty())
{
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.3f, 1.0f), "%s", tool.error.c_str());
return;
}
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();
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()))
fouling_fix();
}
}
void editor_mode::draw_fouling_overlay() const