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

feat: structure gauge window in the menu bar, gauge tunnel around the hit, fixed reach

This commit is contained in:
maj00r
2026-10-04 01:54:17 +02:00
parent 208ad3400d
commit d57215877f
6 changed files with 124 additions and 63 deletions

View File

@@ -340,6 +340,7 @@ editor_mode::editor_mode() {
ui()->set_gizmo_options([this]() { render_gizmo_options(); });
ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); });
ui()->set_track_options([this]() { render_track_ui(); });
ui()->set_gauge_window(&m_gauge.open);
// the orthophoto is fitted onto the same ground as the area fill uses, terrain tile models included
m_orthophoto.ground_source([](glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out) {
gather_ground_triangles(Min, Max, true, 50.0f, Out);
@@ -1036,6 +1037,7 @@ bool editor_mode::update()
render_infra_window();
}
update_gauge();
render_gauge_window();
// --- area fill: outline overlay while the mode is active (its settings are drawn in the node bank window) ---
if (ui()->mode() == nodebank_panel::FILL)

View File

@@ -45,6 +45,8 @@ class editor_mode : public application_mode, private editor_track::observer
std::string model;
glm::dvec3 point{0.0};
double depth{0.0}; // m
TTrack const *track{nullptr}; // nearest one, may be gone by now
int path{0};
};
// constructors
editor_mode();
@@ -316,10 +318,10 @@ class editor_mode : public application_mode, private editor_track::observer
// structure gauge along the line of the selected path, shown in the 3d view
struct gauge_state
{
bool enabled{false};
bool open{false}; // window
bool enabled{true}; // tunnel along the selected path
std::vector<gauge::profile> profiles;
int profile{0};
double reach{500.0}; // m, each way from the path
TTrack const *track{nullptr}; // scanned
std::size_t history{0};
bool pending{false}; // rescan once the edit ends
@@ -338,6 +340,11 @@ class editor_mode : public application_mode, private editor_track::observer
std::vector<glm::dvec3> intruding;
std::vector<gauge_hit> hits;
} map; // whole scenery
struct
{
std::vector<glm::dvec3> surface;
std::vector<glm::dvec3> edges;
} spot; // tunnel around the hit the camera went to
int current{-1}; // hit the camera went to
bool published{false}; // overlay up to date
} m_gauge;
@@ -348,7 +355,8 @@ class editor_mode : public application_mode, private editor_track::observer
void scan_gauge_map();
void gauge_publish();
void gauge_focus(int Index);
void render_gauge_ui();
void gauge_spot(gauge_hit const &Hit);
void render_gauge_window();
void render_gauge_hits();
bool render_gauge_outline();
struct route_design

View File

