diff --git a/application/editormode.cpp b/application/editormode.cpp index f074dc05..dc90ca72 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -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 &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) diff --git a/application/editormode.h b/application/editormode.h index f6dbe166..4bef2f71 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -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 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 intruding; std::vector hits; } map; // whole scenery + struct + { + std::vector surface; + std::vector 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 diff --git a/application/editormodegauge.cpp b/application/editormodegauge.cpp index f01ddce6..2a07a950 100644 --- a/application/editormodegauge.cpp +++ b/application/editormodegauge.cpp @@ -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 samples; std::vector 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 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(u) / steps) + (C - A) * (static_cast(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 &Surface, std::vector &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 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 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 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 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 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 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(hits.size()) + static_cast(hits.size())) % static_cast(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(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(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(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() diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 2fb4ace3..4e3b9a94 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -482,7 +482,6 @@ void editor_mode::render_track_window() m_infra.scope = 1; infra_recognize(); } - render_gauge_ui(); ImGui::End(); } diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index aaef5783..a4610491 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -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 Save, std::function Export) diff --git a/application/editoruilayer.h b/application/editoruilayer.h index f4b3c971..7ae7faa9 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -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 Save, std::function Export); // shows outcome of an operation in the layers window @@ -84,4 +89,5 @@ class editor_ui : public ui_layer std::function m_trackoptions; std::function m_gizmooptions; scene::basic_node *m_node{nullptr}; // currently bound scene node, if any + bool *m_gaugewindow{nullptr}; };