17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 07:59:30 +01:00

fix(editor): the track shown while it's laid over the ground no longer holds up the frames: its grade line is fitted on a thread of its own, the line fitted a moment ago stretched over the track meanwhile, and the click takes the line fitted for the same ground instead of fitting it again; the terrain follows the tracks once the laying ends, not after each click of a chain

Fitting the grade line took 3 ms for 250 m of track, 30 ms for 1 km, 65 ms for 2 km and 200 ms for 4 km, and the preview of the lay tool and of the drag from a free end fitted it every frame, twice for the drag; on the terrain model the preview of some 800 m took 10 to 17 ms a frame, the cursor standing or not. Now the frame waits under 1.5 ms for 4 km. The formations of the tracks led into the terrain went through every chunk under them 0.3 s after each click, some 50 ms, while the cursor went on to the next point; they wait for the laying to end. The tracks laid and the terrain are the same as before.

Selftests: perf logs the longest frame and those over 33 ms, fittime the time of the fit by length, terrainhash a hash of the chunks in memory.
This commit is contained in:
maj00r
2026-10-24 18:00:21 +02:00
parent e35487f767
commit e322947300
12 changed files with 354 additions and 43 deletions

View File

@@ -39,6 +39,7 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorSpeed.hpp"
#include "editor/editorRoad.hpp"
#include "editor/editorProfile.hpp"
#include "editor/editorGroundFollow.hpp"
#include "editor/editorInfra.hpp"
#include "editor/editorGauge.hpp"
#include "editor/editorClips.hpp"
@@ -1512,6 +1513,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri
bool start_extend(editor_track::snap_target const &End);
void finish_extend();
std::vector<segment_data> extend_pieces() const;
// the same for the track shown while it's dragged: its grade line fitted on the thread of the fitter, the frame not waiting for it
std::vector<segment_data> extend_preview() const;
// the free end of the path as the start of a new track; false if it isn't free or has no direction
static bool free_end(TTrack &Track, int const Path, bool const Atend, extend_tool &Tool);
// the free end new pieces are added at: the selected point if it's one, else the end, else the start
@@ -1732,6 +1735,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri
static std::pair<double, double> trackbed(TTrack const *Style);
std::vector<double> bed_heights(std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &Directions, TTrack const *Style) const;
void follow_ground(std::vector<segment_data> &Pieces, double const Height0, double const Grade0, double const Height1, double const Grade1, TTrack const *Style) const;
ground_follow::grading laying_grading(TTrack const *Style) const;
// the grade lines of the tracks shown while they're laid are fitted on a thread of their own, not to hold up the frames
mutable ground_follow::fitter m_fitter;
mutable bool m_follow_preview{false};
glm::dvec3 lay_bed(glm::dvec3 const &Point);
std::optional<glm::dvec3> m_cursor_override;
bool sweep_grabs(glm::dvec3 const &Point);

View File

