diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index cc32a0ce..44bfcd3e 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -18,6 +18,7 @@ http: #include #include #include +#include namespace alignment { @@ -932,133 +933,165 @@ void spread_rolls(std::vector &Pieces, double const Start, double } // namespace +namespace +{ + +struct join_frame +{ + join_request const &request; + glm::dvec2 start_direction; + glm::dvec2 end_direction; + glm::dvec2 chord; + double distance; + double tolerance; + double heading; + double rise; +}; + +std::optional straight_join(join_frame const &Frame) +{ + auto const &a{Frame.request.start}; + auto const &b{Frame.request.end}; + if (std::abs(geometry::signed_angle(Frame.start_direction, Frame.chord)) > Frame.tolerance || std::abs(geometry::signed_angle(Frame.chord, Frame.end_direction)) > Frame.tolerance) + return std::nullopt; + join_result straight; + straight.kind = join_kind::straight; + segment_data path; + path.points[segment_data::point::start] = a.position; + path.points[segment_data::point::end] = b.position; + straight.pieces = {path}; + spread_rolls(straight.pieces, a.roll, b.roll); + straight.length = glm::distance(a.position, b.position); + return straight; +} + +std::optional arc_join(join_frame const &Frame) +{ + auto const &a{Frame.request.start}; + auto const &b{Frame.request.end}; + auto const lateral{cross(Frame.start_direction, Frame.chord)}; + if (std::abs(lateral) <= 1e-6) + return std::nullopt; + auto const angle{2.0 * std::atan2(std::abs(lateral), glm::dot(Frame.start_direction, Frame.chord))}; + int const side{lateral > 0.0 ? 1 : -1}; + auto const radius{Frame.distance * Frame.distance / (2.0 * std::abs(lateral))}; + if (angle >= kPi - 1e-3 || std::abs(geometry::signed_angle(turned(Frame.start_direction, side * angle), Frame.end_direction)) > Frame.tolerance) + return std::nullopt; + join_result arc; + arc.kind = join_kind::arc; + arc.pieces = geometry::arc_pieces(a.position, Frame.start_direction, Frame.rise / (radius * angle), radius, angle, side); + arc.pieces.back().points[segment_data::point::end] = b.position; + spread_rolls(arc.pieces, a.roll, b.roll); + arc.radius = radius; + arc.length = radius * angle; + return arc; +} + +design join_design(join_frame const &Frame, join_kind const Kind) +{ + auto const &request{Frame.request}; + design shape; + shape.norms = request.norms; + shape.shape = request.shape; + shape.speed = request.speed; + shape.arc_piece_angle = 90.0; + shape.start = request.start.position; + shape.end = request.end.position; + shape.start_direction = Frame.start_direction; + shape.end_direction = Frame.end_direction; + shape.start_grade = Frame.rise / Frame.distance; + shape.end_grade = shape.start_grade; + vertex corner; + corner.radius = request.radius > 0.0 ? request.radius : 100000.0; + if (Kind == join_kind::curve) + shape.vertices = {corner}; + else + { + corner.offset = Frame.distance / 3.0; + shape.vertices = {corner, corner}; + } + return shape; +} + +std::optional fitted_join(join_frame const &Frame, join_kind const Kind) +{ + auto shape{join_design(Frame, Kind)}; + auto const r{compute_recommended(shape, Frame.request.transitions)}; + auto const sharp{std::any_of(r.curves.begin(), r.curves.end(), [](curve_report const &Report) { return Report.radius < 10.0; })}; + if (false == r.valid || sharp || turning(r) > Frame.heading + kPi + 1e-6 || r.length > 3.0 * Frame.distance + 1.0) + return std::nullopt; + join_result curve; + curve.kind = Kind; + curve.pieces = pieces(r, shape, 0); + if (curve.pieces.empty()) + return curve; + curve.pieces.front().rolls[0] = static_cast(Frame.request.start.roll); + curve.pieces.back().rolls[1] = static_cast(Frame.request.end.roll); + curve.length = r.length; + curve.speed = shape.speed; + curve.warnings = r.warnings; + curve.radius = r.curves.empty() ? 0.0 : std::numeric_limits::max(); + for (auto const &report : r.curves) + { + curve.radius = std::min(curve.radius, report.radius); + curve.transition = std::max({curve.transition, report.transition_in, report.transition_out}); + } + for (auto const &e : r.elements) + curve.cant = std::max(curve.cant, e.cant); + return curve; +} + +std::optional curve_join(join_frame const &Frame) +{ + design probe; + probe.start = Frame.request.start.position; + probe.end = Frame.request.end.position; + probe.start_direction = Frame.start_direction; + probe.end_direction = Frame.end_direction; + glm::dvec2 intersection; + return fitted_join(Frame, tangent_intersection(probe, intersection) ? join_kind::curve : join_kind::reverse_curve); +} + +join_result bezier_join(join_frame const &Frame) +{ + auto const &a{Frame.request.start}; + auto const &b{Frame.request.end}; + join_result fallback; + fallback.kind = join_kind::bezier; + auto const grade{Frame.rise / Frame.distance}; + auto const handle{Frame.distance / 3.0}; + segment_data path; + path.points[segment_data::point::start] = a.position; + path.points[segment_data::point::end] = b.position; + path.points[segment_data::point::control1] = glm::dvec3{Frame.start_direction.x, grade, Frame.start_direction.y} * handle; + path.points[segment_data::point::control2] = -glm::dvec3{Frame.end_direction.x, grade, Frame.end_direction.y} * handle; + fallback.pieces = {path}; + spread_rolls(fallback.pieces, a.roll, b.roll); + fallback.length = Frame.distance; + return fallback; +} + +} // namespace + join_result join(join_request const &Request) { - join_result chosen; auto const &a{Request.start}; auto const &b{Request.end}; auto const chord{plan_of(b.position) - plan_of(a.position)}; auto const distance{glm::length(chord)}; if (distance < 0.05 || glm::length(a.direction) < 1e-9 || glm::length(b.direction) < 1e-9) - return chosen; + return {}; auto const startdirection{glm::normalize(a.direction)}; auto const enddirection{glm::normalize(b.direction)}; - auto const tolerance{glm::radians(Request.kink)}; - auto const heading{std::abs(geometry::signed_angle(startdirection, enddirection))}; - auto const rise{b.position.y - a.position.y}; + join_frame const frame{Request, startdirection, enddirection, chord, distance, glm::radians(Request.kink), std::abs(geometry::signed_angle(startdirection, enddirection)), b.position.y - a.position.y}; std::vector candidates; - - if (std::abs(geometry::signed_angle(startdirection, chord)) <= tolerance && std::abs(geometry::signed_angle(chord, enddirection)) <= tolerance) - { - join_result straight; - straight.kind = join_kind::straight; - segment_data path; - path.points[segment_data::point::start] = a.position; - path.points[segment_data::point::end] = b.position; - straight.pieces = {path}; - spread_rolls(straight.pieces, a.roll, b.roll); - straight.length = glm::distance(a.position, b.position); - candidates.push_back(straight); - } - - auto const lateral{cross(startdirection, chord)}; - auto const along{glm::dot(startdirection, chord)}; - if (std::abs(lateral) > 1e-6) - { - auto const angle{2.0 * std::atan2(std::abs(lateral), along)}; - int const side{lateral > 0.0 ? 1 : -1}; - auto const radius{distance * distance / (2.0 * std::abs(lateral))}; - if (angle < kPi - 1e-3 && std::abs(geometry::signed_angle(turned(startdirection, side * angle), enddirection)) <= tolerance) - { - join_result arc; - arc.kind = join_kind::arc; - arc.pieces = geometry::arc_pieces(a.position, startdirection, rise / (radius * angle), radius, angle, side); - arc.pieces.back().points[segment_data::point::end] = b.position; - spread_rolls(arc.pieces, a.roll, b.roll); - arc.radius = radius; - arc.length = radius * angle; - candidates.push_back(arc); - } - } - - auto const fitted = [&](join_kind const Kind) { - join_result curve; - design shape; - shape.norms = Request.norms; - shape.shape = Request.shape; - shape.speed = Request.speed; - shape.arc_piece_angle = 90.0; - shape.start = a.position; - shape.end = b.position; - shape.start_direction = startdirection; - shape.end_direction = enddirection; - shape.start_grade = shape.end_grade = rise / distance; - vertex corner; - corner.radius = Request.radius > 0.0 ? Request.radius : 100000.0; - if (Kind == join_kind::curve) - shape.vertices = {corner}; - else - { - corner.offset = distance / 3.0; - shape.vertices = {corner, corner}; - } - auto const r{compute_recommended(shape, Request.transitions)}; - auto const sharp{std::any_of(r.curves.begin(), r.curves.end(), [](curve_report const &Report) { return Report.radius < 10.0; })}; - if (false == r.valid || sharp || turning(r) > heading + kPi + 1e-6 || r.length > 3.0 * distance + 1.0) - return curve; - curve.kind = Kind; - curve.pieces = pieces(r, shape, 0); - if (curve.pieces.empty()) - return curve; - curve.pieces.front().rolls[0] = static_cast(a.roll); - curve.pieces.back().rolls[1] = static_cast(b.roll); - curve.length = r.length; - curve.speed = shape.speed; - curve.warnings = r.warnings; - curve.radius = std::numeric_limits::max(); - for (auto const &report : r.curves) - { - curve.radius = std::min(curve.radius, report.radius); - curve.transition = std::max({curve.transition, report.transition_in, report.transition_out}); - } - for (auto const &e : r.elements) - curve.cant = std::max(curve.cant, e.cant); - if (r.curves.empty()) - curve.radius = 0.0; - return curve; - }; - design probe; - probe.start = a.position; - probe.end = b.position; - probe.start_direction = startdirection; - probe.end_direction = enddirection; - glm::dvec2 intersection; - if (tangent_intersection(probe, intersection)) - { - if (auto curve{fitted(join_kind::curve)}; curve.kind != join_kind::none) - candidates.push_back(curve); - } - else if (auto curve{fitted(join_kind::reverse_curve)}; curve.kind != join_kind::none) - candidates.push_back(curve); - + for (auto const &candidate : {straight_join(frame), arc_join(frame), curve_join(frame)}) + if (candidate.has_value()) + candidates.push_back(*candidate); if (candidates.empty()) - { - join_result fallback; - fallback.kind = join_kind::bezier; - auto const grade{rise / distance}; - auto const handle{distance / 3.0}; - segment_data path; - path.points[segment_data::point::start] = a.position; - path.points[segment_data::point::end] = b.position; - path.points[segment_data::point::control1] = glm::dvec3{startdirection.x, grade, startdirection.y} * handle; - path.points[segment_data::point::control2] = -glm::dvec3{enddirection.x, grade, enddirection.y} * handle; - fallback.pieces = {path}; - spread_rolls(fallback.pieces, a.roll, b.roll); - fallback.length = distance; - candidates.push_back(fallback); - } + candidates.push_back(bezier_join(frame)); auto const count{static_cast(candidates.size())}; - chosen = candidates[((Request.variant % count) + count) % count]; + auto chosen{candidates[((Request.variant % count) + count) % count]}; chosen.variants = count; if (chosen.speed <= 0.0) chosen.speed = Request.speed; diff --git a/editor/editorAlignmentStore.cpp b/editor/editorAlignmentStore.cpp index e9d87c61..55d3445c 100644 --- a/editor/editorAlignmentStore.cpp +++ b/editor/editorAlignmentStore.cpp @@ -264,6 +264,124 @@ void reset() g_loaded = false; } +namespace +{ + +std::optional read_start(std::istringstream &Line, scene::layer_context const &Context, scene::layer_handle const Layer) +{ + record entry; + entry.layer = Layer; + glm::dvec3 start; + glm::dvec3 end; + Line >> start.x >> start.y >> start.z >> end.x >> end.y >> end.z; + if (Line.fail()) + return std::nullopt; + entry.design.start = Context.to_world(start); + entry.design.end = Context.to_world(end); + return entry; +} + +std::optional read_vertex(std::istringstream &Line, scene::layer_context const &Context) +{ + alignment::vertex vertex; + glm::dvec3 position{0.0}; + int reverse{0}; + int compound{0}; + std::size_t arcs{0}; + Line >> position.x >> position.z >> vertex.offset >> vertex.radius >> vertex.transition_in >> vertex.transition_out >> vertex.cant >> reverse >> compound >> vertex.share >> arcs; + for (std::size_t i = 0; i < arcs && false == Line.fail(); ++i) + { + alignment::compound_arc arc; + Line >> arc.radius >> arc.transition >> arc.share; + vertex.arcs.push_back(arc); + } + if (Line.fail()) + return std::nullopt; + position = Context.to_world(position); + vertex.position = {position.x, position.z}; + vertex.reverse_turn = (reverse != 0); + vertex.compound = (compound != 0); + return vertex; +} + +void read_part(record &Entry, std::string const &Keyword, std::istringstream &Line, scene::layer_context const &Context) +{ + auto &design{Entry.design}; + std::string word; + if (Keyword == "source") + { + 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; + } + else if (Keyword == "ends") + { + glm::dvec2 startdirection; + glm::dvec2 enddirection; + Line >> startdirection.x >> startdirection.y >> enddirection.x >> enddirection.y >> design.start_grade >> design.end_grade >> design.start_radius >> design.end_radius; + design.start_direction = world_direction(Context, Context.to_local(design.start), startdirection); + design.end_direction = world_direction(Context, Context.to_local(design.end), enddirection); + } + else if (Keyword == "norms") + { + auto &norms{design.norms}; + Line >> norms.gauge >> norms.unbalanced >> norms.cant_max >> norms.cant_rate >> norms.ramp_factor >> norms.jerk >> norms.tangent_min_time >> norms.arc_min >> norms.arc_min_factor; + } + else if (Keyword == "pieces") + Line >> design.transition_pieces >> design.arc_piece_angle; + else if (Keyword == "vertex") + { + if (auto const vertex{read_vertex(Line, Context)}) + design.vertices.push_back(*vertex); + } + else if (Keyword == "element") + { + stored_element e; + Line >> e.kind >> e.vertex >> e.chainage >> e.length >> e.radius_start >> e.radius_end >> e.cant; + if (false == Line.fail()) + Entry.elements.push_back(e); + } +} + +void close_record(record &Entry, std::string const &Layer) +{ + finish(Entry); + if (false == Entry.result.valid || Entry.result.elements.empty()) + { + WriteLog("Editor: a horizontal alignment in " + Layer + " doesn't make a line, left out", logtype::generic); + return; + } + Entry.consistent = agrees(Entry.elements, Entry.result.elements); + if (false == Entry.consistent) + WriteLog("Editor: the elements of a horizontal alignment in " + Layer + " differ from the ones its design makes now", logtype::generic); + g_records.push_back(std::move(Entry)); +} + +void load_layer(scene::layer_handle const Layer) +{ + auto const &context{scene::Layers.layer(Layer).context_insert()}; + std::optional current; + for (auto const &text : scene::Layers.marked(Layer, kMark)) + { + std::istringstream line{text}; + std::string keyword; + line >> keyword; + if (keyword == "alignment") + current = read_start(line, context, Layer); + else if (false == current.has_value()) + continue; + else if (keyword == "end") + { + close_record(*current, scene::Layers.layer(Layer).name); + current.reset(); + } + else + read_part(*current, keyword, line, context); + } +} + +} // namespace + void load() { if (g_loaded || scene::Layers.empty()) @@ -271,102 +389,7 @@ void load() g_loaded = true; g_records.clear(); for (auto const layer : scene::Layers.marked_layers(kMark)) - { - auto const &context{scene::Layers.layer(layer).context_insert()}; - std::optional current; - std::vector made; - for (auto const &text : scene::Layers.marked(layer, kMark)) - { - std::istringstream line{text}; - std::string keyword, word; - line >> keyword; - if (keyword == "alignment") - { - record entry; - entry.layer = layer; - glm::dvec3 start, end; - line >> start.x >> start.y >> start.z >> end.x >> end.y >> end.z; - if (line.fail()) - { - current.reset(); - continue; - } - entry.design.start = context.to_world(start); - entry.design.end = context.to_world(end); - current = entry; - continue; - } - if (false == current.has_value()) - continue; - auto &entry{*current}; - auto &design{entry.design}; - if (keyword == "source") - { - 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; - } - else if (keyword == "ends") - { - glm::dvec2 startdirection, enddirection; - line >> startdirection.x >> startdirection.y >> enddirection.x >> enddirection.y >> design.start_grade >> design.end_grade >> design.start_radius >> design.end_radius; - design.start_direction = world_direction(context, context.to_local(design.start), startdirection); - design.end_direction = world_direction(context, context.to_local(design.end), enddirection); - } - else if (keyword == "norms") - { - auto &norms{design.norms}; - line >> norms.gauge >> norms.unbalanced >> norms.cant_max >> norms.cant_rate >> norms.ramp_factor >> norms.jerk >> norms.tangent_min_time >> norms.arc_min >> norms.arc_min_factor; - } - else if (keyword == "pieces") - { - line >> design.transition_pieces >> design.arc_piece_angle; - } - else if (keyword == "vertex") - { - alignment::vertex vertex; - glm::dvec3 position{0.0}; - int reverse{0}, compound{0}; - std::size_t arcs{0}; - line >> position.x >> position.z >> vertex.offset >> vertex.radius >> vertex.transition_in >> vertex.transition_out >> vertex.cant >> reverse >> compound >> vertex.share >> arcs; - for (std::size_t i = 0; i < arcs && false == line.fail(); ++i) - { - alignment::compound_arc arc; - line >> arc.radius >> arc.transition >> arc.share; - vertex.arcs.push_back(arc); - } - if (line.fail()) - continue; - position = context.to_world(position); - vertex.position = {position.x, position.z}; - vertex.reverse_turn = (reverse != 0); - vertex.compound = (compound != 0); - design.vertices.push_back(vertex); - } - else if (keyword == "element") - { - stored_element e; - line >> e.kind >> e.vertex >> e.chainage >> e.length >> e.radius_start >> e.radius_end >> e.cant; - if (false == line.fail()) - entry.elements.push_back(e); - } - else if (keyword == "end") - { - finish(entry); - if (false == entry.result.valid || entry.result.elements.empty()) - { - WriteLog("Editor: a horizontal alignment in " + scene::Layers.layer(layer).name + " doesn't make a line, left out", logtype::generic); - current.reset(); - continue; - } - entry.consistent = agrees(entry.elements, entry.result.elements); - if (false == entry.consistent) - WriteLog("Editor: the elements of a horizontal alignment in " + scene::Layers.layer(layer).name + " differ from the ones its design makes now", logtype::generic); - g_records.push_back(std::move(entry)); - current.reset(); - } - } - } + load_layer(layer); g_base = g_records; g_changed = false; if (false == g_records.empty())