diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 2db70fea..efbd0ba5 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -121,6 +121,7 @@ double const kLaySnapRadius{5.0}; float const kSnapPixels{16.0f}; double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch double const kLineReach{2000.0}; // how far each way the Curve tab follows the line +double const kDigitizeFit{0.5}; double const kDrawnReach{100000.0}; double const kJointRange{400.0}; // the ends of the paths are drawn for the tracks this close to the camera ImU32 const kJointColor{IM_COL32(255, 255, 255, 200)}; @@ -1865,6 +1866,8 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * auto const preferred{m_route.design.shape}; auto const other{preferred == alignment::transition_shape::clothoid ? alignment::transition_shape::cubic_parabola : alignment::transition_shape::clothoid}; alignment::result result; + alignment::design best; + alignment::result bestresult; double closest{std::numeric_limits::max()}; TTrack *farthest{nullptr}; bool fits{false}; @@ -1906,8 +1909,18 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * { closest = away; farthest = worst; + best = design; + bestresult = result; } } + double fit{0.0}; + if (false == fits && closest <= kDigitizeFit) + { + design = best; + result = bestresult; + fit = closest; + fits = true; + } if (false == fits) { if (Reason != nullptr) @@ -1933,7 +1946,7 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * auto const layer{covered.empty() ? alignment_store::layer_of(*Chain.tracks.front()) : records[*covered.begin()].layer}; for (auto index{covered.rbegin()}; index != covered.rend(); ++index) alignment_store::forget(*index); - alignment_store::store(design, result, origin, layer, "drawn"); + alignment_store::store(design, result, origin, layer, "drawn", fit); return true; } diff --git a/editor/editorAlignmentStore.cpp b/editor/editorAlignmentStore.cpp index c07b41ea..506fa566 100644 --- a/editor/editorAlignmentStore.cpp +++ b/editor/editorAlignmentStore.cpp @@ -41,7 +41,7 @@ bool g_loaded{false}; // format of the lines, coordinates in the space of the file: // alignment -// source origin shape speed +// source origin shape speed [fit ] // ends // norms // pieces @@ -88,15 +88,18 @@ void sample(record &Record) bool agrees(std::vector const &Stored, std::vector const &Made) { - if (Stored.size() != Made.size()) + std::vector stored; + std::vector made; + std::copy_if(Stored.begin(), Stored.end(), std::back_inserter(stored), [](stored_element const &E) { return E.length > 0.01; }); + for (auto const &e : Made) + if (e.length > 0.01) + made.push_back(describe(e)); + if (stored.size() != made.size()) return false; - for (std::size_t i = 0; i < Stored.size(); ++i) - { - auto const made{describe(Made[i])}; - if (Stored[i].kind != made.kind || std::abs(Stored[i].chainage - made.chainage) > 0.01 || std::abs(Stored[i].length - made.length) > 0.01 || - std::abs(Stored[i].radius_start - made.radius_start) > 0.01 || std::abs(Stored[i].radius_end - made.radius_end) > 0.01) + for (std::size_t i = 0; i < stored.size(); ++i) + if (stored[i].kind != made[i].kind || std::abs(stored[i].chainage - made[i].chainage) > 0.01 || std::abs(stored[i].length - made[i].length) > 0.01 || + std::abs(stored[i].radius_start - made[i].radius_start) > 0.01 || std::abs(stored[i].radius_end - made[i].radius_end) > 0.01) return false; - } return true; } @@ -118,7 +121,7 @@ void mark(scene::layer_handle const Layer) lines.emplace_back("# horizontal alignments designed in the track editor, the simulation skips these lines"); auto const &design{entry.design}; lines.push_back("alignment " + local(design.start) + ' ' + local(design.end)); - lines.push_back(format("source %s origin %.4f shape %s speed %.1f", entry.source.c_str(), entry.origin, shape_name(design.shape), design.speed)); + lines.push_back(format("source %s origin %.4f shape %s speed %.1f", entry.source.c_str(), entry.origin, shape_name(design.shape), design.speed) + (entry.fit > 0.0 ? format(" fit %.3f", entry.fit) : std::string{})); auto const startdirection{local_direction(context, design.start, design.start_direction)}; auto const enddirection{local_direction(context, design.end, design.end_direction)}; lines.push_back(format("ends %.9f %.9f %.9f %.9f %.6f %.6f %.3f %.3f", startdirection.x, startdirection.y, enddirection.x, enddirection.y, design.start_grade, design.end_grade, design.start_radius, design.end_radius)); @@ -319,6 +322,8 @@ void read_part(record &Entry, std::string const &Keyword, std::istringstream &Li std::string shape; Line >> Entry.source >> word >> Entry.origin >> word >> shape >> word >> design.speed; design.shape = shape == "clothoid" ? alignment::transition_shape::clothoid : alignment::transition_shape::cubic_parabola; + if (Line >> word && word == "fit") + Line >> Entry.fit; } else if (Keyword == "ends") { @@ -428,7 +433,8 @@ double approximation(geometry::bezier const &Curve) std::optional project(record const &Entry, std::size_t const Index, glm::dvec2 const &Point, double const Reach) { auto const &entry{Entry}; - auto const margin{Reach + kStep}; + auto const reach{Reach + entry.fit}; + auto const margin{reach + kStep}; if (entry.samples.empty() || Point.x < entry.low.x - margin || Point.y < entry.low.y - margin || Point.x > entry.high.x + margin || Point.y > entry.high.y + margin) return std::nullopt; std::size_t nearest{0}; @@ -458,13 +464,13 @@ std::optional project(record const &Entry, std::size_t const Index, glm::dv } auto const chainage{(low + high) * 0.5}; auto const offset{away(chainage)}; - if (offset > Reach) + if (offset > reach) return std::nullopt; if (chainage < 1e-3 || chainage > entry.result.length - 1e-3) { auto const end{alignment::evaluate(entry.result, chainage)}; auto const outward{glm::dot(Point - plan_of(end.position), glm::normalize(plan_of(end.direction))) * (chainage < 1e-3 ? -1.0 : 1.0)}; - if (outward > tolerance) + if (outward > tolerance + entry.fit) return std::nullopt; } auto const &elements{entry.result.elements}; @@ -659,7 +665,7 @@ int departed(std::vector const &Tracks, double &Departure) for (std::size_t r = 0; r < g_records.size(); ++r) { auto const found{project(r, plan_of(curve.point(t)), reach)}; - if (false == found.has_value() || found->offset <= tolerance || found->offset <= Departure) + if (false == found.has_value() || found->offset <= tolerance + g_records[r].fit || found->offset <= Departure) continue; auto known{gaps.find(r)}; if (known == gaps.end()) @@ -724,11 +730,12 @@ std::optional chainage_on(alignment::result const &Result, glm::dvec3 co return found->chainage; } -int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source) +int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source, double const Fit) { load(); record entry; entry.source = Source; + entry.fit = Fit; entry.design = Design; entry.design.start_reserve = 0.0; entry.design.end_reserve = 0.0; diff --git a/editor/editorAlignmentStore.hpp b/editor/editorAlignmentStore.hpp index b5f0b79c..61dff97d 100644 --- a/editor/editorAlignmentStore.hpp +++ b/editor/editorAlignmentStore.hpp @@ -47,6 +47,7 @@ struct record alignment::result result; // made of the design std::vector elements; // as written in the file bool consistent{true}; // the design makes the elements written in the file + double fit{0.0}; glm::dvec2 low{0.0}; glm::dvec2 high{0.0}; std::vector samples; @@ -84,7 +85,7 @@ int find(glm::dvec3 const &Start, glm::dvec3 const &End, bool &Reversed); // stored alignment near the tracks, whose line they no longer follow; Departure receives the largest distance int departed(std::vector const &Tracks, double &Departure); // keeps the design in the file of the layer, in place of the one with the same ends -int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source = "designed"); +int store(alignment::design const &Design, alignment::result const &Result, double const Origin, scene::layer_handle const Layer, std::string const &Source = "designed", double const Fit = 0.0); bool follows(alignment::result const &Result, std::vector const &Tracks); double deviation(alignment::result const &Result, std::vector const &Tracks, TTrack *&Worst); std::optional chainage_on(alignment::result const &Result, glm::dvec3 const &Point);