@@ -101,7 +101,9 @@ void editor_mode::update_build_tools()
if (plan_distance(lay.mouse, points.back()) > 1.0)
points.push_back(lay.mouse);
}
m_follow_preview = true;
lay.preview = lay_pieces(points, end, lay.preview_length, lay.preview_error);
m_follow_preview = false;
}
bool editor_mode::free_end(TTrack &Track, int const Path, bool const Atend, extend_tool &Tool)
@@ -490,6 +492,14 @@ std::vector<segment_data> editor_mode::extend_pieces() const
return pieces;
}
std::vector<segment_data> editor_mode::extend_preview() const
{
m_follow_preview = true;
auto pieces{extend_pieces()};
m_follow_preview = false;
return pieces;
}
TTrack const *editor_mode::speed_track(TTrack const *From, TTrack const *To) const
{
if (m_lay.own_speed && m_lay.style.velocity > 0.0)

View File

@@ -691,6 +691,10 @@ void editor_mode::nmt_follow_tracks()
return;
if (state.stage != nmt_state::automatic::idle || state.fitting || ImGui::GetTime() - state.changed_time < 0.3 || ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseDown(ImGuiMouseButton_Right))
return;
// laying on, from the end of the track laid or dragged from a free end, the terrain waits for the laying to end: shaping it
// goes through every chunk under the tracks, a stall each click
if ((m_lay.active && false == m_lay.points.empty()) || m_extend.active)
return;
if (false == m_streamer.active())
{
state.changed.clear();

View File

@@ -151,7 +151,11 @@ editor_mode::selftest_result editor_mode::selftest_dispatch(std::string const &c
// screenshot
// save
// log <text>
// perf <label> the frames drawn since the last perf and the time per frame logged, with the timings and counts of the renderer
// perf <label> the frames drawn since the last perf and the time per frame logged, the longest frame and those over 33 ms,
// with the timings and counts of the renderer
// fittime <metres>... the grade line of a straight over rolling ground of each length fitted, as at a click, and the time the
// frame waits for it while the track is laid, the line fitted on the thread of the fitter
// terrainhash a hash of the heights of the terrain chunks in memory, to compare the ground two builds made
// rebuildall the geometry of every path in the sections made already laid anew, the time it took logged,
// and the 3d sleepers of all the tracks laid anew, timed apart
// sleepercheck the 3d sleepers of all the tracks checked for beams over each other and rails without enough under them
@@ -177,6 +181,16 @@ void editor_mode::selftest_step()
set_norms_free(true);
}
++editor_selftest::frames();
// how long each frame took, for the longest one perf tells
{
static auto last{std::chrono::steady_clock::now()};
auto const now{std::chrono::steady_clock::now()};
auto const frame{std::chrono::duration<double, std::milli>(now - last).count()};
last = now;
editor_selftest::frame_longest() = std::max(editor_selftest::frame_longest(), frame);
if (frame > 33.0)
++editor_selftest::frames_slow();
}
if (test.wait > 0)
{
--test.wait;

View File

@@ -24,6 +24,19 @@ inline std::size_t &frames()
return count;
}
// the longest frame since the last perf, and the frames longer than 33 ms
inline double &frame_longest()
{
static double value{0.0};
return value;
}
inline std::size_t &frames_slow()
{
static std::size_t count{0};
return count;
}
inline std::string one_line(std::string Text)
{
std::replace(Text.begin(), Text.end(), '\n', ';');

View File

@@ -129,6 +129,35 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b)));
return selftest_result::handled;
}
if (command == "fittime")
{
// the grade line of a straight laid over rolling ground fitted, as at a click, and as while the track follows the
// cursor: the frame then waits only for the ground to be sampled
for (double length{0.0}; words >> length;)
{
auto const ground = [](double const X) { return 130.0 + 4.0 * std::sin(X / 180.0) + 1.5 * std::sin(X / 37.0) + 0.2 * std::sin(X / 6.0); };
auto const bed = [&](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &) {
std::vector<double> heights;
for (auto const &point : Points)
heights.push_back(ground(point.x));
return heights;
};
auto const pieces{geometry::straight_piece({0.0, ground(0.0), 0.0}, {1.0, 0.0}, 0.0, length)};
ground_follow::ends const ends{ground(0.0), std::numeric_limits<double>::quiet_NaN(), ground(length), std::numeric_limits<double>::quiet_NaN()};
auto const grading{laying_grading(nullptr)};
auto fitted{pieces};
auto const start{std::chrono::steady_clock::now()};
ground_follow::follow(fitted, ends, bed, grading);
auto const fit{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count()};
ground_follow::fitter fitter;
auto shown{pieces};
auto const asked{std::chrono::steady_clock::now()};
ground_follow::follow_meanwhile(shown, ends, bed, grading, fitter);
auto const meanwhile{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - asked).count()};
WriteLog(format("SELFTEST fittime %.0f m: fitted in %.1f ms, %zu pieces; while laid %.2f ms", length, fit, fitted.size(), meanwhile));
}
return selftest_result::handled;
}
if (command == "layheights")
{
std::string which;

View File

@@ -113,9 +113,11 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
auto const now{std::chrono::steady_clock::now()};
auto const frames{editor_selftest::frames() - lastframe};
auto const elapsed{std::chrono::duration<double, std::milli>(now - last).count()};
WriteLog(format("SELFTEST perf %s: %zu frames, %.3f ms per frame | %s | %s", label.c_str(), frames, frames > 0 ? elapsed / frames : 0.0, editor_selftest::one_line(GfxRenderer->info_times()).c_str(), editor_selftest::one_line(GfxRenderer->info_stats()).c_str()));
WriteLog(format("SELFTEST perf %s: %zu frames, %.3f ms per frame, the longest %.1f ms, %zu over 33 ms | %s | %s", label.c_str(), frames, frames > 0 ? elapsed / frames : 0.0, editor_selftest::frame_longest(), editor_selftest::frames_slow(), editor_selftest::one_line(GfxRenderer->info_times()).c_str(), editor_selftest::one_line(GfxRenderer->info_stats()).c_str()));
last = now;
lastframe = editor_selftest::frames();
editor_selftest::frame_longest() = 0.0;
editor_selftest::frames_slow() = 0;
return selftest_result::handled;
}
if (command == "rebuildall")

