17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-03 22:22:04 +01:00

feat(editor): a curve of the plan traced on the orthophoto, the points clicked along the track give its radius or the radii and the shares of a compound curve

This commit is contained in:
maj00r
2026-10-21 18:54:14 +02:00
parent 21595ba010
commit 5764b328be
8 changed files with 795 additions and 2 deletions

View File

@@ -83,6 +83,7 @@ void editor_mode::route_reset()
route.vertex = -1;
route.grip = -1;
route.result = {};
route.trace = {};
if (route.from == nullptr || route.to == nullptr)
return;
if (false == editor_track::find_chain(route.from, route.to, route.chain, route.error, true))
@@ -1086,6 +1087,162 @@ void editor_mode::draw_route_overlay() const
}
drawlist->AddText(ImVec2(screen.x + 9.0f, screen.y - 18.0f), overlay_color::marked, format("W%d", i + 1).c_str());
}
if (route.trace.points.empty())
return;
std::vector<glm::dvec2> plan;
for (auto const &point : route.trace.points)
plan.push_back(plan_of(point));
auto const projections{alignment::project(result, plan)};
auto const hit{route_trace_hit()};
for (std::size_t i = 0; i < route.trace.points.size(); ++i)
{
ImVec2 screen;
if (false == projection.project(route.trace.points[i], screen))
continue;
auto const departure{i < projections.size() ? std::abs(projections[i].offset) : 0.0};
auto const colour{departure <= 0.1 ? IM_COL32(90, 220, 110, 255) : departure <= 0.3 ? IM_COL32(255, 200, 60, 255) : IM_COL32(255, 90, 70, 255)};
drawlist->AddCircle(screen, 5.0f, IM_COL32(0, 0, 0, 200), 12, 3.5f);
drawlist->AddCircle(screen, 5.0f, colour, 12, 2.0f);
if (static_cast<int>(i) == hit)
drawlist->AddCircle(screen, 10.0f, overlay_color::highlight, 16, 2.0f);
if (i < projections.size())
drawlist->AddText(ImVec2(screen.x + 8.0f, screen.y - 7.0f), colour, format("%.0f cm", departure * 100.0).c_str());
}
}
void editor_mode::render_route_trace()
{
auto &trace{m_route.trace};
if (false == trace.active)
{
if (ImGui::Button(STR_C("Trace the curve on the photo")))
route_trace_start();
item_tooltip("Click points along the middle of the track on the orthophoto: after each click the curve takes the radius, or the radii of a compound curve and their shares, which follow the points best. The straights and the transition curves stay");
return;
}
ImGui::SeparatorText(STR_C("Tracing the curve"));
if (segmented("traceradii", trace.radii,
{{0, STR_C("Auto"), "As few radii as follow the points, up to three"}, {1, STR_C("1 R"), "One radius"}, {2, STR_C("2 R"), "A compound curve of two radii"}, {3, STR_C("3 R"), "A compound curve of three radii"}}))
route_trace_fit();
if (trace.points.empty())
{
ImGui::PushTextWrapPos(0.0f);
ImGui::TextDisabled("%s", STR_C("Click points along the middle of the track on the photo, spread over the whole curve; a few on the straights at its ends help too"));
ImGui::PopTextWrapPos();
}
else
{
auto const colour = [](double const Departure) { return Departure <= 0.1 ? IM_COL32(90, 220, 110, 255) : Departure <= 0.3 ? IM_COL32(255, 200, 60, 255) : IM_COL32(255, 90, 70, 255); };
metric_tiles({{format("%zu", trace.points.size()), STR_C("points"), ImGui::GetColorU32(ImGuiCol_Text)},
{format("%.0f cm", trace.rms * 100.0), STR_C("departure"), colour(trace.rms)},
{format("%.0f cm", trace.worst * 100.0), STR_C("at most"), colour(trace.worst)}});
}
if (main_button(STR_C("Apply Enter"), ImVec2(-1.0f, 0.0f)))
route_trace_end(true);
if (ImGui::Button(STR_C("Drop Esc")))
route_trace_end(false);
ImGui::SameLine();
ImGui::BeginDisabled(trace.points.empty());
if (ImGui::Button(STR_C("Take out the last point Backspace")))
{
trace.points.pop_back();
route_trace_fit();
}
ImGui::EndDisabled();
}
void editor_mode::route_trace_start()
{
auto &trace{m_route.trace};
trace.active = true;
trace.points.clear();
trace.before = m_route.design;
trace.rms = 0.0;
trace.worst = 0.0;
}
// every point goes to the curve nearest to it along the line, the curves without points keep their shape
void editor_mode::route_trace_fit()
{
auto &route{m_route};
auto &trace{route.trace};
auto design{route.design};
trace.rms = 0.0;
trace.worst = 0.0;
if (trace.points.empty() || design.vertices.empty() || false == route.result.valid)
return;
std::vector<glm::dvec2> plan;
for (auto const &point : trace.points)
plan.push_back(plan_of(point));
auto const projections{alignment::project(route.result, plan)};
std::vector<std::vector<glm::dvec2>> owned(design.vertices.size());
for (std::size_t i = 0; i < plan.size(); ++i)
{
std::size_t nearest{0};
for (std::size_t k = 1; k < route.result.vertex_chainages.size() && k < owned.size(); ++k)
if (std::abs(route.result.vertex_chainages[k] - projections[i].chainage) < std::abs(route.result.vertex_chainages[nearest] - projections[i].chainage))
nearest = k;
owned[nearest].push_back(plan[i]);
}
for (std::size_t k = 0; k < owned.size(); ++k)
{
if (owned[k].empty())
continue;
auto const fit{alignment::fit_trace(design, k, owned[k], trace.radii)};
if (fit.valid)
design.vertices[k] = fit.fitted;
}
route.design = design;
route_update();
double sum{0.0};
for (auto const &projection : alignment::project(route.result, plan))
{
sum += projection.offset * projection.offset;
trace.worst = std::max(trace.worst, std::abs(projection.offset));
}
trace.rms = std::sqrt(sum / static_cast<double>(plan.size()));
}
void editor_mode::route_trace_end(bool const Keep)
{
auto &trace{m_route.trace};
if (false == trace.active)
return;
auto const traced{false == trace.points.empty()};
if (false == Keep)
{
m_route.design = trace.before;
route_update();
}
trace = {};
if (Keep && traced)
route_apply();
}
// the photo under the cursor where the depth finds it, else the level of the design there
glm::dvec3 editor_mode::route_trace_cursor() const
{
if (GfxRenderer->Mouse_Hit())
return cursor_ground();
auto const &result{m_route.result};
auto point{cursor_level(alignment::elevation(result, result.length * 0.5))};
if (auto const projections{alignment::project(result, {plan_of(point)})}; false == projections.empty())
point = cursor_level(alignment::elevation(result, projections.front().chainage));
return point;
}
int editor_mode::route_trace_hit() const
{
screen_projection const projection;
float best{kHandleRadius * kHandleRadius};
int hit{-1};
for (int i = 0; i < static_cast<int>(m_route.trace.points.size()); ++i)
if (auto const distance{projection.mouse_distance2(m_route.trace.points[i])}; distance < best)
{
best = distance;
hit = i;
}
return hit;
}
void editor_mode::render_route_gizmo()
@@ -1166,6 +1323,7 @@ void editor_mode::render_route_ui()
}
item_tooltip("Reads the curves of the line from the scenery again, dropping the changes not applied");
auto changed{render_route_band()};
render_route_trace();
render_route_stored();
changed |= render_route_vertices();
if (ImGui::CollapsingHeader(STR_C("Design speed and limits")))