17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

Structure gauge: map scan in slices over the frames with progress and cancel, skip the triangles far from the lines

This commit is contained in:
maj00r
2026-10-04 02:35:00 +02:00
parent 818fc60e21
commit ac82db8178
2 changed files with 133 additions and 47 deletions

View File

@@ -347,6 +347,7 @@ class editor_mode : public application_mode, private editor_track::observer
} spot; // tunnel around the hit the camera went to
int current{-1}; // hit the camera went to
bool published{false}; // overlay up to date
std::shared_ptr<struct gauge_scan> scan; // of the whole scenery, done in slices over the frames
} m_gauge;
void gauge_load();
// the chosen railway and road outlines
@@ -355,6 +356,8 @@ class editor_mode : public application_mode, private editor_track::observer
void update_gauge();
void scan_gauge(TTrack &Track);
void scan_gauge_map();
void step_gauge_map();
void finish_gauge_map();
void gauge_publish();
void gauge_focus(int Index);
void gauge_spot(gauge_hit const &Hit);

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "editor/editorFormat.hpp"
#include "editor/editorGauge.hpp"
#include "editor/editorGeometry.hpp"
#include "model/AnimModel.h"
@@ -42,6 +43,8 @@ double const gauge_origin_range{2000.0};
// length of the line shown each way from the selected path, and around a hit
double const gauge_reach{500.0};
double const gauge_spot_reach{60.0};
// time spent on the scan of the whole map in a frame
auto const gauge_slice{std::chrono::milliseconds(8)};
// distance of the camera from the place of a hit, along the track
double const gauge_view_distance{4.0};
@@ -171,7 +174,21 @@ public:
return {-Sample.direction.y, Sample.direction.x};
}
// whether the sphere may reach the gauge
// whether the sphere may reach the gauge of a line sampled in the cells around it
bool close(glm::dvec3 const &Center, double const Radius) const
{
auto const center{plan_of(Center)};
auto const reach{Radius + m_margin};
auto const from{index(center - reach, cell)};
auto const to{index(center + reach, cell)};
for (auto x = from.x; x <= to.x; ++x)
for (auto y = from.y; y <= to.y; ++y)
if (m_cells.count(key(x, y)) > 0)
return true;
return false;
}
// whether the sphere may reach the gauge, roughly
bool near(glm::dvec3 const &Center, double const Radius) const
{
auto const center{plan_of(Center)};
@@ -306,42 +323,55 @@ template <typename Visitor> void visit_triangles(TSubModel *Submodel, glm::dmat4
}
}
// the models entering the gauge with the deepest place of each, the deepest models first
// the deepest place of the model in the gauge, added to the hits when it enters it
void scan_model(corridor const &Space, TAnimModel &Instance, std::vector<editor_mode::gauge_hit> &Hits)
{
if (false == Instance.visible() || Instance.Model() == nullptr || Instance.Model()->GetSMRoot() == nullptr)
return;
if (false == Space.near(Instance.location(), Instance.radius()))
return;
editor_mode::gauge_hit hit{Instance.name(), Instance.location()};
// the triangle enters the gauge when any point of a grid spread over it does
visit_triangles(Instance.Model()->GetSMRoot(), placement(Instance), [&](glm::dvec3 const &A, glm::dvec3 const &B, glm::dvec3 const &C) {
auto const middle{(A + B + C) / 3.0};
if (false == Space.close(middle, std::max({glm::distance(middle, A), glm::distance(middle, B), glm::distance(middle, C)})))
return;
auto const edge{std::max({glm::distance(A, B), glm::distance(B, C), glm::distance(C, A)})};
auto const steps{std::clamp(static_cast<int>(std::ceil(edge / 0.5)), 1, 24)};
for (int u = 0; u <= steps; ++u)
for (int v = 0; u + v <= steps; ++v)
{
auto const point{A + (B - A) * (static_cast<double>(u) / steps) + (C - A) * (static_cast<double>(v) / steps)};
auto const found{Space.intrusion(point)};
if (found.depth <= 0.0)
continue;
if (found.depth > hit.depth)
{
hit.depth = found.depth;
hit.point = point;
hit.track = found.span->track;
hit.path = found.span->path;
hit.direction = found.direction;
}
return;
}
});
if (hit.depth > 0.0)
Hits.push_back(std::move(hit));
}
void sort_hits(std::vector<editor_mode::gauge_hit> &Hits)
{
std::sort(Hits.begin(), Hits.end(), [](auto const &Left, auto const &Right) { return Left.depth > Right.depth; });
}
// the models entering the gauge, the deepest first
void scan_models(corridor const &Space, std::vector<editor_mode::gauge_hit> &Hits)
{
for (auto *instance : simulation::Instances.sequence())
{
if (instance == nullptr || false == instance->visible() || instance->Model() == nullptr || instance->Model()->GetSMRoot() == nullptr)
continue;
if (false == Space.near(instance->location(), instance->radius()))
continue;
editor_mode::gauge_hit hit{instance->name(), instance->location()};
// the triangle enters the gauge when any point of a grid spread over it does
visit_triangles(instance->Model()->GetSMRoot(), placement(*instance), [&](glm::dvec3 const &A, glm::dvec3 const &B, glm::dvec3 const &C) {
auto const edge{std::max({glm::distance(A, B), glm::distance(B, C), glm::distance(C, A)})};
auto const steps{std::clamp(static_cast<int>(std::ceil(edge / 0.5)), 1, 24)};
for (int u = 0; u <= steps; ++u)
for (int v = 0; u + v <= steps; ++v)
{
auto const point{A + (B - A) * (static_cast<double>(u) / steps) + (C - A) * (static_cast<double>(v) / steps)};
auto const found{Space.intrusion(point)};
if (found.depth <= 0.0)
continue;
if (found.depth > hit.depth)
{
hit.depth = found.depth;
hit.point = point;
hit.track = found.span->track;
hit.path = found.span->path;
hit.direction = found.direction;
}
return;
}
});
if (hit.depth > 0.0)
Hits.push_back(std::move(hit));
}
std::sort(Hits.begin(), Hits.end(), [](auto const &Left, auto const &Right) { return Left.depth > Right.depth; });
if (instance != nullptr)
scan_model(Space, *instance, Hits);
sort_hits(Hits);
}
// translucent tunnel of the gauge along the line, with the rings every 10 m and the lines along its corners
@@ -431,6 +461,17 @@ bool edit_points(std::vector<gauge::profile::point> &Points)
} // namespace
struct gauge_scan
{
std::optional<gauge::profile> railway;
std::optional<gauge::profile> road;
std::vector<gauge_line> lines;
std::optional<corridor> space; // of the lines
std::size_t next{0}; // instance to check
float progress{0.f};
std::chrono::steady_clock::time_point started;
};
void editor_mode::gauge_load()
{
if (m_gauge.profiles.empty())
@@ -462,6 +503,8 @@ std::vector<editor_mode::gauge_hit> const &editor_mode::gauge_hits() const
void editor_mode::update_gauge()
{
if (m_gauge.scan)
step_gauge_map();
if (false == m_gauge.open)
{
if (false == EditorOverlay.batches.empty())
@@ -513,21 +556,51 @@ void editor_mode::scan_gauge(TTrack &Track)
void editor_mode::scan_gauge_map()
{
gauge_load();
auto const started{std::chrono::steady_clock::now()};
auto scan{std::make_shared<gauge_scan>()};
scan->started = std::chrono::steady_clock::now();
auto const chosen{gauge_choice()};
// copies, the outlines may be edited while it runs
scan->railway = chosen.railway != nullptr ? std::optional<gauge::profile>{*chosen.railway} : std::nullopt;
scan->road = chosen.road != nullptr ? std::optional<gauge::profile>{*chosen.road} : std::nullopt;
scan->lines = scenery_lines({scan->railway ? &*scan->railway : nullptr, scan->road ? &*scan->road : nullptr});
scan->space.emplace(scan->lines);
m_gauge.map = {};
auto const choice{gauge_choice()};
auto const lines{scenery_lines(choice)};
corridor const space{lines};
scan_models(space, m_gauge.map.hits);
m_gauge.map.scanned = true;
m_gauge.map.history = m_history.size();
auto const name{[](gauge::profile const *Profile) { return Profile != nullptr ? Profile->name : std::string{"-"}; }};
m_gauge.map.profile = name(choice.railway) + ", " + name(choice.road);
m_gauge.map.profile = name(chosen.railway) + ", " + name(chosen.road);
m_gauge.current = -1;
m_gauge.published = false;
WriteLog("Structure gauge (" + m_gauge.map.profile + "): " + std::to_string(m_gauge.map.hits.size()) + " models enter it along " + std::to_string(lines.size()) + " lines, scanned in " +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - started).count()) + " ms");
if (false == m_gauge.map.hits.empty())
m_gauge.scan = std::move(scan);
}
void editor_mode::step_gauge_map()
{
auto &scan{*m_gauge.scan};
auto const &instances{simulation::Instances.sequence()};
auto const until{std::chrono::steady_clock::now() + gauge_slice};
while (scan.next < instances.size() && std::chrono::steady_clock::now() < until)
{
if (auto *instance{instances[scan.next++]})
scan_model(*scan.space, *instance, m_gauge.map.hits);
}
scan.progress = instances.empty() ? 1.f : static_cast<float>(scan.next) / instances.size();
if (scan.next >= instances.size())
finish_gauge_map();
}
void editor_mode::finish_gauge_map()
{
auto const scan{std::move(m_gauge.scan)};
auto &hits{m_gauge.map.hits};
// the hit the camera went to stays chosen
auto const chosen{m_gauge.current >= 0 && m_gauge.current < static_cast<int>(hits.size()) ? std::optional<glm::dvec3>{hits[m_gauge.current].point} : std::nullopt};
sort_hits(hits);
if (chosen)
m_gauge.current = static_cast<int>(std::find_if(hits.begin(), hits.end(), [&](auto const &Hit) { return Hit.point == *chosen; }) - hits.begin());
WriteLog("Structure gauge (" + m_gauge.map.profile + "): " + std::to_string(hits.size()) + " models enter it along " + std::to_string(scan->lines.size()) + " lines, scanned in " +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - scan->started).count()) + " ms");
if (false == chosen.has_value() && false == hits.empty())
gauge_focus(0);
}
@@ -620,11 +693,21 @@ void editor_mode::render_gauge_window()
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Clearance over the roads, from 0.5 m beyond the edges of the carriageway");
if (ImGui::Button("Find the violations on the whole map"))
if (m_gauge.scan)
{
auto const label{format("Scanning the map %.0f%%, %zu found", m_gauge.scan->progress * 100.f, m_gauge.map.hits.size())};
ImGui::ProgressBar(m_gauge.scan->progress, ImVec2(-70.0f, 0.0f), label.c_str());
ImGui::SameLine();
if (ImGui::SmallButton("Cancel"))
finish_gauge_map();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Stops the scan and keeps the models found so far");
}
else if (ImGui::Button("Find the violations on the whole map"))
scan_gauge_map();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Checks the models along all tracks of the scenery against the chosen gauge\nand lists them, the deepest first");
if (m_gauge.map.scanned)
else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Checks the models along all tracks of the scenery against the chosen gauge\nand lists them, the deepest first.\nRuns in the background, the list fills up as it goes");
if (m_gauge.map.scanned && false == static_cast<bool>(m_gauge.scan))
{
ImGui::SameLine();
if (ImGui::SmallButton("Clear"))