View File

@@ -42,6 +42,7 @@ http://mozilla.org/MPL/2.0/.
#include <chrono>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <map>
@@ -470,6 +471,23 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co
WriteLog("SELFTEST terrainadd: " + m_terrain_status);
return selftest_result::handled;
}
if (command == "terrainhash")
{
// the heights of the chunks in memory hashed, to compare the ground two builds made
std::vector<editor_terrain *> chunks;
m_streamer.collect(chunks);
std::sort(chunks.begin(), chunks.end(), [](editor_terrain const *A, editor_terrain const *B) { return A->key() < B->key(); });
std::uint64_t hash{1469598103934665603ull};
for (auto const *chunk : chunks)
for (auto const height : chunk->heights())
{
std::uint32_t bits;
std::memcpy(&bits, &height, sizeof(bits));
hash = (hash ^ bits) * 1099511628211ull;
}
WriteLog(format("SELFTEST terrainhash: %zu chunks, %016llx", chunks.size(), static_cast<unsigned long long>(hash)));
return selftest_result::handled;
}
if (command == "terrainprobe")
{
double x{0.0};

View File

@@ -1832,7 +1832,7 @@ std::string editor_mode::track_readout() const
{
double length{0.0};
double radius{0.0};
auto const pieces{extend_pieces()};
auto const pieces{extend_preview()};
for (auto const &piece : pieces)
{
length += bezier{piece}.plan_length();
@@ -2238,7 +2238,7 @@ void editor_mode::draw_build_overlay() const
ImVec2 screen;
if (projection.project(target.position, screen))
drawlist->AddCircle(screen, 14.0f, IM_COL32(255, 60, 255, 255), 20, 3.0f);
for (auto const &piece : extend_pieces())
for (auto const &piece : extend_preview())
drawpath(piece, overlay_color::marked);
}
else if (plan_crossover(plan))
@@ -2249,7 +2249,7 @@ void editor_mode::draw_build_overlay() const
}
else
{
for (auto const &piece : extend_pieces())
for (auto const &piece : extend_preview())
drawpath(piece, overlay_color::marked);
}
}

View File