@@ -38,10 +38,14 @@ char const *const gauge_file{"editor_structure_gauges.txt"};
std::size_t const gauge_triangle_limit{200000};
// past this distance of the camera from the origin of the overlay it's rebuilt, to keep its float precision
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};
// sampled line of the tracks with the widening of the gauge along it
struct gauge_line
{
editor_track::route route;
std::vector<editor_track::route_sample> samples;
std::vector<gauge::section> sections;
};
@@ -50,6 +54,7 @@ gauge_line make_line(editor_track::route &Route)
{
gauge_line line;
line.samples = editor_track::sample_route(Route, 1.0);
line.route = Route;
std::vector<double> chainage, curvature, cant;
for (auto &sample : line.samples)
{
@@ -128,8 +133,13 @@ public:
return false;
}
// deepest intrusion of the point into the gauge of the lines nearby, not positive outside of them
double intrusion(gauge::profile const &Profile, glm::dvec3 const &Point) const
struct hit
{
double depth{0.0}; // not positive outside of the gauge
editor_track::route_span const *span{nullptr}; // of the line the point enters
};
// deepest intrusion of the point into the gauge of the lines nearby
hit intrusion(gauge::profile const &Profile, glm::dvec3 const &Point) const
{
auto const point{plan_of(Point)};
auto const center{index(point, cell)};
@@ -153,7 +163,7 @@ public:
*same = {candidate, distance};
}
}
double deepest{0.0};
hit deepest;
for (auto const &[found, distance] : nearest)
{
auto const &line{m_lines[found.line]};
@@ -163,7 +173,9 @@ public:
if ((found.index == 0 && along < -0.6) || (found.index + 1 == line.samples.size() && along > 0.6))
continue;
auto const height{Point.y - (sample.position.y + sample.grade * along)};
deepest = std::max(deepest, gauge::intrusion(Profile, line.sections[found.index], glm::dot(offset, normal(sample)), height));
auto const depth{gauge::intrusion(Profile, line.sections[found.index], glm::dot(offset, normal(sample)), height)};
if (depth > deepest.depth)
deepest = {depth, &line.route.spans[sample.span]};
}
return deepest;
}
@@ -251,7 +263,7 @@ void scan_models(corridor const &Space, gauge::profile const &Profile, std::vect
continue;
if (false == Space.near(instance->location(), instance->radius()))
continue;
editor_mode::gauge_hit hit{instance->name(), instance->location(), 0.0};
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)})};
@@ -260,14 +272,16 @@ void scan_models(corridor const &Space, gauge::profile const &Profile, std::vect
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 depth{Space.intrusion(Profile, point)};
if (depth <= 0.0)
auto const found{Space.intrusion(Profile, point)};
if (found.depth <= 0.0)
continue;
Triangles.insert(Triangles.end(), {A, B, C});
if (depth > hit.depth)
if (found.depth > hit.depth)
{
hit.depth = depth;
hit.depth = found.depth;
hit.point = point;
hit.track = found.span->track;
hit.path = found.span->path;
}
return;
}
@@ -278,6 +292,46 @@ void scan_models(corridor const &Space, gauge::profile const &Profile, std::vect
std::sort(Hits.begin(), Hits.end(), [](auto const &Left, auto const &Right) { return Left.depth > Right.depth; });
}
// translucent tunnel of the gauge along the line, with the rings every 10 m and the lines along its corners
void build_tunnel(gauge_line const &Line, gauge::profile const &Profile, std::vector<glm::dvec3> &Surface, std::vector<glm::dvec3> &Edges)
{
auto const lift{[](editor_track::route_sample const &Sample, double const Lateral, double const Height) {
auto const normal{corridor::normal(Sample)};
return Sample.position + glm::dvec3(normal.x * Lateral, Height, normal.y * Lateral);
}};
// outline across the track, from the bottom right up and down to the bottom left
auto const ring{[&](std::size_t const Index) {
std::vector<glm::dvec3> points;
for (int side : {-1, 1})
for (std::size_t j = 0; j < Profile.outline.size(); ++j)
{
auto const &point{Profile.outline[side < 0 ? j : Profile.outline.size() - 1 - j]};
auto const width{point.half_width + gauge::widening(Line.sections[Index], point.height, side)};
points.push_back(lift(Line.samples[Index], side * width, point.height));
}
return points;
}};
std::vector<glm::dvec3> previous;
double lastring{-1e9};
for (std::size_t i = 0; i < Line.samples.size(); i += 2)
{
auto const current{ring(i)};
if (false == previous.empty())
for (std::size_t j = 1; j < current.size(); ++j)
{
Surface.insert(Surface.end(), {previous[j - 1], previous[j], current[j], previous[j - 1], current[j], current[j - 1]});
Edges.insert(Edges.end(), {previous[j], current[j]});
}
if (Line.samples[i].chainage - lastring >= 10.0 || i + 2 >= Line.samples.size())
{
lastring = Line.samples[i].chainage;
for (std::size_t j = 1; j < current.size(); ++j)
Edges.insert(Edges.end(), {current[j - 1], current[j]});
}
previous = current;
}
}
double widest(gauge::profile const &Profile)
{
double result{0.0};
@@ -303,7 +357,7 @@ std::vector<editor_mode::gauge_hit> const &editor_mode::gauge_hits() const
void editor_mode::update_gauge()
{
if (ui()->mode() != nodebank_panel::TRACK)
if (false == m_gauge.open)
{
if (false == EditorOverlay.batches.empty())
EditorOverlay.clear();
@@ -340,50 +394,12 @@ void editor_mode::scan_gauge(TTrack &Track)
gauge_load();
m_gauge.line = {};
auto const &profile{m_gauge.profiles[m_gauge.profile]};
auto route{editor_track::run_route(Track, m_gauge.reach)};
auto route{editor_track::run_route(Track, gauge_reach)};
std::vector<gauge_line> const lines{make_line(route)};
auto const &line{lines.front()};
if (line.samples.size() < 2 || profile.outline.size() < 2)
if (lines.front().samples.size() < 2 || profile.outline.size() < 2)
return;
corridor const space{lines, widest(profile)};
auto const lift{[](editor_track::route_sample const &Sample, double const Lateral, double const Height) {
auto const normal{corridor::normal(Sample)};
return Sample.position + glm::dvec3(normal.x * Lateral, Height, normal.y * Lateral);
}};
// outline across the track, from the bottom right up and down to the bottom left
auto const ring{[&](std::size_t const Index) {
std::vector<glm::dvec3> points;
for (int side : {-1, 1})
for (std::size_t j = 0; j < profile.outline.size(); ++j)
{
auto const &point{profile.outline[side < 0 ? j : profile.outline.size() - 1 - j]};
auto const width{point.half_width + gauge::widening(line.sections[Index], point.height, side)};
points.push_back(lift(line.samples[Index], side * width, point.height));
}
return points;
}};
auto &surface{m_gauge.line.surface};
auto &edges{m_gauge.line.edges};
std::vector<glm::dvec3> previous;
double lastring{-1e9};
for (std::size_t i = 0; i < line.samples.size(); i += 2)
{
auto const current{ring(i)};
if (false == previous.empty())
for (std::size_t j = 1; j < current.size(); ++j)
{
surface.insert(surface.end(), {previous[j - 1], previous[j], current[j], previous[j - 1], current[j], current[j - 1]});
edges.insert(edges.end(), {previous[j], current[j]});
}
if (line.samples[i].chainage - lastring >= 10.0 || i + 2 >= line.samples.size())
{
lastring = line.samples[i].chainage;
for (std::size_t j = 1; j < current.size(); ++j)
edges.insert(edges.end(), {current[j - 1], current[j]});
}
previous = current;
}
build_tunnel(lines.front(), profile, m_gauge.line.surface, m_gauge.line.edges);
scan_models(space, profile, m_gauge.line.intruding, m_gauge.line.hits);
if (false == m_gauge.map.scanned)
m_gauge.current = -1;
@@ -433,7 +449,9 @@ void editor_mode::gauge_publish()
batch({0.2f, 1.0f, 0.3f, 0.5f}, GL_LINES, false, m_gauge.line.edges),
batch({1.0f, 0.1f, 0.05f, 0.6f}, GL_TRIANGLES, true, m_gauge.line.intruding),
batch({1.0f, 0.1f, 0.05f, 0.6f}, GL_TRIANGLES, true, m_gauge.map.intruding),
batch({1.0f, 0.9f, 0.1f, 1.0f}, GL_LINES, false, marker)};
batch({1.0f, 0.9f, 0.1f, 1.0f}, GL_LINES, false, marker),
batch({0.2f, 0.9f, 0.3f, 0.12f}, GL_TRIANGLES, false, m_gauge.spot.surface),
batch({0.2f, 1.0f, 0.3f, 0.5f}, GL_LINES, false, m_gauge.spot.edges)};
}
void editor_mode::gauge_focus(int const Index)
@@ -443,6 +461,7 @@ void editor_mode::gauge_focus(int const Index)
return;
m_gauge.current = (Index % static_cast<int>(hits.size()) + static_cast<int>(hits.size())) % static_cast<int>(hits.size());
m_gauge.published = false;
gauge_spot(hits[m_gauge.current]);
auto const target{hits[m_gauge.current].point};
// from the side the camera looks from, a bit above
glm::dvec3 away{Camera.Pos.x - target.x, 0.0, Camera.Pos.z - target.z};
@@ -457,14 +476,38 @@ void editor_mode::gauge_focus(int const Index)
m_focus_duration = 0.6;
}
void editor_mode::render_gauge_ui()
void editor_mode::gauge_spot(gauge_hit const &Hit)
{
if (false == ImGui::CollapsingHeader("Structure gauge"))
m_gauge.spot = {};
auto const &paths{simulation::Paths.sequence()};
// the hit outlives the scan, the path may be gone since
if (Hit.track == nullptr || std::find(paths.begin(), paths.end(), Hit.track) == paths.end())
return;
auto *track{const_cast<TTrack *>(Hit.track)};
editor_track::route route;
if (Hit.path == 0)
route = editor_track::run_route(*track, gauge_spot_reach);
else
route.spans.push_back({track, Hit.path, true});
auto const line{make_line(route)};
if (line.samples.size() >= 2)
build_tunnel(line, m_gauge.profiles[m_gauge.profile], m_gauge.spot.surface, m_gauge.spot.edges);
}
void editor_mode::render_gauge_window()
{
if (false == m_gauge.open)
return;
ImGui::SetNextWindowSize(ImVec2(380.0f, 420.0f), ImGuiCond_FirstUseEver);
if (false == ImGui::Begin("Structure gauge", &m_gauge.open))
{
ImGui::End();
return;
}
if (ImGui::Checkbox("Show along the selected path", &m_gauge.enabled))
m_gauge.pending = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Translucent tunnel of the gauge along the line of the selected path,\nwith the triangles of the models which enter it in red.\nWidened in the curves under 250 m and tilted on the cant, from 20 m (inner side)\nand 26 m (outer side) ahead of the curve, as in the PKP PLK standard, volume II.");
ImGui::SetTooltip("Translucent tunnel of the gauge 500 m each way along the line of the selected path,\nwith the triangles of the models which enter it in red.\nWidened in the curves under 250 m and tilted on the cant, from 20 m (inner side)\nand 26 m (outer side) ahead of the curve, as in the PKP PLK standard, volume II.");
gauge_load();
auto const previous{m_gauge.profile};
for (int i = 0; i < static_cast<int>(m_gauge.profiles.size()); ++i)
@@ -474,10 +517,6 @@ void editor_mode::render_gauge_ui()
ImGui::RadioButton(m_gauge.profiles[i].name.c_str(), &m_gauge.profile, i);
}
bool changed{previous != m_gauge.profile};
float reach{static_cast<float>(m_gauge.reach)};
if (ImGui::SliderFloat("Reach each way (m)", &reach, 50.0f, 2000.0f, "%.0f"))
m_gauge.reach = reach;
changed |= ImGui::IsItemDeactivatedAfterEdit();
if (ImGui::Button("Find the violations on the whole map"))
scan_gauge_map();
@@ -499,6 +538,7 @@ void editor_mode::render_gauge_ui()
changed |= render_gauge_outline();
if (changed)
m_gauge.pending = true;
ImGui::End();
}
void editor_mode::render_gauge_hits()

