diff --git a/application/editormode.cpp b/application/editormode.cpp index f6a0a472..41c8e925 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1009,7 +1009,6 @@ bool editor_mode::update() // --- ImGuizmo: in-viewport transform gizmo for the selected node --- render_gizmo(); - // --- path editing: course and point handles of the selected path, designed route --- if (selected_track()) draw_track_overlay(); if (route_active()) @@ -2245,7 +2244,6 @@ void editor_mode::render_gizmo() return; } - // route design: the gizmo moves the selected vertex of the designed alignment if (route_active()) { render_route_gizmo(); @@ -2258,7 +2256,6 @@ void editor_mode::render_gizmo() return; } - // paths aren't transformed as a whole node, they're edited through their source data if (selected_track()) { render_track_gizmo(); @@ -2690,7 +2687,6 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in break; case GLFW_KEY_ESCAPE: - // track mode: release the selected point handle, so the gizmo moves the whole path again if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK) { m_track_point = {}; @@ -2786,7 +2782,6 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const return; } - // in track mode the left button selects a point handle of the selected path, or another path if (mode == nodebank_panel::TRACK) { if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_track_handle()) diff --git a/application/editormode.h b/application/editormode.h index 597fecbf..a5bc4e15 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -103,9 +103,8 @@ class editor_mode : public application_mode UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes scene::layer_handle layer{null_handle}; // scenery layer of the node, so a recreated node returns to it scene::instance_handle instance{0}; // include of a scenery template which was placed or removed, instead of a node - // TrackEdit: state of all paths modified by the change (the edited path and neighbours dragged along) std::vector> tracks; - std::vector created; // TrackEdit: paths added by the change + std::vector created; }; void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string()); @@ -277,64 +276,50 @@ class editor_mode : public application_mode gizmo_operation m_gizmo_op{gizmo_operation::translate}; // current transform mode (translate/rotate/scale) float m_gizmo_snap{1.0f}; // translation snap step (metres) applied while Ctrl is held - // path (track) editing, implemented in editormodetrack.cpp TTrack *selected_track() const; - // settings and parameters of the selected path, drawn in the node bank tab of the track mode void render_track_ui(); - // parameters and points of the selected path void render_path_ui(); - // curve and point handles of the selected path void draw_track_overlay() const; - // selects the point handle of the selected path located under the cursor. returns: true if a handle was hit bool pick_track_handle(); - // selects the path picked in the viewport void select_track(scene::basic_node *Node); - // gizmo for the selected path: moves the selected point handle, or the whole path void render_track_gizmo(); - // rebuilds paths modified by the ongoing drag; unless forced, at most a few times per second void commit_track_drag(bool const Force); - // records provided state of paths in the undo history, along with paths added by the change void push_track_snapshot(std::vector> States, std::vector Created = {}); - // restores state of paths stored in the history, moving their current state to the opposite stack void restore_track_snapshot(EditorSnapshot const &Snapshot, std::vector &Opposite, bool const Undo); - editor_track::point_ref m_track_point; // selected point handle of the selected path - std::vector m_track_drag; // paths modified by the ongoing gizmo drag - std::vector> m_track_drag_points; // end points of neighbours dragged along - editor_track::snap_target m_track_snap; // connection made at the end of the ongoing drag - glm::dvec3 m_track_pivot{0.0}; // rotation pivot of the ongoing drag - glm::mat4 m_track_gizmo{1.0f}; // gizmo matrix, kept for the duration of a drag + editor_track::point_ref m_track_point; + std::vector m_track_drag; + std::vector> m_track_drag_points; + editor_track::snap_target m_track_snap; + glm::dvec3 m_track_pivot{0.0}; + glm::mat4 m_track_gizmo{1.0f}; bool m_track_gizmo_using{false}; - bool m_track_dirty{false}; // drag changes waiting for rebuild + bool m_track_dirty{false}; std::chrono::steady_clock::time_point m_track_last_commit; - editor_track::state m_track_field_before; // state of the path before the ongoing field edit - std::array, 3> m_track_materials{}; // material names being edited - // route design: re-lays a chain of paths along an alignment designed with intersection points + editor_track::state m_track_field_before; + std::array, 3> m_track_materials{}; struct route_design { - TTrack *from{nullptr}; // first and last path of the re-laid fragment + TTrack *from{nullptr}; TTrack *to{nullptr}; editor_track::chain chain; std::string error; - std::string status; // outcome of the last application + std::string status; alignment::design design; alignment::result result; - int vertex{-1}; // selected vertex + int vertex{-1}; }; void render_route_ui(); - // finds the chain between the selected fragment ends and proposes an initial design for it void route_reset(); void route_update() { m_route.result = alignment::compute(m_route.design); } void route_apply(); - // sets recommended cant, transitions and vertical curve for the vertex void route_recommend(alignment::vertex &Vertex) const; void draw_route_overlay() const; bool pick_route_vertex(); void render_route_gizmo(); - // 3d position of the vertex handle glm::dvec3 route_vertex_position(int const Vertex) const; bool route_active() const; route_design m_route; - bool m_route_tab{true}; // route design tab of the track mode is shown + bool m_route_tab{true}; bool m_route_gizmo_using{false}; glm::mat4 m_route_gizmo{1.0f}; float m_track_snap_radius{1.0f}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 44fe6649..51882ae8 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -4,10 +4,9 @@ terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +http: */ -// path (track) editing part of the editor mode #include "stdafx.h" #include "application/editormode.h" @@ -29,7 +28,6 @@ http://mozilla.org/MPL/2.0/. namespace { -// projects world points onto the screen, with the same camera-relative view and clean perspective the gizmo uses class screen_projection { public: @@ -51,7 +49,6 @@ class screen_projection { return ImVec2((Clip.x / Clip.w * 0.5f + 0.5f) * m_size.x, (0.5f - Clip.y / Clip.w * 0.5f) * m_size.y); } - // returns: true if the point is in front of the camera bool project(glm::dvec3 const &Point, ImVec2 &Screen) const { auto const c = clip(Point); @@ -60,7 +57,6 @@ class screen_projection Screen = screen(c); return true; } - // draws a line, clipped against the near plane so points behind the camera don't flip across the screen void line(ImDrawList *Drawlist, glm::dvec3 const &A, glm::dvec3 const &B, ImU32 const Color, float const Thickness) const { glm::vec4 a = clip(A); @@ -81,7 +77,6 @@ class screen_projection glm::mat4 m_viewprojection; }; -// point on the course of a path, matching the curve TSegment builds from the same data glm::dvec3 path_point(segment_data const &Path, double const T) { auto const &p0 = Path.points[segment_data::point::start]; @@ -114,10 +109,9 @@ bool is_end(editor_track::point_kind const Kind) editor_track::point_kind const kPointKinds[] = {editor_track::point_kind::start, editor_track::point_kind::control1, editor_track::point_kind::control2, editor_track::point_kind::end}; -// screen distance within which a click grabs a point handle float const kHandleRadius{10.0f}; -} // namespace +} TTrack *editor_mode::selected_track() const { @@ -129,11 +123,9 @@ void editor_mode::select_track(scene::basic_node *Node) auto *track = dynamic_cast(Node); if (track == nullptr) { - // a click beside the selected path releases its point handle m_track_point = {}; return; } - // paths are long, so the distance is measured to the nearest point of the path rather than its centre glm::dvec3 const camera{Global.pCamera.Pos}; if (glm::distance(glm::dvec3(track->get_nearest_point(camera)), camera) > static_cast(kMaxPlacementDistance)) return; @@ -164,7 +156,6 @@ bool editor_mode::pick_track_handle() continue; float const dx = screen.x - mouse.x; float const dy = screen.y - mouse.y; - // end points take precedence over control points placed at the same spot float const distance = (dx * dx + dy * dy) * (is_end(kind) ? 0.5f : 1.0f); if (distance < best) { @@ -189,7 +180,6 @@ void editor_mode::draw_track_overlay() const ImDrawList *drawlist = ImGui::GetBackgroundDrawList(); bool const trackmode = ui()->mode() == nodebank_panel::TRACK; - // course of the path, from its source data so it follows the edits before the path is rebuilt ImU32 const coursecolor = IM_COL32(40, 220, 255, 220); int constexpr samples{32}; for (auto const &path : track->m_paths) @@ -205,14 +195,12 @@ void editor_mode::draw_track_overlay() const if (false == trackmode || m_route_tab || false == editor_track::is_supported(*track)) return; - // control vectors ImU32 const vectorcolor = IM_COL32(200, 200, 200, 160); for (int i = 0; i < static_cast(track->m_paths.size()); ++i) { projection.line(drawlist, editor_track::point_position(*track, {i, editor_track::point_kind::start}), editor_track::point_position(*track, {i, editor_track::point_kind::control1}), vectorcolor, 1.0f); projection.line(drawlist, editor_track::point_position(*track, {i, editor_track::point_kind::end}), editor_track::point_position(*track, {i, editor_track::point_kind::control2}), vectorcolor, 1.0f); } - // handles: end points coloured by connection state (green: connected, red: free), control points as squares for (int i = 0; i < static_cast(track->m_paths.size()); ++i) { for (auto const kind : kPointKinds) @@ -235,7 +223,6 @@ void editor_mode::draw_track_overlay() const drawlist->AddCircle(screen, 10.0f, IM_COL32(255, 220, 40, 255), 16, 2.5f); } } - // connection to be made when the ongoing drag ends if (m_track_snap.track != nullptr) { ImVec2 screen; @@ -261,14 +248,12 @@ void editor_mode::render_track_gizmo() ImGuiIO const &io = ImGui::GetIO(); ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y); - // same camera-relative view and clean perspective as the transform gizmo of the other nodes glm::mat4 const view = GfxRenderer->Camera_View_Matrix(); glm::dvec3 const camerapos = GfxRenderer->Camera_Position(); float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor); float const aspect = io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f; glm::mat4 const projection = glm::perspective(fovy, aspect, 0.1f, 10000.0f); - // point handles only move; the whole path moves or turns around the vertical axis ImGuizmo::OPERATION const operation = (false == pointmode && m_gizmo_op == gizmo_operation::rotate) ? ImGuizmo::ROTATE_Y : ImGuizmo::TRANSLATE; if (false == m_track_gizmo_using) @@ -286,7 +271,6 @@ void editor_mode::render_track_gizmo() { if (false == m_track_gizmo_using) { - // drag start: gather the paths it modifies and record their state for undo m_track_gizmo_using = true; m_track_pivot = editor_track::pivot(*track); m_track_drag = {track}; @@ -326,7 +310,6 @@ void editor_mode::render_track_gizmo() for (auto const &connection : m_track_drag_points) editor_track::move_point(*connection.first, connection.second, position); m_track_dirty = true; - // a free end point can be connected with another path; a joint moved as a whole stays as it is if (is_end(m_track_point.kind) && m_track_drag_points.empty()) { std::vector const exclude(m_track_drag.begin(), m_track_drag.end()); @@ -346,7 +329,6 @@ void editor_mode::render_track_gizmo() } else if (m_track_gizmo_using) { - // drag end: make the offered connection and rebuild everything for good if (m_track_snap.track != nullptr && pointmode) { editor_track::snap_point(*track, m_track_point, m_track_snap, m_track_align_tangent); @@ -364,7 +346,6 @@ void editor_mode::commit_track_drag(bool const Force) { if (false == m_track_dirty || m_track_drag.empty()) return; - // each rebuild re-uploads geometry of the affected sections, so it isn't done on every frame of a drag auto const now = std::chrono::steady_clock::now(); if (false == Force && now - m_track_last_commit < std::chrono::milliseconds(100)) return; @@ -399,11 +380,9 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve if (Snapshot.tracks.empty()) return; - // paths restored by this step; paths added by the change exist only on its redo side std::vector tracks; for (auto const &entry : Snapshot.tracks) tracks.emplace_back(entry.first); - // current state goes to the opposite stack, including paths added by the change when it's being undone EditorSnapshot current{Snapshot}; current.tracks.clear(); auto involved{tracks}; @@ -426,7 +405,6 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve for (auto const &entry : Snapshot.tracks) editor_track::apply(*entry.first, entry.second); editor_track::commit(tracks); - // the designed route refers to the chain, which might have changed if (false == m_route.chain.tracks.empty()) { std::string error; @@ -486,7 +464,6 @@ void editor_mode::render_path_ui() ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "Geometry locked: %s", reason.c_str()); auto const before = editor_track::capture(*track); - // records undo state when a field edit starts, and rebuilds the path once it's finished enum class rebuild { none, parameters, geometry }; auto const finish_edit = [&](rebuild const Rebuild) { if (ImGui::IsItemActivated()) @@ -502,7 +479,6 @@ void editor_mode::render_path_ui() } } }; - // applies a change made with a widget which reports it at once, like a combo box auto const apply_now = [&](rebuild const Rebuild) { push_track_snapshot({{track, before}}); if (Rebuild == rebuild::parameters) @@ -545,7 +521,6 @@ void editor_mode::render_path_ui() if (track->m_visible) { - // material names are edited in local buffers, refreshed from the path whenever no field is being edited material_handle *materials[] = {&track->m_material1, &track->m_material2, track->SwitchExtension ? &track->SwitchExtension->m_material3 : nullptr}; char const *labels[] = {"Texture 1 (rails/surface)", "Texture 2 (trackbed/side)", "Trackbed (switch)"}; if (false == ImGui::IsAnyItemActive()) @@ -602,7 +577,6 @@ void editor_mode::render_path_ui() ImGui::PushID(i); if (track->m_paths.size() > 1) ImGui::Text("Path %d", i + 1); - // point selection, same as clicking the handle in the viewport for (auto const kind : kPointKinds) { editor_track::point_ref const point{i, kind}; @@ -623,7 +597,7 @@ void editor_mode::render_path_ui() { auto position = path.points[kind == editor_track::point_kind::start ? segment_data::point::start : segment_data::point::end]; if (ImGui::InputScalarN(point_label(kind), ImGuiDataType_Double, glm::value_ptr(position), 3, nullptr, nullptr, "%.3f")) - editor_track::move_point(*track, {i, kind}, position); // keeps the common start of a switch together + editor_track::move_point(*track, {i, kind}, position); finish_edit(rebuild::geometry); } ImGui::InputScalarN("Control 1 (relative)", ImGuiDataType_Double, glm::value_ptr(path.points[segment_data::point::control1]), 3, nullptr, nullptr, "%.3f"); @@ -655,7 +629,6 @@ void editor_mode::render_path_ui() } } -// route design bool editor_mode::route_active() const { @@ -686,7 +659,6 @@ void editor_mode::route_reset() auto const &chain{route.chain}; alignment::design design; - // limits and the curve type are the user's choice, they carry over to the next fragment design.norms = route.design.norms; design.shape = route.design.shape; design.start = chain.start; @@ -702,7 +674,6 @@ void editor_mode::route_reset() design.speed = chain.velocity > 0.0 ? chain.velocity : 100.0; route.design = design; - // initial layout: a straight, a single curve at the intersection of the end tangents, or a pair of curves glm::dvec2 intersection; if (false == alignment::collinear(design)) { @@ -730,7 +701,6 @@ void editor_mode::route_apply() route.status.clear(); if (false == route.result.valid || route.chain.tracks.empty()) return; - // the paths might have changed since the fragment was selected editor_track::chain chain; if (false == editor_track::find_chain(route.from, route.to, chain, route.error)) return; @@ -829,7 +799,6 @@ void editor_mode::draw_route_overlay() const screen_projection const projection; ImDrawList *drawlist = ImGui::GetBackgroundDrawList(); - // polygon of tangents std::vector polygon{design.start}; for (int i = 0; i < static_cast(design.vertices.size()); ++i) polygon.push_back(route_vertex_position(i)); @@ -837,7 +806,6 @@ void editor_mode::draw_route_overlay() const for (std::size_t i = 0; i + 1 < polygon.size(); ++i) projection.line(drawlist, polygon[i], polygon[i + 1], IM_COL32(255, 210, 60, 150), 1.5f); - // designed alignment, coloured by element: straight white, transition orange, circular curve green if (result.length > 0.0 && result.profile.size() >= 2) { for (auto const &e : result.elements) @@ -857,7 +825,6 @@ void editor_mode::draw_route_overlay() const } } - // fixed ends with their directions, and the vertices for (auto const atend : {false, true}) { auto const &point{atend ? design.end : design.start}; @@ -918,7 +885,6 @@ void editor_mode::render_route_gizmo() glm::dvec3 const moved{camerapos + glm::dvec3(m_route_gizmo[3])}; auto &vertex{route.design.vertices[route.vertex]}; glm::dvec2 const point{moved.x, moved.z}; - // the first and the last vertex slide along the tangents of the fixed ends; a single vertex stays at their intersection if (count >= 2) { if (route.vertex == 0) @@ -1010,7 +976,6 @@ void editor_mode::render_route_ui() ImGui::TreePop(); } - // vertices auto &vertices{design.vertices}; if (ImGui::Button("Add vertex")) { @@ -1020,7 +985,6 @@ void editor_mode::render_route_ui() auto const distance{glm::distance(glm::dvec2{design.start.x, design.start.z}, glm::dvec2{design.end.x, design.end.z})}; if (vertices.size() == 1) { - // a single vertex sits at the intersection of the tangents; two vertices slide along them vertices.front().offset = distance / 3.0; vertex.offset = distance / 3.0; vertices.push_back(vertex); @@ -1031,12 +995,10 @@ void editor_mode::render_route_ui() } else { - // new vertex between the selected one (or the last but one) and the next auto const index{std::clamp(route.vertex, 0, static_cast(vertices.size()) - 2)}; auto const a{route_vertex_position(index)}; auto const b{route_vertex_position(index + 1)}; vertex.position = glm::dvec2{(a.x + b.x) * 0.5, (a.z + b.z) * 0.5}; - // the moved vertices keep their place, so the former first/last vertex positions become free positions for (int i = 0; i < static_cast(vertices.size()); ++i) { auto const p{route_vertex_position(i)}; @@ -1113,7 +1075,6 @@ void editor_mode::render_route_ui() if (changed) route_update(); - // outcome ImGui::Separator(); auto const &result{route.result}; if (result.length > 0.0) diff --git a/application/editoruilayer.h b/application/editoruilayer.h index 28773754..f4b3c971 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -50,7 +50,6 @@ class editor_ui : public ui_layer { m_filloptions = std::move(Renderer); } - // draws the path editing settings in its node bank tab (the editing state lives in the editor mode) void set_track_options(std::function Renderer) { m_trackoptions = std::move(Renderer); diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 82ad834a..7cdeb328 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -4,7 +4,7 @@ terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +http: */ #include "stdafx.h" @@ -30,7 +30,6 @@ double cross(glm::dvec2 const &A, glm::dvec2 const &B) return A.x * B.y - A.y * B.x; } -// vector rotated by 90 degrees counter-clockwise glm::dvec2 perpendicular(glm::dvec2 const &Vector) { return {-Vector.y, Vector.x}; @@ -63,15 +62,13 @@ std::string format(char const *Format, ...) return buffer; } -// point of a transition curve in its local frame: x along the tangent at its start, y towards the curve centre struct local_point { double x{0.0}; double y{0.0}; - double angle{0.0}; // tangent angle relative to the initial tangent + double angle{0.0}; }; -// Parameter is the fraction of the transition length, from the straight (0) to the circular curve (1) local_point transition_point(transition_shape const Shape, double const Length, double const Radius, double const Parameter) { if (Length <= 0.0 || Radius <= 0.0) @@ -81,7 +78,6 @@ local_point transition_point(transition_shape const Shape, double const Length, auto const x{Length * Parameter}; return {x, x * x * x / (6.0 * Radius * Length), std::atan(x * x / (2.0 * Radius * Length))}; } - // clothoid, tangent angle grows with the square of the travelled distance auto const distance{Length * Parameter}; auto const angle = [&](double const S) { return S * S / (2.0 * Radius * Length); }; int const steps{std::max(8, static_cast(std::ceil(distance / 0.5)) * 2)}; @@ -97,7 +93,6 @@ local_point transition_point(transition_shape const Shape, double const Length, return {x * h / 3.0, y * h / 3.0, angle(distance)}; } -// plan position and direction of travel at given distance from the start of the element void element_point(element const &Element, transition_shape const Shape, double const Distance, glm::dvec2 &Position, glm::dvec2 &Direction) { switch (Element.kind) @@ -147,7 +142,6 @@ element const &element_at(result const &Result, double const Chainage) return lookup == Result.elements.begin() ? Result.elements.front() : *std::prev(lookup); } -// polar form of a cubic Bezier curve; control points of the part between parameters a and b are f(a,a,a), f(a,a,b), f(a,b,b), f(b,b,b) glm::dvec2 blossom(std::array const &Points, double const A, double const B, double const C) { glm::dvec2 r[3], s[2]; @@ -158,7 +152,6 @@ glm::dvec2 blossom(std::array const &Points, double const A, doub return glm::mix(s[0], s[1], C); } -// number of cubic pieces needed to represent the element accurately std::size_t element_pieces(element const &Element, transition_shape const Shape) { if (Element.length < 1e-3) @@ -166,7 +159,6 @@ std::size_t element_pieces(element const &Element, transition_shape const Shape) switch (Element.kind) { case element_kind::arc: - // cubic approximation of a 10 degree arc deviates from the circle by less than 1e-7 of its radius return static_cast(std::max(1.0, std::ceil(Element.length / Element.radius / (10.0 * kPi / 180.0) - 1e-9))); case element_kind::transition_in: case element_kind::transition_out: @@ -176,7 +168,7 @@ std::size_t element_pieces(element const &Element, transition_shape const Shape) } } -} // namespace +} double unbalanced_acceleration(double const Speed, double const Radius, double const Cant, limits const &Limits) { @@ -190,11 +182,9 @@ double transition_length(double const Speed, double const Radius, double const C double length{0.0}; if (Cant > 0.0) { - // rate of cant change in time, and the inclination of the cant ramp length = std::max(length, Speed * Cant / (3.6 * Limits.cant_rate)); length = std::max(length, Cant / 1000.0 * Limits.ramp_factor * Speed); } - // rate of change of the unbalanced acceleration auto const unbalanced{std::max(0.0, unbalanced_acceleration(Speed, Radius, Cant, Limits))}; length = std::max(length, unbalanced * Speed / (3.6 * Limits.jerk)); return length > 0.0 ? ceil_to(length, 5.0) : 0.0; @@ -203,7 +193,7 @@ double transition_length(double const Speed, double const Radius, double const C recommendation recommend(double const Speed, double const Radius, limits const &Limits) { recommendation result; - auto const factor{Limits.gauge / (kGravity * 12.96)}; // ~11.8 for standard gauge + auto const factor{Limits.gauge / (kGravity * 12.96)}; auto const allowance{Limits.gauge * Limits.unbalanced / kGravity}; result.radius_min = factor * Speed * Speed / (Limits.cant_max + allowance); result.vertical_radius = std::max(Limits.vertical_min, Limits.vertical_factor * Speed * Speed); @@ -211,7 +201,6 @@ recommendation recommend(double const Speed, double const Radius, limits const & return result; result.cant_equilibrium = factor * Speed * Speed / Radius; result.cant_min = std::max(0.0, result.cant_equilibrium - allowance); - // keep a part of the permissible unbalanced acceleration in reserve, but never go below the required minimum result.cant = std::min(Limits.cant_max, ceil_to(std::max(result.cant_min, result.cant_equilibrium * 2.0 / 3.0), 5.0)); result.transition = transition_length(Speed, Radius, result.cant, Limits); return result; @@ -229,7 +218,6 @@ result compute(design const &Design) auto const enddirection{glm::normalize(Design.end_direction)}; auto const count{Design.vertices.size()}; - // resolve vertex positions; the first and the last vertex lie on the tangents of the fixed ends r.vertices.resize(count); if (count == 0) { @@ -307,7 +295,6 @@ result compute(design const &Design) auto const lead{glm::dot(position - cursor, directionin)}; if (deflection < 1e-7) { - // tangents are collinear, there's no curve to fit if (lead < -1e-4) r.errors.emplace_back(format("Vertex %zu lies inside the curve of the previous vertex", k + 1)); push_straight(cursor, directionin, lead); @@ -332,13 +319,11 @@ result compute(design const &Design) auto const lengthout{std::max(0.0, vertex.transition_out)}; auto const endin{transition_point(Design.shape, lengthin, radius, 1.0)}; auto const endout{transition_point(Design.shape, lengthout, radius, 1.0)}; - // shift of the circular curve towards its centre, and position of the shifted curve start along the tangent auto const shiftin{endin.y - radius * (1.0 - std::cos(endin.angle))}; auto const shiftout{endout.y - radius * (1.0 - std::cos(endout.angle))}; auto const middlein{endin.x - radius * std::sin(endin.angle)}; auto const middleout{endout.x - radius * std::sin(endout.angle)}; - // the centre lies at distances R + shift from both tangents auto const normalin{perpendicular(directionin) * static_cast(side)}; auto const normalout{perpendicular(directionout) * static_cast(side)}; auto const a{radius + shiftin}; @@ -420,7 +405,6 @@ result compute(design const &Design) report.recommended = recommend(Design.speed, radius, Design.norms); r.curves.push_back(report); - // verification against the limits auto const &norms{Design.norms}; if (radius < report.recommended.radius_min - 1e-6) r.warnings.emplace_back(format("Vertex %zu: R %.0f m is below the minimum %.0f m for %.0f km/h", k + 1, radius, report.recommended.radius_min, Design.speed)); @@ -441,7 +425,6 @@ result compute(design const &Design) push_straight(cursor, lastdirection, tail); r.length = chainage; - // straights between curves, measured as travel time if (Design.norms.tangent_min_time > 0.0) { auto const minimum{Design.norms.tangent_min_time * Design.speed / 3.6}; @@ -453,7 +436,6 @@ result compute(design const &Design) } } - // vertical profile: grades between the fixed ends and the vertex elevations, joined with vertical curves r.vertex_chainages = vertexchainage; r.profile.push_back({0.0, Design.start.y, 0.0, 0.0}); for (std::size_t k = 0; k < count; ++k) @@ -463,7 +445,7 @@ result compute(design const &Design) r.vertex_elevations.push_back(elevation); auto const radius{vertex.vertical_radius > 0.0 ? vertex.vertical_radius : std::max(Design.norms.vertical_min, Design.norms.vertical_factor * Design.speed * Design.speed)}; if (vertexchainage[k] <= r.profile.back().chainage + 1e-3) - continue; // vertex without its own place in the profile + continue; r.profile.push_back({vertexchainage[k], elevation, radius, 0.0}); } r.profile.push_back({r.length, Design.end.y, 0.0, 0.0}); @@ -488,7 +470,6 @@ result compute(design const &Design) if (std::abs(last - Design.end_grade) > 0.0005) r.warnings.emplace_back(format("Grade break at the end: %.1f per mille against %.1f of the adjoining track", last * 1000.0, Design.end_grade * 1000.0)); } - // curvature continuity with the adjoining tracks; the designed fragment starts and ends on a tangent if (Design.start_radius > 0.0 && Design.start_radius < 10000.0) r.warnings.emplace_back(format("The adjoining track at the start is curved (R %.0f m), the curvature changes abruptly", Design.start_radius)); if (Design.end_radius > 0.0 && Design.end_radius < 10000.0) @@ -615,7 +596,6 @@ std::vector pieces(result const &Result, design const &Design, std for (std::size_t j = 0; j < count; ++j) spans.push_back({i, e.length * j / count, e.length * (j + 1) / count}); } - // use up the requested number of pieces by dividing the longest ones while (false == spans.empty() && spans.size() < Count) { auto longest = std::max_element(spans.begin(), spans.end(), [](span const &A, span const &B) { return A.to - A.from < B.to - B.from; }); @@ -651,7 +631,6 @@ std::vector pieces(result const &Result, design const &Design, std { if (Design.shape == transition_shape::cubic_parabola) { - // exact: part of the cubic which describes the whole transition std::array const local{glm::dvec2{0.0, 0.0}, glm::dvec2{e.transition / 3.0, 0.0}, glm::dvec2{e.transition * 2.0 / 3.0, 0.0}, glm::dvec2{e.transition, e.transition * e.transition / (6.0 * e.radius)}}; auto const a{e.kind == element_kind::transition_in ? piece.from / e.transition : 1.0 - piece.from / e.transition}; auto const b{e.kind == element_kind::transition_in ? piece.to / e.transition : 1.0 - piece.to / e.transition}; @@ -670,7 +649,6 @@ std::vector pieces(result const &Result, design const &Design, std break; } } - // heights follow the profile, with end slopes matching its grade auto const s0{e.chainage + piece.from}; auto const s3{e.chainage + piece.to}; auto const y0{elevation(Result, s0)}; @@ -693,4 +671,4 @@ std::vector pieces(result const &Result, design const &Design, std return result; } -} // namespace alignment +} diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 4241eb2b..9e5a9bc2 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -4,7 +4,7 @@ terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +http: */ #pragma once @@ -15,80 +15,68 @@ http://mozilla.org/MPL/2.0/. #include "world/Segment.h" -// Railway alignment design: horizontal alignment defined by a polygon of tangents, with a circular curve -// and transition curves fitted at each intersection point (vertex), and a vertical profile made of grades -// joined with parabolic vertical curves. -// -// Plan coordinates are (x, z) of the scene; heights are scene y. Design values follow the usual railway -// conventions: speed in km/h, cant in mm, radii and lengths in metres. All the limits are configurable. namespace alignment { enum class transition_shape { - cubic_parabola, // y = x^3 / (6RL), represented exactly by a single cubic Bezier - clothoid // curvature linear along the length, approximated by cubic pieces + cubic_parabola, + clothoid }; -// permissible values used to recommend and verify the design struct limits { - double gauge{1500.0}; // distance between rail axes, mm - double unbalanced{0.6}; // permissible unbalanced lateral acceleration, m/s2 - double cant_max{150.0}; // maximum cant, mm - double cant_rate{35.0}; // permissible rate of cant change along transition, mm/s - double ramp_factor{10.0}; // cant ramp inclination limited to 1 / (factor * V) - double jerk{0.3}; // permissible rate of change of unbalanced acceleration, m/s3 - double vertical_factor{0.4}; // minimum vertical curve radius = factor * V^2 - double vertical_min{2000.0}; // absolute minimum vertical curve radius, m - double tangent_min_time{0.0}; // minimum length of a straight between curves expressed as travel time, s (0: not checked) + double gauge{1500.0}; + double unbalanced{0.6}; + double cant_max{150.0}; + double cant_rate{35.0}; + double ramp_factor{10.0}; + double jerk{0.3}; + double vertical_factor{0.4}; + double vertical_min{2000.0}; + double tangent_min_time{0.0}; }; -// intersection point of two tangents of the horizontal alignment, with the curve fitted there struct vertex { - glm::dvec2 position{0.0}; // plan position of an unconstrained vertex - double offset{100.0}; // first/last vertex: distance from the fixed end along its tangent + glm::dvec2 position{0.0}; + double offset{100.0}; double radius{1000.0}; double transition_in{0.0}; double transition_out{0.0}; - double cant{0.0}; // mm - bool auto_elevation{true}; // elevation interpolated between the fixed ends - double elevation{0.0}; // height of the profile at the vertex - double vertical_radius{0.0}; // 0: minimum radius from the limits + double cant{0.0}; + bool auto_elevation{true}; + double elevation{0.0}; + double vertical_radius{0.0}; }; struct design { - // fixed ends of the designed fragment, with plan directions pointing along the route glm::dvec3 start{0.0}; glm::dvec3 end{0.0}; glm::dvec2 start_direction{0.0, 1.0}; glm::dvec2 end_direction{0.0, 1.0}; - double start_grade{0.0}; // grade of the adjoining track, for continuity check + double start_grade{0.0}; double end_grade{0.0}; - double start_radius{0.0}; // plan radius of the adjoining track at the joint, 0 for straight + double start_radius{0.0}; double end_radius{0.0}; std::vector vertices; transition_shape shape{transition_shape::cubic_parabola}; - double speed{100.0}; // design speed, km/h + double speed{100.0}; limits norms; }; -// values derived from the design speed for a curve of given radius struct recommendation { - double cant_equilibrium{0.0}; // cant compensating the lateral acceleration fully, mm - double cant_min{0.0}; // smallest cant keeping the unbalanced acceleration within the limit, mm - double cant{0.0}; // proposed cant, mm - double radius_min{0.0}; // smallest radius for the design speed with maximum cant, m - double transition{0.0}; // minimum transition length for the proposed cant, m - double vertical_radius{0.0}; // minimum vertical curve radius, m + double cant_equilibrium{0.0}; + double cant_min{0.0}; + double cant{0.0}; + double radius_min{0.0}; + double transition{0.0}; + double vertical_radius{0.0}; }; recommendation recommend(double const Speed, double const Radius, limits const &Limits); -// minimum transition length for given speed, cant and radius double transition_length(double const Speed, double const Radius, double const Cant, limits const &Limits); -// unbalanced lateral acceleration, m/s2 double unbalanced_acceleration(double const Speed, double const Radius, double const Cant, limits const &Limits); enum class element_kind @@ -99,36 +87,31 @@ enum class element_kind transition_out }; -// single geometric element of the computed alignment struct element { element_kind kind{element_kind::straight}; - int vertex{-1}; // vertex the curve element belongs to - double chainage{0.0}; // distance from the start of the fragment to the start of the element + int vertex{-1}; + double chainage{0.0}; double length{0.0}; - double radius{0.0}; // curve radius (also the final radius of a transition) - double transition{0.0}; // transition length - int turn{0}; // +1/-1 direction of the curve, 0 for straights - double cant{0.0}; // full cant of the curve, mm - // geometric frame: straight - start point and direction; arc - centre, radius vector to its start point - // and direction at the start; transition_in - its start point (TS) with the tangent direction and inner normal; - // transition_out - its end point (ST) with the direction pointing backwards and inner normal + double radius{0.0}; + double transition{0.0}; + int turn{0}; + double cant{0.0}; glm::dvec2 origin{0.0}; glm::dvec2 direction{0.0, 1.0}; glm::dvec2 normal{0.0}; }; -// verification of a single curve against the limits struct curve_report { int vertex{-1}; - double deflection{0.0}; // angle between the tangents, radians + double deflection{0.0}; double arc_length{0.0}; - double tangent_in{0.0}; // distance from the vertex to the start of the curve - double tangent_out{0.0}; // distance from the vertex to the end of the curve - double unbalanced{0.0}; // m/s2 - double cant_rate_in{0.0}, cant_rate_out{0.0}; // mm/s - double jerk_in{0.0}, jerk_out{0.0}; // m/s3 + double tangent_in{0.0}; + double tangent_out{0.0}; + double unbalanced{0.0}; + double cant_rate_in{0.0}, cant_rate_out{0.0}; + double jerk_in{0.0}, jerk_out{0.0}; recommendation recommended; }; @@ -136,8 +119,8 @@ struct profile_point { double chainage{0.0}; double elevation{0.0}; - double radius{0.0}; // vertical curve radius at the grade break - double tangent{0.0}; // vertical curve tangent length + double radius{0.0}; + double tangent{0.0}; }; struct result @@ -147,9 +130,9 @@ struct result double gauge{1500.0}; std::vector errors; std::vector warnings; - std::vector vertices; // resolved plan positions of the vertices - std::vector vertex_chainages; // chainage of the middle of each vertex curve - std::vector vertex_elevations; // profile elevation at each vertex + std::vector vertices; + std::vector vertex_chainages; + std::vector vertex_elevations; std::vector elements; std::vector curves; std::vector profile; @@ -157,12 +140,9 @@ struct result }; result compute(design const &Design); -// intersection of the tangents of the fixed ends, if it lies ahead of both ends bool tangent_intersection(design const &Design, glm::dvec2 &Point); -// checks whether the fixed ends lie on a common straight line bool collinear(design const &Design); -// position, direction of travel (unit, 3d), cant and curvature direction at specified chainage struct station { glm::dvec3 position{0.0}; @@ -171,14 +151,10 @@ struct station int turn{0}; }; station evaluate(result const &Result, double const Chainage); -// profile height and grade at specified chainage double elevation(result const &Result, double const Chainage); double grade(result const &Result, double const Chainage); -// smallest number of cubic pieces representing the alignment with the required accuracy std::size_t minimum_pieces(result const &Result, design const &Design); -// splits the alignment into at least Count cubic pieces (the longest pieces are divided further to reach the count), -// in the order of the route, as path source data with absolute end points and relative control vectors std::vector pieces(result const &Result, design const &Design, std::size_t const Count); -} // namespace alignment +} diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index c4ae7c2a..94bb7b45 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -4,7 +4,7 @@ terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +http: */ #include "stdafx.h" @@ -26,7 +26,6 @@ http://mozilla.org/MPL/2.0/. namespace { -// end points closer than this are treated as the same point double const kSamePoint{0.05}; segment_data::point point_index(editor_track::point_kind const Kind) @@ -56,7 +55,6 @@ void add_unique(std::vector &Tracks, TTrack *Track) Tracks.emplace_back(Track); } -// first derivative of the path curve at its start or end, in the direction from the start to the end glm::dvec3 path_tangent(segment_data const &Path, bool const Atend) { auto const &start{Path.points[segment_data::point::start]}; @@ -68,7 +66,6 @@ glm::dvec3 path_tangent(segment_data const &Path, bool const Atend) return control2 != glm::dvec3{} ? -control2 : end - (start + control1); } -// plan radius of the path curve at specified parameter, 0 if it's straight there double path_radius(segment_data const &Path, double const T) { auto const &p0{Path.points[segment_data::point::start]}; @@ -87,7 +84,6 @@ double path_radius(segment_data const &Path, double const T) return curvature > 1e-7 ? 1.0 / curvature : 0.0; } -// the same path run in the opposite direction segment_data reversed(segment_data const &Path) { segment_data result{Path}; @@ -95,13 +91,10 @@ segment_data reversed(segment_data const &Path) result.points[segment_data::point::end] = Path.points[segment_data::point::start]; result.points[segment_data::point::control1] = Path.points[segment_data::point::control2]; result.points[segment_data::point::control2] = Path.points[segment_data::point::control1]; - // roll follows the direction of the curve, which flips along with the direction of travel result.rolls = {-Path.rolls[1], -Path.rolls[0]}; return result; } -// direction of travel and plan radius at the joint of a chain with an adjoining path. -// Leaving: the chain continues into the adjoining path (its end), otherwise it comes from it (its start) bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius) { if (Neighbour == nullptr) @@ -113,8 +106,6 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool auto const &point{path.points[atend ? segment_data::point::end : segment_data::point::start]}; if (glm::distance(point, Joint) > kSamePoint) continue; - // the tangent runs from the start of the neighbour to its end; it's the direction of travel when - // entering the neighbour at its start, or arriving from it through its end auto const tangent{glm::normalize(path_tangent(path, atend))}; Direction = (Leaving != atend) ? tangent : -tangent; Radius = path_radius(path, atend ? 1.0 : 0.0); @@ -124,7 +115,7 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool return false; } -} // namespace +} editor_track::state editor_track::capture(TTrack const &Track) { @@ -175,7 +166,7 @@ void editor_track::apply(TTrack &Track, state const &State) bool editor_track::is_supported(TTrack const &Track) { if ((Track.iCategoryFlag & 0x80) != 0) - return false; // auto-generated helper path + return false; if (Track.eType == tt_Normal) return Track.Segment != nullptr; if (Track.eType == tt_Switch) @@ -209,7 +200,6 @@ void editor_track::commit(std::vector const &Tracks) if (Tracks.empty()) return; - // paths whose render geometry depends on the modified ones std::vector affected; for (auto *track : Tracks) { @@ -217,27 +207,23 @@ void editor_track::commit(std::vector const &Tracks) for (auto *neighbour : neighbours(*track)) add_unique(affected, neighbour); } - // take the paths out of the scene while their ends still match the old segments... for (auto *track : Tracks) { disconnect(*track); simulation::Region->erase_and_unregister(track); } - // ...rebuild the segments and put the paths back in their new place... for (auto *track : Tracks) { track->init_segments(false); track->update_location(); - track->m_area.radius = -1.f; // forces radius recalculation + track->m_area.radius = -1.f; simulation::Region->insert_and_register(track); } - // ...connect them with whatever they touch now... for (auto *track : Tracks) join(*track); for (auto *track : Tracks) for (auto *neighbour : neighbours(*track)) add_unique(affected, neighbour); - // ...and update the visuals for (auto *track : affected) update_transition(*track); for (auto *track : affected) @@ -249,7 +235,6 @@ void editor_track::commit(std::vector const &Tracks) void editor_track::commit_parameters(TTrack &Track) { Track.fTrackWidth2 = Track.fTrackWidth; - // segments are re-created from unchanged data, but road texture proportions and roll fix depend on parameters Track.init_segments(false); std::vector affected{&Track}; @@ -302,8 +287,6 @@ void editor_track::move_point(TTrack &Track, point_ref const &Point, glm::dvec3 auto &point{path.points[point_index(Point.kind)]}; auto const oldposition{point}; point = Position; - // control vectors are relative to their end points, so they follow them automatically. - // other paths of the same piece sharing this point (the common start of a switch) follow as well for (auto &otherpath : Track.m_paths) { if (&otherpath == &path) @@ -406,7 +389,6 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl auto const excluded = [&](TTrack const *Other) { return std::find(Exclude.begin(), Exclude.end(), Other) != Exclude.end(); }; auto const category{Track.iCategoryFlag & 15}; - // NOTE: copy, the region returns its scratchpad auto const sections{simulation::Region->sections(Position, static_cast(Radius))}; for (auto *section : sections) { @@ -425,7 +407,6 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl auto const distance{glm::distance(point, Position)}; if (distance > Radius || distance >= result.distance) continue; - // skip ends already connected with something which stays in place auto const connections{connected_points(*other, point)}; if (std::any_of(connections.begin(), connections.end(), [&](auto const &Connection) { return false == excluded(Connection.first); })) continue; @@ -466,7 +447,6 @@ void editor_track::snap_point(TTrack &Track, point_ref const &Point, snap_target if (false == Aligntangent) return; - // the path continues the target, so it leaves the shared point in the opposite direction auto &path{Track.m_paths[Point.path]}; auto const &start{path.points[segment_data::point::start]}; auto const &end{path.points[segment_data::point::end]}; @@ -515,7 +495,6 @@ void editor_track::velocity(TTrack &Track, double const Velocity) { if (Track.SwitchExtension && Track.eType == tt_Switch) { - // same rules as TTrack::Switch() Track.SwitchExtension->fVelocity = static_cast(Velocity); if (Velocity <= -2.0) Track.fVelocity = Track.SwitchExtension->CurrentIndex ? -Velocity : -1.0; @@ -594,7 +573,6 @@ void editor_track::join(TTrack &Track) } case tt_Switch: { - // like during scenery load, switches are connected from the side of the regular paths for (int i = 0; i < 2; ++i) { auto const &segment{Track.SwitchExtension->Segments[i]}; @@ -638,7 +616,6 @@ void editor_track::connect(TTrack &Track, bool const Prevside, TTrack *Other, in { if (false == has_switch_paths(*Other)) break; - // same connections as in path_table::InitTracks(), but made without moving the switch blades auto const path{(Endpointid - 2) / 2}; auto const atend{((Endpointid - 2) % 2) != 0}; bind_switch_path(*Other, path); @@ -667,7 +644,6 @@ void editor_track::connect(TTrack &Track, bool const Prevside, TTrack *Other, in void editor_track::update_transition(TTrack &Track) { - // same rule as TTrack::ConnectNextPrev(): transition is drawn by the path whose end 2 meets start of a different path Track.iTrapezoid &= ~2; auto const *next{Track.trNext}; if (next == nullptr || Track.iNextDirection != 0) @@ -692,7 +668,6 @@ void editor_track::bind_switch_path(TTrack &Switch, int const Path) void editor_track::store_switch_path(TTrack &Switch, int const Path) { - // same as TTrack::SetConnections(), without switching back to the base position auto &extension{*Switch.SwitchExtension}; auto const path{Path & 1}; extension.pNexts[path] = Switch.trNext; @@ -701,7 +676,6 @@ void editor_track::store_switch_path(TTrack &Switch, int const Path) extension.iPrevDirection[path] = Switch.iPrevDirection; if (Switch.eType == tt_Switch) { - // switch paths share their start point, so they share the path connected there as well extension.pPrevs[path ^ 1] = Switch.trPrev; extension.iPrevDirection[path ^ 1] = Switch.iPrevDirection; } @@ -727,7 +701,6 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin if (From != To) { bool found{false}; - // try to reach the last path leaving the first one through its end, then through its start for (auto const throughend : {true, false}) { Chain.tracks = {From}; @@ -780,7 +753,6 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin Chain.radius = radius; } - // fixed ends, with directions taken from the adjoining paths when there are any auto const &first{From->m_paths.front()}; auto const firstforward{Chain.forward.front()}; Chain.start = first.points[firstforward ? segment_data::point::start : segment_data::point::end]; @@ -806,7 +778,6 @@ std::vector editor_track::relay(chain const &Chain, std::vector tracks; std::size_t previous{count}; for (std::size_t i = 0; i < Pieces.size(); ++i) @@ -841,7 +812,7 @@ TTrack *editor_track::clone(TTrack const &Template) cParser parser(text.str(), cParser::buffer_TEXT); parser.getTokens(); std::string token; - parser >> token; // node type, consumed before the path is loaded + parser >> token; scene::node_data data; data.type = "track"; @@ -854,7 +825,6 @@ TTrack *editor_track::clone(TTrack const &Template) track->m_rangesquaredmin = Template.m_rangesquaredmin; track->m_rangesquaredmax = Template.m_rangesquaredmax; track->Load(&parser, glm::dvec3{}); - // events stay with the original path only for (auto *events : {&track->m_events0, &track->m_events1, &track->m_events2, &track->m_events0all, &track->m_events1all, &track->m_events2all}) events->clear(); track->m_events = false; @@ -885,6 +855,5 @@ void editor_track::revive(TTrack &Track) void editor_track::rebuild_geometry(TTrack &Track) { auto §ion{simulation::Region->section(Track.location())}; - // sections which weren't displayed yet create geometry of their content on their own Track.rebuild_geometry(section.m_geometrycreated ? section.m_geometrybank : gfx::geometrybank_handle{}); } diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 17ed0247..267c0c03 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -4,7 +4,7 @@ terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +http: */ #pragma once @@ -19,25 +19,18 @@ http://mozilla.org/MPL/2.0/. class TTrack; -// Editing of paths (tracks, roads, rivers) loaded from the scenery. -// -// The source of truth for path geometry is TTrack::m_paths (the same data the scenery export writes). -// Edits modify m_paths and then commit() rebuilds everything derived from it: the segments, placement -// in the scene cells, connections with neighbouring paths and the render geometry. class editor_track { public: - // identifies an editable point of a path enum class point_kind { start, control1, control2, end }; struct point_ref { - int path{-1}; // index into TTrack::m_paths + int path{-1}; point_kind kind{point_kind::start}; bool valid() const { return path >= 0; } bool operator==(point_ref const &Other) const { return path == Other.path && kind == Other.kind; } }; - // snapshot of the editable state of a path, used by the undo history struct state { std::vector paths; @@ -58,85 +51,57 @@ class editor_track float texslope{0.9f}; }; static state capture(TTrack const &Track); - // restores provided state; follow with commit() to rebuild the path static void apply(TTrack &Track, state const &State); - // checks whether geometry of the path can be modified. returns: true if it can, otherwise false and the reason static bool can_edit_geometry(TTrack const &Track, std::string &Reason); - // checks whether the path type is supported by the editor at all static bool is_supported(TTrack const &Track); - // rebuilds provided paths after modification of their source data (m_paths): segments, placement in the region, - // connections with neighbours and render geometry of the paths and all their old and new neighbours static void commit(std::vector const &Tracks); - // rebuilds the path after change of parameters which don't affect its course (width, textures, etc) static void commit_parameters(TTrack &Track); - // world position of specified point. for control points it's the absolute position of the handle; - // a zero (straight) control vector is presented at 1/3 of the chord so it can be grabbed static glm::dvec3 point_position(TTrack const &Track, point_ref const &Point); - // moves specified point to provided world position. moving an end point carries its control vector along. - // switches keep the start points of both their paths together static void move_point(TTrack &Track, point_ref const &Point, glm::dvec3 const &Position); - // moves the whole path by provided offset static void translate(TTrack &Track, glm::dvec3 const &Offset); - // rotates the whole path around vertical axis going through provided pivot point static void rotate_y(TTrack &Track, glm::dvec3 const &Pivot, double const Angle); - // pivot used for rotation of the whole path static glm::dvec3 pivot(TTrack const &Track); - // lists paths currently connected to provided path static std::vector neighbours(TTrack const &Track); - // lists end points of other paths connected to the end point located at provided position, - // as pairs of the neighbour and its matching point static std::vector> connected_points(TTrack const &Track, glm::dvec3 const &Position); - // checks whether end point located at provided position is connected to another path static bool is_connected(TTrack const &Track, point_ref const &Point); - // candidate to connect a dragged end point with struct snap_target { TTrack *track{nullptr}; point_ref point; glm::dvec3 position{0.0}; - glm::dvec3 direction{0.0}; // direction from the point into the target path + glm::dvec3 direction{0.0}; double distance{0.0}; }; - // finds the nearest free end point of another path of the same category as provided path, within specified radius. - // paths listed in Exclude are skipped, and connections to them don't count as occupying an end point static snap_target find_snap_target(TTrack const &Track, glm::dvec3 const &Position, double const Radius, std::vector const &Exclude); - // places the point at the target and optionally aligns the control vector with the target path static void snap_point(TTrack &Track, point_ref const &Point, snap_target const &Target, bool const Aligntangent); - // sequence of regular paths connected end to end struct chain { std::vector tracks; - std::vector forward; // the path runs in the direction of the chain (its start is closer to the chain start) - glm::dvec3 start{0.0}; // outer end of the first path - glm::dvec3 end{0.0}; // outer end of the last path - glm::dvec3 start_direction{0.0}; // direction of travel along the chain at its ends, from the adjoining paths if there are any + std::vector forward; + glm::dvec3 start{0.0}; + glm::dvec3 end{0.0}; + glm::dvec3 start_direction{0.0}; glm::dvec3 end_direction{0.0}; - double start_radius{0.0}; // plan radius of the adjoining paths at the joints, 0 for straight + double start_radius{0.0}; double end_radius{0.0}; double length{0.0}; - double velocity{-1.0}; // highest speed limit of the paths in the chain - double radius{0.0}; // smallest radius of the paths in the chain, 0 if all are straight + double velocity{-1.0}; + double radius{0.0}; }; - // collects regular paths connected between provided paths, both included. returns: true on success static bool find_chain(TTrack *From, TTrack *To, chain &Chain, std::string &Error); - // lays the paths of the chain along provided pieces, in the order of the chain. if there are more pieces than paths, - // additional paths are made as copies of their neighbours. returns: the added paths static std::vector relay(chain const &Chain, std::vector const &Pieces); - // takes a path added by the editor out of the scene, or puts it back static void retire(TTrack &Track); static void revive(TTrack &Track); - // names of the materials used by the path; "none" for missing ones static std::string material_name(material_handle const Material); static material_handle fetch_material(std::string const &Name); - // height of the trackbed edge as written in the scenery (without roll fix adjustment) static float texture_height(TTrack const &Track); static void texture_height(TTrack &Track, float const Height); static double velocity(TTrack const &Track); @@ -144,19 +109,12 @@ class editor_track static void damage(TTrack &Track, int const Damage); private: - // unlinks the path from all its neighbours static void disconnect(TTrack &Track); - // links the path with paths whose free end points match its own free end points static void join(TTrack &Track); - // links specified end of a regular path with matching end of another path static void connect(TTrack &Track, bool const Prevside, TTrack *Other, int const Endpointid); - // recalculates the trackbed transition flag static void update_transition(TTrack &Track); - // sets active connections of a switch to specified path, without moving the blades static void bind_switch_path(TTrack &Switch, int const Path); static void store_switch_path(TTrack &Switch, int const Path); - // rebuilds render geometry of the path in the bank of the section it's placed in static void rebuild_geometry(TTrack &Track); - // makes a new path with the parameters of provided path (without its events) and registers it in the path table static TTrack *clone(TTrack const &Template); }; diff --git a/scene/scene.cpp b/scene/scene.cpp index 7655ace6..e72ab930 100644 --- a/scene/scene.cpp +++ b/scene/scene.cpp @@ -416,14 +416,12 @@ basic_cell::insert( TMemCell *Memorycell ) { // NOTE: memory cells are virtual 'points' hence they don't ever expand cell range } -// removes provided path from the cell void basic_cell::erase( TTrack *Path ) { m_paths.erase( std::remove( std::begin( m_paths ), std::end( m_paths ), Path ), std::end( m_paths ) ); - // drop the path from the animation list, the cell won't update it anymore if( tTrackAnim == Path ) { tTrackAnim = Path->SwitchExtension->pNextAnim; Path->SwitchExtension->pNextAnim = nullptr; @@ -441,7 +439,6 @@ basic_cell::erase( TTrack *Path ) { // TODO: update cell bounding area } -// removes provided path from the lookup directory of the cell void basic_cell::unregister_end( TTrack *Path ) { diff --git a/scene/scene.h b/scene/scene.h index 7b590182..5cad1fcc 100644 --- a/scene/scene.h +++ b/scene/scene.h @@ -145,10 +145,8 @@ public: // registers provided traction piece in the lookup directory of the cell void register_end( TTraction *Traction ); - // removes provided path from the cell void erase( TTrack *Path ); - // removes provided path from the lookup directory of the cell void unregister_end( TTrack *Path ); // removes provided model instance from the cell @@ -314,7 +312,6 @@ public: void register_node( Type_ *Node, glm::dvec3 const &Point ) { cell( Point ).register_end( Node ); } - // removes provided node from the lookup directory of the section enclosing specified point template void unregister_node( Type_ *Node, glm::dvec3 const &Point ) { @@ -446,7 +443,6 @@ public: auto const location{ Node->location() }; if( point_inside( location ) ) { section( location ).erase( Node ); } } - // removes specified node from the region and from the lookup directories of its ends template void erase_and_unregister( Type_ *Node ) { diff --git a/world/Track.cpp b/world/Track.cpp index 3900bd77..3df6aa54 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -687,7 +687,6 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) // to a stale origin and the renderer would draw sleepers in the wrong place). } -// calculates path location from the current segment void TTrack::update_location() { location( ( @@ -697,19 +696,14 @@ void TTrack::update_location() { / 3.0 ); } -// (re)creates path segments from the source path data in m_paths -// Initial: true when called during deserialization; the editor passes false to keep the current switch state void TTrack::init_segments( bool const Initial ) { glm::dvec3 p1, p2, cp1, cp2, p3, p4, cp3, cp4; // dodatkowe punkty potrzebne do skrzyżowań double r1, r2, r3, r4; double segsize = 5.0; // długość odcinka segmentowania - // TSegment::Init() raises the trackbed for the roll fix through MovedUp1(); drop the previous adjustment - // so repeated initialization doesn't accumulate it fTexHeight1 -= fTexHeightOffset; fTexHeightOffset = 0.f; - // the roll flag is re-evaluated below, the transition flag is set again when the path gets connected iTrapezoid = 0; switch (eType) { @@ -1355,9 +1349,6 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const } // wypełnianie tablic VBO -// discards references to current render geometry and generates it anew in specified bank -// NOTE: geometry banks can't release chunks, so the old data stays in the bank, unused -// with null bank the geometry is only discarded, to be created later along with the rest of the owning section void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { Geometry1.clear(); @@ -2358,7 +2349,6 @@ TTrack::export_as_text_( std::ostream &Output ) const { << fTexSlope << ' '; } // path data - // NOTE: default stream precision would round coordinates to centimetres or worse, breaking path connections auto const precision { Output.precision( std::numeric_limits::digits10 ) }; for( auto const &path : m_paths ) { Output diff --git a/world/Track.h b/world/Track.h index 3cadd981..ea974cbd 100644 --- a/world/Track.h +++ b/world/Track.h @@ -185,7 +185,7 @@ public: geometryhandle_sequence Geometry2; // geometry chunks textured with texture 2 std::vector m_paths; // source data for owned paths - bool m_editorremoved { false }; // taken out of the scene by the editor, kept alive for its undo history + bool m_editorremoved { false }; int iterate_stamp = 0; // sleepermodel optional parameter ------------------------------------------------- @@ -280,9 +280,7 @@ public: RouteCount() const { return SwitchExtension != nullptr ? SwitchExtension->iRoads - 1 : 1; } void Load(cParser *parser, glm::dvec3 const &pOrigin); - // (re)creates path segments from the source path data in m_paths void init_segments( bool const Initial ); - // calculates path location from the current segment void update_location(); bool AssignEvents(); bool AssignForcedEvents(basic_event *NewEventPlus, basic_event *NewEventMinus); @@ -306,7 +304,6 @@ public: double ActiveLength(); void create_geometry( gfx::geometrybank_handle const &Bank ); // wypełnianie VBO - // discards current render geometry and generates it anew in specified bank void rebuild_geometry( gfx::geometrybank_handle const &Bank ); void create_map_geometry(std::vector &Bank, const gfx::geometrybank_handle Extra); void get_map_active_paths(map_colored_paths &handles);