@@ -134,6 +134,16 @@ void editor_mode::follow_ground(std::vector<segment_data> &Pieces, double const
ground_follow::follow(Pieces, {Height0, Grade0, Height1, Grade1}, [](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &) { return std::vector<double>(Points.size(), std::numeric_limits<double>::quiet_NaN()); });
return;
}
auto const grading{laying_grading(Style)};
auto const bed = [&](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &Directions) { return bed_heights(Points, Directions, Style); };
if (m_follow_preview)
ground_follow::follow_meanwhile(Pieces, {Height0, Grade0, Height1, Grade1}, bed, grading, m_fitter);
else
ground_follow::follow(Pieces, {Height0, Grade0, Height1, Grade1}, bed, grading, &m_fitter);
}
ground_follow::grading editor_mode::laying_grading(TTrack const *Style) const
{
// the line goes over the ground as the tracks go onto the terrain: the vertical curves of the norms, the grades as steep as the
// ground has them, a rise or a hollow longer than a structure followed closely, the bumps and what parts from it gone past
ground_follow::grading grading;
@@ -148,7 +158,7 @@ void editor_mode::follow_ground(std::vector<segment_data> &Pieces, double const
grading.fit.follow_steepest = 100.0;
grading.fit.follow_tolerance = 0.05;
grading.reach = kModel;
ground_follow::follow(Pieces, {Height0, Grade0, Height1, Grade1}, [&](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &Directions) { return bed_heights(Points, Directions, Style); }, grading);
return grading;
}
glm::dvec3 editor_mode::lay_bed(glm::dvec3 const &Point)

View File

@@ -62,13 +62,47 @@ samples sample(std::vector<segment_data> const &Pieces, std::vector<double> cons
return result;
}
bool grade_line(samples const &Points, std::vector<double> const &Targets, double const Total, ends const &Ends, grading const &Grading, profile::line &Line)
bool same(double const A, double const B)
{
return A == B || (std::isnan(A) && std::isnan(B));
}
bool same(std::vector<double> const &A, std::vector<double> const &B)
{
return A.size() == B.size() && std::equal(A.begin(), A.end(), B.begin(), [](double const X, double const Y) { return same(X, Y); });
}
bool same(ends const &A, ends const &B)
{
return same(A.height0, B.height0) && same(A.grade0, B.grade0) && same(A.height1, B.height1) && same(A.grade1, B.grade1);
}
bool same(grading const &A, grading const &B)
{
auto const &a{A.settings};
auto const &b{B.settings};
auto const &an{a.norms};
auto const &bn{b.norms};
auto const &af{A.fit};
auto const &bf{B.fit};
// the laying tells no points, humps nor bounds: a grading with any isn't taken for another
return a.points.empty() && b.points.empty() && a.speed == b.speed && a.join_start == b.join_start && a.join_end == b.join_end && an.grade_max == bn.grade_max && an.grade_switch_max == bn.grade_switch_max &&
an.radius_factor == bn.radius_factor && an.radius_min == bn.radius_min && an.curve_threshold == bn.curve_threshold && an.element_min == bn.element_min && an.fixed_tolerance == bn.fixed_tolerance &&
af.tolerance == bf.tolerance && af.grade_max == bf.grade_max && af.follow_length == bf.follow_length && af.follow_height == bf.follow_height && af.follow_steepest == bf.follow_steepest &&
af.follow_tolerance == bf.follow_tolerance && af.humps.empty() && bf.humps.empty() && af.hump_radius == bf.hump_radius && af.bounds.empty() && bf.bounds.empty() && af.hold_grades == bf.hold_grades &&
A.reach == B.reach;
}
bool grade_line(request const &Asked, profile::line &Line)
{
auto const Total{Asked.total};
auto const &Ends{Asked.heights};
auto const &Grading{Asked.grades};
auto const none{std::numeric_limits<double>::quiet_NaN()};
std::vector<double> chainages{0.0};
std::vector<double> ground{none};
chainages.insert(chainages.end(), Points.chainages.begin(), Points.chainages.end());
ground.insert(ground.end(), Targets.begin(), Targets.end());
chainages.insert(chainages.end(), Asked.chainages.begin(), Asked.chainages.end());
ground.insert(ground.end(), Asked.targets.begin(), Asked.targets.end());
chainages.push_back(Total);
ground.push_back(none);
if (std::none_of(ground.begin(), ground.end(), [](double const Height) { return std::isfinite(Height); }))
@@ -151,6 +185,61 @@ void lift(segment_data &Piece, std::pair<double, double> const &Span, std::funct
control2 = {-along.x, -end * length / 3.0, -along.y};
}
// the pieces cut where the grade of the line breaks and lifted onto it, the chainages of the line Scale times those of the
// pieces; with no line the heights lead from one end to the other
void lay_on(std::vector<segment_data> &Pieces, std::vector<double> const &Lengths, double const Total, ends const &Ends, profile::line const *Graded, double const Scale)
{
std::vector<std::pair<double, double>> spans;
if (Graded != nullptr)
{
auto knots{profile::breaks(*Graded)};
for (auto &knot : knots)
knot /= Scale;
// the ends of the track go on in the height and the grade of the tracks they join, whatever the line found
auto result{cut(Pieces, Lengths, knots, spans)};
for (std::size_t i = 0; i < result.size(); ++i)
lift(result[i], spans[i], [&](double const Chainage, double &Grade) {
if (Chainage < 1e-6 && false == std::isnan(Ends.grade0))
{
Grade = Ends.grade0;
return Ends.height0;
}
if (Chainage > Total - 1e-6 && false == std::isnan(Ends.grade1))
{
Grade = Ends.grade1;
return Ends.height1;
}
Grade = profile::grade(*Graded, Chainage * Scale) * Scale;
return profile::elevation(*Graded, Chainage * Scale);
});
Pieces = std::move(result);
return;
}
knot_line line{{0.0, Total}, {Ends.height0, Ends.height1}, {}, Ends.grade0, Ends.grade1};
line.refresh();
auto result{cut(Pieces, Lengths, line.knots, spans)};
for (std::size_t i = 0; i < result.size(); ++i)
lift(result[i], spans[i], [&](double const Chainage, double &Grade) { return line.at(Chainage, Grade); });
Pieces = std::move(result);
}
// the lengths of the pieces in plan and the ground sampled along them; returns the length of them all
double measure(std::vector<segment_data> const &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading, std::vector<double> &Lengths, request &Asked)
{
double total{0.0};
for (auto const &piece : Pieces)
total += Lengths.emplace_back(bezier{piece}.plan_length(256));
if (total < 1e-3)
return total;
auto points{sample(Pieces, Lengths, total, Ends)};
Asked.targets = Bed(points.points, points.directions);
Asked.chainages = std::move(points.chainages);
Asked.total = total;
Asked.heights = Ends;
Asked.grades = Grading;
return total;
}
} // namespace
void knot_line::refresh()
@@ -177,45 +266,109 @@ double knot_line::at(double const Chainage, double &Grade) const
return geometry::hermite(length, heights[i], slopes[i], heights[i + 1], slopes[i + 1], std::clamp((Chainage - knots[i]) / length, 0.0, 1.0), Grade);
}
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading)
bool request::operator==(request const &Other) const
{
return total == Other.total && same(heights, Other.heights) && chainages == Other.chainages && same(targets, Other.targets) && same(grades, Other.grades);
}
fitter::~fitter()
{
{
std::lock_guard<std::mutex> lock{m_mutex};
m_stop = true;
}
m_wake.notify_all();
if (m_thread.joinable())
m_thread.join();
}
bool fitter::done(request const &Request, profile::line &Line, bool &Graded) const
{
std::lock_guard<std::mutex> lock{m_mutex};
if (false == m_result.has_value() || false == (m_result->asked == Request))
return false;
Line = m_result->line;
Graded = m_result->graded;
return true;
}
bool fitter::latest(request &Request, profile::line &Line, bool &Graded) const
{
std::lock_guard<std::mutex> lock{m_mutex};
if (false == m_result.has_value())
return false;
Request = m_result->asked;
Line = m_result->line;
Graded = m_result->graded;
return true;
}
void fitter::ask(request Request)
{
{
std::lock_guard<std::mutex> lock{m_mutex};
if ((m_asked.has_value() && *m_asked == Request) || (m_fitting.has_value() && *m_fitting == Request) || (m_result.has_value() && m_result->asked == Request))
return;
m_asked = std::move(Request);
if (false == m_thread.joinable())
m_thread = std::thread{[this]() { work(); }};
}
m_wake.notify_one();
}
void fitter::work()
{
std::unique_lock<std::mutex> lock{m_mutex};
for (;;)
{
m_wake.wait(lock, [this]() { return m_stop || m_asked.has_value(); });
if (m_stop)
return;
m_fitting = std::move(m_asked);
m_asked.reset();
result fitted{*m_fitting};
lock.unlock();
fitted.graded = grade_line(fitted.asked, fitted.line);
lock.lock();
m_result = std::move(fitted);
m_fitting.reset();
}
}
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading, fitter const *Fitted)
{
std::vector<double> lengths;
double total{0.0};
for (auto const &piece : Pieces)
total += lengths.emplace_back(bezier{piece}.plan_length(256));
request asked;
auto const total{measure(Pieces, Ends, Bed, Grading, lengths, asked)};
if (total < 1e-3)
return;
auto const points{sample(Pieces, lengths, total, Ends)};
auto const targets{Bed(points.points, points.directions)};
std::vector<std::pair<double, double>> spans;
if (profile::line graded; grade_line(points, targets, total, Ends, Grading, graded))
profile::line graded;
bool found{false};
if (Fitted == nullptr || false == Fitted->done(asked, graded, found))
found = grade_line(asked, graded);
lay_on(Pieces, lengths, total, Ends, found ? &graded : nullptr, 1.0);
}
void follow_meanwhile(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading, fitter &Fitter)
{
std::vector<double> lengths;
request asked;
auto const total{measure(Pieces, Ends, Bed, Grading, lengths, asked)};
if (total < 1e-3)
return;
profile::line graded;
bool found{false};
if (Fitter.done(asked, graded, found))
{
// the ends of the track go on in the height and the grade of the tracks they join, whatever the line found
auto result{cut(Pieces, lengths, profile::breaks(graded), spans)};
for (std::size_t i = 0; i < result.size(); ++i)
lift(result[i], spans[i], [&](double const Chainage, double &Grade) {
if (Chainage < 1e-6 && false == std::isnan(Ends.grade0))
{
Grade = Ends.grade0;
return Ends.height0;
}
if (Chainage > total - 1e-6 && false == std::isnan(Ends.grade1))
{
Grade = Ends.grade1;
return Ends.height1;
}
Grade = profile::grade(graded, Chainage);
return profile::elevation(graded, Chainage);
});
Pieces = std::move(result);
lay_on(Pieces, lengths, total, Ends, found ? &graded : nullptr, 1.0);
return;
}
knot_line line{{0.0, total}, {Ends.height0, Ends.height1}, {}, Ends.grade0, Ends.grade1};
line.refresh();
auto result{cut(Pieces, lengths, line.knots, spans)};
for (std::size_t i = 0; i < result.size(); ++i)
lift(result[i], spans[i], [&](double const Chainage, double &Grade) { return line.at(Chainage, Grade); });
Pieces = std::move(result);
// the cursor moves on a little from frame to frame: the line fitted for where it was a moment ago is near enough, as long
// as the track goes on from the same end
request last;
auto const stretched{Fitter.latest(last, graded, found) && found && last.total > 1e-3 && same(last.heights.height0, Ends.height0) && same(last.heights.grade0, Ends.grade0)};
Fitter.ask(std::move(asked));
lay_on(Pieces, lengths, total, Ends, stretched ? &graded : nullptr, stretched ? last.total / total : 1.0);
}
} // namespace ground_follow

