17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-23 14:55:15 +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

@@ -675,23 +675,19 @@ void editor_mode::start_focus(scene::basic_node *node, double duration)
double const len = glm::length(dir);
dir = len > 1e-3 ? dir / len : glm::normalize(glm::dvec3(1.0, 0.5, 1.0));
// looking from there straight at the object
fly_to(center, -dir, distance, duration);
}
void editor_mode::fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration)
{
m_focus_start_pos = Camera.Pos;
m_focus_start_angle = Camera.Angle;
m_focus_target_pos = center + dir * distance;
// target orientation looks from the target position straight at the object
glm::dvec3 look = center - m_focus_target_pos;
double const looklen = glm::length(look);
if (looklen > 1e-6)
look /= looklen;
m_focus_target_angle = glm::vec3(
static_cast<float>(std::asin(glm::clamp(look.y, -1.0, 1.0))), // pitch
static_cast<float>(std::atan2(-look.x, -look.z)), // yaw
0.0f); // roll
m_focus_target_pos = Target - Look * Distance;
m_focus_target_angle = glm::vec3(static_cast<float>(std::asin(std::clamp(Look.y, -1.0, 1.0))), static_cast<float>(std::atan2(-Look.x, -Look.z)), 0.0f);
m_focus_active = true;
m_focus_time = 0.0;
m_focus_duration = duration;
m_focus_duration = Duration;
}
void editor_mode::snap_to_ground(scene::basic_node *node, bool const Alone, bool const Joined)

View File

@@ -1374,7 +1374,7 @@ class editor_mode : public application_mode, private editor_track::observer
void draw_track_hover() const;
struct track_intent
{
enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style };
enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style, fouling };
kind what{kind::none};
TTrack *track{nullptr};
int path{0};
@@ -1447,6 +1447,8 @@ class editor_mode : public application_mode, private editor_track::observer
void render_track_context();
void split_track_at(TTrack &Track, glm::dvec3 const &Point);
void focus_track(TTrack &Track, int const Path, glm::dvec3 const &Point);
// the camera flies over Duration seconds to the place Distance back from the target along Look, looking along it
void fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration = 0.6);
// paths found by their names
struct track_search
{
@@ -1737,7 +1739,7 @@ class editor_mode : public application_mode, private editor_track::observer
{
bool open{false};
bool expand{false};
int scope{0}; // 0: the selected switch, 1: around the camera, 2: the whole scenery
int scope{1}; // 0: the selected switch, 1: around the camera, 2: the whole scenery
double reach{1500.0};
double spacing{3.5}; // m between the axes
double tolerance{2.0}; // m, a marker farther than this from its point is wrong
@@ -1756,8 +1758,13 @@ class editor_mode : public application_mode, private editor_track::observer
};
fouling_tool m_fouling;
void fouling_update();
void fouling_place();
void fouling_fix();
// the markers put at the fouling points without one, or at the one only
void fouling_place(int const Only = -1);
// the markers standing wrong moved to their places, or the one found only
void fouling_fix(int const Only = -1);
// the fouling point without a marker or the marker standing wrong under the cursor: the index of the point or of the
// marker found, and whether it's a marker
std::optional<std::pair<int, bool>> fouling_hit() const;
void render_fouling_ui();
void draw_fouling_overlay() const;

View File

@@ -677,13 +677,7 @@ void editor_mode::gauge_focus(int const Index)
look = glm::length(look) > 1e-6 ? glm::normalize(look) : glm::dvec3(0.0, 0.0, 1.0);
if (glm::dot(Camera.Pos - hit.point, look) > 0.0)
look = -look;
m_focus_start_pos = Camera.Pos;
m_focus_start_angle = Camera.Angle;
m_focus_target_pos = hit.point - look * gauge_view_distance;
m_focus_target_angle = glm::vec3(static_cast<float>(std::asin(std::clamp(look.y, -1.0, 1.0))), 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(hit.point, look, gauge_view_distance);
}
void editor_mode::gauge_spot(gauge_hit const &Hit)

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

View File

@@ -783,6 +783,11 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm
values[pair.substr(0, equals)] = pair.substr(equals + 1);
}
auto &tool{m_fouling};
if (values["show"] == "1")
{
show_track_tab(track_tab::lineside);
tool.expand = true;
}
tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2");
tool.height_mode = values["mode"] == "ground" ? 0 : 1;
tool.height = std::stod(values.count("height") ? values["height"] : "0");
@@ -793,6 +798,19 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm
for (auto const &found : tool.found)
if (found.wrong && shown++ < 5)
WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error));
if (values["place"] == "one")
{
// the first fouling point without a marker alone, as a click on it in the view
for (int index = 0; index < static_cast<int>(tool.points.size()); ++index)
if (tool.points[index].standing < 0)
{
fouling_place(index);
break;
}
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling one: " + tool.status);
}
if (values["place"] == "1")
{
fouling_place();

View File

@@ -437,6 +437,14 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
return make(kind::switch_at, "Press and drag: a switch here, along sets its direction, sideways its side");
}
}
if (m_track_tab == track_tab::lineside && m_fouling.open)
if (auto const hit{fouling_hit()}; hit.has_value())
{
intent.index = hit->first;
intent.path = hit->second ? 1 : 0;
intent.position = hit->second ? m_fouling.found[hit->first].marker.location : m_fouling.points[hit->first].position;
return make(kind::fouling, hit->second ? "Click: move this W17 to its place" : "Click: put the W17 here");
}
if (m_hover.track != nullptr)
{
intent.track = m_hover.track;
@@ -552,6 +560,12 @@ void editor_mode::track_press(track_intent const &Intent)
handle_drag_start(Intent.position);
return;
case kind::switch_at: start_switch_placement(); return;
case kind::fouling:
if (Intent.path == 1)
fouling_fix(Intent.index);
else
fouling_place(Intent.index);
return;
case kind::extend_end:
if (false == start_extend(Intent.snap))
point_drag_start(Intent.snap.point);
@@ -700,13 +714,7 @@ void editor_mode::focus_track(TTrack &Track, int const Path, glm::dvec3 const &P
if (glm::dot(Camera.Pos - Point, look) > 0.0)
look = -look;
look = glm::normalize(look - glm::dvec3(0.0, 0.25, 0.0));
m_focus_start_pos = Camera.Pos;
m_focus_start_angle = Camera.Angle;
m_focus_target_pos = Point - look * kFocusDistance;
m_focus_target_angle = glm::vec3(static_cast<float>(std::asin(std::clamp(look.y, -1.0, 1.0))), 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(Point, look, kFocusDistance);
}
void editor_mode::render_track_context()

View File

@@ -7034,3 +7034,21 @@ msgstr "Wciśnij na nim i przeciągnij: wzdłuż kierunek, w bok strona"
msgid "Connection: drag from a straight onto the other track"
msgstr "Połączenie: przeciągnij z prostej na drugi tor"
msgid "Click the green or red point in the view: that one only"
msgstr "Kliknij zielony albo czerwony punkt w widoku: tylko ten"
msgid "%s: no W17"
msgstr "%s: brak W17"
msgid "%s: W17 %.1f m off, %+.2f m high"
msgstr "%s: W17 %.1f m od miejsca, %+.2f m w pionie"
msgid "Settings##fouling"
msgstr "Ustawienia##fouling"
msgid "Click: move this W17 to its place"
msgstr "Klik: przesuń ten W17 na miejsce"
msgid "Click: put the W17 here"
msgstr "Klik: postaw tu W17"