mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 10:24:53 +02:00
feat(editor): the joints check finds the tracks lying off the terrain model
The joints check also compares the tracks with the terrain model (NMT), every 5 m. Bridges, tunnels and tracks over others are left out. - Each path lying higher or lower than the model by more than the threshold (0.5 m by default) is listed once, at its farthest place off. - The places are compared with the same offset the model has against the scenery; a scenery not fitted to the model as a whole is measured about its median. - The squares of the model are loaded in the background, and the check waits for them with a progress bar. - These entries can't be fixed from the list. In the top view, the overlay shows the issues over the whole screen. - Self-test: jointsmodel <threshold>.
This commit is contained in:
@@ -75,14 +75,14 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
// joint of the paths found wrong by the joints check
|
||||
struct joint_issue
|
||||
{
|
||||
enum class kind { gap, step, kink, cant, grade, open };
|
||||
enum class kind { gap, step, kink, cant, grade, model, open };
|
||||
kind type{kind::gap};
|
||||
TTrack *track{nullptr};
|
||||
editor_track::point_ref point;
|
||||
TTrack *other{nullptr};
|
||||
editor_track::point_ref theirs;
|
||||
glm::dvec3 position{0.0};
|
||||
double value{0.0}; // m, deg, mm, per mille
|
||||
double value{0.0}; // m, deg, mm, per mille; m of the track over the terrain model
|
||||
bool fixed{false};
|
||||
};
|
||||
// constructors
|
||||
@@ -2511,9 +2511,19 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
double angle{0.2}; // deg
|
||||
double cant{5.0}; // mm
|
||||
double grade{10.0}; // per mille
|
||||
double model{0.5}; // m, the track over or under the terrain model (NMT)
|
||||
bool free_ends{false};
|
||||
bool model_check{true};
|
||||
std::vector<TTrack *> queue;
|
||||
std::size_t next{0};
|
||||
// the places of the tracks compared with the terrain model, in runs of one path each, once the model under them is in
|
||||
std::vector<nmt_place> places;
|
||||
std::vector<std::pair<std::size_t, std::size_t>> runs;
|
||||
std::size_t run_next{0};
|
||||
bool model_waiting{false};
|
||||
bool model_asked{false};
|
||||
double model_polled{0.0};
|
||||
bool model_unplaced{false};
|
||||
std::vector<joint_issue> issues;
|
||||
int filter{-1};
|
||||
int selected{-1};
|
||||
@@ -2524,6 +2534,8 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void joints_start();
|
||||
void joints_step();
|
||||
void joints_check(TTrack &Track);
|
||||
// the paths lying off the terrain model by more than the threshold. returns: false while the model under them is coming
|
||||
bool joints_model();
|
||||
void joints_focus(int const Index);
|
||||
bool joint_fix(joint_issue &Issue, std::vector<std::pair<TTrack *, editor_track::state>> &States, std::vector<TTrack *> &Changed);
|
||||
void joints_fix(std::vector<int> const &Indices);
|
||||
|
||||
@@ -26,6 +26,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
@@ -39,6 +41,10 @@ double const kLinked{0.02}; // m, ends this close are joined by the simulation
|
||||
double const kOverlayRange{3000.0};
|
||||
double const kGauge{1.5}; // m between the rails, for the cant
|
||||
int const kRailCategory{1};
|
||||
double const kModelStep{5.0}; // m between the places of the tracks compared with the terrain model
|
||||
std::size_t const kModelRun{200}; // places of a path whose model is asked for at once
|
||||
double const kModelPoll{0.25}; // s between the looks whether the model is in
|
||||
double const kUnfitted{30.0}; // m; a scenery whose tracks lie this far from the model as a whole isn't fitted to it
|
||||
|
||||
using kind = editor_mode::joint_issue::kind;
|
||||
|
||||
@@ -98,6 +104,7 @@ char const *kind_name(kind const Kind)
|
||||
case kind::kink: return "kink";
|
||||
case kind::cant: return "cant jump";
|
||||
case kind::grade: return "grade break";
|
||||
case kind::model: return "off the terrain model";
|
||||
default: return "free end";
|
||||
}
|
||||
}
|
||||
@@ -111,6 +118,7 @@ std::string value_text(editor_mode::joint_issue const &Issue)
|
||||
case kind::kink: return format("%.3f\xc2\xb0", Issue.value);
|
||||
case kind::cant: return format("%.1f mm", Issue.value);
|
||||
case kind::grade: return format("%.1f \xe2\x80\xb0", Issue.value);
|
||||
case kind::model: return format("%+.2f m", Issue.value);
|
||||
default: return "-";
|
||||
}
|
||||
}
|
||||
@@ -124,6 +132,7 @@ ImU32 kind_colour(kind const Kind)
|
||||
case kind::kink: return IM_COL32(255, 190, 60, 225);
|
||||
case kind::cant: return IM_COL32(200, 120, 255, 225);
|
||||
case kind::grade: return IM_COL32(90, 200, 255, 220);
|
||||
case kind::model: return IM_COL32(70, 200, 190, 235);
|
||||
default: return IM_COL32(200, 200, 200, 200);
|
||||
}
|
||||
}
|
||||
@@ -156,6 +165,114 @@ void editor_mode::joints_start()
|
||||
state.queue.push_back(track);
|
||||
}
|
||||
state.history = m_history.size();
|
||||
state.places.clear();
|
||||
state.runs.clear();
|
||||
state.run_next = 0;
|
||||
state.model_waiting = false;
|
||||
state.model_asked = false;
|
||||
state.model_polled = 0.0;
|
||||
state.model_unplaced = false;
|
||||
if (false == state.model_check || state.queue.empty())
|
||||
return;
|
||||
nmt_locate();
|
||||
if (false == m_nmt.placed())
|
||||
{
|
||||
state.model_unplaced = true;
|
||||
return;
|
||||
}
|
||||
std::set<TTrack const *> const queued(state.queue.begin(), state.queue.end());
|
||||
for (auto const &place : nmt_track_places(kModelStep))
|
||||
if (place.shapes && queued.count(place.track) > 0)
|
||||
{
|
||||
if (state.places.empty() || state.places.back().track != place.track || state.places.back().path != place.path || state.places.size() - state.runs.back().first >= kModelRun)
|
||||
state.runs.emplace_back(state.places.size(), state.places.size());
|
||||
state.places.push_back(place);
|
||||
state.runs.back().second = state.places.size();
|
||||
}
|
||||
state.model_waiting = false == state.places.empty();
|
||||
}
|
||||
|
||||
bool editor_mode::joints_model()
|
||||
{
|
||||
auto &state{m_joints};
|
||||
auto const now{ImGui::GetTime()};
|
||||
if (state.model_asked && now - state.model_polled < kModelPoll)
|
||||
return false;
|
||||
state.model_polled = now;
|
||||
auto const box = [&](std::pair<std::size_t, std::size_t> const &Run, glm::dvec2 &Min, glm::dvec2 &Max) {
|
||||
Min = glm::dvec2{std::numeric_limits<double>::max()};
|
||||
Max = glm::dvec2{std::numeric_limits<double>::lowest()};
|
||||
for (auto i = Run.first; i < Run.second; ++i)
|
||||
{
|
||||
glm::dvec2 const at{state.places[i].position.x, state.places[i].position.z};
|
||||
Min = glm::min(Min, at - 2.0);
|
||||
Max = glm::max(Max, at + 2.0);
|
||||
}
|
||||
};
|
||||
glm::dvec2 low, high;
|
||||
// all of it asked for once, then the runs looked over in turn as their squares come
|
||||
if (false == state.model_asked)
|
||||
{
|
||||
for (auto const &run : state.runs)
|
||||
{
|
||||
box(run, low, high);
|
||||
m_nmt.request(low, high);
|
||||
}
|
||||
state.model_asked = true;
|
||||
}
|
||||
while (state.run_next < state.runs.size())
|
||||
{
|
||||
box(state.runs[state.run_next], low, high);
|
||||
if (false == m_nmt.request(low, high))
|
||||
return false;
|
||||
++state.run_next;
|
||||
}
|
||||
std::vector<double> differences(state.places.size(), std::numeric_limits<double>::quiet_NaN());
|
||||
std::vector<double> known;
|
||||
for (std::size_t i = 0; i < state.places.size(); ++i)
|
||||
if (double height; m_nmt.height(state.places[i].position.x, state.places[i].position.z, height))
|
||||
{
|
||||
differences[i] = state.places[i].crown - (height + m_nmt_state.offset);
|
||||
known.push_back(differences[i]);
|
||||
}
|
||||
auto shift{0.0};
|
||||
if (false == m_nmt_state.fitted && false == known.empty())
|
||||
{
|
||||
std::nth_element(known.begin(), known.begin() + known.size() / 2, known.end());
|
||||
if (std::abs(known[known.size() / 2]) > kUnfitted)
|
||||
shift = known[known.size() / 2];
|
||||
}
|
||||
// the place of each path the farthest from the model
|
||||
std::map<std::pair<TTrack *, int>, std::size_t> worst;
|
||||
for (std::size_t i = 0; i < state.places.size(); ++i)
|
||||
{
|
||||
if (false == std::isfinite(differences[i]))
|
||||
continue;
|
||||
differences[i] -= shift;
|
||||
if (std::abs(differences[i]) <= state.model)
|
||||
continue;
|
||||
auto const key{std::make_pair(state.places[i].track, state.places[i].path)};
|
||||
auto const found{worst.find(key)};
|
||||
if (found == worst.end())
|
||||
worst.emplace(key, i);
|
||||
else if (std::abs(differences[i]) > std::abs(differences[found->second]))
|
||||
found->second = i;
|
||||
}
|
||||
for (auto const &entry : worst)
|
||||
{
|
||||
auto const &place{state.places[entry.second]};
|
||||
joint_issue issue;
|
||||
issue.type = kind::model;
|
||||
issue.track = place.track;
|
||||
issue.point = {place.path, editor_track::point_kind::start};
|
||||
issue.position = place.position;
|
||||
issue.value = differences[entry.second];
|
||||
state.issues.push_back(issue);
|
||||
}
|
||||
state.places.clear();
|
||||
state.runs.clear();
|
||||
state.model_waiting = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void editor_mode::joints_check(TTrack &Track)
|
||||
@@ -252,7 +369,7 @@ void editor_mode::joints_check(TTrack &Track)
|
||||
void editor_mode::joints_step()
|
||||
{
|
||||
auto &state{m_joints};
|
||||
if (state.next >= state.queue.size())
|
||||
if (state.next >= state.queue.size() && false == state.model_waiting)
|
||||
return;
|
||||
auto const deadline{std::chrono::steady_clock::now() + std::chrono::milliseconds(15)};
|
||||
while (state.next < state.queue.size() && std::chrono::steady_clock::now() < deadline)
|
||||
@@ -264,17 +381,23 @@ void editor_mode::joints_step()
|
||||
}
|
||||
if (state.next < state.queue.size())
|
||||
return;
|
||||
if (state.model_waiting && false == joints_model())
|
||||
return;
|
||||
std::stable_sort(state.issues.begin(), state.issues.end(), [](joint_issue const &A, joint_issue const &B) {
|
||||
if (A.type != B.type)
|
||||
return A.type < B.type;
|
||||
return A.value > B.value;
|
||||
return std::abs(A.value) > std::abs(B.value);
|
||||
});
|
||||
std::array<int, 6> counts{};
|
||||
std::array<int, 7> counts{};
|
||||
for (auto const &issue : state.issues)
|
||||
++counts[static_cast<int>(issue.type)];
|
||||
state.status = format(STR_C("%zu paths checked: %d gaps, %d steps, %d kinks, %d cant jumps, %d grade breaks"), state.queue.size(), counts[0], counts[1], counts[2], counts[3], counts[4]);
|
||||
if (state.model_check && state.model_unplaced)
|
||||
state.status += STR_C(", the terrain model left out: the scenery isn't placed on the map (the orthophoto settings)");
|
||||
else if (state.model_check)
|
||||
state.status += format(STR_C(", %d paths off the terrain model"), counts[5]);
|
||||
if (state.free_ends)
|
||||
state.status += format(STR_C(", %d free ends"), counts[5]);
|
||||
state.status += format(STR_C(", %d free ends"), counts[6]);
|
||||
WriteLog("Editor: joints check - " + state.status, logtype::generic);
|
||||
}
|
||||
|
||||
@@ -296,7 +419,7 @@ void editor_mode::joints_focus(int const Index)
|
||||
|
||||
bool editor_mode::joint_fix(joint_issue &Issue, std::vector<std::pair<TTrack *, editor_track::state>> &States, std::vector<TTrack *> &Changed)
|
||||
{
|
||||
if (Issue.fixed || Issue.type == kind::open || Issue.track == nullptr || Issue.other == nullptr || Issue.track->m_editorremoved || Issue.other->m_editorremoved)
|
||||
if (Issue.fixed || Issue.type == kind::open || Issue.type == kind::model || Issue.track == nullptr || Issue.other == nullptr || Issue.track->m_editorremoved || Issue.other->m_editorremoved)
|
||||
return false;
|
||||
auto const movable = [](TTrack const *Track) {
|
||||
std::string reason;
|
||||
@@ -392,14 +515,25 @@ void editor_mode::render_joints_body()
|
||||
ImGui::TextDisabled("%s", STR_C("Looks for faults at the joints of the tracks"));
|
||||
ImGui::PopTextWrapPos();
|
||||
segmented("jointscope", state.scope, {{0, STR_C("Around the camera")}, {1, STR_C("Whole scenery")}});
|
||||
bool const running{state.next < state.queue.size()};
|
||||
bool const running{state.next < state.queue.size() || state.model_waiting};
|
||||
if (running)
|
||||
{
|
||||
auto const progress{static_cast<float>(state.next) / static_cast<float>(state.queue.size())};
|
||||
ImGui::ProgressBar(progress, ImVec2(-70.0f, 0.0f), format(STR_C("Checking %zu / %zu"), state.next, state.queue.size()).c_str());
|
||||
if (state.next < state.queue.size())
|
||||
{
|
||||
auto const progress{static_cast<float>(state.next) / static_cast<float>(state.queue.size())};
|
||||
ImGui::ProgressBar(progress, ImVec2(-70.0f, 0.0f), format(STR_C("Checking %zu / %zu"), state.next, state.queue.size()).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const progress{static_cast<float>(state.run_next) / static_cast<float>(std::max<std::size_t>(1, state.runs.size()))};
|
||||
ImGui::ProgressBar(progress, ImVec2(-70.0f, 0.0f), format(STR_C("Terrain model %zu / %zu"), state.run_next, state.runs.size()).c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(STR_C("Cancel")))
|
||||
{
|
||||
state.queue.resize(state.next);
|
||||
state.model_waiting = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -427,15 +561,19 @@ void editor_mode::render_joints_body()
|
||||
limit("Kink (deg)", "##jointkink", state.angle, "%.2f");
|
||||
limit("Cant jump (mm)", "##jointcant", state.cant, "%.1f");
|
||||
limit("Grade break (per mille)", "##jointgrade", state.grade, "%.1f");
|
||||
limit("Off the terrain model (m)", "##jointmodel", state.model, "%.2f");
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("The track lies higher or lower than the terrain model (NMT) says the ground is, by more than this.\nEach path is listed once, at its place the farthest off; bridges and tunnels are left out"));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
toggle_chips("jointflags", {{STR_C("Free ends too"), &state.free_ends}});
|
||||
toggle_chips("jointflags", {{STR_C("Free ends too"), &state.free_ends}, {STR_C("Against the terrain model"), &state.model_check}});
|
||||
state.reach = std::clamp(state.reach, 50.0, 50000.0);
|
||||
state.gap = std::clamp(state.gap, 0.03, 20.0);
|
||||
state.step = std::max(0.001, state.step);
|
||||
state.angle = std::max(0.01, state.angle);
|
||||
state.cant = std::max(0.5, state.cant);
|
||||
state.grade = std::max(0.5, state.grade);
|
||||
state.model = std::max(0.05, state.model);
|
||||
ImGui::TextDisabled("%s", STR_C("Change the thresholds, then Check again"));
|
||||
ImGui::TreePop();
|
||||
}
|
||||
@@ -447,7 +585,8 @@ void editor_mode::render_joints_body()
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", STR_C("The scenery changed since the check"));
|
||||
int filter{state.filter + 1};
|
||||
ImGui::SetNextItemWidth(150.0f);
|
||||
auto const filters{std::string{STR_C("All")} + '\0' + STR_C("gap") + '\0' + STR_C("step") + '\0' + STR_C("kink") + '\0' + STR_C("cant jump") + '\0' + STR_C("grade break") + '\0' + STR_C("free end") + '\0' + '\0'};
|
||||
auto const filters{std::string{STR_C("All")} + '\0' + STR_C("gap") + '\0' + STR_C("step") + '\0' + STR_C("kink") + '\0' + STR_C("cant jump") + '\0' + STR_C("grade break") + '\0' + STR_C("off the terrain model") + '\0' +
|
||||
STR_C("free end") + '\0' + '\0'};
|
||||
if (ImGui::Combo("##jointfilter", &filter, filters.c_str()))
|
||||
state.filter = filter - 1;
|
||||
std::vector<int> listed;
|
||||
@@ -458,7 +597,7 @@ void editor_mode::render_joints_body()
|
||||
if (state.filter >= 0 && static_cast<int>(issue.type) != state.filter)
|
||||
continue;
|
||||
listed.push_back(i);
|
||||
if (false == issue.fixed && issue.type != kind::open)
|
||||
if (false == issue.fixed && issue.type != kind::open && issue.type != kind::model)
|
||||
fixable.push_back(i);
|
||||
}
|
||||
if (false == fixable.empty())
|
||||
@@ -505,7 +644,7 @@ void editor_mode::render_joints_body()
|
||||
{
|
||||
auto &issue{state.issues[state.selected]};
|
||||
ImGui::Text("%s %s: %s / %s", STR_C(kind_name(issue.type)), value_text(issue).c_str(), name_of(issue.track).c_str(), name_of(issue.other).c_str());
|
||||
if (false == issue.fixed && issue.type != kind::open && ImGui::Button(STR_C("Fix this one")))
|
||||
if (false == issue.fixed && issue.type != kind::open && issue.type != kind::model && ImGui::Button(STR_C("Fix this one")))
|
||||
joints_fix({state.selected});
|
||||
}
|
||||
}
|
||||
@@ -521,7 +660,7 @@ void editor_mode::draw_joints_overlay() const
|
||||
for (int i = 0; i < static_cast<int>(state.issues.size()); ++i)
|
||||
{
|
||||
auto const &issue{state.issues[i]};
|
||||
if (issue.fixed || (state.filter >= 0 && static_cast<int>(issue.type) != state.filter) || glm::distance(issue.position, camera) > kOverlayRange)
|
||||
if (issue.fixed || (state.filter >= 0 && static_cast<int>(issue.type) != state.filter) || (false == Global.EditorOrtho && glm::distance(issue.position, camera) > kOverlayRange))
|
||||
continue;
|
||||
ImVec2 screen;
|
||||
if (false == projection.project(issue.position, screen))
|
||||
|
||||
@@ -230,18 +230,49 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
|
||||
WriteLog(format("SELFTEST layclicks %d paths, ballast foot over the ground up to %.3f m, under it up to %.3f m", paths, hanging, buried));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "jointsmodel")
|
||||
{
|
||||
// the whole scenery against the terrain model, as the check of the panel does it, waiting for the model
|
||||
static bool started{false};
|
||||
if (false == started)
|
||||
{
|
||||
double threshold{0.5};
|
||||
words >> threshold;
|
||||
m_joints.scope = 1;
|
||||
m_joints.model_check = true;
|
||||
m_joints.model = threshold;
|
||||
joints_start();
|
||||
started = true;
|
||||
}
|
||||
joints_step();
|
||||
if (m_joints.next < m_joints.queue.size() || m_joints.model_waiting)
|
||||
{
|
||||
--m_selftest.next;
|
||||
return selftest_result::pause;
|
||||
}
|
||||
started = false;
|
||||
std::size_t count{0};
|
||||
for (auto const &issue : m_joints.issues)
|
||||
if (issue.type == joint_issue::kind::model && count++ < 40)
|
||||
WriteLog(format("SELFTEST jointsmodel: %+.2f m at %.1f %.1f, %s path %d", issue.value, issue.position.x, issue.position.z, issue.track != nullptr ? issue.track->name().c_str() : "-", issue.point.path));
|
||||
WriteLog("SELFTEST jointsmodel: " + m_joints.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "joints")
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||
points.push_back(point);
|
||||
auto const scope{m_joints.scope};
|
||||
auto const model{m_joints.model_check};
|
||||
m_joints.scope = 1;
|
||||
m_joints.model_check = false;
|
||||
joints_start();
|
||||
while (m_joints.next < m_joints.queue.size())
|
||||
joints_step();
|
||||
m_joints.scope = scope;
|
||||
char const *const kinds[]{"gap", "step", "kink", "cant jump", "grade break", "free end"};
|
||||
m_joints.model_check = model;
|
||||
char const *const kinds[]{"gap", "step", "kink", "cant jump", "grade break", "off the terrain model", "free end"};
|
||||
int around{0};
|
||||
for (auto const &issue : m_joints.issues)
|
||||
{
|
||||
|
||||
@@ -9898,3 +9898,24 @@ msgstr "Podsypka"
|
||||
|
||||
msgid "Off: the top view draws the rails and sleepers of the tracks without the ballast, so the ground under them shows"
|
||||
msgstr "Wyłączone: widok z góry rysuje szyny i podkłady bez podsypki, widać teren pod torami"
|
||||
|
||||
msgid "off the terrain model"
|
||||
msgstr "odstaje od NMT"
|
||||
|
||||
msgid ", the terrain model left out: the scenery isn't placed on the map (the orthophoto settings)"
|
||||
msgstr ", bez NMT: sceneria nie jest umieszczona na mapie (ustawienia ortofotomapy)"
|
||||
|
||||
msgid ", %d paths off the terrain model"
|
||||
msgstr ", odstające od NMT: %d"
|
||||
|
||||
msgid "Terrain model %zu / %zu"
|
||||
msgstr "NMT %zu / %zu"
|
||||
|
||||
msgid "Off the terrain model (m)"
|
||||
msgstr "Odstaje od NMT o (m)"
|
||||
|
||||
msgid "The track lies higher or lower than the terrain model (NMT) says the ground is, by more than this.\nEach path is listed once, at its place the farthest off; bridges and tunnels are left out"
|
||||
msgstr "Tor leży wyżej lub niżej, niż według NMT jest grunt, o więcej niż tyle.\nKażdy tor jest na liście raz, w miejscu, gdzie odstaje najbardziej; mosty i tunele są pominięte"
|
||||
|
||||
msgid "Against the terrain model"
|
||||
msgstr "Także względem NMT"
|
||||
|
||||
Reference in New Issue
Block a user