View File

@@ -9,7 +9,11 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <condition_variable>
#include <functional>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
#include <glm/glm.hpp>
@@ -49,7 +53,54 @@ struct grading
double reach{100.0}; // m, how far the ground may part from the line and still be followed
};
// where Bed finds no ground, the heights lead from one end to the other
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading = {});
// the ground under a track, sampled along it, and what the grade line is fitted to
struct request
{
std::vector<double> chainages;
std::vector<double> targets; // the height of the bed at each chainage, NaN where there's no ground
double total{0.0}; // m, the length in plan
ends heights;
grading grades;
bool operator==(request const &Other) const;
};
// the grade lines of the track shown while it's laid, fitted on a thread of their own: fitting one takes some milliseconds,
// tens of them on a long track over the terrain model, and the track follows the cursor frame after frame. The latest one
// asked for is fitted, the ones asked for meanwhile are left out
class fitter
{
public:
~fitter();
// the line of exactly this request, once fitted; graded false: none was found, the heights lead from end to end
bool done(request const &Request, profile::line &Line, bool &Graded) const;
// the line fitted last, of whatever request
bool latest(request &Request, profile::line &Line, bool &Graded) const;
void ask(request Request);
private:
struct result
{
request asked;
profile::line line;
bool graded{false};
};
void work();
mutable std::mutex m_mutex;
std::condition_variable m_wake;
std::optional<request> m_asked;
std::optional<request> m_fitting;
std::optional<result> m_result;
bool m_stop{false};
std::thread m_thread;
};
// where Bed finds no ground, the heights lead from one end to the other. With a fitter the line it fitted for the same ground
// is taken instead of fitting it again
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading = {}, fitter const *Fitted = nullptr);
// the same, though the line is fitted by the fitter: until it has it, the track gets the line it fitted last stretched over its
// length, if it went on from the same end, or the heights from end to end
void follow_meanwhile(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed, grading const &Grading, fitter &Fitter);
} // namespace ground_follow