View File

@@ -482,7 +482,6 @@ void editor_mode::render_track_window()
m_infra.scope = 1;
infra_recognize();
}
render_gauge_ui();
ImGui::End();
}

View File

@@ -143,6 +143,12 @@ void editor_ui::render_menu_contents()
ImGui::MenuItem(STR_C("Include database"), nullptr, &m_includespanel.is_open);
ImGui::EndMenu();
}
if (m_gaugewindow != nullptr)
{
ImGui::MenuItem(STR_C("Structure gauge"), nullptr, m_gaugewindow);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Checks which models enter the structure gauge of the tracks (skrajnia budowli)"));
}
}
void editor_ui::set_file_actions(std::function<void()> Save, std::function<void()> Export)

View File

@@ -59,6 +59,11 @@ class editor_ui : public ui_layer
{
m_gizmooptions = std::move(Renderer);
}
// switch of the structure gauge window in the menu bar (the window lives in the editor mode)
void set_gauge_window(bool *Open)
{
m_gaugewindow = Open;
}
// actions of the file menu, performed by the editor mode
void set_file_actions(std::function<void()> Save, std::function<void()> Export);
// shows outcome of an operation in the layers window
@@ -84,4 +89,5 @@ class editor_ui : public ui_layer
std::function<void()> m_trackoptions;
std::function<void()> m_gizmooptions;
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
bool *m_gaugewindow{nullptr};
};