diff --git a/application/editormode.h b/application/editormode.h index 8b8ac018..f6453368 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -674,6 +674,7 @@ class editor_mode : public application_mode, private editor_track::observer void track_gizmo_settle(); void track_gizmo_stretch(TTrack &Track, double const Length); void track_gizmo_control(TTrack &Track, glm::dvec3 Control); + void track_gizmo_point(TTrack &Track, glm::dvec3 const &Position); void track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation); void track_gizmo_turn(TTrack &Track, int const Axis, double const Angle); void track_gizmo_length(TTrack &Track, double Length); diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index ed657a7c..67f1a78c 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -357,7 +357,9 @@ void editor_mode::selftest_step() auto const lateral{(point.x - base.x) * -direction.y + (point.z - base.z) * direction.x}; auto const &path{track->m_paths[0]}; auto const roll{glm::radians(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * along)}; - auto const cone{base.y - lateral * std::tan(roll)}; + auto const tangent{main.first(along)}; + auto const beyond{(point.x - base.x) * direction.x + (point.z - base.z) * direction.y}; + auto const cone{base.y + beyond * tangent.y / glm::length(geometry::plan_of(tangent)) - lateral * std::tan(roll)}; text += format(" [t %.3f off %.3f dy %.4f]", t, lateral, point.y - cone); } WriteLog("SELFTEST conecheck " + name + ":" + text); @@ -1214,10 +1216,7 @@ void editor_mode::selftest_step() words >> offset.x >> offset.y >> offset.z; if (m_track_point.valid()) { - auto const position{editor_track::point_position(*track, m_track_point) + offset}; - editor_track::move_point(*track, m_track_point, position); - for (auto const &connection : m_track_drag_points) - editor_track::move_point(*connection.first, connection.second, position); + track_gizmo_point(*track, editor_track::point_position(*track, m_track_point) + offset); } else { diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 1e39454e..2a978ddd 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -383,16 +383,18 @@ glm::mat4 gizmo_along(glm::dvec3 const &Position, glm::dvec3 const &Axis) void track_middle(TTrack const &Track, glm::dvec3 &Point, glm::dvec3 &Tangent, double &Chainage) { + auto const count{Track.eType == tt_Switch ? std::min(1, Track.m_paths.size()) : Track.m_paths.size()}; double total{0.0}; - for (auto const &path : Track.m_paths) - total += bezier{path}.plan_length(); + for (std::size_t i = 0; i < count; ++i) + total += bezier{Track.m_paths[i]}.plan_length(); Chainage = total * 0.5; double along{0.0}; - for (auto const &path : Track.m_paths) + for (std::size_t i = 0; i < count; ++i) { + auto const &path{Track.m_paths[i]}; bezier const curve{path}; auto const length{curve.plan_length()}; - if (along + length >= Chainage || &path == &Track.m_paths.back()) + if (along + length >= Chainage || i + 1 == count) { auto const t{curve.parameter(Chainage - along)}; Point = curve.point(t); @@ -417,6 +419,13 @@ double roll_into(TTrack const &Track, editor_track::point_ref const &Point) return Point.kind == editor_track::point_kind::start ? path.rolls[0] : -path.rolls[1]; } +editor_track::point_ref switch_main(TTrack const &Track, editor_track::point_ref Point) +{ + if (Track.eType == tt_Switch && Point.valid() && Point.path > 0) + Point.path = 0; + return Point; +} + editor_track::point_ref stretch_remap(editor_track::point_ref Point, int const Path, std::size_t const Pieces) { if (false == Point.valid() || Point.path < Path) @@ -686,9 +695,29 @@ void editor_mode::track_gizmo_control(TTrack &Track, glm::dvec3 Control) Control = glm::length(Control) > 1e-9 ? glm::normalize(Control) * 0.5 : drag.axis * 0.5; editor_track::move_point(Track, drag.point, editor_track::point_position(Track, editor_track::end_of(drag.point)) + Control); editor_track::align_controls(Track, drag.point, drag.partners); + if (Track.eType == tt_Switch) + { + editor_track::conform_switch(Track); + editor_track::follow(Track, m_track_joints); + editor_track::follow_grade(Track, m_track_joints); + } m_track_dirty = true; } +void editor_mode::track_gizmo_point(TTrack &Track, glm::dvec3 const &Position) +{ + editor_track::move_point(Track, m_track_point, Position); + if (Track.eType == tt_Switch) + { + editor_track::conform_switch(Track); + editor_track::follow(Track, m_track_joints); + editor_track::follow_grade(Track, m_track_joints); + } + auto const moved{editor_track::point_position(Track, m_track_point)}; + for (auto const &connection : m_track_drag_points) + editor_track::move_point(*connection.first, connection.second, moved); +} + void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation) { bool const controlmode{Pointmode && false == editor_track::is_end(m_track_point.kind)}; @@ -729,14 +758,15 @@ void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_o { std::string reason; auto const &path{partner.first->m_paths[partner.second.path]}; - if (partner.first->eType != tt_Normal || false == editor_track::can_edit_geometry(*partner.first, reason)) + bool const own{partner.first == &Track}; + if (own ? m_track_point.path != 0 : partner.first->eType != tt_Normal || false == editor_track::can_edit_geometry(*partner.first, reason)) { bool const start{partner.second.kind == editor_track::point_kind::start}; auto const theirs{path.points[start ? segment_data::point::control1 : segment_data::point::control2]}; auto const chord{path.points[start ? segment_data::point::end : segment_data::point::start] - path.points[start ? segment_data::point::start : segment_data::point::end]}; auto const along{theirs != glm::dvec3{} ? theirs : chord}; if (glm::length(along) > 1e-9) - drag.fixed = -glm::normalize(along); + drag.fixed = (own && partner.second.kind == editor_track::end_of(m_track_point).kind ? 1.0 : -1.0) * glm::normalize(along); continue; } if (false == m_track_drag_connected && partner.first != &Track) @@ -746,6 +776,18 @@ void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_o m_track_drag.emplace_back(partner.first); } } + if (false == whole && Track.eType == tt_Switch && m_track_drag_connected) + { + auto const edited{editor_track::point_position(Track, editor_track::end_of(m_track_point))}; + for (auto const &joint : editor_track::joints(Track)) + { + if (controlmode && glm::distance(editor_track::point_position(Track, joint.own), edited) < geometry::same_point) + continue; + m_track_joints.push_back(joint); + if (std::find(m_track_drag.begin(), m_track_drag.end(), joint.other) == m_track_drag.end()) + m_track_drag.emplace_back(joint.other); + } + } std::vector> states; for (auto *dragged : m_track_drag) states.emplace_back(dragged, editor_track::capture(*dragged)); @@ -778,8 +820,13 @@ void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_o drag.lengths.push_back(bezier{path}.plan_length()); along += drag.lengths.back(); } - auto const end{editor_track::end_of(drag.point)}; - if (end.valid() && end.path < static_cast(drag.chainages.size())) + auto const end{switch_main(Track, editor_track::end_of(drag.point))}; + if (Track.eType == tt_Switch && false == Track.m_paths.empty()) + { + drag.pivot_chainage = editor_track::switch_station(Track, drag.pivot); + drag.roll0 = end.valid() && end.kind == editor_track::point_kind::end ? Track.m_paths[0].rolls[1] : Track.m_paths[0].rolls[0]; + } + else if (end.valid() && end.path < static_cast(drag.chainages.size())) { drag.pivot_chainage = drag.chainages[end.path] + (end.kind == editor_track::point_kind::end ? drag.lengths[end.path] : 0.0); drag.roll0 = end.kind == editor_track::point_kind::start ? Track.m_paths[end.path].rolls[0] : Track.m_paths[end.path].rolls[1]; @@ -836,7 +883,8 @@ void editor_mode::track_gizmo_turn(TTrack &Track, int const Axis, double const A track_gizmo_control(Track, {control.x, control.y + step * run * (along >= 0.0 ? 1.0 : -1.0), control.z}); return; } - for (std::size_t i = 0; i < Track.m_paths.size() && i < drag.chainages.size(); ++i) + auto const count{Track.eType == tt_Switch ? std::min(1, Track.m_paths.size()) : Track.m_paths.size()}; + for (std::size_t i = 0; i < count && i < drag.chainages.size(); ++i) { auto &path{Track.m_paths[i]}; auto const from{drag.chainages[i] - drag.pivot_chainage}; @@ -848,12 +896,14 @@ void editor_mode::track_gizmo_turn(TTrack &Track, int const Axis, double const A control1.y += step * glm::length(plan_of(control1)); control2.y -= step * glm::length(plan_of(control2)); } + editor_track::conform_switch(Track); editor_track::follow(Track, m_track_joints); editor_track::follow_grade(Track, m_track_joints); m_track_dirty = true; return; } auto const end{editor_track::end_of(drag.point)}; + auto const canted{switch_main(Track, end)}; auto target{glm::degrees(drag.point.kind == editor_track::point_kind::control2 ? -Angle : Angle)}; if (snap) target = glm::degrees(std::asin(std::clamp(std::round(1500.0 * std::sin(glm::radians(drag.roll0 + target)) / 5.0) * 5.0 / 1500.0, -1.0, 1.0))) - drag.roll0; @@ -864,11 +914,17 @@ void editor_mode::track_gizmo_turn(TTrack &Track, int const Axis, double const A for (int i = 0; i < static_cast(Track.m_paths.size()); ++i) { auto &path{Track.m_paths[i]}; - if (false == end.valid() || (i == end.path && end.kind == editor_track::point_kind::start)) + if (false == canted.valid() || (i == canted.path && canted.kind == editor_track::point_kind::start)) path.rolls[0] += step; - if (false == end.valid() || (i == end.path && end.kind == editor_track::point_kind::end)) + if (false == canted.valid() || (i == canted.path && canted.kind == editor_track::point_kind::end)) path.rolls[1] += step; } + if (Track.eType == tt_Switch) + { + editor_track::conform_switch(Track); + editor_track::follow(Track, m_track_joints); + editor_track::follow_grade(Track, m_track_joints); + } for (auto const &joint : m_track_joints) { if (joint.own.path >= static_cast(Track.m_paths.size()) || joint.theirs.path >= static_cast(joint.other->m_paths.size())) @@ -882,6 +938,8 @@ void editor_mode::track_gizmo_turn(TTrack &Track, int const Axis, double const A } for (auto const &partner : drag.partners) { + if (partner.first == &Track) + continue; auto const roll{static_cast(roll_into(Track, end))}; auto &theirs{partner.first->m_paths[partner.second.path]}; if (partner.second.kind == editor_track::point_kind::start) @@ -994,10 +1052,8 @@ void editor_mode::render_track_gizmo() } else if (pointmode) { - glm::dvec3 const position = frame.camera + glm::dvec3(m_track_gizmo[3]); - editor_track::move_point(*track, m_track_point, position); - for (auto const &connection : m_track_drag_points) - editor_track::move_point(*connection.first, connection.second, position); + track_gizmo_point(*track, frame.camera + glm::dvec3(m_track_gizmo[3])); + auto const position{editor_track::point_position(*track, m_track_point)}; m_track_dirty = true; if (editor_track::is_end(m_track_point.kind) && m_track_drag_points.empty()) { diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 54466bd0..99d3236e 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -678,6 +678,129 @@ void editor_track::follow_grade(TTrack const &Track, std::vector const &J } } +namespace +{ + +struct surface_foot +{ + double t{0.0}; + double along{0.0}; + double lateral{0.0}; + glm::dvec3 base{0.0}; + glm::dvec2 direction{0.0, 1.0}; + double grade{0.0}; +}; + +surface_foot foot_on(segment_data const &Path, glm::dvec3 const &Position) +{ + bezier const curve{Path}; + auto const distance = [&](double const T) { return glm::distance(plan_of(curve.point(T)), plan_of(Position)); }; + int constexpr samples{200}; + double best{std::numeric_limits::max()}; + int nearest{0}; + for (int i = 0; i <= samples; ++i) + if (auto const d{distance(static_cast(i) / samples)}; d < best) + { + best = d; + nearest = i; + } + auto low{std::max(0, nearest - 1) / static_cast(samples)}; + auto high{std::min(samples, nearest + 1) / static_cast(samples)}; + for (int i = 0; i < 40; ++i) + { + auto const a{low + (high - low) / 3.0}; + auto const b{high - (high - low) / 3.0}; + if (distance(a) < distance(b)) + high = b; + else + low = a; + } + surface_foot foot; + foot.t = std::clamp((low + high) * 0.5, 0.0, 1.0); + foot.base = curve.point(foot.t); + auto const tangent{curve.first(foot.t)}; + auto const plan{plan_of(tangent)}; + auto const run{glm::length(plan)}; + if (run > 1e-9) + { + foot.direction = plan / run; + foot.grade = tangent.y / run; + } + auto const offset{plan_of(Position) - plan_of(foot.base)}; + foot.along = glm::dot(offset, foot.direction); + foot.lateral = glm::dot(offset, glm::dvec2{-foot.direction.y, foot.direction.x}); + return foot; +} + +double foot_roll(segment_data const &Path, surface_foot const &Foot) +{ + return Path.rolls[0] + (Path.rolls[1] - Path.rolls[0]) * Foot.t; +} + +double surface_height(segment_data const &Path, surface_foot const &Foot) +{ + return Foot.base.y + Foot.along * Foot.grade - Foot.lateral * std::tan(glm::radians(foot_roll(Path, Foot))); +} + +} // namespace + +void editor_track::conform_switch(TTrack &Track) +{ + if (Track.eType != tt_Switch || Track.m_paths.size() != 2) + return; + auto const &main{Track.m_paths[0]}; + auto &path{Track.m_paths[1]}; + auto &start{path.points[segment_data::point::start]}; + auto &end{path.points[segment_data::point::end]}; + auto &control1{path.points[segment_data::point::control1]}; + auto &control2{path.points[segment_data::point::control2]}; + if (control1 == glm::dvec3{} && control2 == glm::dvec3{}) + { + control1 = (end - start) / 3.0; + control2 = (start - end) / 3.0; + } + auto const height = [&](glm::dvec3 const &Point) { return surface_height(main, foot_on(main, Point)); }; + auto const slope = [&](glm::dvec3 const &Point, glm::dvec3 const &Control) { + auto const plan{plan_of(Control)}; + auto const run{glm::length(plan)}; + if (run < 1e-9) + return 0.0; + double constexpr step{0.05}; + glm::dvec3 const offset{plan.x / run * step, 0.0, plan.y / run * step}; + return (4.0 * height(Point + offset) - 3.0 * height(Point) - height(Point + 2.0 * offset)) / (2.0 * step) * run; + }; + start.y = height(start); + end.y = height(end); + control1.y = slope(start, control1); + control2.y = slope(end, control2); + bezier const curve{path}; + for (int i = 0; i < 2; ++i) + { + auto const foot{foot_on(main, i == 0 ? start : end)}; + auto const along{glm::dot(plan_of(curve.first(i)), foot.direction) >= 0.0 ? 1.0 : -1.0}; + path.rolls[i] = static_cast(foot_roll(main, foot) * along); + } +} + +double editor_track::switch_station(TTrack const &Track, glm::dvec3 const &Position) +{ + if (Track.m_paths.empty()) + return 0.0; + auto const &main{Track.m_paths[0]}; + auto const foot{foot_on(main, Position)}; + bezier const curve{main}; + int constexpr steps{64}; + double length{0.0}; + auto previous{plan_of(curve.point(0.0))}; + for (int i = 1; i <= steps; ++i) + { + auto const next{plan_of(curve.point(foot.t * i / steps))}; + length += glm::distance(previous, next); + previous = next; + } + return length + foot.along; +} + std::vector editor_track::hold_controls(TTrack &Track) { std::vector result; @@ -757,7 +880,8 @@ void editor_track::align_controls(TTrack const &Track, point_ref const &Control, bool const start{partner.second.kind == point_kind::start}; auto &control{path.points[start ? segment_data::point::control1 : segment_data::point::control2]}; auto const length{control != glm::dvec3{} ? glm::length(control) : glm::distance(path.points[segment_data::point::start], path.points[segment_data::point::end]) / 3.0}; - control = -direction * length; + auto const same{partner.first == &Track && partner.second.kind == end_of(Control).kind}; + control = (same ? direction : -direction) * length; } } diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index f4ee9ee8..8f8040aa 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -117,6 +117,8 @@ class editor_track // moves the joined ends to where the ends of the track are now, turning them as the track turned there static void follow(TTrack const &Track, std::vector const &Joints); static void follow_grade(TTrack const &Track, std::vector const &Joints); + static void conform_switch(TTrack &Track); + static double switch_station(TTrack const &Track, glm::dvec3 const &Position); static std::vector hold_controls(TTrack &Track); static void settle_controls(TTrack &Track, std::vector const &Paths); static std::vector> partners(TTrack &Track, point_ref const &End);